<?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/>.

namespace tool_usertours;

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

/**
 * 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
 */
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://moodle.net/search', ['q' => 'user 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(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));
            $tour->set_showtourwhen($data->showtourwhen);

            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);
                }

                $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(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($tourname, $tour->get_view_link());

        $this->header($tourname);
        echo \html_writer::span(get_string('viewtour_info', 'tool_usertours', [
                '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(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', ['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;

        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, 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(helper::get_string_from_input($tour->get_name()), $tour->get_view_link());
        if (isset($id)) {
            $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', 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,
            '40_tour_navigation_course_student.json' => 3,
            '42_tour_gradebook_grader_report.json' => 1,
        ];

        // These are tours that we used to ship but don't ship any longer.
        // 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,
            '311_activity_information_activity_page_teacher.json' => 2,
            '311_activity_information_course_page_student.json' => 2,
            '311_activity_information_course_page_teacher.json' => 2,
        ];

        $existingtourrecords = $DB->get_recordset('tool_usertours_tours');

        // Get all of the existing shipped tours and check if they need to be
        // 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();
        }
    }
}
