MDL-69739 tool_usertours: Add tour-level CSS selector

This commit is contained in:
Katie Ransom 2020-09-30 14:32:58 +01:00 committed by clransom
parent 11b094be4c
commit 51caf76655
16 changed files with 461 additions and 81 deletions

View File

@ -0,0 +1,2 @@
define ("tool_usertours/filter_cssselector",["exports"],function(a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.filterMatches=void 0;a.filterMatches=function filterMatches(a){var b=a.filtervalues.cssselector;if(b[0]){return!!document.querySelector(b[0])}return!0}});
//# sourceMappingURL=filter_cssselector.min.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":["../src/filter_cssselector.js"],"names":["filterMatches","tourConfig","filterValues","filtervalues","cssselector","document","querySelector"],"mappings":"yKA+B6B,QAAhBA,CAAAA,aAAgB,CAASC,CAAT,CAAqB,CAC9C,GAAIC,CAAAA,CAAY,CAAGD,CAAU,CAACE,YAAX,CAAwBC,WAA3C,CACA,GAAIF,CAAY,CAAC,CAAD,CAAhB,CAAqB,CACjB,MAAO,CAAC,CAACG,QAAQ,CAACC,aAAT,CAAuBJ,CAAY,CAAC,CAAD,CAAnC,CACZ,CAED,QACH,C","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * CSS selector client side filter.\n *\n * @module tool_usertours/filter_cssselector\n * @class filter_cssselector\n * @package tool_usertours\n * @copyright 2020 The Open University\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n/**\n * Checks whether the configured CSS selector exists on this page.\n *\n * @param {array} tourConfig The tour configuration.\n * @returns {boolean}\n */\nexport const filterMatches = function(tourConfig) {\n let filterValues = tourConfig.filtervalues.cssselector;\n if (filterValues[0]) {\n return !!document.querySelector(filterValues[0]);\n }\n // If there is no CSS selector configured, this page matches.\n return true;\n};\n"],"file":"filter_cssselector.min.js"}

View File

@ -1,2 +1,2 @@
define ("tool_usertours/usertours",["core/ajax","tool_usertours/tour","jquery","core/templates","core/str","core/log","core/notification"],function(a,b,c,d,e,f,g){var h={tourId:null,currentTour:null,context:null,init:function init(a,b,d){h.tourId=a;h.context=d;if("undefined"==typeof b){b=!0}if(b){h.fetchTour(a)}h.addResetLink();c("body").on("click","[data-action=\"tool_usertours/resetpagetour\"]",function(a){a.preventDefault();h.resetTourState(h.tourId)})},fetchTour:function fetchTour(b){M.util.js_pending("admin_usertour_fetchTour"+b);c.when(a.call([{methodname:"tool_usertours_fetch_and_start_tour",args:{tourid:b,context:h.context,pageurl:window.location.href}}])[0],d.render("tool_usertours/tourstep",{})).then(function(a,c){if(!a.hasOwnProperty("tourconfig")){return}return h.startBootstrapTour(b,c[0],a.tourconfig)}).always(function(){M.util.js_complete("admin_usertour_fetchTour"+b)}).fail(g.exception)},addResetLink:function addResetLink(){var a;M.util.js_pending("admin_usertour_addResetLink");if(c(".tool_usertours-resettourcontainer").length){a=c(".tool_usertours-resettourcontainer")}else if(c(".logininfo").length){a=c(".logininfo")}else if(c("footer").length){a=c("footer")}else{a=c("body")}d.render("tool_usertours/resettour",{}).then(function(b,c){d.appendNodeContents(a,b,c)}).always(function(){M.util.js_complete("admin_usertour_addResetLink")}).fail()},startBootstrapTour:function startBootstrapTour(a,c,d){if(h.currentTour){d.onEnd=null;h.currentTour.endTour();delete h.currentTour}d.eventHandlers={afterEnd:[h.markTourComplete],afterRender:[h.markStepShown]};d.tourName=d.name;delete d.name;d.template=c;d.steps=d.steps.map(function(a){if("undefined"!=typeof a.element){a.target=a.element;delete a.element}if("undefined"!=typeof a.reflex){a.moveOnClick=!!a.reflex;delete a.reflex}if("undefined"!=typeof a.content){a.body=a.content;delete a.content}return a});h.currentTour=new b(d);return h.currentTour.startTour()},markStepShown:function markStepShown(){var b=this.getStepConfig(this.getCurrentStepNumber());c.when(a.call([{methodname:"tool_usertours_step_shown",args:{tourid:h.tourId,context:h.context,pageurl:window.location.href,stepid:b.stepid,stepindex:this.getCurrentStepNumber()}}])[0]).fail(f.error)},markTourComplete:function markTourComplete(){var b=this.getStepConfig(this.getCurrentStepNumber());c.when(a.call([{methodname:"tool_usertours_complete_tour",args:{tourid:h.tourId,context:h.context,pageurl:window.location.href,stepid:b.stepid,stepindex:this.getCurrentStepNumber()}}])[0]).fail(f.error)},resetTourState:function resetTourState(b){c.when(a.call([{methodname:"tool_usertours_reset_tour",args:{tourid:b,context:h.context,pageurl:window.location.href}}])[0]).then(function(a){if(a.startTour){h.fetchTour(a.startTour)}}).fail(g.exception)}};return{init:h.init,resetTourState:h.resetTourState}}); define ("tool_usertours/usertours",["core/ajax","tool_usertours/tour","jquery","core/templates","core/str","core/log","core/notification"],function(a,b,c,d,e,f,g){var h={tourId:null,currentTour:null,init:function init(a,b){for(var d=[],e=0;e<b.length;e++){d[e]="tool_usertours/filter_"+b[e]}require(d,function(){var d=null;for(var k in a){for(var e=a[k],f=0,g;f<b.length;f++){g=arguments[f];if(g.filterMatches(e)){d=e}else{d=null;break}}if(d){break}}if(null===d){return}h.tourId=d.tourId;var j=d.startTour;if("undefined"==typeof j){j=!0}if(j){h.fetchTour(h.tourId)}h.addResetLink();c("body").on("click","[data-action=\"tool_usertours/resetpagetour\"]",function(a){a.preventDefault();h.resetTourState(h.tourId)})})},fetchTour:function fetchTour(b){M.util.js_pending("admin_usertour_fetchTour"+b);c.when(a.call([{methodname:"tool_usertours_fetch_and_start_tour",args:{tourid:b,context:M.cfg.contextid,pageurl:window.location.href}}])[0],d.render("tool_usertours/tourstep",{})).then(function(a,c){if(!a.hasOwnProperty("tourconfig")){return}return h.startBootstrapTour(b,c[0],a.tourconfig)}).always(function(){M.util.js_complete("admin_usertour_fetchTour"+b)}).fail(g.exception)},addResetLink:function addResetLink(){var a;M.util.js_pending("admin_usertour_addResetLink");if(c(".tool_usertours-resettourcontainer").length){a=c(".tool_usertours-resettourcontainer")}else if(c(".logininfo").length){a=c(".logininfo")}else if(c("footer").length){a=c("footer")}else{a=c("body")}d.render("tool_usertours/resettour",{}).then(function(b,c){d.appendNodeContents(a,b,c)}).always(function(){M.util.js_complete("admin_usertour_addResetLink")}).fail()},startBootstrapTour:function startBootstrapTour(a,c,d){if(h.currentTour){d.onEnd=null;h.currentTour.endTour();delete h.currentTour}d.eventHandlers={afterEnd:[h.markTourComplete],afterRender:[h.markStepShown]};d.tourName=d.name;delete d.name;d.template=c;d.steps=d.steps.map(function(a){if("undefined"!=typeof a.element){a.target=a.element;delete a.element}if("undefined"!=typeof a.reflex){a.moveOnClick=!!a.reflex;delete a.reflex}if("undefined"!=typeof a.content){a.body=a.content;delete a.content}return a});h.currentTour=new b(d);return h.currentTour.startTour()},markStepShown:function markStepShown(){var b=this.getStepConfig(this.getCurrentStepNumber());c.when(a.call([{methodname:"tool_usertours_step_shown",args:{tourid:h.tourId,context:M.cfg.contextid,pageurl:window.location.href,stepid:b.stepid,stepindex:this.getCurrentStepNumber()}}])[0]).fail(f.error)},markTourComplete:function markTourComplete(){var b=this.getStepConfig(this.getCurrentStepNumber());c.when(a.call([{methodname:"tool_usertours_complete_tour",args:{tourid:h.tourId,context:M.cfg.contextid,pageurl:window.location.href,stepid:b.stepid,stepindex:this.getCurrentStepNumber()}}])[0]).fail(f.error)},resetTourState:function resetTourState(b){c.when(a.call([{methodname:"tool_usertours_reset_tour",args:{tourid:b,context:M.cfg.contextid,pageurl:window.location.href}}])[0]).then(function(a){if(a.startTour){h.fetchTour(a.startTour)}}).fail(g.exception)}};return{init:h.init,resetTourState:h.resetTourState}});
//# sourceMappingURL=usertours.min.js.map //# sourceMappingURL=usertours.min.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,39 @@
// 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/>.
/**
* CSS selector client side filter.
*
* @module tool_usertours/filter_cssselector
* @class filter_cssselector
* @package tool_usertours
* @copyright 2020 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Checks whether the configured CSS selector exists on this page.
*
* @param {array} tourConfig The tour configuration.
* @returns {boolean}
*/
export const filterMatches = function(tourConfig) {
let filterValues = tourConfig.filtervalues.cssselector;
if (filterValues[0]) {
return !!document.querySelector(filterValues[0]);
}
// If there is no CSS selector configured, this page matches.
return true;
};

View File

@ -14,36 +14,62 @@ function(ajax, BootstrapTour, $, templates, str, log, notification) {
currentTour: null, currentTour: null,
context: null,
/** /**
* Initialise the user tour for the current page. * Initialise the user tour for the current page.
* *
* @method init * @method init
* @param {Number} tourId The ID of the tour to start. * @param {Array} tourDetails The matching tours for this page.
* @param {Bool} startTour Attempt to start the tour now. * @param {Array} filters The names of all client side filters.
* @param {Number} context The context of the current page.
*/ */
init: function(tourId, startTour, context) { init: function(tourDetails, filters) {
// Only one tour per page is allowed. let requirements = [];
usertours.tourId = tourId; for (var req = 0; req < filters.length; req++) {
requirements[req] = 'tool_usertours/filter_' + filters[req];
usertours.context = context;
if (typeof startTour === 'undefined') {
startTour = true;
} }
require(requirements, function() {
// Run the client side filters to find the first matching tour.
let matchingTour = null;
for (let key in tourDetails) {
let tour = tourDetails[key];
for (let i = 0; i < filters.length; i++) {
let filter = arguments[i];
if (filter.filterMatches(tour)) {
matchingTour = tour;
} else {
// If any filter doesn't match, move on to the next tour.
matchingTour = null;
break;
}
}
// If all filters matched then use this tour.
if (matchingTour) {
break;
}
}
if (startTour) { if (matchingTour === null) {
// Fetch the tour configuration. return;
usertours.fetchTour(tourId); }
}
usertours.addResetLink(); // Only one tour per page is allowed.
// Watch for the reset link. usertours.tourId = matchingTour.tourId;
$('body').on('click', '[data-action="tool_usertours/resetpagetour"]', function(e) {
e.preventDefault(); let startTour = matchingTour.startTour;
usertours.resetTourState(usertours.tourId); if (typeof startTour === 'undefined') {
startTour = true;
}
if (startTour) {
// Fetch the tour configuration.
usertours.fetchTour(usertours.tourId);
}
usertours.addResetLink();
// Watch for the reset link.
$('body').on('click', '[data-action="tool_usertours/resetpagetour"]', function(e) {
e.preventDefault();
usertours.resetTourState(usertours.tourId);
});
}); });
}, },
@ -61,7 +87,7 @@ function(ajax, BootstrapTour, $, templates, str, log, notification) {
methodname: 'tool_usertours_fetch_and_start_tour', methodname: 'tool_usertours_fetch_and_start_tour',
args: { args: {
tourid: tourId, tourid: tourId,
context: usertours.context, context: M.cfg.contextid,
pageurl: window.location.href, pageurl: window.location.href,
} }
} }
@ -186,7 +212,7 @@ function(ajax, BootstrapTour, $, templates, str, log, notification) {
methodname: 'tool_usertours_step_shown', methodname: 'tool_usertours_step_shown',
args: { args: {
tourid: usertours.tourId, tourid: usertours.tourId,
context: usertours.context, context: M.cfg.contextid,
pageurl: window.location.href, pageurl: window.location.href,
stepid: stepConfig.stepid, stepid: stepConfig.stepid,
stepindex: this.getCurrentStepNumber(), stepindex: this.getCurrentStepNumber(),
@ -209,7 +235,7 @@ function(ajax, BootstrapTour, $, templates, str, log, notification) {
methodname: 'tool_usertours_complete_tour', methodname: 'tool_usertours_complete_tour',
args: { args: {
tourid: usertours.tourId, tourid: usertours.tourId,
context: usertours.context, context: M.cfg.contextid,
pageurl: window.location.href, pageurl: window.location.href,
stepid: stepConfig.stepid, stepid: stepConfig.stepid,
stepindex: this.getCurrentStepNumber(), stepindex: this.getCurrentStepNumber(),
@ -232,7 +258,7 @@ function(ajax, BootstrapTour, $, templates, str, log, notification) {
methodname: 'tool_usertours_reset_tour', methodname: 'tool_usertours_reset_tour',
args: { args: {
tourid: tourId, tourid: tourId,
context: usertours.context, context: M.cfg.contextid,
pageurl: window.location.href, pageurl: window.location.href,
} }
} }

View File

@ -131,8 +131,9 @@ class tour extends external_api {
$result = []; $result = [];
if ($tourinstance = \tool_usertours\manager::get_matching_tours(new \moodle_url($params['pageurl']))) { $matchingtours = \tool_usertours\manager::get_matching_tours(new \moodle_url($params['pageurl']));
if ($tour->get_id() === $tourinstance->get_id()) { foreach ($matchingtours as $match) {
if ($tour->get_id() === $match->get_id()) {
$result['startTour'] = $tour->get_id(); $result['startTour'] = $tour->get_id();
\tool_usertours\event\tour_reset::create([ \tool_usertours\event\tour_reset::create([
@ -142,7 +143,7 @@ class tour extends external_api {
'pageurl' => $params['pageurl'], 'pageurl' => $params['pageurl'],
], ],
])->trigger(); ])->trigger();
break;
} }
} }

View File

@ -24,6 +24,8 @@
namespace tool_usertours; namespace tool_usertours;
use tool_usertours\local\clientside_filter\clientside_filter;
defined('MOODLE_INTERNAL') || die(); defined('MOODLE_INTERNAL') || die();
/** /**
@ -523,23 +525,28 @@ class helper {
} }
self::$bootstrapped = true; self::$bootstrapped = true;
if ($tour = manager::get_current_tour()) { $tours = manager::get_current_tours();
$PAGE->requires->js_call_amd('tool_usertours/usertours', 'init', [
$tour->get_id(),
$tour->should_show_for_user(),
$PAGE->context->id,
]);
}
}
/** if ($tours) {
* Add the reset link to the current page. $filters = static::get_all_clientside_filters();
*/
public static function bootstrap_reset() { $tourdetails = array_map(function($tour) use ($filters) {
if (manager::get_current_tour()) { return [
echo \html_writer::link('', get_string('resettouronpage', 'tool_usertours'), [ 'tourId' => $tour->get_id(),
'data-action' => 'tool_usertours/resetpagetour', 'startTour' => $tour->should_show_for_user(),
]); 'filtervalues' => $tour->get_client_filter_values($filters),
];
}, $tours);
$filternames = [];
foreach ($filters as $filter) {
$filternames[] = $filter::get_filter_name();
}
$PAGE->requires->js_call_amd('tool_usertours/usertours', 'init', [
$tourdetails,
$filternames,
]);
} }
} }
@ -557,6 +564,25 @@ class helper {
return $rc->isInstantiable(); return $rc->isInstantiable();
}); });
$filters = array_merge($filters, static::get_all_clientside_filters());
return $filters;
}
/**
* Get a list of all clientside filters.
*
* @return array
*/
public static function get_all_clientside_filters() {
$filters = \core_component::get_component_classes_in_namespace('tool_usertours', 'local\clientside_filter');
$filters = array_keys($filters);
$filters = array_filter($filters, function($filterclass) {
$rc = new \ReflectionClass($filterclass);
return $rc->isInstantiable();
});
return $filters; return $filters;
} }
} }

View File

@ -0,0 +1,55 @@
<?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/>.
/**
* Clientside filter base.
*
* @package tool_usertours
* @copyright 2020 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_usertours\local\clientside_filter;
defined('MOODLE_INTERNAL') || die();
use stdClass;
use tool_usertours\local\filter\base;
use tool_usertours\tour;
/**
* Clientside filter base.
*
* @copyright 2020 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class clientside_filter extends base {
/**
* Returns the filter values needed for client side filtering.
*
* @param tour $tour The tour to find the filter values for
* @return stdClass
*/
public static function get_client_side_values(tour $tour): stdClass {
$data = (object) [];
if (is_a(static::class, clientside_filter::class, true)) {
$data->filterdata = $tour->get_filter_values(static::get_filter_name());
}
return $data;
}
}

View File

@ -0,0 +1,115 @@
<?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/>.
/**
* Selector filter.
*
* @package tool_usertours
* @copyright 2020 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_usertours\local\clientside_filter;
use stdClass;
use tool_usertours\tour;
/**
* Course filter.
*
* @copyright 2020 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cssselector extends clientside_filter {
/**
* The name of the filter.
*
* @return string
*/
public static function get_filter_name() {
return 'cssselector';
}
/**
* Overrides the base add form element with a selector text box.
*
* @param \MoodleQuickForm $mform
*/
public static function add_filter_to_form(\MoodleQuickForm &$mform) {
$filtername = self::get_filter_name();
$key = "filter_{$filtername}";
$mform->addElement('text', $key, get_string($key, 'tool_usertours'));
$mform->setType($key, PARAM_RAW);
$mform->addHelpButton($key, $key, 'tool_usertours');
}
/**
* Prepare the filter values for the form.
*
* @param tour $tour The tour to prepare values from
* @param stdClass $data The data value
* @return stdClass
*/
public static function prepare_filter_values_for_form(tour $tour, \stdClass $data) {
$filtername = static::get_filter_name();
$key = "filter_{$filtername}";
$values = $tour->get_filter_values($filtername);
if (empty($values)) {
$values = [""];
}
$data->$key = $values[0];
return $data;
}
/**
* Save the filter values from the form to the tour.
*
* @param tour $tour The tour to save values to
* @param stdClass $data The data submitted in the form
*/
public static function save_filter_values_from_form(tour $tour, \stdClass $data) {
$filtername = static::get_filter_name();
$key = "filter_{$filtername}";
$newvalue = [$data->$key];
if (empty($data->$key)) {
$newvalue = [];
}
$tour->set_filter_values($filtername, $newvalue);
}
/**
* Returns the filter values needed for client side filtering.
*
* @param tour $tour The tour to find the filter values for
* @return stdClass
*/
public static function get_client_side_values(tour $tour): stdClass {
$filtername = static::get_filter_name();
$filtervalues = $tour->get_filter_values($filtername);
// Filter values might not exist for tours that were created before this filter existed.
if (!$filtervalues) {
return new stdClass;
}
return (object) $filtervalues;
}
}

View File

@ -608,42 +608,44 @@ class manager {
} }
/** /**
* Get the first tour matching the current page URL. * Get all tours for the current page URL.
* *
* @param bool $reset Forcibly update the current tour * @param bool $reset Forcibly update the current tours
* @return tour * @return array
*/ */
public static function get_current_tour($reset = false) { public static function get_current_tours($reset = false): array {
global $PAGE; global $PAGE;
static $tour = false; static $tours = false;
if ($tour === false || $reset) { if ($tours === false || $reset) {
$tour = self::get_matching_tours($PAGE->url); $tours = self::get_matching_tours($PAGE->url);
} }
return $tour; return $tours;
} }
/** /**
* Get the first tour matching the specified URL. * Get all tours matching the specified URL.
* *
* @param moodle_url $pageurl The URL to match. * @param moodle_url $pageurl The URL to match.
* @return tour * @return array
*/ */
public static function get_matching_tours(\moodle_url $pageurl) { public static function get_matching_tours(\moodle_url $pageurl): array {
global $PAGE; global $PAGE;
$tours = cache::get_matching_tourdata($pageurl); $tours = cache::get_matching_tourdata($pageurl);
$matches = [];
$filters = helper::get_all_filters();
foreach ($tours as $record) { foreach ($tours as $record) {
$tour = tour::load_from_record($record); $tour = tour::load_from_record($record);
if ($tour->is_enabled() && $tour->matches_all_filters($PAGE->context)) { if ($tour->is_enabled() && $tour->matches_all_filters($PAGE->context, $filters)) {
return $tour; $matches[] = $tour;
} }
} }
return null; return $matches;
} }
/** /**

View File

@ -24,6 +24,8 @@
namespace tool_usertours; namespace tool_usertours;
use tool_usertours\local\clientside_filter\clientside_filter;
defined('MOODLE_INTERNAL') || die(); defined('MOODLE_INTERNAL') || die();
/** /**
@ -769,11 +771,14 @@ class tour {
/** /**
* Check whether this tour matches all filters. * Check whether this tour matches all filters.
* *
* @param context $context The context to check * @param \context $context The context to check.
* @param array|null $filters Optional array of filters.
* @return bool * @return bool
*/ */
public function matches_all_filters(\context $context) { public function matches_all_filters(\context $context, array $filters = null): bool {
$filters = helper::get_all_filters(); if (!$filters) {
$filters = helper::get_all_filters();
}
// All filters must match. // All filters must match.
// If any one filter fails to match, we return false. // If any one filter fails to match, we return false.
@ -785,4 +790,20 @@ class tour {
return true; return true;
} }
/**
* Gets all filter values for use in client side filters.
*
* @param array $filters Array of clientside filters.
* @return array
*/
public function get_client_filter_values(array $filters): array {
$results = [];
foreach ($filters as $filter) {
$results[$filter::get_filter_name()] = $filter::get_client_side_values($this);
}
return $results;
}
} }

View File

@ -63,6 +63,8 @@ $string['filter_course'] = 'Courses';
$string['filter_course_help'] = 'Show the tour on a page that is associated with the selected course.'; $string['filter_course_help'] = 'Show the tour on a page that is associated with the selected course.';
$string['filter_courseformat'] = 'Course format'; $string['filter_courseformat'] = 'Course format';
$string['filter_courseformat_help'] = 'Show the tour on a page that is associated with a course using the selected course format.'; $string['filter_courseformat_help'] = 'Show the tour on a page that is associated with a course using the selected course format.';
$string['filter_cssselector'] = 'CSS selector';
$string['filter_cssselector_help'] = 'Only show the tour when the specified CSS selector is found on the page.';
$string['filter_header'] = 'Tour filters'; $string['filter_header'] = 'Tour filters';
$string['filter_help'] = 'Select the conditions under which the tour will be shown. All of the filters must match for a tour to be shown to a user.'; $string['filter_help'] = 'Select the conditions under which the tour will be shown. All of the filters must match for a tour to be shown to a user.';
$string['filter_date_account_creation'] = 'User account creation date within'; $string['filter_date_account_creation'] = 'User account creation date within';

View File

@ -142,3 +142,88 @@ Feature: Apply tour filters to a tour
When I am on "Course 2" course homepage When I am on "Course 2" course homepage
And I wait until the page is ready And I wait until the page is ready
Then I should not see "Welcome to your course tour." Then I should not see "Welcome to your course tour."
@javascript
Scenario: Add tours with CSS selectors
Given the following "users" exist:
| username | firstname | lastname | email |
| student1 | Student | 1 | student1@example.com |
Given the following "courses" exist:
| fullname | shortname | format | enablecompletion |
| Course 1 | C1 | topics | 1 |
| Course 2 | C2 | topics | 1 |
And I log in as "admin"
And I am on "Course 1" course homepage with editing mode on
And I add a "Wiki" to section "1" and I fill the form with:
| Wiki name | Test wiki name |
| Description | Test wiki description |
| First page name | First page |
| Wiki mode | Collaborative wiki |
And I am on "Course 2" course homepage
And I add a "Forum" to section "1" and I fill the form with:
| Forum name | Test forum name |
| Forum type | Standard forum for general use |
| Description | Test forum description |
And I add a new user tour with:
| Name | Wiki tour |
| Description | A tour with both matches |
| Apply to URL match | /course/view.php% |
| Tour is enabled | 1 |
| CSS selector | .modtype_wiki |
And I add steps to the "Wiki tour" tour:
| targettype | Title | Content |
| Display in middle of page | Welcome | Welcome to the Wiki tour |
And I add a new user tour with:
| Name | Forum tour |
| Description | A tour with both matches |
| Apply to URL match | /course/view.php% |
| Tour is enabled | 1 |
| CSS selector | .modtype_forum |
And I add steps to the "Forum tour" tour:
| targettype | Title | Content |
| Display in middle of page | Welcome | Welcome to the Forum tour |
And I am on "Course 1" course homepage
Then I should see "Welcome to the Wiki tour"
And I am on "Course 2" course homepage
Then I should see "Welcome to the Forum tour"
@javascript
Scenario: Check filtering respects the sort order
Given the following "users" exist:
| username | firstname | lastname | email |
| student1 | Student | 1 | student1@example.com |
And I log in as "admin"
And I add a new user tour with:
| Name | First tour |
| Description | The first tour |
| Apply to URL match | /my/% |
| Tour is enabled | 1 |
| CSS selector | #page-my-index |
And I add steps to the "First tour" tour:
| targettype | Title | Content |
| Display in middle of page | Welcome | Welcome to the First tour |
And I add a new user tour with:
| Name | Second tour |
| Description | The second tour |
| Apply to URL match | /my/% |
| Tour is enabled | 0 |
| CSS selector | #page-my-index |
And I add steps to the "Second tour" tour:
| targettype | Title | Content |
| Display in middle of page | Welcome | Welcome to the Second tour |
And I add a new user tour with:
| Name | Third tour |
| Description | The third tour |
| Apply to URL match | /my/% |
| Tour is enabled | 1 |
| CSS selector | #page-my-index |
And I add steps to the "Third tour" tour:
| targettype | Title | Content |
| Display in middle of page | Welcome | Welcome to the Third tour |
And I am on homepage
Then I should see "Welcome to the First tour"
And I open the User tour settings page
And I click on "Move tour down" "link" in the "The first tour" "table_row"
And I click on "Move tour down" "link" in the "The first tour" "table_row"
And I am on homepage
Then I should see "Welcome to the Third tour"

View File

@ -222,6 +222,13 @@ class tool_usertours_manager_testcase extends advanced_testcase {
'description' => '', 'description' => '',
'configdata' => '', 'configdata' => '',
], ],
[
'pathmatch' => '/my/%',
'enabled' => true,
'name' => 'My tour enabled 2',
'description' => '',
'configdata' => '',
],
[ [
'pathmatch' => '/my/%', 'pathmatch' => '/my/%',
'enabled' => false, 'enabled' => false,
@ -277,32 +284,32 @@ class tool_usertours_manager_testcase extends advanced_testcase {
'No matches found' => [ 'No matches found' => [
$alltours, $alltours,
$CFG->wwwroot . '/some/invalid/value', $CFG->wwwroot . '/some/invalid/value',
null, [],
], ],
'Never return a disabled tour' => [ 'Never return a disabled tour' => [
$alltours, $alltours,
$CFG->wwwroot . '/my/index.php', $CFG->wwwroot . '/my/index.php',
'My tour enabled', ['My tour enabled', 'My tour enabled 2'],
], ],
'My not course' => [ 'My not course' => [
$alltours, $alltours,
$CFG->wwwroot . '/my/index.php', $CFG->wwwroot . '/my/index.php',
'My tour enabled', ['My tour enabled', 'My tour enabled 2'],
], ],
'My with params' => [ 'My with params' => [
$alltours, $alltours,
$CFG->wwwroot . '/my/index.php?id=42', $CFG->wwwroot . '/my/index.php?id=42',
'My tour enabled', ['My tour enabled', 'My tour enabled 2'],
], ],
'Course with params' => [ 'Course with params' => [
$alltours, $alltours,
$CFG->wwwroot . '/course/?id=42', $CFG->wwwroot . '/course/?id=42',
'course tour enabled', ['course tour enabled'],
], ],
'Course with params and trailing content' => [ 'Course with params and trailing content' => [
$alltours, $alltours,
$CFG->wwwroot . '/course/?id=42&foo=bar', $CFG->wwwroot . '/course/?id=42&foo=bar',
'course tour with additional params enabled', ['course tour with additional params enabled', 'course tour enabled'],
], ],
]; ];
} }
@ -311,11 +318,11 @@ class tool_usertours_manager_testcase extends advanced_testcase {
* Tests for the get_matching_tours function. * Tests for the get_matching_tours function.
* *
* @dataProvider get_matching_tours_provider * @dataProvider get_matching_tours_provider
* @param array $alltours The list of tours to insert * @param array $alltours The list of tours to insert.
* @param string $url The URL to test * @param string $url The URL to test.
* @param string $expected The name of the expected matching tour * @param array $expected List of names of the expected matching tours.
*/ */
public function test_get_matching_tours($alltours, $url, $expected) { public function test_get_matching_tours(array $alltours, string $url, array $expected) {
$this->resetAfterTest(); $this->resetAfterTest();
foreach ($alltours as $tourconfig) { foreach ($alltours as $tourconfig) {
@ -323,12 +330,10 @@ class tool_usertours_manager_testcase extends advanced_testcase {
$this->helper_create_step((object) ['tourid' => $tour->get_id()]); $this->helper_create_step((object) ['tourid' => $tour->get_id()]);
} }
$match = \tool_usertours\manager::get_matching_tours(new moodle_url($url)); $matches = \tool_usertours\manager::get_matching_tours(new moodle_url($url));
if ($expected === null) { $this->assertEquals(count($expected), count($matches));
$this->assertNull($match); for ($i = 0; $i < count($matches); $i++) {
} else { $this->assertEquals($expected[$i], $matches[$i]->get_name());
$this->assertNotNull($match);
$this->assertEquals($expected, $match->get_name());
} }
} }
} }

View File

@ -24,6 +24,6 @@
defined('MOODLE_INTERNAL') || die(); defined('MOODLE_INTERNAL') || die();
$plugin->version = 2021052501; // The current module version (Date: YYYYMMDDXX). $plugin->version = 2021052502; // The current module version (Date: YYYYMMDDXX).
$plugin->requires = 2021052500; // Requires this Moodle version. $plugin->requires = 2021052500; // Requires this Moodle version.
$plugin->component = 'tool_usertours'; // Full name of the plugin (used for diagnostics). $plugin->component = 'tool_usertours'; // Full name of the plugin (used for diagnostics).