See Release Notes
Long Term Support Release
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 mod_data; 18 19 use core_component; 20 use invalid_parameter_exception; 21 use data_field_base; 22 use moodle_exception; 23 use SimpleXMLElement; 24 use stdClass; 25 use stored_file; 26 27 /** 28 * Class preset for database activity. 29 * 30 * @package mod_data 31 * @copyright 2022 Sara Arjona <sara@moodle.com> 32 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 33 */ 34 class preset { 35 36 /** @var manager manager instance. */ 37 private $manager; 38 39 /** @var bool whether the preset is a plugin or has been saved by the user. */ 40 public $isplugin; 41 42 /** @var string The preset name. */ 43 public $name; 44 45 /** @var string The preset shortname. For datapreset plugins that is the folder; for saved presets, that's the preset name. */ 46 public $shortname; 47 48 /** @var string The preset description. */ 49 public $description; 50 51 /** @var stored_file For saved presets that's the file object for the root folder. It's null for plugins or for presets that 52 * haven't been saved yet. */ 53 public $storedfile; 54 55 /** @var array|null the field sample instances. */ 56 private $fields = null; 57 58 /** @var stdClass|null the preset.xml parsed information. */ 59 protected $xmlinfo = null; 60 61 /** 62 * Class constructor. 63 * 64 * @param manager|null $manager the current instance manager 65 * @param bool $isplugin whether the preset is a plugin or has been saved by the user 66 * @param string $name the preset name 67 * @param string $shortname the preset shortname 68 * @param string|null $description the preset description 69 * @param stored_file|null $storedfile for saved presets, that's the file for the root folder 70 * @throws invalid_parameter_exception 71 */ 72 protected function __construct( 73 ?manager $manager, 74 bool $isplugin, 75 string $name, 76 string $shortname, 77 ?string $description = '', 78 ?stored_file $storedfile = null 79 ) { 80 if (!$isplugin && is_null($manager)) { 81 throw new invalid_parameter_exception('The $manager parameter can only be null for plugin presets.'); 82 } 83 $this->manager = $manager; 84 $this->isplugin = $isplugin; 85 $this->name = $name; 86 $this->shortname = $shortname; 87 $this->description = $description; 88 $this->storedfile = $storedfile; 89 } 90 91 /** 92 * Create a preset instance from a stored file. 93 * 94 * @param manager $manager the current instance manager 95 * @param stored_file $file the preset root folder 96 * @return preset|null If the given file doesn't belong to the expected component/filearea/context, null will be returned 97 */ 98 public static function create_from_storedfile(manager $manager, stored_file $file): ?self { 99 if ($file->get_component() != DATA_PRESET_COMPONENT 100 || $file->get_filearea() != DATA_PRESET_FILEAREA 101 || $file->get_contextid() != DATA_PRESET_CONTEXT) { 102 return null; 103 } 104 105 $isplugin = false; 106 $name = trim($file->get_filepath(), '/'); 107 $description = static::get_attribute_value($file->get_filepath(), 'description'); 108 109 return new self($manager, $isplugin, $name, $name, $description, $file); 110 } 111 112 /** 113 * Create a preset instance from a plugin. 114 * 115 * @param manager|null $manager the current instance manager 116 * @param string $pluginname the datapreset plugin name 117 * @return preset|null The plugin preset or null if there is no datapreset plugin with the given name. 118 */ 119 public static function create_from_plugin(?manager $manager, string $pluginname): ?self { 120 $found = false; 121 122 $plugins = array_keys(core_component::get_plugin_list('datapreset')); 123 foreach ($plugins as $plugin) { 124 if ($plugin == $pluginname) { 125 $found = true; 126 break; 127 } 128 } 129 130 if (!$found) { 131 // If there is no datapreset plugin with this name, return null. 132 return null; 133 } 134 135 $name = static::get_name_from_plugin($pluginname); 136 $description = static::get_description_from_plugin($pluginname); 137 138 return new self($manager, true, $name, $pluginname, $description); 139 } 140 141 /** 142 * Create a preset instance from a data_record entry, a preset name and a description. 143 * 144 * @param manager $manager the current instance manager 145 * @param string $presetname the preset name 146 * @param string|null $description the preset description 147 * @return preset 148 */ 149 public static function create_from_instance(manager $manager, string $presetname, ?string $description = ''): self { 150 $isplugin = false; 151 152 $path = '/' . $presetname . '/'; 153 $file = static::get_file($path, '.'); 154 if (!is_null($file)) { 155 // If the file is not empty, create the instance based on the storedfile. 156 return self::create_from_storedfile($manager, $file); 157 } 158 159 return new self($manager, $isplugin, $presetname, $presetname, $description, $file); 160 } 161 162 /** 163 * Create a preset instance from the preset fullname. 164 * 165 * The preset fullname is a concatenation of userid and pluginname|presetname used by most 166 * preset pages. Plugins uses userid zero while preset instances has the owner as identifier. 167 * 168 * This method will throw an exception if the preset instance has a different userid thant the one 169 * from the $fullname. However, it won't check the current user capabilities. 170 * 171 * @param manager $manager the current instance manager 172 * @param string $fullname the preset full name 173 * @return preset 174 */ 175 public static function create_from_fullname(manager $manager, string $fullname): self { 176 $parts = explode('/', $fullname, 2); 177 $userid = empty($parts[0]) ? 0 : (int)$parts[0]; 178 $shortname = empty($parts[1]) ? '' : $parts[1]; 179 180 // Shortnames with userid zero are plugins. 181 if ($userid == 0) { 182 return static::create_from_plugin($manager, $shortname); 183 } 184 185 $path = '/' . $shortname . '/'; 186 $file = static::get_file($path, '.'); 187 $result = static::create_from_storedfile($manager, $file); 188 if ($result->get_userid() != $userid) { 189 throw new moodle_exception('invalidpreset', manager::PLUGINNAME); 190 } 191 return $result; 192 } 193 194 /** 195 * Save this preset. 196 * 197 * @return bool true if the preset has been saved; false otherwise. 198 */ 199 public function save(): bool { 200 global $USER; 201 202 if ($this->isplugin) { 203 // Plugin presets can't be saved. 204 return false; 205 } 206 207 if (!is_null($this->storedfile)) { 208 // It's a pre-existing preset, so it needs to be updated. 209 return $this->update_user_preset(); 210 } 211 212 // The preset hasn't been saved before. 213 $fs = get_file_storage(); 214 215 // Create and save the preset.xml file, with the description, settings, fields... 216 $filerecord = static::get_filerecord('preset.xml', $this->get_path(), $USER->id); 217 $fs->create_file_from_string($filerecord, $this->generate_preset_xml()); 218 219 // Create and save the template files. 220 $instance = $this->manager->get_instance(); 221 foreach (manager::TEMPLATES_LIST as $templatename => $templatefile) { 222 $filerecord->filename = $templatefile; 223 $fs->create_file_from_string($filerecord, $instance->{$templatename}); 224 } 225 // Update the storedfile with the one we've just saved. 226 $this->storedfile = static::get_file($this->get_path(), '.'); 227 228 return true; 229 } 230 231 /** 232 * Update the stored user preset. 233 * This method is used internally by the save method. 234 * 235 * @return bool true if the preset has been saved; false otherwise. 236 */ 237 private function update_user_preset(): bool { 238 global $USER; 239 240 $result = false; 241 $shouldbesaved = false; 242 243 // Update description (if required). 244 $oldpresetfile = static::get_file($this->storedfile->get_filepath(), 'preset.xml'); 245 $presetxml = $oldpresetfile->get_content(); 246 $parsedxml = simplexml_load_string($presetxml); 247 if (property_exists($parsedxml, 'description')) { 248 if ($parsedxml->description != $this->description) { 249 $parsedxml->description = $this->description; 250 $shouldbesaved = true; 251 } 252 } else { 253 if (!is_null($this->description)) { 254 $parsedxml->addChild('description', $this->description); 255 $shouldbesaved = true; 256 } 257 } 258 259 // Update name (if required). 260 $oldname = trim($this->storedfile->get_filepath(), '/'); 261 $newpath = '/' . $this->name . '/'; 262 if ($oldname != $this->name) { 263 // Preset name has changed, so files need to be updated too because the preset name is saved in the filepath. 264 foreach (manager::TEMPLATES_LIST as $templatename => $templatefile) { 265 $oldfile = static::get_file($this->storedfile->get_filepath(), $templatefile); 266 $oldfile->rename($newpath, $templatefile); 267 } 268 // The root folder should also be renamed. 269 $this->storedfile->rename($newpath, $this->storedfile->get_filename()); 270 $shouldbesaved = true; 271 } 272 273 // Only save the new preset.xml if there are changes. 274 if ($shouldbesaved) { 275 // Before saving preset.xml, the old preset.xml file should be removed. 276 $oldpresetfile->delete(); 277 // Create the new file with the new content. 278 $filerecord = static::get_filerecord('preset.xml', $newpath, $USER->id); 279 $presetcontent = $parsedxml->asXML(); 280 $fs = get_file_storage(); 281 $fs->create_file_from_string($filerecord, $presetcontent); 282 $result = true; 283 } 284 285 return $result; 286 } 287 288 /** 289 * Export this preset. 290 * 291 * @return string the full path to the exported preset file. 292 */ 293 public function export(): string { 294 if ($this->isplugin) { 295 // For now, only saved presets can be exported. 296 return ''; 297 } 298 299 $presetname = clean_filename($this->name) . '-preset-' . gmdate("Ymd_Hi"); 300 $exportsubdir = "mod_data/presetexport/$presetname"; 301 $exportdir = make_temp_directory($exportsubdir); 302 303 // Generate and write the preset.xml file. 304 $presetxmldata = static::generate_preset_xml(); 305 $presetxmlfile = fopen($exportdir . '/preset.xml', 'w'); 306 fwrite($presetxmlfile, $presetxmldata); 307 fclose($presetxmlfile); 308 309 // Write the template files. 310 $instance = $this->manager->get_instance(); 311 foreach (manager::TEMPLATES_LIST as $templatename => $templatefilename) { 312 $templatefile = fopen("$exportdir/$templatefilename", 'w'); 313 fwrite($templatefile, $instance->{$templatename} ?? ''); 314 fclose($templatefile); 315 } 316 317 // Check if all files have been generated. 318 if (! static::is_directory_a_preset($exportdir)) { 319 throw new \moodle_exception('generateerror', 'data'); 320 } 321 322 $presetfilenames = array_merge(array_values(manager::TEMPLATES_LIST), ['preset.xml']); 323 324 $filelist = []; 325 foreach ($presetfilenames as $filename) { 326 $filelist[$filename] = $exportdir . '/' . $filename; 327 } 328 329 $exportfile = $exportdir.'.zip'; 330 file_exists($exportfile) && unlink($exportfile); 331 332 $fp = get_file_packer('application/zip'); 333 $fp->archive_to_pathname($filelist, $exportfile); 334 335 foreach ($filelist as $file) { 336 unlink($file); 337 } 338 rmdir($exportdir); 339 340 return $exportfile; 341 } 342 343 /** 344 * Return the preset author. 345 * 346 * Preset plugins do not have any user id. 347 * 348 * @return int|null the userid or null if it is a plugin 349 */ 350 public function get_userid(): ?int { 351 if (!empty($this->storedfile)) { 352 return $this->storedfile->get_userid(); 353 } 354 return null; 355 } 356 357 /** 358 * Return the preset fullname. 359 * 360 * Preset fullname is used mostly for urls. 361 * 362 * @return string the preset fullname 363 */ 364 public function get_fullname(): string { 365 $userid = $this->get_userid() ?? '0'; 366 return "{$userid}/{$this->shortname}"; 367 } 368 369 /** 370 * Returns the preset path. 371 * 372 * @return string|null the preset path is null for plugins and /presetname/ for saved presets. 373 */ 374 public function get_path(): ?string { 375 if ($this->isplugin) { 376 return null; 377 } 378 379 if (!empty($this->storedfile)) { 380 return $this->storedfile->get_filepath(); 381 } 382 383 return '/' . $this->name . '/'; 384 } 385 386 /** 387 * Return the field instances of the preset. 388 * 389 * @param bool $forpreview if the fields are only for preview 390 * @return data_field_base[] and array with field objects 391 */ 392 public function get_fields(bool $forpreview = false): array { 393 if ($this->fields !== null) { 394 return $this->fields; 395 } 396 // Parse the preset.xml file. 397 $this->load_preset_xml(); 398 if (empty($this->xmlinfo) || empty($this->xmlinfo->field)) { 399 $this->fields = []; 400 return $this->fields; 401 } 402 // Generate field instances. 403 $result = []; 404 foreach ($this->xmlinfo->field as $fieldinfo) { 405 $result[(string) $fieldinfo->name] = $this->get_field_instance($fieldinfo, count($result), $forpreview); 406 } 407 $this->fields = $result; 408 return $result; 409 } 410 411 /** 412 * Convert a preset.xml field data into field instance. 413 * 414 * @param SimpleXMLElement $fieldinfo the field xml information 415 * @param int $id the field id to use 416 * @param bool $forpreview if the field should support preview 417 * @return data_field_base the field instance 418 */ 419 private function get_field_instance( 420 SimpleXMLElement $fieldinfo, 421 int $id = 0, 422 bool $forpreview = false 423 ): data_field_base { 424 global $CFG; // Some old field plugins require $CFG to be in the scope. 425 426 $fieldrecord = $this->get_fake_field_record($fieldinfo, $id); 427 $instance = $this->manager->get_instance(); 428 $cm = $this->manager->get_coursemodule(); 429 430 // Include the plugin. 431 $filepath = "{$this->manager->path}/field/{$fieldrecord->type}/field.class.php"; 432 if (file_exists($filepath)) { 433 require_once($filepath); 434 } 435 $classname = "data_field_{$fieldrecord->type}"; 436 $newfield = null; 437 if (class_exists($classname)) { 438 $newfield = new $classname($fieldrecord, $instance, $cm); 439 if ($forpreview && !$newfield->supports_preview()) { 440 $newfield = new data_field_base($fieldrecord, $instance, $cm); 441 } 442 } else { 443 $newfield = new data_field_base($fieldrecord, $instance, $cm); 444 } 445 if ($forpreview) { 446 $newfield->set_preview(true); 447 } 448 return $newfield; 449 } 450 451 /** 452 * Generate a fake field record fomr the preset.xml field data. 453 * 454 * @param SimpleXMLElement $fieldinfo the field xml information 455 * @param int $id the field id to use 456 * @return stdClass the fake record 457 */ 458 private function get_fake_field_record(SimpleXMLElement $fieldinfo, int $id = 0): stdClass { 459 $instance = $this->manager->get_instance(); 460 // Generate stub record. 461 $fieldrecord = (object)[ 462 'id' => $id, 463 'dataid' => $instance->id, 464 'type' => (string) $fieldinfo->type, 465 'name' => (string) $fieldinfo->name, 466 'description' => (string) $fieldinfo->description ?? '', 467 'required' => (int) $fieldinfo->required ?? 0, 468 ]; 469 for ($i = 1; $i < 11; $i++) { 470 $name = "param{$i}"; 471 $fieldrecord->{$name} = null; 472 if (property_exists($fieldinfo, $name)) { 473 $fieldrecord->{$name} = (string) $fieldinfo->{$name}; 474 } 475 } 476 return $fieldrecord; 477 } 478 479 /** 480 * Return sample entries to preview this preset. 481 * 482 * @param int $count the number of entries to generate. 483 * @return array of sample entries 484 */ 485 public function get_sample_entries(int $count = 1): array { 486 global $USER; 487 $fields = $this->get_fields(); 488 $instance = $this->manager->get_instance(); 489 $entries = []; 490 for ($current = 1; $current <= $count; $current++) { 491 $entry = (object)[ 492 'id' => $current, 493 'userid' => $USER->id, 494 'groupid' => 0, 495 'dataid' => $instance->id, 496 'timecreated' => time(), 497 'timemodified' => time(), 498 'approved' => 1, 499 ]; 500 // Add all necessary user fields. 501 $userfieldsapi = \core_user\fields::for_userpic()->excluding('id'); 502 $fields = $userfieldsapi->get_required_fields(); 503 foreach ($fields as $field) { 504 $entry->{$field} = $USER->{$field}; 505 } 506 $entries[$current] = $entry; 507 } 508 return $entries; 509 } 510 511 /** 512 * Load all the information from the preset.xml. 513 */ 514 protected function load_preset_xml() { 515 if (!empty($this->xmlinfo)) { 516 return; 517 } 518 // Load everything from the XML. 519 $presetxml = null; 520 if ($this->isplugin) { 521 $path = $this->manager->path . '/preset/' . $this->shortname . '/preset.xml'; 522 $presetxml = file_get_contents($path); 523 } else { 524 $presetxml = static::get_content_from_file($this->storedfile->get_filepath(), 'preset.xml'); 525 } 526 $this->xmlinfo = simplexml_load_string($presetxml); 527 } 528 529 /** 530 * Return the template content from the preset. 531 * 532 * @param string $templatename the template name 533 * @return string the template content 534 */ 535 public function get_template_content(string $templatename): string { 536 $filename = "{$templatename}.html"; 537 if ($templatename == 'csstemplate') { 538 $filename = "{$templatename}.css"; 539 } 540 if ($templatename == 'jstemplate') { 541 $filename = "{$templatename}.js"; 542 } 543 if ($this->isplugin) { 544 $path = $this->manager->path . '/preset/' . $this->shortname . '/' . $filename; 545 $result = file_get_contents($path); 546 } else { 547 $result = static::get_content_from_file($this->storedfile->get_filepath(), $filename); 548 } 549 if (empty($result)) { 550 return ''; 551 } 552 return $result; 553 } 554 555 /** 556 * Checks if a directory contains all the required files to define a preset. 557 * 558 * @param string $directory The patch to check if it contains the preset files or not. 559 * @return bool True if the directory contains all the preset files; false otherwise. 560 */ 561 public static function is_directory_a_preset(string $directory): bool { 562 $status = true; 563 $directory = rtrim($directory, '/\\') . '/'; 564 $presetfilenames = array_merge(array_values(manager::TEMPLATES_LIST), ['preset.xml']); 565 foreach ($presetfilenames as $filename) { 566 $status &= file_exists($directory.$filename); 567 } 568 569 return $status; 570 } 571 572 /** 573 * Returns the best name to show for a datapreset plugin. 574 * 575 * @param string $pluginname The datapreset plugin name. 576 * @return string The plugin preset name to display. 577 */ 578 public static function get_name_from_plugin(string $pluginname): string { 579 $pos = strpos($pluginname, '/'); 580 if ($pos !== false) { 581 $pluginname = substr($pluginname, $pos + 1); 582 } 583 if (!strpos(trim($pluginname), ' ') && get_string_manager()->string_exists('modulename', 'datapreset_'.$pluginname)) { 584 return get_string('modulename', 'datapreset_'.$pluginname); 585 } else { 586 return $pluginname; 587 } 588 } 589 590 /** 591 * Returns the description to show for a datapreset plugin. 592 * 593 * @param string $pluginname The datapreset plugin name. 594 * @return string The plugin preset description to display. 595 */ 596 public static function get_description_from_plugin(string $pluginname): string { 597 if (get_string_manager()->string_exists('modulename_help', 'datapreset_'.$pluginname)) { 598 return get_string('modulename_help', 'datapreset_'.$pluginname); 599 } else { 600 return ''; 601 } 602 } 603 604 /** 605 * Helper to get the value of one of the elements in the presets.xml file. 606 * 607 * @param string $filepath The preset filepath. 608 * @param string $name Attribute name to return. 609 * @return string|null The attribute value; null if the it doesn't exist or the file is not a valid XML. 610 */ 611 protected static function get_attribute_value(string $filepath, string $name): ?string { 612 $value = null; 613 $presetxml = static::get_content_from_file($filepath, 'preset.xml'); 614 $parsedxml = simplexml_load_string($presetxml); 615 if ($parsedxml) { 616 switch ($name) { 617 case 'description': 618 if (property_exists($parsedxml, 'description')) { 619 $value = $parsedxml->description; 620 } 621 break; 622 } 623 } 624 625 return $value; 626 } 627 628 /** 629 * Helper method to get a file record given a filename, a filepath and a userid, for any of the preset files. 630 * 631 * @param string $filename The filename for the filerecord that will be returned. 632 * @param string $filepath The filepath for the filerecord that will be returned. 633 * @param int $userid The userid for the filerecord that will be returned. 634 * @return stdClass A filerecord object with the datapreset context, component and filearea and the given information. 635 */ 636 protected static function get_filerecord(string $filename, string $filepath, int $userid): stdClass { 637 $filerecord = new stdClass; 638 $filerecord->contextid = DATA_PRESET_CONTEXT; 639 $filerecord->component = DATA_PRESET_COMPONENT; 640 $filerecord->filearea = DATA_PRESET_FILEAREA; 641 $filerecord->itemid = 0; 642 $filerecord->filepath = $filepath; 643 $filerecord->userid = $userid; 644 $filerecord->filename = $filename; 645 646 return $filerecord; 647 } 648 649 /** 650 * Helper method to retrieve a file. 651 * 652 * @param string $filepath the directory to look in 653 * @param string $filename the name of the file we want 654 * @return stored_file|null the file or null if the file doesn't exist. 655 */ 656 public static function get_file(string $filepath, string $filename): ?stored_file { 657 $file = null; 658 $fs = get_file_storage(); 659 $fileexists = $fs->file_exists( 660 DATA_PRESET_CONTEXT, 661 DATA_PRESET_COMPONENT, 662 DATA_PRESET_FILEAREA, 663 0, 664 $filepath, 665 $filename 666 ); 667 if ($fileexists) { 668 $file = $fs->get_file( 669 DATA_PRESET_CONTEXT, 670 DATA_PRESET_COMPONENT, 671 DATA_PRESET_FILEAREA, 672 0, 673 $filepath, 674 $filename 675 ); 676 } 677 678 return $file; 679 } 680 681 /** 682 * Helper method to retrieve the contents of a file. 683 * 684 * @param string $filepath the directory to look in 685 * @param string $filename the name of the file we want 686 * @return string|null the contents of the file or null if the file doesn't exist. 687 */ 688 protected static function get_content_from_file(string $filepath, string $filename): ?string { 689 $templatefile = static::get_file($filepath, $filename); 690 if ($templatefile) { 691 return $templatefile->get_content(); 692 } 693 694 return null; 695 } 696 697 /** 698 * Helper method to generate the XML for this preset. 699 * 700 * @return string The XML for the preset 701 */ 702 protected function generate_preset_xml(): string { 703 global $DB; 704 705 if ($this->isplugin) { 706 // Only saved presets can generate the preset.xml file. 707 return ''; 708 } 709 710 $presetxmldata = "<preset>\n\n"; 711 712 // Add description. 713 $presetxmldata .= '<description>' . htmlspecialchars($this->description ?? '', ENT_COMPAT) . "</description>\n\n"; 714 715 // Add settings. 716 // Raw settings are not preprocessed during saving of presets. 717 $rawsettings = [ 718 'intro', 719 'comments', 720 'requiredentries', 721 'requiredentriestoview', 722 'maxentries', 723 'rssarticles', 724 'approval', 725 'manageapproved', 726 'defaultsortdir', 727 ]; 728 $presetxmldata .= "<settings>\n"; 729 $instance = $this->manager->get_instance(); 730 // First, settings that do not require any conversion. 731 foreach ($rawsettings as $setting) { 732 $presetxmldata .= "<$setting>" . htmlspecialchars($instance->$setting, ENT_COMPAT) . "</$setting>\n"; 733 } 734 735 // Now specific settings. 736 if ($instance->defaultsort > 0 && $sortfield = data_get_field_from_id($instance->defaultsort, $instance)) { 737 $presetxmldata .= '<defaultsort>' . htmlspecialchars($sortfield->field->name, ENT_COMPAT) . "</defaultsort>\n"; 738 } else { 739 $presetxmldata .= "<defaultsort>0</defaultsort>\n"; 740 } 741 $presetxmldata .= "</settings>\n\n"; 742 743 // Add fields. Grab all that are non-empty. 744 $fields = $DB->get_records('data_fields', ['dataid' => $instance->id]); 745 ksort($fields); 746 if (!empty($fields)) { 747 foreach ($fields as $field) { 748 $presetxmldata .= "<field>\n"; 749 foreach ($field as $key => $value) { 750 if ($value != '' && $key != 'id' && $key != 'dataid') { 751 $presetxmldata .= "<$key>" . htmlspecialchars($value, ENT_COMPAT) . "</$key>\n"; 752 } 753 } 754 $presetxmldata .= "</field>\n\n"; 755 } 756 } 757 $presetxmldata .= '</preset>'; 758 759 // Check this content is a valid XML. 760 $preset = new SimpleXMLElement($presetxmldata); 761 762 return $preset->asXML(); 763 } 764 765 /** 766 * Checks to see if the user has permission to manage the preset. 767 * 768 * @return bool Returns true if the user can manage this preset, false otherwise. 769 */ 770 public function can_manage(): bool { 771 global $USER; 772 773 if ($this->isplugin) { 774 // Plugin presets can't be removed or edited. 775 return false; 776 } 777 778 $context = $this->manager->get_context(); 779 if (has_capability('mod/data:manageuserpresets', $context)) { 780 return true; 781 } else { 782 if ($this->get_userid() == $USER->id) { 783 return true; 784 } 785 } 786 return false; 787 } 788 789 /** 790 * Deletes all files related to a saved preset. 791 * 792 * @return bool True if the preset is a saved preset and the file exists in the file system; false otherwise. 793 */ 794 public function delete(): bool { 795 if ($this->isplugin) { 796 // Plugin presets can't be removed. 797 return false; 798 } 799 800 $exists = false; 801 $filepath = $this->get_path(); 802 803 $dir = self::get_file($filepath, '.'); 804 if (!empty($dir)) { 805 $exists = true; 806 807 $fs = get_file_storage(); 808 $files = $fs->get_directory_files( 809 $dir->get_contextid(), 810 $dir->get_component(), 811 $dir->get_filearea(), 812 $dir->get_itemid(), 813 $filepath 814 ); 815 if (!empty($files)) { 816 foreach ($files as $file) { 817 $file->delete(); 818 } 819 } 820 $dir->delete(); 821 // Reseting storedfile property because the file has been removed. 822 $this->storedfile = null; 823 } 824 825 return $exists; 826 } 827 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body