[TASK] Streamline formatting of html templates
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Tests / Unit / DataHandling / SlugHelperTest.php
1 <?php
2 declare(strict_types = 1);
3
4 namespace TYPO3\CMS\Core\Tests\Unit\DataHandling;
5
6 /*
7 * This file is part of the TYPO3 CMS project.
8 *
9 * It is free software; you can redistribute it and/or modify it under
10 * the terms of the GNU General Public License, either version 2
11 * of the License, or any later version.
12 *
13 * For the full copyright and license information, please read the
14 * LICENSE.txt file that was distributed with this source code.
15 *
16 * The TYPO3 project - inspiring people to share!
17 */
18
19 use TYPO3\CMS\Core\DataHandling\SlugHelper;
20 use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
21
22 class SlugHelperTest extends UnitTestCase
23 {
24 /**
25 * @var bool
26 */
27 protected $resetSingletonInstances = true;
28
29 /**
30 * @return array
31 */
32 public function sanitizeDataProvider(): array
33 {
34 return [
35 'empty string' => [
36 [],
37 '',
38 '',
39 ],
40 'existing base' => [
41 [],
42 '/',
43 '',
44 ],
45 'invalid base' => [
46 [],
47 '//',
48 '',
49 ],
50 'invalid slug' => [
51 [],
52 '/slug//',
53 'slug/',
54 ],
55 'lowercase characters' => [
56 [],
57 '1AZÄ',
58 '1azae',
59 ],
60 'strig tags' => [
61 [],
62 '<foo>bar</foo>',
63 'bar'
64 ],
65 'replace special chars to -' => [
66 [],
67 '1 2-3+4_5',
68 '1-2-3-4-5',
69 ],
70 'empty fallback character' => [
71 [
72 'fallbackCharacter' => '',
73 ],
74 '1_2',
75 '12',
76 ],
77 'different fallback character' => [
78 [
79 'fallbackCharacter' => '_',
80 ],
81 '1-2',
82 '1_2',
83 ],
84 'convert umlauts' => [
85 [],
86 'ä ß Ö',
87 'ae-ss-oe'
88 ],
89 'keep slashes' => [
90 [],
91 '1/2',
92 '1/2',
93 ],
94 'keep pending slash' => [
95 [],
96 '/1/2',
97 '1/2',
98 ],
99 'do not remove trailing slash' => [
100 [],
101 '1/2/',
102 '1/2/',
103 ],
104 'keep pending slash and remove fallback' => [
105 [],
106 '/-1/2',
107 '1/2',
108 ],
109 'do not remove trailing slash, but remove fallback' => [
110 [],
111 '1/2-/',
112 '1/2/',
113 ],
114 'reduce multiple fallback chars to one' => [
115 [],
116 '1---2',
117 '1-2',
118 ],
119 'various special chars' => [
120 [],
121 'special-chars-«-∑-€-®-†-Ω-¨-ø-π-å-‚-∂-ƒ-©-ª-º-∆-@-¥-≈-ç-√-∫-~-µ-∞-…-–',
122 'special-chars-eur-r-o-oe-p-aa-f-c-a-o-yen-c-u'
123 ],
124 'ensure colon and other http related parts are disallowed' => [
125 [],
126 'https://example.com:80/my/page/slug/',
127 'https//examplecom80/my/page/slug/'
128 ],
129 'non-ASCII characters are kept' => [
130 [],
131 'bla-arg应---用-ascii',
132 'bla-arg应-用-ascii'
133 ],
134 ];
135 }
136
137 /**
138 * @test
139 * @dataProvider sanitizeDataProvider
140 * @param array $configuration
141 * @param string $input
142 * @param string $expected
143 */
144 public function sanitizeConvertsString(array $configuration, string $input, string $expected)
145 {
146 $subject = new SlugHelper(
147 'dummyTable',
148 'dummyField',
149 $configuration
150 );
151 static::assertEquals(
152 $expected,
153 $subject->sanitize($input)
154 );
155 }
156
157 public function generateNeverDeliversEmptySlugDataProvider()
158 {
159 return [
160 'simple title' => [
161 'Products',
162 'products'
163 ],
164 'title with spaces' => [
165 'Product Cow',
166 'product-cow'
167 ],
168 'title with invalid characters' => [
169 'Products - Cows',
170 'products-cows'
171 ],
172 'title with only invalid characters' => [
173 '!!!',
174 'default-51cf35392c'
175 ],
176 ];
177 }
178
179 /**
180 * @dataProvider generateNeverDeliversEmptySlugDataProvider
181 * @param string $input
182 * @param string $expected
183 * @test
184 */
185 public function generateNeverDeliversEmptySlug(string $input, string $expected)
186 {
187 $GLOBALS['dummyTable']['ctrl'] = [];
188 $subject = new SlugHelper(
189 'dummyTable',
190 'dummyField',
191 ['generatorOptions' => ['fields' => ['title']]]
192 );
193 static::assertEquals(
194 $expected,
195 $subject->generate(['title' => $input, 'uid' => 13], 13)
196 );
197 }
198
199 /**
200 * @return array
201 */
202 public function sanitizeForPagesDataProvider(): array
203 {
204 return [
205 'empty string' => [
206 [],
207 '',
208 '/',
209 ],
210 'existing base' => [
211 [],
212 '/',
213 '/',
214 ],
215 'invalid base' => [
216 [],
217 '//',
218 '/',
219 ],
220 'invalid slug' => [
221 [],
222 '/slug//',
223 '/slug/',
224 ],
225 'lowercase characters' => [
226 [],
227 '1AZÄ',
228 '/1azae',
229 ],
230 'strig tags' => [
231 [],
232 '<foo>bar</foo>',
233 '/bar'
234 ],
235 'replace special chars to -' => [
236 [],
237 '1 2-3+4_5',
238 '/1-2-3-4-5',
239 ],
240 'empty fallback character' => [
241 [
242 'fallbackCharacter' => '',
243 ],
244 '1_2',
245 '/12',
246 ],
247 'different fallback character' => [
248 [
249 'fallbackCharacter' => '_',
250 ],
251 '1-2',
252 '/1_2',
253 ],
254 'convert umlauts' => [
255 [],
256 'ä ß Ö',
257 '/ae-ss-oe'
258 ],
259 'keep slashes' => [
260 [],
261 '1/2',
262 '/1/2',
263 ],
264 'keep pending slash' => [
265 [],
266 '/1/2',
267 '/1/2',
268 ],
269 'do not remove trailing slash' => [
270 [],
271 '1/2/',
272 '/1/2/',
273 ],
274 'keep pending slash and remove fallback' => [
275 [],
276 '/-1/2',
277 '/1/2',
278 ],
279 'do not remove trailing slash, but remove fallback' => [
280 [],
281 '1/2-/',
282 '/1/2/',
283 ],
284 'reduce multiple fallback chars to one' => [
285 [],
286 '1---2',
287 '/1-2',
288 ],
289 'various special chars' => [
290 [],
291 'special-chars-«-∑-€-®-†-Ω-¨-ø-π-å-‚-∂-ƒ-©-ª-º-∆-@-¥-≈-ç-√-∫-~-µ-∞-…-–',
292 '/special-chars-eur-r-o-oe-p-aa-f-c-a-o-yen-c-u'
293 ],
294 'ensure colon and other http related parts are disallowed' => [
295 [],
296 'https://example.com:80/my/page/slug/',
297 '/https//examplecom80/my/page/slug/'
298 ],
299 'chinese' => [
300 [],
301 '应用',
302 '/应用'
303 ],
304 ];
305 }
306
307 /**
308 * @test
309 * @dataProvider sanitizeForPagesDataProvider
310 * @param array $configuration
311 * @param string $input
312 * @param string $expected
313 */
314 public function sanitizeConvertsStringForPages(array $configuration, string $input, string $expected)
315 {
316 $subject = new SlugHelper(
317 'pages',
318 'slug',
319 $configuration
320 );
321 static::assertEquals(
322 $expected,
323 $subject->sanitize($input)
324 );
325 }
326
327 public function generateNeverDeliversEmptySlugForPagesDataProvider()
328 {
329 return [
330 'simple title' => [
331 'Products',
332 '/products'
333 ],
334 'title with spaces' => [
335 'Product Cow',
336 '/product-cow'
337 ],
338 'title with invalid characters' => [
339 'Products - Cows',
340 '/products-cows'
341 ],
342 'title with only invalid characters' => [
343 '!!!',
344 '/default-51cf35392c'
345 ],
346 ];
347 }
348
349 /**
350 * @dataProvider generateNeverDeliversEmptySlugForPagesDataProvider
351 * @param string $input
352 * @param string $expected
353 * @test
354 */
355 public function generateNeverDeliversEmptySlugForPages(string $input, string $expected)
356 {
357 $GLOBALS['dummyTable']['ctrl'] = [];
358 $subject = new SlugHelper(
359 'pages',
360 'slug',
361 ['generatorOptions' => ['fields' => ['title']]]
362 );
363 static::assertEquals(
364 $expected,
365 $subject->generate(['title' => $input, 'uid' => 13], 13)
366 );
367 }
368
369 /**
370 * @return array
371 */
372 public function generatePrependsSlugsForPagesDataProvider(): array
373 {
374 return [
375 'simple title' => [
376 'Products',
377 '/parent-page/products',
378 [
379 'generatorOptions' => [
380 'fields' => ['title'],
381 'prefixParentPageSlug' => true,
382 ],
383 ]
384 ],
385 'title with spaces' => [
386 'Product Cow',
387 '/parent-page/product-cow',
388 [
389 'generatorOptions' => [
390 'fields' => ['title'],
391 'prefixParentPageSlug' => true,
392 ],
393 ]
394 ],
395 'title with slash' => [
396 'Product/Cow',
397 '/parent-page/product/cow',
398 [
399 'generatorOptions' => [
400 'fields' => ['title'],
401 'prefixParentPageSlug' => true,
402 ],
403 ]
404 ],
405 'title with slash and replace' => [
406 'Product/Cow',
407 '/parent-page/productcow',
408 [
409 'generatorOptions' => [
410 'fields' => ['title'],
411 'prefixParentPageSlug' => true,
412 'replacements' => [
413 '/' => ''
414 ]
415 ],
416 ]
417 ],
418 'title with slash and replace #2' => [
419 'Some Job in city1/city2 (m/w)',
420 '/parent-page/some-job-in-city1-city2',
421 [
422 'generatorOptions' => [
423 'fields' => ['title'],
424 'prefixParentPageSlug' => true,
425 'replacements' => [
426 '(m/w)' => '',
427 '/' => '-'
428 ]
429 ],
430 ]
431 ],
432 'title with invalid characters' => [
433 'Products - Cows',
434 '/parent-page/products-cows',
435 [
436 'generatorOptions' => [
437 'fields' => ['title'],
438 'prefixParentPageSlug' => true,
439 ],
440 ]
441 ],
442 'title with only invalid characters' => [
443 '!!!',
444 '/parent-page/default-51cf35392c',
445 [
446 'generatorOptions' => [
447 'fields' => ['title'],
448 'prefixParentPageSlug' => true,
449 ],
450 ]
451 ],
452 ];
453 }
454
455 /**
456 * @dataProvider generatePrependsSlugsForPagesDataProvider
457 * @param string $input
458 * @param string $expected
459 * @test
460 */
461 public function generatePrependsSlugsForPages(string $input, string $expected, array $options)
462 {
463 $GLOBALS['dummyTable']['ctrl'] = [];
464 $parentPage = [
465 'uid' => '13',
466 'pid' => '10',
467 'title' => 'Parent Page',
468 ];
469 $subject = $this->getAccessibleMock(
470 SlugHelper::class,
471 ['resolveParentPageRecord'],
472 [
473 'pages',
474 'slug',
475 $options
476 ]
477 );
478 $subject->expects(static::at(0))
479 ->method('resolveParentPageRecord')->with(13)->willReturn($parentPage);
480 $subject->expects(static::at(1))
481 ->method('resolveParentPageRecord')->with(10)->willReturn(null);
482 static::assertEquals(
483 $expected,
484 $subject->generate(['title' => $input, 'uid' => 13], 13)
485 );
486 }
487
488 /**
489 * @return array
490 */
491 public function generateSlugWithNavTitleAndFallbackForPagesDataProvider(): array
492 {
493 return [
494 'title and empty nav_title' => [
495 ['title' => 'Products', 'nav_title' => '', 'subtitle' => ''],
496 '/products',
497 [
498 'generatorOptions' => [
499 'fields' => [
500 ['nav_title', 'title']
501 ],
502 ],
503 ]
504 ],
505 'title and nav_title' => [
506 ['title' => 'Products', 'nav_title' => 'Best products', 'subtitle' => ''],
507 '/best-products',
508 [
509 'generatorOptions' => [
510 'fields' => [
511 ['nav_title', 'title']
512 ],
513 ],
514 ]
515 ],
516 'title and nav_title and subtitle' => [
517 ['title' => 'Products', 'nav_title' => 'Best products', 'subtitle' => 'Product subtitle'],
518 '/product-subtitle',
519 [
520 'generatorOptions' => [
521 'fields' => [
522 ['subtitle', 'nav_title', 'title']
523 ],
524 ],
525 ]
526 ],
527 'definition with a non existing field (misconfiguration)' => [
528 ['title' => 'Products', 'nav_title' => '', 'subtitle' => ''],
529 '/products',
530 [
531 'generatorOptions' => [
532 'fields' => [
533 ['custom_field', 'title']
534 ],
535 ],
536 ]
537 ],
538 'empty fields deliver default slug' => [
539 ['title' => '', 'nav_title' => '', 'subtitle' => ''],
540 '/default-b4dac929c2',
541 [
542 'generatorOptions' => [
543 'fields' => [
544 ['nav_title', 'title']
545 ],
546 ],
547 ]
548 ],
549 'fallback combined with a second field' => [
550 ['title' => 'Products', 'nav_title' => 'Best products', 'subtitle' => 'Product subtitle'],
551 '/best-products/product-subtitle',
552 [
553 'generatorOptions' => [
554 'fields' => [
555 ['nav_title', 'title'], 'subtitle'
556 ],
557 ],
558 ]
559 ],
560 'empty config array deliver default slug' => [
561 ['title' => 'Products', 'nav_title' => 'Best products', 'subtitle' => 'Product subtitle'],
562 '/default-e13d142b36',
563 [
564 'generatorOptions' => [
565 'fields' => [
566 []
567 ],
568 ],
569 ]
570 ],
571 'empty config deliver default slug' => [
572 ['title' => 'Products', 'nav_title' => 'Best products', 'subtitle' => 'Product subtitle'],
573 '/default-e13d142b36',
574 [
575 'generatorOptions' => [
576 'fields' => [],
577 ],
578 ]
579 ],
580 'combine two fallbacks' => [
581 ['title' => 'Products', 'nav_title' => 'Best products', 'subtitle' => 'Product subtitle', 'seo_title' => 'SEO product title'],
582 '/seo-product-title/best-products',
583 [
584 'generatorOptions' => [
585 'fields' => [
586 ['seo_title', 'title'], ['nav_title', 'subtitle']
587 ],
588 ],
589 ]
590 ],
591 ];
592 }
593
594 /**
595 * @dataProvider generateSlugWithNavTitleAndFallbackForPagesDataProvider
596 * @param array $input
597 * @param string $expected
598 * @param array $options
599 * @test
600 */
601 public function generateSlugWithNavTitleAndFallbackForPages(array $input, string $expected, array $options)
602 {
603 $GLOBALS['dummyTable']['ctrl'] = [];
604 $subject = new SlugHelper(
605 'pages',
606 'slug',
607 ['generatorOptions' => $options['generatorOptions']]
608 );
609 static::assertEquals(
610 $expected,
611 $subject->generate([
612 'title' => $input['title'],
613 'nav_title' => $input['nav_title'],
614 'subtitle' => $input['subtitle'],
615 'seo_title' => $input['seo_title'] ?? '',
616 'uid' => 13
617 ], 13)
618 );
619 }
620 }