64955f20de6501087e03afe6d9e798fbbce45bc2
[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_NEWSTEP The action to create a new step.
83      */
84     const ACTION_NEWSTEP = 'newstep';
86     /**
87      * @var ACTION_EDITSTEP The action to edit step configuration.
88      */
89     const ACTION_EDITSTEP = 'editstep';
91     /**
92      * @var ACTION_MOVESTEP The action to move a step up or down.
93      */
94     const ACTION_MOVESTEP = 'movestep';
96     /**
97      * @var ACTION_DELETESTEP The action to delete a step.
98      */
99     const ACTION_DELETESTEP = 'deletestep';
101     /**
102      * @var ACTION_VIEWSTEP The action to view a step.
103      */
104     const ACTION_VIEWSTEP = 'viewstep';
106     /**
107      * @var ACTION_HIDETOUR The action to hide a tour.
108      */
109     const ACTION_HIDETOUR = 'hidetour';
111     /**
112      * @var ACTION_SHOWTOUR The action to show a tour.
113      */
114     const ACTION_SHOWTOUR = 'showtour';
116     /**
117      * @var ACTION_RESETFORALL
118      */
119     const ACTION_RESETFORALL = 'resetforall';
121     /**
122      * @var CONFIG_SHIPPED_TOUR
123      */
124     const CONFIG_SHIPPED_TOUR = 'shipped_tour';
126     /**
127      * @var CONFIG_SHIPPED_FILENAME
128      */
129     const CONFIG_SHIPPED_FILENAME = 'shipped_filename';
131     /**
132      * @var CONFIG_SHIPPED_VERSION
133      */
134     const CONFIG_SHIPPED_VERSION = 'shipped_version';
136     /**
137      * This is the entry point for this controller class.
138      *
139      * @param   string  $action     The action to perform.
140      */
141     public function execute($action) {
142         admin_externalpage_setup('tool_usertours/tours');
143         // Add the main content.
144         switch($action) {
145             case self::ACTION_NEWTOUR:
146             case self::ACTION_EDITTOUR:
147                 $this->edit_tour(optional_param('id', null, PARAM_INT));
148                 break;
150             case self::ACTION_MOVETOUR:
151                 $this->move_tour(required_param('id', PARAM_INT));
152                 break;
154             case self::ACTION_EXPORTTOUR:
155                 $this->export_tour(required_param('id', PARAM_INT));
156                 break;
158             case self::ACTION_IMPORTTOUR:
159                 $this->import_tour();
160                 break;
162             case self::ACTION_VIEWTOUR:
163                 $this->view_tour(required_param('id', PARAM_INT));
164                 break;
166             case self::ACTION_HIDETOUR:
167                 $this->hide_tour(required_param('id', PARAM_INT));
168                 break;
170             case self::ACTION_SHOWTOUR:
171                 $this->show_tour(required_param('id', PARAM_INT));
172                 break;
174             case self::ACTION_DELETETOUR:
175                 $this->delete_tour(required_param('id', PARAM_INT));
176                 break;
178             case self::ACTION_RESETFORALL:
179                 $this->reset_tour_for_all(required_param('id', PARAM_INT));
180                 break;
182             case self::ACTION_NEWSTEP:
183             case self::ACTION_EDITSTEP:
184                 $this->edit_step(optional_param('id', null, PARAM_INT));
185                 break;
187             case self::ACTION_MOVESTEP:
188                 $this->move_step(required_param('id', PARAM_INT));
189                 break;
191             case self::ACTION_DELETESTEP:
192                 $this->delete_step(required_param('id', PARAM_INT));
193                 break;
195             case self::ACTION_LISTTOURS:
196             default:
197                 $this->print_tour_list();
198                 break;
199         }
200     }
202     /**
203      * Print out the page header.
204      *
205      * @param   string  $title     The title to display.
206      */
207     protected function header($title = null) {
208         global $OUTPUT;
210         // Print the page heading.
211         echo $OUTPUT->header();
213         if ($title === null) {
214             $title = get_string('tours', 'tool_usertours');
215         }
217         echo $OUTPUT->heading($title);
218     }
220     /**
221      * Print out the page footer.
222      *
223      * @return void
224      */
225     protected function footer() {
226         global $OUTPUT;
228         echo $OUTPUT->footer();
229     }
231     /**
232      * Print the the list of tours.
233      */
234     protected function print_tour_list() {
235         global $PAGE, $OUTPUT;
237         $this->header();
238         echo \html_writer::span(get_string('tourlist_explanation', 'tool_usertours'));
239         $table = new table\tour_list();
240         $tours = helper::get_tours();
241         foreach ($tours as $tour) {
242             $table->add_data_keyed($table->format_row($tour));
243         }
245         $table->finish_output();
246         $actions = [
247             (object) [
248                 'link'  => helper::get_edit_tour_link(),
249                 'linkproperties' => [],
250                 'img'   => 'b/tour-new',
251                 'title' => get_string('newtour', 'tool_usertours'),
252             ],
253             (object) [
254                 'link'  => helper::get_import_tour_link(),
255                 'linkproperties' => [],
256                 'img'   => 'b/tour-import',
257                 'title' => get_string('importtour', 'tool_usertours'),
258             ],
259             (object) [
260                 'link'  => new \moodle_url('https://moodle.net/tours'),
261                 'linkproperties' => [
262                         'target' => '_blank',
263                     ],
264                 'img'   => 'b/tour-shared',
265                 'title' => get_string('sharedtourslink', 'tool_usertours'),
266             ],
267         ];
269         echo \html_writer::start_tag('div', [
270                 'class' => 'tour-actions',
271             ]);
273         echo \html_writer::start_tag('ul');
274         foreach ($actions as $config) {
275             $action = \html_writer::start_tag('li');
276             $linkproperties = $config->linkproperties;
277             $linkproperties['href'] = $config->link;
278             $action .= \html_writer::start_tag('a', $linkproperties);
279             $action .= $OUTPUT->pix_icon($config->img, $config->title, 'tool_usertours');
280             $action .= \html_writer::div($config->title);
281             $action .= \html_writer::end_tag('a');
282             $action .= \html_writer::end_tag('li');
283             echo $action;
284         }
285         echo \html_writer::end_tag('ul');
286         echo \html_writer::end_tag('div');
288         // JS for Tour management.
289         $PAGE->requires->js_call_amd('tool_usertours/managetours', 'setup');
290         $this->footer();
291     }
293     /**
294      * Return the edit tour link.
295      *
296      * @param   int         $id     The ID of the tour
297      * @return string
298      */
299     protected function get_edit_tour_link($id = null) {
300         $addlink = helper::get_edit_tour_link($id);
301         return \html_writer::link($addlink, get_string('newtour', 'tool_usertours'));
302     }
304     /**
305      * Print the edit tour link.
306      *
307      * @param   int         $id     The ID of the tour
308      */
309     protected function print_edit_tour_link($id = null) {
310         echo $this->get_edit_tour_link($id);
311     }
313     /**
314      * Get the import tour link.
315      *
316      * @return string
317      */
318     protected function get_import_tour_link() {
319         $importlink = helper::get_import_tour_link();
320         return \html_writer::link($importlink, get_string('importtour', 'tool_usertours'));
321     }
323     /**
324      * Print the edit tour page.
325      *
326      * @param   int         $id     The ID of the tour
327      */
328     protected function edit_tour($id = null) {
329         global $PAGE;
330         if ($id) {
331             $tour = tour::instance($id);
332             $PAGE->navbar->add($tour->get_name(), $tour->get_edit_link());
334         } else {
335             $tour = new tour();
336             $PAGE->navbar->add(get_string('newtour', 'tool_usertours'), $tour->get_edit_link());
337         }
339         $form = new forms\edittour($tour);
341         if ($form->is_cancelled()) {
342             redirect(helper::get_list_tour_link());
343         } else if ($data = $form->get_data()) {
344             // Creating a new tour.
345             $tour->set_name($data->name);
346             $tour->set_description($data->description);
347             $tour->set_pathmatch($data->pathmatch);
348             $tour->set_enabled(!empty($data->enabled));
350             foreach (configuration::get_defaultable_keys() as $key) {
351                 $tour->set_config($key, $data->$key);
352             }
354             // Save filter values.
355             foreach (helper::get_all_filters() as $filterclass) {
356                 $filterclass::save_filter_values_from_form($tour, $data);
357             }
359             $tour->persist();
361             redirect(helper::get_list_tour_link());
362         } else {
363             if (empty($tour)) {
364                 $this->header('newtour');
365             } else {
366                 if (!empty($tour->get_config(self::CONFIG_SHIPPED_TOUR))) {
367                     notification::add(get_string('modifyshippedtourwarning', 'tool_usertours'), notification::WARNING);
368                 }
370                 $this->header($tour->get_name());
371                 $data = $tour->prepare_data_for_form();
373                 // Prepare filter values for the form.
374                 foreach (helper::get_all_filters() as $filterclass) {
375                     $filterclass::prepare_filter_values_for_form($tour, $data);
376                 }
377                 $form->set_data($data);
378             }
380             $form->display();
381             $this->footer();
382         }
383     }
385     /**
386      * Print the export tour page.
387      *
388      * @param   int         $id     The ID of the tour
389      */
390     protected function export_tour($id) {
391         $tour = tour::instance($id);
393         // Grab the full data record.
394         $export = $tour->to_record();
396         // Remove the id.
397         unset($export->id);
399         // Set the version.
400         $export->version = get_config('tool_usertours', 'version');
402         // Step export.
403         $export->steps = [];
404         foreach ($tour->get_steps() as $step) {
405             $record = $step->to_record();
406             unset($record->id);
407             unset($record->tourid);
409             $export->steps[] = $record;
410         }
412         $exportstring = json_encode($export);
414         $filename = 'tour_export_' . $tour->get_id() . '_' . time() . '.json';
416         // Force download.
417         header('Last-Modified: ' . gmdate('D, d M Y H:i:s', time()) . ' GMT');
418         header('Cache-Control: private, must-revalidate, pre-check=0, post-check=0, max-age=0');
419         header('Expires: ' . gmdate('D, d M Y H:i:s', 0) . 'GMT');
420         header('Pragma: no-cache');
421         header('Accept-Ranges: none');
422         header('Content-disposition: attachment; filename=' . $filename);
423         header('Content-length: ' . strlen($exportstring));
424         header('Content-type: text/calendar; charset=utf-8');
426         echo $exportstring;
427         die;
428     }
430     /**
431      * Handle tour import.
432      */
433     protected function import_tour() {
434         global $PAGE;
435         $PAGE->navbar->add(get_string('importtour', 'tool_usertours'), helper::get_import_tour_link());
437         $form = new forms\importtour();
439         if ($form->is_cancelled()) {
440             redirect(helper::get_list_tour_link());
441         } else if ($form->get_data()) {
442             // Importing a tour.
443             $tourconfigraw = $form->get_file_content('tourconfig');
444             $tour = self::import_tour_from_json($tourconfigraw);
446             redirect($tour->get_view_link());
447         } else {
448             $this->header();
449             $form->display();
450             $this->footer();
451         }
452     }
454     /**
455      * Print the view tour page.
456      *
457      * @param   int         $tourid     The ID of the tour to display.
458      */
459     protected function view_tour($tourid) {
460         global $PAGE;
461         $tour = helper::get_tour($tourid);
463         $PAGE->navbar->add($tour->get_name(), $tour->get_view_link());
465         $this->header($tour->get_name());
466         echo \html_writer::span(get_string('viewtour_info', 'tool_usertours', [
467                 'tourname'  => $tour->get_name(),
468                 'path'      => $tour->get_pathmatch(),
469             ]));
470         echo \html_writer::div(get_string('viewtour_edit', 'tool_usertours', [
471                 'editlink'  => $tour->get_edit_link()->out(),
472                 'resetlink' => $tour->get_reset_link()->out(),
473             ]));
475         $table = new table\step_list($tourid);
476         foreach ($tour->get_steps() as $step) {
477             $table->add_data_keyed($table->format_row($step));
478         }
480         $table->finish_output();
481         $this->print_edit_step_link($tourid);
483         // JS for Step management.
484         $PAGE->requires->js_call_amd('tool_usertours/managesteps', 'setup');
486         $this->footer();
487     }
489     /**
490      * Show the tour.
491      *
492      * @param   int         $tourid     The ID of the tour to display.
493      */
494     protected function show_tour($tourid) {
495         $this->show_hide_tour($tourid, 1);
496     }
498     /**
499      * Hide the tour.
500      *
501      * @param   int         $tourid     The ID of the tour to display.
502      */
503     protected function hide_tour($tourid) {
504         $this->show_hide_tour($tourid, 0);
505     }
507     /**
508      * Show or Hide the tour.
509      *
510      * @param   int         $tourid     The ID of the tour to display.
511      * @param   int         $visibility The intended visibility.
512      */
513     protected function show_hide_tour($tourid, $visibility) {
514         global $DB;
516         require_sesskey();
518         $tour = $DB->get_record('tool_usertours_tours', array('id' => $tourid));
519         $tour->enabled = $visibility;
520         $DB->update_record('tool_usertours_tours', $tour);
522         redirect(helper::get_list_tour_link());
523     }
525     /**
526      * Delete the tour.
527      *
528      * @param   int         $tourid     The ID of the tour to remove.
529      */
530     protected function delete_tour($tourid) {
531         require_sesskey();
533         $tour = tour::instance($tourid);
534         $tour->remove();
536         redirect(helper::get_list_tour_link());
537     }
539     /**
540      * Reset the tour state for all users.
541      *
542      * @param   int         $tourid     The ID of the tour to remove.
543      */
544     protected function reset_tour_for_all($tourid) {
545         require_sesskey();
547         $tour = tour::instance($tourid);
548         $tour->mark_major_change();
550         redirect(helper::get_view_tour_link($tourid), get_string('tour_resetforall', 'tool_usertours'));
551     }
553     /**
554      * Get the first tour matching the current page URL.
555      *
556      * @param   bool        $reset      Forcibly update the current tour
557      * @return  tour
558      */
559     public static function get_current_tour($reset = false) {
560         global $PAGE;
562         static $tour = false;
564         if ($tour === false || $reset) {
565             $tour = self::get_matching_tours($PAGE->url);
566         }
568         return $tour;
569     }
571     /**
572      * Get the first tour matching the specified URL.
573      *
574      * @param   moodle_url  $pageurl        The URL to match.
575      * @return  tour
576      */
577     public static function get_matching_tours(\moodle_url $pageurl) {
578         global $PAGE;
580         $tours = cache::get_matching_tourdata($pageurl);
582         foreach ($tours as $record) {
583             $tour = tour::load_from_record($record);
584             if ($tour->is_enabled() && $tour->matches_all_filters($PAGE->context)) {
585                 return $tour;
586             }
587         }
589         return null;
590     }
592     /**
593      * Import the provided tour JSON.
594      *
595      * @param   string      $json           The tour configuration.
596      * @return  tour
597      */
598     public static function import_tour_from_json($json) {
599         $tourconfig = json_decode($json);
601         // We do not use this yet - we may do in the future.
602         unset($tourconfig->version);
604         $steps = $tourconfig->steps;
605         unset($tourconfig->steps);
607         $tourconfig->id = null;
608         $tourconfig->sortorder = null;
609         $tour = tour::load_from_record($tourconfig, true);
610         $tour->persist(true);
612         // Ensure that steps are orderered by their sortorder.
613         \core_collator::asort_objects_by_property($steps, 'sortorder', \core_collator::SORT_NUMERIC);
615         foreach ($steps as $stepconfig) {
616             $stepconfig->id = null;
617             $stepconfig->tourid = $tour->get_id();
618             $step = step::load_from_record($stepconfig, true);
619             $step->persist(true);
620         }
622         return $tour;
623     }
625     /**
626      * Helper to fetch the renderer.
627      *
628      * @return  renderer
629      */
630     protected function get_renderer() {
631         global $PAGE;
632         return $PAGE->get_renderer('tool_usertours');
633     }
635     /**
636      * Print the edit step link.
637      *
638      * @param   int     $tourid     The ID of the tour.
639      * @param   int     $stepid     The ID of the step.
640      * @return  string
641      */
642     protected function print_edit_step_link($tourid, $stepid = null) {
643         $addlink = helper::get_edit_step_link($tourid, $stepid);
644         $attributes = [];
645         if (empty($stepid)) {
646             $attributes['class'] = 'createstep';
647         }
648         echo \html_writer::link($addlink, get_string('newstep', 'tool_usertours'), $attributes);
649     }
651     /**
652      * Display the edit step form for the specified step.
653      *
654      * @param   int     $id     The step to edit.
655      */
656     protected function edit_step($id) {
657         global $PAGE;
659         if (isset($id)) {
660             $step = step::instance($id);
661         } else {
662             $step = new step();
663             $step->set_tourid(required_param('tourid', PARAM_INT));
664         }
666         $tour = $step->get_tour();
668         if (!empty($tour->get_config(self::CONFIG_SHIPPED_TOUR))) {
669             notification::add(get_string('modifyshippedtourwarning', 'tool_usertours'), notification::WARNING);
670         }
672         $PAGE->navbar->add($tour->get_name(), $tour->get_view_link());
673         if (isset($id)) {
674             $PAGE->navbar->add($step->get_title(), $step->get_edit_link());
675         } else {
676             $PAGE->navbar->add(get_string('newstep', 'tool_usertours'), $step->get_edit_link());
677         }
679         $form = new forms\editstep($step->get_edit_link(), $step);
680         if ($form->is_cancelled()) {
681             redirect($step->get_tour()->get_view_link());
682         } else if ($data = $form->get_data()) {
683             $step->handle_form_submission($form, $data);
684             $step->get_tour()->reset_step_sortorder();
685             redirect($step->get_tour()->get_view_link());
686         } else {
687             if (empty($id)) {
688                 $this->header(get_string('newstep', 'tool_usertours'));
689             } else {
690                 $this->header(get_string('editstep', 'tool_usertours', $step->get_title()));
691             }
692             $form->set_data($step->prepare_data_for_form());
694             $form->display();
695             $this->footer();
696         }
697     }
699     /**
700      * Move a tour up or down and redirect once complete.
701      *
702      * @param   int     $id     The tour to move.
703      */
704     protected function move_tour($id) {
705         require_sesskey();
707         $direction = required_param('direction', PARAM_INT);
709         $tour = tour::instance($id);
710         self::_move_tour($tour, $direction);
712         redirect(helper::get_list_tour_link());
713     }
715     /**
716      * Move a tour up or down.
717      *
718      * @param   tour    $tour   The tour to move.
719      *
720      * @param   int     $direction
721      */
722     protected static function _move_tour(tour $tour, $direction) {
723         $currentsortorder   = $tour->get_sortorder();
724         $targetsortorder    = $currentsortorder + $direction;
726         $swapwith = helper::get_tour_from_sortorder($targetsortorder);
728         // Set the sort order to something out of the way.
729         $tour->set_sortorder(-1);
730         $tour->persist();
732         // Swap the two sort orders.
733         $swapwith->set_sortorder($currentsortorder);
734         $swapwith->persist();
736         $tour->set_sortorder($targetsortorder);
737         $tour->persist();
738     }
740     /**
741      * Move a step up or down.
742      *
743      * @param   int     $id     The step to move.
744      */
745     protected function move_step($id) {
746         require_sesskey();
748         $direction = required_param('direction', PARAM_INT);
750         $step = step::instance($id);
751         $currentsortorder   = $step->get_sortorder();
752         $targetsortorder    = $currentsortorder + $direction;
754         $tour = $step->get_tour();
755         $swapwith = helper::get_step_from_sortorder($tour->get_id(), $targetsortorder);
757         // Set the sort order to something out of the way.
758         $step->set_sortorder(-1);
759         $step->persist();
761         // Swap the two sort orders.
762         $swapwith->set_sortorder($currentsortorder);
763         $swapwith->persist();
765         $step->set_sortorder($targetsortorder);
766         $step->persist();
768         // Reset the sort order.
769         $tour->reset_step_sortorder();
770         redirect($tour->get_view_link());
771     }
773     /**
774      * Delete the step.
775      *
776      * @param   int         $stepid     The ID of the step to remove.
777      */
778     protected function delete_step($stepid) {
779         require_sesskey();
781         $step = step::instance($stepid);
782         $tour = $step->get_tour();
784         $step->remove();
785         redirect($tour->get_view_link());
786     }
788     /**
789      * Make sure all of the default tours that are shipped with Moodle are created
790      * and up to date with the latest version.
791      */
792     public static function update_shipped_tours() {
793         global $DB, $CFG;
795         // A list of tours that are shipped with Moodle. They are in
796         // the format filename => version. The version value needs to
797         // be increased if the tour has been updated.
798         $shippedtours = [
799             '36_dashboard.json' => 3,
800             '36_messaging.json' => 3,
801         ];
803         // These are tours that we used to ship but don't ship any longer.
804         // We do not remove them, but we do disable them.
805         $unshippedtours = [
806             'boost_administrator.json' => 1,
807             'boost_course_view.json' => 1,
808         ];
810         $existingtourrecords = $DB->get_recordset('tool_usertours_tours');
812         // Get all of the existing shipped tours and check if they need to be
813         // updated.
814         foreach ($existingtourrecords as $tourrecord) {
815             $tour = tour::load_from_record($tourrecord);
817             if (!empty($tour->get_config(self::CONFIG_SHIPPED_TOUR))) {
818                 $filename = $tour->get_config(self::CONFIG_SHIPPED_FILENAME);
819                 $version = $tour->get_config(self::CONFIG_SHIPPED_VERSION);
821                 // If we know about this tour (otherwise leave it as is).
822                 if (isset($shippedtours[$filename])) {
823                     // And the version in the DB is an older version.
824                     if ($version < $shippedtours[$filename]) {
825                         // Remove the old version because it's been updated
826                         // and needs to be recreated.
827                         $tour->remove();
828                     } else {
829                         // The tour has not been updated so we don't need to
830                         // do anything with it.
831                         unset($shippedtours[$filename]);
832                     }
833                 }
835                 if (isset($unshippedtours[$filename])) {
836                     if ($version <= $unshippedtours[$filename]) {
837                         $tour = tour::instance($tour->get_id());
838                         $tour->set_enabled(tour::DISABLED);
839                         $tour->persist();
840                     }
841                 }
842             }
843         }
844         $existingtourrecords->close();
846         foreach (array_reverse($shippedtours) as $filename => $version) {
847             $filepath = $CFG->dirroot . "/{$CFG->admin}/tool/usertours/tours/" . $filename;
848             $tourjson = file_get_contents($filepath);
849             $tour = self::import_tour_from_json($tourjson);
851             // Set some additional config data to record that this tour was
852             // added as a shipped tour.
853             $tour->set_config(self::CONFIG_SHIPPED_TOUR, true);
854             $tour->set_config(self::CONFIG_SHIPPED_FILENAME, $filename);
855             $tour->set_config(self::CONFIG_SHIPPED_VERSION, $version);
857             // Bump new tours to the top of the list.
858             while ($tour->get_sortorder() > 0) {
859                 self::_move_tour($tour, helper::MOVE_UP);
860             }
862             if (defined('BEHAT_SITE_RUNNING') || (defined('PHPUNIT_TEST') && PHPUNIT_TEST)) {
863                 // Disable this tour if this is behat or phpunit.
864                 $tour->set_enabled(false);
865             }
867             $tour->persist();
868         }
869     }