See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 401 and 402] [Versions 401 and 403]
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 * Step class. 19 * 20 * @package tool_usertours 21 * @copyright 2016 Andrew Nicols <andrew@nicols.co.uk> 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 25 namespace tool_usertours; 26 27 use context_system; 28 use stdClass; 29 30 defined('MOODLE_INTERNAL') || die(); 31 32 /** 33 * Step class. 34 * 35 * @copyright 2016 Andrew Nicols <andrew@nicols.co.uk> 36 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 37 */ 38 class step { 39 40 /** 41 * @var int $id The id of the step. 42 */ 43 protected $id; 44 45 /** 46 * @var int $tourid The id of the tour that this step belongs to. 47 */ 48 protected $tourid; 49 50 /** 51 * @var tour $tour The tour class that this step belongs to. 52 */ 53 protected $tour; 54 55 /** 56 * @var string $title The title of the step. 57 */ 58 protected $title; 59 60 /** 61 * @var string $content The content of this step. 62 */ 63 protected $content; 64 65 /** 66 * @var int $contentformat The content format: FORMAT_MOODLE/FORMAT_HTML/FORMAT_PLAIN/FORMAT_MARKDOWN. 67 */ 68 protected $contentformat; 69 70 /** 71 * @var int $targettype The type of target. 72 */ 73 protected $targettype; 74 75 /** 76 * @var string $targetvalue The value for this type of target. 77 */ 78 protected $targetvalue; 79 80 /** 81 * @var int $sortorder The sort order. 82 */ 83 protected $sortorder; 84 85 /** 86 * @var object $config The configuration as an object. 87 */ 88 protected $config; 89 90 /** 91 * @var bool $dirty Whether the step has been changed since it was loaded 92 */ 93 protected $dirty = false; 94 95 /** 96 * @var bool $isimporting Whether the step is being imported or not. 97 */ 98 protected $isimporting; 99 100 /** 101 * @var stdClass[] $files The list of attached files for this step. 102 */ 103 protected $files = []; 104 105 /** 106 * Fetch the step instance. 107 * 108 * @param int $id The id of the step to be retrieved. 109 * @return step 110 */ 111 public static function instance($id) { 112 $step = new step(); 113 return $step->fetch($id); 114 } 115 116 /** 117 * Load the step instance. 118 * 119 * @param stdClass $record The step record to be loaded. 120 * @param bool $clean Clean the values. 121 * @param bool $isimporting Whether the step is being imported or not. 122 * @return step 123 */ 124 public static function load_from_record($record, $clean = false, bool $isimporting = false) { 125 $step = new self(); 126 $step->set_importing($isimporting); 127 return $step->reload_from_record($record, $clean); 128 } 129 130 /** 131 * Fetch the step instance. 132 * 133 * @param int $id The id of the step to be retrieved. 134 * @return step 135 */ 136 protected function fetch($id) { 137 global $DB; 138 139 return $this->reload_from_record( 140 $DB->get_record('tool_usertours_steps', array('id' => $id)) 141 ); 142 } 143 144 /** 145 * Refresh the current step from the datbase. 146 * 147 * @return step 148 */ 149 protected function reload() { 150 return $this->fetch($this->id); 151 } 152 153 /** 154 * Reload the current step from the supplied record. 155 * 156 * @param stdClass $record The step record to be loaded. 157 * @param bool $clean Clean the values. 158 * @return step 159 */ 160 protected function reload_from_record($record, $clean = false) { 161 $this->id = $record->id; 162 $this->tourid = $record->tourid; 163 if ($clean) { 164 $this->title = clean_param($record->title, PARAM_TEXT); 165 $this->content = clean_text($record->content); 166 } else { 167 $this->title = $record->title; 168 $this->content = $record->content; 169 } 170 $this->contentformat = isset($record->contentformat) ? $record->contentformat : FORMAT_MOODLE; 171 $this->targettype = $record->targettype; 172 $this->targetvalue = $record->targetvalue; 173 $this->sortorder = $record->sortorder; 174 $this->config = json_decode($record->configdata); 175 $this->dirty = false; 176 177 if ($this->isimporting && isset($record->files)) { 178 // We are importing/exporting the step. 179 $this->files = $record->files; 180 } 181 182 return $this; 183 } 184 185 /** 186 * Set the import state for the step. 187 * 188 * @param bool $isimporting True if the step is imported, otherwise false. 189 * @return void 190 */ 191 protected function set_importing(bool $isimporting = false): void { 192 $this->isimporting = $isimporting; 193 } 194 195 /** 196 * Get the ID of the step. 197 * 198 * @return int 199 */ 200 public function get_id() { 201 return $this->id; 202 } 203 204 /** 205 * Get the Tour ID of the step. 206 * 207 * @return int 208 */ 209 public function get_tourid() { 210 return $this->tourid; 211 } 212 213 /** 214 * Get the Tour instance that this step belongs to. 215 * 216 * @return tour 217 */ 218 public function get_tour() { 219 if ($this->tour === null) { 220 $this->tour = tour::instance($this->tourid); 221 } 222 return $this->tour; 223 } 224 225 /** 226 * Set the id of the tour. 227 * 228 * @param int $value The id of the tour. 229 * @return self 230 */ 231 public function set_tourid($value) { 232 $this->tourid = $value; 233 $this->tour = null; 234 $this->dirty = true; 235 236 return $this; 237 } 238 239 /** 240 * Get the Title of the step. 241 * 242 * @return string 243 */ 244 public function get_title() { 245 return $this->title; 246 } 247 248 /** 249 * Set the title for this step. 250 * 251 * @param string $value The new title to use. 252 * @return $this 253 */ 254 public function set_title($value) { 255 $this->title = clean_text($value); 256 $this->dirty = true; 257 258 return $this; 259 } 260 261 /** 262 * Get the content format of the step. 263 * 264 * @return int 265 */ 266 public function get_contentformat(): int { 267 return $this->contentformat; 268 } 269 270 /** 271 * Get the body content of the step. 272 * 273 * @return string 274 */ 275 public function get_content() { 276 return $this->content; 277 } 278 279 /** 280 * Set the content value for this step. 281 * 282 * @param string $value The new content to use. 283 * @param int $format The new format to use: FORMAT_MOODLE/FORMAT_HTML/FORMAT_PLAIN/FORMAT_MARKDOWN. 284 * @return $this 285 */ 286 public function set_content($value, $format = FORMAT_HTML) { 287 $this->content = clean_text($value); 288 $this->contentformat = $format; 289 $this->dirty = true; 290 291 return $this; 292 } 293 294 /** 295 * Get the content value for this step. 296 * 297 * @return string 298 */ 299 public function get_targettype() { 300 return $this->targettype; 301 } 302 303 /** 304 * Set the type of target for this step. 305 * 306 * @param string $value The new target to use. 307 * @return $this 308 */ 309 public function set_targettype($value) { 310 $this->targettype = $value; 311 $this->dirty = true; 312 313 return $this; 314 } 315 316 /** 317 * Get the target value for this step. 318 * 319 * @return string 320 */ 321 public function get_targetvalue() { 322 return $this->targetvalue; 323 } 324 325 /** 326 * Set the target value for this step. 327 * 328 * @param string $value The new target value to use. 329 * @return $this 330 */ 331 public function set_targetvalue($value) { 332 $this->targetvalue = $value; 333 $this->dirty = true; 334 335 return $this; 336 } 337 338 /** 339 * Get the target instance for this step. 340 * 341 * @return target 342 */ 343 public function get_target() { 344 return target::get_target_instance($this); 345 } 346 347 /** 348 * Get the current sortorder for this step. 349 * 350 * @return int 351 */ 352 public function get_sortorder() { 353 return (int) $this->sortorder; 354 } 355 356 /** 357 * Whether this step is the first step in the tour. 358 * 359 * @return boolean 360 */ 361 public function is_first_step() { 362 return ($this->get_sortorder() === 0); 363 } 364 365 /** 366 * Whether this step is the last step in the tour. 367 * 368 * @return boolean 369 */ 370 public function is_last_step() { 371 $stepcount = $this->get_tour()->count_steps(); 372 return ($this->get_sortorder() === $stepcount - 1); 373 } 374 375 /** 376 * Set the sortorder for this step. 377 * 378 * @param int $value The new sortorder to use. 379 * @return $this 380 */ 381 public function set_sortorder($value) { 382 $this->sortorder = $value; 383 $this->dirty = true; 384 385 return $this; 386 } 387 388 /** 389 * Get the link to move this step up in the sortorder. 390 * 391 * @return moodle_url 392 */ 393 public function get_moveup_link() { 394 return helper::get_move_step_link($this->get_id(), helper::MOVE_UP); 395 } 396 397 /** 398 * Get the link to move this step down in the sortorder. 399 * 400 * @return moodle_url 401 */ 402 public function get_movedown_link() { 403 return helper::get_move_step_link($this->get_id(), helper::MOVE_DOWN); 404 } 405 406 /** 407 * Get the value of the specified configuration item. 408 * 409 * If notvalue was found, and no default was specified, the default for the tour will be used. 410 * 411 * @param string $key The configuration key to set. 412 * @param mixed $default The default value to use if a value was not found. 413 * @return mixed 414 */ 415 public function get_config($key = null, $default = null) { 416 if ($this->config === null) { 417 $this->config = (object) array(); 418 } 419 420 if ($key === null) { 421 return $this->config; 422 } 423 424 if ($this->get_targettype() !== null) { 425 $target = $this->get_target(); 426 if ($target->is_setting_forced($key)) { 427 return $target->get_forced_setting_value($key); 428 } 429 } 430 431 if (property_exists($this->config, $key)) { 432 return $this->config->$key; 433 } 434 435 if ($default !== null) { 436 return $default; 437 } 438 439 return $this->get_tour()->get_config($key); 440 } 441 442 /** 443 * Set the configuration item as specified. 444 * 445 * @param string $key The configuration key to set. 446 * @param mixed $value The new value for the configuration item. 447 * @return $this 448 */ 449 public function set_config($key, $value) { 450 if ($this->config === null) { 451 $this->config = (object) array(); 452 } 453 454 if ($value === null) { 455 unset($this->config->$key); 456 } else { 457 $this->config->$key = $value; 458 } 459 $this->dirty = true; 460 461 return $this; 462 } 463 464 /** 465 * Get the edit link for this step. 466 * 467 * @return moodle_url 468 */ 469 public function get_edit_link() { 470 return helper::get_edit_step_link($this->tourid, $this->id); 471 } 472 473 /** 474 * Get the delete link for this step. 475 * 476 * @return moodle_url 477 */ 478 public function get_delete_link() { 479 return helper::get_delete_step_link($this->id); 480 } 481 482 /** 483 * Embed attached file to the json file for step. 484 * 485 * @return array List of files. 486 */ 487 protected function embed_files(): array { 488 $systemcontext = context_system::instance(); 489 $fs = get_file_storage(); 490 $areafiles = $fs->get_area_files($systemcontext->id, 'tool_usertours', 'stepcontent', $this->id); 491 $files = []; 492 foreach ($areafiles as $file) { 493 if ($file->is_directory()) { 494 continue; 495 } 496 $files[] = [ 497 'name' => $file->get_filename(), 498 'path' => $file->get_filepath(), 499 'content' => base64_encode($file->get_content()), 500 'encode' => 'base64' 501 ]; 502 } 503 504 return $files; 505 } 506 507 /** 508 * Get the embed files information and create store_file for this step. 509 * 510 * @return void 511 */ 512 protected function extract_files() { 513 $fs = get_file_storage(); 514 $systemcontext = context_system::instance(); 515 foreach ($this->files as $file) { 516 $filename = $file->name; 517 $filepath = $file->path; 518 $filecontent = $file->content; 519 $filerecord = [ 520 'contextid' => $systemcontext->id, 521 'component' => 'tool_usertours', 522 'filearea' => 'stepcontent', 523 'itemid' => $this->get_id(), 524 'filepath' => $filepath, 525 'filename' => $filename, 526 ]; 527 $fs->create_file_from_string($filerecord, base64_decode($filecontent)); 528 } 529 } 530 531 /** 532 * Prepare this step for saving to the database. 533 * 534 * @param bool $isexporting Whether the step is being exported or not. 535 * @return object 536 */ 537 public function to_record(bool $isexporting = false) { 538 $record = [ 539 'id' => $this->id, 540 'tourid' => $this->tourid, 541 'title' => $this->title, 542 'content' => $this->content, 543 'contentformat' => $this->contentformat, 544 'targettype' => $this->targettype, 545 'targetvalue' => $this->targetvalue, 546 'sortorder' => $this->sortorder, 547 'configdata' => json_encode($this->config), 548 ]; 549 if ($isexporting) { 550 // We are exporting the step, adding files node to the json record. 551 $record['files'] = $this->embed_files(); 552 } 553 return (object) $record; 554 } 555 556 /** 557 * Calculate the next sort-order value. 558 * 559 * @return int 560 */ 561 protected function calculate_sortorder() { 562 $count = $this->get_tour()->count_steps(); 563 $this->sortorder = $count; 564 565 return $this; 566 } 567 568 /** 569 * Save the tour and it's configuration to the database. 570 * 571 * @param boolean $force Whether to force writing to the database. 572 * @return $this 573 */ 574 public function persist($force = false) { 575 global $DB; 576 577 if (!$this->dirty && !$force) { 578 return $this; 579 } 580 581 if ($this->id) { 582 $record = $this->to_record(); 583 $DB->update_record('tool_usertours_steps', $record); 584 } else { 585 $this->calculate_sortorder(); 586 $record = $this->to_record(); 587 unset($record->id); 588 $this->id = $DB->insert_record('tool_usertours_steps', $record); 589 $this->get_tour()->reset_step_sortorder(); 590 } 591 592 $systemcontext = context_system::instance(); 593 if ($draftid = file_get_submitted_draft_itemid('content')) { 594 // Take any files added to the stepcontent draft file area and 595 // convert them into the proper event description file area. Also 596 // parse the content text and replace the URLs to the draft files 597 // with the @@PLUGIN_FILE@@ placeholder to be persisted in the DB. 598 $this->content = file_save_draft_area_files( 599 $draftid, 600 $systemcontext->id, 601 'tool_usertours', 602 'stepcontent', 603 $this->id, 604 ['subdirs' => true], 605 $this->content 606 ); 607 $DB->set_field('tool_usertours_steps', 'content', $this->content, ['id' => $this->id]); 608 } 609 610 if ($this->isimporting) { 611 // We are importing the step, we need to create store_file from the json record. 612 $this->extract_files(); 613 } 614 $this->reload(); 615 616 // Notify of a change to the step configuration. 617 // This must be done separately to tour change notifications. 618 cache::notify_step_change($this->get_tourid()); 619 620 // Notify the cache that a tour has changed. 621 // Tours are only stored in the cache if there are steps. 622 // If there step count has changed for some reason, this will change the potential cache results. 623 cache::notify_tour_change(); 624 625 return $this; 626 } 627 628 /** 629 * Remove this step. 630 */ 631 public function remove() { 632 global $DB; 633 634 if ($this->id === null) { 635 return; 636 } 637 638 $DB->delete_records('tool_usertours_steps', array('id' => $this->id)); 639 $this->get_tour()->reset_step_sortorder(); 640 641 // Notify of a change to the step configuration. 642 // This must be done separately to tour change notifications. 643 cache::notify_step_change($this->get_id()); 644 645 // Notify the cache that a tour has changed. 646 // Tours are only stored in the cache if there are steps. 647 // If there step count has changed for some reason, this will change the potential cache results. 648 cache::notify_tour_change(); 649 } 650 651 /** 652 * Get the list of possible placement options. 653 * 654 * @return array 655 */ 656 public function get_placement_options() { 657 return configuration::get_placement_options(true); 658 } 659 660 /** 661 * The list of possible configuration keys. 662 * 663 * @return array 664 */ 665 public static function get_config_keys() { 666 return [ 667 'placement', 668 'orphan', 669 'backdrop', 670 'reflex', 671 ]; 672 } 673 674 /** 675 * Add the step configuration to the form. 676 * 677 * @param MoodleQuickForm $mform The form to add configuration to. 678 * @return $this 679 */ 680 public function add_config_to_form(\MoodleQuickForm $mform) { 681 $tour = $this->get_tour(); 682 683 $options = configuration::get_placement_options($tour->get_config('placement')); 684 $mform->addElement('select', 'placement', get_string('placement', 'tool_usertours'), $options); 685 $mform->addHelpButton('placement', 'placement', 'tool_usertours'); 686 687 $this->add_config_field_to_form($mform, 'orphan'); 688 $this->add_config_field_to_form($mform, 'backdrop'); 689 $this->add_config_field_to_form($mform, 'reflex'); 690 691 return $this; 692 } 693 694 /** 695 * Add the specified step field configuration to the form. 696 * 697 * @param MoodleQuickForm $mform The form to add configuration to. 698 * @param string $key The key to add. 699 * @return $this 700 */ 701 public function add_config_field_to_form(\MoodleQuickForm $mform, $key) { 702 $tour = $this->get_tour(); 703 704 $default = (bool) $tour->get_config($key); 705 706 $options = [ 707 true => get_string('yes'), 708 false => get_string('no'), 709 ]; 710 711 if (!isset($options[$default])) { 712 $default = configuration::get_default_value($key); 713 } 714 715 $options = array_reverse($options, true); 716 $options[configuration::TOURDEFAULT] = get_string('defaultvalue', 'tool_usertours', $options[$default]); 717 $options = array_reverse($options, true); 718 719 $mform->addElement('select', $key, get_string($key, 'tool_usertours'), $options); 720 $mform->setDefault($key, configuration::TOURDEFAULT); 721 $mform->addHelpButton($key, $key, 'tool_usertours'); 722 723 return $this; 724 } 725 726 /** 727 * Prepare the configuration data for the moodle form. 728 * 729 * @return object 730 */ 731 public function prepare_data_for_form() { 732 $data = $this->to_record(); 733 foreach (self::get_config_keys() as $key) { 734 $data->$key = $this->get_config($key, configuration::get_step_default_value($key)); 735 } 736 737 if ($this->get_targettype() !== null) { 738 $this->get_target()->prepare_data_for_form($data); 739 } 740 741 // Prepare content for editing in a form 'editor' field type. 742 $draftitemid = file_get_submitted_draft_itemid('tool_usertours'); 743 $systemcontext = context_system::instance(); 744 $data->content = [ 745 'format' => $data->contentformat, 746 'itemid' => $draftitemid, 747 'text' => file_prepare_draft_area( 748 $draftitemid, 749 $systemcontext->id, 750 'tool_usertours', 751 'stepcontent', 752 $this->id, 753 ['subdirs' => true], 754 $data->content 755 ), 756 ]; 757 758 return $data; 759 } 760 761 /** 762 * Handle submission of the step editing form. 763 * 764 * @param local\forms\editstep $mform The sumitted form. 765 * @param stdClass $data The submitted data. 766 * @return $this 767 */ 768 public function handle_form_submission(local\forms\editstep &$mform, stdClass $data) { 769 $this->set_title($data->title); 770 $this->set_content($data->content['text'], $data->content['format']); 771 $this->set_targettype($data->targettype); 772 773 $this->set_targetvalue($this->get_target()->get_value_from_form($data)); 774 775 foreach (self::get_config_keys() as $key) { 776 if (!$this->get_target()->is_setting_forced($key)) { 777 if (isset($data->$key)) { 778 $value = $data->$key; 779 } else { 780 $value = configuration::TOURDEFAULT; 781 } 782 if ($value === configuration::TOURDEFAULT) { 783 $this->set_config($key, null); 784 } else { 785 $this->set_config($key, $value); 786 } 787 } 788 } 789 790 $this->persist(); 791 792 return $this; 793 } 794 795 /** 796 * Attempt to fetch any matching langstring if the string is in the 797 * format identifier,component. 798 * 799 * @deprecated since Moodle 4.0 MDL-72783. Please use helper::get_string_from_input() instead. 800 * @param string $string 801 * @return string 802 */ 803 public static function get_string_from_input($string) { 804 debugging('Use of ' . __FUNCTION__ . 805 '() have been deprecated, please update your code to use helper::get_string_from_input()', DEBUG_DEVELOPER); 806 return helper::get_string_from_input($string); 807 } 808 809 /** 810 * Attempt to replace PIXICON placeholder with the correct images for tour step content. 811 * 812 * @param string $content Tour content 813 * @return string Processed tour content 814 */ 815 public static function get_step_image_from_input(string $content): string { 816 if (strpos($content, '@@PIXICON') === false) { 817 return $content; 818 } 819 820 $content = preg_replace_callback('%@@PIXICON::(?P<identifier>([^::]*))::(?P<component>([^@@]*))@@%', 821 function(array $matches) { 822 global $OUTPUT; 823 $component = $matches['component']; 824 if ($component == 'moodle') { 825 $component = 'core'; 826 } 827 return \html_writer::img($OUTPUT->image_url($matches['identifier'], $component)->out(false), '', 828 ['class' => 'img-fluid']); 829 }, 830 $content 831 ); 832 833 return $content; 834 } 835 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body