Merge branch 'MDL-62274-master' of git://github.com/bmbrands/moodle
[moodle.git] / privacy / tests / moodle_content_writer_test.php
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/>.
17 /**
18  * Unit Tests for the Moodle Content Writer.
19  *
20  * @package     core_privacy
21  * @category    test
22  * @copyright   2018 Andrew Nicols <andrew@nicols.co.uk>
23  * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24  */
26 defined('MOODLE_INTERNAL') || die();
28 global $CFG;
30 use \core_privacy\local\request\writer;
31 use \core_privacy\local\request\moodle_content_writer;
33 /**
34  * Tests for the \core_privacy API's moodle_content_writer functionality.
35  *
36  * @copyright   2018 Andrew Nicols <andrew@nicols.co.uk>
37  * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
38  */
39 class moodle_content_writer_test extends advanced_testcase {
41     /**
42      * Test that exported data is saved correctly within the system context.
43      *
44      * @dataProvider export_data_provider
45      * @param   \stdClass  $data Data
46      */
47     public function test_export_data($data) {
48         $context = \context_system::instance();
49         $subcontext = [];
51         $writer = $this->get_writer_instance()
52             ->set_context($context)
53             ->export_data($subcontext, $data);
55         $fileroot = $this->fetch_exported_content($writer);
57         $contextpath = $this->get_context_path($context, $subcontext, 'data.json');
58         $this->assertTrue($fileroot->hasChild($contextpath));
60         $json = $fileroot->getChild($contextpath)->getContent();
61         $expanded = json_decode($json);
62         $this->assertEquals($data, $expanded);
63     }
65     /**
66      * Test that exported data is saved correctly for context/subcontext.
67      *
68      * @dataProvider export_data_provider
69      * @param   \stdClass  $data Data
70      */
71     public function test_export_data_different_context($data) {
72         $context = \context_user::instance(\core_user::get_user_by_username('admin')->id);
73         $subcontext = ['sub', 'context'];
75         $writer = $this->get_writer_instance()
76             ->set_context($context)
77             ->export_data($subcontext, $data);
79         $fileroot = $this->fetch_exported_content($writer);
81         $contextpath = $this->get_context_path($context, $subcontext, 'data.json');
82         $this->assertTrue($fileroot->hasChild($contextpath));
84         $json = $fileroot->getChild($contextpath)->getContent();
85         $expanded = json_decode($json);
86         $this->assertEquals($data, $expanded);
87     }
89     /**
90      * Test that exported is saved within the correct directory locations.
91      */
92     public function test_export_data_writes_to_multiple_context() {
93         $subcontext = ['sub', 'context'];
95         $systemcontext = \context_system::instance();
96         $systemdata = (object) [
97             'belongsto' => 'system',
98         ];
99         $usercontext = \context_user::instance(\core_user::get_user_by_username('admin')->id);
100         $userdata = (object) [
101             'belongsto' => 'user',
102         ];
104         $writer = $this->get_writer_instance();
106         $writer
107             ->set_context($systemcontext)
108             ->export_data($subcontext, $systemdata);
110         $writer
111             ->set_context($usercontext)
112             ->export_data($subcontext, $userdata);
114         $fileroot = $this->fetch_exported_content($writer);
116         $contextpath = $this->get_context_path($systemcontext, $subcontext, 'data.json');
117         $this->assertTrue($fileroot->hasChild($contextpath));
119         $json = $fileroot->getChild($contextpath)->getContent();
120         $expanded = json_decode($json);
121         $this->assertEquals($systemdata, $expanded);
123         $contextpath = $this->get_context_path($usercontext, $subcontext, 'data.json');
124         $this->assertTrue($fileroot->hasChild($contextpath));
126         $json = $fileroot->getChild($contextpath)->getContent();
127         $expanded = json_decode($json);
128         $this->assertEquals($userdata, $expanded);
129     }
131     /**
132      * Test that multiple writes to the same location cause the latest version to be written.
133      */
134     public function test_export_data_multiple_writes_same_context() {
135         $subcontext = ['sub', 'context'];
137         $systemcontext = \context_system::instance();
138         $originaldata = (object) [
139             'belongsto' => 'system',
140         ];
142         $newdata = (object) [
143             'abc' => 'def',
144         ];
146         $writer = $this->get_writer_instance();
148         $writer
149             ->set_context($systemcontext)
150             ->export_data($subcontext, $originaldata);
152         $writer
153             ->set_context($systemcontext)
154             ->export_data($subcontext, $newdata);
156         $fileroot = $this->fetch_exported_content($writer);
158         $contextpath = $this->get_context_path($systemcontext, $subcontext, 'data.json');
159         $this->assertTrue($fileroot->hasChild($contextpath));
161         $json = $fileroot->getChild($contextpath)->getContent();
162         $expanded = json_decode($json);
163         $this->assertEquals($newdata, $expanded);
164     }
166     /**
167      * Data provider for exporting user data.
168      */
169     public function export_data_provider() {
170         return [
171             'basic' => [
172                 (object) [
173                     'example' => (object) [
174                         'key' => 'value',
175                     ],
176                 ],
177             ],
178         ];
179     }
181     /**
182      * Test that metadata can be set.
183      *
184      * @dataProvider export_metadata_provider
185      * @param   string  $key Key
186      * @param   string  $value Value
187      * @param   string  $description Description
188      */
189     public function test_export_metadata($key, $value, $description) {
190         $context = \context_system::instance();
191         $subcontext = ['a', 'b', 'c'];
193         $writer = $this->get_writer_instance()
194             ->set_context($context)
195             ->export_metadata($subcontext, $key, $value, $description);
197         $fileroot = $this->fetch_exported_content($writer);
199         $contextpath = $this->get_context_path($context, $subcontext, 'metadata.json');
200         $this->assertTrue($fileroot->hasChild($contextpath));
202         $json = $fileroot->getChild($contextpath)->getContent();
203         $expanded = json_decode($json);
204         $this->assertTrue(isset($expanded->$key));
205         $this->assertEquals($value, $expanded->$key->value);
206         $this->assertEquals($description, $expanded->$key->description);
207     }
209     /**
210      * Test that metadata can be set additively.
211      */
212     public function test_export_metadata_additive() {
213         $context = \context_system::instance();
214         $subcontext = [];
216         $writer = $this->get_writer_instance();
218         $writer
219             ->set_context($context)
220             ->export_metadata($subcontext, 'firstkey', 'firstvalue', 'firstdescription');
222         $writer
223             ->set_context($context)
224             ->export_metadata($subcontext, 'secondkey', 'secondvalue', 'seconddescription');
226         $fileroot = $this->fetch_exported_content($writer);
228         $contextpath = $this->get_context_path($context, $subcontext, 'metadata.json');
229         $this->assertTrue($fileroot->hasChild($contextpath));
231         $json = $fileroot->getChild($contextpath)->getContent();
232         $expanded = json_decode($json);
234         $this->assertTrue(isset($expanded->firstkey));
235         $this->assertEquals('firstvalue', $expanded->firstkey->value);
236         $this->assertEquals('firstdescription', $expanded->firstkey->description);
238         $this->assertTrue(isset($expanded->secondkey));
239         $this->assertEquals('secondvalue', $expanded->secondkey->value);
240         $this->assertEquals('seconddescription', $expanded->secondkey->description);
241     }
243     /**
244      * Test that metadata can be set additively.
245      */
246     public function test_export_metadata_to_multiple_contexts() {
247         $systemcontext = \context_system::instance();
248         $usercontext = \context_user::instance(\core_user::get_user_by_username('admin')->id);
249         $subcontext = [];
251         $writer = $this->get_writer_instance();
253         $writer
254             ->set_context($systemcontext)
255             ->export_metadata($subcontext, 'firstkey', 'firstvalue', 'firstdescription')
256             ->export_metadata($subcontext, 'secondkey', 'secondvalue', 'seconddescription');
258         $writer
259             ->set_context($usercontext)
260             ->export_metadata($subcontext, 'firstkey', 'alternativevalue', 'alternativedescription')
261             ->export_metadata($subcontext, 'thirdkey', 'thirdvalue', 'thirddescription');
263         $fileroot = $this->fetch_exported_content($writer);
265         $systemcontextpath = $this->get_context_path($systemcontext, $subcontext, 'metadata.json');
266         $this->assertTrue($fileroot->hasChild($systemcontextpath));
268         $json = $fileroot->getChild($systemcontextpath)->getContent();
269         $expanded = json_decode($json);
271         $this->assertTrue(isset($expanded->firstkey));
272         $this->assertEquals('firstvalue', $expanded->firstkey->value);
273         $this->assertEquals('firstdescription', $expanded->firstkey->description);
274         $this->assertTrue(isset($expanded->secondkey));
275         $this->assertEquals('secondvalue', $expanded->secondkey->value);
276         $this->assertEquals('seconddescription', $expanded->secondkey->description);
277         $this->assertFalse(isset($expanded->thirdkey));
279         $usercontextpath = $this->get_context_path($usercontext, $subcontext, 'metadata.json');
280         $this->assertTrue($fileroot->hasChild($usercontextpath));
282         $json = $fileroot->getChild($usercontextpath)->getContent();
283         $expanded = json_decode($json);
285         $this->assertTrue(isset($expanded->firstkey));
286         $this->assertEquals('alternativevalue', $expanded->firstkey->value);
287         $this->assertEquals('alternativedescription', $expanded->firstkey->description);
288         $this->assertFalse(isset($expanded->secondkey));
289         $this->assertTrue(isset($expanded->thirdkey));
290         $this->assertEquals('thirdvalue', $expanded->thirdkey->value);
291         $this->assertEquals('thirddescription', $expanded->thirdkey->description);
292     }
294     /**
295      * Data provider for exporting user metadata.
296      *
297      * return   array
298      */
299     public function export_metadata_provider() {
300         return [
301             'basic' => [
302                 'key',
303                 'value',
304                 'This is a description',
305             ],
306             'valuewithspaces' => [
307                 'key',
308                 'value has mixed',
309                 'This is a description',
310             ],
311             'encodedvalue' => [
312                 'key',
313                 base64_encode('value has mixed'),
314                 'This is a description',
315             ],
316         ];
317     }
319     /**
320      * Exporting a single stored_file should cause that file to be output in the files directory.
321      */
322     public function test_export_area_files() {
323         $this->resetAfterTest();
324         $context = \context_system::instance();
325         $fs = get_file_storage();
327         // Add two files to core_privacy::tests::0.
328         $files = [];
329         $file = (object) [
330             'component' => 'core_privacy',
331             'filearea' => 'tests',
332             'itemid' => 0,
333             'path' => '/',
334             'name' => 'a.txt',
335             'content' => 'Test file 0',
336         ];
337         $files[] = $file;
339         $file = (object) [
340             'component' => 'core_privacy',
341             'filearea' => 'tests',
342             'itemid' => 0,
343             'path' => '/sub/',
344             'name' => 'b.txt',
345             'content' => 'Test file 1',
346         ];
347         $files[] = $file;
349         // One with a different itemid.
350         $file = (object) [
351             'component' => 'core_privacy',
352             'filearea' => 'tests',
353             'itemid' => 1,
354             'path' => '/',
355             'name' => 'c.txt',
356             'content' => 'Other',
357         ];
358         $files[] = $file;
360         // One with a different filearea.
361         $file = (object) [
362             'component' => 'core_privacy',
363             'filearea' => 'alternative',
364             'itemid' => 0,
365             'path' => '/',
366             'name' => 'd.txt',
367             'content' => 'Alternative',
368         ];
369         $files[] = $file;
371         // One with a different component.
372         $file = (object) [
373             'component' => 'core',
374             'filearea' => 'tests',
375             'itemid' => 0,
376             'path' => '/',
377             'name' => 'e.txt',
378             'content' => 'Other tests',
379         ];
380         $files[] = $file;
382         foreach ($files as $file) {
383             $record = [
384                 'contextid' => $context->id,
385                 'component' => $file->component,
386                 'filearea'  => $file->filearea,
387                 'itemid'    => $file->itemid,
388                 'filepath'  => $file->path,
389                 'filename'  => $file->name,
390             ];
392             $file->namepath = '/' . $file->filearea . '/' . ($file->itemid ?: '') . $file->path . $file->name;
393             $file->storedfile = $fs->create_file_from_string($record, $file->content);
394         }
396         $writer = $this->get_writer_instance()
397             ->set_context($context)
398             ->export_area_files([], 'core_privacy', 'tests', 0);
400         $fileroot = $this->fetch_exported_content($writer);
402         $firstfiles = array_slice($files, 0, 2);
403         foreach ($firstfiles as $file) {
404             $contextpath = $this->get_context_path($context, ['_files'], $file->namepath);
405             $this->assertTrue($fileroot->hasChild($contextpath));
406             $this->assertEquals($file->content, $fileroot->getChild($contextpath)->getContent());
407         }
409         $otherfiles = array_slice($files, 2);
410         foreach ($otherfiles as $file) {
411             $contextpath = $this->get_context_path($context, ['_files'], $file->namepath);
412             $this->assertFalse($fileroot->hasChild($contextpath));
413         }
414     }
416     /**
417      * Exporting a single stored_file should cause that file to be output in the files directory.
418      *
419      * @dataProvider    export_file_provider
420      * @param   string  $filearea File area
421      * @param   int     $itemid Item ID
422      * @param   string  $filepath File path
423      * @param   string  $filename File name
424      * @param   string  $content Content
425      */
426     public function test_export_file($filearea, $itemid, $filepath, $filename, $content) {
427         $this->resetAfterTest();
428         $context = \context_system::instance();
429         $filenamepath = '/' . $filearea . '/' . ($itemid ?: '') . $filepath . $filename;
431         $filerecord = array(
432             'contextid' => $context->id,
433             'component' => 'core_privacy',
434             'filearea'  => $filearea,
435             'itemid'    => $itemid,
436             'filepath'  => $filepath,
437             'filename'  => $filename,
438         );
440         $fs = get_file_storage();
441         $file = $fs->create_file_from_string($filerecord, $content);
443         $writer = $this->get_writer_instance()
444             ->set_context($context)
445             ->export_file([], $file);
447         $fileroot = $this->fetch_exported_content($writer);
449         $contextpath = $this->get_context_path($context, ['_files'], $filenamepath);
450         $this->assertTrue($fileroot->hasChild($contextpath));
451         $this->assertEquals($content, $fileroot->getChild($contextpath)->getContent());
452     }
454     /**
455      * Data provider for the test_export_file function.
456      *
457      * @return  array
458      */
459     public function export_file_provider() {
460         return [
461             'basic' => [
462                 'intro',
463                 0,
464                 '/',
465                 'testfile.txt',
466                 'An example file content',
467             ],
468             'longpath' => [
469                 'attachments',
470                 '12',
471                 '/path/within/a/path/within/a/path/',
472                 'testfile.txt',
473                 'An example file content',
474             ],
475             'pathwithspaces' => [
476                 'intro',
477                 0,
478                 '/path with/some spaces/',
479                 'testfile.txt',
480                 'An example file content',
481             ],
482             'filewithspaces' => [
483                 'submission_attachments',
484                 1,
485                 '/path with/some spaces/',
486                 'test file.txt',
487                 'An example file content',
488             ],
489             'image' => [
490                 'intro',
491                 0,
492                 '/',
493                 'logo.png',
494                 file_get_contents(__DIR__ . '/fixtures/logo.png'),
495             ],
496             'UTF8' => [
497                 'submission_content',
498                 2,
499                 '/Žluťoučký/',
500                 'koníček.txt',
501                 'koníček',
502             ],
503             'EUC-JP' => [
504                 'intro',
505                 0,
506                 '/言語設定/',
507                 '言語設定.txt',
508                 '言語設定',
509             ],
510         ];
511     }
513     /**
514      * User preferences can be exported against a user.
515      *
516      * @dataProvider    export_user_preference_provider
517      * @param   string      $component  Component
518      * @param   string      $key Key
519      * @param   string      $value Value
520      * @param   string      $desc Description
521      */
522     public function test_export_user_preference_context_user($component, $key, $value, $desc) {
523         $admin = \core_user::get_user_by_username('admin');
525         $writer = $this->get_writer_instance();
527         $context = \context_user::instance($admin->id);
528         $writer = $this->get_writer_instance()
529             ->set_context($context)
530             ->export_user_preference($component, $key, $value, $desc);
532         $fileroot = $this->fetch_exported_content($writer);
534         $contextpath = $this->get_context_path($context, [get_string('userpreferences')], "{$component}.json");
535         $this->assertTrue($fileroot->hasChild($contextpath));
537         $json = $fileroot->getChild($contextpath)->getContent();
538         $expanded = json_decode($json);
539         $this->assertTrue(isset($expanded->$key));
540         $data = $expanded->$key;
541         $this->assertEquals($value, $data->value);
542         $this->assertEquals($desc, $data->description);
543     }
545     /**
546      * User preferences can be exported against a course category.
547      *
548      * @dataProvider    export_user_preference_provider
549      * @param   string      $component  Component
550      * @param   string      $key Key
551      * @param   string      $value Value
552      * @param   string      $desc Description
553      */
554     public function test_export_user_preference_context_coursecat($component, $key, $value, $desc) {
555         global $DB;
557         $categories = $DB->get_records('course_categories');
558         $firstcategory = reset($categories);
560         $context = \context_coursecat::instance($firstcategory->id);
561         $writer = $this->get_writer_instance()
562             ->set_context($context)
563             ->export_user_preference($component, $key, $value, $desc);
565         $fileroot = $this->fetch_exported_content($writer);
567         $contextpath = $this->get_context_path($context, [get_string('userpreferences')], "{$component}.json");
568         $this->assertTrue($fileroot->hasChild($contextpath));
570         $json = $fileroot->getChild($contextpath)->getContent();
571         $expanded = json_decode($json);
572         $this->assertTrue(isset($expanded->$key));
573         $data = $expanded->$key;
574         $this->assertEquals($value, $data->value);
575         $this->assertEquals($desc, $data->description);
576     }
578     /**
579      * User preferences can be exported against a course.
580      *
581      * @dataProvider    export_user_preference_provider
582      * @param   string      $component  Component
583      * @param   string      $key Key
584      * @param   string      $value Value
585      * @param   string      $desc Description
586      */
587     public function test_export_user_preference_context_course($component, $key, $value, $desc) {
588         global $DB;
590         $this->resetAfterTest();
592         $course = $this->getDataGenerator()->create_course();
594         $context = \context_course::instance($course->id);
595         $writer = $this->get_writer_instance()
596             ->set_context($context)
597             ->export_user_preference($component, $key, $value, $desc);
599         $fileroot = $this->fetch_exported_content($writer);
601         $contextpath = $this->get_context_path($context, [get_string('userpreferences')], "{$component}.json");
602         $this->assertTrue($fileroot->hasChild($contextpath));
604         $json = $fileroot->getChild($contextpath)->getContent();
605         $expanded = json_decode($json);
606         $this->assertTrue(isset($expanded->$key));
607         $data = $expanded->$key;
608         $this->assertEquals($value, $data->value);
609         $this->assertEquals($desc, $data->description);
610     }
612     /**
613      * User preferences can be exported against a module context.
614      *
615      * @dataProvider    export_user_preference_provider
616      * @param   string      $component  Component
617      * @param   string      $key Key
618      * @param   string      $value Value
619      * @param   string      $desc Description
620      */
621     public function test_export_user_preference_context_module($component, $key, $value, $desc) {
622         global $DB;
624         $this->resetAfterTest();
626         $course = $this->getDataGenerator()->create_course();
627         $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
629         $context = \context_module::instance($forum->cmid);
630         $writer = $this->get_writer_instance()
631             ->set_context($context)
632             ->export_user_preference($component, $key, $value, $desc);
634         $fileroot = $this->fetch_exported_content($writer);
636         $contextpath = $this->get_context_path($context, [get_string('userpreferences')], "{$component}.json");
637         $this->assertTrue($fileroot->hasChild($contextpath));
639         $json = $fileroot->getChild($contextpath)->getContent();
640         $expanded = json_decode($json);
641         $this->assertTrue(isset($expanded->$key));
642         $data = $expanded->$key;
643         $this->assertEquals($value, $data->value);
644         $this->assertEquals($desc, $data->description);
645     }
647     /**
648      * User preferences can not be exported against a block context.
649      *
650      * @dataProvider    export_user_preference_provider
651      * @param   string      $component  Component
652      * @param   string      $key Key
653      * @param   string      $value Value
654      * @param   string      $desc Description
655      */
656     public function test_export_user_preference_context_block($component, $key, $value, $desc) {
657         global $DB;
659         $blocks = $DB->get_records('block_instances');
660         $block = reset($blocks);
662         $context = \context_block::instance($block->id);
663         $writer = $this->get_writer_instance()
664             ->set_context($context)
665             ->export_user_preference($component, $key, $value, $desc);
667         $fileroot = $this->fetch_exported_content($writer);
669         $contextpath = $this->get_context_path($context, [get_string('userpreferences')], "{$component}.json");
670         $this->assertTrue($fileroot->hasChild($contextpath));
672         $json = $fileroot->getChild($contextpath)->getContent();
673         $expanded = json_decode($json);
674         $this->assertTrue(isset($expanded->$key));
675         $data = $expanded->$key;
676         $this->assertEquals($value, $data->value);
677         $this->assertEquals($desc, $data->description);
678     }
680     /**
681      * User preferences can be exported against the system.
682      *
683      * @dataProvider    export_user_preference_provider
684      * @param   string      $component  Component
685      * @param   string      $key Key
686      * @param   string      $value Value
687      * @param   string      $desc Description
688      */
689     public function test_export_user_preference_context_system($component, $key, $value, $desc) {
690         $context = \context_system::instance();
691         $writer = $this->get_writer_instance()
692             ->set_context($context)
693             ->export_user_preference($component, $key, $value, $desc);
695         $fileroot = $this->fetch_exported_content($writer);
697         $contextpath = $this->get_context_path($context, [get_string('userpreferences')], "{$component}.json");
698         $this->assertTrue($fileroot->hasChild($contextpath));
700         $json = $fileroot->getChild($contextpath)->getContent();
701         $expanded = json_decode($json);
702         $this->assertTrue(isset($expanded->$key));
703         $data = $expanded->$key;
704         $this->assertEquals($value, $data->value);
705         $this->assertEquals($desc, $data->description);
706     }
708     /**
709      * User preferences can be exported against the system.
710      */
711     public function test_export_multiple_user_preference_context_system() {
712         $context = \context_system::instance();
713         $writer = $this->get_writer_instance();
714         $component = 'core_privacy';
716         $writer
717             ->set_context($context)
718             ->export_user_preference($component, 'key1', 'val1', 'desc1')
719             ->export_user_preference($component, 'key2', 'val2', 'desc2');
721         $fileroot = $this->fetch_exported_content($writer);
723         $contextpath = $this->get_context_path($context, [get_string('userpreferences')], "{$component}.json");
724         $this->assertTrue($fileroot->hasChild($contextpath));
726         $json = $fileroot->getChild($contextpath)->getContent();
727         $expanded = json_decode($json);
729         $this->assertTrue(isset($expanded->key1));
730         $data = $expanded->key1;
731         $this->assertEquals('val1', $data->value);
732         $this->assertEquals('desc1', $data->description);
734         $this->assertTrue(isset($expanded->key2));
735         $data = $expanded->key2;
736         $this->assertEquals('val2', $data->value);
737         $this->assertEquals('desc2', $data->description);
738     }
740     /**
741      * User preferences can be exported against the system.
742      */
743     public function test_export_user_preference_replace() {
744         $context = \context_system::instance();
745         $writer = $this->get_writer_instance();
746         $component = 'core_privacy';
747         $key = 'key';
749         $writer
750             ->set_context($context)
751             ->export_user_preference($component, $key, 'val1', 'desc1');
753         $writer
754             ->set_context($context)
755             ->export_user_preference($component, $key, 'val2', 'desc2');
757         $fileroot = $this->fetch_exported_content($writer);
759         $contextpath = $this->get_context_path($context, [get_string('userpreferences')], "{$component}.json");
760         $this->assertTrue($fileroot->hasChild($contextpath));
762         $json = $fileroot->getChild($contextpath)->getContent();
763         $expanded = json_decode($json);
765         $this->assertTrue(isset($expanded->$key));
766         $data = $expanded->$key;
767         $this->assertEquals('val2', $data->value);
768         $this->assertEquals('desc2', $data->description);
769     }
771     /**
772      * Provider for various user preferences.
773      *
774      * @return  array
775      */
776     public function export_user_preference_provider() {
777         return [
778             'basic' => [
779                 'core_privacy',
780                 'onekey',
781                 'value',
782                 'description',
783             ],
784             'encodedvalue' => [
785                 'core_privacy',
786                 'donkey',
787                 base64_encode('value'),
788                 'description',
789             ],
790             'long description' => [
791                 'core_privacy',
792                 'twokey',
793                 'value',
794                 'This is a much longer description which actually states what this is used for. Blah blah blah.',
795             ],
796         ];
797     }
799     /**
800      * Test that exported data is human readable.
801      *
802      * @dataProvider unescaped_unicode_export_provider
803      * @param string $text
804      */
805     public function test_export_data_unescaped_unicode($text) {
806         $context = \context_system::instance();
807         $subcontext = [];
808         $data = (object) ['key' => $text];
810         $writer = $this->get_writer_instance()
811                 ->set_context($context)
812                 ->export_data($subcontext, $data);
814         $fileroot = $this->fetch_exported_content($writer);
816         $contextpath = $this->get_context_path($context, $subcontext, 'data.json');
818         $json = $fileroot->getChild($contextpath)->getContent();
819         $this->assertRegExp("/$text/", $json);
821         $expanded = json_decode($json);
822         $this->assertEquals($data, $expanded);
823     }
825     /**
826      * Test that exported metadata is human readable.
827      *
828      * @dataProvider unescaped_unicode_export_provider
829      * @param string $text
830      */
831     public function test_export_metadata_unescaped_unicode($text) {
832         $context = \context_system::instance();
833         $subcontext = ['a', 'b', 'c'];
835         $writer = $this->get_writer_instance()
836                 ->set_context($context)
837                 ->export_metadata($subcontext, $text, $text, $text);
839         $fileroot = $this->fetch_exported_content($writer);
841         $contextpath = $this->get_context_path($context, $subcontext, 'metadata.json');
843         $json = $fileroot->getChild($contextpath)->getContent();
844         $this->assertRegExp("/$text.*$text.*$text/", $json);
846         $expanded = json_decode($json);
847         $this->assertTrue(isset($expanded->$text));
848         $this->assertEquals($text, $expanded->$text->value);
849         $this->assertEquals($text, $expanded->$text->description);
850     }
852     /**
853      * Test that exported related data is human readable.
854      *
855      * @dataProvider unescaped_unicode_export_provider
856      * @param string $text
857      */
858     public function test_export_related_data_unescaped_unicode($text) {
859         $context = \context_system::instance();
860         $subcontext = [];
861         $data = (object) ['key' => $text];
863         $writer = $this->get_writer_instance()
864                 ->set_context($context)
865                 ->export_related_data($subcontext, 'name', $data);
867         $fileroot = $this->fetch_exported_content($writer);
869         $contextpath = $this->get_context_path($context, $subcontext, 'name.json');
871         $json = $fileroot->getChild($contextpath)->getContent();
872         $this->assertRegExp("/$text/", $json);
874         $expanded = json_decode($json);
875         $this->assertEquals($data, $expanded);
876     }
878     /**
879      * Test that exported user preference is human readable.
880      *
881      * @dataProvider unescaped_unicode_export_provider
882      * @param string $text
883      */
884     public function test_export_user_preference_unescaped_unicode($text) {
885         $context = \context_system::instance();
886         $component = 'core_privacy';
888         $writer = $this->get_writer_instance()
889                 ->set_context($context)
890                 ->export_user_preference($component, $text, $text, $text);
892         $fileroot = $this->fetch_exported_content($writer);
894         $contextpath = $this->get_context_path($context, [get_string('userpreferences')], "{$component}.json");
896         $json = $fileroot->getChild($contextpath)->getContent();
897         $this->assertRegExp("/$text.*$text.*$text/", $json);
899         $expanded = json_decode($json);
900         $this->assertTrue(isset($expanded->$text));
901         $this->assertEquals($text, $expanded->$text->value);
902         $this->assertEquals($text, $expanded->$text->description);
903     }
905     /**
906      * Provider for various user preferences.
907      *
908      * @return array
909      */
910     public function unescaped_unicode_export_provider() {
911         return [
912             'Unicode' => ['ةكءيٓ‌پچژکگیٹڈڑہھےâîûğŞAaÇÖáǽ你好!'],
913         ];
914     }
916     /**
917      * Test that exported data is shortened when exceeds the limit.
918      *
919      * @dataProvider long_filename_provider
920      * @param string $longtext
921      * @param string $expected
922      * @param string $text
923      */
924     public function test_export_data_long_filename($longtext, $expected, $text) {
925         $context = \context_system::instance();
926         $subcontext = [$longtext];
927         $data = (object) ['key' => $text];
929         $writer = $this->get_writer_instance()
930                 ->set_context($context)
931                 ->export_data($subcontext, $data);
933         $fileroot = $this->fetch_exported_content($writer);
935         $contextpath = $this->get_context_path($context, $subcontext, 'data.json');
936         $expectedpath = 'System/'.$expected.'/data.json';
937         $this->assertEquals($expectedpath, $contextpath);
939         $json = $fileroot->getChild($contextpath)->getContent();
940         $this->assertRegExp("/$text/", $json);
942         $expanded = json_decode($json);
943         $this->assertEquals($data, $expanded);
944     }
946     /**
947      * Test that exported related data is shortened when exceeds the limit.
948      *
949      * @dataProvider long_filename_provider
950      * @param string $longtext
951      * @param string $expected
952      * @param string $text
953      */
954     public function test_export_related_data_long_filename($longtext, $expected, $text) {
955         $context = \context_system::instance();
956         $subcontext = [$longtext];
957         $data = (object) ['key' => $text];
959         $writer = $this->get_writer_instance()
960                 ->set_context($context)
961                 ->export_related_data($subcontext, 'name', $data);
963         $fileroot = $this->fetch_exported_content($writer);
965         $contextpath = $this->get_context_path($context, $subcontext, 'name.json');
966         $expectedpath = 'System/'.$expected.'/name.json';
967         $this->assertEquals($expectedpath, $contextpath);
969         $json = $fileroot->getChild($contextpath)->getContent();
970         $this->assertRegExp("/$text/", $json);
972         $expanded = json_decode($json);
973         $this->assertEquals($data, $expanded);
974     }
976     /**
977      * Test that exported metadata is shortened when exceeds the limit.
978      *
979      * @dataProvider long_filename_provider
980      * @param string $longtext
981      * @param string $expected
982      * @param string $text
983      */
984     public function test_export_metadata_long_filename($longtext, $expected, $text) {
985         $context = \context_system::instance();
986         $subcontext = [$longtext];
987         $data = (object) ['key' => $text];
989         $writer = $this->get_writer_instance()
990                 ->set_context($context)
991                 ->export_metadata($subcontext, $text, $text, $text);
993         $fileroot = $this->fetch_exported_content($writer);
995         $contextpath = $this->get_context_path($context, $subcontext, 'metadata.json');
996         $expectedpath = 'System/'.$expected.'/metadata.json';
997         $this->assertEquals($expectedpath, $contextpath);
999         $json = $fileroot->getChild($contextpath)->getContent();
1000         $this->assertRegExp("/$text.*$text.*$text/", $json);
1002         $expanded = json_decode($json);
1003         $this->assertTrue(isset($expanded->$text));
1004         $this->assertEquals($text, $expanded->$text->value);
1005         $this->assertEquals($text, $expanded->$text->description);
1006     }
1008     /**
1009      * Test that exported user preference is shortened when exceeds the limit.
1010      *
1011      * @dataProvider long_filename_provider
1012      * @param string $longtext
1013      * @param string $expected
1014      * @param string $text
1015      */
1016     public function test_export_user_preference_long_filename($longtext, $expected, $text) {
1017         $this->resetAfterTest();
1019         if (!array_key_exists('json', core_filetypes::get_types())) {
1020             // Add json as mime type to avoid lose the extension when shortening filenames.
1021             core_filetypes::add_type('json', 'application/json', 'archive', [], '', 'JSON file archive');
1022         }
1023         $expectedpath = 'System/User preferences/'.$expected.'.json';
1025         $context = \context_system::instance();
1026         $component = $longtext;
1028         $writer = $this->get_writer_instance()
1029                 ->set_context($context)
1030                 ->export_user_preference($component, $text, $text, $text);
1032         $fileroot = $this->fetch_exported_content($writer);
1034         $contextpath = $this->get_context_path($context, [get_string('userpreferences')], "{$component}.json");
1035         $this->assertEquals($expectedpath, $contextpath);
1037         $json = $fileroot->getChild($contextpath)->getContent();
1038         $this->assertRegExp("/$text.*$text.*$text/", $json);
1040         $expanded = json_decode($json);
1041         $this->assertTrue(isset($expanded->$text));
1042         $this->assertEquals($text, $expanded->$text->value);
1043         $this->assertEquals($text, $expanded->$text->description);
1044     }
1046     /**
1047      * Provider for long filenames.
1048      *
1049      * @return array
1050      */
1051     public function long_filename_provider() {
1052         return [
1053             'More than 100 characters' => [
1054                 'Etiam sit amet dui vel leo blandit viverra. Proin viverra suscipit velit. Aenean efficitur suscipit nibh nec suscipit',
1055                 'Etiam sit amet dui vel leo blandit viverra. Proin viverra suscipit velit. Aenean effici - 22f7a5030d',
1056                 'value',
1057             ],
1058         ];
1059     }
1061     /**
1062      * Get a fresh content writer.
1063      *
1064      * @return  moodle_content_writer
1065      */
1066     public function get_writer_instance() {
1067         $factory = $this->createMock(writer::class);
1068         return new moodle_content_writer($factory);
1069     }
1071     /**
1072      * Fetch the exported content for inspection.
1073      *
1074      * @param   moodle_content_writer   $writer
1075      * @return  \org\bovigo\vfs\vfsStreamDirectory
1076      */
1077     protected function fetch_exported_content(moodle_content_writer $writer) {
1078         $export = $writer
1079             ->set_context(\context_system::instance())
1080             ->finalise_content();
1082         $fileroot = \org\bovigo\vfs\vfsStream::setup('root');
1084         $target = \org\bovigo\vfs\vfsStream::url('root');
1085         $fp = get_file_packer();
1086         $fp->extract_to_pathname($export, $target);
1088         return $fileroot;
1089     }
1091     /**
1092      * Determine the path for the current context.
1093      *
1094      * Note: This is a wrapper around the real function.
1095      *
1096      * @param   \context        $context    The context being written
1097      * @param   array           $subcontext The subcontext path
1098      * @param   string          $name       THe name of the file target
1099      * @return  array                       The context path.
1100      */
1101     protected function get_context_path($context, $subcontext = null, $name = '') {
1102         $rc = new ReflectionClass(moodle_content_writer::class);
1103         $writer = $this->get_writer_instance();
1104         $writer->set_context($context);
1106         if (null === $subcontext) {
1107             $rcm = $rc->getMethod('get_context_path');
1108             $rcm->setAccessible(true);
1109             return $rcm->invoke($writer);
1110         } else {
1111             $rcm = $rc->getMethod('get_path');
1112             $rcm->setAccessible(true);
1113             return $rcm->invoke($writer, $subcontext, $name);
1114         }
1115     }
1117     /**
1118      * Test correct rewriting of @@PLUGINFILE@@ in the exported contents.
1119      *
1120      * @dataProvider rewrite_pluginfile_urls_provider
1121      * @param string $filearea The filearea within that component.
1122      * @param int $itemid Which item those files belong to.
1123      * @param string $input Raw text as stored in the database.
1124      * @param string $expectedoutput Expected output of URL rewriting.
1125      */
1126     public function test_rewrite_pluginfile_urls($filearea, $itemid, $input, $expectedoutput) {
1128         $writer = $this->get_writer_instance();
1129         $writer->set_context(\context_system::instance());
1131         $realoutput = $writer->rewrite_pluginfile_urls([], 'core_test', $filearea, $itemid, $input);
1133         $this->assertEquals($expectedoutput, $realoutput);
1134     }
1136     /**
1137      * Provides testable sample data for {@link self::test_rewrite_pluginfile_urls()}.
1138      *
1139      * @return array
1140      */
1141     public function rewrite_pluginfile_urls_provider() {
1142         return [
1143             'zeroitemid' => [
1144                 'intro',
1145                 0,
1146                 '<p><img src="@@PLUGINFILE@@/hello.gif" /></p>',
1147                 '<p><img src="_files/intro/hello.gif" /></p>',
1148             ],
1149             'nonzeroitemid' => [
1150                 'submission_content',
1151                 34,
1152                 '<p><img src="@@PLUGINFILE@@/first.png" alt="First" /></p>',
1153                 '<p><img src="_files/submission_content/34/first.png" alt="First" /></p>',
1154             ],
1155             'withfilepath' => [
1156                 'post_content',
1157                 9889,
1158                 '<a href="@@PLUGINFILE@@/embedded/docs/muhehe.exe">Click here!</a>',
1159                 '<a href="_files/post_content/9889/embedded/docs/muhehe.exe">Click here!</a>',
1160             ],
1161         ];
1162     }