Failed Conditions
Push — psr2 ( 2b9c4a...b47790 )
by Andreas
08:24 queued 05:51
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
    /**
176
     * Unicode aware replacement for substr_replace()
177
     *
178
     * @param string $string input string
179
     * @param string $replacement the replacement
180
     * @param int $start the replacing will begin at the start'th offset into string.
181
     * @param int $length If given and is positive, it represents the length of the portion of string which is
182
     *                            to be replaced. If length is zero then this function will have the effect of inserting
183
     *                            replacement into string at the given start offset.
184
     * @return string
185
     * @see    substr_replace()
186
     *
187
     * @author Andreas Gohr <[email protected]>
188
     */
189
    public static function substr_replace($string, $replacement, $start, $length = 0)
190
    {
191
        $ret = '';
192
        if ($start > 0) $ret .= self::substr($string, 0, $start);
193
        $ret .= $replacement;
194
        $ret .= self::substr($string, $start + $length);
195
        return $ret;
196
    }
197
198
    /**
199
     * Unicode aware replacement for ltrim()
200
     *
201
     * @param string $str
202
     * @param string $charlist
203
     * @return string
204
     * @see    ltrim()
205
     *
206
     * @author Andreas Gohr <[email protected]>
207
     */
208
    public static function ltrim($str, $charlist = '')
209
    {
210
        if ($charlist === '') return ltrim($str);
211
212
        //quote charlist for use in a characterclass
213
        $charlist = preg_replace('!([\\\\\\-\\]\\[/])!', '\\\${1}', $charlist);
214
215
        return preg_replace('/^[' . $charlist . ']+/u', '', $str);
216
    }
217
218
    /**
219
     * Unicode aware replacement for rtrim()
220
     *
221
     * @param string $str
222
     * @param string $charlist
223
     * @return string
224
     * @see    rtrim()
225
     *
226
     * @author Andreas Gohr <[email protected]>
227
     */
228
    public static function rtrim($str, $charlist = '')
229
    {
230
        if ($charlist === '') return rtrim($str);
231
232
        //quote charlist for use in a characterclass
233
        $charlist = preg_replace('!([\\\\\\-\\]\\[/])!', '\\\${1}', $charlist);
234
235
        return preg_replace('/[' . $charlist . ']+$/u', '', $str);
236
    }
237
238
    /**
239
     * Unicode aware replacement for trim()
240
     *
241
     * @param string $str
242
     * @param string $charlist
243
     * @return string
244
     * @see    trim()
245
     *
246
     * @author Andreas Gohr <[email protected]>
247
     */
248
    public static function trim($str, $charlist = '')
249
    {
250
        if ($charlist === '') return trim($str);
251
252
        return self::ltrim(self::rtrim($str, $charlist), $charlist);
253
    }
254
255
    /**
256
     * This is a unicode aware replacement for strtolower()
257
     *
258
     * Uses mb_string extension if available
259
     *
260
     * @param string $string
261
     * @return string
262
     * @see    \dokuwiki\Utf8\PhpString::strtoupper()
263
     *
264
     * @author Leo Feyer <[email protected]>
265
     * @see    strtolower()
266
     */
267
    public static function strtolower($string)
268
    {
269
        if (UTF8_MBSTRING) {
270
            if (class_exists('Normalizer', $autoload = false)) {
271
                return \Normalizer::normalize(mb_strtolower($string, 'utf-8'));
272
            }
273
            return (mb_strtolower($string, 'utf-8'));
274
        }
275
        return strtr($string, Table::upperCaseToLowerCase());
276
    }
277
278
    /**
279
     * This is a unicode aware replacement for strtoupper()
280
     *
281
     * Uses mb_string extension if available
282
     *
283
     * @param string $string
284
     * @return string
285
     * @see    \dokuwiki\Utf8\PhpString::strtoupper()
286
     *
287
     * @author Leo Feyer <[email protected]>
288
     * @see    strtoupper()
289
     */
290
    public static function strtoupper($string)
291
    {
292
        if (UTF8_MBSTRING) return mb_strtoupper($string, 'utf-8');
293
294
        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...
295
    }
296
297
298
    /**
299
     * UTF-8 aware alternative to ucfirst
300
     * Make a string's first character uppercase
301
     *
302
     * @param string $str
303
     * @return string with first character as upper case (if applicable)
304
     * @author Harry Fuecks
305
     *
306
     */
307
    public static function ucfirst($str)
308
    {
309
        switch (self::strlen($str)) {
310
            case 0:
311
                return '';
312
            case 1:
313
                return self::strtoupper($str);
314
            default:
315
                preg_match('/^(.{1})(.*)$/us', $str, $matches);
316
                return self::strtoupper($matches[1]) . $matches[2];
317
        }
318
    }
319
320
    /**
321
     * UTF-8 aware alternative to ucwords
322
     * Uppercase the first character of each word in a string
323
     *
324
     * @param string $str
325
     * @return string with first char of each word uppercase
326
     * @author Harry Fuecks
327
     * @see http://php.net/ucwords
328
     *
329
     */
330
    public static function ucwords($str)
331
    {
332
        // Note: [\x0c\x09\x0b\x0a\x0d\x20] matches;
333
        // form feeds, horizontal tabs, vertical tabs, linefeeds and carriage returns
334
        // This corresponds to the definition of a "word" defined at http://php.net/ucwords
335
        $pattern = '/(^|([\x0c\x09\x0b\x0a\x0d\x20]+))([^\x0c\x09\x0b\x0a\x0d\x20]{1})[^\x0c\x09\x0b\x0a\x0d\x20]*/u';
336
337
        return preg_replace_callback(
338
            $pattern,
339
            function ($matches) {
340
                $leadingws = $matches[2];
341
                $ucfirst = self::strtoupper($matches[3]);
342
                $ucword = self::substr_replace(ltrim($matches[0]), $ucfirst, 0, 1);
343
                return $leadingws . $ucword;
344
            },
345
            $str
346
        );
347
    }
348
349
    /**
350
     * This is an Unicode aware replacement for strpos
351
     *
352
     * @param string $haystack
353
     * @param string $needle
354
     * @param integer $offset
355
     * @return integer
356
     * @author Leo Feyer <[email protected]>
357
     * @see    strpos()
358
     *
359
     */
360
    public static function strpos($haystack, $needle, $offset = 0)
361
    {
362
        $comp = 0;
363
        $length = null;
364
365
        while ($length === null || $length < $offset) {
366
            $pos = strpos($haystack, $needle, $offset + $comp);
367
368
            if ($pos === false)
369
                return false;
370
371
            $length = self::strlen(substr($haystack, 0, $pos));
372
373
            if ($length < $offset)
374
                $comp = $pos - $length;
375
        }
376
377
        return $length;
378
    }
379
380
381
}
382