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