Differences Between: [Versions 310 and 311] [Versions 311 and 401] [Versions 311 and 402] [Versions 311 and 403] [Versions 39 and 311]
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 /** 18 * This file contains the moodle format implementation of the content writer. 19 * 20 * @package core_privacy 21 * @copyright 2018 Andrew Nicols <andrew@nicols.co.uk> 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 namespace core_privacy\tests\request; 25 26 defined('MOODLE_INTERNAL') || die(); 27 28 /** 29 * An implementation of the content_writer for use in unit tests. 30 * 31 * This implementation does not export any data but instead stores it in 32 * structures within the instance which can be easily queried for use 33 * during unit tests. 34 * 35 * @copyright 2018 Andrew Nicols <andrew@nicols.co.uk> 36 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 37 */ 38 class content_writer implements \core_privacy\local\request\content_writer { 39 /** 40 * @var \context The context currently being exported. 41 */ 42 protected $context; 43 44 /** 45 * @var \stdClass The collection of metadata which has been exported. 46 */ 47 protected $metadata; 48 49 /** 50 * @var \stdClass The data which has been exported. 51 */ 52 protected $data; 53 54 /** 55 * @var \stdClass The related data which has been exported. 56 */ 57 protected $relateddata; 58 59 /** 60 * @var \stdClass The list of stored files which have been exported. 61 */ 62 protected $files; 63 64 /** 65 * @var \stdClass The custom files which have been exported. 66 */ 67 protected $customfiles; 68 69 /** 70 * @var \stdClass The user preferences which have been exported. 71 */ 72 protected $userprefs; 73 74 /** 75 * Whether any data has been exported at all within the current context. 76 * 77 * @param array $subcontext The location within the current context that this data belongs - 78 * in this method it can be partial subcontext path (or none at all to check presence of any data anywhere). 79 * User preferences never have subcontext, if $subcontext is specified, user preferences are not checked. 80 * @return bool 81 */ 82 public function has_any_data($subcontext = []) { 83 if (empty($subcontext)) { 84 // When subcontext is not specified check presence of user preferences in this context and in system context. 85 $hasuserprefs = !empty($this->userprefs->{$this->context->id}); 86 $systemcontext = \context_system::instance(); 87 $hasglobaluserprefs = !empty($this->userprefs->{$systemcontext->id}); 88 if ($hasuserprefs || $hasglobaluserprefs) { 89 return true; 90 } 91 } 92 93 foreach (['data', 'relateddata', 'metadata', 'files', 'customfiles'] as $datatype) { 94 if (!property_exists($this->$datatype, $this->context->id)) { 95 // No data of this type for this context at all. Continue to the next data type. 96 continue; 97 } 98 $basepath = $this->$datatype->{$this->context->id}; 99 foreach ($subcontext as $subpath) { 100 if (!isset($basepath->children->$subpath)) { 101 // No data of this type is present for this path. Continue to the next data type. 102 continue 2; 103 } 104 $basepath = $basepath->children->$subpath; 105 } 106 if (!empty($basepath)) { 107 // Some data found for this type for this subcontext. 108 return true; 109 } 110 } 111 return false; 112 } 113 114 /** 115 * Whether any data has been exported for any context. 116 * 117 * @return bool 118 */ 119 public function has_any_data_in_any_context() { 120 $checkfordata = function($location) { 121 foreach ($location as $context => $data) { 122 if (!empty($data)) { 123 return true; 124 } 125 } 126 127 return false; 128 }; 129 130 $hasanydata = $checkfordata($this->data); 131 $hasanydata = $hasanydata || $checkfordata($this->relateddata); 132 $hasanydata = $hasanydata || $checkfordata($this->metadata); 133 $hasanydata = $hasanydata || $checkfordata($this->files); 134 $hasanydata = $hasanydata || $checkfordata($this->customfiles); 135 $hasanydata = $hasanydata || $checkfordata($this->userprefs); 136 137 return $hasanydata; 138 } 139 140 /** 141 * Constructor for the content writer. 142 * 143 * Note: The writer_factory must be passed. 144 * @param \core_privacy\local\request\writer $writer The writer factory. 145 */ 146 public function __construct(\core_privacy\local\request\writer $writer) { 147 $this->data = (object) []; 148 $this->relateddata = (object) []; 149 $this->metadata = (object) []; 150 $this->files = (object) []; 151 $this->customfiles = (object) []; 152 $this->userprefs = (object) []; 153 } 154 155 /** 156 * Set the context for the current item being processed. 157 * 158 * @param \context $context The context to use 159 */ 160 public function set_context(\context $context) : \core_privacy\local\request\content_writer { 161 $this->context = $context; 162 163 if (isset($this->data->{$this->context->id}) && empty((array) $this->data->{$this->context->id})) { 164 $this->data->{$this->context->id} = (object) [ 165 'children' => (object) [], 166 'data' => [], 167 ]; 168 } 169 170 if (isset($this->relateddata->{$this->context->id}) && empty((array) $this->relateddata->{$this->context->id})) { 171 $this->relateddata->{$this->context->id} = (object) [ 172 'children' => (object) [], 173 'data' => [], 174 ]; 175 } 176 177 if (isset($this->metadata->{$this->context->id}) && empty((array) $this->metadata->{$this->context->id})) { 178 $this->metadata->{$this->context->id} = (object) [ 179 'children' => (object) [], 180 'data' => [], 181 ]; 182 } 183 184 if (isset($this->files->{$this->context->id}) && empty((array) $this->files->{$this->context->id})) { 185 $this->files->{$this->context->id} = (object) [ 186 'children' => (object) [], 187 'data' => [], 188 ]; 189 } 190 191 if (isset($this->customfiles->{$this->context->id}) && empty((array) $this->customfiles->{$this->context->id})) { 192 $this->customfiles->{$this->context->id} = (object) [ 193 'children' => (object) [], 194 'data' => [], 195 ]; 196 } 197 198 if (isset($this->userprefs->{$this->context->id}) && empty((array) $this->userprefs->{$this->context->id})) { 199 $this->userprefs->{$this->context->id} = (object) [ 200 'children' => (object) [], 201 'data' => [], 202 ]; 203 } 204 205 return $this; 206 } 207 208 /** 209 * Return the current context. 210 * 211 * @return \context 212 */ 213 public function get_current_context() : \context { 214 return $this->context; 215 } 216 217 /** 218 * Export the supplied data within the current context, at the supplied subcontext. 219 * 220 * @param array $subcontext The location within the current context that this data belongs. 221 * @param \stdClass $data The data to be exported 222 */ 223 public function export_data(array $subcontext, \stdClass $data) : \core_privacy\local\request\content_writer { 224 $current = $this->fetch_root($this->data, $subcontext); 225 $current->data = $data; 226 227 return $this; 228 } 229 230 /** 231 * Get all data within the subcontext. 232 * 233 * @param array $subcontext The location within the current context that this data belongs. 234 * @return array The metadata as a series of keys to value + descrition objects. 235 */ 236 public function get_data(array $subcontext = []) { 237 return $this->fetch_data_root($this->data, $subcontext); 238 } 239 240 /** 241 * Export metadata about the supplied subcontext. 242 * 243 * Metadata consists of a key/value pair and a description of the value. 244 * 245 * @param array $subcontext The location within the current context that this data belongs. 246 * @param string $key The metadata name. 247 * @param string $value The metadata value. 248 * @param string $description The description of the value. 249 * @return $this 250 */ 251 public function export_metadata(array $subcontext, string $key, $value, string $description) 252 : \core_privacy\local\request\content_writer { 253 $current = $this->fetch_root($this->metadata, $subcontext); 254 $current->data[$key] = (object) [ 255 'value' => $value, 256 'description' => $description, 257 ]; 258 259 return $this; 260 } 261 262 /** 263 * Get all metadata within the subcontext. 264 * 265 * @param array $subcontext The location within the current context that this data belongs. 266 * @return array The metadata as a series of keys to value + descrition objects. 267 */ 268 public function get_all_metadata(array $subcontext = []) { 269 return $this->fetch_data_root($this->metadata, $subcontext); 270 } 271 272 /** 273 * Get the specified metadata within the subcontext. 274 * 275 * @param array $subcontext The location within the current context that this data belongs. 276 * @param string $key The metadata to be fetched within the context + subcontext. 277 * @param boolean $valueonly Whether to fetch only the value, rather than the value + description. 278 * @return array The metadata as a series of keys to value + descrition objects. 279 */ 280 public function get_metadata(array $subcontext, $key, $valueonly = true) { 281 $keys = $this->get_all_metadata($subcontext); 282 283 if (isset($keys[$key])) { 284 $metadata = $keys[$key]; 285 } else { 286 return null; 287 } 288 289 if ($valueonly) { 290 return $metadata->value; 291 } else { 292 return $metadata; 293 } 294 } 295 296 /** 297 * Export a piece of related data. 298 * 299 * @param array $subcontext The location within the current context that this data belongs. 300 * @param string $name The name of the file to be exported. 301 * @param \stdClass $data The related data to export. 302 */ 303 public function export_related_data(array $subcontext, $name, $data) : \core_privacy\local\request\content_writer { 304 $current = $this->fetch_root($this->relateddata, $subcontext); 305 $current->data[$name] = $data; 306 307 return $this; 308 } 309 310 /** 311 * Get all data within the subcontext. 312 * 313 * @param array $subcontext The location within the current context that this data belongs. 314 * @param string $filename The name of the intended filename. 315 * @return array The metadata as a series of keys to value + descrition objects. 316 */ 317 public function get_related_data(array $subcontext = [], $filename = null) { 318 $current = $this->fetch_data_root($this->relateddata, $subcontext); 319 320 if (null === $filename) { 321 return $current; 322 } 323 324 if (isset($current[$filename])) { 325 return $current[$filename]; 326 } 327 328 return []; 329 } 330 331 /** 332 * Export a piece of data in a custom format. 333 * 334 * @param array $subcontext The location within the current context that this data belongs. 335 * @param string $filename The name of the file to be exported. 336 * @param string $filecontent The content to be exported. 337 */ 338 public function export_custom_file(array $subcontext, $filename, $filecontent) : \core_privacy\local\request\content_writer { 339 $filename = clean_param($filename, PARAM_FILE); 340 341 $current = $this->fetch_root($this->customfiles, $subcontext); 342 $current->data[$filename] = $filecontent; 343 344 return $this; 345 } 346 347 /** 348 * Get the specified custom file within the subcontext. 349 * 350 * @param array $subcontext The location within the current context that this data belongs. 351 * @param string $filename The name of the file to be fetched within the context + subcontext. 352 * @return string The content of the file. 353 */ 354 public function get_custom_file(array $subcontext = [], $filename = null) { 355 $current = $this->fetch_data_root($this->customfiles, $subcontext); 356 357 if (null === $filename) { 358 return $current; 359 } 360 361 if (isset($current[$filename])) { 362 return $current[$filename]; 363 } 364 365 return null; 366 } 367 368 /** 369 * Prepare a text area by processing pluginfile URLs within it. 370 * 371 * Note that this method does not implement the pluginfile URL rewriting. Such a job tightly depends on how the 372 * actual writer exports files so it can be reliably tested only in real writers such as 373 * {@link core_privacy\local\request\moodle_content_writer}. 374 * 375 * However we have to remove @@PLUGINFILE@@ since otherwise {@link format_text()} shows debugging messages 376 * 377 * @param array $subcontext The location within the current context that this data belongs. 378 * @param string $component The name of the component that the files belong to. 379 * @param string $filearea The filearea within that component. 380 * @param string $itemid Which item those files belong to. 381 * @param string $text The text to be processed 382 * @return string The processed string 383 */ 384 public function rewrite_pluginfile_urls(array $subcontext, $component, $filearea, $itemid, $text) : string { 385 return str_replace('@@PLUGINFILE@@/', 'files/', $text); 386 } 387 388 /** 389 * Export all files within the specified component, filearea, itemid combination. 390 * 391 * @param array $subcontext The location within the current context that this data belongs. 392 * @param string $component The name of the component that the files belong to. 393 * @param string $filearea The filearea within that component. 394 * @param string $itemid Which item those files belong to. 395 */ 396 public function export_area_files(array $subcontext, $component, $filearea, $itemid) : \core_privacy\local\request\content_writer { 397 $fs = get_file_storage(); 398 $files = $fs->get_area_files($this->context->id, $component, $filearea, $itemid); 399 foreach ($files as $file) { 400 $this->export_file($subcontext, $file); 401 } 402 403 return $this; 404 } 405 406 /** 407 * Export the specified file in the target location. 408 * 409 * @param array $subcontext The location within the current context that this data belongs. 410 * @param \stored_file $file The file to be exported. 411 */ 412 public function export_file(array $subcontext, \stored_file $file) : \core_privacy\local\request\content_writer { 413 if (!$file->is_directory()) { 414 $filepath = $file->get_filepath(); 415 // Directory separator in the stored_file class should always be '/'. The following line is just a fail safe. 416 $filepath = str_replace(DIRECTORY_SEPARATOR, '/', $filepath); 417 $filepath = explode('/', $filepath); 418 $filepath[] = $file->get_filename(); 419 $filepath = array_filter($filepath); 420 $filepath = implode('/', $filepath); 421 $current = $this->fetch_root($this->files, $subcontext); 422 $current->data[$filepath] = $file; 423 } 424 425 return $this; 426 } 427 428 /** 429 * Get all files in the specfied subcontext. 430 * 431 * @param array $subcontext The location within the current context that this data belongs. 432 * @return \stored_file[] The list of stored_files in this context + subcontext. 433 */ 434 public function get_files(array $subcontext = []) { 435 return $this->fetch_data_root($this->files, $subcontext); 436 } 437 438 /** 439 * Export the specified user preference. 440 * 441 * @param string $component The name of the component. 442 * @param string $key The name of th key to be exported. 443 * @param string $value The value of the preference 444 * @param string $description A description of the value 445 * @return \core_privacy\local\request\content_writer 446 */ 447 public function export_user_preference( 448 string $component, 449 string $key, 450 string $value, 451 string $description 452 ) : \core_privacy\local\request\content_writer { 453 $prefs = $this->fetch_root($this->userprefs, []); 454 455 if (!isset($prefs->{$component})) { 456 $prefs->{$component} = (object) []; 457 } 458 459 $prefs->{$component}->$key = (object) [ 460 'value' => $value, 461 'description' => $description, 462 ]; 463 464 return $this; 465 } 466 467 /** 468 * Get all user preferences for the specified component. 469 * 470 * @param string $component The name of the component. 471 * @return \stdClass 472 */ 473 public function get_user_preferences(string $component) { 474 $context = \context_system::instance(); 475 $prefs = $this->fetch_root($this->userprefs, [], $context->id); 476 if (isset($prefs->{$component})) { 477 return $prefs->{$component}; 478 } else { 479 return (object) []; 480 } 481 } 482 483 /** 484 * Get all user preferences for the specified component. 485 * 486 * @param string $component The name of the component. 487 * @return \stdClass 488 */ 489 public function get_user_context_preferences(string $component) { 490 $prefs = $this->fetch_root($this->userprefs, []); 491 if (isset($prefs->{$component})) { 492 return $prefs->{$component}; 493 } else { 494 return (object) []; 495 } 496 } 497 498 /** 499 * Perform any required finalisation steps and return the location of the finalised export. 500 * 501 * @return string 502 */ 503 public function finalise_content() : string { 504 return 'mock_path'; 505 } 506 507 /** 508 * Fetch the entire root record at the specified location type, creating it if required. 509 * 510 * @param \stdClass $base The base to use - e.g. $this->data 511 * @param array $subcontext The subcontext to fetch 512 * @param int $temporarycontextid A temporary context ID to use for the fetch. 513 * @return array 514 */ 515 protected function fetch_root($base, $subcontext, $temporarycontextid = null) { 516 $contextid = !empty($temporarycontextid) ? $temporarycontextid : $this->context->id; 517 if (!isset($base->{$contextid})) { 518 $base->{$contextid} = (object) [ 519 'children' => (object) [], 520 'data' => [], 521 ]; 522 } 523 524 $current = $base->{$contextid}; 525 foreach ($subcontext as $node) { 526 if (!isset($current->children->{$node})) { 527 $current->children->{$node} = (object) [ 528 'children' => (object) [], 529 'data' => [], 530 ]; 531 } 532 $current = $current->children->{$node}; 533 } 534 535 return $current; 536 } 537 538 /** 539 * Fetch the data region of the specified root. 540 * 541 * @param \stdClass $base The base to use - e.g. $this->data 542 * @param array $subcontext The subcontext to fetch 543 * @return array 544 */ 545 protected function fetch_data_root($base, $subcontext) { 546 $root = $this->fetch_root($base, $subcontext); 547 548 return $root->data; 549 } 550 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body