Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
// This file is part of Moodle - http://moodle.org/
3
//
4
// Moodle is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// Moodle is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
 
17
namespace core;
18
 
19
/**
20
 * Tests for Moodle's String Formatter.
21
 *
22
 * @package   core
23
 * @copyright 2023 Andrew Nicols <andrew@nicols.co.uk>
24
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25
 * @covers    \core\formatting
26
 * @coversDefaultClass \core\formatting
27
 */
28
class formatting_test extends \advanced_testcase {
29
    /**
30
     * @covers ::format_string
31
     */
32
    public function test_format_string_striptags_cfg(): void {
33
        global $CFG;
34
 
35
        $this->resetAfterTest();
36
 
37
        $formatting = new formatting();
38
 
39
        // Check < and > signs.
40
        $CFG->formatstringstriptags = false;
41
        $this->assertSame('x &lt; 1', $formatting->format_string('x < 1'));
42
        $this->assertSame('x &gt; 1', $formatting->format_string('x > 1'));
43
        $this->assertSame('x &lt; 1 and x &gt; 0', $formatting->format_string('x < 1 and x > 0'));
44
 
45
        $CFG->formatstringstriptags = true;
46
        $this->assertSame('x &lt; 1', $formatting->format_string('x < 1'));
47
        $this->assertSame('x &gt; 1', $formatting->format_string('x > 1'));
48
        $this->assertSame('x &lt; 1 and x &gt; 0', $formatting->format_string('x < 1 and x > 0'));
49
    }
50
 
51
    /**
52
     * @covers ::format_string
53
     */
54
    public function test_format_string_striptags_prop(): void {
55
        $formatting = new formatting();
56
 
57
        // Check < and > signs.
58
        $formatting->set_striptags(false);
59
        $this->assertSame('x &lt; 1', $formatting->format_string('x < 1'));
60
        $this->assertSame('x &gt; 1', $formatting->format_string('x > 1'));
61
        $this->assertSame('x &lt; 1 and x &gt; 0', $formatting->format_string('x < 1 and x > 0'));
62
 
63
        $formatting->set_striptags(true);
64
        $this->assertSame('x &lt; 1', $formatting->format_string('x < 1'));
65
        $this->assertSame('x &gt; 1', $formatting->format_string('x > 1'));
66
        $this->assertSame('x &lt; 1 and x &gt; 0', $formatting->format_string('x < 1 and x > 0'));
67
    }
68
 
69
    /**
70
     * @covers ::format_string
71
     * @dataProvider format_string_provider
72
     * @param string $expected
73
     * @param mixed $input
74
     * @param array $options
75
     */
76
    public function test_format_string_values(
77
        string $expected,
78
        array $params,
79
    ): void {
80
        $formatting = new formatting();
81
        $this->assertSame(
82
            $expected,
83
            $formatting->format_string(...$params),
84
        );
85
    }
86
 
87
    /**
88
     * Data provider for format_string tests.
89
     *
90
     * @return array
91
     */
92
    public static function format_string_provider(): array {
93
        return [
94
            // Ampersands.
95
            [
96
                'expected' => "&amp; &amp;&amp;&amp;&amp;&amp; &amp;&amp;",
97
                'params' => ["& &&&&& &&"],
98
            ],
99
            [
100
                'expected' => "ANother &amp; &amp;&amp;&amp;&amp;&amp; Category",
101
                'params' => ["ANother & &&&&& Category"],
102
            ],
103
            [
104
                'expected' => "ANother &amp; &amp;&amp;&amp;&amp;&amp; Category",
105
                'params' => [
106
                    'string' => "ANother & &&&&& Category",
107
                    'striplinks' => true,
108
                ],
109
            ],
110
            [
111
                'expected' => "Nick's Test Site &amp; Other things",
112
                'params' => [
113
                    'string' => "Nick's Test Site & Other things",
114
                    'striplinks' => true,
115
                ],
116
            ],
117
            [
118
                'expected' => "& < > \" '",
119
                'params' => [
120
                    'string' => "& < > \" '",
121
                    'striplinks' => true,
122
                    'escape' => false,
123
                ],
124
            ],
125
 
126
            // String entities.
127
            [
128
                'expected' => "&quot;",
129
                'params' => ["&quot;"],
130
            ],
131
 
132
            // Digital entities.
133
            [
134
                'expected' => "&11234;",
135
                'params' => ["&11234;"],
136
            ],
137
 
138
            // Unicode entities.
139
            [
140
                'expected' => "&#4475;",
141
                'params' => ["&#4475;"],
142
            ],
143
 
144
            // Nulls.
145
            ['', [null]],
146
            [
147
                'expected' => '',
148
                'params' => [
149
                    'string' => null,
150
                    'striplinks' => true,
151
                    'escape' => false,
152
                ],
153
            ],
154
        ];
155
    }
156
 
157
    /**
158
     * The format string static caching should include the filters option to make
159
     * sure filters are correctly applied when requested.
160
     */
161
    public function test_format_string_static_caching_with_filters(): void {
162
        global $CFG;
163
 
164
        $this->resetAfterTest(true);
165
        $this->setAdminUser();
166
        $generator = $this->getDataGenerator();
167
        $course = $generator->create_course();
168
        $user = $generator->create_user();
169
 
170
        $rawstring = '<span lang="en" class="multilang">English</span><span lang="ca" class="multilang">Catalan</span>';
171
        $expectednofilter = strip_tags($rawstring);
172
        $expectedfilter = 'English';
173
        $context = \core\context\course::instance($course->id);
174
        $options = [
175
            'striplinks' => true,
176
            'context' => $context,
177
            'escape' => true,
178
            'filter' => false,
179
        ];
180
 
181
        $this->setUser($user);
182
 
183
        $formatting = new formatting();
184
 
185
        // Format the string without filters. It should just strip the
186
        // links.
187
        $nofilterresult = $formatting->format_string($rawstring, ...$options);
188
        $this->assertEquals($expectednofilter, $nofilterresult);
189
 
190
        // Add the multilang filter. Make sure it's enabled globally.
191
        $CFG->stringfilters = 'multilang';
192
        filter_set_global_state('multilang', TEXTFILTER_ON);
193
        filter_set_local_state('multilang', $context->id, TEXTFILTER_ON);
194
 
195
        // Even after setting the filters, no filters are applied yet.
196
        $nofilterresult = $formatting->format_string($rawstring,...$options);
197
        $this->assertEquals($expectednofilter, $nofilterresult);
198
 
199
        // Apply the filter as an option.
200
        $options['filter'] = true;
201
        $filterresult = $formatting->format_string($rawstring,  ...$options);
202
        $this->assertMatchesRegularExpression("/$expectedfilter/", $filterresult);
203
 
204
        // Apply it as a formatting setting.
205
        unset($options['filter']);
206
        $formatting->set_filterall(true);
207
        $filterresult = $formatting->format_string($rawstring,  ...$options);
208
        $this->assertMatchesRegularExpression("/$expectedfilter/", $filterresult);
209
 
210
        // Unset it and we do not filter.
211
        $formatting->set_filterall(false);
212
        $nofilterresult = $formatting->format_string($rawstring,  ...$options);
213
        $this->assertEquals($expectednofilter, $nofilterresult);
214
 
215
        // Set it again.
216
        $formatting->set_filterall(true);
217
        filter_set_local_state('multilang', $context->id, TEXTFILTER_OFF);
218
 
219
        // Confirm that we get back the cached string. The result should be
220
        // the same as the filtered text above even though we've disabled the
221
        // multilang filter in between.
222
        $cachedresult = $formatting->format_string($rawstring, ...$options);
223
        $this->assertMatchesRegularExpression("/$expectedfilter/", $cachedresult);
224
    }
225
 
226
    /**
227
     * Test trust option of format_text().
228
     *
229
     * @covers ::format_text
230
     * @dataProvider format_text_trusted_provider
231
     */
232
    public function test_format_text_trusted(
233
        $expected,
234
        int $enabletrusttext,
235
        mixed $input,
236
        // Yes... FORMAT_ constants are strings of ints.
237
        string $format,
238
        array $options = [],
239
    ): void {
240
        global $CFG;
241
        $this->resetAfterTest();
242
 
243
        $CFG->enabletrusttext = $enabletrusttext;
244
 
245
        $formatter = new formatting();
246
        $this->assertEquals(
247
            $expected,
248
            $formatter->format_text($input, $format, ...$options),
249
        );
250
    }
251
 
252
    public static function format_text_trusted_provider(): array {
253
        $text = "lala <object>xx</object>";
254
        return [
255
            [
256
                s($text),
257
                0,
258
                $text,
259
                FORMAT_PLAIN,
260
                ['trusted' => true],
261
            ],
262
            [
263
                "<p>lala xx</p>\n",
264
                0,
265
                $text,
266
                FORMAT_MARKDOWN,
267
                ['trusted' => true],
268
            ],
269
            [
270
                '<div class="text_to_html">lala xx</div>',
271
                0,
272
                $text,
273
                FORMAT_MOODLE,
274
                ['trusted' => true],
275
            ],
276
            [
277
                'lala xx',
278
                0,
279
                $text,
280
                FORMAT_HTML,
281
                ['trusted' => true],
282
            ],
283
 
284
            [
285
                s($text),
286
                0,
287
                $text,
288
                FORMAT_PLAIN,
289
                ['trusted' => false],
290
            ],
291
            [
292
                "<p>lala xx</p>\n",
293
                0,
294
                $text,
295
                FORMAT_MARKDOWN,
296
                ['trusted' => false],
297
            ],
298
            [
299
                '<div class="text_to_html">lala xx</div>',
300
                0,
301
                $text,
302
                FORMAT_MOODLE,
303
                ['trusted' => false],
304
            ],
305
            [
306
                'lala xx',
307
                0,
308
                $text,
309
                FORMAT_HTML,
310
                ['trusted' => false],
311
            ],
312
 
313
            [
314
                s($text),
315
                1,
316
                $text,
317
                FORMAT_PLAIN,
318
                ['trusted' => true],
319
            ],
320
            [
321
                "<p>lala xx</p>\n",
322
                1,
323
                $text,
324
                FORMAT_MARKDOWN,
325
                ['trusted' => true],
326
            ],
327
            [
328
                '<div class="text_to_html">lala <object>xx</object></div>',
329
                1,
330
                $text,
331
                FORMAT_MOODLE,
332
                ['trusted' => true],
333
            ],
334
            [
335
                'lala <object>xx</object>',
336
                1,
337
                $text,
338
                FORMAT_HTML,
339
                ['trusted' => true],
340
            ],
341
 
342
            [
343
                s($text),
344
                1,
345
                $text,
346
                FORMAT_PLAIN,
347
                ['trusted' => false],
348
            ],
349
            [
350
                "<p>lala xx</p>\n",
351
                1,
352
                $text,
353
                FORMAT_MARKDOWN,
354
                ['trusted' => false],
355
            ],
356
            [
357
                '<div class="text_to_html">lala xx</div>',
358
                1,
359
                $text,
360
                FORMAT_MOODLE,
361
                ['trusted' => false],
362
            ],
363
            [
364
                'lala xx',
365
                1,
366
                $text,
367
                FORMAT_HTML,
368
                ['trusted' => false],
369
            ],
370
 
371
            [
372
                "<p>lala <object>xx</object></p>\n",
373
                1,
374
                $text,
375
                FORMAT_MARKDOWN,
376
                ['trusted' => true, 'clean' => false],
377
            ],
378
            [
379
                "<p>lala <object>xx</object></p>\n",
380
                1,
381
                $text,
382
                FORMAT_MARKDOWN,
383
                ['trusted' => false, 'clean' => false],
384
            ],
385
        ];
386
    }
387
 
388
    public function test_format_text_format_html(): void {
389
        $this->resetAfterTest();
390
        $formatter = new formatting();
391
 
392
        filter_set_global_state('emoticon', TEXTFILTER_ON);
393
        $this->assertMatchesRegularExpression(
394
            '~^<p><img class="icon emoticon" alt="smile" title="smile" ' .
395
                'src="https://www.example.com/moodle/theme/image.php/boost/core/1/s/smiley" /></p>$~',
396
            $formatter->format_text('<p>:-)</p>', FORMAT_HTML)
397
        );
398
    }
399
 
400
    public function test_format_text_format_html_no_filters(): void {
401
        $this->resetAfterTest();
402
        $formatter = new formatting();
403
 
404
        filter_set_global_state('emoticon', TEXTFILTER_ON);
405
        $this->assertEquals(
406
            '<p>:-)</p>',
407
            $formatter->format_text(
408
                '<p>:-)</p>',
409
                FORMAT_HTML,
410
                filter: false,
411
            )
412
        );
413
    }
414
 
415
    public function test_format_text_format_plain(): void {
416
        // Note FORMAT_PLAIN does not filter ever, no matter we ask for filtering.
417
        $this->resetAfterTest();
418
        $formatter = new formatting();
419
 
420
        filter_set_global_state('emoticon', TEXTFILTER_ON);
421
        $this->assertEquals(
422
            ':-)',
423
            $formatter->format_text(':-)', FORMAT_PLAIN)
424
        );
425
    }
426
 
427
    public function test_format_text_format_plain_no_filters(): void {
428
        $this->resetAfterTest();
429
        $formatter = new formatting();
430
 
431
        filter_set_global_state('emoticon', TEXTFILTER_ON);
432
        $this->assertEquals(
433
            ':-)',
434
            $formatter->format_text(
435
                ':-)',
436
                FORMAT_PLAIN,
437
                filter: false,
438
            )
439
        );
440
    }
441
 
442
    public function test_format_text_format_markdown(): void {
443
        $this->resetAfterTest();
444
        $formatter = new formatting();
445
 
446
        filter_set_global_state('emoticon', TEXTFILTER_ON);
447
        $this->assertMatchesRegularExpression(
448
            '~^<p><em><img class="icon emoticon" alt="smile" title="smile" ' .
449
                'src="https://www.example.com/moodle/theme/image.php/boost/core/1/s/smiley" />' .
450
                '</em></p>\n$~',
451
            $formatter->format_text('*:-)*', FORMAT_MARKDOWN)
452
        );
453
    }
454
 
455
    public function test_format_text_format_markdown_nofilter(): void {
456
        $this->resetAfterTest();
457
        $formatter = new formatting();
458
 
459
        filter_set_global_state('emoticon', TEXTFILTER_ON);
460
        $this->assertEquals(
461
            "<p><em>:-)</em></p>\n",
462
            $formatter->format_text('*:-)*', FORMAT_MARKDOWN, filter: false)
463
        );
464
    }
465
 
466
    public function test_format_text_format_moodle(): void {
467
        $this->resetAfterTest();
468
        $formatter = new formatting();
469
 
470
        filter_set_global_state('emoticon', TEXTFILTER_ON);
471
        $this->assertMatchesRegularExpression(
472
            '~^<div class="text_to_html"><p>' .
473
                '<img class="icon emoticon" alt="smile" title="smile" ' .
474
                'src="https://www.example.com/moodle/theme/image.php/boost/core/1/s/smiley" /></p></div>$~',
475
            $formatter->format_text('<p>:-)</p>', FORMAT_MOODLE)
476
        );
477
    }
478
 
479
    public function test_format_text_format_moodle_no_filters(): void {
480
        $this->resetAfterTest();
481
        $formatter = new formatting();
482
 
483
        filter_set_global_state('emoticon', TEXTFILTER_ON);
484
        $this->assertEquals(
485
            '<div class="text_to_html"><p>:-)</p></div>',
486
            $formatter->format_text('<p>:-)</p>', FORMAT_MOODLE, filter: false)
487
        );
488
    }
489
 
490
    /**
491
     * Make sure that nolink tags and spans prevent linking in filters that support it.
492
     */
493
    public function test_format_text_nolink(): void {
494
        global $CFG;
495
        $this->resetAfterTest();
496
        $formatter = new formatting();
497
 
498
        filter_set_global_state('activitynames', TEXTFILTER_ON);
499
 
500
        $course = $this->getDataGenerator()->create_course();
501
        $context = \context_course::instance($course->id);
502
        $page = $this->getDataGenerator()->create_module(
503
            'page',
504
            ['course' => $course->id, 'name' => 'Test 1'],
505
        );
506
        $cm = get_coursemodule_from_instance('page', $page->id, $page->course, false, MUST_EXIST);
507
        $pageurl = $CFG->wwwroot . '/mod/page/view.php?id=' . $cm->id;
508
 
509
        $this->assertSame(
510
            '<p>Read <a class="autolink" title="Test 1" href="' . $pageurl . '">Test 1</a>.</p>',
511
            $formatter->format_text('<p>Read Test 1.</p>', FORMAT_HTML, context: $context),
512
        );
513
 
514
        $this->assertSame(
515
            '<p>Read <a class="autolink" title="Test 1" href="' . $pageurl . '">Test 1</a>.</p>',
516
            $formatter->format_text(
517
                '<p>Read Test 1.</p>',
518
                FORMAT_HTML,
519
                context: $context,
520
                clean: false,
521
            ),
522
        );
523
 
524
        $this->assertSame(
525
            '<p>Read Test 1.</p>',
526
            $formatter->format_text(
527
                '<p><nolink>Read Test 1.</nolink></p>',
528
                FORMAT_HTML,
529
                context: $context,
530
                clean: true,
531
            ),
532
        );
533
 
534
        $this->assertSame(
535
            '<p>Read Test 1.</p>',
536
            $formatter->format_text(
537
                '<p><nolink>Read Test 1.</nolink></p>',
538
                FORMAT_HTML,
539
                context: $context,
540
                clean: false,
541
            ),
542
        );
543
 
544
        $this->assertSame(
545
            '<p><span class="nolink">Read Test 1.</span></p>',
546
            $formatter->format_text(
547
                '<p><span class="nolink">Read Test 1.</span></p>',
548
                FORMAT_HTML,
549
                context: $context,
550
            ),
551
        );
552
    }
553
 
554
    public function test_format_text_overflowdiv(): void {
555
        $formatter = new formatting();
556
 
557
        $this->assertEquals(
558
            '<div class="no-overflow"><p>Hello world</p></div>',
559
            $formatter->format_text(
560
                '<p>Hello world</p>',
561
                FORMAT_HTML,
562
                overflowdiv: true,
563
            ),
564
        );
565
    }
566
 
567
    /**
568
     * Test adding blank target attribute to links
569
     *
570
     * @dataProvider format_text_blanktarget_testcases
571
     * @param string $link The link to add target="_blank" to
572
     * @param string $expected The expected filter value
573
     */
574
    public function test_format_text_blanktarget($link, $expected): void {
575
        $formatter = new formatting();
576
        $actual = $formatter->format_text(
577
            $link,
578
            FORMAT_MOODLE,
579
            blanktarget: true,
580
            filter: false,
581
            clean: false,
582
        );
583
        $this->assertEquals($expected, $actual);
584
    }
585
 
586
    /**
587
     * Data provider for the test_format_text_blanktarget testcase
588
     *
589
     * @return array of testcases
590
     */
591
    public static function format_text_blanktarget_testcases(): array {
592
        return [
593
            'Simple link' => [
594
                '<a href="https://www.youtube.com/watch?v=JeimE8Wz6e4">Hey, that\'s pretty good!</a>',
595
                '<div class="text_to_html"><a href="https://www.youtube.com/watch?v=JeimE8Wz6e4" target="_blank"' .
596
                    ' rel="noreferrer">Hey, that\'s pretty good!</a></div>',
597
            ],
598
            'Link with rel' => [
599
                '<a href="https://www.youtube.com/watch?v=JeimE8Wz6e4" rel="nofollow">Hey, that\'s pretty good!</a>',
600
                '<div class="text_to_html"><a href="https://www.youtube.com/watch?v=JeimE8Wz6e4" rel="nofollow noreferrer"' .
601
                    ' target="_blank">Hey, that\'s pretty good!</a></div>',
602
            ],
603
            'Link with rel noreferrer' => [
604
                '<a href="https://www.youtube.com/watch?v=JeimE8Wz6e4" rel="noreferrer">Hey, that\'s pretty good!</a>',
605
                '<div class="text_to_html"><a href="https://www.youtube.com/watch?v=JeimE8Wz6e4" rel="noreferrer"' .
606
                    ' target="_blank">Hey, that\'s pretty good!</a></div>',
607
            ],
608
            'Link with target' => [
609
                '<a href="https://www.youtube.com/watch?v=JeimE8Wz6e4" target="_self">Hey, that\'s pretty good!</a>',
610
                '<div class="text_to_html"><a href="https://www.youtube.com/watch?v=JeimE8Wz6e4" target="_self">' .
611
                    'Hey, that\'s pretty good!</a></div>',
612
            ],
613
            'Link with target blank' => [
614
                '<a href="https://www.youtube.com/watch?v=JeimE8Wz6e4" target="_blank">Hey, that\'s pretty good!</a>',
615
                '<div class="text_to_html"><a href="https://www.youtube.com/watch?v=JeimE8Wz6e4" target="_blank"' .
616
                    ' rel="noreferrer">Hey, that\'s pretty good!</a></div>',
617
            ],
618
            'Link with Frank\'s casket inscription' => [
619
                // phpcs:ignore moodle.Files.LineLength
620
                '<a href="https://en.wikipedia.org/wiki/Franks_Casket">ᚠᛁᛋᚳ᛫ᚠᛚᚩᛞᚢ᛫ᚪᚻᚩᚠᚩᚾᚠᛖᚱᚷ ᛖᚾᛒᛖᚱᛁᚷ ᚹᚪᚱᚦᚷᚪ᛬ᛋᚱᛁᚳᚷᚱᚩᚱᚾᚦᚫᚱᚻᛖᚩᚾᚷᚱᛖᚢᛏᚷᛁᛋᚹᚩᛗ ᚻ' .
621
                    'ᚱᚩᚾᚫᛋᛒᚪᚾ ᛗᚫᚷᛁᚠᛁᛋᚳ᛫ᚠᛚᚩᛞᚢ᛫ᚪᚻᚩᚠᚩᚾᚠᛖᚱᚷ ᛖᚾᛒᛖᚱᛁᚷ ᚹᚪᚱᚦᚷᚪ᛬ᛋᚱᛁᚳᚷᚱᚩᚱᚾᚦᚫᚱᚻᛖᚩᚾᚷᚱᛖᚢᛏᚷᛁᛋᚹᚩᛗ ᚻᚱᚩᚾᚫᛋᛒᚪᚾ ᛗᚫᚷᛁ</a>',
622
                    '<div class="text_to_html"><a href="https://en.wikipedia.org/wiki/Franks_Casket" target="_blank" ' .
623
                    // phpcs:ignore moodle.Files.LineLength
624
                    'rel="noreferrer">ᚠᛁᛋᚳ᛫ᚠᛚᚩᛞᚢ᛫ᚪᚻᚩᚠᚩᚾᚠᛖᚱᚷ ᛖᚾᛒᛖᚱᛁᚷ ᚹᚪᚱᚦᚷᚪ᛬ᛋᚱᛁᚳᚷᚱᚩᚱᚾᚦᚫᚱᚻᛖᚩᚾᚷᚱᛖᚢᛏᚷᛁᛋᚹᚩᛗ ᚻᚱᚩᚾᚫᛋᛒᚪᚾ ᛗᚫᚷᛁᚠᛁᛋᚳ᛫ᚠᛚᚩᛞᚢ᛫ᚪᚻᚩᚠᚩᚾᚠᛖᚱᚷ ᛖᚾ' .
625
                    'ᛒᛖᚱᛁᚷ ᚹᚪᚱᚦᚷᚪ᛬ᛋᚱᛁᚳᚷᚱᚩᚱᚾᚦᚫᚱᚻᛖᚩᚾᚷᚱᛖᚢᛏᚷᛁᛋᚹᚩᛗ ᚻᚱᚩᚾᚫᛋᛒᚪᚾ ᛗᚫᚷᛁ</a></div>',
626
            ],
627
            'No link' => [
628
                'Some very boring text written with the Latin script',
629
                '<div class="text_to_html">Some very boring text written with the Latin script</div>',
630
            ],
631
            'No link with Thror\'s map runes' => [
632
                // phpcs:ignore moodle.Files.LineLength
633
                'ᛋᛏᚫᚾᛞ ᛒᚣ ᚦᛖ ᚷᚱᛖᚣ ᛋᛏᚩᚾᛖ ᚻᚹᛁᛚᛖ ᚦᛖ ᚦᚱᚢᛋᚻ ᚾᚩᚳᛋ ᚫᚾᛞ ᚦᛖ ᛋᛖᛏᛏᛁᚾᚷ ᛋᚢᚾ ᚹᛁᚦ ᚦᛖ ᛚᚫᛋᛏ ᛚᛁᚷᚻᛏ ᚩᚠ ᛞᚢᚱᛁᚾᛋ ᛞᚫᚣ ᚹᛁᛚᛚ ᛋᚻᛁᚾᛖ ᚢᛈᚩᚾ ᚦᛖ ᚳᛖᚣᚻᚩᛚᛖ',
634
                // phpcs:ignore moodle.Files.LineLength
635
                '<div class="text_to_html">ᛋᛏᚫᚾᛞ ᛒᚣ ᚦᛖ ᚷᚱᛖᚣ ᛋᛏᚩᚾᛖ ᚻᚹᛁᛚᛖ ᚦᛖ ᚦᚱᚢᛋᚻ ᚾᚩᚳᛋ ᚫᚾᛞ ᚦᛖ ᛋᛖᛏᛏᛁᚾᚷ ᛋᚢᚾ ᚹᛁᚦ ᚦᛖ ᛚᚫᛋᛏ ᛚᛁᚷᚻᛏ ᚩᚠ ᛞᚢᚱᛁᚾᛋ ᛞᚫᚣ ᚹ' .
636
                    'ᛁᛚᛚ ᛋᚻᛁᚾᛖ ᚢᛈᚩᚾ ᚦᛖ ᚳᛖᚣᚻᚩᛚᛖ</div>',
637
            ],
638
        ];
639
    }
640
 
641
    /**
642
     * Test ability to force cleaning of otherwise non-cleaned content.
643
     *
644
     * @dataProvider format_text_cleaning_testcases
645
     *
646
     * @param string $input Input text
647
     * @param string $nocleaned Expected output of format_text() with noclean=true
648
     * @param string $cleaned Expected output of format_text() with noclean=false
649
     */
650
    public function test_format_text_cleaning($input, $nocleaned, $cleaned): void {
651
        $formatter = new formatting();
652
 
653
        $formatter->set_forceclean(false);
654
        $actual = $formatter->format_text($input, FORMAT_HTML, filter: false, clean: true);
655
        $this->assertEquals($cleaned, $actual);
656
 
657
        $formatter->set_forceclean(true);
658
        $actual = $formatter->format_text($input, FORMAT_HTML, filter: false, clean: true);
659
        $this->assertEquals($cleaned, $actual);
660
 
661
        $formatter->set_forceclean(false);
662
        $actual = $formatter->format_text($input, FORMAT_HTML, filter: false, clean: false);
663
        $this->assertEquals($nocleaned, $actual);
664
 
665
        $formatter->set_forceclean(true);
666
        $actual = $formatter->format_text($input, FORMAT_HTML, filter: false, clean: false);
667
        $this->assertEquals($cleaned, $actual);
668
    }
669
 
670
    /**
671
     * Data provider for the test_format_text_cleaning testcase
672
     *
673
     * @return array of testcases (string)testcasename => [(string)input, (string)nocleaned, (string)cleaned]
674
     */
675
    public static function format_text_cleaning_testcases(): array {
676
        return [
677
            'JavaScript' => [
678
                'Hello <script type="text/javascript">alert("XSS");</script> world',
679
                'Hello <script type="text/javascript">alert("XSS");</script> world',
680
                'Hello  world',
681
            ],
682
            'Inline frames' => [
683
                'Let us go phishing! <iframe src="https://1.2.3.4/google.com"></iframe>',
684
                'Let us go phishing! <iframe src="https://1.2.3.4/google.com"></iframe>',
685
                'Let us go phishing! ',
686
            ],
687
            'Malformed A tags' => [
688
                '<a onmouseover="alert(document.cookie)">xxs link</a>',
689
                '<a onmouseover="alert(document.cookie)">xxs link</a>',
690
                '<a>xxs link</a>',
691
            ],
692
            'Malformed IMG tags' => [
693
                '<IMG """><SCRIPT>alert("XSS")</SCRIPT>">',
694
                '<IMG """><SCRIPT>alert("XSS")</SCRIPT>">',
695
                '"&gt;',
696
            ],
697
            'On error alert' => [
698
                '<IMG SRC=/ onerror="alert(String.fromCharCode(88,83,83))"></img>',
699
                '<IMG SRC=/ onerror="alert(String.fromCharCode(88,83,83))"></img>',
700
                '<img src="/" alt="" />',
701
            ],
702
            'IMG onerror and javascript alert encode' => [
703
                '<img src=x onerror="&#0000106&#0000097&#0000118&#0000097&#0000115&#0000099&#0000083&#0000083&#0000039&#0000041">',
704
                '<img src=x onerror="&#0000106&#0000097&#0000118&#0000097&#0000115&#0000099&#0000083&#0000083&#0000039&#0000041">',
705
                '<img src="x" alt="x" />',
706
            ],
707
            'DIV background-image' => [
708
                '<DIV STYLE="background-image: url(javascript:alert(\'XSS\'))">',
709
                '<DIV STYLE="background-image: url(javascript:alert(\'XSS\'))">',
710
                '<div></div>',
711
            ],
712
        ];
713
    }
714
}