• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

JBZoo / Utils / 29770052588

20 Jul 2026 06:55PM UTC coverage: 92.619% (-0.1%) from 92.722%
29770052588

push

github

web-flow
Merge pull request #57 from JBZoo/release/8.0

8.0.0 — PHP 8.3+ floor & lock-step major

15 of 16 new or added lines in 7 files covered. (93.75%)

106 existing lines in 13 files now uncovered.

1669 of 1802 relevant lines covered (92.62%)

41.2 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.76
/src/Slug.php
1
<?php
2

3
/**
4
 * JBZoo Toolbox - Utils.
5
 *
6
 * This file is part of the JBZoo Toolbox project.
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 *
10
 * @license    MIT
11
 * @copyright  Copyright (C) JBZoo.com, All rights reserved.
12
 * @see        https://github.com/JBZoo/Utils
13
 */
14

15
declare(strict_types=1);
16

17
namespace JBZoo\Utils;
18

19
/**
20
 * @psalm-suppress UnusedClass
21
 */
22
final class Slug
23
{
24
    // The map to convert characters to ASCII characters.
25
    public static array $maps = [
26
        'de' => [// German
27
            'Ä' => 'Ae',
28
            'Ö' => 'Oe',
29
            'Ü' => 'Ue',
30
            'ä' => 'ae',
31
            'ö' => 'oe',
32
            'ü' => 'ue',
33
            'ß' => 'ss',
34
            'ẞ' => 'SS',
35
        ],
36

37
        'latin' => [
38
            'À' => 'A',
39
            'Á' => 'A',
40
            'Â' => 'A',
41
            'Ã' => 'A',
42
            'Ä' => 'A',
43
            'Å' => 'A',
44
            'Ă' => 'A',
45
            'Æ' => 'AE',
46
            'Ç' => 'C',
47
            'È' => 'E',
48
            'É' => 'E',
49
            'Ê' => 'E',
50
            'Ë' => 'E',
51
            'Ì' => 'I',
52
            'Í' => 'I',
53
            'Î' => 'I',
54
            'Ï' => 'I',
55
            'Ð' => 'D',
56
            'Ñ' => 'N',
57
            'Ò' => 'O',
58
            'Ó' => 'O',
59
            'Ô' => 'O',
60
            'Õ' => 'O',
61
            'Ö' => 'O',
62
            'Ő' => 'O',
63
            'Ø' => 'O',
64
            'Ș' => 'S',
65
            'Ț' => 'T',
66
            'Ù' => 'U',
67
            'Ú' => 'U',
68
            'Û' => 'U',
69
            'Ü' => 'U',
70
            'Ű' => 'U',
71
            'Ý' => 'Y',
72
            'Þ' => 'TH',
73
            'ß' => 'ss',
74
            'à' => 'a',
75
            'á' => 'a',
76
            'â' => 'a',
77
            'ã' => 'a',
78
            'ä' => 'a',
79
            'å' => 'a',
80
            'ă' => 'a',
81
            'æ' => 'ae',
82
            'ç' => 'c',
83
            'è' => 'e',
84
            'é' => 'e',
85
            'ê' => 'e',
86
            'ë' => 'e',
87
            'ì' => 'i',
88
            'í' => 'i',
89
            'î' => 'i',
90
            'ï' => 'i',
91
            'ð' => 'd',
92
            'ñ' => 'n',
93
            'ò' => 'o',
94
            'ó' => 'o',
95
            'ô' => 'o',
96
            'õ' => 'o',
97
            'ö' => 'o',
98
            'ő' => 'o',
99
            'ø' => 'o',
100
            'ș' => 's',
101
            'ț' => 't',
102
            'ù' => 'u',
103
            'ú' => 'u',
104
            'û' => 'u',
105
            'ü' => 'u',
106
            'ű' => 'u',
107
            'ý' => 'y',
108
            'þ' => 'th',
109
            'ÿ' => 'y',
110
        ],
111

112
        'latin_symbols' => [
113
            '©' => '(c)',
114
        ],
115

116
        'el' => [// Greek
117
            'α' => 'a',
118
            'β' => 'b',
119
            'γ' => 'g',
120
            'δ' => 'd',
121
            'ε' => 'e',
122
            'ζ' => 'z',
123
            'η' => 'h',
124
            'θ' => '8',
125
            'ι' => 'i',
126
            'κ' => 'k',
127
            'λ' => 'l',
128
            'μ' => 'm',
129
            'ν' => 'n',
130
            'ξ' => '3',
131
            'ο' => 'o',
132
            'π' => 'p',
133
            'ρ' => 'r',
134
            'σ' => 's',
135
            'τ' => 't',
136
            'υ' => 'y',
137
            'φ' => 'f',
138
            'χ' => 'x',
139
            'ψ' => 'ps',
140
            'ω' => 'w',
141
            'ά' => 'a',
142
            'έ' => 'e',
143
            'ί' => 'i',
144
            'ό' => 'o',
145
            'ύ' => 'y',
146
            'ή' => 'h',
147
            'ώ' => 'w',
148
            'ς' => 's',
149
            'ϊ' => 'i',
150
            'ΰ' => 'y',
151
            'ϋ' => 'y',
152
            'ΐ' => 'i',
153
            'Α' => 'A',
154
            'Β' => 'B',
155
            'Γ' => 'G',
156
            'Δ' => 'D',
157
            'Ε' => 'E',
158
            'Ζ' => 'Z',
159
            'Η' => 'H',
160
            'Θ' => '8',
161
            'Ι' => 'I',
162
            'Κ' => 'K',
163
            'Λ' => 'L',
164
            'Μ' => 'M',
165
            'Ν' => 'N',
166
            'Ξ' => '3',
167
            'Ο' => 'O',
168
            'Π' => 'P',
169
            'Ρ' => 'R',
170
            'Σ' => 'S',
171
            'Τ' => 'T',
172
            'Υ' => 'Y',
173
            'Φ' => 'F',
174
            'Χ' => 'X',
175
            'Ψ' => 'PS',
176
            'Ω' => 'W',
177
            'Ά' => 'A',
178
            'Έ' => 'E',
179
            'Ί' => 'I',
180
            'Ό' => 'O',
181
            'Ύ' => 'Y',
182
            'Ή' => 'H',
183
            'Ώ' => 'W',
184
            'Ϊ' => 'I',
185
            'Ϋ' => 'Y',
186
        ],
187

188
        'tr' => [// Turkish
189
            'ş' => 's',
190
            'Ş' => 'S',
191
            'ı' => 'i',
192
            'İ' => 'I',
193
            'ç' => 'c',
194
            'Ç' => 'C',
195
            'ü' => 'u',
196
            'Ü' => 'U',
197
            'ö' => 'o',
198
            'Ö' => 'O',
199
            'ğ' => 'g',
200
            'Ğ' => 'G',
201
        ],
202

203
        'bg' => [// Bulgarian
204
            'Щ' => 'Sht',
205
            'Ш' => 'Sh',
206
            'Ч' => 'Ch',
207
            'Ц' => 'C',
208
            'Ю' => 'Yu',
209
            'Я' => 'Ya',
210
            'Ж' => 'J',
211
            'А' => 'A',
212
            'Б' => 'B',
213
            'В' => 'V',
214
            'Г' => 'G',
215
            'Д' => 'D',
216
            'Е' => 'E',
217
            'З' => 'Z',
218
            'И' => 'I',
219
            'Й' => 'Y',
220
            'К' => 'K',
221
            'Л' => 'L',
222
            'М' => 'M',
223
            'Н' => 'N',
224
            'О' => 'O',
225
            'П' => 'P',
226
            'Р' => 'R',
227
            'С' => 'S',
228
            'Т' => 'T',
229
            'У' => 'U',
230
            'Ф' => 'F',
231
            'Х' => 'H',
232
            'Ь' => '',
233
            'Ъ' => 'A',
234
            'щ' => 'sht',
235
            'ш' => 'sh',
236
            'ч' => 'ch',
237
            'ц' => 'c',
238
            'ю' => 'yu',
239
            'я' => 'ya',
240
            'ж' => 'j',
241
            'а' => 'a',
242
            'б' => 'b',
243
            'в' => 'v',
244
            'г' => 'g',
245
            'д' => 'd',
246
            'е' => 'e',
247
            'з' => 'z',
248
            'и' => 'i',
249
            'й' => 'y',
250
            'к' => 'k',
251
            'л' => 'l',
252
            'м' => 'm',
253
            'н' => 'n',
254
            'о' => 'o',
255
            'п' => 'p',
256
            'р' => 'r',
257
            'с' => 's',
258
            'т' => 't',
259
            'у' => 'u',
260
            'ф' => 'f',
261
            'х' => 'h',
262
            'ь' => '',
263
            'ъ' => 'a',
264
        ],
265

266
        'ru' => [// Russian
267
            'а' => 'a',
268
            'б' => 'b',
269
            'в' => 'v',
270
            'г' => 'g',
271
            'д' => 'd',
272
            'е' => 'e',
273
            'ё' => 'yo',
274
            'ж' => 'zh',
275
            'з' => 'z',
276
            'и' => 'i',
277
            'й' => 'j',
278
            'к' => 'k',
279
            'л' => 'l',
280
            'м' => 'm',
281
            'н' => 'n',
282
            'о' => 'o',
283
            'п' => 'p',
284
            'р' => 'r',
285
            'с' => 's',
286
            'т' => 't',
287
            'у' => 'u',
288
            'ф' => 'f',
289
            'х' => 'h',
290
            'ц' => 'c',
291
            'ч' => 'ch',
292
            'ш' => 'sh',
293
            'щ' => 'sh',
294
            'ъ' => '',
295
            'ы' => 'y',
296
            'ь' => '',
297
            'э' => 'e',
298
            'ю' => 'yu',
299
            'я' => 'ya',
300
            'А' => 'A',
301
            'Б' => 'B',
302
            'В' => 'V',
303
            'Г' => 'G',
304
            'Д' => 'D',
305
            'Е' => 'E',
306
            'Ё' => 'Yo',
307
            'Ж' => 'Zh',
308
            'З' => 'Z',
309
            'И' => 'I',
310
            'Й' => 'J',
311
            'К' => 'K',
312
            'Л' => 'L',
313
            'М' => 'M',
314
            'Н' => 'N',
315
            'О' => 'O',
316
            'П' => 'P',
317
            'Р' => 'R',
318
            'С' => 'S',
319
            'Т' => 'T',
320
            'У' => 'U',
321
            'Ф' => 'F',
322
            'Х' => 'H',
323
            'Ц' => 'C',
324
            'Ч' => 'Ch',
325
            'Ш' => 'Sh',
326
            'Щ' => 'Sh',
327
            'Ъ' => '',
328
            'Ы' => 'Y',
329
            'Ь' => '',
330
            'Э' => 'E',
331
            'Ю' => 'Yu',
332
            'Я' => 'Ya',
333
            '№' => '',
334
        ],
335

336
        'uk' => [// Ukrainian
337
            'Є' => 'Ye',
338
            'І' => 'I',
339
            'Ї' => 'Yi',
340
            'Ґ' => 'G',
341
            'є' => 'ye',
342
            'і' => 'i',
343
            'ї' => 'yi',
344
            'ґ' => 'g',
345
        ],
346

347
        'cs' => [// Czech
348
            'č' => 'c',
349
            'ď' => 'd',
350
            'ě' => 'e',
351
            'ň' => 'n',
352
            'ř' => 'r',
353
            'š' => 's',
354
            'ť' => 't',
355
            'ů' => 'u',
356
            'ž' => 'z',
357
            'Č' => 'C',
358
            'Ď' => 'D',
359
            'Ě' => 'E',
360
            'Ň' => 'N',
361
            'Ř' => 'R',
362
            'Š' => 'S',
363
            'Ť' => 'T',
364
            'Ů' => 'U',
365
            'Ž' => 'Z',
366
        ],
367

368
        'pl' => [// Polish
369
            'ą' => 'a',
370
            'ć' => 'c',
371
            'ę' => 'e',
372
            'ł' => 'l',
373
            'ń' => 'n',
374
            'ó' => 'o',
375
            'ś' => 's',
376
            'ź' => 'z',
377
            'ż' => 'z',
378
            'Ą' => 'A',
379
            'Ć' => 'C',
380
            'Ę' => 'e',
381
            'Ł' => 'L',
382
            'Ń' => 'N',
383
            'Ó' => 'O',
384
            'Ś' => 'S',
385
            'Ź' => 'Z',
386
            'Ż' => 'Z',
387
        ],
388

389
        'ro' => [// Romanian
390
            'ă' => 'a',
391
            'â' => 'a',
392
            'î' => 'i',
393
            'ș' => 's',
394
            'ț' => 't',
395
            'Ţ' => 'T',
396
            'ţ' => 't',
397
        ],
398

399
        'lv' => [// Latvian
400
            'ā' => 'a',
401
            'č' => 'c',
402
            'ē' => 'e',
403
            'ģ' => 'g',
404
            'ī' => 'i',
405
            'ķ' => 'k',
406
            'ļ' => 'l',
407
            'ņ' => 'n',
408
            'š' => 's',
409
            'ū' => 'u',
410
            'ž' => 'z',
411
            'Ā' => 'A',
412
            'Č' => 'C',
413
            'Ē' => 'E',
414
            'Ģ' => 'G',
415
            'Ī' => 'i',
416
            'Ķ' => 'k',
417
            'Ļ' => 'L',
418
            'Ņ' => 'N',
419
            'Š' => 'S',
420
            'Ū' => 'u',
421
            'Ž' => 'Z',
422
        ],
423

424
        'lt' => [// Lithuanian
425
            'ą' => 'a',
426
            'č' => 'c',
427
            'ę' => 'e',
428
            'ė' => 'e',
429
            'į' => 'i',
430
            'š' => 's',
431
            'ų' => 'u',
432
            'ū' => 'u',
433
            'ž' => 'z',
434
            'Ą' => 'A',
435
            'Č' => 'C',
436
            'Ę' => 'E',
437
            'Ė' => 'E',
438
            'Į' => 'I',
439
            'Š' => 'S',
440
            'Ų' => 'U',
441
            'Ū' => 'U',
442
            'Ž' => 'Z',
443
        ],
444

445
        'vn' => [// Vietnamese
446
            'Á' => 'A',
447
            'À' => 'A',
448
            'Ả' => 'A',
449
            'Ã' => 'A',
450
            'Ạ' => 'A',
451
            'Ă' => 'A',
452
            'Ắ' => 'A',
453
            'Ằ' => 'A',
454
            'Ẳ' => 'A',
455
            'Ẵ' => 'A',
456
            'Ặ' => 'A',
457
            'Â' => 'A',
458
            'Ấ' => 'A',
459
            'Ầ' => 'A',
460
            'Ẩ' => 'A',
461
            'Ẫ' => 'A',
462
            'Ậ' => 'A',
463
            'á' => 'a',
464
            'à' => 'a',
465
            'ả' => 'a',
466
            'ã' => 'a',
467
            'ạ' => 'a',
468
            'ă' => 'a',
469
            'ắ' => 'a',
470
            'ằ' => 'a',
471
            'ẳ' => 'a',
472
            'ẵ' => 'a',
473
            'ặ' => 'a',
474
            'â' => 'a',
475
            'ấ' => 'a',
476
            'ầ' => 'a',
477
            'ẩ' => 'a',
478
            'ẫ' => 'a',
479
            'ậ' => 'a',
480
            'É' => 'E',
481
            'È' => 'E',
482
            'Ẻ' => 'E',
483
            'Ẽ' => 'E',
484
            'Ẹ' => 'E',
485
            'Ê' => 'E',
486
            'Ế' => 'E',
487
            'Ề' => 'E',
488
            'Ể' => 'E',
489
            'Ễ' => 'E',
490
            'Ệ' => 'E',
491
            'é' => 'e',
492
            'è' => 'e',
493
            'ẻ' => 'e',
494
            'ẽ' => 'e',
495
            'ẹ' => 'e',
496
            'ê' => 'e',
497
            'ế' => 'e',
498
            'ề' => 'e',
499
            'ể' => 'e',
500
            'ễ' => 'e',
501
            'ệ' => 'e',
502
            'Í' => 'I',
503
            'Ì' => 'I',
504
            'Ỉ' => 'I',
505
            'Ĩ' => 'I',
506
            'Ị' => 'I',
507
            'í' => 'i',
508
            'ì' => 'i',
509
            'ỉ' => 'i',
510
            'ĩ' => 'i',
511
            'ị' => 'i',
512
            'Ó' => 'O',
513
            'Ò' => 'O',
514
            'Ỏ' => 'O',
515
            'Õ' => 'O',
516
            'Ọ' => 'O',
517
            'Ô' => 'O',
518
            'Ố' => 'O',
519
            'Ồ' => 'O',
520
            'Ổ' => 'O',
521
            'Ỗ' => 'O',
522
            'Ộ' => 'O',
523
            'Ơ' => 'O',
524
            'Ớ' => 'O',
525
            'Ờ' => 'O',
526
            'Ở' => 'O',
527
            'Ỡ' => 'O',
528
            'Ợ' => 'O',
529
            'ó' => 'o',
530
            'ò' => 'o',
531
            'ỏ' => 'o',
532
            'õ' => 'o',
533
            'ọ' => 'o',
534
            'ô' => 'o',
535
            'ố' => 'o',
536
            'ồ' => 'o',
537
            'ổ' => 'o',
538
            'ỗ' => 'o',
539
            'ộ' => 'o',
540
            'ơ' => 'o',
541
            'ớ' => 'o',
542
            'ờ' => 'o',
543
            'ở' => 'o',
544
            'ỡ' => 'o',
545
            'ợ' => 'o',
546
            'Ú' => 'U',
547
            'Ù' => 'U',
548
            'Ủ' => 'U',
549
            'Ũ' => 'U',
550
            'Ụ' => 'U',
551
            'Ư' => 'U',
552
            'Ứ' => 'U',
553
            'Ừ' => 'U',
554
            'Ử' => 'U',
555
            'Ữ' => 'U',
556
            'Ự' => 'U',
557
            'ú' => 'u',
558
            'ù' => 'u',
559
            'ủ' => 'u',
560
            'ũ' => 'u',
561
            'ụ' => 'u',
562
            'ư' => 'u',
563
            'ứ' => 'u',
564
            'ừ' => 'u',
565
            'ử' => 'u',
566
            'ữ' => 'u',
567
            'ự' => 'u',
568
            'Ý' => 'Y',
569
            'Ỳ' => 'Y',
570
            'Ỷ' => 'Y',
571
            'Ỹ' => 'Y',
572
            'Ỵ' => 'Y',
573
            'ý' => 'y',
574
            'ỳ' => 'y',
575
            'ỷ' => 'y',
576
            'ỹ' => 'y',
577
            'ỵ' => 'y',
578
            'Đ' => 'D',
579
            'đ' => 'd',
580
        ],
581

582
        'ar' => [// Arabic
583
            'أ' => 'a',
584
            'ب' => 'b',
585
            'ت' => 't',
586
            'ث' => 'th',
587
            'ج' => 'g',
588
            'ح' => 'h',
589
            'خ' => 'kh',
590
            'د' => 'd',
591
            'ذ' => 'th',
592
            'ر' => 'r',
593
            'ز' => 'z',
594
            'س' => 's',
595
            'ش' => 'sh',
596
            'ص' => 's',
597
            'ض' => 'd',
598
            'ط' => 't',
599
            'ظ' => 'th',
600
            'ع' => 'aa',
601
            'غ' => 'gh',
602
            'ف' => 'f',
603
            'ق' => 'k',
604
            'ك' => 'k',
605
            'ل' => 'l',
606
            'م' => 'm',
607
            'ن' => 'n',
608
            'ه' => 'h',
609
            'و' => 'o',
610
            'ي' => 'y',
611
        ],
612

613
        'sr' => [// Serbian
614
            'ђ' => 'dj',
615
            'ј' => 'j',
616
            'љ' => 'lj',
617
            'њ' => 'nj',
618
            'ћ' => 'c',
619
            'џ' => 'dz',
620
            'đ' => 'dj',
621
            'Ђ' => 'Dj',
622
            'Ј' => 'j',
623
            'Љ' => 'Lj',
624
            'Њ' => 'Nj',
625
            'Ћ' => 'C',
626
            'Џ' => 'Dz',
627
            'Đ' => 'Dj',
628
        ],
629

630
        'az' => [// Azerbaijani
631
            'ç' => 'c',
632
            'ə' => 'e',
633
            'ğ' => 'g',
634
            'ı' => 'i',
635
            'ö' => 'o',
636
            'ş' => 's',
637
            'ü' => 'u',
638
            'Ç' => 'C',
639
            'Ə' => 'E',
640
            'Ğ' => 'G',
641
            'İ' => 'I',
642
            'Ö' => 'O',
643
            'Ş' => 'S',
644
            'Ü' => 'U',
645
        ],
646
    ];
647

648
    /** The character map. */
649
    private static array $map = [];
650

651
    /** The character list as a string. */
652
    private static string $chars = '';
653

654
    /** The character list as a regular expression. */
655
    private static string $regex = '';
656

657
    /** The current language. */
658
    private static string $language = '';
659

660
    /**
661
     * Converts any accent characters to their equivalent normal characters and converts any other non-alphanumeric
662
     * characters to dashes, then converts any sequence of two or more dashes to a single dash. This function generates
663
     * slugs safe for use as URLs, and if you pass true as the second parameter, it will create strings safe for
664
     * use as CSS classes or IDs.
665
     * @param null|string $string    A string to convert to a slug
666
     * @param string      $separator The string to separate words with
667
     * @param bool        $cssMode   Whether to generate strings safe for CSS classes/IDs (Default to false)
668
     */
669
    public static function filter(?string $string, string $separator = '-', bool $cssMode = false): string
670
    {
671
        $slug = (string)\preg_replace('/([^a-z0-9]+)/', $separator, \strtolower(self::removeAccents((string)$string)));
24✔
672
        $slug = \trim($slug, $separator);
24✔
673

674
        if ($cssMode) {
24✔
675
            $firstLetter = (int)$slug[0];
6✔
676
            $digits      = ['zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine'];
6✔
677
            if (isset($digits[$firstLetter])) {
6✔
678
                $slug = $digits[$firstLetter] . $separator . \substr($slug, 1);
6✔
679
            }
680
        }
681

682
        return $slug;
24✔
683
    }
684

685
    /**
686
     * Checks to see if a string is utf8 encoded.
687
     * NOTE: This function checks for 5-Byte sequences, UTF8 has Bytes Sequences with a maximum length of 4.
688
     * Written by Tony Ferrara <http://blog.ircmaxwell.com>.
689
     * @param string $string The string to be checked
690
     */
691
    public static function seemsUTF8(string $string): bool
692
    {
693
        if (Str::isMBString()) {
12✔
694
            // If mbstring is available, this is significantly faster than
695
            // using PHP regexps.
696
            return \mb_check_encoding($string, Str::$encoding);
12✔
697
        }
698

UNCOV
699
        return self::seemsUtf8Regex($string);
×
700
    }
701

702
    /**
703
     * Transliterates characters to their ASCII equivalents.
704
     * Part of the URLify.php Project <https://github.com/jbroadway/urlify/>.
705
     * @see https://github.com/jbroadway/urlify/blob/master/URLify.php
706
     * @param  string $text     Text that might have not-ASCII characters
707
     * @param  string $language specifies a priority for a specific language
708
     * @return string Filtered string with replaced "nice" characters
709
     */
710
    public static function downCode(string $text, string $language = ''): string
711
    {
712
        self::initLanguageMap($language);
12✔
713

714
        if (self::$regex !== '' && self::seemsUTF8($text)) {
12✔
715
            if (\preg_match_all(self::$regex, $text, $matches) > 0) {
12✔
716
                $matchesCount = \count($matches[0]);
12✔
717

718
                /** @noinspection ForeachInvariantsInspection */
719
                for ($i = 0; $i < $matchesCount; $i++) {
12✔
720
                    $char = $matches[0][$i];
12✔
721

722
                    if (\array_key_exists($char, self::$map)) {
12✔
723
                        $text = \str_replace($char, self::$map[$char], $text);
12✔
724
                    }
725
                }
726
            }
727
        } else {
728
            // Not a UTF-8 string so we assume its ISO-8859-1
729
            $search = "\x80\x83\x8a\x8e\x9a\x9e\x9f\xa2\xa5\xb5\xc0\xc1\xc2\xc3\xc4\xc5\xc7\xc8\xc9\xca\xcb\xcc\xcd";
6✔
730
            $search .= "\xce\xcf\xd1\xd2\xd3\xd4\xd5\xd6\xd8\xd9\xda\xdb\xdc\xdd\xe0\xe1\xe2\xe3\xe4\xe5\xe7\xe8\xe9";
6✔
731
            $search .= "\xea\xeb\xec\xed\xee\xef\xf1\xf2\xf3\xf4\xf5\xf6\xf8\xf9\xfa\xfb\xfc\xfd\xff";
6✔
732
            $text = \strtr($text, $search, 'EfSZszYcYuAAAAAACEEEEIIIINOOOOOOUUUUYaaaaaaceeeeiiiinoooooouuuuyy');
6✔
733

734
            // These latin characters should be represented by two characters, so
735
            // we can't use strtr
736
            $complexSearch  = ["\x8c", "\x9c", "\xc6", "\xd0", "\xde", "\xdf", "\xe6", "\xf0", "\xfe"];
6✔
737
            $complexReplace = ['OE', 'oe', 'AE', 'DH', 'TH', 'ss', 'ae', 'dh', 'th'];
6✔
738
            $text           = \str_replace($complexSearch, $complexReplace, $text);
6✔
739
        }
740

741
        return $text;
12✔
742
    }
743

744
    /**
745
     * Converts all accent characters to ASCII characters.
746
     * If there are no accent characters, then the string given is just returned.
747
     * @param  string $string   Text that might have accent characters
748
     * @param  string $language specifies a priority for a specific language
749
     * @return string Filtered  string with replaced "nice" characters
750
     */
751
    public static function removeAccents(string $string, string $language = ''): string
752
    {
753
        if (\preg_match('/[\x80-\xff]/', $string) === 0) {
42✔
754
            return $string;
30✔
755
        }
756

757
        return self::downCode($string, $language);
12✔
758
    }
759

760
    /**
761
     * Initializes the character map.
762
     * Part of the URLify.php Project <https://github.com/jbroadway/urlify/>.
763
     * @see https://github.com/jbroadway/urlify/blob/master/URLify.php
764
     */
765
    private static function initLanguageMap(string $language = ''): void
766
    {
767
        if ((isStrEmpty($language) || $language === self::$language) && \count(self::$map) > 0) {
12✔
768
            return;
6✔
769
        }
770

771
        // Is a specific map associated with $language?
772

773
        if (\array_key_exists($language, self::$maps) && \is_array(self::$maps[$language])) {
12✔
774
            // Move this map to end. This means it will have priority over others
775
            $langMap = self::$maps[$language];
6✔
776
            unset(self::$maps[$language]);
6✔
777
            self::$maps[$language] = $langMap;
6✔
778
        }
779

780
        // Reset static vars
781
        self::$language = $language;
12✔
782
        self::$map      = [];
12✔
783
        self::$chars    = '';
12✔
784

785
        foreach (self::$maps as $map) {
12✔
786
            foreach ((array)$map as $orig => $conv) {
12✔
787
                self::$map[$orig] = $conv;
12✔
788
                self::$chars .= $orig;
12✔
789
            }
790
        }
791

792
        self::$regex = '/[' . self::$chars . ']/u';
12✔
793
    }
794

795
    /**
796
     * A non-Mbstring UTF-8 checker.
797
     * @see http://stackoverflow.com/a/11709412/430062
798
     */
799
    private static function seemsUtf8Regex(string $string): bool
800
    {
UNCOV
801
        $regex = '/(
×
802
            [\xC0-\xC1]                                                         # Invalid UTF-8 Bytes
803
            | [\xF5-\xFF]                                                       # Invalid UTF-8 Bytes
804
            | \xE0[\x80-\x9F]                                                   # Overlong encoding of prior code point
805
            | \xF0[\x80-\x8F]                                                   # Overlong encoding of prior code point
806
            | [\xC2-\xDF](?![\x80-\xBF])                                        # Invalid UTF-8 Sequence Start
807
            | [\xE0-\xEF](?![\x80-\xBF]{2})                                     # Invalid UTF-8 Sequence Start
808
            | [\xF0-\xF4](?![\x80-\xBF]{3})                                     # Invalid UTF-8 Sequence Start
809
            | (?<=[\x0-\x7F\xF5-\xFF])[\x80-\xBF]                               # Invalid UTF-8 Sequence Middle
UNCOV
810
            | (?<![\xC2-\xDF]|[\xE0-\xEF]|[\xE0-\xEF][\x80-\xBF]|[\xF0-\xF4]|'
×
UNCOV
811
            . '[\xF0-\xF4][\x80-\xBF]|[\xF0-\xF4][\x80-\xBF]{2})[\x80-\xBF]     # Overlong Sequence
×
812
            | (?<=[\xE0-\xEF])[\x80-\xBF](?![\x80-\xBF])                        # Short 3 byte sequence
813
            | (?<=[\xF0-\xF4])[\x80-\xBF](?![\x80-\xBF]{2})                     # Short 4 byte sequence
814
            | (?<=[\xF0-\xF4][\x80-\xBF])[\x80-\xBF](?![\x80-\xBF])             # Short 4 byte sequence (2)
UNCOV
815
        )/x';
×
816

UNCOV
817
        return \preg_match($regex, $string) === 0;
×
818
    }
819
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc