6741568b0b5f560946d71f81868d7d033878f70f
[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      * Helper method to initialize admin page, setting appropriate extra URL parameters
143      *
144      * @param string $action
145      */
146     protected function setup_admin_externalpage(string $action): void {
147         admin_externalpage_setup('tool_usertours/tours', '', array_filter([
148             'action' => $action,
149             'id' => optional_param('id', 0, PARAM_INT),
150             'tourid' => optional_param('tourid', 0, PARAM_INT),
151             'direction' => optional_param('direction', 0, PARAM_INT),
152         ]));
153     }
155     /**
156      * This is the entry point for this controller class.
157      *
158      * @param   string  $action     The action to perform.
159      */
160     public function execute($action) {
161         $this->setup_admin_externalpage($action);
163         // Add the main content.
164         switch($action) {
165             case self::ACTION_NEWTOUR:
166             case self::ACTION_EDITTOUR:
167                 $this->edit_tour(optional_param('id', null, PARAM_INT));
168                 break;
170             case self::ACTION_MOVETOUR:
171                 $this->move_tour(required_param('id', PARAM_INT));
172                 break;
174             case self::ACTION_EXPORTTOUR:
175                 $this->export_tour(required_param('id', PARAM_INT));
176                 break;
178             case self::ACTION_IMPORTTOUR:
179                 $this->import_tour();
180                 break;
182             case self::ACTION_VIEWTOUR:
183                 $this->view_tour(required_param('id', PARAM_INT));
184                 break;
186             case self::ACTION_DUPLICATETOUR:
187                 $this->duplicate_tour(required_param('id', PARAM_INT));
188                 break;
190             case self::ACTION_HIDETOUR:
191                 $this->hide_tour(required_param('id', PARAM_INT));
192                 break;
194             case self::ACTION_SHOWTOUR:
195                 $this->show_tour(required_param('id', PARAM_INT));
196                 break;
198             case self::ACTION_DELETETOUR:
199                 $this->delete_tour(required_param('id', PARAM_INT));
200                 break;
202             case self::ACTION_RESETFORALL:
203                 $this->reset_tour_for_all(required_param('id', PARAM_INT));
204                 break;
206             case self::ACTION_NEWSTEP:
207             case self::ACTION_EDITSTEP:
208                 $this->edit_step(optional_param('id', null, PARAM_INT));
209                 break;
211             case self::ACTION_MOVESTEP:
212                 $this->move_step(required_param('id', PARAM_INT));
213                 break;
215             case self::ACTION_DELETESTEP:
216                 $this->delete_step(required_param('id', PARAM_INT));
217                 break;
219             case self::ACTION_LISTTOURS:
220             default:
221                 $this->print_tour_list();
222                 break;
223         }
224     }
226     /**
227      * Print out the page header.
228      *
229      * @param   string  $title     The title to display.
230      */
231     protected function header($title = null) {
232         global $OUTPUT;
234         // Print the page heading.
235         echo $OUTPUT->header();
237         if ($title === null) {
238             $title = get_string('tours', 'tool_usertours');
239         }
241         echo $OUTPUT->heading($title);
242     }
244     /**
245      * Print out the page footer.
246      *
247      * @return void
248      */
249     protected function footer() {
250         global $OUTPUT;
252         echo $OUTPUT->footer();
253     }
255     /**
256      * Print the the list of tours.
257      */
258     protected function print_tour_list() {
259         global $PAGE, $OUTPUT;
261         $this->header();
262         echo \html_writer::span(get_string('tourlist_explanation', 'tool_usertours'));
263         $table = new table\tour_list();
264         $tours = helper::get_tours();
265         foreach ($tours as $tour) {
266             $table->add_data_keyed($table->format_row($tour));
267         }
269         $table->finish_output();
270         $actions = [
271             (object) [
272                 'link'  => helper::get_edit_tour_link(),
273                 'linkproperties' => [],
274                 'img'   => 'b/tour-new',
275                 'title' => get_string('newtour', 'tool_usertours'),
276             ],
277             (object) [
278                 'link'  => helper::get_import_tour_link(),
279                 'linkproperties' => [],
280                 'img'   => 'b/tour-import',
281                 'title' => get_string('importtour', 'tool_usertours'),
282             ],
283             (object) [
284                 'link'  => new \moodle_url('https://archive.moodle.net/tours'),
285                 'linkproperties' => [
286                         'target' => '_blank',
287                     ],
288                 'img'   => 'b/tour-shared',
289                 'title' => get_string('sharedtourslink', 'tool_usertours'),
290             ],
291         ];
293         echo \html_writer::start_tag('div', [
294                 'class' => 'tour-actions',
295             ]);
297         echo \html_writer::start_tag('ul');
298         foreach ($actions as $config) {
299             $action = \html_writer::start_tag('li');
300             $linkproperties = $config->linkproperties;
301             $linkproperties['href'] = $config->link;
302             $action .= \html_writer::start_tag('a', $linkproperties);
303             $action .= $OUTPUT->pix_icon($config->img, $config->title, 'tool_usertours');
304             $action .= \html_writer::div($config->title);
305             $action .= \html_writer::end_tag('a');
306             $action .= \html_writer::end_tag('li');
307             echo $action;
308         }
309         echo \html_writer::end_tag('ul');
310         echo \html_writer::end_tag('div');
312         // JS for Tour management.
313         $PAGE->requires->js_call_amd('tool_usertours/managetours', 'setup');
314         $this->footer();
315     }
317     /**
318      * Return the edit tour link.
319      *
320      * @param   int         $id     The ID of the tour
321      * @return string
322      */
323     protected function get_edit_tour_link($id = null) {
324         $addlink = helper::get_edit_tour_link($id);
325         return \html_writer::link($addlink, get_string('newtour', 'tool_usertours'));
326     }
328     /**
329      * Print the edit tour link.
330      *
331      * @param   int         $id     The ID of the tour
332      */
333     protected function print_edit_tour_link($id = null) {
334         echo $this->get_edit_tour_link($id);
335     }
337     /**
338      * Get the import tour link.
339      *
340      * @return string
341      */
342     protected function get_import_tour_link() {
343         $importlink = helper::get_import_tour_link();
344         return \html_writer::link($importlink, get_string('importtour', 'tool_usertours'));
345     }
347     /**
348      * Print the edit tour page.
349      *
350      * @param   int         $id     The ID of the tour
351      */
352     protected function edit_tour($id = null) {
353         global $PAGE;
354         if ($id) {
355             $tour = tour::instance($id);
356             $PAGE->navbar->add($tour->get_name(), $tour->get_edit_link());
358         } else {
359             $tour = new tour();
360             $PAGE->navbar->add(get_string('newtour', 'tool_usertours'), $tour->get_edit_link());
361         }
363         $form = new forms\edittour($tour);
365         if ($form->is_cancelled()) {
366             redirect(helper::get_list_tour_link());
367         } else if ($data = $form->get_data()) {
368             // Creating a new tour.
369             $tour->set_name($data->name);
370             $tour->set_description($data->description);
371             $tour->set_pathmatch($data->pathmatch);
372             $tour->set_enabled(!empty($data->enabled));
374             foreach (configuration::get_defaultable_keys() as $key) {
375                 $tour->set_config($key, $data->$key);
376             }
378             // Save filter values.
379             foreach (helper::get_all_filters() as $filterclass) {
380                 $filterclass::save_filter_values_from_form($tour, $data);
381             }
383             $tour->persist();
385             redirect(helper::get_list_tour_link());
386         } else {
387             if (empty($tour)) {
388                 $this->header('newtour');
389             } else {
390                 if (!empty($tour->get_config(self::CONFIG_SHIPPED_TOUR))) {
391                     notification::add(get_string('modifyshippedtourwarning', 'tool_usertours'), notification::WARNING);
392                 }
394                 $this->header($tour->get_name());
395                 $data = $tour->prepare_data_for_form();
397                 // Prepare filter values for the form.
398                 foreach (helper::get_all_filters() as $filterclass) {
399                     $filterclass::prepare_filter_values_for_form($tour, $data);
400                 }
401                 $form->set_data($data);
402             }
404             $form->display();
405             $this->footer();
406         }
407     }
409     /**
410      * Print the export tour page.
411      *
412      * @param   int         $id     The ID of the tour
413      */
414     protected function export_tour($id) {
415         $tour = tour::instance($id);
417         // Grab the full data record.
418         $export = $tour->to_record();
420         // Remove the id.
421         unset($export->id);
423         // Set the version.
424         $export->version = get_config('tool_usertours', 'version');
426         // Step export.
427         $export->steps = [];
428         foreach ($tour->get_steps() as $step) {
429             $record = $step->to_record();
430             unset($record->id);
431             unset($record->tourid);
433             $export->steps[] = $record;
434         }
436         $exportstring = json_encode($export);
438         $filename = 'tour_export_' . $tour->get_id() . '_' . time() . '.json';
440         // Force download.
441         header('Last-Modified: ' . gmdate('D, d M Y H:i:s', time()) . ' GMT');
442         header('Cache-Control: private, must-revalidate, pre-check=0, post-check=0, max-age=0');
443         header('Expires: ' . gmdate('D, d M Y H:i:s', 0) . 'GMT');
444         header('Pragma: no-cache');
445         header('Accept-Ranges: none');
446         header('Content-disposition: attachment; filename=' . $filename);
447         header('Content-length: ' . strlen($exportstring));
448         header('Content-type: text/calendar; charset=utf-8');
450         echo $exportstring;
451         die;
452     }
454     /**
455      * Handle tour import.
456      */
457     protected function import_tour() {
458         global $PAGE;
459         $PAGE->navbar->add(get_string('importtour', 'tool_usertours'), helper::get_import_tour_link());
461         $form = new forms\importtour();
463         if ($form->is_cancelled()) {
464             redirect(helper::get_list_tour_link());
465         } else if ($form->get_data()) {
466             // Importing a tour.
467             $tourconfigraw = $form->get_file_content('tourconfig');
468             $tour = self::import_tour_from_json($tourconfigraw);
470             redirect($tour->get_view_link());
471         } else {
472             $this->header();
473             $form->display();
474             $this->footer();
475         }
476     }
478     /**
479      * Print the view tour page.
480      *
481      * @param   int         $tourid     The ID of the tour to display.
482      */
483     protected function view_tour($tourid) {
484         global $PAGE;
485         $tour = helper::get_tour($tourid);
487         $PAGE->navbar->add($tour->get_name(), $tour->get_view_link());
489         $this->header($tour->get_name());
490         echo \html_writer::span(get_string('viewtour_info', 'tool_usertours', [
491                 'tourname'  => $tour->get_name(),
492                 'path'      => $tour->get_pathmatch(),
493             ]));
494         echo \html_writer::div(get_string('viewtour_edit', 'tool_usertours', [
495                 'editlink'  => $tour->get_edit_link()->out(),
496                 'resetlink' => $tour->get_reset_link()->out(),
497             ]));
499         $table = new table\step_list($tourid);
500         foreach ($tour->get_steps() as $step) {
501             $table->add_data_keyed($table->format_row($step));
502         }
504         $table->finish_output();
505         $this->print_edit_step_link($tourid);
507         // JS for Step management.
508         $PAGE->requires->js_call_amd('tool_usertours/managesteps', 'setup');
510         $this->footer();
511     }
513     /**
514      * Duplicate an existing tour.
515      *
516      * @param   int         $tourid     The ID of the tour to duplicate.
517      */
518     protected function duplicate_tour($tourid) {
519         $tour = helper::get_tour($tourid);
521         $export = $tour->to_record();
522         // Remove the id.
523         unset($export->id);
525         // Set the version.
526         $export->version = get_config('tool_usertours', 'version');
528         $export->name = get_string('duplicatetour_name', 'tool_usertours', $export->name);
530         // Step export.
531         $export->steps = [];
532         foreach ($tour->get_steps() as $step) {
533             $record = $step->to_record();
534             unset($record->id);
535             unset($record->tourid);
537             $export->steps[] = $record;
538         }
540         $exportstring = json_encode($export);
541         $newtour = self::import_tour_from_json($exportstring);
543         redirect($newtour->get_view_link());
544     }
546     /**
547      * Show the tour.
548      *
549      * @param   int         $tourid     The ID of the tour to display.
550      */
551     protected function show_tour($tourid) {
552         $this->show_hide_tour($tourid, 1);
553     }
555     /**
556      * Hide the tour.
557      *
558      * @param   int         $tourid     The ID of the tour to display.
559      */
560     protected function hide_tour($tourid) {
561         $this->show_hide_tour($tourid, 0);
562     }
564     /**
565      * Show or Hide the tour.
566      *
567      * @param   int         $tourid     The ID of the tour to display.
568      * @param   int         $visibility The intended visibility.
569      */
570     protected function show_hide_tour($tourid, $visibility) {
571         global $DB;
573         require_sesskey();
575         $tour = $DB->get_record('tool_usertours_tours', array('id' => $tourid));
576         $tour->enabled = $visibility;
577         $DB->update_record('tool_usertours_tours', $tour);
579         redirect(helper::get_list_tour_link());
580     }
582     /**
583      * Delete the tour.
584      *
585      * @param   int         $tourid     The ID of the tour to remove.
586      */
587     protected function delete_tour($tourid) {
588         require_sesskey();
590         $tour = tour::instance($tourid);
591         $tour->remove();
593         redirect(helper::get_list_tour_link());
594     }
596     /**
597      * Reset the tour state for all users.
598      *
599      * @param   int         $tourid     The ID of the tour to remove.
600      */
601     protected function reset_tour_for_all($tourid) {
602         require_sesskey();
604         $tour = tour::instance($tourid);
605         $tour->mark_major_change();
607         redirect(helper::get_view_tour_link($tourid), get_string('tour_resetforall', 'tool_usertours'));
608     }
610     /**
611      * Get the first tour matching the current page URL.
612      *
613      * @param   bool        $reset      Forcibly update the current tour
614      * @return  tour
615      */
616     public static function get_current_tour($reset = false) {
617         global $PAGE;
619         static $tour = false;
621         if ($tour === false || $reset) {
622             $tour = self::get_matching_tours($PAGE->url);
623         }
625         return $tour;
626     }
628     /**
629      * Get the first tour matching the specified URL.
630      *
631      * @param   moodle_url  $pageurl        The URL to match.
632      * @return  tour
633      */
634     public static function get_matching_tours(\moodle_url $pageurl) {
635         global $PAGE;
637         $tours = cache::get_matching_tourdata($pageurl);
639         foreach ($tours as $record) {
640             $tour = tour::load_from_record($record);
641             if ($tour->is_enabled() && $tour->matches_all_filters($PAGE->context)) {
642                 return $tour;
643             }
644         }
646         return null;
647     }
649     /**
650      * Import the provided tour JSON.
651      *
652      * @param   string      $json           The tour configuration.
653      * @return  tour
654      */
655     public static function import_tour_from_json($json) {
656         $tourconfig = json_decode($json);
658         // We do not use this yet - we may do in the future.
659         unset($tourconfig->version);
661         $steps = $tourconfig->steps;
662         unset($tourconfig->steps);
664         $tourconfig->id = null;
665         $tourconfig->sortorder = null;
666         $tour = tour::load_from_record($tourconfig, true);
667         $tour->persist(true);
669         // Ensure that steps are orderered by their sortorder.
670         \core_collator::asort_objects_by_property($steps, 'sortorder', \core_collator::SORT_NUMERIC);
672         foreach ($steps as $stepconfig) {
673             $stepconfig->id = null;
674             $stepconfig->tourid = $tour->get_id();
675             $step = step::load_from_record($stepconfig, true);
676             $step->persist(true);
677         }
679         return $tour;
680     }
682     /**
683      * Helper to fetch the renderer.
684      *
685      * @return  renderer
686      */
687     protected function get_renderer() {
688         global $PAGE;
689         return $PAGE->get_renderer('tool_usertours');
690     }
692     /**
693      * Print the edit step link.
694      *
695      * @param   int     $tourid     The ID of the tour.
696      * @param   int     $stepid     The ID of the step.
697      * @return  string
698      */
699     protected function print_edit_step_link($tourid, $stepid = null) {
700         $addlink = helper::get_edit_step_link($tourid, $stepid);
701         $attributes = [];
702         if (empty($stepid)) {
703             $attributes['class'] = 'createstep';
704         }
705         echo \html_writer::link($addlink, get_string('newstep', 'tool_usertours'), $attributes);
706     }
708     /**
709      * Display the edit step form for the specified step.
710      *
711      * @param   int     $id     The step to edit.
712      */
713     protected function edit_step($id) {
714         global $PAGE;
716         if (isset($id)) {
717             $step = step::instance($id);
718         } else {
719             $step = new step();
720             $step->set_tourid(required_param('tourid', PARAM_INT));
721         }
723         $tour = $step->get_tour();
725         if (!empty($tour->get_config(self::CONFIG_SHIPPED_TOUR))) {
726             notification::add(get_string('modifyshippedtourwarning', 'tool_usertours'), notification::WARNING);
727         }
729         $PAGE->navbar->add($tour->get_name(), $tour->get_view_link());
730         if (isset($id)) {
731             $PAGE->navbar->add($step->get_title(), $step->get_edit_link());
732         } else {
733             $PAGE->navbar->add(get_string('newstep', 'tool_usertours'), $step->get_edit_link());
734         }
736         $form = new forms\editstep($step->get_edit_link(), $step);
737         if ($form->is_cancelled()) {
738             redirect($step->get_tour()->get_view_link());
739         } else if ($data = $form->get_data()) {
740             $step->handle_form_submission($form, $data);
741             $step->get_tour()->reset_step_sortorder();
742             redirect($step->get_tour()->get_view_link());
743         } else {
744             if (empty($id)) {
745                 $this->header(get_string('newstep', 'tool_usertours'));
746             } else {
747                 $this->header(get_string('editstep', 'tool_usertours', $step->get_title()));
748             }
749             $form->set_data($step->prepare_data_for_form());
751             $form->display();
752             $this->footer();
753         }
754     }
756     /**
757      * Move a tour up or down and redirect once complete.
758      *
759      * @param   int     $id     The tour to move.
760      */
761     protected function move_tour($id) {
762         require_sesskey();
764         $direction = required_param('direction', PARAM_INT);
766         $tour = tour::instance($id);
767         self::_move_tour($tour, $direction);
769         redirect(helper::get_list_tour_link());
770     }
772     /**
773      * Move a tour up or down.
774      *
775      * @param   tour    $tour   The tour to move.
776      *
777      * @param   int     $direction
778      */
779     protected static function _move_tour(tour $tour, $direction) {
780         // We can't move the first tour higher, nor the last tour any lower.
781         if (($tour->is_first_tour() && $direction == helper::MOVE_UP) ||
782                 ($tour->is_last_tour() && $direction == helper::MOVE_DOWN)) {
784             return;
785         }
787         $currentsortorder   = $tour->get_sortorder();
788         $targetsortorder    = $currentsortorder + $direction;
790         $swapwith = helper::get_tour_from_sortorder($targetsortorder);
792         // Set the sort order to something out of the way.
793         $tour->set_sortorder(-1);
794         $tour->persist();
796         // Swap the two sort orders.
797         $swapwith->set_sortorder($currentsortorder);
798         $swapwith->persist();
800         $tour->set_sortorder($targetsortorder);
801         $tour->persist();
802     }
804     /**
805      * Move a step up or down.
806      *
807      * @param   int     $id     The step to move.
808      */
809     protected function move_step($id) {
810         require_sesskey();
812         $direction = required_param('direction', PARAM_INT);
814         $step = step::instance($id);
815         $currentsortorder   = $step->get_sortorder();
816         $targetsortorder    = $currentsortorder + $direction;
818         $tour = $step->get_tour();
819         $swapwith = helper::get_step_from_sortorder($tour->get_id(), $targetsortorder);
821         // Set the sort order to something out of the way.
822         $step->set_sortorder(-1);
823         $step->persist();
825         // Swap the two sort orders.
826         $swapwith->set_sortorder($currentsortorder);
827         $swapwith->persist();
829         $step->set_sortorder($targetsortorder);
830         $step->persist();
832         // Reset the sort order.
833         $tour->reset_step_sortorder();
834         redirect($tour->get_view_link());
835     }
837     /**
838      * Delete the step.
839      *
840      * @param   int         $stepid     The ID of the step to remove.
841      */
842     protected function delete_step($stepid) {
843         require_sesskey();
845         $step = step::instance($stepid);
846         $tour = $step->get_tour();
848         $step->remove();
849         redirect($tour->get_view_link());
850     }
852     /**
853      * Make sure all of the default tours that are shipped with Moodle are created
854      * and up to date with the latest version.
855      */
856     public static function update_shipped_tours() {
857         global $DB, $CFG;
859         // A list of tours that are shipped with Moodle. They are in
860         // the format filename => version. The version value needs to
861         // be increased if the tour has been updated.
862         $shippedtours = [
863         ];
865         // These are tours that we used to ship but don't ship any longer.
866         // We do not remove them, but we do disable them.
867         $unshippedtours = [
868             // Formerly included in Moodle 3.2.0.
869             'boost_administrator.json' => 1,
870             'boost_course_view.json' => 1,
872             // Formerly included in Moodle 3.6.0.
873             '36_dashboard.json' => 3,
874             '36_messaging.json' => 3,
875         ];
877         $existingtourrecords = $DB->get_recordset('tool_usertours_tours');
879         // Get all of the existing shipped tours and check if they need to be
880         // updated.
881         foreach ($existingtourrecords as $tourrecord) {
882             $tour = tour::load_from_record($tourrecord);
884             if (!empty($tour->get_config(self::CONFIG_SHIPPED_TOUR))) {
885                 $filename = $tour->get_config(self::CONFIG_SHIPPED_FILENAME);
886                 $version = $tour->get_config(self::CONFIG_SHIPPED_VERSION);
888                 // If we know about this tour (otherwise leave it as is).
889                 if (isset($shippedtours[$filename])) {
890                     // And the version in the DB is an older version.
891                     if ($version < $shippedtours[$filename]) {
892                         // Remove the old version because it's been updated
893                         // and needs to be recreated.
894                         $tour->remove();
895                     } else {
896                         // The tour has not been updated so we don't need to
897                         // do anything with it.
898                         unset($shippedtours[$filename]);
899                     }
900                 }
902                 if (isset($unshippedtours[$filename])) {
903                     if ($version <= $unshippedtours[$filename]) {
904                         $tour = tour::instance($tour->get_id());
905                         $tour->set_enabled(tour::DISABLED);
906                         $tour->persist();
907                     }
908                 }
909             }
910         }
911         $existingtourrecords->close();
913         // Ensure we correct the sortorder in any existing tours, prior to adding latest shipped tours.
914         helper::reset_tour_sortorder();
916         foreach (array_reverse($shippedtours) as $filename => $version) {
917             $filepath = $CFG->dirroot . "/{$CFG->admin}/tool/usertours/tours/" . $filename;
918             $tourjson = file_get_contents($filepath);
919             $tour = self::import_tour_from_json($tourjson);
921             // Set some additional config data to record that this tour was
922             // added as a shipped tour.
923             $tour->set_config(self::CONFIG_SHIPPED_TOUR, true);
924             $tour->set_config(self::CONFIG_SHIPPED_FILENAME, $filename);
925             $tour->set_config(self::CONFIG_SHIPPED_VERSION, $version);
927             // Bump new tours to the top of the list.
928             while ($tour->get_sortorder() > 0) {
929                 self::_move_tour($tour, helper::MOVE_UP);
930             }
932             if (defined('BEHAT_SITE_RUNNING') || (defined('PHPUNIT_TEST') && PHPUNIT_TEST)) {
933                 // Disable this tour if this is behat or phpunit.
934                 $tour->set_enabled(false);
935             }
937             $tour->persist();
938         }
939     }