Merge branch 'MDL-52777-master' of git://github.com/andrewnicols/moodle

This commit is contained in:
David Monllao
2016-10-19 09:59:27 +08:00
79 changed files with 12511 additions and 1 deletions
+2
View File
@@ -3,6 +3,8 @@
*/**/build/
node_modules/
vendor/
admin/tool/usertours/amd/src/tour.js
admin/tool/usertours/amd/src/popper.js
auth/cas/CAS/
auth/fc/fcFPP.php
enrol/lti/ims-blti/
+2
View File
@@ -4,6 +4,8 @@ theme/clean/style/custom.css
theme/more/style/custom.css
node_modules/
vendor/
admin/tool/usertours/amd/src/tour.js
admin/tool/usertours/amd/src/popper.js
auth/cas/CAS/
auth/fc/fcFPP.php
enrol/lti/ims-blti/
+1
View File
@@ -0,0 +1 @@
define(["jquery","core/str","core/notification"],function(a,b,c){var d={removeStep:function(d){d.preventDefault(),b.get_strings([{key:"confirmstepremovaltitle",component:"tool_usertours"},{key:"confirmstepremovalquestion",component:"tool_usertours"},{key:"yes",component:"moodle"},{key:"no",component:"moodle"}]).done(function(b){c.confirm(b[0],b[1],b[2],b[3],a.proxy(function(){window.location=a(this).attr("href")},d.currentTarget))})},setup:function(){a("body").delegate('[data-action="delete"]',"click",d.removeStep)}};return{setup:d.setup}});
+1
View File
@@ -0,0 +1 @@
define(["jquery","core/ajax","core/str","core/notification"],function(a,b,c,d){var e={removeTour:function(b){b.preventDefault(),c.get_strings([{key:"confirmtourremovaltitle",component:"tool_usertours"},{key:"confirmtourremovalquestion",component:"tool_usertours"},{key:"yes",component:"moodle"},{key:"no",component:"moodle"}]).done(function(c){d.confirm(c[0],c[1],c[2],c[3],a.proxy(function(){window.location=a(this).attr("href")},b.currentTarget))})},setup:function(){a("body").delegate('[data-action="delete"]',"click",e.removeTour)}};return{setup:e.setup}});
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
define(["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(a,b,d){h.tourId=a,h.context=d,"undefined"==typeof b&&(b=!0),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(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){h.startBootstrapTour(b,c[0],a.tourconfig)}).fail(g.exception)},addResetLink:function(){e.get_string("resettouronpage","tool_usertours").done(function(a){c("footer, .logininfo").last().append('<div class="usertour"><a href="#" data-action="tool_usertours/resetpagetour">'+a+"</a></div>")})},startBootstrapTour:function(a,c,d){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){return"undefined"!=typeof a.element&&(a.target=a.element,delete a.element),"undefined"!=typeof a.reflex&&(a.moveOnClick=!!a.reflex,delete a.reflex),"undefined"!=typeof a.content&&(a.body=a.content,delete a.content),a}),h.currentTour=new b(d),h.currentTour.startTour()},markStepShown:function(){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(){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(b){c.when(a.call([{methodname:"tool_usertours_reset_tour",args:{tourid:b,context:h.context,pageurl:window.location.href}}])[0]).then(function(a){a.startTour&&h.fetchTour(a.startTour)}).fail(g.exception)}};return{init:h.init,resetTourState:h.resetTourState}});
@@ -0,0 +1,17 @@
Description of External library imports into Moodle
Flexitour Instructions
----------------------
1. Clone https://github.com/andrewnicols/flexitour into an unrelated directory
2. Copy /build/tour.js to amd/src/tour.js
3. Open the amd/src/tour.js file and find the AMD module define.
4. Change the "popper" inclusion to "./popper"
5. Update thirdpartylibs.xml
6. Run `grunt amd`
Popper.js Instructions
----------------------
1. Clone https://github.com/FezVrasta/popper.js into an unrelated directory
2. Copy /build/popper.js to amd/src/popper.js
3. Update thirdpartylibs.xml
4. Run `grunt amd`
@@ -0,0 +1,64 @@
/**
* Step management code.
*
* @module tool_usertours/managesteps
* @class managesteps
* @package tool_usertours
* @copyright 2016 Andrew Nicols <[email protected]>
*/
define(
['jquery', 'core/str', 'core/notification'],
function($, str, notification) {
var manager = {
/**
* Confirm removal of the specified step.
*
* @method removeStep
* @param {EventFacade} e The EventFacade
*/
removeStep: function(e) {
e.preventDefault();
str.get_strings([
{
key: 'confirmstepremovaltitle',
component: 'tool_usertours'
},
{
key: 'confirmstepremovalquestion',
component: 'tool_usertours'
},
{
key: 'yes',
component: 'moodle'
},
{
key: 'no',
component: 'moodle'
}
]).done(function(s) {
notification.confirm(s[0], s[1], s[2], s[3], $.proxy(function() {
window.location = $(this).attr('href');
}, e.currentTarget));
});
},
/**
* Setup the step management UI.
*
* @method setup
*/
setup: function() {
$('body').delegate('[data-action="delete"]', 'click', manager.removeStep);
}
};
return /** @alias module:tool_usertours/managesteps */ {
/**
* Setup the step management UI.
*
* @method setup
*/
setup: manager.setup
};
});
@@ -0,0 +1,64 @@
/**
* Tour management code.
*
* @module tool_usertours/managetours
* @class managetours
* @package tool_usertours
* @copyright 2016 Andrew Nicols <[email protected]>
*/
define(
['jquery', 'core/ajax', 'core/str', 'core/notification'],
function($, ajax, str, notification) {
var manager = {
/**
* Confirm removal of the specified tour.
*
* @method removeTour
* @param {EventFacade} e The EventFacade
*/
removeTour: function(e) {
e.preventDefault();
str.get_strings([
{
key: 'confirmtourremovaltitle',
component: 'tool_usertours'
},
{
key: 'confirmtourremovalquestion',
component: 'tool_usertours'
},
{
key: 'yes',
component: 'moodle'
},
{
key: 'no',
component: 'moodle'
}
]).done(function(s) {
notification.confirm(s[0], s[1], s[2], s[3], $.proxy(function() {
window.location = $(this).attr('href');
}, e.currentTarget));
});
},
/**
* Setup the tour management UI.
*
* @method setup
*/
setup: function() {
$('body').delegate('[data-action="delete"]', 'click', manager.removeTour);
}
};
return /** @alias module:tool_usertours/managetours */ {
/**
* Setup the tour management UI.
*
* @method setup
*/
setup: manager.setup
};
});
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+238
View File
@@ -0,0 +1,238 @@
/**
* User tour control library.
*
* @module tool_usertours/usertours
* @class usertours
* @package tool_usertours
* @copyright 2016 Andrew Nicols <[email protected]>
*/
define(
['core/ajax', 'tool_usertours/tour', 'jquery', 'core/templates', 'core/str', 'core/log', 'core/notification'],
function(ajax, BootstrapTour, $, templates, str, log, notification) {
var usertours = {
tourId: null,
currentTour: null,
context: null,
/**
* Initialise the user tour for the current page.
*
* @method init
* @param {Number} tourId The ID of the tour to start.
* @param {Bool} startTour Attempt to start the tour now.
* @param {Number} context The context of the current page.
*/
init: function(tourId, startTour, context) {
// Only one tour per page is allowed.
usertours.tourId = tourId;
usertours.context = context;
if (typeof startTour === 'undefined') {
startTour = true;
}
if (startTour) {
// Fetch the tour configuration.
usertours.fetchTour(tourId);
}
usertours.addResetLink();
// Watch for the reset link.
$('body').on('click', '[data-action="tool_usertours/resetpagetour"]', function(e) {
e.preventDefault();
usertours.resetTourState(usertours.tourId);
});
},
/**
* Fetch the configuration specified tour, and start the tour when it has been fetched.
*
* @method fetchTour
* @param {Number} tourId The ID of the tour to start.
*/
fetchTour: function(tourId) {
$.when(
ajax.call([
{
methodname: 'tool_usertours_fetch_and_start_tour',
args: {
tourid: tourId,
context: usertours.context,
pageurl: window.location.href,
}
}
])[0],
templates.render('tool_usertours/tourstep', {})
).then(function(response, template) {
usertours.startBootstrapTour(tourId, template[0], response.tourconfig);
}).fail(notification.exception);
},
/**
* Add a reset link to the page.
*
* @method addResetLink
*/
addResetLink: function() {
str.get_string('resettouronpage', 'tool_usertours')
.done(function(s) {
// Grab the last item in the page of these.
$('footer, .logininfo')
.last()
.append(
'<div class="usertour">' +
'<a href="#" data-action="tool_usertours/resetpagetour">' +
s +
'</a>' +
'</div>'
);
});
},
/**
* Start the specified tour.
*
* @method startBootstrapTour
* @param {Number} tourId The ID of the tour to start.
* @param {String} template The template to use.
* @param {Object} tourConfig The tour configuration.
*/
startBootstrapTour: function(tourId, template, tourConfig) {
if (usertours.currentTour) {
// End the current tour, but disable end tour handler.
tourConfig.onEnd = null;
usertours.currentTour.endTour();
delete usertours.currentTour;
}
// Normalize for the new library.
tourConfig.eventHandlers = {
afterEnd: [usertours.markTourComplete],
afterRender: [usertours.markStepShown],
};
// Sort out the tour name.
tourConfig.tourName = tourConfig.name;
delete tourConfig.name;
// Add the template to the configuration.
// This enables translations of the buttons.
tourConfig.template = template;
tourConfig.steps = tourConfig.steps.map(function(step) {
if (typeof step.element !== 'undefined') {
step.target = step.element;
delete step.element;
}
if (typeof step.reflex !== 'undefined') {
step.moveOnClick = !!step.reflex;
delete step.reflex;
}
if (typeof step.content !== 'undefined') {
step.body = step.content;
delete step.content;
}
return step;
});
usertours.currentTour = new BootstrapTour(tourConfig);
usertours.currentTour.startTour();
},
/**
* Mark the specified step as being shownd by the user.
*
* @method markStepShown
*/
markStepShown: function() {
var stepConfig = this.getStepConfig(this.getCurrentStepNumber());
$.when(
ajax.call([
{
methodname: 'tool_usertours_step_shown',
args: {
tourid: usertours.tourId,
context: usertours.context,
pageurl: window.location.href,
stepid: stepConfig.stepid,
stepindex: this.getCurrentStepNumber(),
}
}
])[0]
).fail(log.error);
},
/**
* Mark the specified tour as being completed by the user.
*
* @method markTourComplete
*/
markTourComplete: function() {
var stepConfig = this.getStepConfig(this.getCurrentStepNumber());
$.when(
ajax.call([
{
methodname: 'tool_usertours_complete_tour',
args: {
tourid: usertours.tourId,
context: usertours.context,
pageurl: window.location.href,
stepid: stepConfig.stepid,
stepindex: this.getCurrentStepNumber(),
}
}
])[0]
).fail(log.error);
},
/**
* Reset the state, and restart the the tour on the current page.
*
* @method resetTourState
* @param {Number} tourId The ID of the tour to start.
*/
resetTourState: function(tourId) {
$.when(
ajax.call([
{
methodname: 'tool_usertours_reset_tour',
args: {
tourid: tourId,
context: usertours.context,
pageurl: window.location.href,
}
}
])[0]
).then(function(response) {
if (response.startTour) {
usertours.fetchTour(response.startTour);
}
}).fail(notification.exception);
}
};
return /** @alias module:tool_usertours/usertours */ {
/**
* Initialise the user tour for the current page.
*
* @method init
* @param {Number} tourId The ID of the tour to start.
* @param {Bool} startTour Attempt to start the tour now.
*/
init: usertours.init,
/**
* Reset the state, and restart the the tour on the current page.
*
* @method resetTourState
* @param {Number} tourId The ID of the tour to restart.
*/
resetTourState: usertours.resetTourState
};
});
@@ -0,0 +1,118 @@
<?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/>.
/**
* Step configuration detail class.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_usertours;
defined('MOODLE_INTERNAL') || die();
/**
* Step configuration detail class.
*
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class configuration {
/**
* @var TOURDEFAULT
*/
const TOURDEFAULT = 'usetourdefault';
/**
* Get the list of keys which can be defaulted in the tour.
*
* @return array
*/
public static function get_defaultable_keys() {
return [
'placement',
'orphan',
'backdrop',
'reflex',
];
}
/**
* Get the default value for the specified key.
*
* @param string $key The key for the specified value
* @return mixed
*/
public static function get_default_value($key) {
switch($key) {
case 'placement':
return 'bottom';
case 'orphan':
case 'backdrop':
case 'reflex':
return false;
}
}
/**
* Get the default value for the specified key for the step form.
*
* @param string $key The key for the specified value
* @return mixed
*/
public static function get_step_default_value($key) {
switch($key) {
case 'placement':
case 'orphan':
case 'backdrop':
case 'reflex':
return self::TOURDEFAULT;
}
}
/**
* Get the list of possible placement options.
*
* @param string $default The default option.
* @return array
*/
public static function get_placement_options($default = null) {
$values = [
'top' => get_string('top', 'tool_usertours'),
'bottom' => get_string('bottom', 'tool_usertours'),
'left' => get_string('left', 'tool_usertours'),
'right' => get_string('right', 'tool_usertours'),
];
if ($default === null) {
return $values;
}
if (!isset($values[$default])) {
$default = self::get_default_value('placement');
}
$values = array_reverse($values, true);
$values[self::TOURDEFAULT] = get_string('defaultvalue', 'tool_usertours', $values[$default]);
$values = array_reverse($values, true);
return $values;
}
}
@@ -0,0 +1,146 @@
<?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/>.
/**
* The tool_usertours step_shown event.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_usertours\event;
defined('MOODLE_INTERNAL') || die();
/**
* The tool_usertours step_shown event.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*
* @property-read array $other {
* Extra information about the event.
*
* - int tourid: The id of the tour
* - string pageurl: The URL of the page viewing the tour
* }
*/
class step_shown extends \core\event\base {
/**
* Init method.
*/
protected function init() {
$this->data['crud'] = 'r';
$this->data['edulevel'] = self::LEVEL_PARTICIPATING;
$this->data['objecttable'] = 'tool_usertours_steps';
}
/**
* Returns localised general event name.
*
* @return string
*/
public static function get_name() {
return get_string('event_step_shown', 'tool_usertours');
}
/**
* Custom validation.
*
* @throws \coding_exception
* @return void
*/
protected function validate_data() {
parent::validate_data();
if (!isset($this->other['tourid'])) {
throw new \coding_exception('The \'tourid\' value must be set in other.');
}
if (!isset($this->other['stepindex'])) {
throw new \coding_exception('The \'stepindex\' value must be set in other.');
}
if (!isset($this->other['pageurl'])) {
throw new \coding_exception('The \'pageurl\' value must be set in other.');
}
}
/**
* This is used when restoring course logs where it is required that we
* map the information in 'other' to it's new value in the new course.
*
* Does nothing in the base class except display a debugging message warning
* the user that the event does not contain the required functionality to
* map this information. For events that do not store any other information this
* won't be called, so no debugging message will be displayed.
*
* @return array an array of other values and their corresponding mapping
*/
public static function get_other_mapping() {
return [
'pageurl' => \core\event\base::NOT_MAPPED,
'tourid' => [
'db' => 'tool_usertours_tours',
'restore' => \core\event\base::NOT_MAPPED,
],
'stepindex' => \core\event\base::NOT_MAPPED,
];
}
/**
* This is used when restoring course logs where it is required that we
* map the objectid to it's new value in the new course.
*
* Does nothing in the base class except display a debugging message warning
* the user that the event does not contain the required functionality to
* map this information. For events that do not store an objectid this won't
* be called, so no debugging message will be displayed.
*
* @return string the name of the restore mapping the objectid links to
*/
public static function get_objectid_mapping() {
return [
'db' => 'tool_usertours_steps',
'restore' => \core\event\base::NOT_MAPPED,
];
}
/**
* Returns non-localised event description with id's for admin use only.
*
* @return string
*/
public function get_description() {
return "The user with id '{$this->userid}' has viewed the tour with id " .
"'{$this->other['tourid']}' at step index " .
"'{$this->other['stepindex']}' (id '{$this->objectid}') on the page with URL " .
"'{$this->other['pageurl']}'.";
}
/**
* Returns relevant URL.
*
* @return \moodle_url
*/
public function get_url() {
return \tool_usertours\helper::get_edit_step_link($this->other['tourid'], $this->objectid);
}
}
@@ -0,0 +1,145 @@
<?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/>.
/**
* The tool_usertours tour_ended event.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_usertours\event;
defined('MOODLE_INTERNAL') || die();
/**
* The tool_usertours tour_ended event.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*
* @property-read array $other {
* Extra information about the event.
*
* - int tourid: The id of the tour
* - string pageurl: The URL of the page viewing the tour
* }
*/
class tour_ended extends \core\event\base {
/**
* Init method.
*/
protected function init() {
$this->data['crud'] = 'c';
$this->data['edulevel'] = self::LEVEL_PARTICIPATING;
$this->data['objecttable'] = 'tool_usertours_tours';
}
/**
* Returns localised general event name.
*
* @return string
*/
public static function get_name() {
return get_string('event_tour_ended', 'tool_usertours');
}
/**
* Custom validation.
*
* @throws \coding_exception
* @return void
*/
protected function validate_data() {
parent::validate_data();
if (!isset($this->other['stepindex'])) {
throw new \coding_exception('The \'stepindex\' value must be set in other.');
}
if (!isset($this->other['stepid'])) {
throw new \coding_exception('The \'stepid\' value must be set in other.');
}
if (!isset($this->other['pageurl'])) {
throw new \coding_exception('The \'pageurl\' value must be set in other.');
}
}
/**
* This is used when restoring course logs where it is required that we
* map the information in 'other' to it's new value in the new course.
*
* Does nothing in the base class except display a debugging message warning
* the user that the event does not contain the required functionality to
* map this information. For events that do not store any other information this
* won't be called, so no debugging message will be displayed.
*
* @return array an array of other values and their corresponding mapping
*/
public static function get_other_mapping() {
return [
'stepindex' => \core\event\base::NOT_MAPPED,
'stepid' => [
'db' => 'tool_usertours_steps',
'restore' => \core\event\base::NOT_MAPPED,
],
'pageurl' => \core\event\base::NOT_MAPPED,
];
}
/**
* This is used when restoring course logs where it is required that we
* map the objectid to it's new value in the new course.
*
* Does nothing in the base class except display a debugging message warning
* the user that the event does not contain the required functionality to
* map this information. For events that do not store an objectid this won't
* be called, so no debugging message will be displayed.
*
* @return string the name of the restore mapping the objectid links to
*/
public static function get_objectid_mapping() {
return [
'db' => 'tool_usertours_tours',
'restore' => \core\event\base::NOT_MAPPED,
];
}
/**
* Returns non-localised event description with id's for admin use only.
*
* @return string
*/
public function get_description() {
return "The user with id '{$this->userid}' has ended the tour with id " .
"'{$this->objectid}' at step index " .
"'{$this->other['stepindex']}' (id '{$this->other['stepid']}') on the page with URL " .
"'{$this->other['pageurl']}'.";
}
/**
* Returns relevant URL.
*
* @return \moodle_url
*/
public function get_url() {
return \tool_usertours\helper::get_view_tour_link($this->objectid);
}
}
@@ -0,0 +1,132 @@
<?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/>.
/**
* The tool_usertours tour_reset event.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_usertours\event;
defined('MOODLE_INTERNAL') || die();
/**
* The tool_usertours tour_reset event.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*
* @property-read array $other {
* Extra information about the event.
*
* - int tourid: The id of the tour
* - string pageurl: The URL of the page viewing the tour
* }
*/
class tour_reset extends \core\event\base {
/**
* Init method.
*/
protected function init() {
$this->data['crud'] = 'c';
$this->data['edulevel'] = self::LEVEL_PARTICIPATING;
$this->data['objecttable'] = 'tool_usertours_tours';
}
/**
* Returns localised general event name.
*
* @return string
*/
public static function get_name() {
return get_string('event_tour_reset', 'tool_usertours');
}
/**
* Custom validation.
*
* @throws \coding_exception
* @return void
*/
protected function validate_data() {
parent::validate_data();
if (!isset($this->other['pageurl'])) {
throw new \coding_exception('The \'pageurl\' value must be set in other.');
}
}
/**
* This is used when restoring course logs where it is required that we
* map the information in 'other' to it's new value in the new course.
*
* Does nothing in the base class except display a debugging message warning
* the user that the event does not contain the required functionality to
* map this information. For events that do not store any other information this
* won't be called, so no debugging message will be displayed.
*
* @return array an array of other values and their corresponding mapping
*/
public static function get_other_mapping() {
return [
'pageurl' => \core\event\base::NOT_MAPPED,
];
}
/**
* This is used when restoring course logs where it is required that we
* map the objectid to it's new value in the new course.
*
* Does nothing in the base class except display a debugging message warning
* the user that the event does not contain the required functionality to
* map this information. For events that do not store an objectid this won't
* be called, so no debugging message will be displayed.
*
* @return string the name of the restore mapping the objectid links to
*/
public static function get_objectid_mapping() {
return [
'db' => 'tool_usertours_tours',
'restore' => \core\event\base::NOT_MAPPED,
];
}
/**
* Returns non-localised event description with id's for admin use only.
*
* @return string
*/
public function get_description() {
return "The user with id " .
"'{$this->userid}' has reset the state of tour with id " .
"'{$this->objectid}' on the page with URL " .
"'{$this->other['pageurl']}'.";
}
/**
* Returns relevant URL.
*
* @return \moodle_url
*/
public function get_url() {
return \tool_usertours\helper::get_view_tour_link($this->objectid);
}
}
@@ -0,0 +1,131 @@
<?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/>.
/**
* The tool_usertours tour_started event.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_usertours\event;
defined('MOODLE_INTERNAL') || die();
/**
* The tool_usertours tour_started event.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*
* @property-read array $other {
* Extra information about the event.
*
* - int tourid: The id of the tour
* - string pageurl: The URL of the page viewing the tour
* }
*/
class tour_started extends \core\event\base {
/**
* Init method.
*/
protected function init() {
$this->data['crud'] = 'r';
$this->data['edulevel'] = self::LEVEL_PARTICIPATING;
$this->data['objecttable'] = 'tool_usertours_tours';
}
/**
* Returns localised general event name.
*
* @return string
*/
public static function get_name() {
return get_string('event_tour_started', 'tool_usertours');
}
/**
* Custom validation.
*
* @throws \coding_exception
* @return void
*/
protected function validate_data() {
parent::validate_data();
if (!isset($this->other['pageurl'])) {
throw new \coding_exception('The \'pageurl\' value must be set in other.');
}
}
/**
* This is used when restoring course logs where it is required that we
* map the information in 'other' to it's new value in the new course.
*
* Does nothing in the base class except display a debugging message warning
* the user that the event does not contain the required functionality to
* map this information. For events that do not store any other information this
* won't be called, so no debugging message will be displayed.
*
* @return array an array of other values and their corresponding mapping
*/
public static function get_other_mapping() {
return [
'pageurl' => \core\event\base::NOT_MAPPED,
];
}
/**
* This is used when restoring course logs where it is required that we
* map the objectid to it's new value in the new course.
*
* Does nothing in the base class except display a debugging message warning
* the user that the event does not contain the required functionality to
* map this information. For events that do not store an objectid this won't
* be called, so no debugging message will be displayed.
*
* @return string the name of the restore mapping the objectid links to
*/
public static function get_objectid_mapping() {
return [
'db' => 'tool_usertours_tours',
'restore' => \core\event\base::NOT_MAPPED,
];
}
/**
* Returns non-localised event description with id's for admin use only.
*
* @return string
*/
public function get_description() {
return "The user with id '{$this->userid}' " .
"has started the tour with id '{$this->objectid}' " .
"on the page with URL '{$this->other['pageurl']}'.";
}
/**
* Returns relevant URL.
*
* @return \moodle_url
*/
public function get_url() {
return \tool_usertours\helper::get_view_tour_link($this->objectid);
}
}
+330
View File
@@ -0,0 +1,330 @@
<?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/>.
/**
* Web Service functions for steps.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_usertours\external;
defined('MOODLE_INTERNAL') || die();
use external_api;
use external_function_parameters;
use external_single_structure;
use external_multiple_structure;
use external_value;
use tool_usertours\tour as tourinstance;
use tool_usertours\step;
/**
* Web Service functions for steps.
*
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class tour extends external_api {
/**
* Fetch the tour configuration for the specified tour.
*
* @param int $tourid The ID of the tour to fetch.
* @param int $context The Context ID of the current page.
* @param string $pageurl The path of the current page.
* @return array As described in fetch_and_start_tour_returns
*/
public static function fetch_and_start_tour($tourid, $context, $pageurl) {
global $PAGE;
$params = self::validate_parameters(self::fetch_and_start_tour_parameters(), [
'tourid' => $tourid,
'context' => $context,
'pageurl' => $pageurl,
]);
$context = \context_helper::instance_by_id($params['context']);
self::validate_context($context);
$tour = tourinstance::instance($params['tourid']);
if (!$tour->should_show_for_user()) {
return [];
}
$touroutput = new \tool_usertours\output\tour($tour);
\tool_usertours\event\tour_started::create([
'contextid' => $context->id,
'objectid' => $tourid,
'other' => [
'pageurl' => $pageurl,
],
])->trigger();
return [
'tourconfig' => $touroutput->export_for_template($PAGE->get_renderer('core')),
];
}
/**
* The parameters for fetch_and_start_tour.
*
* @return external_function_parameters
*/
public static function fetch_and_start_tour_parameters() {
return new external_function_parameters([
'tourid' => new external_value(PARAM_INT, 'Tour ID'),
'context' => new external_value(PARAM_INT, 'Context ID'),
'pageurl' => new external_value(PARAM_URL, 'Page URL'),
]);
}
/**
* The return configuration for fetch_and_start_tour.
*
* @return external_single_structure
*/
public static function fetch_and_start_tour_returns() {
return new external_single_structure([
'tourconfig' => new external_single_structure([
'name' => new external_value(PARAM_RAW, 'Tour Name'),
'steps' => new external_multiple_structure(self::step_structure_returns()),
])
]);
}
/**
* Reset the specified tour for the current user.
*
* @param int $tourid The ID of the tour.
* @param int $context The Context ID of the current page.
* @param string $pageurl The path of the current page requesting the reset.
* @return array As described in reset_tour_returns
*/
public static function reset_tour($tourid, $context, $pageurl) {
$params = self::validate_parameters(self::reset_tour_parameters(), [
'tourid' => $tourid,
'context' => $context,
'pageurl' => $pageurl,
]);
$context = \context_helper::instance_by_id($params['context']);
self::validate_context($context);
$tour = tourinstance::instance($params['tourid']);
$tour->request_user_reset();
$result = [];
if ($tourinstance = \tool_usertours\manager::get_matching_tours(new \moodle_url($params['pageurl']))) {
if ($tour->get_id() === $tourinstance->get_id()) {
$result['startTour'] = $tour->get_id();
\tool_usertours\event\tour_reset::create([
'contextid' => $context->id,
'objectid' => $params['tourid'],
'other' => [
'pageurl' => $params['pageurl'],
],
])->trigger();
}
}
return $result;
}
/**
* The parameters for reset_tour.
*
* @return external_function_parameters
*/
public static function reset_tour_parameters() {
return new external_function_parameters([
'tourid' => new external_value(PARAM_INT, 'Tour ID'),
'context' => new external_value(PARAM_INT, 'Context ID'),
'pageurl' => new external_value(PARAM_URL, 'Current page location'),
]);
}
/**
* The return configuration for reset_tour.
*
* @return external_single_structure
*/
public static function reset_tour_returns() {
return new external_single_structure([
'startTour' => new external_value(PARAM_INT, 'Tour ID', VALUE_OPTIONAL),
]);
}
/**
* Mark the specified tour as completed for the current user.
*
* @param int $tourid The ID of the tour.
* @param int $context The Context ID of the current page.
* @param string $pageurl The path of the current page.
* @param int $stepid The step id
* @param int $stepindex The step index
* @return array As described in complete_tour_returns
*/
public static function complete_tour($tourid, $context, $pageurl, $stepid, $stepindex) {
$params = self::validate_parameters(self::complete_tour_parameters(), [
'tourid' => $tourid,
'context' => $context,
'pageurl' => $pageurl,
'stepid' => $stepid,
'stepindex' => $stepindex,
]);
$context = \context_helper::instance_by_id($params['context']);
self::validate_context($context);
$tour = tourinstance::instance($params['tourid']);
$tour->mark_user_completed();
\tool_usertours\event\tour_ended::create([
'contextid' => $context->id,
'objectid' => $params['tourid'],
'other' => [
'pageurl' => $params['pageurl'],
'stepid' => $params['stepid'],
'stepindex' => $params['stepindex'],
],
])->trigger();
return [];
}
/**
* The parameters for complete_tour.
*
* @return external_function_parameters
*/
public static function complete_tour_parameters() {
return new external_function_parameters([
'tourid' => new external_value(PARAM_INT, 'Tour ID'),
'context' => new external_value(PARAM_INT, 'Context ID'),
'pageurl' => new external_value(PARAM_LOCALURL, 'Page URL'),
'stepid' => new external_value(PARAM_INT, 'Step ID'),
'stepindex' => new external_value(PARAM_INT, 'Step Number'),
]);
}
/**
* The return configuration for complete_tour.
*
* @return external_single_structure
*/
public static function complete_tour_returns() {
return new external_single_structure([]);
}
/**
* Mark the specified toru step as shown for the current user.
*
* @param int $tourid The ID of the tour.
* @param int $context The Context ID of the current page.
* @param string $pageurl The path of the current page.
* @param int $stepid The step id
* @param int $stepindex The step index
* @return array As described in complete_tour_returns
*/
public static function step_shown($tourid, $context, $pageurl, $stepid, $stepindex) {
$params = self::validate_parameters(self::step_shown_parameters(), [
'tourid' => $tourid,
'context' => $context,
'pageurl' => $pageurl,
'stepid' => $stepid,
'stepindex' => $stepindex,
]);
$context = \context_helper::instance_by_id($params['context']);
self::validate_context($context);
$step = step::instance($params['stepid']);
if ($step->get_tourid() !== $params['tourid']) {
throw new \moodle_exception('Incorrect tour specified.');
}
\tool_usertours\event\step_shown::create([
'contextid' => $context->id,
'objectid' => $params['stepid'],
'other' => [
'pageurl' => $params['pageurl'],
'tourid' => $params['tourid'],
'stepindex' => $params['stepindex'],
],
])->trigger();
return [];
}
/**
* The parameters for step_shown.
*
* @return external_function_parameters
*/
public static function step_shown_parameters() {
return new external_function_parameters([
'tourid' => new external_value(PARAM_INT, 'Tour ID'),
'context' => new external_value(PARAM_INT, 'Context ID'),
'pageurl' => new external_value(PARAM_URL, 'Page URL'),
'stepid' => new external_value(PARAM_INT, 'Step ID'),
'stepindex' => new external_value(PARAM_INT, 'Step Number'),
]);
}
/**
* The return configuration for step_shown.
*
* @return external_single_structure
*/
public static function step_shown_returns() {
return new external_single_structure([]);
}
/**
* The standard return structure for a step.
*
* @return external_multiple_structure
*/
public static function step_structure_returns() {
return new external_single_structure([
'title' => new external_value(PARAM_RAW,
'Step Title'),
'content' => new external_value(PARAM_RAW,
'Step Content'),
'element' => new external_value(PARAM_TEXT,
'Step Target'),
'placement' => new external_value(PARAM_TEXT,
'Step Placement'),
'delay' => new external_value(PARAM_INT,
'Delay before showing the step (ms)', VALUE_OPTIONAL),
'backdrop' => new external_value(PARAM_BOOL,
'Whether a backdrop should be used', VALUE_OPTIONAL),
'reflex' => new external_value(PARAM_BOOL,
'Whether to move to the next step when the target element is clicked', VALUE_OPTIONAL),
'orphan' => new external_value(PARAM_BOOL,
'Whether to display the step even if it could not be found', VALUE_OPTIONAL),
'stepid' => new external_value(PARAM_INT,
'The actual ID of the step', VALUE_OPTIONAL),
]);
}
}
+523
View File
@@ -0,0 +1,523 @@
<?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 helper.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_usertours;
defined('MOODLE_INTERNAL') || die();
/**
* Tour helper.
*
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class helper {
/**
* @var MOVE_UP
*/
const MOVE_UP = -1;
/**
* @var MOVE_DOWN
*/
const MOVE_DOWN = 1;
/**
* Get the link to edit the step.
*
* If no stepid is specified, then a link to create a new step is provided. The $targettype must be specified in this case.
*
* @param int $tourid The tour that the step belongs to.
* @param int $stepid The step ID.
* @param int $targettype The type of step.
*
* @return moodle_url
*/
public static function get_edit_step_link($tourid, $stepid = null, $targettype = null) {
$link = new \moodle_url('/admin/tool/usertours/configure.php');
if ($stepid) {
$link->param('action', manager::ACTION_EDITSTEP);
$link->param('id', $stepid);
} else {
$link->param('action', manager::ACTION_NEWSTEP);
$link->param('tourid', $tourid);
}
return $link;
}
/**
* Get the link to move the tour.
*
* @param int $tourid The tour ID.
* @param int $direction The direction to move in
*
* @return moodle_url
*/
public static function get_move_tour_link($tourid, $direction = self::MOVE_DOWN) {
$link = new \moodle_url('/admin/tool/usertours/configure.php');
$link->param('action', manager::ACTION_MOVETOUR);
$link->param('id', $tourid);
$link->param('direction', $direction);
$link->param('sesskey', sesskey());
return $link;
}
/**
* Get the link to move the step.
*
* @param int $stepid The step ID.
* @param int $direction The direction to move in
*
* @return moodle_url
*/
public static function get_move_step_link($stepid, $direction = self::MOVE_DOWN) {
$link = new \moodle_url('/admin/tool/usertours/configure.php');
$link->param('action', manager::ACTION_MOVESTEP);
$link->param('id', $stepid);
$link->param('direction', $direction);
$link->param('sesskey', sesskey());
return $link;
}
/**
* Get the link ot create a new step.
*
* @param int $tourid The ID of the tour to attach this step to.
* @param int $targettype The type of target.
*
* @return moodle_url The required URL.
*/
public static function get_new_step_link($tourid, $targettype = null) {
$link = new \moodle_url('/admin/tool/usertours/configure.php');
$link->param('action', manager::ACTION_NEWSTEP);
$link->param('tourid', $tourid);
$link->param('targettype', $targettype);
return $link;
}
/**
* Get the link used to view the tour.
*
* @param int $tourid The ID of the tour to display.
* @return moodle_url The URL.
*/
public static function get_view_tour_link($tourid) {
return new \moodle_url('/admin/tool/usertours/configure.php', [
'id' => $tourid,
'action' => manager::ACTION_VIEWTOUR,
]);
}
/**
* Get the link used to reset the tour state for all users.
*
* @param int $tourid The ID of the tour to display.
* @return moodle_url The URL.
*/
public static function get_reset_tour_for_all_link($tourid) {
return new \moodle_url('/admin/tool/usertours/configure.php', [
'id' => $tourid,
'action' => manager::ACTION_RESETFORALL,
'sesskey' => sesskey(),
]);
}
/**
* Get the link used to edit the tour.
*
* @param int $tourid The ID of the tour to edit.
* @return moodle_url The URL.
*/
public static function get_edit_tour_link($tourid = null) {
$link = new \moodle_url('/admin/tool/usertours/configure.php');
if ($tourid) {
$link->param('action', manager::ACTION_EDITTOUR);
$link->param('id', $tourid);
} else {
$link->param('action', manager::ACTION_NEWTOUR);
}
return $link;
}
/**
* Get the link used to import the tour.
*
* @return moodle_url The URL.
*/
public static function get_import_tour_link() {
$link = new \moodle_url('/admin/tool/usertours/configure.php', [
'action' => manager::ACTION_IMPORTTOUR,
]);
return $link;
}
/**
* Get the link used to export the tour.
*
* @param int $tourid The ID of the tour to export.
* @return moodle_url The URL.
*/
public static function get_export_tour_link($tourid) {
$link = new \moodle_url('/admin/tool/usertours/configure.php', [
'action' => manager::ACTION_EXPORTTOUR,
'id' => $tourid,
]);
return $link;
}
/**
* Get the link used to delete the tour.
*
* @param int $tourid The ID of the tour to delete.
* @return moodle_url The URL.
*/
public static function get_delete_tour_link($tourid) {
return new \moodle_url('/admin/tool/usertours/configure.php', [
'id' => $tourid,
'action' => manager::ACTION_DELETETOUR,
'sesskey' => sesskey(),
]);
}
/**
* Get the link for listing tours.
*
* @return moodle_url The URL.
*/
public static function get_list_tour_link() {
$link = new \moodle_url('/admin/tool/usertours/configure.php');
$link->param('action', manager::ACTION_LISTTOURS);
return $link;
}
/**
* Get a filler icon for display in the actions column of a table.
*
* @param string $url The URL for the icon.
* @param string $icon The icon identifier.
* @param string $alt The alt text for the icon.
* @param string $iconcomponent The icon component.
* @param array $options Display options.
* @return string
*/
public static function format_icon_link($url, $icon, $alt, $iconcomponent = 'moodle', $options = array()) {
global $OUTPUT;
return $OUTPUT->action_icon(
$url,
new \pix_icon($icon, $alt, $iconcomponent, [
'title' => $alt,
]),
null,
$options
);
}
/**
* Get a filler icon for display in the actions column of a table.
*
* @param array $options Display options.
* @return string
*/
public static function get_filler_icon($options = array()) {
global $OUTPUT;
return \html_writer::span(
$OUTPUT->pix_icon('t/filler', '', 'tool_usertours', $options),
'action-icon'
);
}
/**
* Get the link for deleting steps.
*
* @param int $stepid The ID of the step to display.
* @return moodle_url The URL.
*/
public static function get_delete_step_link($stepid) {
return new \moodle_url('/admin/tool/usertours/configure.php', [
'action' => manager::ACTION_DELETESTEP,
'id' => $stepid,
'sesskey' => sesskey(),
]);
}
/**
* Render the inplace editable used to edit the tour name.
*
* @param tour $tour The tour to edit.
* @return string
*/
public static function render_tourname_inplace_editable(tour $tour) {
return new \core\output\inplace_editable(
'tool_usertours',
'tourname',
$tour->get_id(),
true,
\html_writer::link(
$tour->get_view_link(),
$tour->get_name()
),
$tour->get_name()
);
}
/**
* Render the inplace editable used to edit the tour description.
*
* @param tour $tour The tour to edit.
* @return string
*/
public static function render_tourdescription_inplace_editable(tour $tour) {
return new \core\output\inplace_editable(
'tool_usertours',
'tourdescription',
$tour->get_id(),
true,
$tour->get_description(),
$tour->get_description()
);
}
/**
* Render the inplace editable used to edit the tour enable state.
*
* @param tour $tour The tour to edit.
* @return string
*/
public static function render_tourenabled_inplace_editable(tour $tour) {
global $OUTPUT;
if ($tour->is_enabled()) {
$icon = 't/hide';
$alt = get_string('disable');
$value = 1;
} else {
$icon = 't/show';
$alt = get_string('enable');
$value = 0;
}
$editable = new \core\output\inplace_editable(
'tool_usertours',
'tourenabled',
$tour->get_id(),
true,
$OUTPUT->pix_icon($icon, $alt, 'moodle', [
'title' => $alt,
]),
$value
);
$editable->set_type_toggle();
return $editable;
}
/**
* Render the inplace editable used to edit the step name.
*
* @param step $step The step to edit.
* @return string
*/
public static function render_stepname_inplace_editable(step $step) {
return new \core\output\inplace_editable(
'tool_usertours',
'stepname',
$step->get_id(),
true,
\html_writer::link(
$step->get_edit_link(),
$step->get_title()
),
$step->get_title(false)
);
}
/**
* Get all of the tours.
*
* @return stdClass[]
*/
public static function get_tours() {
global $DB;
$tours = $DB->get_records('tool_usertours_tours', array(), 'sortorder ASC');
$return = [];
foreach ($tours as $tour) {
$return[$tour->id] = tour::load_from_record($tour);
}
return $return;
}
/**
* Get the specified tour.
*
* @param int $tourid The tour that the step belongs to.
* @return stdClass
*/
public static function get_tour($tourid) {
return tour::instance($tourid);
}
/**
* Fetch the tour with the specified sortorder.
*
* @param int $sortorder The sortorder of the tour.
* @return tour
*/
public static function get_tour_from_sortorder($sortorder) {
global $DB;
$tour = $DB->get_record('tool_usertours_tours', array('sortorder' => $sortorder));
return tour::load_from_record($tour);
}
/**
* Return the count of all tours.
*
* @return int
*/
public static function count_tours() {
global $DB;
return $DB->count_records('tool_usertours_tours');
}
/**
* Reset the sortorder for all tours.
*/
public static function reset_tour_sortorder() {
global $DB;
$tours = $DB->get_records('tool_usertours_tours', null, 'sortorder ASC, pathmatch DESC', 'id, sortorder');
$index = 0;
foreach ($tours as $tour) {
if ($tour->sortorder != $index) {
$DB->set_field('tool_usertours_tours', 'sortorder', $index, array('id' => $tour->id));
}
$index++;
}
}
/**
* Get all of the steps in the tour.
*
* @param int $tourid The tour that the step belongs to.
* @return stdClass[]
*/
public static function get_steps($tourid) {
global $DB;
$order = 'sortorder ASC';
$steps = $DB->get_records('tool_usertours_steps', array('tourid' => $tourid), $order);
$return = [];
foreach ($steps as $step) {
$return[$step->id] = step::load_from_record($step);
}
return $return;
}
/**
* Fetch the specified step.
*
* @param int $stepid The id of the step to fetch.
* @return step
*/
public static function get_step($stepid) {
return step::instance($stepid);
}
/**
* Fetch the step with the specified sortorder.
*
* @param int $tourid The tour that the step belongs to.
* @param int $sortorder The sortorder of the step.
* @return step
*/
public static function get_step_from_sortorder($tourid, $sortorder) {
global $DB;
$step = $DB->get_record('tool_usertours_steps', array('tourid' => $tourid, 'sortorder' => $sortorder));
return step::load_from_record($step);
}
/**
* Handle addition of the tour into the current page.
*/
public static function bootstrap() {
global $PAGE;
if ($tour = manager::get_current_tour()) {
$PAGE->requires->js_call_amd('tool_usertours/usertours', 'init', [
$tour->get_id(),
$tour->should_show_for_user(),
$PAGE->context->id,
]);
}
}
/**
* Add the reset link to the current page.
*/
public static function bootstrap_reset() {
if (manager::get_current_tour()) {
echo \html_writer::link('', get_string('resettouronpage', 'tool_usertours'), [
'data-action' => 'tool_usertours/resetpagetour',
]);
}
}
/**
* Get a list of all possible filters.
*
* @return array
*/
public static function get_all_filters() {
$filters = \core_component::get_component_classes_in_namespace('tool_usertours', 'local\filter');
$filters = array_keys($filters);
$filters = array_filter($filters, function($filterclass) {
$rc = new \ReflectionClass($filterclass);
return $rc->isInstantiable();
});
return $filters;
}
}
@@ -0,0 +1,135 @@
<?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/>.
/**
* Filter base.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_usertours\local\filter;
defined('MOODLE_INTERNAL') || die();
use tool_usertours\tour;
use context;
/**
* Filter base.
*
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class base {
/**
* Any Value.
*/
const ANYVALUE = '__ANYVALUE__';
/**
* The name of the filter.
*
* @return string
*/
public static function get_filter_name() {
throw new \coding_exception('get_filter_name() must be defined');
}
/**
* Retrieve the list of available filter options.
*
* @return array An array whose keys are the valid options
*/
public static function get_filter_options() {
return [];
}
/**
* Check whether the filter matches the specified tour and/or context.
*
* @param tour $tour The tour to check
* @param context $context The context to check
* @return boolean
*/
public static function filter_matches(tour $tour, context $context) {
return true;
}
/**
* Add the form elements for the filter to the supplied form.
*
* @param MoodleQuickForm $mform The form to add filter settings to.
*/
public static function add_filter_to_form(\MoodleQuickForm &$mform) {
$options = [
static::ANYVALUE => get_string('all'),
];
$options += static::get_filter_options();
$filtername = static::get_filter_name();
$key = "filter_{$filtername}";
$mform->addElement('select', $key, get_string($key, 'tool_usertours'), $options, [
'multiple' => true,
]);
$mform->setDefault($key, static::ANYVALUE);
$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 = static::ANYVALUE;
}
$data->$key = $values;
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;
foreach ($data->$key as $value) {
if ($value === static::ANYVALUE) {
$newvalue = [];
break;
}
}
$tour->set_filter_values($filtername, $newvalue);
}
}
@@ -0,0 +1,99 @@
<?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/>.
/**
* Theme filter.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_usertours\local\filter;
defined('MOODLE_INTERNAL') || die();
use tool_usertours\tour;
use context;
/**
* Theme filter.
*
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class role extends base {
/**
* The name of the filter.
*
* @return string
*/
public static function get_filter_name() {
return 'role';
}
/**
* Retrieve the list of available filter options.
*
* @return array An array whose keys are the valid options
* And whose values are the values to display
*/
public static function get_filter_options() {
return role_get_names(null, ROLENAME_ALIAS, true);
}
/**
* Check whether the filter matches the specified tour and/or context.
*
* @param tour $tour The tour to check
* @param context $context The context to check
* @return boolean
*/
public static function filter_matches(tour $tour, context $context) {
global $USER;
$values = $tour->get_filter_values(self::get_filter_name());
if (empty($values)) {
// There are no values configured.
// No values means all.
return true;
}
if (is_siteadmin()) {
return true;
}
// Presence within the array is sufficient. Ignore any value.
$values = array_flip($values);
$cache = \cache::make_from_params(\cache_store::MODE_REQUEST, 'tool_usertours', 'filter_role');
$cachekey = "{$USER->id}_{$context->id}";
$userroles = $cache->get($cachekey);
if ($userroles === false) {
$userroles = get_user_roles_with_special($context);
$cache->set($cachekey, $userroles);
}
foreach ($userroles as $role) {
if (isset($values[$role->roleid])) {
return true;
}
}
return false;
}
}
@@ -0,0 +1,102 @@
<?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/>.
/**
* Theme filter.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_usertours\local\filter;
defined('MOODLE_INTERNAL') || die();
use tool_usertours\tour;
use context;
/**
* Theme filter.
*
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class theme extends base {
/**
* The name of the filter.
*
* @return string
*/
public static function get_filter_name() {
return 'theme';
}
/**
* Retrieve the list of available filter options.
*
* @return array An array whose keys are the valid options
* And whose values are the values to display
*/
public static function get_filter_options() {
$manager = \core_plugin_manager::instance();
$themes = $manager->get_installed_plugins('theme');
$options = [];
foreach (array_keys($themes) as $themename) {
try {
$theme = \theme_config::load($themename);
} catch (Exception $e) {
// Bad theme, just skip it for now.
continue;
}
if ($themename !== $theme->name) {
// Obsoleted or broken theme, just skip for now.
continue;
}
if ($theme->hidefromselector) {
// The theme doesn't want to be shown in the theme selector and as theme
// designer mode is switched off we will respect that decision.
continue;
}
$options[$theme->name] = get_string('pluginname', "theme_{$theme->name}");
}
return $options;
}
/**
* Check whether the filter matches the specified tour and/or context.
*
* @param tour $tour The tour to check
* @param context $context The context to check
* @return boolean
*/
public static function filter_matches(tour $tour, context $context) {
global $PAGE;
$values = $tour->get_filter_values('theme');
if (empty($values)) {
// There are no values configured.
// No values means all.
return true;
}
// Presence within the array is sufficient. Ignore any value.
$values = array_flip($values);
return isset($values[$PAGE->theme->name]);
}
}
@@ -0,0 +1,96 @@
<?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/>.
/**
* Form for editing steps.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_usertours\local\forms;
defined('MOODLE_INTERNAL') || die('Direct access to this script is forbidden.');
require_once($CFG->libdir . '/formslib.php');
/**
* Form for editing steps.
*
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class editstep extends \moodleform {
/**
* @var tool_usertours\step $step
*/
protected $step;
/**
* Create the edit step form.
*
* @param string $target The target of the form.
* @param step $step The step being editted.
*/
public function __construct($target, \tool_usertours\step $step) {
$this->step = $step;
parent::__construct($target);
}
/**
* Form definition.
*/
public function definition() {
$mform = $this->_form;
$types = [];
foreach (\tool_usertours\target::get_target_types() as $value => $type) {
$types[$value] = get_string('target_' . $type, 'tool_usertours');
}
$mform->addElement('select', 'targettype', get_string('targettype', 'tool_usertours'), $types);
// The target configuration.
foreach (\tool_usertours\target::get_target_types() as $value => $type) {
$targetclass = \tool_usertours\target::get_classname($type);
$targetclass::add_config_to_form($mform);
}
// Title of the step.
$mform->addElement('textarea', 'title', get_string('title', 'tool_usertours'));
$mform->addRule('title', get_string('required'), 'required', null, 'client');
$mform->setType('title', PARAM_TEXT);
$mform->addHelpButton('title', 'title', 'tool_usertours');
$mform->addElement('textarea', 'content', get_string('content', 'tool_usertours'));
$mform->addRule('content', get_string('required'), 'required', null, 'client');
$mform->setType('content', PARAM_RAW);
$mform->addHelpButton('content', 'content', 'tool_usertours');
// Add the step configuration.
// All step configuration is defined in the step.
$this->step->add_config_to_form($mform);
// And apply any form constraints.
foreach (\tool_usertours\target::get_target_types() as $value => $type) {
$targetclass = \tool_usertours\target::get_classname($type);
$targetclass::add_disabled_constraints_to_form($mform);
}
$this->add_action_buttons();
}
}
@@ -0,0 +1,95 @@
<?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/>.
/**
* Form for editing tours.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_usertours\local\forms;
defined('MOODLE_INTERNAL') || die('Direct access to this script is forbidden.');
require_once($CFG->libdir . '/formslib.php');
use \tool_usertours\helper;
/**
* Form for editing tours.
*
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class edittour extends \moodleform {
/**
* @var tool_usertours\tour $tour
*/
protected $tour;
/**
* Create the edit tour form.
*
* @param tour $tour The tour being editted.
*/
public function __construct(\tool_usertours\tour $tour) {
$this->tour = $tour;
parent::__construct($tour->get_edit_link());
}
/**
* Form definition.
*/
public function definition() {
$mform = $this->_form;
// ID of existing tour.
$mform->addElement('hidden', 'id');
$mform->setType('id', PARAM_INT);
// Name of the tour.
$mform->addElement('text', 'name', get_string('name', 'tool_usertours'));
$mform->addRule('name', get_string('required'), 'required', null, 'client');
$mform->setType('name', PARAM_TEXT);
// Admin-only descriptions.
$mform->addElement('textarea', 'description', get_string('description', 'tool_usertours'));
$mform->setType('description', PARAM_RAW);
// Application.
$mform->addElement('text', 'pathmatch', get_string('pathmatch', 'tool_usertours'));
$mform->setType('pathmatch', PARAM_RAW);
$mform->addHelpButton('pathmatch', 'pathmatch', 'tool_usertours');
$mform->addElement('checkbox', 'enabled', get_string('enabled', 'tool_usertours'));
// Configuration.
$this->tour->add_config_to_form($mform);
// Filters.
$mform->addElement('header', 'filters', get_string('filter_header', 'tool_usertours'));
$mform->addElement('static', 'filterhelp', '', get_string('filter_help', 'tool_usertours'));
foreach (helper::get_all_filters() as $filterclass) {
$filterclass::add_filter_to_form($mform);
}
$this->add_action_buttons();
}
}
@@ -0,0 +1,56 @@
<?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/>.
/**
* Form for editing tours.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_usertours\local\forms;
defined('MOODLE_INTERNAL') || die('Direct access to this script is forbidden.');
require_once($CFG->libdir . '/formslib.php');
/**
* Form for importing tours.
*
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class importtour extends \moodleform {
/**
* Create the import tour form.
*/
public function __construct() {
parent::__construct(\tool_usertours\helper::get_import_tour_link());
}
/**
* Form definition.
*/
public function definition() {
$mform = $this->_form;
$mform->addElement('filepicker', 'tourconfig', get_string('tourconfig', 'tool_usertours'));
$mform->addRule('tourconfig', null, 'required');
$this->add_action_buttons();
}
}
@@ -0,0 +1,143 @@
<?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/>.
/**
* Table to show the list of steps in a tour.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_usertours\local\table;
defined('MOODLE_INTERNAL') || die();
use tool_usertours\helper;
use tool_usertours\tour;
use tool_usertours\step;
require_once($CFG->libdir . '/tablelib.php');
/**
* Table to show the list of steps in a tour.
*
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class step_list extends \flexible_table {
/**
* @var int $tourid The id of the tour.
*/
protected $tourid;
/**
* Construct the table for the specified tour ID.
*
* @param int $tourid The id of the tour.
*/
public function __construct($tourid) {
parent::__construct('steps');
$this->tourid = $tourid;
$baseurl = new \moodle_url('/tool/usertours/configure.php', array(
'id' => $tourid,
));
$this->define_baseurl($baseurl);
// Column definition.
$this->define_columns(array(
'title',
'content',
'target',
'actions',
));
$this->define_headers(array(
get_string('title', 'tool_usertours'),
get_string('content', 'tool_usertours'),
get_string('target', 'tool_usertours'),
get_string('actions', 'tool_usertours'),
));
$this->set_attribute('class', 'admintable generaltable steptable');
$this->setup();
}
/**
* Format the current row's title column.
*
* @param step $step The step for this row.
* @return string
*/
protected function col_title(step $step) {
global $OUTPUT;
return $OUTPUT->render(helper::render_stepname_inplace_editable($step));
}
/**
* Format the current row's content column.
*
* @param step $step The step for this row.
* @return string
*/
protected function col_content(step $step) {
return $step->get_content(false);
}
/**
* Format the current row's target column.
*
* @param step $step The step for this row.
* @return string
*/
protected function col_target(step $step) {
return $step->get_target()->get_displayname();
}
/**
* Format the current row's actions column.
*
* @param step $step The step for this row.
* @return string
*/
protected function col_actions(step $step) {
$actions = [];
if ($step->is_first_step()) {
$actions[] = helper::get_filler_icon();
} else {
$actions[] = helper::format_icon_link($step->get_moveup_link(), 't/up', get_string('movestepup', 'tool_usertours'));
}
if ($step->is_last_step()) {
$actions[] = helper::get_filler_icon();
} else {
$actions[] = helper::format_icon_link($step->get_movedown_link(), 't/down',
get_string('movestepdown', 'tool_usertours'));
}
$actions[] = helper::format_icon_link($step->get_edit_link(), 't/edit', get_string('edit'));
$actions[] = helper::format_icon_link($step->get_delete_link(), 't/delete', get_string('delete'), 'moodle', [
'data-action' => 'delete',
'data-id' => $step->get_id(),
]);
return implode('&nbsp;', $actions);
}
}
@@ -0,0 +1,150 @@
<?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/>.
/**
* Table to show the list of tours.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_usertours\local\table;
defined('MOODLE_INTERNAL') || die();
use tool_usertours\helper;
use tool_usertours\tour;
require_once($CFG->libdir . '/tablelib.php');
/**
* Table to show the list of tours.
*
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class tour_list extends \flexible_table {
/**
* Construct the tour table.
*/
public function __construct() {
parent::__construct('tours');
$baseurl = new \moodle_url('/tool/usertours/configure.php');
$this->define_baseurl($baseurl);
// Column definition.
$this->define_columns(array(
'name',
'description',
'appliesto',
'enabled',
'actions',
));
$this->define_headers(array(
get_string('name', 'tool_usertours'),
get_string('description', 'tool_usertours'),
get_string('appliesto', 'tool_usertours'),
get_string('enabled', 'tool_usertours'),
get_string('actions', 'tool_usertours'),
));
$this->set_attribute('class', 'admintable generaltable');
$this->setup();
$this->tourcount = helper::count_tours();
}
/**
* Format the current row's name column.
*
* @param tour $tour The tour for this row.
* @return string
*/
protected function col_name(tour $tour) {
global $OUTPUT;
return $OUTPUT->render(helper::render_tourname_inplace_editable($tour));
}
/**
* Format the current row's description column.
*
* @param tour $tour The tour for this row.
* @return string
*/
protected function col_description(tour $tour) {
global $OUTPUT;
return $OUTPUT->render(helper::render_tourdescription_inplace_editable($tour));
}
/**
* Format the current row's appliesto column.
*
* @param tour $tour The tour for this row.
* @return string
*/
protected function col_appliesto(tour $tour) {
return $tour->get_pathmatch();
}
/**
* Format the current row's enabled column.
*
* @param tour $tour The tour for this row.
* @return string
*/
protected function col_enabled(tour $tour) {
global $OUTPUT;
return $OUTPUT->render(helper::render_tourenabled_inplace_editable($tour));
}
/**
* Format the current row's actions column.
*
* @param tour $tour The tour for this row.
* @return string
*/
protected function col_actions(tour $tour) {
$actions = [];
if ($tour->is_first_tour()) {
$actions[] = helper::get_filler_icon();
} else {
$actions[] = helper::format_icon_link($tour->get_moveup_link(), 't/up',
get_string('movetourup', 'tool_usertours'));
}
if ($tour->is_last_tour($this->tourcount)) {
$actions[] = helper::get_filler_icon();
} else {
$actions[] = helper::format_icon_link($tour->get_movedown_link(), 't/down',
get_string('movetourdown', 'tool_usertours'));
}
$actions[] = helper::format_icon_link($tour->get_view_link(), 't/viewdetails', get_string('view'));
$actions[] = helper::format_icon_link($tour->get_edit_link(), 't/edit', get_string('edit'));
$actions[] = helper::format_icon_link($tour->get_export_link(), 't/export',
get_string('exporttour', 'tool_usertours'), 'tool_usertours');
$actions[] = helper::format_icon_link($tour->get_delete_link(), 't/delete', get_string('delete'), null, [
'data-action' => 'delete',
'data-id' => $tour->get_id(),
]);
return implode('&nbsp;', $actions);
}
}
@@ -0,0 +1,119 @@
<?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/>.
/**
* Target base.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_usertours\local\target;
defined('MOODLE_INTERNAL') || die();
use tool_usertours\step;
/**
* Target base.
*
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class base {
/**
* @var step $step The step being targetted.
*/
protected $step;
/**
* @var array $forcedsettings The settings forced by this type.
*/
protected static $forcedsettings = [];
/**
* Create the target type.
*
* @param step $step The step being targetted.
*/
public function __construct(step $step) {
$this->step = $step;
}
/**
* Convert the target value to a valid CSS selector for use in the
* output configuration.
*
* @return string
*/
abstract public function convert_to_css();
/**
* Convert the step target to a friendly name for use in the UI.
*
* @return string
*/
abstract public function get_displayname();
/**
* Add the target type configuration to the form.
*
* @param MoodleQuickForm $mform The form to add configuration to.
*/
public static function add_config_to_form(\MoodleQuickForm $mform) {
}
/**
* Add the disabledIf values.
*
* @param MoodleQuickForm $mform The form to add configuration to.
*/
public static function add_disabled_constraints_to_form(\MoodleQuickForm $mform) {
}
/**
* Whether the specified step setting is forced by this target type.
*
* @param string $key The name of the key to check.
* @return boolean
*/
public function is_setting_forced($key) {
return isset(static::$forcedsettings[$key]);
}
/**
* The value of the forced setting.
*
* @param string $key The name of the key to check.
* @return mixed
*/
public function get_forced_setting_value($key) {
if ($this->is_setting_forced($key)) {
return static::$forcedsettings[$key];
}
return null;
}
/**
* Fetch the targetvalue from the form for this target type.
*
* @param stdClass $data The data submitted in the form
* @return string
*/
abstract public function get_value_from_form($data);
}
@@ -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/>.
/**
* Block target.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_usertours\local\target;
defined('MOODLE_INTERNAL') || die();
use tool_usertours\step;
/**
* Block target.
*
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class block extends base {
/**
* Convert the target value to a valid CSS selector for use in the
* output configuration.
*
* @return string
*/
public function convert_to_css() {
// The block has the following CSS class selector style:
// .block-region .block_[name] .
return sprintf('.block-region .block_%s', $this->step->get_targetvalue());
}
/**
* Convert the step target to a friendly name for use in the UI.
*
* @return string
*/
public function get_displayname() {
return get_string('block_named', 'tool_usertours', $this->get_block_name());
}
/**
* Get the translated name of the block.
*
* @return string
*/
protected function get_block_name() {
return get_string('pluginname', self::get_frankenstyle($this->step->get_targetvalue()));
}
/**
* Get the frankenstyle name of the block.
*
* @param string $block The block name.
* @return The frankenstyle block name.
*/
protected static function get_frankenstyle($block) {
return sprintf('block_%s', $block);
}
/**
* Add the target type configuration to the form.
*
* @param MoodleQuickForm $mform The form to add configuration to.
* @return $this
*/
public static function add_config_to_form(\MoodleQuickForm $mform) {
global $PAGE;
$blocks = [];
foreach ($PAGE->blocks->get_installed_blocks() as $block) {
$blocks[$block->name] = get_string('pluginname', 'block_' . $block->name);
}
$mform->addElement('select', 'targetvalue_block', get_string('block', 'tool_usertours'), $blocks);
}
/**
* Add the disabledIf values.
*
* @param MoodleQuickForm $mform The form to add configuration to.
*/
public static function add_disabled_constraints_to_form(\MoodleQuickForm $mform) {
$mform->disabledIf('targetvalue_block', 'targettype', 'noteq',
\tool_usertours\target::get_target_constant_for_class(get_class()));
}
/**
* Fetch the targetvalue from the form for this target type.
*
* @param stdClass $data The data submitted in the form
* @return string
*/
public function get_value_from_form($data) {
return $data->targetvalue_block;
}
}
@@ -0,0 +1,107 @@
<?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 target.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_usertours\local\target;
defined('MOODLE_INTERNAL') || die();
use tool_usertours\step;
/**
* Selector target.
*
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class selector extends base {
/**
* Convert the target value to a valid CSS selector for use in the
* output configuration.
*
* @return string
*/
public function convert_to_css() {
return $this->step->get_targetvalue();
}
/**
* Convert the step target to a friendly name for use in the UI.
*
* @return string
*/
public function get_displayname() {
return get_string('selectordisplayname', 'tool_usertours', $this->step->get_targetvalue());
}
/**
* Get the default title.
*
* @return string
*/
public function get_default_title() {
return get_string('selector_defaulttitle', 'tool_usertours');
}
/**
* Get the default content.
*
* @return string
*/
public function get_default_content() {
return get_string('selector_defaultcontent', 'tool_usertours');
}
/**
* Add the target type configuration to the form.
*
* @param MoodleQuickForm $mform The form to add configuration to.
* @return $this
*/
public static function add_config_to_form(\MoodleQuickForm $mform) {
$mform->addElement('text', 'targetvalue_selector', get_string('cssselector', 'tool_usertours'));
$mform->setType('targetvalue_selector', PARAM_RAW);
$mform->addHelpButton('targetvalue_selector', 'target_selector_targetvalue', 'tool_usertours');
}
/**
* Add the disabledIf values.
*
* @param MoodleQuickForm $mform The form to add configuration to.
*/
public static function add_disabled_constraints_to_form(\MoodleQuickForm $mform) {
$mform->disabledIf('targetvalue_selector', 'targettype', 'noteq',
\tool_usertours\target::get_target_constant_for_class(get_class()));
}
/**
* Fetch the targetvalue from the form for this target type.
*
* @param stdClass $data The data submitted in the form
* @return string
*/
public function get_value_from_form($data) {
return $data->targetvalue_selector;
}
}
@@ -0,0 +1,100 @@
<?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/>.
/**
* A step designed to be orphaned.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_usertours\local\target;
defined('MOODLE_INTERNAL') || die();
use tool_usertours\step;
/**
* A step designed to be orphaned.
*
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class unattached extends base {
/**
* @var array $forcedsettings The settings forced by this type.
*/
protected static $forcedsettings = [
'placement' => 'top',
'orphan' => true,
'reflex' => false,
];
/**
* Convert the target value to a valid CSS selector for use in the
* output configuration.
*
* @return string
*/
public function convert_to_css() {
return '';
}
/**
* Convert the step target to a friendly name for use in the UI.
*
* @return string
*/
public function get_displayname() {
return get_string('target_unattached', 'tool_usertours');
}
/**
* Add the target type configuration to the form.
*
* @param MoodleQuickForm $mform The form to add configuration to.
* @return $this
*/
public static function add_config_to_form(\MoodleQuickForm $mform) {
// There is no relevant value here.
$mform->addElement('hidden', 'targetvalue_unattached', '');
$mform->setType('targetvalue_unattached', PARAM_TEXT);
}
/**
* Add the disabledIf values.
*
* @param MoodleQuickForm $mform The form to add configuration to.
*/
public static function add_disabled_constraints_to_form(\MoodleQuickForm $mform) {
$myvalue = \tool_usertours\target::get_target_constant_for_class(get_class());
foreach (array_keys(self::$forcedsettings) as $settingname) {
$mform->disabledIf($settingname, 'targettype', 'eq', $myvalue);
}
}
/**
* Fetch the targetvalue from the form for this target type.
*
* @param stdClass $data The data submitted in the form
* @return string
*/
public function get_value_from_form($data) {
return '';
}
}
+769
View File
@@ -0,0 +1,769 @@
<?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 <[email protected]>
* @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;
/**
* Tour manager.
*
* @copyright 2016 Andrew Nicols <[email protected]>
* @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_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';
/**
* This is the entry point for this controller class.
*
* @param string $action The action to perform.
*/
public function execute($action) {
admin_externalpage_setup('tool_usertours/tours');
// 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_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/mod/data/view.php', [
'id' => 17,
]),
'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 .= \html_writer::img(
$OUTPUT->pix_url($config->img, 'tool_usertours'),
$config->title);
$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());
} 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));
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 {
$this->header($tour->get_name());
$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();
unset($record->id);
unset($record->tourid);
$export->steps[] = $record;
}
$exportstring = json_encode($export);
$filename = 'tour_export_' . $tour->get_id() . '_' . time() . '.json';
// Force download.
header('Last-Modified: ' . gmdate('D, d M Y H:i:s', time()) . ' GMT');
header('Cache-Control: private, must-revalidate, pre-check=0, post-check=0, max-age=0');
header('Expires: ' . gmdate('D, d M Y H:i:s', 0) . 'GMT');
header('Pragma: no-cache');
header('Accept-Ranges: none');
header('Content-disposition: attachment; filename=' . $filename);
header('Content-length: ' . strlen($exportstring));
header('Content-type: text/calendar; charset=utf-8');
echo $exportstring;
die;
}
/**
* 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);
$PAGE->navbar->add($tour->get_name(), $tour->get_view_link());
$this->header($tour->get_name());
echo \html_writer::span(get_string('viewtour_info', 'tool_usertours', [
'tourname' => $tour->get_name(),
'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();
}
/**
* 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 the first tour matching the current page URL.
*
* @param bool $reset Forcibly update the current tour
* @return tour
*/
public static function get_current_tour($reset = false) {
global $PAGE;
static $tour = false;
if ($tour === false || $reset) {
$tour = self::get_matching_tours($PAGE->url);
}
return $tour;
}
/**
* Get the first tour matching the specified URL.
*
* @param moodle_url $pageurl The URL to match.
* @return tour
*/
public static function get_matching_tours(\moodle_url $pageurl) {
global $DB, $PAGE;
// Do not show tours whilst upgrades are pending.
if (moodle_needs_upgrading()) {
return null;
}
$sql = <<<EOF
SELECT * FROM {tool_usertours_tours}
WHERE enabled = 1
AND ? LIKE pathmatch
ORDER BY sortorder ASC
EOF;
$tours = $DB->get_records_sql($sql, array(
$pageurl->out_as_local_url(),
));
foreach ($tours as $record) {
$tour = tour::load_from_record($record);
if ($tour->is_enabled() && $tour->matches_all_filters($PAGE->context) & $tour->get_steps() > 0) {
return $tour;
}
}
return null;
}
/**
* 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->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();
$PAGE->navbar->add($tour->get_name(), $tour->get_view_link());
if (isset($id)) {
$PAGE->navbar->add($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()));
}
$form->set_data($step->prepare_data_for_form());
$form->display();
$this->footer();
}
}
/**
* Move a tour up or down.
*
* @param int $id The tour to move.
*/
protected function move_tour($id) {
require_sesskey();
$direction = required_param('direction', PARAM_INT);
$tour = tour::instance($id);
$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();
redirect(helper::get_list_tour_link());
}
/**
* 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());
}
}
@@ -0,0 +1,36 @@
<?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/>.
/**
* Renderer.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_usertours\output;
defined('MOODLE_INTERNAL') || die();
/**
* Renderer.
*
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class renderer extends \plugin_renderer_base {
}
@@ -0,0 +1,121 @@
<?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 Step Renderable.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_usertours\output;
defined('MOODLE_INTERNAL') || die();
use tool_usertours\step as stepsource;
/**
* Tour Step Renderable.
*
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class step implements \renderable {
/**
* @var The step instance.
*/
protected $step;
/**
* The step output.
*
* @param stepsource $step The step being output.
*/
public function __construct(stepsource $step) {
$this->step = $step;
}
/**
* Export the step configuration.
*
* @param renderer_base $output The renderer.
* @return object
*/
public function export_for_template(\renderer_base $output) {
global $PAGE;
$step = $this->step;
$result = (object) [
'stepid' => $step->get_id(),
'title' => external_format_text(
static::get_string_from_input($step->get_title()),
FORMAT_HTML,
$PAGE->context->id,
'tool_usertours',
null,
null,
[
'filter' => true,
]
)[0],
'content' => external_format_text(
static::get_string_from_input($step->get_content()),
FORMAT_HTML,
$PAGE->context->id,
'tool_usertours',
null,
null,
[
'filter' => true,
]
)[0],
'element' => $step->get_target()->convert_to_css(),
];
$result->content = str_replace("\n", "<br>\n", $result->content);
foreach ($step->get_config_keys() as $key) {
$result->$key = $step->get_config($key);
}
return $result;
}
/**
* Attempt to fetch any matching langstring if the string is in the
* format identifier,component.
*
* @param string $string
* @return string
*/
protected static function get_string_from_input($string) {
$string = trim($string);
if (preg_match('|^([a-zA-Z][a-zA-Z0-9\.:/_-]*),([a-zA-Z][a-zA-Z0-9\.:/_-]*)$|', $string, $matches)) {
if ($matches[2] === 'moodle') {
$matches[2] = 'core';
}
if (get_string_manager()->string_exists($matches[1], $matches[2])) {
$string = get_string($matches[1], $matches[2]);
}
}
return $string;
}
}
@@ -0,0 +1,71 @@
<?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 renderable.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_usertours\output;
defined('MOODLE_INTERNAL') || die();
use tool_usertours\tour as toursource;
/**
* Tour renderable.
*
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class tour implements \renderable {
/**
* @var The tour instance.
*/
protected $tour;
/**
* The tour output.
*
* @param toursource $tour The tour being output.
*/
public function __construct (toursource $tour) {
$this->tour = $tour;
}
/**
* Prepare the data for export.
*
* @param \renderer_base $output The output renderable.
* @return object
*/
public function export_for_template(\renderer_base $output) {
$result = (object) [
'name' => $this->tour->get_tour_key(),
'steps' => [],
];
foreach ($this->tour->get_steps() as $step) {
$result->steps[] = (new step($step))->export_for_template($output);
}
return $result;
}
}
+629
View File
@@ -0,0 +1,629 @@
<?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/>.
/**
* Step class.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_usertours;
defined('MOODLE_INTERNAL') || die();
/**
* Step class.
*
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class step {
/**
* @var int $id The id of the step.
*/
protected $id;
/**
* @var int $tourid The id of the tour that this step belongs to.
*/
protected $tourid;
/**
* @var tour $tour The tour class that this step belongs to.
*/
protected $tour;
/**
* @var string $title The title of the step.
*/
protected $title;
/**
* @var string $content The content of this step.
*/
protected $content;
/**
* @var int $targettype The type of target.
*/
protected $targettype;
/**
* @var string $targetvalue The value for this type of target.
*/
protected $targetvalue;
/**
* @var int $sortorder The sort order.
*/
protected $sortorder;
/**
* @var object $config The configuration as an object.
*/
protected $config;
/**
* @var bool $dirty Whether the step has been changed since it was loaded
*/
protected $dirty = false;
/**
* Fetch the step instance.
*
* @param int $id The id of the step to be retrieved.
* @return step
*/
public static function instance($id) {
$step = new step();
return $step->fetch($id);
}
/**
* Load the step instance.
*
* @param stdClass $record The step record to be loaded.
* @param boolean $clean Clean the values.
* @return step
*/
public static function load_from_record($record, $clean = false) {
$step = new self();
return $step->reload_from_record($record, $clean);
}
/**
* Fetch the step instance.
*
* @param int $id The id of the step to be retrieved.
* @return step
*/
protected function fetch($id) {
global $DB;
return $this->reload_from_record(
$DB->get_record('tool_usertours_steps', array('id' => $id))
);
}
/**
* Refresh the current step from the datbase.
*
* @return step
*/
protected function reload() {
return $this->fetch($this->id);
}
/**
* Reload the current step from the supplied record.
*
* @param stdClass $record The step record to be loaded.
* @param boolean $clean Clean the values.
* @return step
*/
protected function reload_from_record($record, $clean = false) {
$this->id = $record->id;
$this->tourid = $record->tourid;
if ($clean) {
$this->title = clean_param($record->title, PARAM_TEXT);
$this->content = clean_text($record->content);
} else {
$this->title = $record->title;
$this->content = $record->content;
}
$this->targettype = $record->targettype;
$this->targetvalue = $record->targetvalue;
$this->sortorder = $record->sortorder;
$this->config = json_decode($record->configdata);
$this->dirty = false;
return $this;
}
/**
* Get the ID of the step.
*
* @return int
*/
public function get_id() {
return $this->id;
}
/**
* Get the Tour ID of the step.
*
* @return int
*/
public function get_tourid() {
return $this->tourid;
}
/**
* Get the Tour instance that this step belongs to.
*
* @return tour
*/
public function get_tour() {
if ($this->tour === null) {
$this->tour = tour::instance($this->tourid);
}
return $this->tour;
}
/**
* Set the id of the tour.
*
* @param int $value The id of the tour.
* @return self
*/
public function set_tourid($value) {
$this->tourid = $value;
$this->tour = null;
$this->dirty = true;
return $this;
}
/**
* Get the Title of the step.
*
* @return string
*/
public function get_title() {
return $this->title;
}
/**
* Set the title for this step.
*
* @param string $value The new title to use.
* @return $this
*/
public function set_title($value) {
$this->title = clean_param($value, PARAM_TEXT);
$this->dirty = true;
return $this;
}
/**
* Get the body content of the step.
*
* @return string
*/
public function get_content() {
return $this->content;
}
/**
* Set the content value for this step.
*
* @param string $value The new content to use.
* @return $this
*/
public function set_content($value) {
$this->content = clean_text($value);
$this->dirty = true;
return $this;
}
/**
* Get the content value for this step.
*
* @return string
*/
public function get_targettype() {
return $this->targettype;
}
/**
* Set the type of target for this step.
*
* @param string $value The new target to use.
* @return $this
*/
public function set_targettype($value) {
$this->targettype = $value;
$this->dirty = true;
return $this;
}
/**
* Get the target value for this step.
*
* @return string
*/
public function get_targetvalue() {
return $this->targetvalue;
}
/**
* Set the target value for this step.
*
* @param string $value The new target value to use.
* @return $this
*/
public function set_targetvalue($value) {
$this->targetvalue = $value;
$this->dirty = true;
return $this;
}
/**
* Get the target instance for this step.
*
* @return target
*/
public function get_target() {
return target::get_target_instance($this);
}
/**
* Get the current sortorder for this step.
*
* @return int
*/
public function get_sortorder() {
return (int) $this->sortorder;
}
/**
* Whether this step is the first step in the tour.
*
* @return boolean
*/
public function is_first_step() {
return ($this->get_sortorder() === 0);
}
/**
* Whether this step is the last step in the tour.
*
* @return boolean
*/
public function is_last_step() {
$stepcount = $this->get_tour()->count_steps();
return ($this->get_sortorder() === $stepcount - 1);
}
/**
* Set the sortorder for this step.
*
* @param int $value The new sortorder to use.
* @return $this
*/
public function set_sortorder($value) {
$this->sortorder = $value;
$this->dirty = true;
return $this;
}
/**
* Get the link to move this step up in the sortorder.
*
* @return moodle_url
*/
public function get_moveup_link() {
return helper::get_move_step_link($this->get_id(), helper::MOVE_UP);
}
/**
* Get the link to move this step down in the sortorder.
*
* @return moodle_url
*/
public function get_movedown_link() {
return helper::get_move_step_link($this->get_id(), helper::MOVE_DOWN);
}
/**
* Get the value of the specified configuration item.
*
* If notvalue was found, and no default was specified, the default for the tour will be used.
*
* @param string $key The configuration key to set.
* @param mixed $default The default value to use if a value was not found.
* @return mixed
*/
public function get_config($key = null, $default = null) {
if ($this->config === null) {
$this->config = (object) array();
}
if ($key === null) {
return $this->config;
}
if ($this->get_targettype()) {
$target = $this->get_target();
if ($target->is_setting_forced($key)) {
return $target->get_forced_setting_value($key);
}
}
if (property_exists($this->config, $key)) {
return $this->config->$key;
}
if ($default !== null) {
return $default;
}
return $this->get_tour()->get_config($key);
}
/**
* Set the configuration item as specified.
*
* @param string $key The configuration key to set.
* @param mixed $value The new value for the configuration item.
* @return $this
*/
public function set_config($key, $value) {
if ($this->config === null) {
$this->config = (object) array();
}
if ($value === null) {
unset($this->config->$key);
} else {
$this->config->$key = $value;
}
$this->dirty = true;
return $this;
}
/**
* Get the edit link for this step.
*
* @return moodle_url
*/
public function get_edit_link() {
return helper::get_edit_step_link($this->tourid, $this->id);
}
/**
* Get the delete link for this step.
*
* @return moodle_url
*/
public function get_delete_link() {
return helper::get_delete_step_link($this->id);
}
/**
* Prepare this step for saving to the database.
*
* @return object
*/
public function to_record() {
return (object) array(
'id' => $this->id,
'tourid' => $this->tourid,
'title' => $this->title,
'content' => $this->content,
'targettype' => $this->targettype,
'targetvalue' => $this->targetvalue,
'sortorder' => $this->sortorder,
'configdata' => json_encode($this->config),
);
}
/**
* Calculate the next sort-order value.
*
* @return int
*/
protected function calculate_sortorder() {
$count = $this->get_tour()->count_steps();
$this->sortorder = $count;
return $this;
}
/**
* Save the tour and it's configuration to the database.
*
* @param boolean $force Whether to force writing to the database.
* @return $this
*/
public function persist($force = false) {
global $DB;
if (!$this->dirty && !$force) {
return $this;
}
if ($this->id) {
$record = $this->to_record();
$DB->update_record('tool_usertours_steps', $record);
} else {
$this->calculate_sortorder();
$record = $this->to_record();
unset($record->id);
$this->id = $DB->insert_record('tool_usertours_steps', $record);
}
$this->reload();
return $this;
}
/**
* Remove this step.
*/
public function remove() {
global $DB;
if ($this->id === null) {
return;
}
$DB->delete_records('tool_usertours_steps', array('id' => $this->id));
$this->get_tour()->reset_step_sortorder();
}
/**
* Get the list of possible placement options.
*
* @return array
*/
public function get_placement_options() {
return configuration::get_placement_options(true);
}
/**
* The list of possible configuration keys.
*
* @return array
*/
public static function get_config_keys() {
return [
'placement',
'orphan',
'backdrop',
'reflex',
];
}
/**
* Add the step configuration to the form.
*
* @param MoodleQuickForm $mform The form to add configuration to.
* @return $this
*/
public function add_config_to_form(\MoodleQuickForm $mform) {
$tour = $this->get_tour();
$options = configuration::get_placement_options($tour->get_config('placement'));
$mform->addElement('select', 'placement', get_string('placement', 'tool_usertours'), $options);
$mform->addHelpButton('placement', 'placement', 'tool_usertours');
$this->add_config_field_to_form($mform, 'orphan');
$this->add_config_field_to_form($mform, 'backdrop');
$this->add_config_field_to_form($mform, 'reflex');
return $this;
}
/**
* Add the specified step field configuration to the form.
*
* @param MoodleQuickForm $mform The form to add configuration to.
* @param string $key The key to add.
* @return $this
*/
public function add_config_field_to_form(\MoodleQuickForm $mform, $key) {
$tour = $this->get_tour();
$default = (bool) $tour->get_config($key);
$options = [
true => get_string('yes'),
false => get_string('no'),
];
if (!isset($options[$default])) {
$default = configuration::get_default_value($key);
}
$options = array_reverse($options, true);
$options[configuration::TOURDEFAULT] = get_string('defaultvalue', 'tool_usertours', $options[$default]);
$options = array_reverse($options, true);
$mform->addElement('select', $key, get_string($key, 'tool_usertours'), $options);
$mform->setDefault($key, configuration::TOURDEFAULT);
$mform->addHelpButton($key, $key, 'tool_usertours');
return $this;
}
/**
* Prepare the configuration data for the moodle form.
*
* @return object
*/
public function prepare_data_for_form() {
$data = $this->to_record();
foreach (self::get_config_keys() as $key) {
$data->$key = $this->get_config($key, configuration::get_step_default_value($key));
}
return $data;
}
/**
* Handle submission of the step editing form.
*
* @param local\forms\editstep $mform The sumitted form.
* @param stdClass $data The submitted data.
* @return $this
*/
public function handle_form_submission(local\forms\editstep &$mform, \stdClass $data) {
$this->set_title($data->title);
$this->set_content($data->content);
$this->set_targettype($data->targettype);
$this->set_targetvalue($this->get_target()->get_value_from_form($data));
foreach (self::get_config_keys() as $key) {
if (!$this->get_target()->is_setting_forced($key)) {
if (isset($data->$key)) {
$value = $data->$key;
} else {
$value = configuration::TOURDEFAULT;
}
if ($value === configuration::TOURDEFAULT) {
$this->set_config($key, null);
} else {
$this->set_config($key, $value);
}
}
}
$this->persist();
return $this;
}
}
+128
View File
@@ -0,0 +1,128 @@
<?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/>.
/**
* Target class.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_usertours;
defined('MOODLE_INTERNAL') || die();
/**
* Target class.
*
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class target {
/**
* @var TARGET_SELECTOR The target is a CSS selector.
*/
const TARGET_SELECTOR = 0;
/**
* @var TARGET_BLOCK The target is a block.
*/
const TARGET_BLOCK = 1;
/**
* @var TARGET_UNATTACHED The target is unattached to any specific node.
*/
const TARGET_UNATTACHED = 2;
/**
* @var array $mapping The list of target type to target name.
*/
protected static $mapping = [
self::TARGET_BLOCK => 'block',
self::TARGET_SELECTOR => 'selector',
self::TARGET_UNATTACHED => 'unattached',
];
/**
* Return the name of the class for this target type.
*
* @param int $type The type of target.
* @return string The class name.
*/
public static function get_classname($type) {
$targettype = self::$mapping[self::get_target_constant($type)];
return "\\tool_usertours\\local\\target\\{$targettype}";
}
/**
* Return the instance of the class for this target.
*
* @param step $step The step.
* @return target The target instance.
*/
public static function get_target_type(step $step) {
if (!isset(self::$mapping[$step->get_targettype()])) {
throw new \moodle_exception('Unknown Target type');
}
$targettype = self::$mapping[$step->get_targettype()];
return "\\tool_usertours\\local\\target\\{$targettype}";
}
/**
* Return the constant used to describe this target.
*
* @param string $type The type of the target.
* @return int The constant for this target.
*/
public static function get_target_constant($type) {
return array_search($type, self::$mapping);
}
/**
* Return the constant used to describe this class.
*
* @param string $classname The fully-qualified class name of the target
* @return int The constant for this target.
*/
public static function get_target_constant_for_class($classname) {
$rc = new \ReflectionClass($classname);
return self::get_target_constant($rc->getShortName());
}
/**
* Return the instance of the class for this target.
*
* @param step $step The step.
* @return target The target instance.
*/
public static function get_target_instance(step $step) {
$targetclass = self::get_target_type($step);
return new $targetclass($step);
}
/**
* Return the complete lits of target types.
*
* @return array
*/
public static function get_target_types() {
return self::$mapping;
}
}
+761
View File
@@ -0,0 +1,761 @@
<?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 class.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_usertours;
defined('MOODLE_INTERNAL') || die();
/**
* Tour class.
*
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class tour {
/**
* The tour is currently disabled
*
* @var DISABLED
*/
const DISABLED = 0;
/**
* The tour is currently disabled
*
* @var DISABLED
*/
const ENABLED = 1;
/**
* The user preference value to indicate the time of completion of the tour for a user.
*
* @var TOUR_LAST_COMPLETED_BY_USER
*/
const TOUR_LAST_COMPLETED_BY_USER = 'tool_usertours_tour_completion_time_';
/**
* The user preference value to indicate the time that a user last requested to see the tour.
*
* @var TOUR_REQUESTED_BY_USER
*/
const TOUR_REQUESTED_BY_USER = 'tool_usertours_tour_reset_time_';
/**
* @var $id The tour ID.
*/
protected $id;
/**
* @var $name The tour name.
*/
protected $name;
/**
* @var $description The tour description.
*/
protected $description;
/**
* @var $pathmatch The tour pathmatch.
*/
protected $pathmatch;
/**
* @var $enabled The tour enabled state.
*/
protected $enabled;
/**
* @var $sortorder The sort order.
*/
protected $sortorder;
/**
* @var $dirty Whether the current view of the tour has been modified.
*/
protected $dirty = false;
/**
* @var $config The configuration object for the tour.
*/
protected $config;
/**
* @var $filtervalues The filter configuration object for the tour.
*/
protected $filtervalues;
/**
* @var $steps The steps in this tour.
*/
protected $steps = [];
/**
* Create an instance of the specified tour.
*
* @param int $id The ID of the tour to load.
* @return tour
*/
public static function instance($id) {
$tour = new self();
return $tour->fetch($id);
}
/**
* Create an instance of tour from its provided DB record.
*
* @param stdClass $record The record of the tour to load.
* @param boolean $clean Clean the values.
* @return tour
*/
public static function load_from_record($record, $clean = false) {
$tour = new self();
return $tour->reload_from_record($record, $clean);
}
/**
* Fetch the specified tour into the current object.
*
* @param int $id The ID of the tour to fetch.
* @return tour
*/
protected function fetch($id) {
global $DB;
return $this->reload_from_record(
$DB->get_record('tool_usertours_tours', array('id' => $id), '*', MUST_EXIST)
);
}
/**
* Reload the current tour from database.
*
* @return tour
*/
protected function reload() {
return $this->fetch($this->id);
}
/**
* Reload the tour into the current object.
*
* @param stdClass $record The record to reload.
* @param boolean $clean Clean the values.
* @return tour
*/
protected function reload_from_record($record, $clean = false) {
$this->id = $record->id;
if (!property_exists($record, 'description')) {
if (property_exists($record, 'comment')) {
$record->description = $record->comment;
unset($record->comment);
}
}
if ($clean) {
$this->name = clean_param($record->name, PARAM_TEXT);
$this->description = clean_text($record->description);
} else {
$this->name = $record->name;
$this->description = $record->description;
}
$this->pathmatch = $record->pathmatch;
$this->enabled = $record->enabled;
if (isset($record->sortorder)) {
$this->sortorder = $record->sortorder;
}
$this->config = json_decode($record->configdata);
$this->dirty = false;
$this->steps = [];
return $this;
}
/**
* Fetch all steps in the tour.
*
* @return stdClass[]
*/
public function get_steps() {
if (empty($this->steps)) {
$this->steps = helper::get_steps($this->id);
}
return $this->steps;
}
/**
* Count the number of steps in the tour.
*
* @return int
*/
public function count_steps() {
return count($this->get_steps());
}
/**
* The ID of the tour.
*
* @return int
*/
public function get_id() {
return $this->id;
}
/**
* The name of the tour.
*
* @return string
*/
public function get_name() {
return $this->name;
}
/**
* Set the name of the tour to the specified value.
*
* @param string $value The new name.
* @return $this
*/
public function set_name($value) {
$this->name = clean_param($value, PARAM_TEXT);
$this->dirty = true;
return $this;
}
/**
* The description associated with the tour.
*
* @return string
*/
public function get_description() {
return $this->description;
}
/**
* Set the description of the tour to the specified value.
*
* @param string $value The new description.
* @return $this
*/
public function set_description($value) {
$this->description = clean_text($value);
$this->dirty = true;
return $this;
}
/**
* The path match for the tour.
*
* @return string
*/
public function get_pathmatch() {
return $this->pathmatch;
}
/**
* Set the patchmatch of the tour to the specified value.
*
* @param string $value The new patchmatch.
* @return $this
*/
public function set_pathmatch($value) {
$this->pathmatch = $value;
$this->dirty = true;
return $this;
}
/**
* The enabled state of the tour.
*
* @return int
*/
public function get_enabled() {
return $this->enabled;
}
/**
* Whether the tour is currently enabled.
*
* @return boolean
*/
public function is_enabled() {
return ($this->enabled == self::ENABLED);
}
/**
* Set the enabled state of the tour to the specified value.
*
* @param boolean $value The new state.
* @return $this
*/
public function set_enabled($value) {
$this->enabled = $value;
$this->dirty = true;
return $this;
}
/**
* The link to view this tour.
*
* @return moodle_url
*/
public function get_view_link() {
return helper::get_view_tour_link($this->id);
}
/**
* The link to edit this tour.
*
* @return moodle_url
*/
public function get_edit_link() {
return helper::get_edit_tour_link($this->id);
}
/**
* The link to reset the state of this tour for all users.
*
* @return moodle_url
*/
public function get_reset_link() {
return helper::get_reset_tour_for_all_link($this->id);
}
/**
* The link to export this tour.
*
* @return moodle_url
*/
public function get_export_link() {
return helper::get_export_tour_link($this->id);
}
/**
* The link to remove this tour.
*
* @return moodle_url
*/
public function get_delete_link() {
return helper::get_delete_tour_link($this->id);
}
/**
* Prepare this tour for saving to the database.
*
* @return object
*/
public function to_record() {
return (object) array(
'id' => $this->id,
'name' => $this->name,
'description' => $this->description,
'pathmatch' => $this->pathmatch,
'enabled' => $this->enabled,
'sortorder' => $this->sortorder,
'configdata' => json_encode($this->config),
);
}
/**
* Get the current sortorder for this tour.
*
* @return int
*/
public function get_sortorder() {
return (int) $this->sortorder;
}
/**
* Whether this tour is the first tour.
*
* @return boolean
*/
public function is_first_tour() {
return ($this->get_sortorder() === 0);
}
/**
* Whether this tour is the last tour.
*
* @param int $tourcount The pre-fetched count of tours
* @return boolean
*/
public function is_last_tour($tourcount = null) {
if ($tourcount === null) {
$tourcount = helper::count_tours();
}
return ($this->get_sortorder() === ($tourcount - 1));
}
/**
* Set the sortorder for this tour.
*
* @param int $value The new sortorder to use.
* @return $this
*/
public function set_sortorder($value) {
$this->sortorder = $value;
$this->dirty = true;
return $this;
}
/**
* Calculate the next sort-order value.
*
* @return int
*/
protected function calculate_sortorder() {
$this->sortorder = helper::count_tours();
return $this;
}
/**
* Get the link to move this tour up in the sortorder.
*
* @return moodle_url
*/
public function get_moveup_link() {
return helper::get_move_tour_link($this->get_id(), helper::MOVE_UP);
}
/**
* Get the link to move this tour down in the sortorder.
*
* @return moodle_url
*/
public function get_movedown_link() {
return helper::get_move_tour_link($this->get_id(), helper::MOVE_DOWN);
}
/**
* Get the value of the specified configuration item.
*
* @param string $key The configuration key to set.
* @param mixed $default The default value to use if a value was not found.
* @return mixed
*/
public function get_config($key = null, $default = null) {
if ($this->config === null) {
$this->config = (object) array();
}
if ($key === null) {
return $this->config;
}
if (property_exists($this->config, $key)) {
return $this->config->$key;
}
if ($default !== null) {
return $default;
}
return configuration::get_default_value($key);
}
/**
* Set the configuration item as specified.
*
* @param string $key The configuration key to set.
* @param mixed $value The new value for the configuration item.
* @return $this
*/
public function set_config($key, $value) {
if ($this->config === null) {
$this->config = (object) array();
}
$this->config->$key = $value;
$this->dirty = true;
return $this;
}
/**
* Save the tour and it's configuration to the database.
*
* @param boolean $force Whether to force writing to the database.
* @return $this
*/
public function persist($force = false) {
global $DB;
if (!$this->dirty && !$force) {
return $this;
}
if ($this->id) {
$record = $this->to_record();
$DB->update_record('tool_usertours_tours', $record);
} else {
$this->calculate_sortorder();
$record = $this->to_record();
unset($record->id);
$this->id = $DB->insert_record('tool_usertours_tours', $record);
}
$this->reload();
return $this;
}
/**
* Remove this step.
*/
public function remove() {
global $DB;
if ($this->id === null) {
// Nothing to delete - this tour has not been persisted.
return null;
}
// Delete all steps associated with this tour.
// Note, although they are currently just DB records, there may be other components in the future.
foreach ($this->get_steps() as $step) {
$step->remove();
}
// Remove the configuration for the tour.
$DB->delete_records('tool_usertours_tours', array('id' => $this->id));
helper::reset_tour_sortorder();
return null;
}
/**
* Reset the sortorder for all steps in the tour.
*
* @return $this
*/
public function reset_step_sortorder() {
global $DB;
$steps = $DB->get_records('tool_usertours_steps', array('tourid' => $this->id), 'sortorder ASC', 'id');
$index = 0;
foreach ($steps as $step) {
$DB->set_field('tool_usertours_steps', 'sortorder', $index, array('id' => $step->id));
$index++;
}
return $this;
}
/**
* Whether this tour should be displayed to the user.
*
* @return boolean
*/
public function should_show_for_user() {
if (!$this->is_enabled()) {
// The tour is disabled - it should not be shown.
return false;
}
if ($tourcompletiondate = get_user_preferences(self::TOUR_LAST_COMPLETED_BY_USER . $this->get_id(), null)) {
if ($tourresetdate = get_user_preferences(self::TOUR_REQUESTED_BY_USER . $this->get_id(), null)) {
if ($tourresetdate >= $tourcompletiondate) {
return true;
}
}
$lastmajorupdate = $this->get_config('majorupdatetime', time());
if ($tourcompletiondate > $lastmajorupdate) {
// The user has completed the tour since the last major update.
return false;
}
}
return true;
}
/**
* Get the key for this tour.
* This is used in the session cookie to determine whether the user has seen this tour before.
*/
public function get_tour_key() {
$tourtime = $this->get_config('majorupdatetime', null);
if ($tourtime === null) {
// This tour has no majorupdate time.
// Set one now to prevent repeated displays to the user.
$this->set_config('majorupdatetime', time());
$this->persist();
$tourtime = $this->get_config('majorupdatetime', null);
}
if ($userresetdate = get_user_preferences(self::TOUR_REQUESTED_BY_USER . $this->get_id(), null)) {
$tourtime = max($tourtime, $userresetdate);
}
return sprintf('tool_usertours_%d_%s', $this->get_id(), $tourtime);
}
/**
* Reset the requested by user date.
*
* @return $this
*/
public function request_user_reset() {
set_user_preference(self::TOUR_REQUESTED_BY_USER . $this->get_id(), time());
return $this;
}
/**
* Mark this tour as completed for this user.
*
* @return $this
*/
public function mark_user_completed() {
set_user_preference(self::TOUR_LAST_COMPLETED_BY_USER . $this->get_id(), time());
return $this;
}
/**
* Update a tour giving it a new major update time.
* This will ensure that it is displayed to all users, even those who have already seen it.
*
* @return $this
*/
public function mark_major_change() {
global $DB;
// Clear old reset and completion notes.
$DB->delete_records('user_preferences', ['name' => self::TOUR_LAST_COMPLETED_BY_USER . $this->get_id()]);
$DB->delete_records('user_preferences', ['name' => self::TOUR_REQUESTED_BY_USER . $this->get_id()]);
$this->set_config('majorupdatetime', time());
$this->persist();
return $this;
}
/**
* Add the step configuration to the form.
*
* @param MoodleQuickForm $mform The form to add configuration to.
* @return $this
*/
public function add_config_to_form(\MoodleQuickForm &$mform) {
$options = configuration::get_placement_options();
$mform->addElement('select', 'placement', get_string('placement', 'tool_usertours'), $options);
$mform->addHelpButton('placement', 'placement', 'tool_usertours');
$this->add_config_field_to_form($mform, 'orphan');
$this->add_config_field_to_form($mform, 'backdrop');
$this->add_config_field_to_form($mform, 'reflex');
return $this;
}
/**
* Add the specified step field configuration to the form.
*
* @param MoodleQuickForm $mform The form to add configuration to.
* @param string $key The key to add.
* @return $this
*/
protected function add_config_field_to_form(\MoodleQuickForm &$mform, $key) {
$options = [
true => get_string('yes'),
false => get_string('no'),
];
$mform->addElement('select', $key, get_string($key, 'tool_usertours'), $options);
$mform->setDefault($key, configuration::get_default_value($key));
$mform->addHelpButton($key, $key, 'tool_usertours');
return $this;
}
/**
* Prepare the configuration data for the moodle form.
*
* @return object
*/
public function prepare_data_for_form() {
$data = $this->to_record();
foreach (configuration::get_defaultable_keys() as $key) {
$data->$key = $this->get_config($key, configuration::get_default_value($key));
}
return $data;
}
/**
* Get the configured filter values.
*
* @param string $filter The filter to retrieve values for.
* @return array
*/
public function get_filter_values($filter) {
if ($allvalues = (array) $this->get_config('filtervalues')) {
if (isset($allvalues[$filter])) {
return $allvalues[$filter];
}
}
return [];
}
/**
* Set the values for the specified filter.
*
* @param string $filter The filter to set.
* @param array $values The values to set.
* @return $this
*/
public function set_filter_values($filter, array $values = []) {
$allvalues = (array) $this->get_config('filtervalues', []);
$allvalues[$filter] = $values;
return $this->set_config('filtervalues', $allvalues);
}
/**
* Check whether this tour matches all filters.
*
* @param context $context The context to check
* @return bool
*/
public function matches_all_filters(\context $context) {
$filters = helper::get_all_filters();
// All filters must match.
// If any one filter fails to match, we return false.
foreach ($filters as $filterclass) {
if (!$filterclass::filter_matches($this, $context)) {
return false;
}
}
return true;
}
}
+35
View File
@@ -0,0 +1,35 @@
<?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/>.
/**
* Configuration page.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once('../../../config.php');
require_once($CFG->libdir . '/adminlib.php');
$action = optional_param('action', \tool_usertours\manager::ACTION_LISTTOURS, PARAM_ALPHANUMEXT);
$pluginmanager = new \tool_usertours\manager();
$PAGE->set_context(context_system::instance());
$pluginmanager->execute(
$action
);
+36
View File
@@ -0,0 +1,36 @@
<?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/>.
/**
* Plugin capabilities.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$capabilities = array(
'tool/usertours:managetours' => [
'captype' => 'write',
'riskbitmask' => RISK_XSS,
'contextlevel' => CONTEXT_SYSTEM,
'archetypes' => [
'manager' => CAP_ALLOW,
]
],
);
+74
View File
@@ -0,0 +1,74 @@
<?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/>.
/**
* Install code for tours.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
/**
* Perform the post-install procedures.
*/
function xmldb_tool_usertours_install() {
global $DB;
$localplugin = core_plugin_manager::instance()->get_plugin_info('local_usertours');
if ($localplugin) {
// If the old local plugin was previously installed, copy over the data from the old tables.
// The 'comment' field was renamed to 'description' in:
// * 3.0 version 2015111604
// * 3.1 version 2016052303
// We need to attempt to fetch comment for these older versions.
$hasdescription = ($localplugin->versiondb < 2016052301 && $localplugin->versiondb >= 2015111604);
$hasdescription = $hasdescription || ($localplugin->versiondb > 2016052303);
$tours = $DB->get_recordset('usertours_tours');
$mapping = [];
foreach ($tours as $tour) {
if (!$hasdescription) {
if (property_exists($tour, 'comment')) {
$tour->description = $tour->comment;
unset($tour->comment);
} else {
$tour->description = '';
}
}
$mapping[$tour->id] = $DB->insert_record('tool_usertours_tours', $tour);
}
$tours->close();
$steps = $DB->get_recordset('usertours_steps');
foreach ($steps as $step) {
if (!isset($mapping[$step->tourid])) {
// Skip this one. It has somehow become orphaned.
continue;
}
$step->tourid = $mapping[$step->tourid];
$DB->insert_record('tool_usertours_steps', $step);
}
$steps->close();
// Delete the old records.
$DB->delete_records('usertours_steps', null);
$DB->delete_records('usertours_tours', null);
}
}
+41
View File
@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8" ?>
<XMLDB PATH="tool/usertours/db" VERSION="20160830" COMMENT="XMLDB file for Moodle tool/usertours"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../../lib/xmldb/xmldb.xsd"
>
<TABLES>
<TABLE NAME="tool_usertours_tours" COMMENT="List of tours">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="name" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" COMMENT="Name of the user tour"/>
<FIELD NAME="description" TYPE="text" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="pathmatch" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="enabled" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="sortorder" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="configdata" TYPE="text" NOTNULL="true" SEQUENCE="false"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
</KEYS>
</TABLE>
<TABLE NAME="tool_usertours_steps" COMMENT="Steps in an tour">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="tourid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="title" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="Title of the step"/>
<FIELD NAME="content" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="Content of the user tour - allow for multilang tags"/>
<FIELD NAME="targettype" TYPE="int" LENGTH="2" NOTNULL="true" SEQUENCE="false" COMMENT="Type of the target (e.g. block, CSS selector, etc.)"/>
<FIELD NAME="targetvalue" TYPE="text" NOTNULL="true" SEQUENCE="false" COMMENT="The value for the specified target type."/>
<FIELD NAME="sortorder" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="configdata" TYPE="text" NOTNULL="true" SEQUENCE="false"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="tourid-tour" TYPE="foreign" FIELDS="tourid" REFTABLE="tool_usertours_tours" REFFIELDS="id"/>
</KEYS>
<INDEXES>
<INDEX NAME="orderedsteps" UNIQUE="false" FIELDS="tourid, sortorder"/>
</INDEXES>
</TABLE>
</TABLES>
</XMLDB>
+63
View File
@@ -0,0 +1,63 @@
<?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/>.
/**
* List of Web Services for the tool_usertours plugin.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$functions = array(
'tool_usertours_fetch_and_start_tour' => array(
'classname' => 'tool_usertours\external\tour',
'methodname' => 'fetch_and_start_tour',
'description' => 'Fetch the specified tour',
'type' => 'read',
'capabilities' => '',
'ajax' => true,
),
'tool_usertours_step_shown' => array(
'classname' => 'tool_usertours\external\tour',
'methodname' => 'step_shown',
'description' => 'Mark the specified step as completed for the current user',
'type' => 'write',
'capabilities' => '',
'ajax' => true,
),
'tool_usertours_complete_tour' => array(
'classname' => 'tool_usertours\external\tour',
'methodname' => 'complete_tour',
'description' => 'Mark the specified tour as completed for the current user',
'type' => 'write',
'capabilities' => '',
'ajax' => true,
),
'tool_usertours_reset_tour' => array(
'classname' => 'tool_usertours\external\tour',
'methodname' => 'reset_tour',
'description' => 'Remove the specified tour',
'type' => 'write',
'capabilities' => '',
'ajax' => true,
),
);
@@ -0,0 +1,162 @@
<?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/>.
/**
* Strings for tool_usertours.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['actions'] = 'Actions';
$string['appliesto'] = 'Applies to';
$string['block'] = 'Block';
$string['block_named'] = 'Block named \'{$a}\'';
$string['bottom'] = 'Bottom';
$string['description'] = 'Description';
$string['confirmstepremovalquestion'] = 'Are you sure that you wish to remove this step?';
$string['confirmstepremovaltitle'] = 'Confirm step removal';
$string['confirmtourremovalquestion'] = 'Are you sure that you wish to remove this tour?';
$string['confirmtourremovaltitle'] = 'Confirm tour removal';
$string['content'] = 'Content';
$string['content_help'] = 'This is the content of the step.
You can enter a content in the following formats:
<dl>
<dt>Plain text</dt>
<dd>A plain text description</dd>
<dt>Moodle MultiLang</dt>
<dd>A string which makes use of the Moodle MultiLang format</dd>
<dt>Moodle Translated string</dt>
<dd>A value found in a standard Moodle language file in the format identifier,component</dd>
</dl>';
$string['cssselector'] = 'CSS Selector';
$string['defaultvalue'] = 'Default ({$a})';
$string['delay'] = 'Delay before showing the step';
$string['done'] = 'Done';
$string['editstep'] = 'Editing "{$a}"';
$string['enabled'] = 'Enabled';
$string['event_tour_started'] = 'Tour started';
$string['event_tour_reset'] = 'Tour reset';
$string['event_tour_ended'] = 'Tour ended';
$string['event_step_shown'] = 'Step shown';
$string['exporttour'] = 'Export tour';
$string['filter_header'] = 'Tour filters';
$string['filter_help'] = 'Your can choose which conditions your tour will be shown under.
All of the filters must match for a tour to be shown to that user.';
$string['filter_theme'] = 'Theme';
$string['filter_theme_help'] = 'Show the tour when the user is using one of the selected themes.';
$string['filter_role'] = 'Role';
$string['filter_role_help'] = 'Only show the tour to users with one of the specified roles.';
$string['importtour'] = 'Import tour';
$string['left'] = 'Left';
$string['movestepdown'] = 'Move step down';
$string['movestepup'] = 'Move step up';
$string['movetourdown'] = 'Move tour down';
$string['movetourup'] = 'Move tour up';
$string['name'] = 'Name';
$string['newstep'] = 'Create step';
$string['newstep'] = 'New step';
$string['newtour'] = 'Create a new tour';
$string['next'] = 'Next';
$string['pathmatch'] = 'Apply to URL match';
$string['pathmatch_help'] = 'Tours will be displayed on any page whose URL matches this value.
You can use the % character as a wildcard to mean anything.
Some example values include:
* /my/% - to match the Dashboard
* /course/view.php?id=2 - to match a specific course
* /mod/forum/view.php% - to match the forum discussion list
* /user/profile.php% - to match the user profile page';
$string['placement'] = 'Placement';
$string['pluginname'] = 'User Tours';
$string['resettouronpage'] = 'Reset user tour on this page';
$string['right'] = 'Right';
$string['select_block'] = 'Select a block';
$string['select_targettype'] = 'Every step is associated with a part of the page which you must choose. To make this easier there are several types of target for different types of page content.
<dl>
<dt>Block</dt>
<dd>Display the step next to the first matching block of the type on the page</dd>
<dt>Selector</dt>
<dd>CSS Selectors are a powerful way which allow you to select different parts of the page based on metadata built into the page.</dd>
<dt>Display in middle of the page</dt>
<dd>Instead of associating the step with a specific part of the page you can have it displayed in the middle of the page.</dd>
</dl>';
$string['selector_defaulttitle'] = 'Enter a descriptive title';
$string['selectordisplayname'] = 'A CSS selector matching \'{$a}\'';
$string['skip'] = 'Skip';
$string['target'] = 'Target';
$string['target_block'] = 'Block';
$string['target_selector'] = 'Selector';
$string['target_unattached'] = 'Display in middle of page';
$string['targettype'] = 'Target type';
$string['title'] = 'Title';
$string['title_help'] = 'This is the title shown at the top of the step.
You can enter a title in the following formats:
<dl>
<dt>Plain text</dt>
<dd>A plain text description</dd>
<dt>Moodle MultiLang</dt>
<dd>A string which makes use of the Moodle MultiLang format</dd>
<dt>Moodle Translated string</dt>
<dd>A value found in a standard Moodle language file in the format identifier,component</dd>
</dl>';
$string['top'] = 'Top';
$string['tourconfig'] = 'Tour configuration file to import';
$string['tourlist_explanation'] = 'You can create as many tours as you like and enable them for different parts of Moodle. Only one tour can be created per page.';
$string['tours'] = 'Tours';
$string['pausetour'] = 'Pause';
$string['resumetour'] = 'Resume';
$string['endtour'] = 'End tour';
$string['orphan'] = 'Show if target not found';
$string['orphan_help'] = 'Show the step if the target could not be found on the page.';
$string['backdrop'] = 'Show with backdrop';
$string['backdrop_help'] = 'You can use a backdrop to highlight the part of the page that you are pointing to.
Note: Backdrops are not compatible with some parts of the page such as the navigation bar.
';
$string['reflex'] = 'Move on click';
$string['reflex_help'] = 'Move on to the next step when the target is clicked on.';
$string['placement_help'] = 'You can place a step either above, below, to the left of, or to the right of the target.
The best options are top, or bottom as these adjust better for mobile display.';
$string['delay_help'] = 'You can optionally choose to add a delay before the step is displayed.
This delay is in milliseconds.';
$string['selecttype'] = 'Select step type';
$string['sharedtourslink'] = 'Tour repository';
$string['usertours'] = 'User tours';
$string['usertours:managetours'] = 'Create, edit, and remove user tours';
$string['target_selector_targetvalue'] = 'CSS Selectors';
$string['target_selector_targetvalue_help'] = 'You can use a "CSS Selector" to target almost any element on the page.
CSS Selectors are very powerful and you can easily find parts of the page by building up the selector gradually.
Mozilla provide some [very good
documentation](https://developer.mozilla.org/en/docs/Web/Guide/CSS/Getting_started/Selectors)
for selectors which may help you to build your selectors.
You will also find your browser\'s developer tools to be extremely useful in creating these selectors:
* [Google Chrome](https://developer.chrome.com/devtools#dom-and-styles)
* [Mozilla Firefox](https://developer.mozilla.org/en-US/docs/Tools/DOM_Property_Viewer)
* [Microsoft Edge](https://developer.microsoft.com/en-us/microsoft-edge/platform/documentation/f12-devtools-guide/)
* [Apple Safari](https://developer.apple.com/library/iad/documentation/AppleApplications/Conceptual/Safari_Developer_Guide/ResourcesandtheDOM/ResourcesandtheDOM.html#//apple_ref/doc/uid/TP40007874-CH3-SW1)
';
$string['viewtour_info'] = 'This is the \'{$a->tourname}\' tour. It applies to the path \'{$a->path}\'.';
$string['viewtour_edit'] = 'You can <a href="{$a->editlink}">edit the tour defaults</a> and <a href="{$a->resetlink}">force the tour to be displayed</a> to all users again.';
$string['tour_resetforall'] = 'The state of the tour has been reset. It will be displayed to all users again.';
+69
View File
@@ -0,0 +1,69 @@
<?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.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
use tool_usertours\helper;
/**
* Manage inplace editable saves.
*
* @param string $itemtype The type of item.
* @param int $itemid The ID of the item.
* @param mixed $newvalue The new value
* @return string
*/
function tool_usertours_inplace_editable($itemtype, $itemid, $newvalue) {
$context = \context_system::instance();
external_api::validate_context($context);
require_capability('tool/usertours:managetours', $context);
if ($itemtype === 'tourname') {
$tour = helper::get_tour($itemid);
$tour->set_name($newvalue)->persist();
return helper::render_tourname_inplace_editable($tour);
} else if ($itemtype === 'tourdescription') {
$tour = helper::get_tour($itemid);
$tour->set_description($newvalue)->persist();
return helper::render_tourdescription_inplace_editable($tour);
} else if ($itemtype === 'tourenabled') {
$tour = helper::get_tour($itemid);
$tour->set_enabled(!!$newvalue)->persist();
return helper::render_tourenabled_inplace_editable($tour);
} else if ($itemtype === 'stepname') {
$step = helper::get_step($itemid);
$step->set_title($newvalue)->persist();
return helper::render_stepname_inplace_editable($step);
}
}
/**
* Extend the user navigation to bootstrap tours.
*/
function tool_usertours_extend_navigation_user() {
\tool_usertours\helper::bootstrap();
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 B

+3
View File
@@ -0,0 +1,3 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" preserveAspectRatio="xMinYMid meet" overflow="visible"><path d="M7.9 16c4 0 7.4-3 7.8-7h-3c-.5 2-2.5 4-4.9 4-2.8 0-5-2.2-5-5s2.2-5 5-5c1.1 0 2.1 0 3 1h-1c-.5 0-.8.5-.8 1.1v1c0 .5.3.9.8.9h5c.6 0 1.2-.4 1.2-.9v-5c0-.6-.6-1.1-1.2-1.1h-1c-.5 0-.8.5-.8 1.1V2C12 .9 9.9.2 8 .2 3.6.2 0 3.7 0 8.1 0 12.4 3.5 16 7.9 16z" fill="#999"/></svg>

After

Width:  |  Height:  |  Size: 571 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 B

+16
View File
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="12px" height="12px" viewBox="0 0 12 12" style="overflow:visible;enable-background:new 0 0 12 12;"
xml:space="preserve" preserveAspectRatio="xMinYMid meet">
<defs>
</defs>
<path style="fill:#999999;" d="M12,7v4c0,0.5-0.5,1-1,1h-0.5l0,0H1c-0.5,0-1-0.5-1-1V7c0-0.5,0.5-1,1-1h0.5c0.5,0,1,0.5,1,1l0,2.5h7
V7c0-0.5,0.5-1,1-1H11C11.5,6,12,6.5,12,7z M9.9,3.6L6.6,0.3C6.4,0.1,6.2,0,5.9,0C5.7,0,5.4,0.1,5.2,0.3L1.9,3.6
C1.5,4,1.5,4.6,1.9,5l0.3,0.3c0.4,0.4,1,0.4,1.4,0l1-1.1l0,2.7c0,0.5,0.5,1,1,1l0.5,0c0.5,0,1-0.5,1-1l0-2.7l1,1
c0.4,0.4,1,0.4,1.4,0L10,5C10.3,4.6,10.3,4,9.9,3.6z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

+12
View File
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="12px" height="12px" viewBox="0 0 12 12" style="overflow:visible;enable-background:new 0 0 12 12;"
xml:space="preserve" preserveAspectRatio="xMinYMid meet">
<defs>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 637 B

+40
View File
@@ -0,0 +1,40 @@
<?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/>.
/**
* Settings page.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
$capabilities = [
'tool/usertours:managetours',
];
if ($hassiteconfig || has_any_capability($capabilities, context_system::instance())) {
$ADMIN->add(
'appearance',
new admin_externalpage(
'tool_usertours/tours',
get_string('usertours', 'tool_usertours'),
new moodle_url('/admin/tool/usertours/configure.php'),
'tool/usertours:managetours'
)
);
}
+68
View File
@@ -0,0 +1,68 @@
div[data-flexitour="backdrop"] {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
span[data-flexitour="container"].orphan {
position: absolute;
}
span[data-flexitour="container"].orphan div[data-role="arrow"] {
display: none;
}
body div[data-flexitour="step-background-fader"],
body div[data-flexitour="step-background"] {
position: absolute;
background: inherit;
}
div[data-flexitour="step-background-fader"],
[data-flexitour="step-backdrop"] > td,
[data-flexitour="step-backdrop"] {
position: relative;
}
span[data-flexitour="container"].orphan div[data-role="arrow"] {
display: none;
}
.path-admin-local-usertours .stepcreator .input-append {
font-size: inherit;
}
.tour-actions > ul {
margin-left: auto;
margin-right: auto;
text-align: center;
}
.tour-actions > ul > li {
background-color: #f5f5f5;
}
.tour-actions > ul {
margin-left: auto;
margin-right: auto;
text-align: center;
}
.tour-actions > ul > li {
background-color: #f5f5f5;
border-radius: 6px;
border: 1px solid #ccc;
display: inline-block;
height: 9em;
margin: .5em;
text-align: center;
width: 13em;
}
.tour-actions > ul > li > a {
height: 100%;
display: block;
padding: 1em;
}
@@ -0,0 +1,60 @@
{{!
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/>.
}}
{{!
@template tool_usertours/selecttarget
Template for selecting the type of step target in a dialogue.
Classes required for JS:
* none
Data attributes required for JS:
* none
Context variables required for this template:
* targetTypes [object]
** type
** title
Example context (json):
{
"targetType": [
{
type: 1,
title: "CSS Selector"
}
]
}
}}
<span class="stepcreator" data-step="selecttarget">
<form>
<label for="select_targettype">{{# str }} select_targettype, tool_usertours {{/ str }}</label>
<div class="input-append">
<select name="targettype" id="select_targettype" class="input-xlarge">
{{# targetTypes }}
<option value="{{ type }}">
{{ title }}
</option>
{{/ targetTypes }}
</select>
<button class="btn next" type="submit">
{{# str }} next, tool_usertours {{/ str }}
</button>
</div>
</form>
</span>
@@ -0,0 +1,62 @@
{{!
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/>.
}}
{{!
@template tool_usertours/tourstep
Template used as a template to render step bubbles with their content
in a user tour.
Classes required for JS:
* arrow
* popover-title
* popover-content
* popover-navigation
* popover-title
Data attributes required for JS:
* data-role=prev
* data-role=next
* data-role=pause-resume
* data-pause-text
* data-resume-text
* data-role=end
* data-placeholder=body
* data-placeholder=title
Context variables required for this template:
* None
Example context (json):
{
}
}}
<div class="modal" data-role="flexitour-step">
<div data-role="arrow"></div>
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true" data-role="end">&times;</button>
<h3 data-placeholder="title"></h3>
</div>
<div class="modal-body" data-placeholder="body"></div>
<div class="modal-footer">
<div class="btn-group">
<button href="#" class="btn" data-role="previous">{{# str }} previous, moodle {{/ str }}</button>
<button href="#" class="btn btn-primary" data-role="next">{{# str }} next, moodle {{/ str }}</button>
</div>
<button class="btn" data-role="end"> {{# str }} endtour, tool_usertours {{/ str }} </button>
</div>
</div>
@@ -0,0 +1,92 @@
<?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/>.
/**
* User tour related steps definitions.
*
* @package tool_usertours
* @category test
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once(__DIR__ . '/../../../../../lib/behat/behat_base.php');
use Behat\Gherkin\Node\TableNode as TableNode;
/**
* User tour related steps definitions.
*
* @package tool_usertours
* @category test
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class behat_tool_usertours extends behat_base {
/**
* Add a new user tour.
*
* @Given /^I add a new user tour with:$/
* @param TableNode $table
*/
public function i_add_a_new_user_tour_with(TableNode $table) {
$this->execute('behat_tool_usertours::i_open_the_user_tour_settings_page');
$this->execute('behat_general::click_link', get_string('newtour', 'tool_usertours'));
// Fill form and post.
$this->execute('behat_forms::i_set_the_following_fields_to_these_values', $table);
$this->execute('behat_forms::press_button', get_string('savechanges', 'moodle'));
$this->execute('behat_general::i_wait_to_be_redirected');
}
/**
* Add new steps to a user tour.
*
* @Given /^I add steps to the "(?P<tour_name_string>(?:[^"]|\\")*)" tour:$/
* @param string $tourname The name of the tour to add steps to.
* @param TableNode $table
*/
public function i_add_steps_to_the_named_tour($tourname, TableNode $table) {
$this->execute('behat_tool_usertours::i_open_the_user_tour_settings_page');
$this->execute('behat_general::click_link', $this->escape($tourname));
foreach ($table->getHash() as $step) {
$this->execute('behat_general::click_link', get_string('newstep', 'tool_usertours'));
foreach ($step as $locator => $value) {
$this->execute('behat_forms::i_set_the_field_to', [$this->escape($locator), $this->escape($value)]);
}
$this->execute('behat_forms::press_button', get_string('savechanges', 'moodle'));
$this->execute('behat_general::i_wait_to_be_redirected');
}
}
/**
* Navigate to the user tour settings page.
*
* @Given /^I open the User tour settings page$/
*/
public function i_open_the_user_tour_settings_page() {
$this->execute('behat_navigation::i_navigate_to_node_in', [
get_string('usertours', 'tool_usertours'),
implode(' > ', [
get_string('administrationsite', 'moodle'),
get_string('appearance', 'admin'),
])
]);
}
}
@@ -0,0 +1,81 @@
@tool @tool_usertours
Feature: Add a new user tour
In order to help users learn of new features
As an administrator
I need to create a user tour
@javascript
Scenario: Add a new tour
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 | My first tour |
| Apply to URL match | /my/% |
| Enabled | 1 |
And I add steps to the "First tour" tour:
| targettype | Title | Content |
| Display in middle of page | Welcome | Welcome to your personal learning space. We'd like to give you a quick tour to show you some of the areas you may find helpful |
And I add steps to the "First tour" tour:
| targettype | targetvalue_block | Title | Content |
| Block | Course overview | Course overview | This area shows you what's happening in some of your courses |
| Block | Calendar | Calendar | This is the Calendar. All of your assignments and due dates can be found here |
And I add steps to the "First tour" tour:
| targettype | targetvalue_selector | Title | Content |
| Selector | .usermenu | User menu | This is your personal user menu. You'll find your personal preferences and your user profile here. |
When I click on "Dashboard" "link" in the "Navigation" "block"
Then I should see "Welcome to your personal learning space. We'd like to give you a quick tour to show you some of the areas you may find helpful"
And I press "Next"
And I should see "This area shows you what's happening in some of your courses"
And I should not see "This is the Calendar. All of your assignments and due dates can be found here"
And I press "Next"
And I should see "This is the Calendar. All of your assignments and due dates can be found here"
And I should not see "This area shows you what's happening in some of your courses"
And I press "Prev"
And I should not see "This is the Calendar. All of your assignments and due dates can be found here"
And I should see "This area shows you what's happening in some of your courses"
And I press "End tour"
And I should not see "This area shows you what's happening in some of your courses"
And I click on "Dashboard" "link" in the "Navigation" "block"
And I should not see "Welcome to your personal learning space. We'd like to give you a quick tour to show you some of the areas you may find helpful"
And I should not see "This area shows you what's happening in some of your courses"
And I follow "Reset user tour on this page"
And I should see "Welcome to your personal learning space. We'd like to give you a quick tour to show you some of the areas you may find helpful"
@javascript
Scenario: A hidden tour should not be visible
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 | My first tour |
| Apply to URL match | /my/% |
| Enabled | 0 |
And I add steps to the "First tour" tour:
| targettype | Title | Content |
| Display in middle of page | Welcome | Welcome to your personal learning space. We'd like to give you a quick tour to show you some of the areas you may find helpful |
When I click on "Dashboard" "link" in the "Navigation" "block"
Then I should not see "Welcome to your personal learning space. We'd like to give you a quick tour to show you some of the areas you may find helpful"
@javascript
Scenario: Tour visibility can be toggled
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 | My first tour |
| Apply to URL match | /my/% |
| Enabled | 0 |
And I add steps to the "First tour" tour:
| targettype | Title | Content |
| Display in middle of page | Welcome | Welcome to your personal learning space. We'd like to give you a quick tour to show you some of the areas you may find helpful |
And I open the User tour settings page
When I click on "Enable" "link" in the "My first tour" "table_row"
And I click on "Dashboard" "link" in the "Navigation" "block"
Then I should see "Welcome to your personal learning space. We'd like to give you a quick tour to show you some of the areas you may find helpful"
@@ -0,0 +1,62 @@
@tool @tool_usertours
Feature: Apply tour filters to a tour
In order to give more directed tours
As an administrator
I need to create a user tour with filters applied
@javascript
Scenario: Add a tour for a different theme
Given I log in as "admin"
And I add a new user tour with:
| Name | First tour |
| Description | My first tour |
| Apply to URL match | /my/% |
| Enabled | 1 |
| Theme | More |
And I add steps to the "First tour" tour:
| targettype | Title | Content |
| Display in middle of page | Welcome | Welcome to your personal learning space. We'd like to give you a quick tour to show you some of the areas you may find helpful |
When I click on "Dashboard" "link" in the "Navigation" "block"
Then I should not see "Welcome to your personal learning space. We'd like to give you a quick tour to show you some of the areas you may find helpful"
@javascript
Scenario: Add a tour for a specific role
Given the following "courses" exist:
| fullname | shortname | format | enablecompletion |
| Course 1 | C1 | topics | 1 |
And the following "users" exist:
| username |
| editor1 |
| teacher1 |
| student1 |
And the following "course enrolments" exist:
| user | course | role |
| editor1 | C1 | editingteacher |
| teacher1 | C1 | teacher |
| student1 | C1 | student |
And I log in as "admin"
And I add a new user tour with:
| Name | First tour |
| Description | My first tour |
| Apply to URL match | /course/view.php% |
| Enabled | 1 |
| Role | Student,Non-editing teacher |
And I add steps to the "First tour" tour:
| targettype | Title | Content |
| Display in middle of page | Welcome | Welcome to your course tour.|
And I log out
And I log in as "editor1"
And I am on site homepage
When I follow "Course 1"
Then I should not see "Welcome to your course tour."
And I log out
And I log in as "student1"
And I am on site homepage
And I follow "Course 1"
And I should see "Welcome to your course tour."
And I click on "End tour" "button"
And I log out
And I log in as "teacher1"
And I am on site homepage
And I follow "Course 1"
And I should see "Welcome to your course tour."
+280
View File
@@ -0,0 +1,280 @@
<?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/>.
/**
* Tests for manager.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->libdir . '/formslib.php');
/**
* Tests for step.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class manager_testcase extends advanced_testcase {
/**
* @var moodle_database
*/
protected $db;
/**
* Setup to store the DB reference.
*/
public function setUp() {
global $DB;
$this->db = $DB;
}
/**
* Tear down to restore the original DB reference.
*/
public function tearDown() {
global $DB;
$DB = $this->db;
}
/**
* Helper to mock the database.
*
* @return moodle_database
*/
public function mock_database() {
global $DB;
$DB = $this->getMockBuilder('moodle_database')->getMock();
return $DB;
}
/**
* Data provider to ensure that all modification actions require the session key.
*
* @return array
*/
public function sesskey_required_provider() {
$tourid = rand(1, 100);
$stepid = rand(1, 100);
return [
'Tour removal' => [
'delete_tour',
[$tourid],
],
'Step removal' => [
'delete_step',
[$stepid],
],
'Tour visibility' => [
'show_hide_tour',
[$tourid, true],
],
'Move step' => [
'move_step',
[$stepid],
],
];
}
/**
* Ensure that all modification actions require the session key.
*
* @dataProvider sesskey_required_provider
* @param string $function The function to test
* @param array $arguments The arguments to pass with it
*/
public function test_sesskey_required($function, $arguments) {
$manager = new \tool_usertours\manager();
$rc = new \ReflectionClass('\tool_usertours\manager');
$rcm = $rc->getMethod($function);
$rcm->setAccessible(true);
$this->setExpectedException('moodle_exception', 'A required parameter (sesskey) was missing');
$rcm->invokeArgs($manager, $arguments);
}
/**
* Data Provider for get_matching_tours tests.
*
* @return array
*/
public function get_matching_tours_provider() {
global $CFG;
$alltours = [
[
'pathmatch' => '/my/%',
'enabled' => false,
'name' => 'Failure',
'description' => '',
'configdata' => '',
],
[
'pathmatch' => '/my/%',
'enabled' => true,
'name' => 'My tour enabled',
'description' => '',
'configdata' => '',
],
[
'pathmatch' => '/my/%',
'enabled' => false,
'name' => 'Failure',
'description' => '',
'configdata' => '',
],
[
'pathmatch' => '/course/?id=%foo=bar',
'enabled' => false,
'name' => 'Failure',
'description' => '',
'configdata' => '',
],
[
'pathmatch' => '/course/?id=%foo=bar',
'enabled' => true,
'name' => 'course tour with additional params enabled',
'description' => '',
'configdata' => '',
],
[
'pathmatch' => '/course/?id=%foo=bar',
'enabled' => false,
'name' => 'Failure',
'description' => '',
'configdata' => '',
],
[
'pathmatch' => '/course/?id=%',
'enabled' => false,
'name' => 'Failure',
'description' => '',
'configdata' => '',
],
[
'pathmatch' => '/course/?id=%',
'enabled' => true,
'name' => 'course tour enabled',
'description' => '',
'configdata' => '',
],
[
'pathmatch' => '/course/?id=%',
'enabled' => false,
'name' => 'Failure',
'description' => '',
'configdata' => '',
],
];
return [
'No matches found' => [
$alltours,
$CFG->wwwroot . '/some/invalid/value',
null,
],
'Never return a disabled tour' => [
$alltours,
$CFG->wwwroot . '/my/index.php',
'My tour enabled',
],
'My not course' => [
$alltours,
$CFG->wwwroot . '/my/index.php',
'My tour enabled',
],
'My with params' => [
$alltours,
$CFG->wwwroot . '/my/index.php?id=42',
'My tour enabled',
],
'Course with params' => [
$alltours,
$CFG->wwwroot . '/course/?id=42',
'course tour enabled',
],
'Course with params and trailing content' => [
$alltours,
$CFG->wwwroot . '/course/?id=42&foo=bar',
'course tour with additional params enabled',
],
];
}
/**
* Tests for the get_matching_tours function.
*
* @dataProvider get_matching_tours_provider
* @param array $alltours The list of tours to insert
* @param string $url The URL to test
* @param string $expected The name of the expected matching tour
*/
public function test_get_matching_tours($alltours, $url, $expected) {
$this->resetAfterTest();
foreach ($alltours as $tourconfig) {
$tourconfig = (object) $tourconfig;
$tourconfig->id = null;
$tour = \tool_usertours\tour::load_from_record($tourconfig, true);
$tour->persist(true);
}
$match = \tool_usertours\manager::get_matching_tours(new moodle_url($url));
if ($expected === null) {
$this->assertNull($match);
} else {
$this->assertEquals($expected, $match->get_name());
}
}
/**
* Tests for the get_matching_tours function when requiring an upgrade
*
* @dataProvider get_matching_tours_provider
* @param array $alltours The list of tours to insert
* @param string $url The URL to test
* @param string $expected The name of the expected matching tour
*/
public function test_get_matching_tours_requires_upgrade($alltours, $url, $expected) {
$this->resetAfterTest();
global $CFG;
unset($CFG->version);
foreach ($alltours as $tourconfig) {
$tourconfig = (object) $tourconfig;
$tourconfig->id = null;
$tour = \tool_usertours\tour::load_from_record($tourconfig, true);
$tour->persist(true);
}
$this->assertNull(\tool_usertours\manager::get_matching_tours(new moodle_url($url)));
}
}
@@ -0,0 +1,188 @@
<?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/>.
/**
* Tests for role filter.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Tests for role filter.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class tool_usertours_role_filter_testcase extends advanced_testcase {
/**
* @var $course Test course
*/
protected $course;
/**
* @var $student Test student
*/
protected $student;
/**
* @var $teacher Test teacher
*/
protected $teacher;
/**
* @var $editingteacher Test editor
*/
protected $editingteacher;
/**
* @var $roles List of all roles
*/
protected $roles;
public function setUp() {
global $DB;
$this->resetAfterTest(true);
$generator = $this->getDataGenerator();
$this->course = $generator->create_course();
$this->roles = $DB->get_records_menu('role', [], null, 'shortname, id');
$this->testroles = ['student', 'teacher', 'editingteacher'];
foreach ($this->testroles as $role) {
$user = $this->$role = $generator->create_user();
$generator->enrol_user($user->id, $this->course->id, $this->roles[$role]);
}
}
/**
* Test the filter_matches function when any is set.
*/
public function test_filter_matches_any() {
$context = \context_course::instance($this->course->id);
// Note: No need to persist this tour.
$tour = new \tool_usertours\tour();
$tour->set_filter_values('role', []);
// Note: The role filter does not use the context.
foreach ($this->testroles as $role) {
$this->setUser($this->$role);
$this->assertTrue(\tool_usertours\local\filter\role::filter_matches($tour, $context));
}
// The admin should always be able to view too.
$this->setAdminUser();
$this->assertTrue(\tool_usertours\local\filter\role::filter_matches($tour, $context));
}
/**
* Test the filter_matches function when one role is set.
*/
public function test_filter_matches_single_role() {
$context = \context_course::instance($this->course->id);
$roles = [
$this->roles['student'],
];
// Note: No need to persist this tour.
$tour = new \tool_usertours\tour();
$tour->set_filter_values('role', $roles);
// Note: The role filter does not use the context.
foreach ($this->testroles as $role) {
$this->setUser($this->$role);
if ($role === 'student') {
$this->assertTrue(\tool_usertours\local\filter\role::filter_matches($tour, $context));
} else {
$this->assertFalse(\tool_usertours\local\filter\role::filter_matches($tour, $context));
}
}
// The admin should always be able to view too.
$this->setAdminUser();
$this->assertTrue(\tool_usertours\local\filter\role::filter_matches($tour, $context));
}
/**
* Test the filter_matches function when multiple roles are set.
*/
public function test_filter_matches_multiple_role() {
$context = \context_course::instance($this->course->id);
$roles = [
$this->roles['teacher'],
$this->roles['editingteacher'],
];
// Note: No need to persist this tour.
$tour = new \tool_usertours\tour();
$tour->set_filter_values('role', $roles);
// Note: The role filter does not use the context.
foreach ($this->testroles as $role) {
$this->setUser($this->$role);
if ($role === 'student') {
$this->assertFalse(\tool_usertours\local\filter\role::filter_matches($tour, $context));
} else {
$this->assertTrue(\tool_usertours\local\filter\role::filter_matches($tour, $context));
}
}
// The admin should always be able to view too.
$this->setAdminUser();
$this->assertTrue(\tool_usertours\local\filter\role::filter_matches($tour, $context));
}
/**
* Test the filter_matches function when one user has multiple roles.
*/
public function test_filter_matches_multiple_role_one_user() {
$context = \context_course::instance($this->course->id);
$roles = [
$this->roles['student'],
];
$this->getDataGenerator()->enrol_user($this->student->id, $this->course->id, $this->roles['teacher']);
// Note: No need to persist this tour.
$tour = new \tool_usertours\tour();
$tour->set_filter_values('role', $roles);
// Note: The role filter does not use the context.
foreach ($this->testroles as $role) {
$this->setUser($this->$role);
if ($role === 'student') {
$this->assertTrue(\tool_usertours\local\filter\role::filter_matches($tour, $context));
} else {
$this->assertFalse(\tool_usertours\local\filter\role::filter_matches($tour, $context));
}
}
// The admin should always be able to view too.
$this->setAdminUser();
$this->assertTrue(\tool_usertours\local\filter\role::filter_matches($tour, $context));
}
}
@@ -0,0 +1,84 @@
<?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/>.
/**
* Tests for step.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Tests for step.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class step_output_testcase extends advanced_testcase {
/**
* Data Provider for get_string_from_inpu.
*
* @return array
*/
public function get_string_from_input_provider() {
return [
'Text' => [
'example',
'example',
],
'Text which looks like a langstring' => [
'example,fakecomponent',
'example,fakecomponent',
],
'Text which is a langstring' => [
'administration,core',
'Administration',
],
'Text which is a langstring but uses "moodle" instead of "core"' => [
'administration,moodle',
'Administration',
],
'Text which is a langstring, but with extra whitespace' => [
' administration,moodle ',
'Administration',
],
'Looks like a langstring, but has incorrect space around comma' => [
'administration , moodle',
'administration , moodle',
],
];
}
/**
* Ensure that the get_string_from_input function returns translated
* strings correctly.
*
* @dataProvider get_string_from_input_provider
* @param string $string The string to test
* @param string $expected The expected result
*/
public function test_get_string_from_input($string, $expected) {
$rc = new ReflectionClass('\\tool_usertours\\output\\step');
$rcm = $rc->getMethod('get_string_from_input');
$rcm->setAccessible(true);
$this->assertEquals($expected, $rcm->invoke(null, $string));
}
}
+806
View File
@@ -0,0 +1,806 @@
<?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/>.
/**
* Tests for step.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->libdir . '/formslib.php');
/**
* Tests for step.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class step_testcase extends advanced_testcase {
/**
* @var moodle_database
*/
protected $db;
/**
* Setup to store the DB reference.
*/
public function setUp() {
global $DB;
$this->db = $DB;
}
/**
* Tear down to restore the original DB reference.
*/
public function tearDown() {
global $DB;
$DB = $this->db;
}
/**
* Helper to mock the database.
*
* @return moodle_database
*/
public function mock_database() {
global $DB;
$DB = $this->getMockBuilder('moodle_database')
->getMock()
;
return $DB;
}
/**
* Data provider for the dirty value tester.
*
* @return array
*/
public function dirty_value_provider() {
return [
'tourid' => [
'tourid',
[1],
],
'title' => [
'title',
['Lorem'],
],
'content' => [
'content',
['Lorem'],
],
'targettype' => [
'targettype',
['Lorem'],
],
'targetvalue' => [
'targetvalue',
['Lorem'],
],
'sortorder' => [
'sortorder',
[1],
],
'config' => [
'config',
['key', 'value'],
],
];
}
/**
* Test the fetch function.
*/
public function test_fetch() {
$step = $this->getMockBuilder(\tool_usertours\step::class)
->setMethods(['reload_from_record'])
->getMock()
;
$idretval = rand(1, 100);
$DB = $this->mock_database();
$DB->method('get_record')
->willReturn($idretval)
;
$retval = rand(1, 100);
$step->expects($this->once())
->method('reload_from_record')
->with($this->equalTo($idretval))
->wilLReturn($retval)
;
$rc = new \ReflectionClass(\tool_usertours\step::class);
$rcm = $rc->getMethod('fetch');
$rcm->setAccessible(true);
$id = rand(1, 100);
$this->assertEquals($retval, $rcm->invoke($step, 'fetch', $id));
}
/**
* Test that setters mark things as dirty.
*
* @dataProvider dirty_value_provider
* @param string $name The key to update
* @param string $value The value to set
*/
public function test_dirty_values($name, $value) {
$step = new \tool_usertours\step();
$method = 'set_' . $name;
call_user_func_array([$step, $method], $value);
$rc = new \ReflectionClass(\tool_usertours\step::class);
$rcp = $rc->getProperty('dirty');
$rcp->setAccessible(true);
$this->assertTrue($rcp->getValue($step));
}
/**
* Provider for is_first_step.
*
* @return array
*/
public function step_sortorder_provider() {
return [
[0, 5, true, false],
[1, 5, false, false],
[4, 5, false, true],
];
}
/**
* Test is_first_step.
*
* @dataProvider step_sortorder_provider
* @param int $sortorder The sortorder to check
* @param int $count Unused in this function
* @param bool $isfirst Whether this is the first step
* @param bool $islast Whether this is the last step
*/
public function test_is_first_step($sortorder, $count, $isfirst, $islast) {
$step = $this->getMockBuilder(\tool_usertours\step::class)
->setMethods(['get_sortorder'])
->getMock();
$step->expects($this->once())
->method('get_sortorder')
->willReturn($sortorder)
;
$this->assertEquals($isfirst, $step->is_first_step());
}
/**
* Test is_last_step.
*
* @dataProvider step_sortorder_provider
* @param int $sortorder The sortorder to check
* @param int $count Total number of steps for this test
* @param bool $isfirst Whether this is the first step
* @param bool $islast Whether this is the last step
*/
public function test_is_last_step($sortorder, $count, $isfirst, $islast) {
$step = $this->getMockBuilder(\tool_usertours\step::class)
->setMethods(['get_sortorder', 'get_tour'])
->getMock();
$tour = $this->getMockBuilder(\tool_usertours\tour::class)
->setMethods(['count_steps'])
->getMock();
$step->expects($this->once())
->method('get_tour')
->willReturn($tour)
;
$tour->expects($this->once())
->method('count_steps')
->willReturn($count)
;
$step->expects($this->once())
->method('get_sortorder')
->willReturn($sortorder)
;
$this->assertEquals($islast, $step->is_last_step());
}
/**
* Test get_config with no keys provided.
*/
public function test_get_config_no_keys() {
$step = new \tool_usertours\step();
$rc = new \ReflectionClass(\tool_usertours\step::class);
$rcp = $rc->getProperty('config');
$rcp->setAccessible(true);
$allvalues = (object) [
'some' => 'value',
'another' => 42,
'key' => [
'somethingelse',
],
];
$rcp->setValue($step, $allvalues);
$this->assertEquals($allvalues, $step->get_config());
}
/**
* Data provider for get_config.
*
* @return array
*/
public function get_config_provider() {
$allvalues = (object) [
'some' => 'value',
'another' => 42,
'key' => [
'somethingelse',
],
];
$tourconfig = rand(1, 100);
$forcedconfig = rand(1, 100);
return [
'No initial config' => [
null,
null,
null,
$tourconfig,
false,
$forcedconfig,
(object) [],
],
'All values' => [
$allvalues,
null,
null,
$tourconfig,
false,
$forcedconfig,
$allvalues,
],
'Valid string value' => [
$allvalues,
'some',
null,
$tourconfig,
false,
$forcedconfig,
'value',
],
'Valid array value' => [
$allvalues,
'key',
null,
$tourconfig,
false,
$forcedconfig,
['somethingelse'],
],
'Invalid value' => [
$allvalues,
'notavalue',
null,
$tourconfig,
false,
$forcedconfig,
$tourconfig,
],
'Configuration value' => [
$allvalues,
'placement',
null,
$tourconfig,
false,
$forcedconfig,
$tourconfig,
],
'Invalid value with default' => [
$allvalues,
'notavalue',
'somedefault',
$tourconfig,
false,
$forcedconfig,
'somedefault',
],
'Value forced at target' => [
$allvalues,
'somevalue',
'somedefault',
$tourconfig,
true,
$forcedconfig,
$forcedconfig,
],
];
}
/**
* Test get_config with valid keys provided.
*
* @dataProvider get_config_provider
* @param object $values The config values
* @param string $key The key
* @param mixed $default The default value
* @param mixed $tourconfig The tour config
* @param bool $isforced Whether the setting is forced
* @param mixed $forcedvalue The example value
* @param mixed $expected The expected value
*/
public function test_get_config_valid_keys($values, $key, $default, $tourconfig, $isforced, $forcedvalue, $expected) {
$step = $this->getMockBuilder(\tool_usertours\step::class)
->setMethods(['get_target', 'get_targettype', 'get_tour'])
->getMock();
$rc = new \ReflectionClass(\tool_usertours\step::class);
$rcp = $rc->getProperty('config');
$rcp->setAccessible(true);
$rcp->setValue($step, $values);
$target = $this->getMockBuilder(\tool_usertours\local\target\base::class)
->disableOriginalConstructor()
->getMock()
;
$target->expects($this->any())
->method('is_setting_forced')
->willReturn($isforced)
;
$target->expects($this->any())
->method('get_forced_setting_value')
->with($this->equalTo($key))
->willReturn($forcedvalue)
;
$step->expects($this->any())
->method('get_targettype')
->willReturn('type')
;
$step->expects($this->any())
->method('get_target')
->willReturn($target)
;
$tour = $this->getMockBuilder(\tool_usertours\tour::class)
->getMock()
;
$tour->expects($this->any())
->method('get_config')
->willReturn($tourconfig)
;
$step->expects($this->any())
->method('get_tour')
->willReturn($tour)
;
$this->assertEquals($expected, $step->get_config($key, $default));
}
/**
* Data provider for set_config.
*/
public function set_config_provider() {
$allvalues = (object) [
'some' => 'value',
'another' => 42,
'key' => [
'somethingelse',
],
];
$randvalue = rand(1, 100);
$provider = [];
$newvalues = $allvalues;
$newvalues->some = 'unset';
$provider['Unset an existing value'] = [
$allvalues,
'some',
null,
$newvalues,
];
$newvalues = $allvalues;
$newvalues->some = $randvalue;
$provider['Set an existing value'] = [
$allvalues,
'some',
$randvalue,
$newvalues,
];
$provider['Set a new value'] = [
$allvalues,
'newkey',
$randvalue,
(object) array_merge((array) $allvalues, ['newkey' => $randvalue]),
];
return $provider;
}
/**
* Test that set_config works in the anticipated fashion.
*
* @dataProvider set_config_provider
* @param mixed $initialvalues The inital value to set
* @param string $key The key to test
* @param mixed $newvalue The new value to set
* @param mixed $expected The expected value
*/
public function test_set_config($initialvalues, $key, $newvalue, $expected) {
$step = new \tool_usertours\step();
$rc = new \ReflectionClass(\tool_usertours\step::class);
$rcp = $rc->getProperty('config');
$rcp->setAccessible(true);
$rcp->setValue($step, $initialvalues);
$target = $this->getMockBuilder(\tool_usertours\local\target\base::class)
->disableOriginalConstructor()
->getMock()
;
$target->expects($this->any())
->method('is_setting_forced')
->willReturn(false)
;
$step->set_config($key, $newvalue);
$this->assertEquals($expected, $rcp->getValue($step));
}
/**
* Ensure that non-dirty tours are not persisted.
*/
public function test_persist_non_dirty() {
$step = $this->getMockBuilder(\tool_usertours\step::class)
->setMethods([
'to_record',
'reload',
])
->getMock()
;
$step->expects($this->never())
->method('to_record')
;
$step->expects($this->never())
->method('reload')
;
$this->assertSame($step, $step->persist());
}
/**
* Ensure that new dirty steps are persisted.
*/
public function test_persist_dirty_new() {
// Mock the database.
$DB = $this->mock_database();
$DB->expects($this->once())
->method('insert_record')
->willReturn(42)
;
// Mock the tour.
$step = $this->getMockBuilder(\tool_usertours\step::class)
->setMethods([
'to_record',
'calculate_sortorder',
'reload',
])
->getMock()
;
$step->expects($this->once())
->method('to_record')
->willReturn((object)['id' => 42]);
;
$step->expects($this->once())
->method('calculate_sortorder')
;
$step->expects($this->once())
->method('reload')
;
$rc = new \ReflectionClass(\tool_usertours\step::class);
$rcp = $rc->getProperty('dirty');
$rcp->setAccessible(true);
$rcp->setValue($step, true);
$this->assertSame($step, $step->persist());
}
/**
* Ensure that new non-dirty, forced steps are persisted.
*/
public function test_persist_force_new() {
global $DB;
// Mock the database.
$DB = $this->mock_database();
$DB->expects($this->once())
->method('insert_record')
->willReturn(42)
;
// Mock the tour.
$step = $this->getMockBuilder(\tool_usertours\step::class)
->setMethods([
'to_record',
'calculate_sortorder',
'reload',
])
->getMock()
;
$step->expects($this->once())
->method('to_record')
->willReturn((object)['id' => 42]);
;
$step->expects($this->once())
->method('calculate_sortorder')
;
$step->expects($this->once())
->method('reload')
;
$this->assertSame($step, $step->persist(true));
}
/**
* Ensure that existing dirty steps are persisted.
*/
public function test_persist_dirty_existing() {
// Mock the database.
$DB = $this->mock_database();
$DB->expects($this->once())
->method('update_record')
;
// Mock the tour.
$step = $this->getMockBuilder(\tool_usertours\step::class)
->setMethods([
'to_record',
'calculate_sortorder',
'reload',
])
->getMock()
;
$step->expects($this->once())
->method('to_record')
->willReturn((object)['id' => 42]);
;
$step->expects($this->never())
->method('calculate_sortorder')
;
$step->expects($this->once())
->method('reload')
;
$rc = new \ReflectionClass(\tool_usertours\step::class);
$rcp = $rc->getProperty('id');
$rcp->setAccessible(true);
$rcp->setValue($step, 42);
$rcp = $rc->getProperty('dirty');
$rcp->setAccessible(true);
$rcp->setValue($step, true);
$this->assertSame($step, $step->persist());
}
/**
* Ensure that existing non-dirty, forced steps are persisted.
*/
public function test_persist_force_existing() {
global $DB;
// Mock the database.
$DB = $this->mock_database();
$DB->expects($this->once())
->method('update_record')
;
// Mock the tour.
$step = $this->getMockBuilder(\tool_usertours\step::class)
->setMethods([
'to_record',
'calculate_sortorder',
'reload',
])
->getMock()
;
$step->expects($this->once())
->method('to_record')
->willReturn((object)['id' => 42]);
;
$step->expects($this->never())
->method('calculate_sortorder')
;
$step->expects($this->once())
->method('reload')
;
$rc = new \ReflectionClass(\tool_usertours\step::class);
$rcp = $rc->getProperty('id');
$rcp->setAccessible(true);
$rcp->setValue($step, 42);
$this->assertSame($step, $step->persist(true));
}
/**
* Check that a tour which has never been persisted is removed correctly.
*/
public function test_remove_non_persisted() {
$step = $this->getMockBuilder(\tool_usertours\step::class)
->setMethods(null)
->getMock()
;
// Mock the database.
$DB = $this->mock_database();
$DB->expects($this->never())
->method('delete_records')
;
$this->assertNull($step->remove());
}
/**
* Check that a tour which has been persisted is removed correctly.
*/
public function test_remove_persisted() {
$id = rand(1, 100);
$tour = $this->getMockBuilder(\tool_usertours\tour::class)
->setMethods([
'reset_step_sortorder',
])
->getMock()
;
$tour->expects($this->once())
->method('reset_step_sortorder')
;
$step = $this->getMockBuilder(\tool_usertours\step::class)
->setMethods([
'get_tour',
])
->getMock()
;
$step->expects($this->once())
->method('get_tour')
->willReturn($tour)
;
// Mock the database.
$DB = $this->mock_database();
$DB->expects($this->once())
->method('delete_records')
->with($this->equalTo('tool_usertours_steps'), $this->equalTo(['id' => $id]))
;
$rc = new \ReflectionClass(\tool_usertours\step::class);
$rcp = $rc->getProperty('id');
$rcp->setAccessible(true);
$rcp->setValue($step, $id);
$this->assertEquals($id, $step->get_id());
$this->assertNull($step->remove());
}
/**
* Data provider for the get_ tests.
*
* @return array
*/
public function getter_provider() {
return [
'id' => [
'id',
rand(1, 100),
],
'tourid' => [
'tourid',
rand(1, 100),
],
'title' => [
'title',
'Lorem',
],
'content' => [
'content',
'Lorem',
],
'targettype' => [
'targettype',
'Lorem',
],
'targetvalue' => [
'targetvalue',
'Lorem',
],
'sortorder' => [
'sortorder',
rand(1, 100),
],
];
}
/**
* Test that getters return the configured value.
*
* @dataProvider getter_provider
* @param string $key The key to test
* @param mixed $value The expected value
*/
public function test_getters($key, $value) {
$step = new \tool_usertours\step();
$rc = new \ReflectionClass(\tool_usertours\step::class);
$rcp = $rc->getProperty($key);
$rcp->setAccessible(true);
$rcp->setValue($step, $value);
$getter = 'get_' . $key;
$this->assertEquals($value, $step->$getter());
}
}
@@ -0,0 +1,100 @@
<?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/>.
/**
* Tests for theme filter.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Tests for theme filter.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class tool_usertours_theme_filter_testcase extends advanced_testcase {
/**
* Data Provider for filter_matches function.
*
* @return array
*/
public function filter_matches_provider() {
return [
'No config set; Matches' => [
null,
'boost',
true,
],
'Empty config set; Matches' => [
[],
'boost',
true,
],
'Single matching value set; Matches' => [
['boost'],
'boost',
true,
],
'Multiple values set including matching; Matches' => [
['boost', 'clean'],
'boost',
true,
],
'Single value set; No match' => [
['clean'],
'boost',
false,
],
'Multiple values set; No match' => [
['clean', 'artificial'],
'boost',
false,
],
];
}
/**
* Test the filter_matches function.
*
* @dataProvider filter_matches_provider
* @param array $filtervalues The filter values
* @param string $currenttheme The name of the current theme
* @param boolean $expected Whether the tour is expected to match
*/
public function test_filter_matches($filtervalues, $currenttheme, $expected) {
global $PAGE;
$filtername = \tool_usertours\local\filter\theme::class;
// Note: No need to persist this tour.
$tour = new \tool_usertours\tour();
if ($filtervalues !== null) {
$tour->set_filter_values('theme', $filtervalues);
}
$PAGE->theme->name = $currenttheme;
// Note: The theme filter does not use the context.
$this->assertEquals($expected, $filtername::filter_matches($tour, \context_system::instance()));
}
}
File diff suppressed because it is too large Load Diff
+17
View File
@@ -0,0 +1,17 @@
<?xml version="1.0"?>
<libraries>
<library>
<location>amd/src/tour.js</location>
<name>Flexitour</name>
<license>GPLv3</license>
<version>0.9.6</version>
<licenseversion>3</licenseversion>
</library>
<library>
<location>amd/src/popper.js</location>
<name>Popper.js</name>
<license>MIT</license>
<version>v0.6.4</version>
<licenseversion></licenseversion>
</library>
</libraries>
+29
View File
@@ -0,0 +1,29 @@
<?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/>.
/**
* Version information.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2016101800; // The current module version (Date: YYYYMMDDXX).
$plugin->requires = 2016052300; // Requires this Moodle version.
$plugin->component = 'tool_usertours'; // Full name of the plugin (used for diagnostics).
+1 -1
View File
@@ -1908,7 +1908,7 @@ class core_plugin_manager {
'dbtransfer', 'filetypes', 'generator', 'health', 'innodb', 'installaddon',
'langimport', 'log', 'lp', 'lpimportcsv', 'lpmigrate', 'messageinbound', 'mobile', 'multilangupgrade', 'monitor',
'phpunit', 'profiling', 'recyclebin', 'replace', 'spamcleaner', 'task', 'templatelibrary',
'unittest', 'uploadcourse', 'uploaduser', 'unsuproles', 'xmldb'
'unittest', 'uploadcourse', 'uploaduser', 'unsuproles', 'usertours', 'xmldb'
),
'webservice' => array(
+1
View File
@@ -38,3 +38,4 @@ $breadcrumb-divider-rtl: "◀" !default;
@import "moodle/expendable";
@import "moodle/sticky-footer";
@import "moodle/popover-region";
@import "moodle/tool_usertours";
+105
View File
@@ -0,0 +1,105 @@
/**
* Tour step must sit above all other UI components.
* The backdrop is the lowest point in the tour.
* Everything else is in the container, and the target background should be at the same z-index.
* ----- moodle
* ---- step backdrop
* --- step container
* --- step target background
*/
$flexitour-base-zindex: 1040;
// The backdrop is the backdrop used in 'modal' step display.
div[data-flexitour="backdrop"] {
background-color: #{$modal-backdrop-bg};
opacity: #{$modal-backdrop-opacity};
// The backdrop needs to have a lower z-index than everything else in the tour, but higher than everything else in Moodle.
z-index: #{$flexitour-base-zindex};
}
// The step-background is used to highlight the region targetted in the step.
div[data-flexitour="step-background-fader"],
div[data-flexitour="step-background"] {
@include border-radius($border-radius-lg);
// The step container, and the target background should be at the same z-index.
z-index: #{$flexitour-base-zindex} + 1;
}
span[data-flexitour="container"],
div[data-flexitour="step-background-fader"],
[data-flexitour="step-backdrop"] > td,
[data-flexitour="step-backdrop"] {
// The step container, and the target background should be at the same z-index.
z-index: #{$flexitour-base-zindex} + 2;
}
span[data-flexitour="container"] {
.modal-dialog {
/**
* Remove all margins to:
* 1) ensure that the arrow touches the target; and
* 2) ensure that the focus border touches the modal.
*/
margin: 0;
}
div[data-role="arrow"] {
border-width: #{$popover-arrow-outer-width};
}
div[data-role="arrow"],
div[data-role="arrow"]:after {
position: absolute;
display: block;
width: 0;
height: 0;
border-color: transparent;
border-style: solid;
border-width: #{$popover-arrow-width};
}
// We need to know the opposite sides for arrow placement.
$opposites:
(
top: bottom,
bottom: top,
left: right,
right: left,
);
// These are the next leading side and allow for placement half-way along the step dialogue.
$sides:
(
top: left,
bottom: left,
left: top,
right: top,
);
@each $direction in map-keys($opposites) {
$opposite: map_get($opposites, $direction);
$side: map_get($sides, $direction);
$oppositeside: map_get($opposites, $side);
&[x-placement="#{$direction}"],
&[x-placement="#{$direction}-start"] {
margin-#{$opposite}: #{$popover-arrow-width};
div[data-role="arrow"] {
#{$opposite}: -#{$popover-arrow-outer-width};
#{$side}: 50%;
margin-#{$side}: -#{$popover-arrow-outer-width};
border-#{$opposite}-width: 0;
border-#{$direction}-color: #{$popover-arrow-outer-color};
}
div[data-role="arrow"]:after {
#{$opposite}: 1px;
margin-#{$side}: -#{$popover-arrow-width};
content: " ";
border-#{$opposite}-width: 0;
border-#{$direction}-color: #{$popover-arrow-color};
}
}
}
}
@@ -0,0 +1,65 @@
{{!
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/>.
}}
{{!
@template tool_usertours/tourstep
Template used as a template to render step bubbles with their content
in a user tour.
Classes required for JS:
* arrow
* popover-title
* popover-content
* popover-navigation
* popover-title
Data attributes required for JS:
* data-role=prev
* data-role=next
* data-role=pause-resume
* data-pause-text
* data-resume-text
* data-role=end
* data-placeholder=body
* data-placeholder=title
Context variables required for this template:
* None
Example context (json):
{
}
}}
<div class="modal-dialog" role="document" data-role="flexitour-step">
<div class="modal-content">
<div class="tooltip-arrow" data-role="arrow"></div>
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close" data-role="end">
<span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title" data-placeholder="title"></h4>
</div>
<div class="modal-body" data-placeholder="body">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-role="previous">{{# str }} previous, moodle {{/ str }}</button>
<button type="button" class="btn btn-primary" data-role="next">{{# str }} next, moodle {{/ str }}</button>
<button class="btn btn-secondary" data-role="end"> {{# str }} endtour, tool_usertours {{/ str }} </button>
</div>
</div>
</div>
+1
View File
@@ -60,6 +60,7 @@ body {
@import "moodle/buttons";
@import "moodle/grade";
@import "moodle/dock";
@import "moodle/tool_usertours";
// Styles for mustache templates.
@import "moodle/templates";
@@ -0,0 +1,146 @@
/**
* Tour step must sit above all other UI components.
* The backdrop is the lowest point in the tour.
* Everything else is in the container, and the target background should be at the same z-index.
* ----- moodle
* ---- step backdrop
* --- step container
* --- step target background
*/
@flexitour-base-zindex: 4030;
// The backdrop is the backdrop used in 'modal' step display.
div[data-flexitour="backdrop"] {
.modal-backdrop();
// The backdrop needs to have a lower z-index than everything else in the tour, but higher than everything else in Moodle.
z-index: @flexitour-base-zindex;
}
// The step-background is used to highlight the region targetted in the step.
div[data-flexitour="step-background-fader"],
div[data-flexitour="step-background"] {
.border-radius(6px);
// The step target background should be above the base.
z-index: (@flexitour-base-zindex + 1);
}
span[data-flexitour="container"],
div[data-flexitour="step-background-fader"],
[data-flexitour="step-backdrop"] > td,
[data-flexitour="step-backdrop"] {
// The step target background should be above the base.
z-index: (@flexitour-base-zindex + 2);
}
span[data-flexitour="container"] {
div[data-role="flexitour-step"] {
background-color: #fff;
border-radius: 6px;
border: 1px solid rgba(0, 0, 0, .2);
box-shadow: 0 5px 10px rgba(0, 0, 0, .2);
> div {
// When targetting some regions, the selectors override the modal ones.
display: block;
margin: 0;
}
}
.modal {
position: static;
margin-left: 0;
}
div[data-role="arrow"] {
border-width: @popoverArrowOuterWidth;
}
div[data-role="arrow"]:after {
content: " ";
}
div[data-role="arrow"],
div[data-role="arrow"]:after {
position: absolute;
display: block;
width: 0;
height: 0;
border-color: transparent;
border-style: solid;
border-width: @popoverArrowWidth;
}
&[x-placement="top"],
&[x-placement="top-start"] {
margin-bottom: @popoverArrowWidth;
div[data-role="arrow"] {
bottom: -@popoverArrowOuterWidth;
left: 50%;
margin-left: -@popoverArrowOuterWidth;
border-bottom-width: 0;
border-top-color: @popoverArrowOuterColor;
}
div[data-role="arrow"]:after {
bottom: 1px;
margin-left: -@popoverArrowWidth;
border-bottom-width: 0;
border-top-color: @popoverArrowColor;
}
}
&[x-placement="bottom"],
&[x-placement="bottom-start"] {
margin-top: @popoverArrowWidth;
div[data-role="arrow"] {
top: -@popoverArrowOuterWidth;
left: 50%;
margin-left: -@popoverArrowOuterWidth;
border-top-width: 0;
border-bottom-color: @popoverArrowOuterColor;
}
div[data-role="arrow"]:after {
top: 1px;
margin-left: -@popoverArrowWidth;
border-top-width: 0;
border-bottom-color: @popoverArrowColor;
}
}
&[x-placement="left"],
&[x-placement="left-start"] {
margin-right: @popoverArrowWidth;
div[data-role="arrow"] {
right: -@popoverArrowOuterWidth;
top: 50%;
margin-top: -@popoverArrowOuterWidth;
border-right-width: 0;
border-left-color: @popoverArrowOuterColor;
}
div[data-role="arrow"]:after {
right: 1px;
margin-top: -@popoverArrowWidth;
border-right-width: 0;
border-left-color: @popoverArrowColor;
}
}
&[x-placement="right"],
&[x-placement="right-start"] {
margin-left: @popoverArrowWidth;
div[data-role="arrow"] {
left: -@popoverArrowOuterWidth;
top: 50%;
margin-top: -@popoverArrowOuterWidth;
border-left-width: 0;
border-right-color: @popoverArrowOuterColor;
}
div[data-role="arrow"]:after {
left: 1px;
margin-top: -@popoverArrowWidth;
border-left-width: 0;
border-right-color: @popoverArrowColor;
}
}
}
+146
View File
@@ -16665,6 +16665,152 @@ body.has_dock div#dock {
padding-left: 65px;
}
}
/**
* Tour step must sit above all other UI components.
* The backdrop is the lowest point in the tour.
* Everything else is in the container, and the target background should be at the same z-index.
* ----- moodle
* ---- step backdrop
* --- step container
* --- step target background
*/
div[data-flexitour="backdrop"] {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 1040;
background-color: #000;
opacity: 0.8;
filter: alpha(opacity=80);
z-index: 4049;
background-color: #aaa;
opacity: 0.4;
z-index: 4030;
}
div[data-flexitour="backdrop"].fade {
opacity: 0;
}
div[data-flexitour="step-background-fader"],
div[data-flexitour="step-background"] {
-webkit-border-radius: 6px;
-moz-border-radius: 6px;
border-radius: 6px;
z-index: 4031;
}
span[data-flexitour="container"],
div[data-flexitour="step-background-fader"],
[data-flexitour="step-backdrop"] > td,
[data-flexitour="step-backdrop"] {
z-index: 4032;
}
span[data-flexitour="container"] div[data-role="flexitour-step"] {
background-color: #fff;
border-radius: 6px;
border: 1px solid rgba(0, 0, 0, 0.2);
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
}
span[data-flexitour="container"] div[data-role="flexitour-step"] > div {
display: block;
margin: 0;
}
span[data-flexitour="container"] .modal {
position: static;
margin-left: 0;
}
span[data-flexitour="container"] div[data-role="arrow"] {
border-width: 11px;
}
span[data-flexitour="container"] div[data-role="arrow"]:after {
content: " ";
}
span[data-flexitour="container"] div[data-role="arrow"],
span[data-flexitour="container"] div[data-role="arrow"]:after {
position: absolute;
display: block;
width: 0;
height: 0;
border-color: transparent;
border-style: solid;
border-width: 10px;
}
span[data-flexitour="container"][x-placement="top"],
span[data-flexitour="container"][x-placement="top-start"] {
margin-bottom: 10px;
}
span[data-flexitour="container"][x-placement="top"] div[data-role="arrow"],
span[data-flexitour="container"][x-placement="top-start"] div[data-role="arrow"] {
bottom: -11px;
left: 50%;
margin-left: -11px;
border-bottom-width: 0;
border-top-color: rgba(0, 0, 0, 0.25);
}
span[data-flexitour="container"][x-placement="top"] div[data-role="arrow"]:after,
span[data-flexitour="container"][x-placement="top-start"] div[data-role="arrow"]:after {
bottom: 1px;
margin-left: -10px;
border-bottom-width: 0;
border-top-color: #fff;
}
span[data-flexitour="container"][x-placement="bottom"],
span[data-flexitour="container"][x-placement="bottom-start"] {
margin-top: 10px;
}
span[data-flexitour="container"][x-placement="bottom"] div[data-role="arrow"],
span[data-flexitour="container"][x-placement="bottom-start"] div[data-role="arrow"] {
top: -11px;
left: 50%;
margin-left: -11px;
border-top-width: 0;
border-bottom-color: rgba(0, 0, 0, 0.25);
}
span[data-flexitour="container"][x-placement="bottom"] div[data-role="arrow"]:after,
span[data-flexitour="container"][x-placement="bottom-start"] div[data-role="arrow"]:after {
top: 1px;
margin-left: -10px;
border-top-width: 0;
border-bottom-color: #fff;
}
span[data-flexitour="container"][x-placement="left"],
span[data-flexitour="container"][x-placement="left-start"] {
margin-right: 10px;
}
span[data-flexitour="container"][x-placement="left"] div[data-role="arrow"],
span[data-flexitour="container"][x-placement="left-start"] div[data-role="arrow"] {
right: -11px;
top: 50%;
margin-top: -11px;
border-right-width: 0;
border-left-color: rgba(0, 0, 0, 0.25);
}
span[data-flexitour="container"][x-placement="left"] div[data-role="arrow"]:after,
span[data-flexitour="container"][x-placement="left-start"] div[data-role="arrow"]:after {
right: 1px;
margin-top: -10px;
border-right-width: 0;
border-left-color: #fff;
}
span[data-flexitour="container"][x-placement="right"],
span[data-flexitour="container"][x-placement="right-start"] {
margin-left: 10px;
}
span[data-flexitour="container"][x-placement="right"] div[data-role="arrow"],
span[data-flexitour="container"][x-placement="right-start"] div[data-role="arrow"] {
left: -11px;
top: 50%;
margin-top: -11px;
border-left-width: 0;
border-right-color: rgba(0, 0, 0, 0.25);
}
span[data-flexitour="container"][x-placement="right"] div[data-role="arrow"]:after,
span[data-flexitour="container"][x-placement="right-start"] div[data-role="arrow"]:after {
left: 1px;
margin-top: -10px;
border-left-width: 0;
border-right-color: #fff;
}
/* Styling for mustache templates */
/* stylelint-disable declaration-block-no-duplicate-properties, no-browser-hacks */
/* stylelint-enable */