Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
8 / 8
CRAP
100.00% covered (success)
100.00%
86 / 86
Util
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
8 / 8
41
100.00% covered (success)
100.00%
86 / 86
 strEscape
100.00% covered (success)
100.00%
1 / 1
6
100.00% covered (success)
100.00%
11 / 11
 nonEmptyStr
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
1 / 1
 nonEmptyStrs
100.00% covered (success)
100.00%
1 / 1
4
100.00% covered (success)
100.00%
6 / 6
 returnDump
n/a
0 / 0
2
n/a
0 / 0
 tag
100.00% covered (success)
100.00%
1 / 1
8
100.00% covered (success)
100.00%
17 / 17
 getAcceptableLanguages
100.00% covered (success)
100.00%
1 / 1
8
100.00% covered (success)
100.00%
18 / 18
 parseExternalLinks
100.00% covered (success)
100.00%
1 / 1
7
100.00% covered (success)
100.00%
16 / 16
 parseWikiLinks
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
10 / 10
 prettyEncodedWikiUrl
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
7 / 7
<?php declare( strict_types = 1 );
/**
 * Static utitlities class.
 *
 * @license MIT
 * @package krinkle/intuition
 */
namespace Krinkle\Intuition;
/**
 * This class contains the static utility functions for the Intuition class.
 */
class Util {
    private static $articlePath;
    /**
     * Escapes a string with one of the known method and returns it
     *
     * @param string|null $str The string to be escaped
     * @param string $escape The name of the escape routine to be used
     *  if this is an unknown method name it will be ignored and 'plain'
     *  will be used instead.
     * @return string The escaped string.
     */
    public static function strEscape( ?string $str, string $escape = 'plain' ) : string {
        switch ( $escape ) {
            case 'html':
            case 'htmlspecialchars':
                $str = htmlspecialchars( $str );
                break;
            case 'htmlentities':
                $str = htmlentities( $str, ENT_QUOTES, 'UTF-8' );
                break;
            case 'plain':
            default:
                $str = (string)$str;
                break;
        }
        return $str;
    }
    /**
     * @param mixed $var
     * @return bool
     */
    public static function nonEmptyStr( $var ) : bool {
        return is_string( $var ) && $var !== '';
    }
    /**
     * Pass one or more arguments which will be checked until one fails
     * If atleast one argument is not a non-empty string, or if no arguments / NULL passed
     * it will return false, otherwise true;
     *
     * @param mixed|mixed[] ...$args
     * @return bool
     */
    public static function nonEmptyStrs( ...$args ) : bool {
        if ( !isset( $args[0] ) ) {
            return false;
        }
        foreach ( $args as $arg ) {
            if ( !self::nonEmptyStr( $arg ) ) {
                return false;
            }
        }
        // If we're still here, all are good
        return true;
    }
    /**
     * A return version of var_dump().
     * Optionally html-escaped and wrapped in a <pre>-tag.
     *
     * @codeCoverageIgnore
     * @deprecated
     * @return string
     */
    public static function returnDump( $var, $html = true ) {
        $dump = null;
        ob_start();
        var_dump( $var );
        $dump = ob_get_contents();
        ob_end_clean();
        if ( $html ) {
            return '<pre>' . htmlspecialchars( $dump ) . '</pre>';
        }
        return $dump;
    }
    /**
     * Primitive html builder
     *
     * Based on similar methods in krinkle/toollabs-base,
     * and Html::element in MediaWiki.
     *
     * @param string|null $str Content (text or HTML)
     * @param string|int $wrapTag Name of HTML Element (tagName)
     * @param array<string,string|int>|null $attributes [optional]
     * @return string HTML
     */
    public static function tag( ?string $str = null, $wrapTag = 0, ?array $attributes = null ) : string {
        $selfclose = [ 'link', 'input', 'br', 'img' ];
        if ( !is_string( $str ) ) {
            return '';
        }
        if ( !is_string( $wrapTag ) ) {
            return $str;
        }
        $wrapTag = trim( strtolower( $wrapTag ) );
        $attrString = '';
        if ( $attributes !== null ) {
            foreach ( $attributes as $attrKey => $attrVal ) {
                $attrKey = htmlspecialchars( trim( strtolower( $attrKey ) ), ENT_QUOTES );
                $attrVal = htmlspecialchars( trim( (string)$attrVal ), ENT_QUOTES );
                $attrString .= " $attrKey=\"$attrVal\"";
            }
        }
        $return = "<$wrapTag$attrString";
        if ( in_array( $wrapTag, $selfclose ) ) {
            $return .= '/>';
        } else {
            $return .= ">" . htmlspecialchars( $str ) . "</$wrapTag>";
        }
        return $return;
    }
    /**
     * Get a list of acceptable languages from an Accept-Language header.
     *
     * @param string|null $rawList List of language tags, formatted like an
     *  HTTP Accept-Language header. Default: Value of `$_SERVER['HTTP_ACCEPT_LANGUAGE']`.
     * @return array keyed by language codes with q-values as values.
     */
    public static function getAcceptableLanguages( ?string $rawList = null ) : array {
        // @codeCoverageIgnoreStart
        if ( $rawList === null ) {
            $rawList = isset( $_SERVER['HTTP_ACCEPT_LANGUAGE'] ) ?
                $_SERVER['HTTP_ACCEPT_LANGUAGE'] :
                '';
        }
        // @codeCoverageIgnoreEnd
        // Implementation based on MediaWiki 1.21's WebRequest::getAcceptLang
        // Which itself was based on http://www.thefutureoftheweb.com/blog/use-accept-language-header
        // Return the language codes in lower case
        $rawList = strtolower( $rawList );
        // The list of elements is separated by comma and optional LWS
        // Extract the language-range and, if present, the q-value
        $lang_parse = null;
        preg_match_all(
            '/([a-z]{1,8}(-[a-z]{1,8})*|\*)\s*(;\s*q\s*=\s*(1(\.0{0,3})?|0(\.[0-9]{0,3})?)?)?/',
            $rawList,
            $lang_parse
        );
        if ( !count( $lang_parse[1] ) ) {
            return [];
        }
        $langcodes = $lang_parse[1];
        $qvalues = $lang_parse[4];
        $indices = range( 0, count( $lang_parse[1] ) - 1 );
        // Set default q factor to 1
        foreach ( $indices as $index ) {
            if ( $qvalues[$index] === '' ) {
                $qvalues[$index] = 1;
            } elseif ( $qvalues[$index] == 0 ) {
                unset( $langcodes[$index], $qvalues[$index], $indices[$index] );
            } else {
                $qvalues[$index] = floatval( $qvalues[$index] );
            }
        }
        // Sort list. First by $qvalues, then by order. Reorder $langcodes the same way
        array_multisort( $qvalues, SORT_DESC, SORT_NUMERIC, $indices, $langcodes );
        // Create a list like "en" => 0.8
        $langs = array_combine( $langcodes, $qvalues );
        return $langs;
    }
    // Matches MediaWiki's Parser class
    private const EXT_LINK_URL_CLASS = '[^][<>"\\x00-\\x20\\x7F\p{Zs}]';
    /**
     * Given a text already html-escaped which contains urls in wiki format,
     * convert it to html.
     *
     * @param string $text
     * @return string HTML
     */
    public static function parseExternalLinks( string $text ) : string {
        static $urlProtocols = false;
        static $counter = 0;
        // @codeCoverageIgnoreStart
        if ( !$urlProtocols ) {
            if ( function_exists( 'wfUrlProtocols' ) ) {
                // Allow custom protocols
                $urlProtocols = wfUrlProtocols();
            } else {
                $urlProtocols = 'https?:\/\/|ftp:\/\/';
            }
        }
        // @codeCoverageIgnoreEnd
        $extLinkBracketedRegex = '/(?:(<[^>]*)|' .
            '\[(((?i)' . $urlProtocols . ')' .
                self::EXT_LINK_URL_CLASS .
                '+)\p{Zs}*([^\]\\x00-\\x08\\x0a-\\x1F]*?)\]|' .
            '(((?i)' . $urlProtocols . ')' .
                self::EXT_LINK_URL_CLASS .
                '+))/Su';
        return preg_replace_callback(
            $extLinkBracketedRegex,
            function ( array $bits ) use ( &$counter ) {
                // @codeCoverageIgnoreStart
                if ( $bits[1] != '' ) {
                    return $bits[1];
                }
                // @codeCoverageIgnoreEnd
                if ( isset( $bits[4] ) && $bits[4] != '' ) {
                    return '<a href="' . $bits[2] . '">' . $bits[4] . '</a>';
                } elseif ( isset( $bits[5] ) ) {
                    return '<a href="' . $bits[5] . '">' . $bits[5] . '</a>';
                } else {
                    return '<a href="' . $bits[2] . '">[' . ++$counter . ']</a>';
                }
            },
            $text
        );
    }
    /**
     * Given a text already html-escaped which contains wiki links, convert them to html.
     *
     * @param string $text
     * @param string $articlePath
     * @return string HTML
     */
    public static function parseWikiLinks( string $text, string $articlePath ) : string {
        self::$articlePath = $articlePath;
        return preg_replace_callback(
            '/\[\[:?([^]|]+)(?:\|([^]]*))?\]\]/',
            function ( array $bits ) {
                if ( !isset( $bits[2] ) || $bits[2] == '' ) {
                    $bits[2] = strtr( $bits[1], '_', ' ' );
                }
                $article = html_entity_decode( $bits[1], ENT_QUOTES, 'UTF-8' );
                return '<a href="' . htmlspecialchars(
                    self::prettyEncodedWikiUrl( self::$articlePath, $article ), ENT_COMPAT, 'UTF-8'
                ) . '">' . $bits[2] . "</a>";
            },
            $text
        );
    }
    /**
     * Encode a page title the way MediaWiki would.
     *
     * This is based on MediaWiki's wfUrlencode().
     *
     * @param string $articlePath
     * @param string $article
     * @return string
     */
    public static function prettyEncodedWikiUrl( string $articlePath, string $article ) : string {
        $s = strtr( $article, ' ', '_' );
        $s = urlencode( $s );
        $s = str_ireplace(
            [ '%3B', '%40', '%24', '%21', '%2A', '%28', '%29', '%2C', '%2F', '%3A' ],
            [ ';', '@', '$', '!', '*', '(', ')', ',', '/', ':' ],
            $s
        );
        $s = str_replace( '$1', $s, $articlePath );
        return $s;
    }
}