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