1 <?php
  2 
  3 /**
  4  * String utility class.
  5  *
  6  * @package redaxo\core
  7  */
  8 class rex_string
  9 {
 10     /**
 11      * Returns the string size in bytes.
 12      *
 13      * @param string $string String
 14      *
 15      * @return int Size in bytes
 16      */
 17     public static function size($string)
 18     {
 19         return mb_strlen($string, '8bit');
 20     }
 21 
 22     /**
 23      * Normalizes the encoding of a string (UTF8 NFD to NFC).
 24      *
 25      * On HFS+ filesystem (OS X) filenames are stored in UTF8 NFD while all other filesystems are
 26      * using UTF8 NFC. NFC is more common in general.
 27      *
 28      * @param string $string Input string
 29      *
 30      * @return string
 31      */
 32     public static function normalizeEncoding($string)
 33     {
 34         static $normalizer;
 35 
 36         if (null === $normalizer) {
 37             if (function_exists('normalizer_normalize')) {
 38                 $normalizer = function ($string) {
 39                     return normalizer_normalize($string, Normalizer::FORM_C);
 40                 };
 41             } else {
 42                 $normalizer = function ($string) {
 43                     return str_replace(
 44                         ["A\xcc\x88", "a\xcc\x88", "O\xcc\x88", "o\xcc\x88", "U\xcc\x88", "u\xcc\x88"],
 45                         ['Ä', 'ä', 'Ö', 'ö', 'Ü', 'ü'],
 46                         $string
 47                     );
 48                 };
 49             }
 50         }
 51 
 52         return $normalizer($string);
 53     }
 54 
 55     /**
 56      * Normalizes a string.
 57      *
 58      * Makes the string lowercase, replaces umlauts by their ascii representation (ä -> ae etc.), and replaces all
 59      * other chars that do not match a-z, 0-9 or $allowedChars by $replaceChar.
 60      *
 61      * @param string $string       Input string
 62      * @param string $replaceChar  Character that is used to replace not allowed chars
 63      * @param string $allowedChars Character whitelist
 64      *
 65      * @return string
 66      */
 67     public static function normalize($string, $replaceChar = '_', $allowedChars = '')
 68     {
 69         $string = self::normalizeEncoding($string);
 70         $string = mb_strtolower($string);
 71         $string = str_replace(['ä', 'ö', 'ü', 'ß'], ['ae', 'oe', 'ue', 'ss'], $string);
 72         $string = preg_replace('/[^a-z\d' . preg_quote($allowedChars, '/') . ']+/ui', $replaceChar, $string);
 73         return trim($string, $replaceChar);
 74     }
 75 
 76     /**
 77      * Splits a string by spaces
 78      * (Strings with quotes will be regarded).
 79      *
 80      * Examples:
 81      * "a b 'c d'"   -> array('a', 'b', 'c d')
 82      * "a=1 b='c d'" -> array('a' => 1, 'b' => 'c d')
 83      *
 84      * @param string $string
 85      *
 86      * @return array
 87      */
 88     public static function split($string)
 89     {
 90         $string = trim($string);
 91         if (empty($string)) {
 92             return [];
 93         }
 94         $result = [];
 95         $spacer = '@@@REX_SPACER@@@';
 96         $quoted = [];
 97 
 98         $pattern = '@(?<=\s|=|^)(["\'])((?:.*[^\\\\])?(?:\\\\\\\\)*)\\1(?=\s|$)@Us';
 99         $callback = function ($match) use ($spacer, &$quoted) {
100             $quoted[] = str_replace(['\\' . $match[1], '\\\\'], [$match[1], '\\'], $match[2]);
101             return $spacer;
102         };
103         $string = preg_replace_callback($pattern, $callback, $string);
104 
105         $parts = preg_split('@\s+@', $string);
106         $i = 0;
107         foreach ($parts as $part) {
108             $part = explode('=', $part, 2);
109             if (isset($part[1])) {
110                 $value = $part[1] == $spacer ? $quoted[$i++] : $part[1];
111                 $result[$part[0]] = $value;
112             } else {
113                 $value = $part[0] == $spacer ? $quoted[$i++] : $part[0];
114                 $result[] = $value;
115             }
116         }
117 
118         return $result;
119     }
120 
121     /**
122      * Splits a version string.
123      *
124      * @param string $version Version
125      *
126      * @return array Version parts
127      */
128     public static function versionSplit($version)
129     {
130         return preg_split('/(?<=\d)(?=[a-z])|(?<=[a-z])(?=\d)|[ ._-]+/i', $version);
131     }
132 
133     /**
134      * Compares two version number strings.
135      *
136      * In contrast to version_compare() it treats "1.0" and "1.0.0" as equal and it supports a space as separator for
137      * the version parts, e.g. "1.0 beta1"
138      *
139      * @see http://www.php.net/manual/en/function.version-compare.php
140      *
141      * @param string $version1   First version number
142      * @param string $version2   Second version number
143      * @param string $comparator Optional comparator
144      *
145      * @return int|bool
146      */
147     public static function versionCompare($version1, $version2, $comparator = null)
148     {
149         $version1 = self::versionSplit($version1);
150         $version2 = self::versionSplit($version2);
151         $max = max(count($version1), count($version2));
152         $version1 = implode('.', array_pad($version1, $max, '0'));
153         $version2 = implode('.', array_pad($version2, $max, '0'));
154         return version_compare($version1, $version2, $comparator);
155     }
156 
157     /**
158      * Returns a string containing the YAML representation of $value.
159      *
160      * @param array $value  The value being encoded
161      * @param int   $inline The level where you switch to inline YAML
162      *
163      * @return string
164      */
165     public static function yamlEncode(array $value, $inline = 3)
166     {
167         return Symfony\Component\Yaml\Yaml::dump($value, $inline, 4);
168     }
169 
170     /**
171      * Parses YAML into a PHP array.
172      *
173      * @param string $value YAML string
174      *
175      * @return array
176      *
177      * @throws rex_yaml_parse_exception
178      */
179     public static function yamlDecode($value)
180     {
181         try {
182             return Symfony\Component\Yaml\Yaml::parse($value);
183         } catch (Symfony\Component\Yaml\Exception\ParseException $exception) {
184             throw new rex_yaml_parse_exception($exception->getMessage(), $exception);
185         }
186     }
187 
188     /**
189      * Generates URL-encoded query string.
190      *
191      * @param array  $params
192      * @param string $argSeparator
193      *
194      * @return string
195      */
196     public static function buildQuery(array $params, $argSeparator = '&')
197     {
198         $query = [];
199         $func = function (array $params, $fullkey = null) use (&$query, &$func) {
200             foreach ($params as $key => $value) {
201                 $key = $fullkey ? $fullkey . '[' . urlencode($key) . ']' : urlencode($key);
202                 if (is_array($value)) {
203                     $func($value, $key);
204                 } else {
205                     $query[] = $key . '=' . str_replace('%2F', '/', urlencode($value));
206                 }
207             }
208         };
209         $func($params);
210         return implode($argSeparator, $query);
211     }
212 
213     /**
214      * Returns a string by key="value" pair.
215      *
216      * @param array $attributes
217      *
218      * @return string
219      */
220     public static function buildAttributes(array $attributes)
221     {
222         $attr = '';
223 
224         foreach ($attributes as $key => $value) {
225             if (is_int($key)) {
226                 $attr .= ' ' . rex_escape($value);
227             } else {
228                 if (is_array($value)) {
229                     $value = implode(' ', $value);
230                 }
231                 // for bc reasons avoid double escaping of "&", especially in already escaped urls
232                 $value = str_replace('&amp;', '&', $value);
233                 $attr .= ' ' . rex_escape($key) . '="' . rex_escape($value) . '"';
234             }
235         }
236 
237         return $attr;
238     }
239 
240     /**
241      * Highlights a string.
242      *
243      * @param string $string
244      *
245      * @return string
246      */
247     public static function highlight($string)
248     {
249         $return = str_replace(["\r", "\n"], ['', ''], highlight_string($string, true));
250         return '<pre class="rex-code">' . $return . '</pre>';
251     }
252 }
253