]> git.mxchange.org Git - friendica.git/blob - src/Util/Strings.php
Add new Strings::startsWith method
[friendica.git] / src / Util / Strings.php
1 <?php
2 /**
3  * @copyright Copyright (C) 2020, Friendica
4  *
5  * @license GNU AGPL version 3 or any later version
6  *
7  * This program is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU Affero General Public License as
9  * published by the Free Software Foundation, either version 3 of the
10  * License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU Affero General Public License for more details.
16  *
17  * You should have received a copy of the GNU Affero General Public License
18  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
19  *
20  */
21
22 namespace Friendica\Util;
23
24 use Friendica\Content\ContactSelector;
25 use Friendica\Core\Logger;
26
27 /**
28  * This class handles string functions
29  */
30 class Strings
31 {
32         /**
33          * Generates a pseudo-random string of hexadecimal characters
34          *
35          * @param int $size
36          * @return string
37          * @throws \Exception
38          */
39         public static function getRandomHex($size = 64)
40         {
41                 $byte_size = ceil($size / 2);
42
43                 $bytes = random_bytes($byte_size);
44
45                 $return = substr(bin2hex($bytes), 0, $size);
46
47                 return $return;
48         }
49
50         /**
51          * Checks, if the given string is a valid hexadecimal code
52          *
53          * @param string $hexCode
54          *
55          * @return bool
56          */
57         public static function isHex($hexCode)
58         {
59                 return !empty($hexCode) ? @preg_match("/^[a-f0-9]{2,}$/i", $hexCode) && !(strlen($hexCode) & 1) : false;
60         }
61
62         /**
63          * This is our primary input filter.
64          *
65          * Use this on any text input where angle chars are not valid or permitted
66          * They will be replaced with safer brackets. This may be filtered further
67          * if these are not allowed either.
68          *
69          * @param string $string Input string
70          * @return string Filtered string
71          */
72         public static function escapeTags($string)
73         {
74                 return str_replace(["<", ">"], ['[', ']'], $string);
75         }
76
77         /**
78          * Use this on "body" or "content" input where angle chars shouldn't be removed,
79          * and allow them to be safely displayed.
80          * @param string $string
81          *
82          * @return string
83          */
84         public static function escapeHtml($string)
85         {
86                 return htmlspecialchars($string, ENT_COMPAT, 'UTF-8', false);
87         }
88
89         /**
90          * Generate a string that's random, but usually pronounceable. Used to generate initial passwords
91          *
92          * @param int $len      length
93          *
94          * @return string
95          */
96         public static function getRandomName($len)
97         {
98                 if ($len <= 0) {
99                         return '';
100                 }
101
102                 $vowels = ['a', 'a', 'ai', 'au', 'e', 'e', 'e', 'ee', 'ea', 'i', 'ie', 'o', 'ou', 'u'];
103
104                 if (mt_rand(0, 5) == 4) {
105                         $vowels[] = 'y';
106                 }
107
108                 $cons = [
109                         'b', 'bl', 'br',
110                         'c', 'ch', 'cl', 'cr',
111                         'd', 'dr',
112                         'f', 'fl', 'fr',
113                         'g', 'gh', 'gl', 'gr',
114                         'h',
115                         'j',
116                         'k', 'kh', 'kl', 'kr',
117                         'l',
118                         'm',
119                         'n',
120                         'p', 'ph', 'pl', 'pr',
121                         'qu',
122                         'r', 'rh',
123                         's', 'sc', 'sh', 'sm', 'sp', 'st',
124                         't', 'th', 'tr',
125                         'v',
126                         'w', 'wh',
127                         'x',
128                         'z', 'zh'
129                 ];
130
131                 $midcons = [
132                         'ck', 'ct', 'gn', 'ld', 'lf', 'lm', 'lt', 'mb', 'mm', 'mn', 'mp',
133                         'nd', 'ng', 'nk', 'nt', 'rn', 'rp', 'rt'
134                 ];
135
136                 $noend = [
137                         'bl', 'br', 'cl', 'cr', 'dr', 'fl', 'fr', 'gl', 'gr',
138                         'kh', 'kl', 'kr', 'mn', 'pl', 'pr', 'rh', 'tr', 'qu', 'wh', 'q'
139                 ];
140
141                 $start = mt_rand(0, 2);
142                 if ($start == 0) {
143                         $table = $vowels;
144                 } else {
145                         $table = $cons;
146                 }
147
148                 $word = '';
149
150                 for ($x = 0; $x < $len; $x++) {
151                         $r = mt_rand(0, count($table) - 1);
152                         $word .= $table[$r];
153
154                         if ($table == $vowels) {
155                                 $table = array_merge($cons, $midcons);
156                         } else {
157                                 $table = $vowels;
158                         }
159                 }
160
161                 $word = substr($word, 0, $len);
162
163                 foreach ($noend as $noe) {
164                         $noelen = strlen($noe);
165                         if ((strlen($word) > $noelen) && (substr($word, -$noelen) == $noe)) {
166                                 $word = self::getRandomName($len);
167                                 break;
168                         }
169                 }
170
171                 return $word;
172         }
173
174         /**
175          * Translate and format the network name of a contact
176          *
177          * @param string $network Network name of the contact (e.g. dfrn, rss and so on)
178          * @param string $url     The contact url
179          *
180          * @return string Formatted network name
181          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
182          */
183         public static function formatNetworkName($network, $url = '')
184         {
185                 if ($network != '') {
186                         if ($url != '') {
187                                 $network_name = '<a href="' . $url . '">' . ContactSelector::networkToName($network, $url) . '</a>';
188                         } else {
189                                 $network_name = ContactSelector::networkToName($network);
190                         }
191
192                         return $network_name;
193                 }
194         }
195
196         /**
197          * Remove indentation from a text
198          *
199          * @param string $text  String to be transformed.
200          * @param string $chr   Optional. Indentation tag. Default tab (\t).
201          * @param int    $count Optional. Default null.
202          *
203          * @return string               Transformed string.
204          */
205         public static function deindent($text, $chr = "[\t ]", $count = NULL)
206         {
207                 $lines = explode("\n", $text);
208
209                 if (is_null($count)) {
210                         $m = [];
211                         $k = 0;
212                         while ($k < count($lines) && strlen($lines[$k]) == 0) {
213                                 $k++;
214                         }
215                         preg_match("|^" . $chr . "*|", $lines[$k], $m);
216                         $count = strlen($m[0]);
217                 }
218
219                 for ($k = 0; $k < count($lines); $k++) {
220                         $lines[$k] = preg_replace("|^" . $chr . "{" . $count . "}|", "", $lines[$k]);
221                 }
222
223                 return implode("\n", $lines);
224         }
225
226         /**
227          * Get byte size returned in a Data Measurement (KB, MB, GB)
228          *
229          * @param int $bytes    The number of bytes to be measured
230          * @param int $precision        Optional. Default 2.
231          *
232          * @return string       Size with measured units.
233          */
234         public static function formatBytes($bytes, $precision = 2)
235         {
236                 $units = ['B', 'KB', 'MB', 'GB', 'TB'];
237                 $bytes = max($bytes, 0);
238                 $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
239                 $pow = min($pow, count($units) - 1);
240                 $bytes /= pow(1024, $pow);
241
242                 return round($bytes, $precision) . ' ' . $units[$pow];
243         }
244
245         /**
246          * Protect percent characters in sprintf calls
247          *
248          * @param string $s String to transform.
249          *
250          * @return string       Transformed string.
251          */
252         public static function protectSprintf($s)
253         {
254                 return str_replace('%', '%%', $s);
255         }
256
257         /**
258          * Base64 Encode URL and translate +/ to -_ Optionally strip padding.
259          *
260          * @param string $s                                     URL to encode
261          * @param boolean $strip_padding        Optional. Default false
262          *
263          * @return string       Encoded URL
264          */
265         public static function base64UrlEncode($s, $strip_padding = false)
266         {
267                 $s = strtr(base64_encode($s), '+/', '-_');
268
269                 if ($strip_padding) {
270                         $s = str_replace('=', '', $s);
271                 }
272
273                 return $s;
274         }
275
276         /**
277          * Decode Base64 Encoded URL and translate -_ to +/
278          * @param string $s URL to decode
279          *
280          * @return string       Decoded URL
281          * @throws \Exception
282          */
283         public static function base64UrlDecode($s)
284         {
285                 if (is_array($s)) {
286                         Logger::log('base64url_decode: illegal input: ' . print_r(debug_backtrace(), true));
287                         return $s;
288                 }
289
290                 /*
291                 *  // Placeholder for new rev of salmon which strips base64 padding.
292                 *  // PHP base64_decode handles the un-padded input without requiring this step
293                 *  // Uncomment if you find you need it.
294                 *
295                 *       $l = strlen($s);
296                 *       if (!strpos($s,'=')) {
297                 *               $m = $l % 4;
298                 *               if ($m == 2)
299                 *                       $s .= '==';
300                 *               if ($m == 3)
301                 *                       $s .= '=';
302                 *       }
303                 *
304                 */
305
306                 return base64_decode(strtr($s, '-_', '+/'));
307         }
308
309         /**
310          * Normalize url
311          *
312          * @param string $url   URL to be normalized.
313          *
314          * @return string       Normalized URL.
315          */
316         public static function normaliseLink($url)
317         {
318                 $ret = str_replace(['https:', '//www.'], ['http:', '//'], $url);
319                 return rtrim($ret, '/');
320         }
321
322         /**
323          * Normalize OpenID identity
324          *
325          * @param string $s OpenID Identity
326          *
327          * @return string       normalized OpenId Identity
328          */
329         public static function normaliseOpenID($s)
330         {
331                 return trim(str_replace(['http://', 'https://'], ['', ''], $s), '/');
332         }
333
334         /**
335          * Compare two URLs to see if they are the same, but ignore
336          * slight but hopefully insignificant differences such as if one
337          * is https and the other isn't, or if one is www.something and
338          * the other isn't - and also ignore case differences.
339          *
340          * @param string $a first url
341          * @param string $b second url
342          * @return boolean True if the URLs match, otherwise False
343          *
344          */
345         public static function compareLink($a, $b)
346         {
347                 return (strcasecmp(self::normaliseLink($a), self::normaliseLink($b)) === 0);
348         }
349
350         /**
351          * Ensures the provided URI has its query string punctuation in order.
352          *
353          * @param string $uri
354          * @return string
355          */
356         public static function ensureQueryParameter($uri)
357         {
358                 if (strpos($uri, '?') === false && ($pos = strpos($uri, '&')) !== false) {
359                         $uri = substr($uri, 0, $pos) . '?' . substr($uri, $pos + 1);
360                 }
361
362                 return $uri;
363         }
364
365         /**
366          * Check if the trimmed provided string is starting with one of the provided characters
367          *
368          * @param string $string
369          * @param array  $chars
370          * @return bool
371          */
372         public static function startsWithChars($string, array $chars)
373         {
374                 $return = in_array(substr(trim($string), 0, 1), $chars);
375
376                 return $return;
377         }
378
379         /**
380          * Check if the first string starts with the second
381          *
382          * @param string $string
383          * @param string $start
384          * @return bool
385          */
386         public static function startsWith(string $string, string $start)
387         {
388                 $return = substr_compare($string, $start, 0, strlen($start)) === 0;
389
390                 return $return;
391         }
392
393         /**
394          * Returns the regular expression string to match URLs in a given text
395          *
396          * @return string
397          * @see https://daringfireball.net/2010/07/improved_regex_for_matching_urls
398          */
399         public static function autoLinkRegEx()
400         {
401                 return '@
402 (?<![=\'\]"/])                  # Not preceded by [, =, \', ], ", /
403 \b
404 (                                                          # Capture 1: entire matched URL
405   https?://                                                        # http or https protocol
406   (?:
407         [^/\s\xA0`!()\[\]{};:\'",<>?«»“”‘’.]    # Domain can\'t start with a .
408         [^/\s\xA0`!()\[\]{};:\'",<>?«»“”‘’]+    # Domain can\'t end with a .
409         \.
410         [^/\s\xA0`!()\[\]{};:\'".,<>?«»“”‘’]+/? # Followed by a slash
411   )
412   (?:                                                              # One or more:
413         [^\s\xA0()<>]+                                             # Run of non-space, non-()<>
414         |                                                                  #   or
415         \(([^\s\xA0()<>]+|(\([^\s()<>]+\)))*\) # balanced parens, up to 2 levels
416         |                                                                  #   or
417         [^\s\xA0`!()\[\]{};:\'".,<>?«»“”‘’]    # not a space or one of these punct chars
418   )*
419 )@xiu';
420         }
421
422         /**
423          * Ensures a single path item doesn't contain any path-traversing characters
424          *
425          * @see https://stackoverflow.com/a/46097713
426          * @param string $pathItem
427          * @return string
428          */
429         public static function sanitizeFilePathItem($pathItem)
430         {
431                 $pathItem = str_replace('/', '_', $pathItem);
432                 $pathItem = str_replace('\\', '_', $pathItem);
433                 $pathItem = str_replace(DIRECTORY_SEPARATOR, '_', $pathItem); // In case it does not equal the standard values
434
435                 return $pathItem;
436         }
437
438         /**
439          * Multi-byte safe implementation of substr_replace where $start and $length are character offset and count rather
440          * than byte offset and counts.
441          *
442          * Depends on mbstring, use default encoding.
443          *
444          * @param string   $string
445          * @param string   $replacement
446          * @param int      $start
447          * @param int|null $length
448          * @return string
449          * @see substr_replace()
450          */
451         public static function substringReplace(string $string, string $replacement, int $start, int $length = null)
452         {
453                 $string_length = mb_strlen($string);
454
455                 $length = $length ?? $string_length;
456
457                 if ($start < 0) {
458                         $start = max(0, $string_length + $start);
459                 } else if ($start > $string_length) {
460                         $start = $string_length;
461                 }
462
463                 if ($length < 0) {
464                         $length = max(0, $string_length - $start + $length);
465                 } else if ($length > $string_length) {
466                         $length = $string_length;
467                 }
468
469                 if (($start + $length) > $string_length) {
470                         $length = $string_length - $start;
471                 }
472
473                 return mb_substr($string, 0, $start) . $replacement . mb_substr($string, $start + $length, $string_length - $start - $length);
474         }
475 }