vendor/contao/core-bundle/src/Resources/contao/library/Contao/StringUtil.php line 1137

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of Contao.
  4.  *
  5.  * (c) Leo Feyer
  6.  *
  7.  * @license LGPL-3.0-or-later
  8.  */
  9. namespace Contao;
  10. use Contao\CoreBundle\Util\SimpleTokenParser;
  11. use Patchwork\Utf8;
  12. use Webmozart\PathUtil\Path;
  13. /**
  14.  * Provides string manipulation methods
  15.  *
  16.  * Usage:
  17.  *
  18.  *     $short = StringUtil::substr($str, 32);
  19.  *     $html  = StringUtil::substrHtml($str, 32);
  20.  *     $xhtml = StringUtil::toXhtml($html5);
  21.  *
  22.  * @author Leo Feyer <https://github.com/leofeyer>
  23.  */
  24. class StringUtil
  25. {
  26.     /**
  27.      * Shorten a string to a given number of characters
  28.      *
  29.      * The function preserves words, so the result might be a bit shorter or
  30.      * longer than the number of characters given. It strips all tags.
  31.      *
  32.      * @param string  $strString        The string to shorten
  33.      * @param integer $intNumberOfChars The target number of characters
  34.      * @param string  $strEllipsis      An optional ellipsis to append to the shortened string
  35.      *
  36.      * @return string The shortened string
  37.      */
  38.     public static function substr($strString$intNumberOfChars$strEllipsis=' …')
  39.     {
  40.         $strString preg_replace('/[\t\n\r]+/'' '$strString);
  41.         $strString strip_tags($strString);
  42.         if (Utf8::strlen($strString) <= $intNumberOfChars)
  43.         {
  44.             return $strString;
  45.         }
  46.         $intCharCount 0;
  47.         $arrWords = array();
  48.         $arrChunks preg_split('/\s+/'$strString);
  49.         $blnAddEllipsis false;
  50.         foreach ($arrChunks as $strChunk)
  51.         {
  52.             $intCharCount += Utf8::strlen(static::decodeEntities($strChunk));
  53.             if ($intCharCount++ <= $intNumberOfChars)
  54.             {
  55.                 $arrWords[] = $strChunk;
  56.                 continue;
  57.             }
  58.             // If the first word is longer than $intNumberOfChars already, shorten it
  59.             // with Utf8::substr() so the method does not return an empty string.
  60.             if (empty($arrWords))
  61.             {
  62.                 $arrWords[] = Utf8::substr($strChunk0$intNumberOfChars);
  63.             }
  64.             if ($strEllipsis !== false)
  65.             {
  66.                 $blnAddEllipsis true;
  67.             }
  68.             break;
  69.         }
  70.         // Deprecated since Contao 4.0, to be removed in Contao 5.0
  71.         if ($strEllipsis === true)
  72.         {
  73.             trigger_deprecation('contao/core-bundle''4.0''Passing "true" as third argument to "Contao\StringUtil::substr()" has been deprecated and will no longer work in Contao 5.0. Pass the ellipsis string instead.');
  74.             $strEllipsis ' …';
  75.         }
  76.         return implode(' '$arrWords) . ($blnAddEllipsis $strEllipsis '');
  77.     }
  78.     /**
  79.      * Shorten a HTML string to a given number of characters
  80.      *
  81.      * The function preserves words, so the result might be a bit shorter or
  82.      * longer than the number of characters given. It preserves allowed tags.
  83.      *
  84.      * @param string  $strString        The string to shorten
  85.      * @param integer $intNumberOfChars The target number of characters
  86.      *
  87.      * @return string The shortened HTML string
  88.      */
  89.     public static function substrHtml($strString$intNumberOfChars)
  90.     {
  91.         $strReturn '';
  92.         $intCharCount 0;
  93.         $arrOpenTags = array();
  94.         $arrTagBuffer = array();
  95.         $arrEmptyTags = array('area''base''br''col''embed''hr''img''input''link''meta''param''source''track''wbr');
  96.         $strString preg_replace('/[\t\n\r]+/'' '$strString);
  97.         $strString strip_tags($strStringConfig::get('allowedTags'));
  98.         $strString preg_replace('/ +/'' '$strString);
  99.         // Seperate tags and text
  100.         $arrChunks preg_split('/(<[^>]+>)/'$strString, -1PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY);
  101.         for ($i=0$c=\count($arrChunks); $i<$c$i++)
  102.         {
  103.             // Buffer tags to include them later
  104.             if (preg_match('/<([^>]+)>/'$arrChunks[$i]))
  105.             {
  106.                 $arrTagBuffer[] = $arrChunks[$i];
  107.                 continue;
  108.             }
  109.             $buffer $arrChunks[$i];
  110.             // Get the substring of the current text
  111.             if (!$arrChunks[$i] = static::substr($arrChunks[$i], ($intNumberOfChars $intCharCount), false))
  112.             {
  113.                 break;
  114.             }
  115.             $blnModified = ($buffer !== $arrChunks[$i]);
  116.             $intCharCount += Utf8::strlen(static::decodeEntities($arrChunks[$i]));
  117.             if ($intCharCount <= $intNumberOfChars)
  118.             {
  119.                 foreach ($arrTagBuffer as $strTag)
  120.                 {
  121.                     $strTagName strtolower(trim($strTag));
  122.                     // Extract the tag name (see #5669)
  123.                     if (($pos strpos($strTagName' ')) !== false)
  124.                     {
  125.                         $strTagName substr($strTagName1$pos 1);
  126.                     }
  127.                     else
  128.                     {
  129.                         $strTagName substr($strTagName1, -1);
  130.                     }
  131.                     // Skip empty tags
  132.                     if (\in_array($strTagName$arrEmptyTags))
  133.                     {
  134.                         continue;
  135.                     }
  136.                     // Store opening tags in the open_tags array
  137.                     if (strncmp($strTagName'/'1) !== 0)
  138.                     {
  139.                         if ($i<$c || !empty($arrChunks[$i]))
  140.                         {
  141.                             $arrOpenTags[] = $strTagName;
  142.                         }
  143.                         continue;
  144.                     }
  145.                     // Closing tags will be removed from the "open tags" array
  146.                     if ($i<$c || !empty($arrChunks[$i]))
  147.                     {
  148.                         $arrOpenTags array_values($arrOpenTags);
  149.                         for ($j=\count($arrOpenTags)-1$j>=0$j--)
  150.                         {
  151.                             if ($strTagName == '/' $arrOpenTags[$j])
  152.                             {
  153.                                 unset($arrOpenTags[$j]);
  154.                                 break;
  155.                             }
  156.                         }
  157.                     }
  158.                 }
  159.                 // If the current chunk contains text, add tags and text to the return string
  160.                 if ($i<$c || \strlen($arrChunks[$i]))
  161.                 {
  162.                     $strReturn .= implode(''$arrTagBuffer) . $arrChunks[$i];
  163.                 }
  164.                 // Stop after the first shortened chunk (see #7311)
  165.                 if ($blnModified)
  166.                 {
  167.                     break;
  168.                 }
  169.                 $arrTagBuffer = array();
  170.                 continue;
  171.             }
  172.             break;
  173.         }
  174.         // Close all remaining open tags
  175.         krsort($arrOpenTags);
  176.         foreach ($arrOpenTags as $strTag)
  177.         {
  178.             $strReturn .= '</' $strTag '>';
  179.         }
  180.         return trim($strReturn);
  181.     }
  182.     /**
  183.      * Decode all entities
  184.      *
  185.      * @param mixed   $strString     The string to decode
  186.      * @param integer $strQuoteStyle The quote style (defaults to ENT_QUOTES)
  187.      * @param string  $strCharset    An optional charset
  188.      *
  189.      * @return string The decoded string
  190.      */
  191.     public static function decodeEntities($strString$strQuoteStyle=ENT_QUOTES$strCharset=null)
  192.     {
  193.         if ((string) $strString === '')
  194.         {
  195.             return '';
  196.         }
  197.         if ($strCharset === null)
  198.         {
  199.             $strCharset 'UTF-8';
  200.         }
  201.         $strString preg_replace('/(&#*\w+)[\x00-\x20]+;/i''$1;'$strString);
  202.         $strString preg_replace('/(&#x*)([0-9a-f]+);/i''$1$2;'$strString);
  203.         return html_entity_decode($strString$strQuoteStyle$strCharset);
  204.     }
  205.     /**
  206.      * Restore basic entities
  207.      *
  208.      * @param string $strBuffer The string with the tags to be replaced
  209.      *
  210.      * @return string The string with the original entities
  211.      */
  212.     public static function restoreBasicEntities($strBuffer)
  213.     {
  214.         return str_replace(array('[&]''[&amp;]''[lt]''[gt]''[nbsp]''[-]'), array('&amp;''&amp;''&lt;''&gt;''&nbsp;''&shy;'), $strBuffer);
  215.     }
  216.     /**
  217.      * Generate an alias from a string
  218.      *
  219.      * @param string $strString The string
  220.      *
  221.      * @return string The alias
  222.      */
  223.     public static function generateAlias($strString)
  224.     {
  225.         $strString = static::decodeEntities($strString);
  226.         $strString = static::restoreBasicEntities($strString);
  227.         $strString = static::standardize(strip_tags($strString));
  228.         // Remove the prefix if the alias is not numeric (see #707)
  229.         if (strncmp($strString'id-'3) === && !is_numeric($strSubstr substr($strString3)))
  230.         {
  231.             $strString $strSubstr;
  232.         }
  233.         return $strString;
  234.     }
  235.     /**
  236.      * Prepare a slug
  237.      *
  238.      * @param string $strSlug The slug
  239.      *
  240.      * @return string
  241.      */
  242.     public static function prepareSlug($strSlug)
  243.     {
  244.         $strSlug = static::stripInsertTags($strSlug);
  245.         $strSlug = static::restoreBasicEntities($strSlug);
  246.         $strSlug = static::decodeEntities($strSlug);
  247.         return $strSlug;
  248.     }
  249.     /**
  250.      * Censor a single word or an array of words within a string
  251.      *
  252.      * @param string $strString  The string to censor
  253.      * @param mixed  $varWords   A string or array or words to replace
  254.      * @param string $strReplace An optional replacement string
  255.      *
  256.      * @return string The cleaned string
  257.      */
  258.     public static function censor($strString$varWords$strReplace='')
  259.     {
  260.         foreach ((array) $varWords as $strWord)
  261.         {
  262.             $strString preg_replace('/\b(' str_replace('\*''\w*?'preg_quote($strWord'/')) . ')\b/i'$strReplace$strString);
  263.         }
  264.         return $strString;
  265.     }
  266.     /**
  267.      * Encode all e-mail addresses within a string
  268.      *
  269.      * @param string $strString The string to encode
  270.      *
  271.      * @return string The encoded string
  272.      */
  273.     public static function encodeEmail($strString)
  274.     {
  275.         if (strpos($strString'@') === false)
  276.         {
  277.             return $strString;
  278.         }
  279.         $arrEmails = static::extractEmail($strStringConfig::get('allowedTags'));
  280.         foreach ($arrEmails as $strEmail)
  281.         {
  282.             $strEncoded '';
  283.             $arrCharacters Utf8::str_split($strEmail);
  284.             foreach ($arrCharacters as $index => $strCharacter)
  285.             {
  286.                 $strEncoded .= sprintf(($index 2) ? '&#x%X;' '&#%s;'Utf8::ord($strCharacter));
  287.             }
  288.             $strString str_replace($strEmail$strEncoded$strString);
  289.         }
  290.         return str_replace('mailto:''&#109;&#97;&#105;&#108;&#116;&#111;&#58;'$strString);
  291.     }
  292.     /**
  293.      * Extract all e-mail addresses from a string
  294.      *
  295.      * @param string $strString      The string
  296.      * @param string $strAllowedTags A list of allowed HTML tags
  297.      *
  298.      * @return array The e-mail addresses
  299.      */
  300.     public static function extractEmail($strString$strAllowedTags='')
  301.     {
  302.         $arrEmails = array();
  303.         if (strpos($strString'@') === false)
  304.         {
  305.             return $arrEmails;
  306.         }
  307.         // Find all mailto: addresses
  308.         preg_match_all('/mailto:(?:[^\x00-\x20\x22\x40\x7F]{1,64}+|\x22[^\x00-\x1F\x7F]{1,64}?\x22)@(?:\[(?:IPv)?[a-f0-9.:]{1,47}]|[\w.-]{1,252}\.[a-z]{2,63}\b)/u'$strString$matches);
  309.         foreach ($matches[0] as &$strEmail)
  310.         {
  311.             $strEmail str_replace('mailto:'''$strEmail);
  312.             if (Validator::isEmail($strEmail))
  313.             {
  314.                 $arrEmails[] = $strEmail;
  315.             }
  316.         }
  317.         unset($strEmail);
  318.         // Encode opening arrow brackets (see #3998)
  319.         $strString preg_replace_callback('@</?([^\s<>/]*)@', static function ($matches) use ($strAllowedTags)
  320.         {
  321.             if (!$matches[1] || stripos($strAllowedTags'<' strtolower($matches[1]) . '>') === false)
  322.             {
  323.                 $matches[0] = str_replace('<''&lt;'$matches[0]);
  324.             }
  325.             return $matches[0];
  326.         }, $strString);
  327.         // Find all addresses in the plain text
  328.         preg_match_all('/(?:[^\x00-\x20\x22\x40\x7F]{1,64}|\x22[^\x00-\x1F\x7F]{1,64}?\x22)@(?:\[(?:IPv)?[a-f0-9.:]{1,47}]|[\w.-]{1,252}\.[a-z]{2,63}\b)/u'strip_tags($strString), $matches);
  329.         foreach ($matches[0] as &$strEmail)
  330.         {
  331.             $strEmail str_replace('&lt;''<'$strEmail);
  332.             if (Validator::isEmail($strEmail))
  333.             {
  334.                 $arrEmails[] = $strEmail;
  335.             }
  336.         }
  337.         return array_unique($arrEmails);
  338.     }
  339.     /**
  340.      * Split a friendly-name e-mail address and return name and e-mail as array
  341.      *
  342.      * @param string $strEmail A friendly-name e-mail address
  343.      *
  344.      * @return array An array with name and e-mail address
  345.      */
  346.     public static function splitFriendlyEmail($strEmail)
  347.     {
  348.         if (strpos($strEmail'<') !== false)
  349.         {
  350.             return array_map('trim'explode(' <'str_replace('>'''$strEmail)));
  351.         }
  352.         if (strpos($strEmail'[') !== false)
  353.         {
  354.             return array_map('trim'explode(' ['str_replace(']'''$strEmail)));
  355.         }
  356.         return array(''$strEmail);
  357.     }
  358.     /**
  359.      * Wrap words after a particular number of characers
  360.      *
  361.      * @param string  $strString The string to wrap
  362.      * @param integer $strLength The number of characters to wrap after
  363.      * @param string  $strBreak  An optional break character
  364.      *
  365.      * @return string The wrapped string
  366.      */
  367.     public static function wordWrap($strString$strLength=75$strBreak="\n")
  368.     {
  369.         return wordwrap($strString$strLength$strBreak);
  370.     }
  371.     /**
  372.      * Highlight a phrase within a string
  373.      *
  374.      * @param string $strString     The string
  375.      * @param string $strPhrase     The phrase to highlight
  376.      * @param string $strOpeningTag The opening tag (defaults to <strong>)
  377.      * @param string $strClosingTag The closing tag (defaults to </strong>)
  378.      *
  379.      * @return string The highlighted string
  380.      */
  381.     public static function highlight($strString$strPhrase$strOpeningTag='<strong>'$strClosingTag='</strong>')
  382.     {
  383.         if (!$strString || !$strPhrase)
  384.         {
  385.             return $strString;
  386.         }
  387.         return preg_replace('/(' preg_quote($strPhrase'/') . ')/i'$strOpeningTag '\\1' $strClosingTag$strString);
  388.     }
  389.     /**
  390.      * Split a string of comma separated values
  391.      *
  392.      * @param string $strString    The string to split
  393.      * @param string $strDelimiter An optional delimiter
  394.      *
  395.      * @return array The string chunks
  396.      */
  397.     public static function splitCsv($strString$strDelimiter=',')
  398.     {
  399.         $arrValues preg_split('/' $strDelimiter '(?=(?:[^"]*"[^"]*")*(?![^"]*"))/'$strString);
  400.         foreach ($arrValues as $k=>$v)
  401.         {
  402.             $arrValues[$k] = trim($v' "');
  403.         }
  404.         return $arrValues;
  405.     }
  406.     /**
  407.      * Convert a string to XHTML
  408.      *
  409.      * @param string $strString The HTML5 string
  410.      *
  411.      * @return string The XHTML string
  412.      */
  413.     public static function toXhtml($strString)
  414.     {
  415.         $arrPregReplace = array
  416.         (
  417.             '/<(br|hr|img)([^>]*)>/i' => '<$1$2 />'// Close stand-alone tags
  418.             '/ border="[^"]*"/'       => ''          // Remove deprecated attributes
  419.         );
  420.         $arrStrReplace = array
  421.         (
  422.             '/ />'             => ' />',        // Fix incorrectly closed tags
  423.             '<b>'              => '<strong>',   // Replace <b> with <strong>
  424.             '</b>'             => '</strong>',
  425.             '<i>'              => '<em>',       // Replace <i> with <em>
  426.             '</i>'             => '</em>',
  427.             '<u>'              => '<span style="text-decoration:underline">',
  428.             '</u>'             => '</span>',
  429.             ' target="_self"'  => '',
  430.             ' target="_blank"' => ' onclick="return !window.open(this.href)"'
  431.         );
  432.         $strString preg_replace(array_keys($arrPregReplace), $arrPregReplace$strString);
  433.         $strString str_ireplace(array_keys($arrStrReplace), $arrStrReplace$strString);
  434.         return $strString;
  435.     }
  436.     /**
  437.      * Convert a string to HTML5
  438.      *
  439.      * @param string $strString The XHTML string
  440.      *
  441.      * @return string The HTML5 string
  442.      */
  443.     public static function toHtml5($strString)
  444.     {
  445.         $arrPregReplace = array
  446.         (
  447.             '/<(br|hr|img)([^>]*) \/>/i'                  => '<$1$2>',             // Close stand-alone tags
  448.             '/ (cellpadding|cellspacing|border)="[^"]*"/' => '',                   // Remove deprecated attributes
  449.             '/ rel="lightbox(\[([^\]]+)\])?"/'            => ' data-lightbox="$2"' // see #4073
  450.         );
  451.         $arrStrReplace = array
  452.         (
  453.             '<u>'                                              => '<span style="text-decoration:underline">',
  454.             '</u>'                                             => '</span>',
  455.             ' target="_self"'                                  => '',
  456.             ' onclick="window.open(this.href); return false"'  => ' target="_blank"',
  457.             ' onclick="window.open(this.href);return false"'   => ' target="_blank"',
  458.             ' onclick="window.open(this.href); return false;"' => ' target="_blank"'
  459.         );
  460.         $strString preg_replace(array_keys($arrPregReplace), $arrPregReplace$strString);
  461.         $strString str_ireplace(array_keys($arrStrReplace), $arrStrReplace$strString);
  462.         return $strString;
  463.     }
  464.     /**
  465.      * Parse simple tokens
  466.      *
  467.      * @param string $strString    The string to be parsed
  468.      * @param array  $arrData      The replacement data
  469.      * @param array  $blnAllowHtml Whether HTML should be decoded inside conditions
  470.      *
  471.      * @return string The converted string
  472.      *
  473.      * @throws \RuntimeException         If $strString cannot be parsed
  474.      * @throws \InvalidArgumentException If there are incorrectly formatted if-tags
  475.      *
  476.      * @deprecated Deprecated since Contao 4.10, to be removed in Contao 5.
  477.      *             Use the SimpleTokenParser::class service instead.
  478.      */
  479.     public static function parseSimpleTokens($strString$arrData$blnAllowHtml true)
  480.     {
  481.         trigger_deprecation('contao/core-bundle''4.10''Using "Contao\StringUtil::parseSimpleTokens()" has been deprecated and will no longer work in Contao 5.0. Use the "SimpleTokenParser::class" service instead.');
  482.         return System::getContainer()->get(SimpleTokenParser::class)->parse($strString$arrData$blnAllowHtml);
  483.     }
  484.     /**
  485.      * Convert a UUID string to binary data
  486.      *
  487.      * @param string $uuid The UUID string
  488.      *
  489.      * @return string The binary data
  490.      */
  491.     public static function uuidToBin($uuid)
  492.     {
  493.         return hex2bin(str_replace('-'''$uuid));
  494.     }
  495.     /**
  496.      * Get a UUID string from binary data
  497.      *
  498.      * @param string $data The binary data
  499.      *
  500.      * @return string The UUID string
  501.      */
  502.     public static function binToUuid($data)
  503.     {
  504.         return implode('-'unpack('H8time_low/H4time_mid/H4time_high/H4clock_seq/H12node'$data));
  505.     }
  506.     /**
  507.      * Convert file paths inside "src" attributes to insert tags
  508.      *
  509.      * @param string $data The markup string
  510.      *
  511.      * @return string The markup with file paths converted to insert tags
  512.      */
  513.     public static function srcToInsertTag($data)
  514.     {
  515.         $return '';
  516.         $paths preg_split('/((src|href)="([^"]+)")/i'$data, -1PREG_SPLIT_DELIM_CAPTURE);
  517.         for ($i=0$c=\count($paths); $i<$c$i+=4)
  518.         {
  519.             $return .= $paths[$i];
  520.             if (!isset($paths[$i+1]))
  521.             {
  522.                 continue;
  523.             }
  524.             $file FilesModel::findByPath($paths[$i+3]);
  525.             if ($file !== null)
  526.             {
  527.                 $return .= $paths[$i+2] . '="{{file::' . static::binToUuid($file->uuid) . '}}"';
  528.             }
  529.             else
  530.             {
  531.                 $return .= $paths[$i+2] . '="' $paths[$i+3] . '"';
  532.             }
  533.         }
  534.         return $return;
  535.     }
  536.     /**
  537.      * Convert insert tags inside "src" attributes to file paths
  538.      *
  539.      * @param string $data The markup string
  540.      *
  541.      * @return string The markup with insert tags converted to file paths
  542.      */
  543.     public static function insertTagToSrc($data)
  544.     {
  545.         $return '';
  546.         $paths preg_split('/((src|href)="([^"]*){{file::([^"}|]+)[^"}]*}}")/i'$data, -1PREG_SPLIT_DELIM_CAPTURE);
  547.         for ($i=0$c=\count($paths); $i<$c$i+=5)
  548.         {
  549.             $return .= $paths[$i];
  550.             if (!isset($paths[$i+1]))
  551.             {
  552.                 continue;
  553.             }
  554.             $file FilesModel::findByUuid($paths[$i+4]);
  555.             if ($file !== null)
  556.             {
  557.                 $return .= $paths[$i+2] . '="' $paths[$i+3] . $file->path '"';
  558.             }
  559.             else
  560.             {
  561.                 $return .= $paths[$i+2] . '="' $paths[$i+3] . $paths[$i+4] . '"';
  562.             }
  563.         }
  564.         return $return;
  565.     }
  566.     /**
  567.      * Sanitize a file name
  568.      *
  569.      * @param string $strName The file name
  570.      *
  571.      * @return string The sanitized file name
  572.      */
  573.     public static function sanitizeFileName($strName)
  574.     {
  575.         // Remove invisible control characters and unused code points
  576.         $strName preg_replace('/[\pC]/u'''$strName);
  577.         if ($strName === null)
  578.         {
  579.             throw new \InvalidArgumentException('The file name could not be sanitzied');
  580.         }
  581.         // Remove special characters not supported on e.g. Windows
  582.         $strName str_replace(array('\\''/'':''*''?''"''<''>''|'), '-'$strName);
  583.         return $strName;
  584.     }
  585.     /**
  586.      * Resolve a flagged URL such as assets/js/core.js|static|10184084
  587.      *
  588.      * @param string $url The URL
  589.      *
  590.      * @return \stdClass The options object
  591.      */
  592.     public static function resolveFlaggedUrl(&$url)
  593.     {
  594.         $options = new \stdClass();
  595.         // Defaults
  596.         $options->static false;
  597.         $options->media  null;
  598.         $options->mtime  null;
  599.         $options->async  false;
  600.         $chunks explode('|'$url);
  601.         // Remove the flags from the URL
  602.         $url $chunks[0];
  603.         for ($i=1$c=\count($chunks); $i<$c$i++)
  604.         {
  605.             if (empty($chunks[$i]))
  606.             {
  607.                 continue;
  608.             }
  609.             switch ($chunks[$i])
  610.             {
  611.                 case 'static':
  612.                     $options->static true;
  613.                     break;
  614.                 case 'async':
  615.                     $options->async true;
  616.                     break;
  617.                 case is_numeric($chunks[$i]):
  618.                     $options->mtime $chunks[$i];
  619.                     break;
  620.                 default:
  621.                     $options->media $chunks[$i];
  622.                     break;
  623.             }
  624.         }
  625.         return $options;
  626.     }
  627.     /**
  628.      * Convert the character encoding
  629.      *
  630.      * @param string $str  The input string
  631.      * @param string $to   The target character set
  632.      * @param string $from An optional source character set
  633.      *
  634.      * @return string The converted string
  635.      */
  636.     public static function convertEncoding($str$to$from=null)
  637.     {
  638.         if ($str !== null && !is_scalar($str) && !(\is_object($str) && method_exists($str'__toString')))
  639.         {
  640.             @trigger_error('Passing a non-stringable argument to StringUtil::convertEncoding() has been deprecated an will no longer work in Contao 5.0.'E_USER_DEPRECATED);
  641.             return '';
  642.         }
  643.         $str = (string) $str;
  644.         if ('' === $str)
  645.         {
  646.             return $str;
  647.         }
  648.         if (!$from)
  649.         {
  650.             $from mb_detect_encoding($str'ASCII,ISO-2022-JP,UTF-8,EUC-JP,ISO-8859-1');
  651.         }
  652.         if ($from == $to)
  653.         {
  654.             return $str;
  655.         }
  656.         if ($from == 'UTF-8' && $to == 'ISO-8859-1')
  657.         {
  658.             return utf8_decode($str);
  659.         }
  660.         if ($from == 'ISO-8859-1' && $to == 'UTF-8')
  661.         {
  662.             return utf8_encode($str);
  663.         }
  664.         return mb_convert_encoding($str$to$from);
  665.     }
  666.     /**
  667.      * Convert special characters to HTML entities preventing double conversions
  668.      *
  669.      * @param string  $strString          The input string
  670.      * @param boolean $blnStripInsertTags True to strip insert tags
  671.      * @param boolean $blnDoubleEncode    True to encode existing html entities
  672.      *
  673.      * @return string The converted string
  674.      */
  675.     public static function specialchars($strString$blnStripInsertTags=false$blnDoubleEncode=false)
  676.     {
  677.         if ($blnStripInsertTags)
  678.         {
  679.             $strString = static::stripInsertTags($strString);
  680.         }
  681.         return htmlspecialchars((string) $strStringENT_QUOTES$GLOBALS['TL_CONFIG']['characterSet'] ?? 'UTF-8'$blnDoubleEncode);
  682.     }
  683.     /**
  684.      * Encodes specialchars and nested insert tags for attributes
  685.      *
  686.      * @param string  $strString          The input string
  687.      * @param boolean $blnStripInsertTags True to strip insert tags
  688.      * @param boolean $blnDoubleEncode    True to encode existing html entities
  689.      *
  690.      * @return string The converted string
  691.      */
  692.     public static function specialcharsAttribute($strString$blnStripInsertTags=false$blnDoubleEncode=false)
  693.     {
  694.         $strString self::specialchars($strString$blnStripInsertTags$blnDoubleEncode);
  695.         // Improve compatibility with JSON in attributes if no insert tags are present
  696.         if ($strString === self::stripInsertTags($strString))
  697.         {
  698.             $strString str_replace('}}''&#125;&#125;'$strString);
  699.         }
  700.         // Encode insert tags too
  701.         $strString preg_replace('/(?:\|attr)?}}/''|attr}}'$strString);
  702.         $strString str_replace('|urlattr|attr}}''|urlattr}}'$strString);
  703.         // Encode all remaining single closing curly braces
  704.         $strString preg_replace_callback(
  705.             '/}}?/',
  706.             static function ($match)
  707.             {
  708.                 return \strlen($match[0]) === $match[0] : '&#125;';
  709.             },
  710.             $strString
  711.         );
  712.         return $strString;
  713.     }
  714.     /**
  715.      * Encodes disallowed protocols and specialchars for URL attributes
  716.      *
  717.      * @param string  $strString          The input string
  718.      * @param boolean $blnStripInsertTags True to strip insert tags
  719.      * @param boolean $blnDoubleEncode    True to encode existing html entities
  720.      *
  721.      * @return string The converted string
  722.      */
  723.     public static function specialcharsUrl($strString$blnStripInsertTags=false$blnDoubleEncode=false)
  724.     {
  725.         $strString self::specialchars($strString$blnStripInsertTags$blnDoubleEncode);
  726.         // Encode insert tags too
  727.         $strString preg_replace('/(?:\|urlattr|\|attr)?}}/''|urlattr}}'$strString);
  728.         // Encode all remaining single closing curly braces
  729.         $strString preg_replace_callback(
  730.             '/}}?/',
  731.             static function ($match)
  732.             {
  733.                 return \strlen($match[0]) === $match[0] : '&#125;';
  734.             },
  735.             $strString
  736.         );
  737.         $colonRegEx '('
  738.             ':'                 // Plain text colon
  739.             '|'                 // OR
  740.             '&colon;'           // Named entity
  741.             '|'                 // OR
  742.             '&#(?:'             // Start of entity
  743.                 'x0*+3a'        // Hex number 3A
  744.                 '(?![0-9a-f])'  // Must not be followed by another hex digit
  745.                 '|'             // OR
  746.                 '0*+58'         // Decimal number 58
  747.                 '(?![0-9])'     // Must not be followed by another digit
  748.             ');?'               // Optional semicolon
  749.         ')i';
  750.         // URL-encode colon to prevent disallowed protocols
  751.         if (
  752.             !preg_match('@^(?:https?|ftp|mailto|tel|data):@i'self::decodeEntities($strString))
  753.             && preg_match($colonRegExself::stripInsertTags($strString))
  754.         ) {
  755.             $strString preg_replace($colonRegEx'%3A'$strString);
  756.         }
  757.         return $strString;
  758.     }
  759.     /**
  760.      * Remove Contao insert tags from a string
  761.      *
  762.      * @param string $strString The input string
  763.      *
  764.      * @return string The converted string
  765.      */
  766.     public static function stripInsertTags($strString)
  767.     {
  768.         $count 0;
  769.         do
  770.         {
  771.             $strString preg_replace('/{{[^{}]*}}/'''$strString, -1$count);
  772.         } while ($count 0);
  773.         return $strString;
  774.     }
  775.     /**
  776.      * Standardize a parameter (strip special characters and convert spaces)
  777.      *
  778.      * @param string  $strString            The input string
  779.      * @param boolean $blnPreserveUppercase True to preserver uppercase characters
  780.      *
  781.      * @return string The converted string
  782.      */
  783.     public static function standardize($strString$blnPreserveUppercase=false)
  784.     {
  785.         $arrSearch = array('/[^\pN\pL \.\&\/_-]+/u''/[ \.\&\/-]+/');
  786.         $arrReplace = array('''-');
  787.         $strString html_entity_decode($strStringENT_QUOTES$GLOBALS['TL_CONFIG']['characterSet'] ?? 'UTF-8');
  788.         $strString = static::stripInsertTags($strString);
  789.         $strString preg_replace($arrSearch$arrReplace$strString);
  790.         if (is_numeric(substr($strString01)))
  791.         {
  792.             $strString 'id-' $strString;
  793.         }
  794.         if (!$blnPreserveUppercase)
  795.         {
  796.             $strString Utf8::strtolower($strString);
  797.         }
  798.         return trim($strString'-');
  799.     }
  800.     /**
  801.      * Return an unserialized array or the argument
  802.      *
  803.      * @param mixed   $varValue      The serialized string
  804.      * @param boolean $blnForceArray True to always return an array
  805.      *
  806.      * @return mixed The unserialized array or the unprocessed input value
  807.      */
  808.     public static function deserialize($varValue$blnForceArray=false)
  809.     {
  810.         // Already an array
  811.         if (\is_array($varValue))
  812.         {
  813.             return $varValue;
  814.         }
  815.         // Null
  816.         if ($varValue === null)
  817.         {
  818.             return $blnForceArray ? array() : null;
  819.         }
  820.         // Not a string
  821.         if (!\is_string($varValue))
  822.         {
  823.             return $blnForceArray ? array($varValue) : $varValue;
  824.         }
  825.         // Empty string
  826.         if (trim($varValue) === '')
  827.         {
  828.             return $blnForceArray ? array() : '';
  829.         }
  830.         // Not a serialized array (see #1486)
  831.         if (strncmp($varValue'a:'2) !== 0)
  832.         {
  833.             return $blnForceArray ? array($varValue) : $varValue;
  834.         }
  835.         // Potentially including an object (see #6724)
  836.         if (preg_match('/[OoC]:\+?[0-9]+:"/'$varValue))
  837.         {
  838.             trigger_error('StringUtil::deserialize() does not allow serialized objects'E_USER_WARNING);
  839.             return $blnForceArray ? array($varValue) : $varValue;
  840.         }
  841.         $varUnserialized = @unserialize($varValue, array('allowed_classes' => false));
  842.         if (\is_array($varUnserialized))
  843.         {
  844.             $varValue $varUnserialized;
  845.         }
  846.         elseif ($blnForceArray)
  847.         {
  848.             $varValue = array($varValue);
  849.         }
  850.         return $varValue;
  851.     }
  852.     /**
  853.      * Split a string into fragments, remove whitespace and return fragments as array
  854.      *
  855.      * @param string $strPattern The split pattern
  856.      * @param string $strString  The input string
  857.      *
  858.      * @return array The fragments array
  859.      */
  860.     public static function trimsplit($strPattern$strString)
  861.     {
  862.         // Split
  863.         if (\strlen($strPattern) == 1)
  864.         {
  865.             $arrFragments array_map('trim'explode($strPattern$strString));
  866.         }
  867.         else
  868.         {
  869.             $arrFragments array_map('trim'preg_split('/' $strPattern '/ui'$strString));
  870.         }
  871.         // Empty array
  872.         if (\count($arrFragments) < && !\strlen($arrFragments[0]))
  873.         {
  874.             $arrFragments = array();
  875.         }
  876.         return $arrFragments;
  877.     }
  878.     /**
  879.      * Strip the Contao root dir from the given absolute path
  880.      *
  881.      * @param string $path
  882.      *
  883.      * @return string
  884.      *
  885.      * @throws \InvalidArgumentException
  886.      */
  887.     public static function stripRootDir($path)
  888.     {
  889.         // Compare normalized version of the paths
  890.         $projectDir Path::normalize(System::getContainer()->getParameter('kernel.project_dir'));
  891.         $normalizedPath Path::normalize($path);
  892.         $length = \strlen($projectDir);
  893.         if (strncmp($normalizedPath$projectDir$length) !== || \strlen($normalizedPath) <= $length || $normalizedPath[$length] !== '/')
  894.         {
  895.             throw new \InvalidArgumentException(sprintf('Path "%s" is not inside the Contao root dir "%s"'$path$projectDir));
  896.         }
  897.         return (string) substr($path$length 1);
  898.     }
  899.     /**
  900.      * Convert all ampersands into their HTML entity (default) or unencoded value
  901.      *
  902.      * @param string  $strString
  903.      * @param boolean $blnEncode
  904.      *
  905.      * @return string
  906.      */
  907.     public static function ampersand($strString$blnEncode=true): string
  908.     {
  909.         return preg_replace('/&(amp;)?/i', ($blnEncode '&amp;' '&'), $strString);
  910.     }
  911.     /**
  912.      * Convert an input-encoded string back to the raw UTF-8 value it originated from
  913.      *
  914.      * It handles all Contao input encoding specifics like basic entities and encoded entities.
  915.      */
  916.     public static function revertInputEncoding(string $strValue): string
  917.     {
  918.         $strValue = static::restoreBasicEntities($strValue);
  919.         $strValue = static::decodeEntities($strValue);
  920.         // Ensure valid UTF-8
  921.         if (preg_match('//u'$strValue) !== 1)
  922.         {
  923.             $substituteCharacter mb_substitute_character();
  924.             mb_substitute_character(0xFFFD);
  925.             $strValue mb_convert_encoding($strValue'UTF-8''UTF-8');
  926.             mb_substitute_character($substituteCharacter);
  927.         }
  928.         return $strValue;
  929.     }
  930.     /**
  931.      * Convert an input-encoded string to plain text UTF-8
  932.      *
  933.      * Strips or replaces insert tags, strips HTML tags, decodes entities, escapes insert tag braces.
  934.      *
  935.      * @see StringUtil::revertInputEncoding()
  936.      *
  937.      * @param bool $blnRemoveInsertTags True to remove insert tags instead of replacing them
  938.      */
  939.     public static function inputEncodedToPlainText(string $strValuebool $blnRemoveInsertTags false): string
  940.     {
  941.         if ($blnRemoveInsertTags)
  942.         {
  943.             $strValue = static::stripInsertTags($strValue);
  944.         }
  945.         else
  946.         {
  947.             $strValue Controller::replaceInsertTags($strValuefalse);
  948.         }
  949.         $strValue strip_tags($strValue);
  950.         $strValue = static::revertInputEncoding($strValue);
  951.         $strValue str_replace(array('{{''}}'), array('[{]''[}]'), $strValue);
  952.         return $strValue;
  953.     }
  954.     /**
  955.      * Convert an HTML string to plain text with normalized white space
  956.      *
  957.      * It handles all Contao input encoding specifics like insert tags, basic
  958.      * entities and encoded entities and is meant to be used with content from
  959.      * fields that have the allowHtml flag enabled.
  960.      *
  961.      * @see StringUtil::inputEncodedToPlainText()
  962.      *
  963.      * @param bool $blnRemoveInsertTags True to remove insert tags instead of replacing them
  964.      */
  965.     public static function htmlToPlainText(string $strValuebool $blnRemoveInsertTags false): string
  966.     {
  967.         if (!$blnRemoveInsertTags)
  968.         {
  969.             $strValue Controller::replaceInsertTags($strValuefalse);
  970.         }
  971.         // Add new lines before and after block level elements
  972.         $strValue preg_replace(
  973.             array('/[\r\n]+/''/<\/?(?:br|blockquote|div|dl|figcaption|figure|footer|h\d|header|hr|li|p|pre|tr)\b/i'),
  974.             array(' '"\n$0"),
  975.             $strValue
  976.         );
  977.         $strValue = static::inputEncodedToPlainText($strValuetrue);
  978.         // Remove duplicate line breaks and spaces
  979.         $strValue trim(preg_replace(array('/[^\S\n]+/''/\s*\n\s*/'), array(' '"\n"), $strValue));
  980.         return $strValue;
  981.     }
  982. }
  983. class_alias(StringUtil::class, 'StringUtil');