first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-09-30 18:11:26 -04:00
commit e592ca6823
27270 changed files with 5002257 additions and 0 deletions
+151
View File
@@ -0,0 +1,151 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace tool_usertours;
/**
* Cache manager.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cache {
/**
* @var CACHENAME_TOUR The name of the cache used for storing tours.
*/
const CACHENAME_TOUR = 'tourdata';
/**
* @var CACHEKEY_TOUR The name of the key used for storing tours.
*/
const CACHEKEY_TOUR = 'tours';
/**
* @var CACHENAME_STEP The name of the cache used for storing steps.
*/
const CACHENAME_STEP = 'stepdata';
/**
* Fetch all enabled tours.
*/
public static function get_enabled_tourdata() {
global $DB;
$cache = \cache::make('tool_usertours', self::CACHENAME_TOUR);
$data = $cache->get(self::CACHEKEY_TOUR);
if ($data === false) {
$sql = <<<EOF
SELECT t.*
FROM {tool_usertours_tours} t
WHERE t.enabled = 1
AND t.id IN (SELECT s.tourid FROM {tool_usertours_steps} s GROUP BY s.tourid)
ORDER BY t.sortorder ASC
EOF;
$data = $DB->get_records_sql($sql);
$cache->set('tours', $data);
}
return $data;
}
/**
* Fetch all enabled tours matching the specified target.
*
* @param moodle_url $targetmatch The URL to match.
*/
public static function get_matching_tourdata(\moodle_url $targetmatch) {
$tours = self::get_enabled_tourdata();
// Attempt to determine whether this is the front page.
// This is a special case because the frontpage uses a shortened page path making it difficult to detect exactly.
$isfrontpage = $targetmatch->compare(new \moodle_url('/'), URL_MATCH_BASE);
$isdashboard = $targetmatch->compare(new \moodle_url('/my/'), URL_MATCH_BASE);
$ismycourses = $targetmatch->compare(new \moodle_url('/my/courses.php'), URL_MATCH_BASE);
$possiblematches = [];
if ($isfrontpage) {
$possiblematches = ['FRONTPAGE', 'FRONTPAGE_MY', 'FRONTPAGE_MYCOURSES', 'FRONTPAGE_MY_MYCOURSES'];
} else if ($isdashboard) {
$possiblematches = ['MY', 'FRONTPAGE_MY', 'MY_MYCOURSES', 'FRONTPAGE_MY_MYCOURSES'];
} else if ($ismycourses) {
$possiblematches = ['MYCOURSES', 'FRONTPAGE_MYCOURSES', 'MY_MYCOURSES', 'FRONTPAGE_MY_MYCOURSES'];
}
$target = $targetmatch->out_as_local_url();
return array_filter($tours, function ($tour) use ($possiblematches, $target) {
if (in_array($tour->pathmatch, $possiblematches)) {
return true;
}
$pattern = preg_quote($tour->pathmatch, '@');
if (strpos($pattern, '%') !== false) {
// The URL match format is something like: /my/%.
// We need to find all the URLs which match the first part of the pattern.
$pattern = str_replace('%', '.*', $pattern);
} else {
// The URL match format is something like: /my/courses.php.
// We need to find all the URLs which match with whole pattern.
$pattern .= '$';
}
return !!preg_match("@{$pattern}@", $target);
});
}
/**
* Notify of changes to any tour to clear the tour cache.
*/
public static function notify_tour_change() {
$cache = \cache::make('tool_usertours', self::CACHENAME_TOUR);
$cache->delete(self::CACHEKEY_TOUR);
}
/**
* Fetch the tour data for the specified tour.
*
* @param int $tourid The ID of the tour to fetch steps for
*/
public static function get_stepdata($tourid) {
global $DB;
$cache = \cache::make('tool_usertours', self::CACHENAME_STEP);
$data = $cache->get($tourid);
if ($data === false) {
$sql = <<<EOF
SELECT s.*
FROM {tool_usertours_steps} s
WHERE s.tourid = :tourid
ORDER BY s.sortorder ASC
EOF;
$data = $DB->get_records_sql($sql, ['tourid' => $tourid]);
$cache->set($tourid, $data);
}
return $data;
}
/**
* Notify of changes to any step to clear the step cache for that tour.
*
* @param int $tourid The ID of the tour to clear the step cache for
*/
public static function notify_step_change($tourid) {
$cache = \cache::make('tool_usertours', self::CACHENAME_STEP);
$cache->delete($tourid);
}
}
@@ -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/>.
namespace tool_usertours;
/**
* Step configuration detail class.
*
* @package tool_usertours
* @copyright 2024 Andrew Nicols <andrew@nicols.co.uk>
* @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('above', 'tool_usertours'),
'bottom' => get_string('below', '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,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/>.
namespace tool_usertours\event;
/**
* The tool_usertours step_shown event.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <andrew@nicols.co.uk>
* @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,134 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace tool_usertours\event;
/**
* The tool_usertours tour_ended event.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <andrew@nicols.co.uk>
* @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,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/>.
namespace tool_usertours\event;
/**
* The tool_usertours tour_reset event.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <andrew@nicols.co.uk>
* @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,120 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace tool_usertours\event;
/**
* The tool_usertours tour_started event.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <andrew@nicols.co.uk>
* @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);
}
}
+347
View File
@@ -0,0 +1,347 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace tool_usertours\external;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_multiple_structure;
use core_external\external_single_structure;
use core_external\external_value;
use tool_usertours\tour as tourinstance;
use tool_usertours\step;
/**
* Web Service functions for steps.
*
* @copyright 2016 Andrew Nicols <andrew@nicols.co.uk>
* @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' => $tour->get_id(),
'other' => [
'pageurl' => $params['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()),
'endtourlabel' => new external_value(PARAM_RAW, 'Label of the end tour button'),
'displaystepnumbers' => new external_value(PARAM_BOOL, 'display step number'),
], 'Tour config', VALUE_OPTIONAL),
]);
}
/**
* 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 = [];
$matchingtours = \tool_usertours\manager::get_matching_tours(new \moodle_url($params['pageurl']));
foreach ($matchingtours as $match) {
if ($tour->get_id() === $match->get_id()) {
$result['startTour'] = $tour->get_id();
\tool_usertours\event\tour_reset::create([
'contextid' => $context->id,
'objectid' => $params['tourid'],
'other' => [
'pageurl' => $params['pageurl'],
],
])->trigger();
break;
}
}
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
),
]);
}
}
+649
View File
@@ -0,0 +1,649 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace tool_usertours;
use coding_exception;
use core\output\inplace_editable;
/**
* Tour helper.
*
* @copyright 2016 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @package tool_usertours
*/
class helper {
/**
* @var MOVE_UP
*/
const MOVE_UP = -1;
/**
* @var MOVE_DOWN
*/
const MOVE_DOWN = 1;
/**
* @var boolean Has it been bootstrapped?
*/
private static $bootstrapped = false;
/**
* @var string Regex to check any matching lang string.
*/
protected const LANG_STRING_REGEX = '|^([a-zA-Z][a-zA-Z0-9\.:/_-]*),([a-zA-Z][a-zA-Z0-9\.:/_-]*)$|';
/**
* 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 duplicate the tour.
*
* @param int $tourid The ID of the tour to duplicate.
* @return \moodle_url The URL.
*/
public static function get_duplicate_tour_link($tourid) {
$link = new \moodle_url('/admin/tool/usertours/configure.php', [
'action' => manager::ACTION_DUPLICATETOUR,
'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 = []) {
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 = []) {
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 inplace_editable
*/
public static function render_tourname_inplace_editable(tour $tour): inplace_editable {
$name = format_text(static::get_string_from_input($tour->get_name()), FORMAT_HTML);
return new inplace_editable(
'tool_usertours',
'tourname',
$tour->get_id(),
true,
\html_writer::link(
$tour->get_view_link(),
$name
),
$tour->get_name()
);
}
/**
* Render the inplace editable used to edit the tour description.
*
* @param tour $tour The tour to edit.
* @return inplace_editable
*/
public static function render_tourdescription_inplace_editable(tour $tour): inplace_editable {
$description = format_text(static::get_string_from_input($tour->get_description()), FORMAT_HTML);
return new inplace_editable(
'tool_usertours',
'tourdescription',
$tour->get_id(),
true,
$description,
$tour->get_description()
);
}
/**
* Render the inplace editable used to edit the tour enable state.
*
* @param tour $tour The tour to edit.
* @return inplace_editable
*/
public static function render_tourenabled_inplace_editable(tour $tour): inplace_editable {
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 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 inplace_editable
*/
public static function render_stepname_inplace_editable(step $step): inplace_editable {
$title = format_text(static::get_string_from_input($step->get_title()), FORMAT_HTML);
return new inplace_editable(
'tool_usertours',
'stepname',
$step->get_id(),
true,
\html_writer::link(
$step->get_edit_link(),
$title
),
$step->get_title()
);
}
/**
* Get all of the tours.
*
* @return stdClass[]
*/
public static function get_tours() {
global $DB;
$tours = $DB->get_records('tool_usertours_tours', [], '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 tour
*/
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', ['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, ['id' => $tour->id]);
}
$index++;
}
// Notify the cache that a tour has changed.
// Tours are only stored in the cache if there are steps.
// If there step count has changed for some reason, this will change the potential cache results.
cache::notify_tour_change();
}
/**
* Get all of the steps in the tour.
*
* @param int $tourid The tour that the step belongs to.
* @return step[]
*/
public static function get_steps($tourid) {
$steps = cache::get_stepdata($tourid);
$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', ['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 (!isloggedin() || isguestuser()) {
return;
}
if (in_array($PAGE->pagelayout, ['maintenance', 'print', 'redirect'])) {
// Do not try to show user tours inside iframe, in maintenance mode,
// when printing, or during redirects.
return;
}
if (self::$bootstrapped) {
return;
}
self::$bootstrapped = true;
$tours = manager::get_current_tours();
if ($tours) {
$filters = static::get_all_clientside_filters();
$tourdetails = array_map(function ($tour) use ($filters) {
return [
'tourId' => $tour->get_id(),
'startTour' => $tour->should_show_for_user(),
'filtervalues' => $tour->get_client_filter_values($filters),
];
}, $tours);
$filternames = self::get_clientside_filter_module_names($filters);
$PAGE->requires->js_call_amd('tool_usertours/usertours', 'init', [
$tourdetails,
$filternames,
]);
}
}
/**
* Get the JS module names for the filters.
*
* @param array $filters
* @return array
* @throws coding_exception
*/
public static function get_clientside_filter_module_names(array $filters): array {
$filternames = [];
foreach ($filters as $filter) {
if ($component = \core_component::get_component_from_classname($filter)) {
$filternames[] = sprintf(
"%s/filter_%s",
$component,
$filter::get_filter_name(),
);
} else {
throw new \coding_exception("Could not determine component for filter class {$filter}");
}
}
return $filternames;
}
/**
* Get a list of all possible filters.
*
* @return array
*/
public static function get_all_filters() {
$hook = new hook\before_serverside_filter_fetch(array_keys(
\core_component::get_component_classes_in_namespace('tool_usertours', 'local\filter')
));
\core\di::get(\core\hook\manager::class)->dispatch($hook);
$filters = array_filter(
$hook->get_filter_list(),
function ($filterclass) {
$rc = new \ReflectionClass($filterclass);
return $rc->isInstantiable();
}
);
$filters = array_merge($filters, static::get_all_clientside_filters());
return $filters;
}
/**
* Get a list of all clientside filters.
*
* @return array
*/
public static function get_all_clientside_filters() {
$hook = new hook\before_clientside_filter_fetch(array_keys(
\core_component::get_component_classes_in_namespace('tool_usertours', 'local\clientside_filter')
));
\core\di::get(\core\hook\manager::class)->dispatch($hook);
$filters = array_filter(
$hook->get_filter_list(),
function ($filterclass) {
$rc = new \ReflectionClass($filterclass);
return $rc->isInstantiable();
}
);
return $filters;
}
/**
* Attempt to fetch any matching langstring if the content is in the
* format identifier,component.
*
* @param string $content Step's content or Tour's name or Tour's description
* @return string Processed content, any langstring will be converted to translated text
*/
public static function get_string_from_input(string $content): string {
$content = trim($content);
if (preg_match(static::LANG_STRING_REGEX, $content, $matches)) {
if ($matches[2] === 'moodle') {
$matches[2] = 'core';
}
if (get_string_manager()->string_exists($matches[1], $matches[2])) {
$content = get_string($matches[1], $matches[2]);
}
}
return $content;
}
/**
* Check if the given string contains any matching langstring.
*
* @param string $string
* @return bool
*/
public static function is_language_string_from_input(string $string): bool {
return preg_match(static::LANG_STRING_REGEX, $string) == true;
}
}
@@ -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/>.
namespace tool_usertours\hook;
/**
* Provides the ability to add and remove custom client-side filters to the user tour filter list.
*
* @package tool_usertours
* @copyright 2024 Andrew Lyons <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
#[\core\attribute\label('Provides the ability to add and remove custom client-side filters to the user tour filter list.')]
#[\core\attribute\tags('tool_usertours')]
class before_clientside_filter_fetch {
/**
* Create a new instance of the hook.
*
* @param array $filters
*/
public function __construct(
/** @var array The list of filters applied */
protected array $filters,
) {
}
/**
* Add a filter classname to the list of filters to be processed.
*
* @param string $classname
* @return self
*/
public function add_filter_by_classname(string $classname): self {
if (!\is_a($classname, \tool_usertours\local\clientside_filter\clientside_filter::class, true)) {
throw new \coding_exception("Invalid clientside filter class {$classname}");
}
$this->filters[] = $classname;
return $this;
}
/**
* Remove a filter classname from the list of filters to be processed.
*
* @param string $classname
* @return self
*/
public function remove_filter_by_classname(string $classname): self {
$this->filters = array_filter($this->filters, fn($filter) => $filter !== $classname);
return $this;
}
/**
* Get the list of filters to be processed.
*
* @return array
*/
public function get_filter_list(): array {
return $this->filters;
}
}
@@ -0,0 +1,78 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace tool_usertours\hook;
/**
* Provides the ability to add and remove custom server-side filters to the user tour filter list.
*
* @package tool_usertours
* @copyright 2024 Andrew Lyons <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
#[\core\attribute\label('Provides the ability to add and remove custom server-side filters to the user tour filter list.')]
#[\core\attribute\tags('tool_usertours')]
class before_serverside_filter_fetch {
/**
* Create a new instance of the hook.
*
* @param array $filters
*/
public function __construct(
/** @var array The list of filters applied */
protected array $filters,
) {
}
/**
* Add a filter classname to the list of filters to be processed.
*
* @param string $classname
* @return self
*/
public function add_filter_by_classname(string $classname): self {
if (!\is_a($classname, \tool_usertours\local\filter\base::class, true)) {
throw new \coding_exception("Invalid filter class {$classname}");
}
if (\is_a($classname, \tool_usertours\local\clientside_filter\clientside_filter::class, true)) {
throw new \coding_exception("Invalid filter class {$classname} (client-side filter for server-side hook)");
}
$this->filters[] = $classname;
return $this;
}
/**
* Remove a filter classname from the list of filters to be processed.
*
* @param string $classname
* @return self
*/
public function remove_filter_by_classname(string $classname): self {
$this->filters = array_filter($this->filters, fn($filter) => $filter !== $classname);
return $this;
}
/**
* Get the list of filters to be processed.
*
* @return array
*/
public function get_filter_list(): array {
return $this->filters;
}
}
@@ -0,0 +1,37 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace tool_usertours;
use core\hook\output\before_footer_html_generation;
/**
* Hook callbacks for usertours.
*
* @package tool_usertours
* @copyright 2024 Andrew Lyons <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class hook_callbacks {
/**
* Bootstrap the usertours library.
*
* @param before_footer_html_generation $hook
*/
public static function before_footer_html_generation(before_footer_html_generation $hook): void {
\tool_usertours\helper::bootstrap();
}
}
@@ -0,0 +1,46 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace tool_usertours\local\clientside_filter;
use stdClass;
use tool_usertours\local\filter\base;
use tool_usertours\tour;
/**
* Clientside filter base.
*
* @package tool_usertours
* @copyright 2020 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class clientside_filter extends base {
/**
* Returns the filter values needed for client side filtering.
*
* @param tour $tour The tour to find the filter values for
* @return stdClass
*/
public static function get_client_side_values(tour $tour): stdClass {
$data = (object) [];
if (is_a(static::class, self::class, true)) {
$data->filterdata = $tour->get_filter_values(static::get_filter_name());
}
return $data;
}
}
@@ -0,0 +1,109 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace tool_usertours\local\clientside_filter;
use stdClass;
use tool_usertours\tour;
/**
* Course filter.
*
* @package tool_usertours
* @copyright 2020 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cssselector extends clientside_filter {
/**
* The name of the filter.
*
* @return string
*/
public static function get_filter_name() {
return 'cssselector';
}
/**
* Overrides the base add form element with a selector text box.
*
* @param \MoodleQuickForm $mform
*/
public static function add_filter_to_form(\MoodleQuickForm &$mform) {
$filtername = self::get_filter_name();
$key = "filter_{$filtername}";
$mform->addElement('text', $key, get_string($key, 'tool_usertours'));
$mform->setType($key, PARAM_RAW);
$mform->addHelpButton($key, $key, 'tool_usertours');
}
/**
* Prepare the filter values for the form.
*
* @param tour $tour The tour to prepare values from
* @param stdClass $data The data value
* @return stdClass
*/
public static function prepare_filter_values_for_form(tour $tour, \stdClass $data) {
$filtername = static::get_filter_name();
$key = "filter_{$filtername}";
$values = $tour->get_filter_values($filtername);
if (empty($values)) {
$values = [""];
}
$data->$key = $values[0];
return $data;
}
/**
* Save the filter values from the form to the tour.
*
* @param tour $tour The tour to save values to
* @param stdClass $data The data submitted in the form
*/
public static function save_filter_values_from_form(tour $tour, \stdClass $data) {
$filtername = static::get_filter_name();
$key = "filter_{$filtername}";
$newvalue = [$data->$key];
if (empty($data->$key)) {
$newvalue = [];
}
$tour->set_filter_values($filtername, $newvalue);
}
/**
* Returns the filter values needed for client side filtering.
*
* @param tour $tour The tour to find the filter values for
* @return stdClass
*/
public static function get_client_side_values(tour $tour): stdClass {
$filtername = static::get_filter_name();
$filtervalues = $tour->get_filter_values($filtername);
// Filter values might not exist for tours that were created before this filter existed.
if (!$filtervalues) {
return new stdClass();
}
return (object) $filtervalues;
}
}
@@ -0,0 +1,221 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace tool_usertours\local\filter;
use context;
use tool_usertours\tour;
/**
* Access date filter. Used to determine if USER should see a tour based on a particular access date.
*
* @package tool_usertours
* @copyright 2019 Tom Dickman <tomdickman@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class accessdate extends base {
/**
* Access date filtering constant for setting base date as account creation date.
*/
const FILTER_ACCOUNT_CREATION = 'tool_usertours_accountcreation';
/**
* Access date filtering constant for setting base date as account first login date.
*/
const FILTER_FIRST_LOGIN = 'tool_usertours_firstlogin';
/**
* Access date filtering constant for setting base date as account last login date.
*/
const FILTER_LAST_LOGIN = 'tool_usertours_lastlogin';
/**
* Default this filter to not be enabled.
*/
const FILTER_ENABLED_DEFAULT = 0;
/**
* The name of the filter.
*
* @return string
*/
public static function get_filter_name() {
return 'accessdate';
}
/**
* 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
* @throws \coding_exception
*/
public static function get_filter_options() {
return [
self::FILTER_ACCOUNT_CREATION => get_string('filter_date_account_creation', 'tool_usertours'),
self::FILTER_FIRST_LOGIN => get_string('filter_date_first_login', 'tool_usertours'),
self::FILTER_LAST_LOGIN => get_string('filter_date_last_login', 'tool_usertours'),
];
}
/**
* Add the form elements for the filter to the supplied form.
*
* @param \MoodleQuickForm $mform The form to add filter settings to.
*
* @throws \coding_exception
*/
public static function add_filter_to_form(\MoodleQuickForm &$mform) {
$filtername = static::get_filter_name();
$key = "filter_{$filtername}";
$range = "{$key}_range";
$enabled = "{$key}_enabled";
$mform->addElement(
'advcheckbox',
$enabled,
get_string($key, 'tool_usertours'),
get_string('filter_accessdate_enabled', 'tool_usertours'),
null,
[0, 1]
);
$mform->addHelpButton($enabled, $enabled, 'tool_usertours');
$mform->addElement('select', $key, ' ', self::get_filter_options());
$mform->setDefault($key, self::FILTER_ACCOUNT_CREATION);
$mform->hideIf($key, $enabled, 'notchecked');
$mform->addElement('duration', $range, null, [
'optional' => false,
'defaultunit' => DAYSECS,
]);
$mform->setDefault($range, 90 * DAYSECS);
$mform->hideIf($range, $enabled, 'notchecked');
}
/**
* 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}";
$range = "{$key}_range";
$enabled = "{$key}_enabled";
$values = $tour->get_filter_values($filtername);
// Prepare the advanced checkbox value and prepare filter values based on previously set values.
if (!empty($values)) {
$data->$enabled = $values->$enabled ? $values->$enabled : self::FILTER_ENABLED_DEFAULT;
if ($data->$enabled) {
if (isset($values->$key)) {
$data->$key = $values->$key;
}
if (isset($values->$range)) {
$data->$range = $values->$range;
}
}
} else {
$data->$enabled = self::FILTER_ENABLED_DEFAULT;
}
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}";
$range = "{$key}_range";
$enabled = "{$key}_enabled";
$savedata = [];
$savedata[$key] = $data->$key;
$savedata[$range] = $data->$range;
$savedata[$enabled] = $data->$enabled;
$tour->set_filter_values($filtername, $savedata);
}
/**
* 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;
$filtername = static::get_filter_name();
$key = "filter_{$filtername}";
$range = "{$key}_range";
$enabled = "{$key}_enabled";
// Default behaviour is to match filter.
$result = true;
$values = (array) $tour->get_filter_values(self::get_filter_name());
// If the access date filter is not enabled, end here.
if (empty($values[$enabled])) {
return $result;
}
if (!empty($values[$key])) {
switch ($values[$key]) {
case (self::FILTER_ACCOUNT_CREATION):
$filterbasedate = (int) $USER->timecreated;
break;
case (self::FILTER_FIRST_LOGIN):
$filterbasedate = (int) $USER->firstaccess;
break;
case (self::FILTER_LAST_LOGIN):
$filterbasedate = (int) $USER->lastlogin;
break;
default:
// Use account creation as default.
$filterbasedate = (int) $USER->timecreated;
break;
}
// If the base date has no value because a user hasn't accessed Moodle yet, default to account creation.
if (empty($filterbasedate)) {
$filterbasedate = (int) $USER->timecreated;
}
if (!empty($values[$range])) {
$filterrange = (int) $values[$range];
} else {
$filterrange = 90 * DAYSECS;
}
// If we're outside the set range from the set base date, filter out tour.
if ((time() > ($filterbasedate + $filterrange))) {
$result = false;
}
}
return $result;
}
}
@@ -0,0 +1,126 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace tool_usertours\local\filter;
use tool_usertours\tour;
use context;
/**
* Filter base.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <andrew@nicols.co.uk>
* @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,89 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace tool_usertours\local\filter;
use tool_usertours\tour;
use context;
/**
* Category filter.
*
* @package tool_usertours
* @copyright 2017 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class category extends base {
/**
* The name of the filter.
*
* @return string
*/
public static function get_filter_name() {
return 'category';
}
/**
* 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() {
$options = \core_course_category::make_categories_list();
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) {
$values = $tour->get_filter_values(self::get_filter_name());
if (empty($values) || empty($values[0])) {
// There are no values configured, meaning all.
return true;
}
if ($context->contextlevel < CONTEXT_COURSECAT) {
return false;
}
return self::check_contexts($context, $values);
}
/**
* Recursive function allows checking of parent categories.
*
* @param context $context
* @param array $values
* @return boolean
*/
private static function check_contexts(context $context, $values) {
if ($context->contextlevel > CONTEXT_COURSECAT) {
return self::check_contexts($context->get_parent_context(), $values);
} else if ($context->contextlevel == CONTEXT_COURSECAT) {
if (in_array($context->instanceid, $values)) {
return true;
} else {
return self::check_contexts($context->get_parent_context(), $values);
}
} else {
return false;
}
}
}
@@ -0,0 +1,108 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace tool_usertours\local\filter;
use tool_usertours\tour;
use context;
/**
* Course filter.
*
* @package tool_usertours
* @copyright 2017 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class course extends base {
/**
* The name of the filter.
*
* @return string
*/
public static function get_filter_name() {
return 'course';
}
/**
* Overrides the base add form element with a course selector.
*
* @param \MoodleQuickForm $mform
*/
public static function add_filter_to_form(\MoodleQuickForm &$mform) {
$options = ['multiple' => true];
$filtername = self::get_filter_name();
$key = "filter_{$filtername}";
$mform->addElement('course', $key, get_string($key, 'tool_usertours'), $options);
$mform->setDefault($key, '0');
$mform->addHelpButton($key, $key, 'tool_usertours');
}
/**
* 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 $COURSE;
$values = $tour->get_filter_values(self::get_filter_name());
if (empty($values) || empty($values[0])) {
// There are no values configured, meaning all.
return true;
}
if (empty($COURSE->id)) {
return false;
}
return in_array($COURSE->id, $values);
}
/**
* Overrides the base prepare the filter values for the form with an integer value.
*
* @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 = 0;
}
$data->$key = $values;
return $data;
}
/**
* Overrides the base save the filter values from the form to the tour.
*
* @param tour $tour The tour to save values to
* @param stdClass $data The data submitted in the form
*/
public static function save_filter_values_from_form(tour $tour, \stdClass $data) {
$filtername = static::get_filter_name();
$key = "filter_{$filtername}";
$newvalue = $data->$key;
if (empty($data->$key)) {
$newvalue = [];
}
$tour->set_filter_values($filtername, $newvalue);
}
}
@@ -0,0 +1,73 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace tool_usertours\local\filter;
use tool_usertours\tour;
use context;
/**
* Course format filter.
*
* @package tool_usertours
* @copyright 2017 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class courseformat extends base {
/**
* The name of the filter.
*
* @return string
*/
public static function get_filter_name() {
return 'courseformat';
}
/**
* 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() {
$options = [];
$courseformats = get_sorted_course_formats(true);
foreach ($courseformats as $courseformat) {
$options[$courseformat] = get_string('pluginname', "format_$courseformat");
}
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 $COURSE;
$values = $tour->get_filter_values('courseformat');
if (empty($values)) {
// There are no values configured, meaning all.
return true;
}
if (empty($COURSE->format)) {
return false;
}
return in_array($COURSE->format, $values);
}
}
@@ -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/>.
namespace tool_usertours\local\filter;
use tool_usertours\tour;
use context;
/**
* Theme filter.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class role extends base {
/**
* The Site Admin pseudo-role.
*
* @var ROLE_SITEADMIN int
*/
const ROLE_SITEADMIN = -1;
/**
* 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() {
$allroles = role_get_names(null, ROLENAME_ALIAS);
$roles = [];
foreach ($allroles as $role) {
if ($role->archetype === 'guest') {
// No point in including the 'guest' role as it isn't possible to show tours to a guest.
continue;
}
$roles[$role->shortname] = $role->localname;
}
// Add the Site Administrator pseudo-role.
$roles[self::ROLE_SITEADMIN] = get_string('administrator', 'core');
// Sort alphabetically too.
\core_collator::asort($roles);
return $roles;
}
/**
* 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;
}
// Presence within the array is sufficient. Ignore any value.
$values = array_flip($values);
if (isset($values[self::ROLE_SITEADMIN]) && is_siteadmin()) {
// This tour has been restricted to a role including site admin, and this user is a site admin.
return true;
}
// Use a request cache to save on DB queries.
// We may be checking multiple tours and they'll all be for the same userid, and contextid.
$cache = \cache::make_from_params(\cache_store::MODE_REQUEST, 'tool_usertours', 'filter_role');
// Get all of the roles used in this context, including special roles such as user, and frontpageuser.
$cachekey = "{$USER->id}_{$context->id}";
$userroles = $cache->get($cachekey);
if ($userroles === false) {
$userroles = get_user_roles_with_special($context);
$cache->set($cachekey, $userroles);
}
// Some special roles do not include the shortname.
// Therefore we must fetch all roles too. Thankfully these don't actually change based on context.
// They do require a DB call, so let's cache it.
$cachekey = "allroles";
$allroles = $cache->get($cachekey);
if ($allroles === false) {
$allroles = get_all_roles();
$cache->set($cachekey, $allroles);
}
// Now we can check whether any of the user roles are in the list of allowed roles for this filter.
foreach ($userroles as $role) {
$shortname = $allroles[$role->roleid]->shortname;
if (isset($values[$shortname])) {
return true;
}
}
return false;
}
}
@@ -0,0 +1,93 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace tool_usertours\local\filter;
use tool_usertours\tour;
use context;
/**
* Theme filter.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class 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,221 @@
<?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 <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_usertours\local\forms;
use stdClass;
use tool_usertours\helper;
use tool_usertours\step;
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 <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class editstep extends \moodleform {
/**
* @var tool_usertours\step $step
*/
protected $step;
/**
* @var int Display the step's content by using Moodle language string.
*/
private const CONTENTTYPE_LANGSTRING = 0;
/**
* @var int Display the step's content by entering it manually.
*/
private const CONTENTTYPE_MANUAL = 1;
/**
* 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() {
global $CFG;
$mform = $this->_form;
$mform->addElement('header', 'heading_target', get_string('target_heading', 'tool_usertours'));
$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);
$mform->addHelpButton('targettype', 'targettype', 'tool_usertours');
// 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);
}
// Content of the step.
$mform->addElement('header', 'heading_content', get_string('content_heading', 'tool_usertours'));
$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');
// Content type.
$typeoptions = [
static::CONTENTTYPE_LANGSTRING => get_string('content_type_langstring', 'tool_usertours'),
static::CONTENTTYPE_MANUAL => get_string('content_type_manual', 'tool_usertours'),
];
$mform->addElement('select', 'contenttype', get_string('content_type', 'tool_usertours'), $typeoptions);
$mform->addHelpButton('contenttype', 'content_type', 'tool_usertours');
$mform->setDefault('contenttype', static::CONTENTTYPE_MANUAL);
// Language identifier.
$mform->addElement('textarea', 'contentlangstring', get_string('moodle_language_identifier', 'tool_usertours'));
$mform->setType('contentlangstring', PARAM_TEXT);
$mform->hideIf('contentlangstring', 'contenttype', 'eq', static::CONTENTTYPE_MANUAL);
$editoroptions = [
'subdirs' => 1,
'maxbytes' => $CFG->maxbytes,
'maxfiles' => EDITOR_UNLIMITED_FILES,
'changeformat' => 1,
'trusttext' => true,
];
$mform->addElement('editor', 'content', get_string('content', 'tool_usertours'), null, $editoroptions);
$mform->addHelpButton('content', 'content', 'tool_usertours');
$mform->hideIf('content', 'contenttype', 'eq', static::CONTENTTYPE_LANGSTRING);
// Add the step configuration.
$mform->addElement('header', 'heading_options', get_string('options_heading', 'tool_usertours'));
// 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();
}
/**
* Validate the database on the submitted content type.
*
* @param array $data array of ("fieldname"=>value) of submitted data
* @param array $files array of uploaded files "element_name"=>tmp_file_path
* @return array of "element_name"=>"error_description" if there are errors,
* or an empty array if everything is OK (true allowed for backwards compatibility too).
*/
public function validation($data, $files): array {
$errors = parent::validation($data, $files);
if ($data['contenttype'] == static::CONTENTTYPE_LANGSTRING) {
if (!isset($data['contentlangstring']) || trim($data['contentlangstring']) == '') {
$errors['contentlangstring'] = get_string('required');
} else {
$splitted = explode(',', trim($data['contentlangstring']), 2);
$langid = $splitted[0];
$langcomponent = $splitted[1];
if (!get_string_manager()->string_exists($langid, $langcomponent)) {
$errors['contentlangstring'] = get_string('invalid_lang_id', 'tool_usertours');
}
}
}
// Validate manually entered text content. Validation logic derived from \MoodleQuickForm_Rule_Required::validate()
// without the checking of the "strictformsrequired" admin setting.
if ($data['contenttype'] == static::CONTENTTYPE_MANUAL) {
$value = $data['content']['text'] ?? '';
// All tags except img, canvas and hr, plus all forms of whitespaces.
$stripvalues = [
'#</?(?!img|canvas|hr).*?>#im',
'#(\xc2\xa0|\s|&nbsp;)#',
];
$value = preg_replace($stripvalues, '', (string)$value);
if (empty($value)) {
$errors['contenthtmlgrp'] = get_string('required');
}
}
return $errors;
}
/**
* Load in existing data as form defaults. Usually new entry defaults are stored directly in
* form definition (new entry form); this function is used to load in data where values
* already exist and data is being edited (edit entry form).
*
* @param stdClass|array $data object or array of default values
*/
public function set_data($data): void {
$data = (object) $data;
if (!isset($data->contenttype)) {
if (!empty($data->content['text']) && helper::is_language_string_from_input($data->content['text'])) {
$data->contenttype = static::CONTENTTYPE_LANGSTRING;
$data->contentlangstring = $data->content['text'];
// Empty the editor content.
$data->content = ['text' => ''];
} else {
$data->contenttype = static::CONTENTTYPE_MANUAL;
}
}
parent::set_data($data);
}
/**
* Return submitted data if properly submitted or returns NULL if validation fails or
* if there is no submitted data.
*
* @return object|null submitted data; NULL if not valid or not submitted or cancelled
*/
public function get_data(): ?object {
$data = parent::get_data();
if ($data) {
if ($data->contenttype == static::CONTENTTYPE_LANGSTRING) {
$data->content = [
'text' => $data->contentlangstring,
'format' => FORMAT_MOODLE,
];
}
}
return $data;
}
}
@@ -0,0 +1,116 @@
<?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 <andrew@nicols.co.uk>
* @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;
use tool_usertours\tour;
/**
* Form for editing tours.
*
* @copyright 2016 Andrew Nicols <andrew@nicols.co.uk>
* @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);
$mform->addHelpButton('name', 'name', 'tool_usertours');
// Admin-only descriptions.
$mform->addElement('textarea', 'description', get_string('description', 'tool_usertours'));
$mform->setType('description', PARAM_RAW);
$mform->addHelpButton('description', 'description', 'tool_usertours');
// 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('tourisenabled', 'tool_usertours'));
$mform->addElement('text', 'endtourlabel', get_string('endtourlabel', 'tool_usertours'));
$mform->setType('endtourlabel', PARAM_TEXT);
$mform->addHelpButton('endtourlabel', 'endtourlabel', 'tool_usertours');
$mform->addElement('checkbox', 'displaystepnumbers', get_string('displaystepnumbers', 'tool_usertours'));
$mform->addHelpButton('displaystepnumbers', 'displaystepnumbers', 'tool_usertours');
$mform->addElement(
'select',
'showtourwhen',
get_string('showtourwhen', 'tool_usertours'),
[
tour::SHOW_TOUR_UNTIL_COMPLETE => get_string('showtouruntilcomplete', 'tool_usertours'),
tour::SHOW_TOUR_ON_EACH_PAGE_VISIT => get_string('showtoureachtime', 'tool_usertours'),
]
);
$mform->setDefault('showtourwhen', tour::SHOW_TOUR_UNTIL_COMPLETE);
// 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 <andrew@nicols.co.uk>
* @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 <andrew@nicols.co.uk>
* @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,159 @@
<?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 <andrew@nicols.co.uk>
* @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 <andrew@nicols.co.uk>
* @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', [
'id' => $tourid,
]);
$this->define_baseurl($baseurl);
// Column definition.
$this->define_columns([
'title',
'content',
'target',
'actions',
]);
$this->define_headers([
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) {
$content = $step->get_content();
$systemcontext = \context_system::instance();
$content = file_rewrite_pluginfile_urls(
$content,
'pluginfile.php',
$systemcontext->id,
'tool_usertours',
'stepcontent',
$step->get_id()
);
$content = helper::get_string_from_input($content);
$content = step::get_step_image_from_input($content);
return format_text($content, $step->get_contentformat());
}
/**
* 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,164 @@
<?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 <andrew@nicols.co.uk>
* @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 <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class tour_list extends \flexible_table {
/** @var int The count of all tours. */
protected int $tourcount = 0;
/**
* 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([
'name',
'description',
'appliesto',
'enabled',
'actions',
]);
$this->define_headers([
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_duplicate_link(), 't/copy', get_string('duplicate'));
$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,117 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace tool_usertours\local\target;
use tool_usertours\step;
/**
* Target base.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <andrew@nicols.co.uk>
* @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) {
}
/**
* Prepare data to submit to the form.
*
* @param object $data The data being passed to the form
*/
abstract public function prepare_data_for_form($data);
/**
* 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,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/>.
namespace tool_usertours\local\target;
/**
* Block target.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class 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);
}
\core_collator::asort($blocks);
$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->hideIf(
'targetvalue_block',
'targettype',
'noteq',
\tool_usertours\target::get_target_constant_for_class(self::class)
);
}
/**
* Prepare data to submit to the form.
*
* @param object $data The data being passed to the form
*/
public function prepare_data_for_form($data) {
$data->targetvalue_block = $this->step->get_targetvalue();
}
/**
* 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,108 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace tool_usertours\local\target;
/**
* Selector target.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class 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->hideIf(
'targetvalue_selector',
'targettype',
'noteq',
\tool_usertours\target::get_target_constant_for_class(self::class)
);
}
/**
* Prepare data to submit to the form.
*
* @param object $data The data being passed to the form
*/
public function prepare_data_for_form($data) {
$data->targetvalue_selector = $this->step->get_targetvalue();
}
/**
* 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,98 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace tool_usertours\local\target;
/**
* A step designed to be orphaned.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class 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(self::class);
foreach (array_keys(self::$forcedsettings) as $settingname) {
$mform->hideIf($settingname, 'targettype', 'eq', $myvalue);
}
}
/**
* Prepare data to submit to the form.
*
* @param object $data The data being passed to the form
*/
public function prepare_data_for_form($data) {
$data->targetvalue_unattached = '';
}
/**
* 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 '';
}
}
+949
View File
@@ -0,0 +1,949 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace tool_usertours;
use tool_usertours\local\forms;
use tool_usertours\local\table;
use core\notification;
/**
* Tour manager.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class manager {
/**
* @var ACTION_LISTTOURS The action to get the list of tours.
*/
const ACTION_LISTTOURS = 'listtours';
/**
* @var ACTION_NEWTOUR The action to create a new tour.
*/
const ACTION_NEWTOUR = 'newtour';
/**
* @var ACTION_EDITTOUR The action to edit the tour.
*/
const ACTION_EDITTOUR = 'edittour';
/**
* @var ACTION_MOVETOUR The action to move a tour up or down.
*/
const ACTION_MOVETOUR = 'movetour';
/**
* @var ACTION_EXPORTTOUR The action to export the tour.
*/
const ACTION_EXPORTTOUR = 'exporttour';
/**
* @var ACTION_IMPORTTOUR The action to import the tour.
*/
const ACTION_IMPORTTOUR = 'importtour';
/**
* @var ACTION_DELETETOUR The action to delete the tour.
*/
const ACTION_DELETETOUR = 'deletetour';
/**
* @var ACTION_VIEWTOUR The action to view the tour.
*/
const ACTION_VIEWTOUR = 'viewtour';
/**
* @var ACTION_DUPLICATETOUR The action to duplicate the tour.
*/
const ACTION_DUPLICATETOUR = 'duplicatetour';
/**
* @var ACTION_NEWSTEP The action to create a new step.
*/
const ACTION_NEWSTEP = 'newstep';
/**
* @var ACTION_EDITSTEP The action to edit step configuration.
*/
const ACTION_EDITSTEP = 'editstep';
/**
* @var ACTION_MOVESTEP The action to move a step up or down.
*/
const ACTION_MOVESTEP = 'movestep';
/**
* @var ACTION_DELETESTEP The action to delete a step.
*/
const ACTION_DELETESTEP = 'deletestep';
/**
* @var ACTION_VIEWSTEP The action to view a step.
*/
const ACTION_VIEWSTEP = 'viewstep';
/**
* @var ACTION_HIDETOUR The action to hide a tour.
*/
const ACTION_HIDETOUR = 'hidetour';
/**
* @var ACTION_SHOWTOUR The action to show a tour.
*/
const ACTION_SHOWTOUR = 'showtour';
/**
* @var ACTION_RESETFORALL
*/
const ACTION_RESETFORALL = 'resetforall';
/**
* @var CONFIG_SHIPPED_TOUR
*/
const CONFIG_SHIPPED_TOUR = 'shipped_tour';
/**
* @var CONFIG_SHIPPED_FILENAME
*/
const CONFIG_SHIPPED_FILENAME = 'shipped_filename';
/**
* @var CONFIG_SHIPPED_VERSION
*/
const CONFIG_SHIPPED_VERSION = 'shipped_version';
/**
* Helper method to initialize admin page, setting appropriate extra URL parameters
*
* @param string $action
*/
protected function setup_admin_externalpage(string $action): void {
admin_externalpage_setup('tool_usertours/tours', '', array_filter([
'action' => $action,
'id' => optional_param('id', 0, PARAM_INT),
'tourid' => optional_param('tourid', 0, PARAM_INT),
'direction' => optional_param('direction', 0, PARAM_INT),
]));
}
/**
* This is the entry point for this controller class.
*
* @param string $action The action to perform.
*/
public function execute($action) {
global $PAGE;
$this->setup_admin_externalpage($action);
$PAGE->set_primary_active_tab('siteadminnode');
// Add the main content.
switch ($action) {
case self::ACTION_NEWTOUR:
case self::ACTION_EDITTOUR:
$this->edit_tour(optional_param('id', null, PARAM_INT));
break;
case self::ACTION_MOVETOUR:
$this->move_tour(required_param('id', PARAM_INT));
break;
case self::ACTION_EXPORTTOUR:
$this->export_tour(required_param('id', PARAM_INT));
break;
case self::ACTION_IMPORTTOUR:
$this->import_tour();
break;
case self::ACTION_VIEWTOUR:
$this->view_tour(required_param('id', PARAM_INT));
break;
case self::ACTION_DUPLICATETOUR:
$this->duplicate_tour(required_param('id', PARAM_INT));
break;
case self::ACTION_HIDETOUR:
$this->hide_tour(required_param('id', PARAM_INT));
break;
case self::ACTION_SHOWTOUR:
$this->show_tour(required_param('id', PARAM_INT));
break;
case self::ACTION_DELETETOUR:
$this->delete_tour(required_param('id', PARAM_INT));
break;
case self::ACTION_RESETFORALL:
$this->reset_tour_for_all(required_param('id', PARAM_INT));
break;
case self::ACTION_NEWSTEP:
case self::ACTION_EDITSTEP:
$this->edit_step(optional_param('id', null, PARAM_INT));
break;
case self::ACTION_MOVESTEP:
$this->move_step(required_param('id', PARAM_INT));
break;
case self::ACTION_DELETESTEP:
$this->delete_step(required_param('id', PARAM_INT));
break;
case self::ACTION_LISTTOURS:
default:
$this->print_tour_list();
break;
}
}
/**
* Print out the page header.
*
* @param string $title The title to display.
*/
protected function header($title = null) {
global $OUTPUT;
// Print the page heading.
echo $OUTPUT->header();
if ($title === null) {
$title = get_string('tours', 'tool_usertours');
}
echo $OUTPUT->heading($title);
}
/**
* Print out the page footer.
*
* @return void
*/
protected function footer() {
global $OUTPUT;
echo $OUTPUT->footer();
}
/**
* Print the the list of tours.
*/
protected function print_tour_list() {
global $PAGE, $OUTPUT;
$this->header();
echo \html_writer::span(get_string('tourlist_explanation', 'tool_usertours'));
$table = new table\tour_list();
$tours = helper::get_tours();
foreach ($tours as $tour) {
$table->add_data_keyed($table->format_row($tour));
}
$table->finish_output();
$actions = [
(object) [
'link' => helper::get_edit_tour_link(),
'linkproperties' => [],
'img' => 'b/tour-new',
'title' => get_string('newtour', 'tool_usertours'),
],
(object) [
'link' => helper::get_import_tour_link(),
'linkproperties' => [],
'img' => 'b/tour-import',
'title' => get_string('importtour', 'tool_usertours'),
],
(object) [
'link' => new \moodle_url('https://moodle.net/search', ['q' => 'user tours']),
'linkproperties' => [
'target' => '_blank',
],
'img' => 'b/tour-shared',
'title' => get_string('sharedtourslink', 'tool_usertours'),
],
];
echo \html_writer::start_tag('div', [
'class' => 'tour-actions',
]);
echo \html_writer::start_tag('ul');
foreach ($actions as $config) {
$action = \html_writer::start_tag('li');
$linkproperties = $config->linkproperties;
$linkproperties['href'] = $config->link;
$action .= \html_writer::start_tag('a', $linkproperties);
$action .= $OUTPUT->pix_icon($config->img, $config->title, 'tool_usertours');
$action .= \html_writer::div($config->title);
$action .= \html_writer::end_tag('a');
$action .= \html_writer::end_tag('li');
echo $action;
}
echo \html_writer::end_tag('ul');
echo \html_writer::end_tag('div');
// JS for Tour management.
$PAGE->requires->js_call_amd('tool_usertours/managetours', 'setup');
$this->footer();
}
/**
* Return the edit tour link.
*
* @param int $id The ID of the tour
* @return string
*/
protected function get_edit_tour_link($id = null) {
$addlink = helper::get_edit_tour_link($id);
return \html_writer::link($addlink, get_string('newtour', 'tool_usertours'));
}
/**
* Print the edit tour link.
*
* @param int $id The ID of the tour
*/
protected function print_edit_tour_link($id = null) {
echo $this->get_edit_tour_link($id);
}
/**
* Get the import tour link.
*
* @return string
*/
protected function get_import_tour_link() {
$importlink = helper::get_import_tour_link();
return \html_writer::link($importlink, get_string('importtour', 'tool_usertours'));
}
/**
* Print the edit tour page.
*
* @param int $id The ID of the tour
*/
protected function edit_tour($id = null) {
global $PAGE;
if ($id) {
$tour = tour::instance($id);
$PAGE->navbar->add(helper::get_string_from_input($tour->get_name()), $tour->get_edit_link());
} else {
$tour = new tour();
$PAGE->navbar->add(get_string('newtour', 'tool_usertours'), $tour->get_edit_link());
}
$form = new forms\edittour($tour);
if ($form->is_cancelled()) {
redirect(helper::get_list_tour_link());
} else if ($data = $form->get_data()) {
// Creating a new tour.
$tour->set_name($data->name);
$tour->set_description($data->description);
$tour->set_pathmatch($data->pathmatch);
$tour->set_enabled(!empty($data->enabled));
$tour->set_endtourlabel($data->endtourlabel);
$tour->set_display_step_numbers(!empty($data->displaystepnumbers));
$tour->set_showtourwhen($data->showtourwhen);
foreach (configuration::get_defaultable_keys() as $key) {
$tour->set_config($key, $data->$key);
}
// Save filter values.
foreach (helper::get_all_filters() as $filterclass) {
$filterclass::save_filter_values_from_form($tour, $data);
}
$tour->persist();
redirect(helper::get_list_tour_link());
} else {
if (empty($tour)) {
$this->header('newtour');
} else {
if (!empty($tour->get_config(self::CONFIG_SHIPPED_TOUR))) {
notification::add(get_string('modifyshippedtourwarning', 'tool_usertours'), notification::WARNING);
}
$tourname = !empty($tour->get_name()) ? helper::get_string_from_input($tour->get_name()) : '';
$this->header($tourname);
$data = $tour->prepare_data_for_form();
// Prepare filter values for the form.
foreach (helper::get_all_filters() as $filterclass) {
$filterclass::prepare_filter_values_for_form($tour, $data);
}
$form->set_data($data);
}
$form->display();
$this->footer();
}
}
/**
* Print the export tour page.
*
* @param int $id The ID of the tour
*/
protected function export_tour($id) {
$tour = tour::instance($id);
// Grab the full data record.
$export = $tour->to_record();
// Remove the id.
unset($export->id);
// Set the version.
$export->version = get_config('tool_usertours', 'version');
// Step export.
$export->steps = [];
foreach ($tour->get_steps() as $step) {
$record = $step->to_record(true);
unset($record->id);
unset($record->tourid);
$export->steps[] = $record;
}
$exportstring = json_encode($export);
$filename = 'tour_export_' . $tour->get_id() . '_' . time() . '.json';
// Force download.
send_file($exportstring, $filename, 0, 0, true, true);
}
/**
* Handle tour import.
*/
protected function import_tour() {
global $PAGE;
$PAGE->navbar->add(get_string('importtour', 'tool_usertours'), helper::get_import_tour_link());
$form = new forms\importtour();
if ($form->is_cancelled()) {
redirect(helper::get_list_tour_link());
} else if ($form->get_data()) {
// Importing a tour.
$tourconfigraw = $form->get_file_content('tourconfig');
$tour = self::import_tour_from_json($tourconfigraw);
redirect($tour->get_view_link());
} else {
$this->header();
$form->display();
$this->footer();
}
}
/**
* Print the view tour page.
*
* @param int $tourid The ID of the tour to display.
*/
protected function view_tour($tourid) {
global $PAGE;
$tour = helper::get_tour($tourid);
$tourname = helper::get_string_from_input($tour->get_name());
$PAGE->navbar->add($tourname, $tour->get_view_link());
$this->header($tourname);
echo \html_writer::span(get_string('viewtour_info', 'tool_usertours', [
'tourname' => $tourname,
'path' => $tour->get_pathmatch(),
]));
echo \html_writer::div(get_string('viewtour_edit', 'tool_usertours', [
'editlink' => $tour->get_edit_link()->out(),
'resetlink' => $tour->get_reset_link()->out(),
]));
$table = new table\step_list($tourid);
foreach ($tour->get_steps() as $step) {
$table->add_data_keyed($table->format_row($step));
}
$table->finish_output();
$this->print_edit_step_link($tourid);
// JS for Step management.
$PAGE->requires->js_call_amd('tool_usertours/managesteps', 'setup');
$this->footer();
}
/**
* Duplicate an existing tour.
*
* @param int $tourid The ID of the tour to duplicate.
*/
protected function duplicate_tour($tourid) {
$tour = helper::get_tour($tourid);
$export = $tour->to_record();
// Remove the id.
unset($export->id);
// Set the version.
$export->version = get_config('tool_usertours', 'version');
$export->name = get_string('duplicatetour_name', 'tool_usertours', $export->name);
// Step export.
$export->steps = [];
foreach ($tour->get_steps() as $step) {
$record = $step->to_record(true);
unset($record->id);
unset($record->tourid);
$export->steps[] = $record;
}
$exportstring = json_encode($export);
$newtour = self::import_tour_from_json($exportstring);
redirect($newtour->get_view_link());
}
/**
* Show the tour.
*
* @param int $tourid The ID of the tour to display.
*/
protected function show_tour($tourid) {
$this->show_hide_tour($tourid, 1);
}
/**
* Hide the tour.
*
* @param int $tourid The ID of the tour to display.
*/
protected function hide_tour($tourid) {
$this->show_hide_tour($tourid, 0);
}
/**
* Show or Hide the tour.
*
* @param int $tourid The ID of the tour to display.
* @param int $visibility The intended visibility.
*/
protected function show_hide_tour($tourid, $visibility) {
global $DB;
require_sesskey();
$tour = $DB->get_record('tool_usertours_tours', ['id' => $tourid]);
$tour->enabled = $visibility;
$DB->update_record('tool_usertours_tours', $tour);
redirect(helper::get_list_tour_link());
}
/**
* Delete the tour.
*
* @param int $tourid The ID of the tour to remove.
*/
protected function delete_tour($tourid) {
require_sesskey();
$tour = tour::instance($tourid);
$tour->remove();
redirect(helper::get_list_tour_link());
}
/**
* Reset the tour state for all users.
*
* @param int $tourid The ID of the tour to remove.
*/
protected function reset_tour_for_all($tourid) {
require_sesskey();
$tour = tour::instance($tourid);
$tour->mark_major_change();
redirect(helper::get_view_tour_link($tourid), get_string('tour_resetforall', 'tool_usertours'));
}
/**
* Get all tours for the current page URL.
*
* @param bool $reset Forcibly update the current tours
* @return array
*/
public static function get_current_tours($reset = false): array {
global $PAGE;
static $tours = false;
if ($tours === false || $reset) {
$tours = self::get_matching_tours($PAGE->url);
}
return $tours;
}
/**
* Get all tours matching the specified URL.
*
* @param moodle_url $pageurl The URL to match.
* @return array
*/
public static function get_matching_tours(\moodle_url $pageurl): array {
global $PAGE;
if (\core_user::awaiting_action()) {
// User not fully ready to use the site. Don't show any tours, we need the user to get properly set up so
// that all require_login() and other bits work as expected.
return [];
}
$tours = cache::get_matching_tourdata($pageurl);
$matches = [];
if ($tours) {
$filters = helper::get_all_filters();
foreach ($tours as $record) {
$tour = tour::load_from_record($record);
if ($tour->is_enabled() && $tour->matches_all_filters($PAGE->context, $filters)) {
$matches[] = $tour;
}
}
}
return $matches;
}
/**
* Import the provided tour JSON.
*
* @param string $json The tour configuration.
* @return tour
*/
public static function import_tour_from_json($json) {
$tourconfig = json_decode($json);
// We do not use this yet - we may do in the future.
unset($tourconfig->version);
$steps = $tourconfig->steps;
unset($tourconfig->steps);
$tourconfig->id = null;
$tourconfig->sortorder = null;
$tour = tour::load_from_record($tourconfig, true);
$tour->persist(true);
// Ensure that steps are orderered by their sortorder.
\core_collator::asort_objects_by_property($steps, 'sortorder', \core_collator::SORT_NUMERIC);
foreach ($steps as $stepconfig) {
$stepconfig->id = null;
$stepconfig->tourid = $tour->get_id();
$step = step::load_from_record($stepconfig, true, true);
$step->persist(true);
}
return $tour;
}
/**
* Helper to fetch the renderer.
*
* @return renderer
*/
protected function get_renderer() {
global $PAGE;
return $PAGE->get_renderer('tool_usertours');
}
/**
* Print the edit step link.
*
* @param int $tourid The ID of the tour.
* @param int $stepid The ID of the step.
* @return string
*/
protected function print_edit_step_link($tourid, $stepid = null) {
$addlink = helper::get_edit_step_link($tourid, $stepid);
$attributes = [];
if (empty($stepid)) {
$attributes['class'] = 'createstep';
}
echo \html_writer::link($addlink, get_string('newstep', 'tool_usertours'), $attributes);
}
/**
* Display the edit step form for the specified step.
*
* @param int $id The step to edit.
*/
protected function edit_step($id) {
global $PAGE;
if (isset($id)) {
$step = step::instance($id);
} else {
$step = new step();
$step->set_tourid(required_param('tourid', PARAM_INT));
}
$tour = $step->get_tour();
if (!empty($tour->get_config(self::CONFIG_SHIPPED_TOUR))) {
notification::add(get_string('modifyshippedtourwarning', 'tool_usertours'), notification::WARNING);
}
$PAGE->navbar->add(helper::get_string_from_input($tour->get_name()), $tour->get_view_link());
if (isset($id)) {
$PAGE->navbar->add(helper::get_string_from_input($step->get_title()), $step->get_edit_link());
} else {
$PAGE->navbar->add(get_string('newstep', 'tool_usertours'), $step->get_edit_link());
}
$form = new forms\editstep($step->get_edit_link(), $step);
if ($form->is_cancelled()) {
redirect($step->get_tour()->get_view_link());
} else if ($data = $form->get_data()) {
$step->handle_form_submission($form, $data);
$step->get_tour()->reset_step_sortorder();
redirect($step->get_tour()->get_view_link());
} else {
if (empty($id)) {
$this->header(get_string('newstep', 'tool_usertours'));
} else {
$this->header(get_string('editstep', 'tool_usertours', helper::get_string_from_input($step->get_title())));
}
$form->set_data($step->prepare_data_for_form());
$form->display();
$this->footer();
}
}
/**
* Move a tour up or down and redirect once complete.
*
* @param int $id The tour to move.
*/
protected function move_tour($id) {
require_sesskey();
$direction = required_param('direction', PARAM_INT);
$tour = tour::instance($id);
self::_move_tour($tour, $direction);
redirect(helper::get_list_tour_link());
}
/**
* Move a tour up or down.
*
* @param tour $tour The tour to move.
*
* @param int $direction
*/
protected static function _move_tour(tour $tour, $direction) {
// We can't move the first tour higher, nor the last tour any lower.
if (
($tour->is_first_tour() && $direction == helper::MOVE_UP) ||
($tour->is_last_tour() && $direction == helper::MOVE_DOWN)
) {
return;
}
$currentsortorder = $tour->get_sortorder();
$targetsortorder = $currentsortorder + $direction;
$swapwith = helper::get_tour_from_sortorder($targetsortorder);
// Set the sort order to something out of the way.
$tour->set_sortorder(-1);
$tour->persist();
// Swap the two sort orders.
$swapwith->set_sortorder($currentsortorder);
$swapwith->persist();
$tour->set_sortorder($targetsortorder);
$tour->persist();
}
/**
* Move a step up or down.
*
* @param int $id The step to move.
*/
protected function move_step($id) {
require_sesskey();
$direction = required_param('direction', PARAM_INT);
$step = step::instance($id);
$currentsortorder = $step->get_sortorder();
$targetsortorder = $currentsortorder + $direction;
$tour = $step->get_tour();
$swapwith = helper::get_step_from_sortorder($tour->get_id(), $targetsortorder);
// Set the sort order to something out of the way.
$step->set_sortorder(-1);
$step->persist();
// Swap the two sort orders.
$swapwith->set_sortorder($currentsortorder);
$swapwith->persist();
$step->set_sortorder($targetsortorder);
$step->persist();
// Reset the sort order.
$tour->reset_step_sortorder();
redirect($tour->get_view_link());
}
/**
* Delete the step.
*
* @param int $stepid The ID of the step to remove.
*/
protected function delete_step($stepid) {
require_sesskey();
$step = step::instance($stepid);
$tour = $step->get_tour();
$step->remove();
redirect($tour->get_view_link());
}
/**
* Make sure all of the default tours that are shipped with Moodle are created
* and up to date with the latest version.
*/
public static function update_shipped_tours() {
global $DB, $CFG;
// A list of tours that are shipped with Moodle. They are in
// the format filename => version. The version value needs to
// be increased if the tour has been updated.
$shippedtours = [
'40_tour_navigation_dashboard.json' => 4,
'40_tour_navigation_mycourse.json' => 5,
'40_tour_navigation_course_teacher.json' => 3,
'40_tour_navigation_course_student.json' => 3,
'42_tour_gradebook_grader_report.json' => 1,
];
// These are tours that we used to ship but don't ship any longer.
// We do not remove them, but we do disable them.
$unshippedtours = [
// Formerly included in Moodle 3.2.0.
'boost_administrator.json' => 1,
'boost_course_view.json' => 1,
// Formerly included in Moodle 3.6.0.
'36_dashboard.json' => 3,
'36_messaging.json' => 3,
// Formerly included in Moodle 3.11.0.
'311_activity_information_activity_page_student.json' => 2,
'311_activity_information_activity_page_teacher.json' => 2,
'311_activity_information_course_page_student.json' => 2,
'311_activity_information_course_page_teacher.json' => 2,
];
$existingtourrecords = $DB->get_recordset('tool_usertours_tours');
// Get all of the existing shipped tours and check if they need to be
// updated.
foreach ($existingtourrecords as $tourrecord) {
$tour = tour::load_from_record($tourrecord);
if (!empty($tour->get_config(self::CONFIG_SHIPPED_TOUR))) {
$filename = $tour->get_config(self::CONFIG_SHIPPED_FILENAME);
$version = $tour->get_config(self::CONFIG_SHIPPED_VERSION);
// If we know about this tour (otherwise leave it as is).
if (isset($shippedtours[$filename])) {
// And the version in the DB is an older version.
if ($version < $shippedtours[$filename]) {
// Remove the old version because it's been updated
// and needs to be recreated.
$tour->remove();
} else {
// The tour has not been updated so we don't need to
// do anything with it.
unset($shippedtours[$filename]);
}
}
if (isset($unshippedtours[$filename])) {
if ($version <= $unshippedtours[$filename]) {
$tour = tour::instance($tour->get_id());
$tour->set_enabled(tour::DISABLED);
$tour->persist();
}
}
}
}
$existingtourrecords->close();
// Ensure we correct the sortorder in any existing tours, prior to adding latest shipped tours.
helper::reset_tour_sortorder();
foreach (array_reverse($shippedtours) as $filename => $version) {
$filepath = $CFG->dirroot . "/{$CFG->admin}/tool/usertours/tours/" . $filename;
$tourjson = file_get_contents($filepath);
$tour = self::import_tour_from_json($tourjson);
// Set some additional config data to record that this tour was
// added as a shipped tour.
$tour->set_config(self::CONFIG_SHIPPED_TOUR, true);
$tour->set_config(self::CONFIG_SHIPPED_FILENAME, $filename);
$tour->set_config(self::CONFIG_SHIPPED_VERSION, $version);
// Bump new tours to the top of the list.
while ($tour->get_sortorder() > 0) {
self::_move_tour($tour, helper::MOVE_UP);
}
if (defined('BEHAT_SITE_RUNNING') || (defined('PHPUNIT_TEST') && PHPUNIT_TEST)) {
// Disable this tour if this is behat or phpunit.
$tour->set_enabled(false);
}
$tour->persist();
}
}
}
@@ -0,0 +1,27 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace tool_usertours\output;
/**
* Renderer.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class renderer extends \plugin_renderer_base {
}
@@ -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/>.
/**
* Tour Step Renderable.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_usertours\output;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir . "/filelib.php");
use tool_usertours\helper;
use tool_usertours\step as stepsource;
/**
* Tour Step Renderable.
*
* @copyright 2016 Andrew Nicols <andrew@nicols.co.uk>
* @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;
$content = $step->get_content();
$systemcontext = \context_system::instance();
$content = file_rewrite_pluginfile_urls(
$content,
'pluginfile.php',
$systemcontext->id,
'tool_usertours',
'stepcontent',
$step->get_id()
);
$content = helper::get_string_from_input($content);
$content = $step::get_step_image_from_input($content);
$result = (object) [
'stepid' => $step->get_id(),
'title' => \core_external\util::format_text(
helper::get_string_from_input($step->get_title()),
FORMAT_HTML,
$PAGE->context->id,
'tool_usertours'
)[0],
'content' => \core_external\util::format_text(
$content,
$step->get_contentformat(),
$PAGE->context->id,
'tool_usertours'
)[0],
'element' => $step->get_target()->convert_to_css(),
];
foreach ($step->get_config_keys() as $key) {
$result->$key = $step->get_config($key);
}
return $result;
}
}
@@ -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/>.
namespace tool_usertours\output;
use tool_usertours\tour as toursource;
/**
* Tour renderable.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class 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' => [],
'endtourlabel' => $this->tour->get_endtourlabel(),
'displaystepnumbers' => $this->tour->get_display_step_numbers(),
];
foreach ($this->tour->get_steps() as $step) {
$result->steps[] = (new step($step))->export_for_template($output);
}
return $result;
}
}
@@ -0,0 +1,88 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace tool_usertours\privacy;
use core_privacy\local\request\writer;
use core_privacy\local\metadata\collection;
use core_privacy\local\request\transform;
/**
* Implementation of the privacy subsystem plugin provider for the user tours feature.
*
* @package tool_usertours
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements
// This plugin has data.
\core_privacy\local\metadata\provider,
// This plugin has some sitewide user preferences to export.
\core_privacy\local\request\user_preference_provider {
/**
* Returns meta data about this system.
*
* @param collection $items The initialised item collection to add items to.
* @return collection A listing of user data stored through this system.
*/
public static function get_metadata(collection $items): collection {
// There are several user preferences.
$items->add_user_preference(\tool_usertours\tour::TOUR_REQUESTED_BY_USER, 'privacy:metadata:preference:requested');
$items->add_user_preference(\tool_usertours\tour::TOUR_LAST_COMPLETED_BY_USER, 'privacy:metadata:preference:completed');
return $items;
}
/**
* Store all user preferences for the plugin.
*
* @param int $userid The userid of the user whose data is to be exported.
*/
public static function export_user_preferences(int $userid) {
$preferences = get_user_preferences(null, null, $userid);
foreach ($preferences as $name => $value) {
$descriptionidentifier = null;
$tourid = null;
if (strpos($name, \tool_usertours\tour::TOUR_REQUESTED_BY_USER) === 0) {
$descriptionidentifier = 'privacy:request:preference:requested';
$tourid = substr($name, strlen(\tool_usertours\tour::TOUR_REQUESTED_BY_USER));
} else if (strpos($name, \tool_usertours\tour::TOUR_LAST_COMPLETED_BY_USER) === 0) {
$descriptionidentifier = 'privacy:request:preference:completed';
$tourid = substr($name, strlen(\tool_usertours\tour::TOUR_LAST_COMPLETED_BY_USER));
}
if ($descriptionidentifier !== null) {
try {
$tour = \tool_usertours\tour::instance($tourid);
$time = transform::datetime($value);
writer::export_user_preference(
'tool_usertours',
$name,
$time,
get_string($descriptionidentifier, 'tool_usertours', (object) [
'name' => $tour->get_name(),
'time' => $time,
])
);
} catch (\dml_missing_record_exception $ex) {
// The tour related to this user preference no longer exists.
}
}
}
}
}
+833
View File
@@ -0,0 +1,833 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace tool_usertours;
use context_system;
use stdClass;
/**
* Step class.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class 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 $contentformat The content format: FORMAT_MOODLE/FORMAT_HTML/FORMAT_PLAIN/FORMAT_MARKDOWN.
*/
protected $contentformat;
/**
* @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;
/**
* @var bool $isimporting Whether the step is being imported or not.
*/
protected $isimporting;
/**
* @var stdClass[] $files The list of attached files for this step.
*/
protected $files = [];
/**
* 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 bool $clean Clean the values.
* @param bool $isimporting Whether the step is being imported or not.
* @return step
*/
public static function load_from_record($record, $clean = false, bool $isimporting = false) {
$step = new self();
$step->set_importing($isimporting);
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', ['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 bool $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->contentformat = isset($record->contentformat) ? $record->contentformat : FORMAT_MOODLE;
$this->targettype = $record->targettype;
$this->targetvalue = $record->targetvalue;
$this->sortorder = $record->sortorder;
$this->config = json_decode($record->configdata);
$this->dirty = false;
if ($this->isimporting && isset($record->files)) {
// We are importing/exporting the step.
$this->files = $record->files;
}
return $this;
}
/**
* Set the import state for the step.
*
* @param bool $isimporting True if the step is imported, otherwise false.
* @return void
*/
protected function set_importing(bool $isimporting = false): void {
$this->isimporting = $isimporting;
}
/**
* 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_text($value);
$this->dirty = true;
return $this;
}
/**
* Get the content format of the step.
*
* @return int
*/
public function get_contentformat(): int {
return $this->contentformat;
}
/**
* 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.
* @param int $format The new format to use: FORMAT_MOODLE/FORMAT_HTML/FORMAT_PLAIN/FORMAT_MARKDOWN.
* @return $this
*/
public function set_content($value, $format = FORMAT_HTML) {
$this->content = clean_text($value);
$this->contentformat = $format;
$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) [];
}
if ($key === null) {
return $this->config;
}
if ($this->get_targettype() !== null) {
$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) [];
}
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);
}
/**
* Embed attached file to the json file for step.
*
* @return array List of files.
*/
protected function embed_files(): array {
$systemcontext = context_system::instance();
$fs = get_file_storage();
$areafiles = $fs->get_area_files($systemcontext->id, 'tool_usertours', 'stepcontent', $this->id);
$files = [];
foreach ($areafiles as $file) {
if ($file->is_directory()) {
continue;
}
$files[] = [
'name' => $file->get_filename(),
'path' => $file->get_filepath(),
'content' => base64_encode($file->get_content()),
'encode' => 'base64',
];
}
return $files;
}
/**
* Get the embed files information and create store_file for this step.
*
* @return void
*/
protected function extract_files() {
$fs = get_file_storage();
$systemcontext = context_system::instance();
foreach ($this->files as $file) {
$filename = $file->name;
$filepath = $file->path;
$filecontent = $file->content;
$filerecord = [
'contextid' => $systemcontext->id,
'component' => 'tool_usertours',
'filearea' => 'stepcontent',
'itemid' => $this->get_id(),
'filepath' => $filepath,
'filename' => $filename,
];
$fs->create_file_from_string($filerecord, base64_decode($filecontent));
}
}
/**
* Prepare this step for saving to the database.
*
* @param bool $isexporting Whether the step is being exported or not.
* @return object
*/
public function to_record(bool $isexporting = false) {
$record = [
'id' => $this->id,
'tourid' => $this->tourid,
'title' => $this->title,
'content' => $this->content,
'contentformat' => $this->contentformat,
'targettype' => $this->targettype,
'targetvalue' => $this->targetvalue,
'sortorder' => $this->sortorder,
'configdata' => json_encode($this->config),
];
if ($isexporting) {
// We are exporting the step, adding files node to the json record.
$record['files'] = $this->embed_files();
}
return (object) $record;
}
/**
* 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 $CFG, $DB;
require_once("{$CFG->libdir}/filelib.php");
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->get_tour()->reset_step_sortorder();
}
$systemcontext = context_system::instance();
if ($draftid = file_get_submitted_draft_itemid('content')) {
// Take any files added to the stepcontent draft file area and
// convert them into the proper event description file area. Also
// parse the content text and replace the URLs to the draft files
// with the @@PLUGIN_FILE@@ placeholder to be persisted in the DB.
$this->content = file_save_draft_area_files(
$draftid,
$systemcontext->id,
'tool_usertours',
'stepcontent',
$this->id,
['subdirs' => true],
$this->content
);
$DB->set_field('tool_usertours_steps', 'content', $this->content, ['id' => $this->id]);
}
if ($this->isimporting) {
// We are importing the step, we need to create store_file from the json record.
$this->extract_files();
}
$this->reload();
// Notify of a change to the step configuration.
// This must be done separately to tour change notifications.
cache::notify_step_change($this->get_tourid());
// Notify the cache that a tour has changed.
// Tours are only stored in the cache if there are steps.
// If there step count has changed for some reason, this will change the potential cache results.
cache::notify_tour_change();
return $this;
}
/**
* Remove this step.
*/
public function remove() {
global $DB;
if ($this->id === null) {
return;
}
$DB->delete_records('tool_usertours_steps', ['id' => $this->id]);
$this->get_tour()->reset_step_sortorder();
// Notify of a change to the step configuration.
// This must be done separately to tour change notifications.
cache::notify_step_change($this->get_id());
// Notify the cache that a tour has changed.
// Tours are only stored in the cache if there are steps.
// If there step count has changed for some reason, this will change the potential cache results.
cache::notify_tour_change();
}
/**
* 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() {
global $CFG;
require_once("{$CFG->libdir}/filelib.php");
$data = $this->to_record();
foreach (self::get_config_keys() as $key) {
$data->$key = $this->get_config($key, configuration::get_step_default_value($key));
}
if ($this->get_targettype() !== null) {
$this->get_target()->prepare_data_for_form($data);
}
// Prepare content for editing in a form 'editor' field type.
$draftitemid = file_get_submitted_draft_itemid('tool_usertours');
$systemcontext = context_system::instance();
$data->content = [
'format' => $data->contentformat,
'itemid' => $draftitemid,
'text' => file_prepare_draft_area(
$draftitemid,
$systemcontext->id,
'tool_usertours',
'stepcontent',
$this->id,
['subdirs' => true],
$data->content
),
];
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['text'], $data->content['format']);
$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;
}
/**
* Attempt to fetch any matching langstring if the string is in the
* format identifier,component.
*
* @deprecated since Moodle 4.0 MDL-72783. Please use helper::get_string_from_input() instead.
* @param string $string
* @return string
*/
public static function get_string_from_input($string) {
debugging('Use of ' . __FUNCTION__ .
'() have been deprecated, please update your code to use helper::get_string_from_input()', DEBUG_DEVELOPER);
return helper::get_string_from_input($string);
}
/**
* Attempt to replace PIXICON placeholder with the correct images for tour step content.
*
* @param string $content Tour content
* @return string Processed tour content
*/
public static function get_step_image_from_input(string $content): string {
if (strpos($content, '@@PIXICON') === false) {
return $content;
}
$content = preg_replace_callback(
'%@@PIXICON::(?P<identifier>([^::]*))::(?P<component>([^@@]*))@@%',
function (array $matches) {
global $OUTPUT;
$component = $matches['component'];
if ($component == 'moodle') {
$component = 'core';
}
return \html_writer::img(
$OUTPUT->image_url($matches['identifier'], $component)->out(false),
'',
['class' => 'img-fluid']
);
},
$content
);
return $content;
}
}
+118
View File
@@ -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/>.
namespace tool_usertours;
/**
* Target class.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class 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;
}
}
+900
View File
@@ -0,0 +1,900 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace tool_usertours;
/**
* Tour class.
*
* @package tool_usertours
* @copyright 2016 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class 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 int Whether to show the tour only until it has been marked complete */
const SHOW_TOUR_UNTIL_COMPLETE = 1;
/** @var int Whether to show the tour every time a page matches */
const SHOW_TOUR_ON_EACH_PAGE_VISIT = 2;
/**
* @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 $endtourlabel The end tour label.
*/
protected $endtourlabel;
/**
* @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 = [];
/**
* @var bool $displaystepnumbers Display the step numbers in this tour.
*/
protected $displaystepnumbers = true;
/**
* 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', ['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->endtourlabel = $record->endtourlabel ?? null;
$this->config = json_decode($record->configdata);
$this->dirty = false;
$this->steps = [];
$this->displaystepnumbers = !empty($record->displaystepnumbers);
return $this;
}
/**
* Fetch all steps in the tour.
*
* @return step[]
*/
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 end tour label for the tour.
*
* @return string
*/
public function get_endtourlabel(): string {
if ($this->endtourlabel) {
$label = helper::get_string_from_input($this->endtourlabel);
} else if ($this->count_steps() == 1) {
$label = get_string('endonesteptour', 'tool_usertours');
} else {
$label = get_string('endtour', 'tool_usertours');
}
return $label;
}
/**
* Set the endtourlabel of the tour to the specified value.
*
* @param string $value
* @return $this
*/
public function set_endtourlabel(string $value): tour {
$this->endtourlabel = $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 duplicate this tour.
*
* @return moodle_url
*/
public function get_duplicate_link() {
return helper::get_duplicate_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) [
'id' => $this->id,
'name' => $this->name,
'description' => $this->description,
'pathmatch' => $this->pathmatch,
'enabled' => $this->enabled,
'sortorder' => $this->sortorder,
'endtourlabel' => $this->endtourlabel,
'configdata' => json_encode($this->config),
'displaystepnumbers' => $this->displaystepnumbers,
];
}
/**
* 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) [];
}
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) [];
}
$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();
// Notify the cache that a tour has changed.
cache::notify_tour_change();
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', ['id' => $this->id]);
helper::reset_tour_sortorder();
$this->remove_user_preferences();
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', ['tourid' => $this->id], 'sortorder ASC', 'id');
$index = 0;
foreach ($steps as $step) {
$DB->set_field('tool_usertours_steps', 'sortorder', $index, ['id' => $step->id]);
$index++;
}
// Notify of a change to the step configuration.
// Note: Do not notify of a tour change here. This is only a step change for a tour.
cache::notify_step_change($this->get_id());
return $this;
}
/**
* Remove stored user preferences for the tour
*/
protected function remove_user_preferences(): void {
global $DB;
$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()]);
}
/**
* 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 ($this->get_showtourwhen() === self::SHOW_TOUR_ON_EACH_PAGE_VISIT) {
// The tour should be shown on every page visit.
return true;
}
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() {
global $USER;
$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_%d_%s', $USER->id, $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() {
// Clear old reset and completion notes.
$this->remove_user_preferences();
$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();
$data->showtourwhen = $this->get_showtourwhen();
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.
* @param array|null $filters Optional array of filters.
* @return bool
*/
public function matches_all_filters(\context $context, array $filters = null): bool {
if (!$filters) {
$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;
}
/**
* Gets all filter values for use in client side filters.
*
* @param array $filters Array of clientside filters.
* @return array
*/
public function get_client_filter_values(array $filters): array {
$results = [];
foreach ($filters as $filter) {
$results[$filter::get_filter_name()] = $filter::get_client_side_values($this);
}
return $results;
}
/**
* Set the value for the display step numbers setting.
*
* @param bool $value True for enable.
* @return $this
*/
public function set_display_step_numbers(bool $value): tour {
$this->displaystepnumbers = $value;
$this->dirty = true;
return $this;
}
/**
* Get the value of the display step numbers setting.
*
* @return bool
*/
public function get_display_step_numbers(): bool {
return $this->displaystepnumbers;
}
/**
* Set the value for the when to show the tour.
*
* @see self::SHOW_TOUR_UNTIL_COMPLETE
* @see self::SHOW_TOUR_ON_EACH_PAGE_VISIT
*
* @param int $value
* @return self
*/
public function set_showtourwhen(int $value): tour {
return $this->set_config('showtourwhen', $value);
}
/**
* When to show the tour.
*
* @see self::SHOW_TOUR_UNTIL_COMPLETE
* @see self::SHOW_TOUR_ON_EACH_PAGE_VISIT
*
* @return int
*/
public function get_showtourwhen(): int {
return $this->get_config('showtourwhen', self::SHOW_TOUR_UNTIL_COMPLETE);
}
}