Ir a la última revisión | Autoría | Comparar con el anterior | Ultima modificación | Ver Log |
<?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();}}}