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 * Tour 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 tool_usertours\local\clientside_filter\clientside_filter; 28 29 defined('MOODLE_INTERNAL') || die(); 30 31 /** 32 * Tour class. 33 * 34 * @copyright 2016 Andrew Nicols <andrew@nicols.co.uk> 35 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 36 */ 37 class tour { 38 39 /** 40 * The tour is currently disabled 41 * 42 * @var DISABLED 43 */ 44 const DISABLED = 0; 45 46 /** 47 * The tour is currently disabled 48 * 49 * @var DISABLED 50 */ 51 const ENABLED = 1; 52 53 /** 54 * The user preference value to indicate the time of completion of the tour for a user. 55 * 56 * @var TOUR_LAST_COMPLETED_BY_USER 57 */ 58 const TOUR_LAST_COMPLETED_BY_USER = 'tool_usertours_tour_completion_time_'; 59 60 /** 61 * The user preference value to indicate the time that a user last requested to see the tour. 62 * 63 * @var TOUR_REQUESTED_BY_USER 64 */ 65 const TOUR_REQUESTED_BY_USER = 'tool_usertours_tour_reset_time_'; 66 67 /** 68 * @var $id The tour ID. 69 */ 70 protected $id; 71 72 /** 73 * @var $name The tour name. 74 */ 75 protected $name; 76 77 /** 78 * @var $description The tour description. 79 */ 80 protected $description; 81 82 /** 83 * @var $pathmatch The tour pathmatch. 84 */ 85 protected $pathmatch; 86 87 /** 88 * @var $enabled The tour enabled state. 89 */ 90 protected $enabled; 91 92 /** 93 * @var $endtourlabel The end tour label. 94 */ 95 protected $endtourlabel; 96 97 /** 98 * @var $sortorder The sort order. 99 */ 100 protected $sortorder; 101 102 /** 103 * @var $dirty Whether the current view of the tour has been modified. 104 */ 105 protected $dirty = false; 106 107 /** 108 * @var $config The configuration object for the tour. 109 */ 110 protected $config; 111 112 /** 113 * @var $filtervalues The filter configuration object for the tour. 114 */ 115 protected $filtervalues; 116 117 /** 118 * @var $steps The steps in this tour. 119 */ 120 protected $steps = []; 121 122 /** 123 * @var bool $displaystepnumbers Display the step numbers in this tour. 124 */ 125 protected $displaystepnumbers = true; 126 127 /** 128 * Create an instance of the specified tour. 129 * 130 * @param int $id The ID of the tour to load. 131 * @return tour 132 */ 133 public static function instance($id) { 134 $tour = new self(); 135 return $tour->fetch($id); 136 } 137 138 /** 139 * Create an instance of tour from its provided DB record. 140 * 141 * @param stdClass $record The record of the tour to load. 142 * @param boolean $clean Clean the values. 143 * @return tour 144 */ 145 public static function load_from_record($record, $clean = false) { 146 $tour = new self(); 147 return $tour->reload_from_record($record, $clean); 148 } 149 150 /** 151 * Fetch the specified tour into the current object. 152 * 153 * @param int $id The ID of the tour to fetch. 154 * @return tour 155 */ 156 protected function fetch($id) { 157 global $DB; 158 159 return $this->reload_from_record( 160 $DB->get_record('tool_usertours_tours', array('id' => $id), '*', MUST_EXIST) 161 ); 162 } 163 164 /** 165 * Reload the current tour from database. 166 * 167 * @return tour 168 */ 169 protected function reload() { 170 return $this->fetch($this->id); 171 } 172 173 /** 174 * Reload the tour into the current object. 175 * 176 * @param stdClass $record The record to reload. 177 * @param boolean $clean Clean the values. 178 * @return tour 179 */ 180 protected function reload_from_record($record, $clean = false) { 181 $this->id = $record->id; 182 if (!property_exists($record, 'description')) { 183 if (property_exists($record, 'comment')) { 184 $record->description = $record->comment; 185 unset($record->comment); 186 } 187 } 188 if ($clean) { 189 $this->name = clean_param($record->name, PARAM_TEXT); 190 $this->description = clean_text($record->description); 191 } else { 192 $this->name = $record->name; 193 $this->description = $record->description; 194 } 195 $this->pathmatch = $record->pathmatch; 196 $this->enabled = $record->enabled; 197 if (isset($record->sortorder)) { 198 $this->sortorder = $record->sortorder; 199 } 200 $this->endtourlabel = $record->endtourlabel ?? null; 201 $this->config = json_decode($record->configdata); 202 $this->dirty = false; 203 $this->steps = []; 204 $this->displaystepnumbers = !empty($record->displaystepnumbers); 205 206 return $this; 207 } 208 209 /** 210 * Fetch all steps in the tour. 211 * 212 * @return step[] 213 */ 214 public function get_steps() { 215 if (empty($this->steps)) { 216 $this->steps = helper::get_steps($this->id); 217 } 218 219 return $this->steps; 220 } 221 222 /** 223 * Count the number of steps in the tour. 224 * 225 * @return int 226 */ 227 public function count_steps() { 228 return count($this->get_steps()); 229 } 230 231 /** 232 * The ID of the tour. 233 * 234 * @return int 235 */ 236 public function get_id() { 237 return $this->id; 238 } 239 240 /** 241 * The name of the tour. 242 * 243 * @return string 244 */ 245 public function get_name() { 246 return $this->name; 247 } 248 249 /** 250 * Set the name of the tour to the specified value. 251 * 252 * @param string $value The new name. 253 * @return $this 254 */ 255 public function set_name($value) { 256 $this->name = clean_param($value, PARAM_TEXT); 257 $this->dirty = true; 258 259 return $this; 260 } 261 262 /** 263 * The description associated with the tour. 264 * 265 * @return string 266 */ 267 public function get_description() { 268 return $this->description; 269 } 270 271 /** 272 * Set the description of the tour to the specified value. 273 * 274 * @param string $value The new description. 275 * @return $this 276 */ 277 public function set_description($value) { 278 $this->description = clean_text($value); 279 $this->dirty = true; 280 281 return $this; 282 } 283 284 /** 285 * The path match for the tour. 286 * 287 * @return string 288 */ 289 public function get_pathmatch() { 290 return $this->pathmatch; 291 } 292 293 /** 294 * Set the patchmatch of the tour to the specified value. 295 * 296 * @param string $value The new patchmatch. 297 * @return $this 298 */ 299 public function set_pathmatch($value) { 300 $this->pathmatch = $value; 301 $this->dirty = true; 302 303 return $this; 304 } 305 306 /** 307 * The enabled state of the tour. 308 * 309 * @return int 310 */ 311 public function get_enabled() { 312 return $this->enabled; 313 } 314 315 /** 316 * Whether the tour is currently enabled. 317 * 318 * @return boolean 319 */ 320 public function is_enabled() { 321 return ($this->enabled == self::ENABLED); 322 } 323 324 /** 325 * Set the enabled state of the tour to the specified value. 326 * 327 * @param boolean $value The new state. 328 * @return $this 329 */ 330 public function set_enabled($value) { 331 $this->enabled = $value; 332 $this->dirty = true; 333 334 return $this; 335 } 336 337 /** 338 * The end tour label for the tour. 339 * 340 * @return string 341 */ 342 public function get_endtourlabel(): string { 343 if ($this->endtourlabel) { 344 $label = helper::get_string_from_input($this->endtourlabel); 345 } else if ($this->count_steps() == 1) { 346 $label = get_string('endonesteptour', 'tool_usertours'); 347 } else { 348 $label = get_string('endtour', 'tool_usertours'); 349 } 350 351 return $label; 352 } 353 354 /** 355 * Set the endtourlabel of the tour to the specified value. 356 * 357 * @param string $value 358 * @return $this 359 */ 360 public function set_endtourlabel(string $value): tour { 361 $this->endtourlabel = $value; 362 $this->dirty = true; 363 364 return $this; 365 } 366 367 /** 368 * The link to view this tour. 369 * 370 * @return moodle_url 371 */ 372 public function get_view_link() { 373 return helper::get_view_tour_link($this->id); 374 } 375 376 /** 377 * The link to edit this tour. 378 * 379 * @return moodle_url 380 */ 381 public function get_edit_link() { 382 return helper::get_edit_tour_link($this->id); 383 } 384 385 /** 386 * The link to reset the state of this tour for all users. 387 * 388 * @return moodle_url 389 */ 390 public function get_reset_link() { 391 return helper::get_reset_tour_for_all_link($this->id); 392 } 393 394 /** 395 * The link to export this tour. 396 * 397 * @return moodle_url 398 */ 399 public function get_export_link() { 400 return helper::get_export_tour_link($this->id); 401 } 402 403 /** 404 * The link to duplicate this tour. 405 * 406 * @return moodle_url 407 */ 408 public function get_duplicate_link() { 409 return helper::get_duplicate_tour_link($this->id); 410 } 411 412 /** 413 * The link to remove this tour. 414 * 415 * @return moodle_url 416 */ 417 public function get_delete_link() { 418 return helper::get_delete_tour_link($this->id); 419 } 420 421 /** 422 * Prepare this tour for saving to the database. 423 * 424 * @return object 425 */ 426 public function to_record() { 427 return (object) array( 428 'id' => $this->id, 429 'name' => $this->name, 430 'description' => $this->description, 431 'pathmatch' => $this->pathmatch, 432 'enabled' => $this->enabled, 433 'sortorder' => $this->sortorder, 434 'endtourlabel' => $this->endtourlabel, 435 'configdata' => json_encode($this->config), 436 'displaystepnumbers' => $this->displaystepnumbers, 437 ); 438 } 439 440 /** 441 * Get the current sortorder for this tour. 442 * 443 * @return int 444 */ 445 public function get_sortorder() { 446 return (int) $this->sortorder; 447 } 448 449 /** 450 * Whether this tour is the first tour. 451 * 452 * @return boolean 453 */ 454 public function is_first_tour() { 455 return ($this->get_sortorder() === 0); 456 } 457 458 /** 459 * Whether this tour is the last tour. 460 * 461 * @param int $tourcount The pre-fetched count of tours 462 * @return boolean 463 */ 464 public function is_last_tour($tourcount = null) { 465 if ($tourcount === null) { 466 $tourcount = helper::count_tours(); 467 } 468 return ($this->get_sortorder() === ($tourcount - 1)); 469 } 470 471 /** 472 * Set the sortorder for this tour. 473 * 474 * @param int $value The new sortorder to use. 475 * @return $this 476 */ 477 public function set_sortorder($value) { 478 $this->sortorder = $value; 479 $this->dirty = true; 480 481 return $this; 482 } 483 484 /** 485 * Calculate the next sort-order value. 486 * 487 * @return int 488 */ 489 protected function calculate_sortorder() { 490 $this->sortorder = helper::count_tours(); 491 492 return $this; 493 } 494 495 /** 496 * Get the link to move this tour up in the sortorder. 497 * 498 * @return moodle_url 499 */ 500 public function get_moveup_link() { 501 return helper::get_move_tour_link($this->get_id(), helper::MOVE_UP); 502 } 503 504 /** 505 * Get the link to move this tour down in the sortorder. 506 * 507 * @return moodle_url 508 */ 509 public function get_movedown_link() { 510 return helper::get_move_tour_link($this->get_id(), helper::MOVE_DOWN); 511 } 512 513 /** 514 * Get the value of the specified configuration item. 515 * 516 * @param string $key The configuration key to set. 517 * @param mixed $default The default value to use if a value was not found. 518 * @return mixed 519 */ 520 public function get_config($key = null, $default = null) { 521 if ($this->config === null) { 522 $this->config = (object) array(); 523 } 524 if ($key === null) { 525 return $this->config; 526 } 527 528 if (property_exists($this->config, $key)) { 529 return $this->config->$key; 530 } 531 532 if ($default !== null) { 533 return $default; 534 } 535 536 return configuration::get_default_value($key); 537 } 538 539 /** 540 * Set the configuration item as specified. 541 * 542 * @param string $key The configuration key to set. 543 * @param mixed $value The new value for the configuration item. 544 * @return $this 545 */ 546 public function set_config($key, $value) { 547 if ($this->config === null) { 548 $this->config = (object) array(); 549 } 550 $this->config->$key = $value; 551 $this->dirty = true; 552 553 return $this; 554 } 555 556 /** 557 * Save the tour and it's configuration to the database. 558 * 559 * @param boolean $force Whether to force writing to the database. 560 * @return $this 561 */ 562 public function persist($force = false) { 563 global $DB; 564 565 if (!$this->dirty && !$force) { 566 return $this; 567 } 568 569 if ($this->id) { 570 $record = $this->to_record(); 571 $DB->update_record('tool_usertours_tours', $record); 572 } else { 573 $this->calculate_sortorder(); 574 $record = $this->to_record(); 575 unset($record->id); 576 $this->id = $DB->insert_record('tool_usertours_tours', $record); 577 } 578 579 $this->reload(); 580 581 // Notify the cache that a tour has changed. 582 cache::notify_tour_change(); 583 584 return $this; 585 } 586 587 /** 588 * Remove this step. 589 */ 590 public function remove() { 591 global $DB; 592 593 if ($this->id === null) { 594 // Nothing to delete - this tour has not been persisted. 595 return null; 596 } 597 598 // Delete all steps associated with this tour. 599 // Note, although they are currently just DB records, there may be other components in the future. 600 foreach ($this->get_steps() as $step) { 601 $step->remove(); 602 } 603 604 // Remove the configuration for the tour. 605 $DB->delete_records('tool_usertours_tours', array('id' => $this->id)); 606 helper::reset_tour_sortorder(); 607 608 $this->remove_user_preferences(); 609 610 return null; 611 } 612 613 /** 614 * Reset the sortorder for all steps in the tour. 615 * 616 * @return $this 617 */ 618 public function reset_step_sortorder() { 619 global $DB; 620 $steps = $DB->get_records('tool_usertours_steps', array('tourid' => $this->id), 'sortorder ASC', 'id'); 621 622 $index = 0; 623 foreach ($steps as $step) { 624 $DB->set_field('tool_usertours_steps', 'sortorder', $index, array('id' => $step->id)); 625 $index++; 626 } 627 628 // Notify of a change to the step configuration. 629 // Note: Do not notify of a tour change here. This is only a step change for a tour. 630 cache::notify_step_change($this->get_id()); 631 632 return $this; 633 } 634 635 /** 636 * Remove stored user preferences for the tour 637 */ 638 protected function remove_user_preferences(): void { 639 global $DB; 640 641 $DB->delete_records('user_preferences', ['name' => self::TOUR_LAST_COMPLETED_BY_USER . $this->get_id()]); 642 $DB->delete_records('user_preferences', ['name' => self::TOUR_REQUESTED_BY_USER . $this->get_id()]); 643 } 644 645 /** 646 * Whether this tour should be displayed to the user. 647 * 648 * @return boolean 649 */ 650 public function should_show_for_user() { 651 if (!$this->is_enabled()) { 652 // The tour is disabled - it should not be shown. 653 return false; 654 } 655 656 if ($tourcompletiondate = get_user_preferences(self::TOUR_LAST_COMPLETED_BY_USER . $this->get_id(), null)) { 657 if ($tourresetdate = get_user_preferences(self::TOUR_REQUESTED_BY_USER . $this->get_id(), null)) { 658 if ($tourresetdate >= $tourcompletiondate) { 659 return true; 660 } 661 } 662 $lastmajorupdate = $this->get_config('majorupdatetime', time()); 663 if ($tourcompletiondate > $lastmajorupdate) { 664 // The user has completed the tour since the last major update. 665 return false; 666 } 667 } 668 669 return true; 670 } 671 672 /** 673 * Get the key for this tour. 674 * This is used in the session cookie to determine whether the user has seen this tour before. 675 */ 676 public function get_tour_key() { 677 global $USER; 678 679 $tourtime = $this->get_config('majorupdatetime', null); 680 681 if ($tourtime === null) { 682 // This tour has no majorupdate time. 683 // Set one now to prevent repeated displays to the user. 684 $this->set_config('majorupdatetime', time()); 685 $this->persist(); 686 $tourtime = $this->get_config('majorupdatetime', null); 687 } 688 689 if ($userresetdate = get_user_preferences(self::TOUR_REQUESTED_BY_USER . $this->get_id(), null)) { 690 $tourtime = max($tourtime, $userresetdate); 691 } 692 693 return sprintf('tool_usertours_%d_%d_%s', $USER->id, $this->get_id(), $tourtime); 694 } 695 696 /** 697 * Reset the requested by user date. 698 * 699 * @return $this 700 */ 701 public function request_user_reset() { 702 set_user_preference(self::TOUR_REQUESTED_BY_USER . $this->get_id(), time()); 703 704 return $this; 705 } 706 707 /** 708 * Mark this tour as completed for this user. 709 * 710 * @return $this 711 */ 712 public function mark_user_completed() { 713 set_user_preference(self::TOUR_LAST_COMPLETED_BY_USER . $this->get_id(), time()); 714 715 return $this; 716 } 717 718 /** 719 * Update a tour giving it a new major update time. 720 * This will ensure that it is displayed to all users, even those who have already seen it. 721 * 722 * @return $this 723 */ 724 public function mark_major_change() { 725 // Clear old reset and completion notes. 726 $this->remove_user_preferences(); 727 728 $this->set_config('majorupdatetime', time()); 729 $this->persist(); 730 731 return $this; 732 } 733 734 /** 735 * Add the step configuration to the form. 736 * 737 * @param MoodleQuickForm $mform The form to add configuration to. 738 * @return $this 739 */ 740 public function add_config_to_form(\MoodleQuickForm &$mform) { 741 $options = configuration::get_placement_options(); 742 $mform->addElement('select', 'placement', get_string('placement', 'tool_usertours'), $options); 743 $mform->addHelpButton('placement', 'placement', 'tool_usertours'); 744 745 $this->add_config_field_to_form($mform, 'orphan'); 746 $this->add_config_field_to_form($mform, 'backdrop'); 747 $this->add_config_field_to_form($mform, 'reflex'); 748 749 return $this; 750 } 751 752 /** 753 * Add the specified step field configuration to the form. 754 * 755 * @param MoodleQuickForm $mform The form to add configuration to. 756 * @param string $key The key to add. 757 * @return $this 758 */ 759 protected function add_config_field_to_form(\MoodleQuickForm &$mform, $key) { 760 $options = [ 761 true => get_string('yes'), 762 false => get_string('no'), 763 ]; 764 $mform->addElement('select', $key, get_string($key, 'tool_usertours'), $options); 765 $mform->setDefault($key, configuration::get_default_value($key)); 766 $mform->addHelpButton($key, $key, 'tool_usertours'); 767 768 return $this; 769 } 770 771 /** 772 * Prepare the configuration data for the moodle form. 773 * 774 * @return object 775 */ 776 public function prepare_data_for_form() { 777 $data = $this->to_record(); 778 foreach (configuration::get_defaultable_keys() as $key) { 779 $data->$key = $this->get_config($key, configuration::get_default_value($key)); 780 } 781 782 return $data; 783 } 784 785 /** 786 * Get the configured filter values. 787 * 788 * @param string $filter The filter to retrieve values for. 789 * @return array 790 */ 791 public function get_filter_values($filter) { 792 if ($allvalues = (array) $this->get_config('filtervalues')) { 793 if (isset($allvalues[$filter])) { 794 return $allvalues[$filter]; 795 } 796 } 797 798 return []; 799 } 800 801 /** 802 * Set the values for the specified filter. 803 * 804 * @param string $filter The filter to set. 805 * @param array $values The values to set. 806 * @return $this 807 */ 808 public function set_filter_values($filter, array $values = []) { 809 $allvalues = (array) $this->get_config('filtervalues', []); 810 $allvalues[$filter] = $values; 811 812 return $this->set_config('filtervalues', $allvalues); 813 } 814 815 /** 816 * Check whether this tour matches all filters. 817 * 818 * @param \context $context The context to check. 819 * @param array|null $filters Optional array of filters. 820 * @return bool 821 */ 822 public function matches_all_filters(\context $context, array $filters = null): bool { 823 if (!$filters) { 824 $filters = helper::get_all_filters(); 825 } 826 827 // All filters must match. 828 // If any one filter fails to match, we return false. 829 foreach ($filters as $filterclass) { 830 if (!$filterclass::filter_matches($this, $context)) { 831 return false; 832 } 833 } 834 835 return true; 836 } 837 838 /** 839 * Gets all filter values for use in client side filters. 840 * 841 * @param array $filters Array of clientside filters. 842 * @return array 843 */ 844 public function get_client_filter_values(array $filters): array { 845 $results = []; 846 847 foreach ($filters as $filter) { 848 $results[$filter::get_filter_name()] = $filter::get_client_side_values($this); 849 } 850 851 return $results; 852 } 853 854 /** 855 * Set the value for the display step numbers setting. 856 * 857 * @param bool $value True for enable. 858 * @return $this 859 */ 860 public function set_display_step_numbers(bool $value): tour { 861 $this->displaystepnumbers = $value; 862 $this->dirty = true; 863 864 return $this; 865 } 866 867 /** 868 * Get the value of the display step numbers setting. 869 * 870 * @return bool 871 */ 872 public function get_display_step_numbers(): bool { 873 return $this->displaystepnumbers; 874 } 875 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body