Failed Conditions
Push — stable ( 017e16...b83837 )
by
unknown
10:45 queued 07:58
created

inc/Utf8/PhpString.php (1 issue)

Labels
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace dokuwiki\Utf8;
4
5
/**
6
 * UTF-8 aware equivalents to PHP's string functions
7
 */
8
class PhpString
9
{
10
11
    /**
12
     * A locale independent basename() implementation
13
     *
14
     * works around a bug in PHP's basename() implementation
15
     *
16
     * @param string $path A path
17
     * @param string $suffix If the name component ends in suffix this will also be cut off
18
     * @return string
19
     * @link   https://bugs.php.net/bug.php?id=37738
20
     *
21
     * @see basename()
22
     */
23
    public static function basename($path, $suffix = '')
24
    {
25
        $path = trim($path, '\\/');
26
        $rpos = max(strrpos($path, '/'), strrpos($path, '\\'));
27
        if ($rpos) {
28
            $path = substr($path, $rpos + 1);
29
        }
30
31
        $suflen = strlen($suffix);
32
        if ($suflen && (substr($path, -$suflen) === $suffix)) {
33
            $path = substr($path, 0, -$suflen);
34
        }
35
36
        return $path;
37
    }
38
39
    /**
40
     * Unicode aware replacement for strlen()
41
     *
42
     * utf8_decode() converts characters that are not in ISO-8859-1
43
     * to '?', which, for the purpose of counting, is alright - It's
44
     * even faster than mb_strlen.
45
     *
46
     * @param string $string
47
     * @return int
48
     * @see    utf8_decode()
49
     *
50
     * @author <chernyshevsky at hotmail dot com>
51
     * @see    strlen()
52
     */
53
    public static function strlen($string)
54
    {
55
        if (function_exists('utf8_decode')) {
56
            return strlen(utf8_decode($string));
57
        }
58
59
        if (UTF8_MBSTRING) {
60
            return mb_strlen($string, 'UTF-8');
61
        }
62
63
        if (function_exists('iconv_strlen')) {
64
            return iconv_strlen($string, 'UTF-8');
65
        }
66
67
        return strlen($string);
68
    }
69
70
    /**
71
     * UTF-8 aware alternative to substr
72
     *
73
     * Return part of a string given character offset (and optionally length)
74
     *
75
     * @param string $str
76
     * @param int $offset number of UTF-8 characters offset (from left)
77
     * @param int $length (optional) length in UTF-8 characters from offset
78
     * @return string
79
     * @author Harry Fuecks <[email protected]>
80
     * @author Chris Smith <[email protected]>
81
     *
82
     */
83
    public static function substr($str, $offset, $length = null)
84
    {
85
        if (UTF8_MBSTRING) {
86
            if ($length === null) {
87
                return mb_substr($str, $offset);
88
            }
89
90
            return mb_substr($str, $offset, $length);
91
        }
92
93
        /*
94
         * Notes:
95
         *
96
         * no mb string support, so we'll use pcre regex's with 'u' flag
97
         * pcre only supports repetitions of less than 65536, in order to accept up to MAXINT values for
98
         * offset and length, we'll repeat a group of 65535 characters when needed (ok, up to MAXINT-65536)
99
         *
100
         * substr documentation states false can be returned in some cases (e.g. offset > string length)
101
         * mb_substr never returns false, it will return an empty string instead.
102
         *
103
         * calculating the number of characters in the string is a relatively expensive operation, so
104
         * we only carry it out when necessary. It isn't necessary for +ve offsets and no specified length
105
         */
106
107
        // cast parameters to appropriate types to avoid multiple notices/warnings
108
        $str = (string)$str;                          // generates E_NOTICE for PHP4 objects, but not PHP5 objects
109
        $offset = (int)$offset;
110
        if ($length !== null) $length = (int)$length;
111
112
        // handle trivial cases
113
        if ($length === 0) return '';
114
        if ($offset < 0 && $length < 0 && $length < $offset) return '';
115
116
        $offset_pattern = '';
117
        $length_pattern = '';
118
119
        // normalise -ve offsets (we could use a tail anchored pattern, but they are horribly slow!)
120
        if ($offset < 0) {
121
            $strlen = self::strlen($str);        // see notes
122
            $offset = $strlen + $offset;
123
            if ($offset < 0) $offset = 0;
124
        }
125
126
        // establish a pattern for offset, a non-captured group equal in length to offset
127
        if ($offset > 0) {
128
            $Ox = (int)($offset / 65535);
129
            $Oy = $offset % 65535;
130
131
            if ($Ox) $offset_pattern = '(?:.{65535}){' . $Ox . '}';
132
            $offset_pattern = '^(?:' . $offset_pattern . '.{' . $Oy . '})';
133
        } else {
134
            $offset_pattern = '^';                      // offset == 0; just anchor the pattern
135
        }
136
137
        // establish a pattern for length
138
        if ($length === null) {
139
            $length_pattern = '(.*)$';                  // the rest of the string
140
        } else {
141
142
            if (!isset($strlen)) $strlen = self::strlen($str);    // see notes
143
            if ($offset > $strlen) return '';           // another trivial case
144
145
            if ($length > 0) {
146
147
                // reduce any length that would go past the end of the string
148
                $length = min($strlen - $offset, $length);
149
150
                $Lx = (int)($length / 65535);
151
                $Ly = $length % 65535;
152
153
                // +ve length requires ... a captured group of length characters
154
                if ($Lx) $length_pattern = '(?:.{65535}){' . $Lx . '}';
155
                $length_pattern = '(' . $length_pattern . '.{' . $Ly . '})';
156
157
            } else if ($length < 0) {
158
159
                if ($length < ($offset - $strlen)) return '';
160
161
                $Lx = (int)((-$length) / 65535);
162
                $Ly = (-$length) % 65535;
163
164
                // -ve length requires ... capture everything except a group of -length characters
165
                //                         anchored at the tail-end of the string
166
                if ($Lx) $length_pattern = '(?:.{65535}){' . $Lx . '}';
167
                $length_pattern = '(.*)(?:' . $length_pattern . '.{' . $Ly . '})$';
168
            }
169
        }
170
171
        if (!preg_match('#' . $offset_pattern . $length_pattern . '#us', $str, $match)) return '';
172
        return $match[1];
173
    }
174
175
    // phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps
176
    /**
177
     * Unicode aware replacement for substr_replace()
178
     *
179
     * @param string $string input string
180
     * @param string $replacement the replacement
181
     * @param int $start the replacing will begin at the start'th offset into string.
182
     * @param int $length If given and is positive, it represents the length of the portion of string which is
183
     *                            to be replaced. If length is zero then this function will have the effect of inserting
184
     *                            replacement into string at the given start offset.
185
     * @return string
186
     * @see    substr_replace()
187
     *
188
     * @author Andreas Gohr <[email protected]>
189
     */
190
    public static function substr_replace($string, $replacement, $start, $length = 0)
191
    {
192
        $ret = '';
193
        if ($start > 0) $ret .= self::substr($string, 0, $start);
194
        $ret .= $replacement;
195
        $ret .= self::substr($string, $start + $length);
196
        return $ret;
197
    }
198
    // phpcs:enable PSR1.Methods.CamelCapsMethodName.NotCamelCaps
199
200
    /**
201
     * Unicode aware replacement for ltrim()
202
     *
203
     * @param string $str
204
     * @param string $charlist
205
     * @return string
206
     * @see    ltrim()
207
     *
208
     * @author Andreas Gohr <[email protected]>
209
     */
210
    public static function ltrim($str, $charlist = '')
211
    {
212
        if ($charlist === '') return ltrim($str);
213
214
        //quote charlist for use in a characterclass
215
        $charlist = preg_replace('!([\\\\\\-\\]\\[/])!', '\\\${1}', $charlist);
216
217
        return preg_replace('/^[' . $charlist . ']+/u', '', $str);
218
    }
219
220
    /**
221
     * Unicode aware replacement for rtrim()
222
     *
223
     * @param string $str
224
     * @param string $charlist
225
     * @return string
226
     * @see    rtrim()
227
     *
228
     * @author Andreas Gohr <[email protected]>
229
     */
230
    public static function rtrim($str, $charlist = '')
231
    {
232
        if ($charlist === '') return rtrim($str);
233
234
        //quote charlist for use in a characterclass
235
        $charlist = preg_replace('!([\\\\\\-\\]\\[/])!', '\\\${1}', $charlist);
236
237
        return preg_replace('/[' . $charlist . ']+$/u', '', $str);
238
    }
239
240
    /**
241
     * Unicode aware replacement for trim()
242
     *
243
     * @param string $str
244
     * @param string $charlist
245
     * @return string
246
     * @see    trim()
247
     *
248
     * @author Andreas Gohr <[email protected]>
249
     */
250
    public static function trim($str, $charlist = '')
251
    {
252
        if ($charlist === '') return trim($str);
253
254
        return self::ltrim(self::rtrim($str, $charlist), $charlist);
255
    }
256
257
    /**
258
     * This is a unicode aware replacement for strtolower()
259
     *
260
     * Uses mb_string extension if available
261
     *
262
     * @param string $string
263
     * @return string
264
     * @see    \dokuwiki\Utf8\PhpString::strtoupper()
265
     *
266
     * @author Leo Feyer <[email protected]>
267
     * @see    strtolower()
268
     */
269
    public static function strtolower($string)
270
    {
271
        if (UTF8_MBSTRING) {
272
            if (class_exists('Normalizer', $autoload = false)) {
273
                return \Normalizer::normalize(mb_strtolower($string, 'utf-8'));
274
            }
275
            return (mb_strtolower($string, 'utf-8'));
276
        }
277
        return strtr($string, Table::upperCaseToLowerCase());
278
    }
279
280
    /**
281
     * This is a unicode aware replacement for strtoupper()
282
     *
283
     * Uses mb_string extension if available
284
     *
285
     * @param string $string
286
     * @return string
287
     * @see    \dokuwiki\Utf8\PhpString::strtoupper()
288
     *
289
     * @author Leo Feyer <[email protected]>
290
     * @see    strtoupper()
291
     */
292
    public static function strtoupper($string)
293
    {
294
        if (UTF8_MBSTRING) return mb_strtoupper($string, 'utf-8');
295
296
        return strtr($string, Table::lowerCaseToUpperCase());
0 ignored issues
show
It seems like \dokuwiki\Utf8\Table::lowerCaseToUpperCase() targeting dokuwiki\Utf8\Table::lowerCaseToUpperCase() can also be of type null; however, strtr() does only seem to accept array, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
297
    }
298
299
300
    /**
301
     * UTF-8 aware alternative to ucfirst
302
     * Make a string's first character uppercase
303
     *
304
     * @param string $str
305
     * @return string with first character as upper case (if applicable)
306
     * @author Harry Fuecks
307
     *
308
     */
309
    public static function ucfirst($str)
310
    {
311
        switch (self::strlen($str)) {
312
            case 0:
313
                return '';
314
            case 1:
315
                return self::strtoupper($str);
316
            default:
317
                preg_match('/^(.{1})(.*)$/us', $str, $matches);
318
                return self::strtoupper($matches[1]) . $matches[2];
319
        }
320
    }
321
322
    /**
323
     * UTF-8 aware alternative to ucwords
324
     * Uppercase the first character of each word in a string
325
     *
326
     * @param string $str
327
     * @return string with first char of each word uppercase
328
     * @author Harry Fuecks
329
     * @see http://php.net/ucwords
330
     *
331
     */
332
    public static function ucwords($str)
333
    {
334
        // Note: [\x0c\x09\x0b\x0a\x0d\x20] matches;
335
        // form feeds, horizontal tabs, vertical tabs, linefeeds and carriage returns
336
        // This corresponds to the definition of a "word" defined at http://php.net/ucwords
337
        $pattern = '/(^|([\x0c\x09\x0b\x0a\x0d\x20]+))([^\x0c\x09\x0b\x0a\x0d\x20]{1})[^\x0c\x09\x0b\x0a\x0d\x20]*/u';
338
339
        return preg_replace_callback(
340
            $pattern,
341
            function ($matches) {
342
                $leadingws = $matches[2];
343
                $ucfirst = self::strtoupper($matches[3]);
344
                $ucword = self::substr_replace(ltrim($matches[0]), $ucfirst, 0, 1);
345
                return $leadingws . $ucword;
346
            },
347
            $str
348
        );
349
    }
350
351
    /**
352
     * This is an Unicode aware replacement for strpos
353
     *
354
     * @param string $haystack
355
     * @param string $needle
356
     * @param integer $offset
357
     * @return integer
358
     * @author Leo Feyer <[email protected]>
359
     * @see    strpos()
360
     *
361
     */
362
    public static function strpos($haystack, $needle, $offset = 0)
363
    {
364
        $comp = 0;
365
        $length = null;
366
367
        while ($length === null || $length < $offset) {
368
            $pos = strpos($haystack, $needle, $offset + $comp);
369
370
            if ($pos === false)
371
                return false;
372
373
            $length = self::strlen(substr($haystack, 0, $pos));
374
375
            if ($length < $offset)
376
                $comp = $pos - $length;
377
        }
378
379
        return $length;
380
    }
381
382
383
}
384