diff --git a/privacy/classes/tests/request/content_writer.php b/privacy/classes/tests/request/content_writer.php index 78c1c62624a..22fea829178 100644 --- a/privacy/classes/tests/request/content_writer.php +++ b/privacy/classes/tests/request/content_writer.php @@ -39,32 +39,32 @@ class content_writer implements \core_privacy\local\request\content_writer { /** * @var \context The context currently being exported. */ - protected $context = null; + protected $context; /** - * @var array The collection of metadata which has been exported. + * @var \stdClass The collection of metadata which has been exported. */ - protected $metadata = []; + protected $metadata; /** - * @var array The data which has been exported. + * @var \stdClass The data which has been exported. */ - protected $data = []; + protected $data; /** - * @var array The related data which has been exported. + * @var \stdClass The related data which has been exported. */ - protected $relateddata = []; + protected $relateddata; /** - * @var array The list of stored files which have been exported. + * @var \stdClass The list of stored files which have been exported. */ - protected $files = []; + protected $files; /** - * @var array The custom files which have been exported. + * @var \stdClass The custom files which have been exported. */ - protected $customfiles = []; + protected $customfiles; /** * @var array The site-wide user preferences which have been exported. @@ -75,11 +75,11 @@ class content_writer implements \core_privacy\local\request\content_writer { * Whether any data has been exported at all within the current context. */ public function has_any_data() { - $hasdata = !empty($this->data[$this->context->id]); - $hasrelateddata = !empty($this->relateddata[$this->context->id]); - $hasmetadata = !empty($this->metadata[$this->context->id]); - $hasfiles = !empty($this->files[$this->context->id]); - $hascustomfiles = !empty($this->customfiles[$this->context->id]); + $hasdata = !empty($this->data->{$this->context->id}); + $hasrelateddata = !empty($this->relateddata->{$this->context->id}); + $hasmetadata = !empty($this->metadata->{$this->context->id}); + $hasfiles = !empty($this->files->{$this->context->id}); + $hascustomfiles = !empty($this->customfiles->{$this->context->id}); $hasuserprefs = !empty($this->userprefs); return $hasdata || $hasrelateddata || $hasmetadata || $hasfiles || $hascustomfiles || $hasuserprefs; @@ -92,6 +92,11 @@ class content_writer implements \core_privacy\local\request\content_writer { * @param \core_privacy\local\request\writer $writer The writer factory. */ public function __construct(\core_privacy\local\request\writer $writer) { + $this->data = (object) []; + $this->relateddata = (object) []; + $this->metadata = (object) []; + $this->files = (object) []; + $this->customfiles = (object) []; } /** @@ -102,24 +107,39 @@ class content_writer implements \core_privacy\local\request\content_writer { public function set_context(\context $context) : \core_privacy\local\request\content_writer { $this->context = $context; - if (empty($this->data[$this->context->id])) { - $this->data[$this->context->id] = []; + if (isset($this->data->{$this->context->id}) && empty((array) $this->data->{$this->context->id})) { + $this->data->{$this->context->id} = (object) [ + 'children' => (object) [], + 'data' => [], + ]; } - if (empty($this->relateddata[$this->context->id])) { - $this->relateddata[$this->context->id] = []; + if (isset($this->relateddata->{$this->context->id}) && empty((array) $this->relateddata->{$this->context->id})) { + $this->relateddata->{$this->context->id} = (object) [ + 'children' => (object) [], + 'data' => [], + ]; } - if (empty($this->metadata[$this->context->id])) { - $this->metadata[$this->context->id] = []; + if (isset($this->metadata->{$this->context->id}) && empty((array) $this->metadata->{$this->context->id})) { + $this->metadata->{$this->context->id} = (object) [ + 'children' => (object) [], + 'data' => [], + ]; } - if (empty($this->files[$this->context->id])) { - $this->files[$this->context->id] = []; + if (isset($this->files->{$this->context->id}) && empty((array) $this->files->{$this->context->id})) { + $this->files->{$this->context->id} = (object) [ + 'children' => (object) [], + 'data' => [], + ]; } - if (empty($this->customfiles[$this->context->id])) { - $this->customfiles[$this->context->id] = []; + if (isset($this->customfiles->{$this->context->id}) && empty((array) $this->customfiles->{$this->context->id})) { + $this->customfiles->{$this->context->id} = (object) [ + 'children' => (object) [], + 'data' => [], + ]; } return $this; @@ -141,21 +161,8 @@ class content_writer implements \core_privacy\local\request\content_writer { * @param \stdClass $data The data to be exported */ public function export_data(array $subcontext, \stdClass $data) : \core_privacy\local\request\content_writer { - $finalcontent = [ - 'children' => [], - 'data' => $data, - ]; - - while ($pathtail = array_pop($subcontext)) { - $finalcontent = [ - 'children' => [ - $pathtail => $finalcontent, - ], - 'data' => [], - ]; - } - - $this->data[$this->context->id] = array_replace_recursive($this->data[$this->context->id], $finalcontent); + $current = $this->fetch_root($this->data, $subcontext); + $current->data = $data; return $this; } @@ -167,18 +174,7 @@ class content_writer implements \core_privacy\local\request\content_writer { * @return array The metadata as a series of keys to value + descrition objects. */ public function get_data(array $subcontext = []) { - $basepath = $this->data[$this->context->id]; - while ($subpath = array_shift($subcontext)) { - if (isset($basepath['children']) && isset($basepath['children'][$subpath])) { - $basepath = $basepath['children'][$subpath]; - } - } - - if (isset($basepath['data'])) { - return $basepath['data']; - } else { - return []; - } + return $this->fetch_data_root($this->data, $subcontext); } /** @@ -192,31 +188,13 @@ class content_writer implements \core_privacy\local\request\content_writer { * @param string $description The description of the value. * @return $this */ - public function export_metadata(array $subcontext, - string $key, - $value, - string $description - ) : \core_privacy\local\request\content_writer { - $finalcontent = [ - 'children' => [], - 'metadata' => [ - $key => (object) [ - 'value' => $value, - 'description' => $description, - ], - ], - ]; - - while ($pathtail = array_pop($subcontext)) { - $finalcontent = [ - 'children' => [ - $pathtail => $finalcontent, - ], - 'metadata' => [], + public function export_metadata(array $subcontext, string $key, $value, string $description) + : \core_privacy\local\request\content_writer { + $current = $this->fetch_root($this->metadata, $subcontext); + $current->data[$key] = (object) [ + 'value' => $value, + 'description' => $description, ]; - } - - $this->metadata[$this->context->id] = array_replace_recursive($this->metadata[$this->context->id], $finalcontent); return $this; } @@ -228,18 +206,7 @@ class content_writer implements \core_privacy\local\request\content_writer { * @return array The metadata as a series of keys to value + descrition objects. */ public function get_all_metadata(array $subcontext = []) { - $basepath = $this->metadata[$this->context->id]; - while ($subpath = array_shift($subcontext)) { - if (isset($basepath['children']) && isset($basepath['children'][$subpath])) { - $basepath = $basepath['children'][$subpath]; - } - } - - if (isset($basepath['metadata'])) { - return $basepath['metadata']; - } else { - return []; - } + return $this->fetch_data_root($this->metadata, $subcontext); } /** @@ -274,23 +241,8 @@ class content_writer implements \core_privacy\local\request\content_writer { * @param \stdClass $data The related data to export. */ public function export_related_data(array $subcontext, $name, $data) : \core_privacy\local\request\content_writer { - $finalcontent = [ - 'children' => [], - 'data' => [ - $name => $data, - ], - ]; - - while ($pathtail = array_pop($subcontext)) { - $finalcontent = [ - 'children' => [ - $pathtail => $finalcontent, - ], - 'data' => [], - ]; - } - - $this->relateddata[$this->context->id] = array_replace_recursive($this->relateddata[$this->context->id], $finalcontent); + $current = $this->fetch_root($this->relateddata, $subcontext); + $current->data[$name] = $data; return $this; } @@ -303,26 +255,17 @@ class content_writer implements \core_privacy\local\request\content_writer { * @return array The metadata as a series of keys to value + descrition objects. */ public function get_related_data(array $subcontext = [], $filename = null) { - $basepath = $this->relateddata[$this->context->id]; - while ($subpath = array_shift($subcontext)) { - if (isset($basepath['children']) && isset($basepath['children'][$subpath])) { - $basepath = $basepath['children'][$subpath]; - } + $current = $this->fetch_data_root($this->relateddata, $subcontext); + + if (null === $filename) { + return $current; } - if (isset($basepath['data'])) { - $data = $basepath['data']; - if (null !== $filename) { - if (isset($data[$filename])) { - return $data[$filename]; - } - return null; - } - - return $data; + if (isset($current[$filename])) { + return $current[$filename]; } - return null; + return []; } /** @@ -335,22 +278,8 @@ class content_writer implements \core_privacy\local\request\content_writer { public function export_custom_file(array $subcontext, $filename, $filecontent) : \core_privacy\local\request\content_writer { $filename = clean_param($filename, PARAM_FILE); - $finalcontent = [ - 'children' => [], - 'files' => [ - $filename => $filecontent, - ], - ]; - while ($pathtail = array_pop($subcontext)) { - $finalcontent = [ - 'children' => [ - $pathtail => $finalcontent, - ], - 'files' => [], - ]; - } - - $this->customfiles[$this->context->id] = array_replace_recursive($this->customfiles[$this->context->id], $finalcontent); + $current = $this->fetch_root($this->customfiles, $subcontext); + $current->data[$filename] = $filecontent; return $this; } @@ -363,22 +292,16 @@ class content_writer implements \core_privacy\local\request\content_writer { * @return string The content of the file. */ public function get_custom_file(array $subcontext = [], $filename = null) { - $basepath = $this->customfiles[$this->context->id]; - while ($subpath = array_shift($subcontext)) { - if (isset($basepath['children']) && isset($basepath['children'][$subpath])) { - $basepath = $basepath['children'][$subpath]; - } + $current = $this->fetch_data_root($this->customfiles, $subcontext); + + if (null === $filename) { + return $current; } - if (isset($basepath['files'])) { - if (null !== $filename) { - if (isset($basepath['files'][$filename])) { - return $basepath['files'][$filename]; - } - return null; - } - return $basepath['files']; + if (isset($current[$filename])) { + return $current[$filename]; } + return null; } @@ -427,23 +350,8 @@ class content_writer implements \core_privacy\local\request\content_writer { $filepath = array_filter($filepath); $filepath = implode('/', $filepath); - $finalcontent = [ - 'children' => [], - 'files' => [ - $filepath => $file, - ], - ]; - - while ($pathtail = array_pop($subcontext)) { - $finalcontent = [ - 'children' => [ - $pathtail => $finalcontent, - ], - 'files' => [], - ]; - } - - $this->files[$this->context->id] = array_replace_recursive($this->files[$this->context->id], $finalcontent); + $current = $this->fetch_root($this->files, $subcontext); + $current->data[$filepath] = $file; } return $this; @@ -456,18 +364,7 @@ class content_writer implements \core_privacy\local\request\content_writer { * @return \stored_file[] The list of stored_files in this context + subcontext. */ public function get_files(array $subcontext = []) { - $basepath = $this->files[$this->context->id]; - while ($subpath = array_shift($subcontext)) { - if (isset($basepath['children']) && isset($basepath['children'][$subpath])) { - $basepath = $basepath['children'][$subpath]; - } - } - - if (isset($basepath['files'])) { - return $basepath['files']; - } - - return []; + return $this->fetch_data_root($this->files, $subcontext); } /** @@ -519,4 +416,46 @@ class content_writer implements \core_privacy\local\request\content_writer { public function finalise_content() : string { return 'mock_path'; } + + /** + * Fetch the entire root record at the specified location type, creating it if required. + * + * @param \stdClass $base The base to use - e.g. $this->data + * @param array $subcontext The subcontext to fetch + * @return array + */ + protected function fetch_root($base, $subcontext) { + if (!isset($base->{$this->context->id})) { + $base->{$this->context->id} = (object) [ + 'children' => (object) [], + 'data' => [], + ]; + } + + $current = $base->{$this->context->id}; + foreach ($subcontext as $node) { + if (!isset($current->children->{$node})) { + $current->children->{$node} = (object) [ + 'children' => (object) [], + 'data' => [], + ]; + } + $current = $current->children->{$node}; + } + + return $current; + } + + /** + * Fetch the data region of the specified root. + * + * @param \stdClass $base The base to use - e.g. $this->data + * @param array $subcontext The subcontext to fetch + * @return array + */ + protected function fetch_data_root($base, $subcontext) { + $root = $this->fetch_root($base, $subcontext); + + return $root->data; + } } diff --git a/privacy/tests/tests_content_writer_test.php b/privacy/tests/tests_content_writer_test.php index 1f815c11434..7a70e592846 100644 --- a/privacy/tests/tests_content_writer_test.php +++ b/privacy/tests/tests_content_writer_test.php @@ -93,6 +93,22 @@ class tests_content_writer_test extends advanced_testcase { $this->assertSame($datab, $data); } + /** + * Test export and recover with children. + */ + public function test_get_data_with_children() { + $writer = $this->get_writer_instance(); + $context = \context_system::instance(); + + $writer->set_context($context) + ->export_data(['a'], (object) ['parent' => true]) + ->export_data(['a', 'b'], (object) ['parent' => false]); + + $this->assertTrue($writer->get_data(['a'])->parent); + $this->assertFalse($writer->get_data(['a', 'b'])->parent); + $this->assertEquals([], $writer->get_data(['a', 'b', 'c'])); + } + /** * It should be possible to store and retrieve metadata. */ @@ -167,6 +183,21 @@ class tests_content_writer_test extends advanced_testcase { $this->assertEquals('value2', $writer->get_metadata(['metadata'], 'somekey', true)); } + /** + * Test export and recover with children. + */ + public function test_get_metadata_with_children() { + $writer = $this->get_writer_instance(); + $context = \context_system::instance(); + + $writer->set_context($context) + ->export_metadata(['a'], 'abc', 'ABC', 'A, B, C') + ->export_metadata(['a', 'b'], 'def', 'DEF', 'D, E, F'); + + $this->assertEquals('ABC', $writer->get_metadata(['a'], 'abc')); + $this->assertEquals('DEF', $writer->get_metadata(['a', 'b'], 'def')); + } + /** * It should be possible to export files in the files and children contexts. */ @@ -217,6 +248,29 @@ class tests_content_writer_test extends advanced_testcase { $this->assertEquals($fileb, $files['foo/foo.txt']); } + /** + * Test export and recover with children. + */ + public function test_get_file_with_children() { + $writer = $this->get_writer_instance(); + $context = \context_system::instance(); + + $filea = $this->get_stored_file('/foo/', 'foo.txt'); + $fileb = $this->get_stored_file('/foo/', 'foo.txt'); + + $writer->set_context($context) + ->export_file(['a'], $filea) + ->export_file(['a', 'b'], $fileb); + + $files = $writer->get_files(['a']); + $this->assertCount(1, $files); + $this->assertEquals($filea, $files['foo/foo.txt']); + + $files = $writer->get_files(['a', 'b']); + $this->assertCount(1, $files); + $this->assertEquals($fileb, $files['foo/foo.txt']); + } + /** * It should be possible to export related data in the files and children contexts. */ @@ -269,6 +323,21 @@ class tests_content_writer_test extends advanced_testcase { $this->assertEquals('data2', $data['file']); } + /** + * Test export and recover with children. + */ + public function test_get_related_data_with_children() { + $writer = $this->get_writer_instance(); + $context = \context_system::instance(); + + $writer->set_context($context) + ->export_related_data(['a'], 'abc', 'ABC') + ->export_related_data(['a', 'b'], 'def', 'DEF'); + + $this->assertEquals('ABC', $writer->get_related_data(['a'], 'abc')); + $this->assertEquals('DEF', $writer->get_related_data(['a', 'b'], 'def')); + } + /** * It should be possible to export related files in the files and children contexts. */ @@ -320,6 +389,21 @@ class tests_content_writer_test extends advanced_testcase { $this->assertEquals('Content 2', $files['file.txt']); } + /** + * Test export and recover with children. + */ + public function test_get_custom_file_with_children() { + $writer = $this->get_writer_instance(); + $context = \context_system::instance(); + + $writer->set_context($context) + ->export_custom_file(['a'], 'file.txt', 'ABC') + ->export_custom_file(['a', 'b'], 'file.txt', 'DEF'); + + $this->assertEquals('ABC', $writer->get_custom_file(['a'], 'file.txt')); + $this->assertEquals('DEF', $writer->get_custom_file(['a', 'b'], 'file.txt')); + } + /** * Get a fresh content writer. *