diff --git a/privacy/classes/local/request/moodle_content_writer.php b/privacy/classes/local/request/moodle_content_writer.php index 8e77256bdf2..de607ccdf82 100644 --- a/privacy/classes/local/request/moodle_content_writer.php +++ b/privacy/classes/local/request/moodle_content_writer.php @@ -79,11 +79,12 @@ class moodle_content_writer implements content_writer { * * @param array $subcontext The location within the current context that this data belongs. * @param \stdClass $data The data to be exported + * @return content_writer */ public function export_data(array $subcontext, \stdClass $data) : content_writer { $path = $this->get_path($subcontext, 'data.json'); - $this->write_data($path, json_encode($data)); + $this->write_data($path, json_encode($data, JSON_UNESCAPED_UNICODE)); return $this; } @@ -97,6 +98,7 @@ class moodle_content_writer implements content_writer { * @param string $key The metadata name. * @param string $value The metadata value. * @param string $description The description of the value. + * @return content_writer */ public function export_metadata(array $subcontext, string $key, $value, string $description) : content_writer { $path = $this->get_full_path($subcontext, 'metadata.json'); @@ -113,7 +115,7 @@ class moodle_content_writer implements content_writer { ]; $path = $this->get_path($subcontext, 'metadata.json'); - $this->write_data($path, json_encode($data)); + $this->write_data($path, json_encode($data, JSON_UNESCAPED_UNICODE)); return $this; } @@ -124,11 +126,12 @@ class moodle_content_writer implements content_writer { * @param array $subcontext The location within the current context that this data belongs. * @param string $name The name of the file to be exported. * @param \stdClass $data The related data to export. + * @return content_writer */ public function export_related_data(array $subcontext, $name, $data) : content_writer { $path = $this->get_path($subcontext, "{$name}.json"); - $this->write_data($path, json_encode($data)); + $this->write_data($path, json_encode($data, JSON_UNESCAPED_UNICODE)); return $this; } @@ -227,7 +230,7 @@ class moodle_content_writer implements content_writer { 'value' => $value, 'description' => $description, ]; - $this->write_data($path, json_encode($data)); + $this->write_data($path, json_encode($data, JSON_UNESCAPED_UNICODE)); return $this; } diff --git a/privacy/tests/moodle_content_writer_test.php b/privacy/tests/moodle_content_writer_test.php index f919bfdb2a3..b683348186f 100644 --- a/privacy/tests/moodle_content_writer_test.php +++ b/privacy/tests/moodle_content_writer_test.php @@ -417,6 +417,8 @@ class moodle_content_writer_test extends advanced_testcase { * Exporting a single stored_file should cause that file to be output in the files directory. * * @dataProvider export_file_provider + * @param string $filearea File area + * @param int $itemid Item ID * @param string $filepath File path * @param string $filename File name * @param string $content Content @@ -794,6 +796,123 @@ class moodle_content_writer_test extends advanced_testcase { ]; } + /** + * Test that exported data is human readable. + * + * @dataProvider unescaped_unicode_export_provider + * @param string $text + */ + public function test_export_data_unescaped_unicode($text) { + $context = \context_system::instance(); + $subcontext = []; + $data = (object) ['key' => $text]; + + $writer = $this->get_writer_instance() + ->set_context($context) + ->export_data($subcontext, $data); + + $fileroot = $this->fetch_exported_content($writer); + + $contextpath = $this->get_context_path($context, $subcontext, 'data.json'); + + $json = $fileroot->getChild($contextpath)->getContent(); + $this->assertRegExp("/$text/", $json); + + $expanded = json_decode($json); + $this->assertEquals($data, $expanded); + } + + /** + * Test that exported metadata is human readable. + * + * @dataProvider unescaped_unicode_export_provider + * @param string $text + */ + public function test_export_metadata_unescaped_unicode($text) { + $context = \context_system::instance(); + $subcontext = ['a', 'b', 'c']; + + $writer = $this->get_writer_instance() + ->set_context($context) + ->export_metadata($subcontext, $text, $text, $text); + + $fileroot = $this->fetch_exported_content($writer); + + $contextpath = $this->get_context_path($context, $subcontext, 'metadata.json'); + + $json = $fileroot->getChild($contextpath)->getContent(); + $this->assertRegExp("/$text.*$text.*$text/", $json); + + $expanded = json_decode($json); + $this->assertTrue(isset($expanded->$text)); + $this->assertEquals($text, $expanded->$text->value); + $this->assertEquals($text, $expanded->$text->description); + } + + /** + * Test that exported related data is human readable. + * + * @dataProvider unescaped_unicode_export_provider + * @param string $text + */ + public function test_export_related_data_unescaped_unicode($text) { + $context = \context_system::instance(); + $subcontext = []; + $data = (object) ['key' => $text]; + + $writer = $this->get_writer_instance() + ->set_context($context) + ->export_related_data($subcontext, 'name', $data); + + $fileroot = $this->fetch_exported_content($writer); + + $contextpath = $this->get_context_path($context, $subcontext, 'name.json'); + + $json = $fileroot->getChild($contextpath)->getContent(); + $this->assertRegExp("/$text/", $json); + + $expanded = json_decode($json); + $this->assertEquals($data, $expanded); + } + + /** + * Test that exported user preference is human readable. + * + * @dataProvider unescaped_unicode_export_provider + * @param string $text + */ + public function test_export_user_preference_unescaped_unicode($text) { + $context = \context_system::instance(); + $component = 'core_privacy'; + + $writer = $this->get_writer_instance() + ->set_context($context) + ->export_user_preference($component, $text, $text, $text); + + $fileroot = $this->fetch_exported_content($writer); + + $contextpath = $this->get_context_path($context, [get_string('userpreferences')], "{$component}.json"); + + $json = $fileroot->getChild($contextpath)->getContent(); + $this->assertRegExp("/$text.*$text.*$text/", $json); + + $expanded = json_decode($json); + $this->assertTrue(isset($expanded->$text)); + $this->assertEquals($text, $expanded->$text->value); + $this->assertEquals($text, $expanded->$text->description); + } + + /** + * Provider for various user preferences. + * + * @return array + */ + public function unescaped_unicode_export_provider() { + return [ + 'Unicode' => ['ةكءيٓ‌پچژکگیٹڈڑہھےâîûğŞAaÇÖáǽ你好!'], + ]; + } + /** * Get a fresh content writer. *