Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.

/**
 * Tour manager.
 *
 * @package    tool_usertours
 * @copyright  2016 Andrew Nicols <andrew@nicols.co.uk>
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */

namespace tool_usertours;

defined('MOODLE_INTERNAL') || die();

use tool_usertours\local\forms;
use tool_usertours\local\table;
use core\notification;

/**
 * Tour manager.
 *
 * @copyright  2016 Andrew Nicols <andrew@nicols.co.uk>
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
class manager {

    /**
     * @var ACTION_LISTTOURS      The action to get the list of tours.
     */
    const ACTION_LISTTOURS = 'listtours';

    /**
     * @var ACTION_NEWTOUR        The action to create a new tour.
     */
    const ACTION_NEWTOUR = 'newtour';

    /**
     * @var ACTION_EDITTOUR       The action to edit the tour.
     */
    const ACTION_EDITTOUR = 'edittour';

    /**
     * @var ACTION_MOVETOUR The action to move a tour up or down.
     */
    const ACTION_MOVETOUR = 'movetour';

    /**
     * @var ACTION_EXPORTTOUR     The action to export the tour.
     */
    const ACTION_EXPORTTOUR = 'exporttour';

    /**
     * @var ACTION_IMPORTTOUR     The action to import the tour.
     */
    const ACTION_IMPORTTOUR = 'importtour';

    /**
     * @var ACTION_DELETETOUR     The action to delete the tour.
     */
    const ACTION_DELETETOUR = 'deletetour';

    /**
     * @var ACTION_VIEWTOUR       The action to view the tour.
     */
    const ACTION_VIEWTOUR = 'viewtour';

    /**
     * @var ACTION_DUPLICATETOUR     The action to duplicate the tour.
     */
    const ACTION_DUPLICATETOUR = 'duplicatetour';

    /**
     * @var ACTION_NEWSTEP The action to create a new step.
     */
    const ACTION_NEWSTEP = 'newstep';

    /**
     * @var ACTION_EDITSTEP The action to edit step configuration.
     */
    const ACTION_EDITSTEP = 'editstep';

    /**
     * @var ACTION_MOVESTEP The action to move a step up or down.
     */
    const ACTION_MOVESTEP = 'movestep';

    /**
     * @var ACTION_DELETESTEP The action to delete a step.
     */
    const ACTION_DELETESTEP = 'deletestep';

    /**
     * @var ACTION_VIEWSTEP The action to view a step.
     */
    const ACTION_VIEWSTEP = 'viewstep';

    /**
     * @var ACTION_HIDETOUR The action to hide a tour.
     */
    const ACTION_HIDETOUR = 'hidetour';

    /**
     * @var ACTION_SHOWTOUR The action to show a tour.
     */
    const ACTION_SHOWTOUR = 'showtour';

    /**
     * @var ACTION_RESETFORALL
     */
    const ACTION_RESETFORALL = 'resetforall';

    /**
     * @var CONFIG_SHIPPED_TOUR
     */
    const CONFIG_SHIPPED_TOUR = 'shipped_tour';

    /**
     * @var CONFIG_SHIPPED_FILENAME
     */
    const CONFIG_SHIPPED_FILENAME = 'shipped_filename';

    /**
     * @var CONFIG_SHIPPED_VERSION
     */
    const CONFIG_SHIPPED_VERSION = 'shipped_version';

    /**
     * Helper method to initialize admin page, setting appropriate extra URL parameters
     *
     * @param string $action
     */
    protected function setup_admin_externalpage(string $action): void {
        admin_externalpage_setup('tool_usertours/tours', '', array_filter([
            'action' => $action,
            'id' => optional_param('id', 0, PARAM_INT),
            'tourid' => optional_param('tourid', 0, PARAM_INT),
            'direction' => optional_param('direction', 0, PARAM_INT),
        ]));
    }

    /**
     * This is the entry point for this controller class.
     *
     * @param   string  $action     The action to perform.
     */
    public function execute($action) {
> global $PAGE;
$this->setup_admin_externalpage($action);
> $PAGE->set_primary_active_tab('siteadminnode');
// Add the main content. switch($action) { case self::ACTION_NEWTOUR: case self::ACTION_EDITTOUR: $this->edit_tour(optional_param('id', null, PARAM_INT)); break; case self::ACTION_MOVETOUR: $this->move_tour(required_param('id', PARAM_INT)); break; case self::ACTION_EXPORTTOUR: $this->export_tour(required_param('id', PARAM_INT)); break; case self::ACTION_IMPORTTOUR: $this->import_tour(); break; case self::ACTION_VIEWTOUR: $this->view_tour(required_param('id', PARAM_INT)); break; case self::ACTION_DUPLICATETOUR: $this->duplicate_tour(required_param('id', PARAM_INT)); break; case self::ACTION_HIDETOUR: $this->hide_tour(required_param('id', PARAM_INT)); break; case self::ACTION_SHOWTOUR: $this->show_tour(required_param('id', PARAM_INT)); break; case self::ACTION_DELETETOUR: $this->delete_tour(required_param('id', PARAM_INT)); break; case self::ACTION_RESETFORALL: $this->reset_tour_for_all(required_param('id', PARAM_INT)); break; case self::ACTION_NEWSTEP: case self::ACTION_EDITSTEP: $this->edit_step(optional_param('id', null, PARAM_INT)); break; case self::ACTION_MOVESTEP: $this->move_step(required_param('id', PARAM_INT)); break; case self::ACTION_DELETESTEP: $this->delete_step(required_param('id', PARAM_INT)); break; case self::ACTION_LISTTOURS: default: $this->print_tour_list(); break; } } /** * Print out the page header. * * @param string $title The title to display. */ protected function header($title = null) { global $OUTPUT; // Print the page heading. echo $OUTPUT->header(); if ($title === null) { $title = get_string('tours', 'tool_usertours'); } echo $OUTPUT->heading($title); } /** * Print out the page footer. * * @return void */ protected function footer() { global $OUTPUT; echo $OUTPUT->footer(); } /** * Print the the list of tours. */ protected function print_tour_list() { global $PAGE, $OUTPUT; $this->header(); echo \html_writer::span(get_string('tourlist_explanation', 'tool_usertours')); $table = new table\tour_list(); $tours = helper::get_tours(); foreach ($tours as $tour) { $table->add_data_keyed($table->format_row($tour)); } $table->finish_output(); $actions = [ (object) [ 'link' => helper::get_edit_tour_link(), 'linkproperties' => [], 'img' => 'b/tour-new', 'title' => get_string('newtour', 'tool_usertours'), ], (object) [ 'link' => helper::get_import_tour_link(), 'linkproperties' => [], 'img' => 'b/tour-import', 'title' => get_string('importtour', 'tool_usertours'), ], (object) [ 'link' => new \moodle_url('https://archive.moodle.net/tours'), 'linkproperties' => [ 'target' => '_blank', ], 'img' => 'b/tour-shared', 'title' => get_string('sharedtourslink', 'tool_usertours'), ], ]; echo \html_writer::start_tag('div', [ 'class' => 'tour-actions', ]); echo \html_writer::start_tag('ul'); foreach ($actions as $config) { $action = \html_writer::start_tag('li'); $linkproperties = $config->linkproperties; $linkproperties['href'] = $config->link; $action .= \html_writer::start_tag('a', $linkproperties); $action .= $OUTPUT->pix_icon($config->img, $config->title, 'tool_usertours'); $action .= \html_writer::div($config->title); $action .= \html_writer::end_tag('a'); $action .= \html_writer::end_tag('li'); echo $action; } echo \html_writer::end_tag('ul'); echo \html_writer::end_tag('div'); // JS for Tour management. $PAGE->requires->js_call_amd('tool_usertours/managetours', 'setup'); $this->footer(); } /** * Return the edit tour link. * * @param int $id The ID of the tour * @return string */ protected function get_edit_tour_link($id = null) { $addlink = helper::get_edit_tour_link($id); return \html_writer::link($addlink, get_string('newtour', 'tool_usertours')); } /** * Print the edit tour link. * * @param int $id The ID of the tour */ protected function print_edit_tour_link($id = null) { echo $this->get_edit_tour_link($id); } /** * Get the import tour link. * * @return string */ protected function get_import_tour_link() { $importlink = helper::get_import_tour_link(); return \html_writer::link($importlink, get_string('importtour', 'tool_usertours')); } /** * Print the edit tour page. * * @param int $id The ID of the tour */ protected function edit_tour($id = null) { global $PAGE; if ($id) { $tour = tour::instance($id);
< $PAGE->navbar->add($tour->get_name(), $tour->get_edit_link());
> $PAGE->navbar->add(helper::get_string_from_input($tour->get_name()), $tour->get_edit_link());
} else { $tour = new tour(); $PAGE->navbar->add(get_string('newtour', 'tool_usertours'), $tour->get_edit_link()); } $form = new forms\edittour($tour); if ($form->is_cancelled()) { redirect(helper::get_list_tour_link()); } else if ($data = $form->get_data()) { // Creating a new tour. $tour->set_name($data->name); $tour->set_description($data->description); $tour->set_pathmatch($data->pathmatch); $tour->set_enabled(!empty($data->enabled));
> $tour->set_endtourlabel($data->endtourlabel); > $tour->set_display_step_numbers(!empty($data->displaystepnumbers));
foreach (configuration::get_defaultable_keys() as $key) { $tour->set_config($key, $data->$key); } // Save filter values. foreach (helper::get_all_filters() as $filterclass) { $filterclass::save_filter_values_from_form($tour, $data); } $tour->persist(); redirect(helper::get_list_tour_link()); } else { if (empty($tour)) { $this->header('newtour'); } else { if (!empty($tour->get_config(self::CONFIG_SHIPPED_TOUR))) { notification::add(get_string('modifyshippedtourwarning', 'tool_usertours'), notification::WARNING); }
< $this->header($tour->get_name());
> $tourname = !empty($tour->get_name()) ? helper::get_string_from_input($tour->get_name()) : ''; > $this->header($tourname);
$data = $tour->prepare_data_for_form(); // Prepare filter values for the form. foreach (helper::get_all_filters() as $filterclass) { $filterclass::prepare_filter_values_for_form($tour, $data); } $form->set_data($data); } $form->display(); $this->footer(); } } /** * Print the export tour page. * * @param int $id The ID of the tour */ protected function export_tour($id) { $tour = tour::instance($id); // Grab the full data record. $export = $tour->to_record(); // Remove the id. unset($export->id); // Set the version. $export->version = get_config('tool_usertours', 'version'); // Step export. $export->steps = []; foreach ($tour->get_steps() as $step) {
< $record = $step->to_record();
> $record = $step->to_record(true);
unset($record->id); unset($record->tourid); $export->steps[] = $record; } $exportstring = json_encode($export); $filename = 'tour_export_' . $tour->get_id() . '_' . time() . '.json'; // Force download. send_file($exportstring, $filename, 0, 0, true, true); } /** * Handle tour import. */ protected function import_tour() { global $PAGE; $PAGE->navbar->add(get_string('importtour', 'tool_usertours'), helper::get_import_tour_link()); $form = new forms\importtour(); if ($form->is_cancelled()) { redirect(helper::get_list_tour_link()); } else if ($form->get_data()) { // Importing a tour. $tourconfigraw = $form->get_file_content('tourconfig'); $tour = self::import_tour_from_json($tourconfigraw); redirect($tour->get_view_link()); } else { $this->header(); $form->display(); $this->footer(); } } /** * Print the view tour page. * * @param int $tourid The ID of the tour to display. */ protected function view_tour($tourid) { global $PAGE; $tour = helper::get_tour($tourid);
> $tourname = helper::get_string_from_input($tour->get_name());
< $PAGE->navbar->add($tour->get_name(), $tour->get_view_link());
> $PAGE->navbar->add($tourname, $tour->get_view_link());
< $this->header($tour->get_name());
> $this->header($tourname);
echo \html_writer::span(get_string('viewtour_info', 'tool_usertours', [
< 'tourname' => $tour->get_name(),
> 'tourname' => $tourname,
'path' => $tour->get_pathmatch(), ])); echo \html_writer::div(get_string('viewtour_edit', 'tool_usertours', [ 'editlink' => $tour->get_edit_link()->out(), 'resetlink' => $tour->get_reset_link()->out(), ])); $table = new table\step_list($tourid); foreach ($tour->get_steps() as $step) { $table->add_data_keyed($table->format_row($step)); } $table->finish_output(); $this->print_edit_step_link($tourid); // JS for Step management. $PAGE->requires->js_call_amd('tool_usertours/managesteps', 'setup'); $this->footer(); } /** * Duplicate an existing tour. * * @param int $tourid The ID of the tour to duplicate. */ protected function duplicate_tour($tourid) { $tour = helper::get_tour($tourid); $export = $tour->to_record(); // Remove the id. unset($export->id); // Set the version. $export->version = get_config('tool_usertours', 'version'); $export->name = get_string('duplicatetour_name', 'tool_usertours', $export->name); // Step export. $export->steps = []; foreach ($tour->get_steps() as $step) {
< $record = $step->to_record();
> $record = $step->to_record(true);
unset($record->id); unset($record->tourid); $export->steps[] = $record; } $exportstring = json_encode($export); $newtour = self::import_tour_from_json($exportstring); redirect($newtour->get_view_link()); } /** * Show the tour. * * @param int $tourid The ID of the tour to display. */ protected function show_tour($tourid) { $this->show_hide_tour($tourid, 1); } /** * Hide the tour. * * @param int $tourid The ID of the tour to display. */ protected function hide_tour($tourid) { $this->show_hide_tour($tourid, 0); } /** * Show or Hide the tour. * * @param int $tourid The ID of the tour to display. * @param int $visibility The intended visibility. */ protected function show_hide_tour($tourid, $visibility) { global $DB; require_sesskey(); $tour = $DB->get_record('tool_usertours_tours', array('id' => $tourid)); $tour->enabled = $visibility; $DB->update_record('tool_usertours_tours', $tour); redirect(helper::get_list_tour_link()); } /** * Delete the tour. * * @param int $tourid The ID of the tour to remove. */ protected function delete_tour($tourid) { require_sesskey(); $tour = tour::instance($tourid); $tour->remove(); redirect(helper::get_list_tour_link()); } /** * Reset the tour state for all users. * * @param int $tourid The ID of the tour to remove. */ protected function reset_tour_for_all($tourid) { require_sesskey(); $tour = tour::instance($tourid); $tour->mark_major_change(); redirect(helper::get_view_tour_link($tourid), get_string('tour_resetforall', 'tool_usertours')); } /** * Get all tours for the current page URL. * * @param bool $reset Forcibly update the current tours * @return array */ public static function get_current_tours($reset = false): array { global $PAGE; static $tours = false; if ($tours === false || $reset) { $tours = self::get_matching_tours($PAGE->url); } return $tours; } /** * Get all tours matching the specified URL. * * @param moodle_url $pageurl The URL to match. * @return array */ public static function get_matching_tours(\moodle_url $pageurl): array {
< global $PAGE, $USER; < < // The following three checks make sure that the user is fully ready to use the site. If not, we do not show any tours. < // We need the user to get properly set up so that all require_login() and other bits work as expected. < < if (user_not_fully_set_up($USER)) { < return []; < } < < if (get_user_preferences('auth_forcepasswordchange', false)) { < return []; < } < < if (empty($USER->policyagreed) && !is_siteadmin()) { < $manager = new \core_privacy\local\sitepolicy\manager();
> global $PAGE;
< if ($manager->is_defined(isguestuser())) {
> if (\core_user::awaiting_action()) { > // User not fully ready to use the site. Don't show any tours, we need the user to get properly set up so > // that all require_login() and other bits work as expected.
return []; }
< }
$tours = cache::get_matching_tourdata($pageurl); $matches = []; if ($tours) { $filters = helper::get_all_filters(); foreach ($tours as $record) { $tour = tour::load_from_record($record); if ($tour->is_enabled() && $tour->matches_all_filters($PAGE->context, $filters)) { $matches[] = $tour; } } } return $matches; } /** * Import the provided tour JSON. * * @param string $json The tour configuration. * @return tour */ public static function import_tour_from_json($json) { $tourconfig = json_decode($json); // We do not use this yet - we may do in the future. unset($tourconfig->version); $steps = $tourconfig->steps; unset($tourconfig->steps); $tourconfig->id = null; $tourconfig->sortorder = null; $tour = tour::load_from_record($tourconfig, true); $tour->persist(true); // Ensure that steps are orderered by their sortorder. \core_collator::asort_objects_by_property($steps, 'sortorder', \core_collator::SORT_NUMERIC); foreach ($steps as $stepconfig) { $stepconfig->id = null; $stepconfig->tourid = $tour->get_id();
< $step = step::load_from_record($stepconfig, true);
> $step = step::load_from_record($stepconfig, true, true);
$step->persist(true); } return $tour; } /** * Helper to fetch the renderer. * * @return renderer */ protected function get_renderer() { global $PAGE; return $PAGE->get_renderer('tool_usertours'); } /** * Print the edit step link. * * @param int $tourid The ID of the tour. * @param int $stepid The ID of the step. * @return string */ protected function print_edit_step_link($tourid, $stepid = null) { $addlink = helper::get_edit_step_link($tourid, $stepid); $attributes = []; if (empty($stepid)) { $attributes['class'] = 'createstep'; } echo \html_writer::link($addlink, get_string('newstep', 'tool_usertours'), $attributes); } /** * Display the edit step form for the specified step. * * @param int $id The step to edit. */ protected function edit_step($id) { global $PAGE; if (isset($id)) { $step = step::instance($id); } else { $step = new step(); $step->set_tourid(required_param('tourid', PARAM_INT)); } $tour = $step->get_tour(); if (!empty($tour->get_config(self::CONFIG_SHIPPED_TOUR))) { notification::add(get_string('modifyshippedtourwarning', 'tool_usertours'), notification::WARNING); }
< $PAGE->navbar->add($tour->get_name(), $tour->get_view_link());
> $PAGE->navbar->add(helper::get_string_from_input($tour->get_name()), $tour->get_view_link());
if (isset($id)) {
< $PAGE->navbar->add($step->get_title(), $step->get_edit_link());
> $PAGE->navbar->add(helper::get_string_from_input($step->get_title()), $step->get_edit_link());
} else { $PAGE->navbar->add(get_string('newstep', 'tool_usertours'), $step->get_edit_link()); } $form = new forms\editstep($step->get_edit_link(), $step); if ($form->is_cancelled()) { redirect($step->get_tour()->get_view_link()); } else if ($data = $form->get_data()) { $step->handle_form_submission($form, $data); $step->get_tour()->reset_step_sortorder(); redirect($step->get_tour()->get_view_link()); } else { if (empty($id)) { $this->header(get_string('newstep', 'tool_usertours')); } else {
< $this->header(get_string('editstep', 'tool_usertours', $step->get_title()));
> $this->header(get_string('editstep', 'tool_usertours', helper::get_string_from_input($step->get_title())));
} $form->set_data($step->prepare_data_for_form()); $form->display(); $this->footer(); } } /** * Move a tour up or down and redirect once complete. * * @param int $id The tour to move. */ protected function move_tour($id) { require_sesskey(); $direction = required_param('direction', PARAM_INT); $tour = tour::instance($id); self::_move_tour($tour, $direction); redirect(helper::get_list_tour_link()); } /** * Move a tour up or down. * * @param tour $tour The tour to move. * * @param int $direction */ protected static function _move_tour(tour $tour, $direction) { // We can't move the first tour higher, nor the last tour any lower. if (($tour->is_first_tour() && $direction == helper::MOVE_UP) || ($tour->is_last_tour() && $direction == helper::MOVE_DOWN)) { return; } $currentsortorder = $tour->get_sortorder(); $targetsortorder = $currentsortorder + $direction; $swapwith = helper::get_tour_from_sortorder($targetsortorder); // Set the sort order to something out of the way. $tour->set_sortorder(-1); $tour->persist(); // Swap the two sort orders. $swapwith->set_sortorder($currentsortorder); $swapwith->persist(); $tour->set_sortorder($targetsortorder); $tour->persist(); } /** * Move a step up or down. * * @param int $id The step to move. */ protected function move_step($id) { require_sesskey(); $direction = required_param('direction', PARAM_INT); $step = step::instance($id); $currentsortorder = $step->get_sortorder(); $targetsortorder = $currentsortorder + $direction; $tour = $step->get_tour(); $swapwith = helper::get_step_from_sortorder($tour->get_id(), $targetsortorder); // Set the sort order to something out of the way. $step->set_sortorder(-1); $step->persist(); // Swap the two sort orders. $swapwith->set_sortorder($currentsortorder); $swapwith->persist(); $step->set_sortorder($targetsortorder); $step->persist(); // Reset the sort order. $tour->reset_step_sortorder(); redirect($tour->get_view_link()); } /** * Delete the step. * * @param int $stepid The ID of the step to remove. */ protected function delete_step($stepid) { require_sesskey(); $step = step::instance($stepid); $tour = $step->get_tour(); $step->remove(); redirect($tour->get_view_link()); } /** * Make sure all of the default tours that are shipped with Moodle are created * and up to date with the latest version. */ public static function update_shipped_tours() { global $DB, $CFG; // A list of tours that are shipped with Moodle. They are in // the format filename => version. The version value needs to // be increased if the tour has been updated. $shippedtours = [
> '40_tour_navigation_dashboard.json' => 4, ]; > '40_tour_navigation_mycourse.json' => 5, > '40_tour_navigation_course_teacher.json' => 3, // These are tours that we used to ship but don't ship any longer. > '40_tour_navigation_course_student.json' => 3,
// We do not remove them, but we do disable them. $unshippedtours = [ // Formerly included in Moodle 3.2.0. 'boost_administrator.json' => 1, 'boost_course_view.json' => 1, // Formerly included in Moodle 3.6.0. '36_dashboard.json' => 3, '36_messaging.json' => 3,
> ]; > // Formerly included in Moodle 3.11.0. > '311_activity_information_activity_page_student.json' => 2, $existingtourrecords = $DB->get_recordset('tool_usertours_tours'); > '311_activity_information_activity_page_teacher.json' => 2, > '311_activity_information_course_page_student.json' => 2, // Get all of the existing shipped tours and check if they need to be > '311_activity_information_course_page_teacher.json' => 2,
// updated. foreach ($existingtourrecords as $tourrecord) { $tour = tour::load_from_record($tourrecord); if (!empty($tour->get_config(self::CONFIG_SHIPPED_TOUR))) { $filename = $tour->get_config(self::CONFIG_SHIPPED_FILENAME); $version = $tour->get_config(self::CONFIG_SHIPPED_VERSION); // If we know about this tour (otherwise leave it as is). if (isset($shippedtours[$filename])) { // And the version in the DB is an older version. if ($version < $shippedtours[$filename]) { // Remove the old version because it's been updated // and needs to be recreated. $tour->remove(); } else { // The tour has not been updated so we don't need to // do anything with it. unset($shippedtours[$filename]); } } if (isset($unshippedtours[$filename])) { if ($version <= $unshippedtours[$filename]) { $tour = tour::instance($tour->get_id()); $tour->set_enabled(tour::DISABLED); $tour->persist(); } } } } $existingtourrecords->close(); // Ensure we correct the sortorder in any existing tours, prior to adding latest shipped tours. helper::reset_tour_sortorder(); foreach (array_reverse($shippedtours) as $filename => $version) { $filepath = $CFG->dirroot . "/{$CFG->admin}/tool/usertours/tours/" . $filename; $tourjson = file_get_contents($filepath); $tour = self::import_tour_from_json($tourjson); // Set some additional config data to record that this tour was // added as a shipped tour. $tour->set_config(self::CONFIG_SHIPPED_TOUR, true); $tour->set_config(self::CONFIG_SHIPPED_FILENAME, $filename); $tour->set_config(self::CONFIG_SHIPPED_VERSION, $version); // Bump new tours to the top of the list. while ($tour->get_sortorder() > 0) { self::_move_tour($tour, helper::MOVE_UP); } if (defined('BEHAT_SITE_RUNNING') || (defined('PHPUNIT_TEST') && PHPUNIT_TEST)) { // Disable this tour if this is behat or phpunit. $tour->set_enabled(false); } $tour->persist(); } } }