4a9f5cf48ea71e6b837466372365f9a9a5f8b7f5
[moodle.git] / admin / tool / usertours / classes / manager.php
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/>.
17 /**
18  * Tour manager.
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  */
25 namespace tool_usertours;
27 defined('MOODLE_INTERNAL') || die();
29 use tool_usertours\local\forms;
30 use tool_usertours\local\table;
31 use core\notification;
33 /**
34  * Tour manager.
35  *
36  * @copyright  2016 Andrew Nicols <andrew@nicols.co.uk>
37  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
38  */
39 class manager {
41     /**
42      * @var ACTION_LISTTOURS      The action to get the list of tours.
43      */
44     const ACTION_LISTTOURS = 'listtours';
46     /**
47      * @var ACTION_NEWTOUR        The action to create a new tour.
48      */
49     const ACTION_NEWTOUR = 'newtour';
51     /**
52      * @var ACTION_EDITTOUR       The action to edit the tour.
53      */
54     const ACTION_EDITTOUR = 'edittour';
56     /**
57      * @var ACTION_MOVETOUR The action to move a tour up or down.
58      */
59     const ACTION_MOVETOUR = 'movetour';
61     /**
62      * @var ACTION_EXPORTTOUR     The action to export the tour.
63      */
64     const ACTION_EXPORTTOUR = 'exporttour';
66     /**
67      * @var ACTION_IMPORTTOUR     The action to import the tour.
68      */
69     const ACTION_IMPORTTOUR = 'importtour';
71     /**
72      * @var ACTION_DELETETOUR     The action to delete the tour.
73      */
74     const ACTION_DELETETOUR = 'deletetour';
76     /**
77      * @var ACTION_VIEWTOUR       The action to view the tour.
78      */
79     const ACTION_VIEWTOUR = 'viewtour';
81     /**
82      * @var ACTION_DUPLICATETOUR     The action to duplicate the tour.
83      */
84     const ACTION_DUPLICATETOUR = 'duplicatetour';
86     /**
87      * @var ACTION_NEWSTEP The action to create a new step.
88      */
89     const ACTION_NEWSTEP = 'newstep';
91     /**
92      * @var ACTION_EDITSTEP The action to edit step configuration.
93      */
94     const ACTION_EDITSTEP = 'editstep';
96     /**
97      * @var ACTION_MOVESTEP The action to move a step up or down.
98      */
99     const ACTION_MOVESTEP = 'movestep';
101     /**
102      * @var ACTION_DELETESTEP The action to delete a step.
103      */
104     const ACTION_DELETESTEP = 'deletestep';
106     /**
107      * @var ACTION_VIEWSTEP The action to view a step.
108      */
109     const ACTION_VIEWSTEP = 'viewstep';
111     /**
112      * @var ACTION_HIDETOUR The action to hide a tour.
113      */
114     const ACTION_HIDETOUR = 'hidetour';
116     /**
117      * @var ACTION_SHOWTOUR The action to show a tour.
118      */
119     const ACTION_SHOWTOUR = 'showtour';
121     /**
122      * @var ACTION_RESETFORALL
123      */
124     const ACTION_RESETFORALL = 'resetforall';
126     /**
127      * @var CONFIG_SHIPPED_TOUR
128      */
129     const CONFIG_SHIPPED_TOUR = 'shipped_tour';
131     /**
132      * @var CONFIG_SHIPPED_FILENAME
133      */
134     const CONFIG_SHIPPED_FILENAME = 'shipped_filename';
136     /**
137      * @var CONFIG_SHIPPED_VERSION
138      */
139     const CONFIG_SHIPPED_VERSION = 'shipped_version';
141     /**
142      * This is the entry point for this controller class.
143      *
144      * @param   string  $action     The action to perform.
145      */
146     public function execute($action) {
147         admin_externalpage_setup('tool_usertours/tours');
148         // Add the main content.
149         switch($action) {
150             case self::ACTION_NEWTOUR:
151             case self::ACTION_EDITTOUR:
152                 $this->edit_tour(optional_param('id', null, PARAM_INT));
153                 break;
155             case self::ACTION_MOVETOUR:
156                 $this->move_tour(required_param('id', PARAM_INT));
157                 break;
159             case self::ACTION_EXPORTTOUR:
160                 $this->export_tour(required_param('id', PARAM_INT));
161                 break;
163             case self::ACTION_IMPORTTOUR:
164                 $this->import_tour();
165                 break;
167             case self::ACTION_VIEWTOUR:
168                 $this->view_tour(required_param('id', PARAM_INT));
169                 break;
171             case self::ACTION_DUPLICATETOUR:
172                 $this->duplicate_tour(required_param('id', PARAM_INT));
173                 break;
175             case self::ACTION_HIDETOUR:
176                 $this->hide_tour(required_param('id', PARAM_INT));
177                 break;
179             case self::ACTION_SHOWTOUR:
180                 $this->show_tour(required_param('id', PARAM_INT));
181                 break;
183             case self::ACTION_DELETETOUR:
184                 $this->delete_tour(required_param('id', PARAM_INT));
185                 break;
187             case self::ACTION_RESETFORALL:
188                 $this->reset_tour_for_all(required_param('id', PARAM_INT));
189                 break;
191             case self::ACTION_NEWSTEP:
192             case self::ACTION_EDITSTEP:
193                 $this->edit_step(optional_param('id', null, PARAM_INT));
194                 break;
196             case self::ACTION_MOVESTEP:
197                 $this->move_step(required_param('id', PARAM_INT));
198                 break;
200             case self::ACTION_DELETESTEP:
201                 $this->delete_step(required_param('id', PARAM_INT));
202                 break;
204             case self::ACTION_LISTTOURS:
205             default:
206                 $this->print_tour_list();
207                 break;
208         }
209     }
211     /**
212      * Print out the page header.
213      *
214      * @param   string  $title     The title to display.
215      */
216     protected function header($title = null) {
217         global $OUTPUT;
219         // Print the page heading.
220         echo $OUTPUT->header();
222         if ($title === null) {
223             $title = get_string('tours', 'tool_usertours');
224         }
226         echo $OUTPUT->heading($title);
227     }
229     /**
230      * Print out the page footer.
231      *
232      * @return void
233      */
234     protected function footer() {
235         global $OUTPUT;
237         echo $OUTPUT->footer();
238     }
240     /**
241      * Print the the list of tours.
242      */
243     protected function print_tour_list() {
244         global $PAGE, $OUTPUT;
246         $this->header();
247         echo \html_writer::span(get_string('tourlist_explanation', 'tool_usertours'));
248         $table = new table\tour_list();
249         $tours = helper::get_tours();
250         foreach ($tours as $tour) {
251             $table->add_data_keyed($table->format_row($tour));
252         }
254         $table->finish_output();
255         $actions = [
256             (object) [
257                 'link'  => helper::get_edit_tour_link(),
258                 'linkproperties' => [],
259                 'img'   => 'b/tour-new',
260                 'title' => get_string('newtour', 'tool_usertours'),
261             ],
262             (object) [
263                 'link'  => helper::get_import_tour_link(),
264                 'linkproperties' => [],
265                 'img'   => 'b/tour-import',
266                 'title' => get_string('importtour', 'tool_usertours'),
267             ],
268             (object) [
269                 'link'  => new \moodle_url('https://archive.moodle.net/tours'),
270                 'linkproperties' => [
271                         'target' => '_blank',
272                     ],
273                 'img'   => 'b/tour-shared',
274                 'title' => get_string('sharedtourslink', 'tool_usertours'),
275             ],
276         ];
278         echo \html_writer::start_tag('div', [
279                 'class' => 'tour-actions',
280             ]);
282         echo \html_writer::start_tag('ul');
283         foreach ($actions as $config) {
284             $action = \html_writer::start_tag('li');
285             $linkproperties = $config->linkproperties;
286             $linkproperties['href'] = $config->link;
287             $action .= \html_writer::start_tag('a', $linkproperties);
288             $action .= $OUTPUT->pix_icon($config->img, $config->title, 'tool_usertours');
289             $action .= \html_writer::div($config->title);
290             $action .= \html_writer::end_tag('a');
291             $action .= \html_writer::end_tag('li');
292             echo $action;
293         }
294         echo \html_writer::end_tag('ul');
295         echo \html_writer::end_tag('div');
297         // JS for Tour management.
298         $PAGE->requires->js_call_amd('tool_usertours/managetours', 'setup');
299         $this->footer();
300     }
302     /**
303      * Return the edit tour link.
304      *
305      * @param   int         $id     The ID of the tour
306      * @return string
307      */
308     protected function get_edit_tour_link($id = null) {
309         $addlink = helper::get_edit_tour_link($id);
310         return \html_writer::link($addlink, get_string('newtour', 'tool_usertours'));
311     }
313     /**
314      * Print the edit tour link.
315      *
316      * @param   int         $id     The ID of the tour
317      */
318     protected function print_edit_tour_link($id = null) {
319         echo $this->get_edit_tour_link($id);
320     }
322     /**
323      * Get the import tour link.
324      *
325      * @return string
326      */
327     protected function get_import_tour_link() {
328         $importlink = helper::get_import_tour_link();
329         return \html_writer::link($importlink, get_string('importtour', 'tool_usertours'));
330     }
332     /**
333      * Print the edit tour page.
334      *
335      * @param   int         $id     The ID of the tour
336      */
337     protected function edit_tour($id = null) {
338         global $PAGE;
339         if ($id) {
340             $tour = tour::instance($id);
341             $PAGE->navbar->add($tour->get_name(), $tour->get_edit_link());
343         } else {
344             $tour = new tour();
345             $PAGE->navbar->add(get_string('newtour', 'tool_usertours'), $tour->get_edit_link());
346         }
348         $form = new forms\edittour($tour);
350         if ($form->is_cancelled()) {
351             redirect(helper::get_list_tour_link());
352         } else if ($data = $form->get_data()) {
353             // Creating a new tour.
354             $tour->set_name($data->name);
355             $tour->set_description($data->description);
356             $tour->set_pathmatch($data->pathmatch);
357             $tour->set_enabled(!empty($data->enabled));
359             foreach (configuration::get_defaultable_keys() as $key) {
360                 $tour->set_config($key, $data->$key);
361             }
363             // Save filter values.
364             foreach (helper::get_all_filters() as $filterclass) {
365                 $filterclass::save_filter_values_from_form($tour, $data);
366             }
368             $tour->persist();
370             redirect(helper::get_list_tour_link());
371         } else {
372             if (empty($tour)) {
373                 $this->header('newtour');
374             } else {
375                 if (!empty($tour->get_config(self::CONFIG_SHIPPED_TOUR))) {
376                     notification::add(get_string('modifyshippedtourwarning', 'tool_usertours'), notification::WARNING);
377                 }
379                 $this->header($tour->get_name());
380                 $data = $tour->prepare_data_for_form();
382                 // Prepare filter values for the form.
383                 foreach (helper::get_all_filters() as $filterclass) {
384                     $filterclass::prepare_filter_values_for_form($tour, $data);
385                 }
386                 $form->set_data($data);
387             }
389             $form->display();
390             $this->footer();
391         }
392     }
394     /**
395      * Print the export tour page.
396      *
397      * @param   int         $id     The ID of the tour
398      */
399     protected function export_tour($id) {
400         $tour = tour::instance($id);
402         // Grab the full data record.
403         $export = $tour->to_record();
405         // Remove the id.
406         unset($export->id);
408         // Set the version.
409         $export->version = get_config('tool_usertours', 'version');
411         // Step export.
412         $export->steps = [];
413         foreach ($tour->get_steps() as $step) {
414             $record = $step->to_record();
415             unset($record->id);
416             unset($record->tourid);
418             $export->steps[] = $record;
419         }
421         $exportstring = json_encode($export);
423         $filename = 'tour_export_' . $tour->get_id() . '_' . time() . '.json';
425         // Force download.
426         header('Last-Modified: ' . gmdate('D, d M Y H:i:s', time()) . ' GMT');
427         header('Cache-Control: private, must-revalidate, pre-check=0, post-check=0, max-age=0');
428         header('Expires: ' . gmdate('D, d M Y H:i:s', 0) . 'GMT');
429         header('Pragma: no-cache');
430         header('Accept-Ranges: none');
431         header('Content-disposition: attachment; filename=' . $filename);
432         header('Content-length: ' . strlen($exportstring));
433         header('Content-type: text/calendar; charset=utf-8');
435         echo $exportstring;
436         die;
437     }
439     /**
440      * Handle tour import.
441      */
442     protected function import_tour() {
443         global $PAGE;
444         $PAGE->navbar->add(get_string('importtour', 'tool_usertours'), helper::get_import_tour_link());
446         $form = new forms\importtour();
448         if ($form->is_cancelled()) {
449             redirect(helper::get_list_tour_link());
450         } else if ($form->get_data()) {
451             // Importing a tour.
452             $tourconfigraw = $form->get_file_content('tourconfig');
453             $tour = self::import_tour_from_json($tourconfigraw);
455             redirect($tour->get_view_link());
456         } else {
457             $this->header();
458             $form->display();
459             $this->footer();
460         }
461     }
463     /**
464      * Print the view tour page.
465      *
466      * @param   int         $tourid     The ID of the tour to display.
467      */
468     protected function view_tour($tourid) {
469         global $PAGE;
470         $tour = helper::get_tour($tourid);
472         $PAGE->navbar->add($tour->get_name(), $tour->get_view_link());
474         $this->header($tour->get_name());
475         echo \html_writer::span(get_string('viewtour_info', 'tool_usertours', [
476                 'tourname'  => $tour->get_name(),
477                 'path'      => $tour->get_pathmatch(),
478             ]));
479         echo \html_writer::div(get_string('viewtour_edit', 'tool_usertours', [
480                 'editlink'  => $tour->get_edit_link()->out(),
481                 'resetlink' => $tour->get_reset_link()->out(),
482             ]));
484         $table = new table\step_list($tourid);
485         foreach ($tour->get_steps() as $step) {
486             $table->add_data_keyed($table->format_row($step));
487         }
489         $table->finish_output();
490         $this->print_edit_step_link($tourid);
492         // JS for Step management.
493         $PAGE->requires->js_call_amd('tool_usertours/managesteps', 'setup');
495         $this->footer();
496     }
498     /**
499      * Duplicate an existing tour.
500      *
501      * @param   int         $tourid     The ID of the tour to duplicate.
502      */
503     protected function duplicate_tour($tourid) {
504         $tour = helper::get_tour($tourid);
506         $export = $tour->to_record();
507         // Remove the id.
508         unset($export->id);
510         // Set the version.
511         $export->version = get_config('tool_usertours', 'version');
513         $export->name = get_string('duplicatetour_name', 'tool_usertours', $export->name);
515         // Step export.
516         $export->steps = [];
517         foreach ($tour->get_steps() as $step) {
518             $record = $step->to_record();
519             unset($record->id);
520             unset($record->tourid);
522             $export->steps[] = $record;
523         }
525         $exportstring = json_encode($export);
526         $newtour = self::import_tour_from_json($exportstring);
528         redirect($newtour->get_view_link());
529     }
531     /**
532      * Show the tour.
533      *
534      * @param   int         $tourid     The ID of the tour to display.
535      */
536     protected function show_tour($tourid) {
537         $this->show_hide_tour($tourid, 1);
538     }
540     /**
541      * Hide the tour.
542      *
543      * @param   int         $tourid     The ID of the tour to display.
544      */
545     protected function hide_tour($tourid) {
546         $this->show_hide_tour($tourid, 0);
547     }
549     /**
550      * Show or Hide the tour.
551      *
552      * @param   int         $tourid     The ID of the tour to display.
553      * @param   int         $visibility The intended visibility.
554      */
555     protected function show_hide_tour($tourid, $visibility) {
556         global $DB;
558         require_sesskey();
560         $tour = $DB->get_record('tool_usertours_tours', array('id' => $tourid));
561         $tour->enabled = $visibility;
562         $DB->update_record('tool_usertours_tours', $tour);
564         redirect(helper::get_list_tour_link());
565     }
567     /**
568      * Delete the tour.
569      *
570      * @param   int         $tourid     The ID of the tour to remove.
571      */
572     protected function delete_tour($tourid) {
573         require_sesskey();
575         $tour = tour::instance($tourid);
576         $tour->remove();
578         redirect(helper::get_list_tour_link());
579     }
581     /**
582      * Reset the tour state for all users.
583      *
584      * @param   int         $tourid     The ID of the tour to remove.
585      */
586     protected function reset_tour_for_all($tourid) {
587         require_sesskey();
589         $tour = tour::instance($tourid);
590         $tour->mark_major_change();
592         redirect(helper::get_view_tour_link($tourid), get_string('tour_resetforall', 'tool_usertours'));
593     }
595     /**
596      * Get the first tour matching the current page URL.
597      *
598      * @param   bool        $reset      Forcibly update the current tour
599      * @return  tour
600      */
601     public static function get_current_tour($reset = false) {
602         global $PAGE;
604         static $tour = false;
606         if ($tour === false || $reset) {
607             $tour = self::get_matching_tours($PAGE->url);
608         }
610         return $tour;
611     }
613     /**
614      * Get the first tour matching the specified URL.
615      *
616      * @param   moodle_url  $pageurl        The URL to match.
617      * @return  tour
618      */
619     public static function get_matching_tours(\moodle_url $pageurl) {
620         global $PAGE;
622         $tours = cache::get_matching_tourdata($pageurl);
624         foreach ($tours as $record) {
625             $tour = tour::load_from_record($record);
626             if ($tour->is_enabled() && $tour->matches_all_filters($PAGE->context)) {
627                 return $tour;
628             }
629         }
631         return null;
632     }
634     /**
635      * Import the provided tour JSON.
636      *
637      * @param   string      $json           The tour configuration.
638      * @return  tour
639      */
640     public static function import_tour_from_json($json) {
641         $tourconfig = json_decode($json);
643         // We do not use this yet - we may do in the future.
644         unset($tourconfig->version);
646         $steps = $tourconfig->steps;
647         unset($tourconfig->steps);
649         $tourconfig->id = null;
650         $tourconfig->sortorder = null;
651         $tour = tour::load_from_record($tourconfig, true);
652         $tour->persist(true);
654         // Ensure that steps are orderered by their sortorder.
655         \core_collator::asort_objects_by_property($steps, 'sortorder', \core_collator::SORT_NUMERIC);
657         foreach ($steps as $stepconfig) {
658             $stepconfig->id = null;
659             $stepconfig->tourid = $tour->get_id();
660             $step = step::load_from_record($stepconfig, true);
661             $step->persist(true);
662         }
664         return $tour;
665     }
667     /**
668      * Helper to fetch the renderer.
669      *
670      * @return  renderer
671      */
672     protected function get_renderer() {
673         global $PAGE;
674         return $PAGE->get_renderer('tool_usertours');
675     }
677     /**
678      * Print the edit step link.
679      *
680      * @param   int     $tourid     The ID of the tour.
681      * @param   int     $stepid     The ID of the step.
682      * @return  string
683      */
684     protected function print_edit_step_link($tourid, $stepid = null) {
685         $addlink = helper::get_edit_step_link($tourid, $stepid);
686         $attributes = [];
687         if (empty($stepid)) {
688             $attributes['class'] = 'createstep';
689         }
690         echo \html_writer::link($addlink, get_string('newstep', 'tool_usertours'), $attributes);
691     }
693     /**
694      * Display the edit step form for the specified step.
695      *
696      * @param   int     $id     The step to edit.
697      */
698     protected function edit_step($id) {
699         global $PAGE;
701         if (isset($id)) {
702             $step = step::instance($id);
703         } else {
704             $step = new step();
705             $step->set_tourid(required_param('tourid', PARAM_INT));
706         }
708         $tour = $step->get_tour();
710         if (!empty($tour->get_config(self::CONFIG_SHIPPED_TOUR))) {
711             notification::add(get_string('modifyshippedtourwarning', 'tool_usertours'), notification::WARNING);
712         }
714         $PAGE->navbar->add($tour->get_name(), $tour->get_view_link());
715         if (isset($id)) {
716             $PAGE->navbar->add($step->get_title(), $step->get_edit_link());
717         } else {
718             $PAGE->navbar->add(get_string('newstep', 'tool_usertours'), $step->get_edit_link());
719         }
721         $form = new forms\editstep($step->get_edit_link(), $step);
722         if ($form->is_cancelled()) {
723             redirect($step->get_tour()->get_view_link());
724         } else if ($data = $form->get_data()) {
725             $step->handle_form_submission($form, $data);
726             $step->get_tour()->reset_step_sortorder();
727             redirect($step->get_tour()->get_view_link());
728         } else {
729             if (empty($id)) {
730                 $this->header(get_string('newstep', 'tool_usertours'));
731             } else {
732                 $this->header(get_string('editstep', 'tool_usertours', $step->get_title()));
733             }
734             $form->set_data($step->prepare_data_for_form());
736             $form->display();
737             $this->footer();
738         }
739     }
741     /**
742      * Move a tour up or down and redirect once complete.
743      *
744      * @param   int     $id     The tour to move.
745      */
746     protected function move_tour($id) {
747         require_sesskey();
749         $direction = required_param('direction', PARAM_INT);
751         $tour = tour::instance($id);
752         self::_move_tour($tour, $direction);
754         redirect(helper::get_list_tour_link());
755     }
757     /**
758      * Move a tour up or down.
759      *
760      * @param   tour    $tour   The tour to move.
761      *
762      * @param   int     $direction
763      */
764     protected static function _move_tour(tour $tour, $direction) {
765         // We can't move the first tour higher, nor the last tour any lower.
766         if (($tour->is_first_tour() && $direction == helper::MOVE_UP) ||
767                 ($tour->is_last_tour() && $direction == helper::MOVE_DOWN)) {
769             return;
770         }
772         $currentsortorder   = $tour->get_sortorder();
773         $targetsortorder    = $currentsortorder + $direction;
775         $swapwith = helper::get_tour_from_sortorder($targetsortorder);
777         // Set the sort order to something out of the way.
778         $tour->set_sortorder(-1);
779         $tour->persist();
781         // Swap the two sort orders.
782         $swapwith->set_sortorder($currentsortorder);
783         $swapwith->persist();
785         $tour->set_sortorder($targetsortorder);
786         $tour->persist();
787     }
789     /**
790      * Move a step up or down.
791      *
792      * @param   int     $id     The step to move.
793      */
794     protected function move_step($id) {
795         require_sesskey();
797         $direction = required_param('direction', PARAM_INT);
799         $step = step::instance($id);
800         $currentsortorder   = $step->get_sortorder();
801         $targetsortorder    = $currentsortorder + $direction;
803         $tour = $step->get_tour();
804         $swapwith = helper::get_step_from_sortorder($tour->get_id(), $targetsortorder);
806         // Set the sort order to something out of the way.
807         $step->set_sortorder(-1);
808         $step->persist();
810         // Swap the two sort orders.
811         $swapwith->set_sortorder($currentsortorder);
812         $swapwith->persist();
814         $step->set_sortorder($targetsortorder);
815         $step->persist();
817         // Reset the sort order.
818         $tour->reset_step_sortorder();
819         redirect($tour->get_view_link());
820     }
822     /**
823      * Delete the step.
824      *
825      * @param   int         $stepid     The ID of the step to remove.
826      */
827     protected function delete_step($stepid) {
828         require_sesskey();
830         $step = step::instance($stepid);
831         $tour = $step->get_tour();
833         $step->remove();
834         redirect($tour->get_view_link());
835     }
837     /**
838      * Make sure all of the default tours that are shipped with Moodle are created
839      * and up to date with the latest version.
840      */
841     public static function update_shipped_tours() {
842         global $DB, $CFG;
844         // A list of tours that are shipped with Moodle. They are in
845         // the format filename => version. The version value needs to
846         // be increased if the tour has been updated.
847         $shippedtours = [
848         ];
850         // These are tours that we used to ship but don't ship any longer.
851         // We do not remove them, but we do disable them.
852         $unshippedtours = [
853             // Formerly included in Moodle 3.2.0.
854             'boost_administrator.json' => 1,
855             'boost_course_view.json' => 1,
857             // Formerly included in Moodle 3.6.0.
858             '36_dashboard.json' => 3,
859             '36_messaging.json' => 3,
860         ];
862         $existingtourrecords = $DB->get_recordset('tool_usertours_tours');
864         // Get all of the existing shipped tours and check if they need to be
865         // updated.
866         foreach ($existingtourrecords as $tourrecord) {
867             $tour = tour::load_from_record($tourrecord);
869             if (!empty($tour->get_config(self::CONFIG_SHIPPED_TOUR))) {
870                 $filename = $tour->get_config(self::CONFIG_SHIPPED_FILENAME);
871                 $version = $tour->get_config(self::CONFIG_SHIPPED_VERSION);
873                 // If we know about this tour (otherwise leave it as is).
874                 if (isset($shippedtours[$filename])) {
875                     // And the version in the DB is an older version.
876                     if ($version < $shippedtours[$filename]) {
877                         // Remove the old version because it's been updated
878                         // and needs to be recreated.
879                         $tour->remove();
880                     } else {
881                         // The tour has not been updated so we don't need to
882                         // do anything with it.
883                         unset($shippedtours[$filename]);
884                     }
885                 }
887                 if (isset($unshippedtours[$filename])) {
888                     if ($version <= $unshippedtours[$filename]) {
889                         $tour = tour::instance($tour->get_id());
890                         $tour->set_enabled(tour::DISABLED);
891                         $tour->persist();
892                     }
893                 }
894             }
895         }
896         $existingtourrecords->close();
898         // Ensure we correct the sortorder in any existing tours, prior to adding latest shipped tours.
899         helper::reset_tour_sortorder();
901         foreach (array_reverse($shippedtours) as $filename => $version) {
902             $filepath = $CFG->dirroot . "/{$CFG->admin}/tool/usertours/tours/" . $filename;
903             $tourjson = file_get_contents($filepath);
904             $tour = self::import_tour_from_json($tourjson);
906             // Set some additional config data to record that this tour was
907             // added as a shipped tour.
908             $tour->set_config(self::CONFIG_SHIPPED_TOUR, true);
909             $tour->set_config(self::CONFIG_SHIPPED_FILENAME, $filename);
910             $tour->set_config(self::CONFIG_SHIPPED_VERSION, $version);
912             // Bump new tours to the top of the list.
913             while ($tour->get_sortorder() > 0) {
914                 self::_move_tour($tour, helper::MOVE_UP);
915             }
917             if (defined('BEHAT_SITE_RUNNING') || (defined('PHPUNIT_TEST') && PHPUNIT_TEST)) {
918                 // Disable this tour if this is behat or phpunit.
919                 $tour->set_enabled(false);
920             }
922             $tour->persist();
923         }
924     }