Merge branch 'MDL-68076-master-4' of git://github.com/rezaies/moodle

This commit is contained in:
Andrew Nicols 2020-06-04 10:04:58 +08:00
commit 2edcba39a2
26 changed files with 1077 additions and 2 deletions

View File

@ -20,6 +20,9 @@ $moodleservices = new admin_settingpage('moodleservices', new lang_string('moodl
'admin'));
$ADMIN->add('root', $moodleservices);
$userfeedback = new admin_settingpage('userfeedback', new lang_string('feedbacksettings', 'admin'));
$ADMIN->add('root', $userfeedback);
if ($hassiteconfig) {
$optionalsubsystems = new admin_settingpage('optionalsubsystems', new lang_string('advancedfeatures', 'admin'));
$ADMIN->add('root', $optionalsubsystems);

View File

@ -0,0 +1,49 @@
<?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/>.
/**
* This file contains call to feedback settings
*
* @package core
* @copyright 2020 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
if ($hassiteconfig) {
$userfeedback->add(new admin_setting_configcheckbox('enableuserfeedback',
new lang_string('enableuserfeedback', 'admin'),
new lang_string('enableuserfeedback_desc', 'admin'), 1, 1, 0));
$options = [
core_userfeedback::REMIND_AFTER_UPGRADE => new lang_string('userfeedbackafterupgrade', 'admin'),
core_userfeedback::REMIND_PERIODICALLY => new lang_string('userfeedbackperiodically', 'admin'),
core_userfeedback::REMIND_NEVER => new lang_string('never'),
];
$userfeedback->add(new admin_setting_configselect('userfeedback_nextreminder',
new lang_string('userfeedbacknextreminder', 'admin'),
new lang_string('userfeedbacknextreminder_desc', 'admin'), 1, $options));
$userfeedback->hide_if('userfeedback_nextreminder', 'enableuserfeedback');
$userfeedback->add(new admin_setting_configtext('userfeedback_remindafter',
new lang_string('userfeedbackremindafter', 'admin'),
new lang_string('userfeedbackremindafter_desc', 'admin'), 90, PARAM_INT));
$userfeedback->hide_if('userfeedback_remindafter', 'enableuserfeedback');
$userfeedback->hide_if('userfeedback_remindafter', 'userfeedback_nextreminder', 'eq', 3);
}

View File

@ -546,6 +546,8 @@ $string['enablerssfeeds'] = 'Enable RSS feeds';
$string['enablesearchareas'] = 'Enable search areas';
$string['enablestats'] = 'Enable statistics';
$string['enabletrusttext'] = 'Enable trusted content';
$string['enableuserfeedback'] = 'Enable feedback about Moodle';
$string['enableuserfeedback_desc'] = 'If enabled, a \'Give feedback\' link is displayed in a Dashboard alert and in the footer for users to give feedback about the Moodle LMS to Moodle HQ. The Dashboard alert also has a \'Remind me later\' option.';
$string['enablewebservices'] = 'Enable web services';
$string['enablewsdocumentation'] = 'Web services documentation';
$string['enrolinstancedefaults'] = 'Enrolment instance defaults';
@ -578,6 +580,7 @@ $string['experimentalsettings'] = 'Experimental settings';
$string['extendedusernamechars'] = 'Allow extended characters in usernames';
$string['extramemorylimit'] = 'Extra PHP memory limit';
$string['fatalsessionautostart'] = '<p>Serious configuration error detected, please notify server administrator.</p><p> To operate properly, Moodle requires that administrator changes PHP settings.</p><p><code>session.auto_start</code> must be set to <code>off</code>.</p><p>This setting is controlled by editing <code>php.ini</code>, Apache/IIS <br />configuration or <code>.htaccess</code> file on the server.</p>';
$string['feedbacksettings'] = 'Feedback settings';
$string['filescleanupperiod'] = 'Clean up trash pool files';
$string['filescleanupperiod_help'] = 'How often trash pool files are deleted. These are files that are associated with a context that no longer exists, for example when a course is deleted. Please note: This setting can result in missing files in a course which is backed up, deleted and then restored if the setting \'Include files\' (backup_auto_files) in \'Automated backup settings\' is disabled.';
$string['fileconversioncleanuptask'] = 'Cleanup of temporary records for file conversions.';
@ -1413,6 +1416,12 @@ $string['useexternalyui'] = 'Use online YUI libraries';
$string['user'] = 'User';
$string['userbulk'] = 'Bulk user actions';
$string['userbulkdownload'] = 'Export users as';
$string['userfeedbackafterupgrade'] = 'After every major upgrade';
$string['userfeedbacknextreminder'] = 'Next feedback reminder';
$string['userfeedbacknextreminder_desc'] = 'When should we ask the user to give feedback again?';
$string['userfeedbackperiodically'] = 'Periodically';
$string['userfeedbackremindafter'] = 'Show reminder after';
$string['userfeedbackremindafter_desc'] = 'Remind users again to give feedback after the given number of days.';
$string['userimagesdisabled'] = 'Profile user images are disabled';
$string['userlist'] = 'Browse list of users';
$string['userdefaultpreferences'] = 'User default preferences';

View File

@ -225,6 +225,9 @@ $string['bycourseorder'] = 'By course order';
$string['byname'] = 'by {$a}';
$string['bypassed'] = 'Bypassed';
$string['cachecontrols'] = 'Cache controls';
$string['calltofeedback'] = 'Moodle HQ would like your feedback on the Moodle LMS.';
$string['calltofeedback_give'] = 'Give feedback';
$string['calltofeedback_remind'] = 'Remind me later';
$string['cancel'] = 'Cancel';
$string['cancelled'] = 'Cancelled';
$string['categories'] = 'Course categories';
@ -802,6 +805,8 @@ $string['eventsearchresultsviewed'] = 'Search results viewed';
$string['eventunknownlogged'] = 'Unknown event';
$string['eventusercreated'] = 'User created';
$string['eventuserdeleted'] = 'User deleted';
$string['eventuserfeedbackgiven'] = 'Feedback link clicked';
$string['eventuserfeedbackremind'] = 'Remind me later feedback link clicked';
$string['eventuserlistviewed'] = 'User list viewed';
$string['eventuserloggedout'] = 'User logged out';
$string['eventuserpasswordpolicyfailed'] = 'User password failed password policy';

2
lib/amd/build/userfeedback.min.js vendored Normal file
View File

@ -0,0 +1,2 @@
define ("core/userfeedback",["exports","core/ajax","core/notification"],function(a,b,c){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.registerEventListeners=void 0;b=d(b);c=d(c);function d(a){return a&&a.__esModule?a:{default:a}}var f={regions:{root:"[data-region=\"core/userfeedback\"]"},actions:{}};f.actions.give="".concat(f.regions.root," [data-action=\"give\"]");f.actions.remind="".concat(f.regions.root," [data-action=\"remind\"]");a.registerEventListeners=function registerEventListeners(){document.addEventListener("click",function(a){var b=a.target.closest(f.actions.give);if(b){a.preventDefault();g().then(function(){return i(b)}).then(h).catch(c.default.exception)}var d=a.target.closest(f.actions.remind);if(d){a.preventDefault();Promise.resolve(d).then(i).then(h).catch(c.default.exception)}})};var g=function(){return b.default.call([{methodname:"core_get_userfeedback_url",args:{contextid:M.cfg.contextid}}])[0].then(function(a){if(!window.open(a)){throw new Error("Unable to open popup")}})},h=function(a){if(a.dataset.record){return b.default.call([{methodname:"core_create_userfeedback_action_record",args:{action:a.dataset.action,contextid:M.cfg.contextid}}])[0]}return Promise.resolve()},i=function(a){if(a.dataset.hide){a.closest(f.regions.root).remove()}return a}});
//# sourceMappingURL=userfeedback.min.js.map

File diff suppressed because one or more lines are too long

115
lib/amd/src/userfeedback.js Normal file
View File

@ -0,0 +1,115 @@
// 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/>.
/**
* Handle clicking on action links of the feedback alert.
*
* @module core/cta_feedback
* @copyright 2020 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import Ajax from 'core/ajax';
import Notification from 'core/notification';
const Selectors = {
regions: {
root: '[data-region="core/userfeedback"]',
},
actions: {},
};
Selectors.actions.give = `${Selectors.regions.root} [data-action="give"]`;
Selectors.actions.remind = `${Selectors.regions.root} [data-action="remind"]`;
/**
* Attach the necessary event handlers to the action links
*/
export const registerEventListeners = () => {
document.addEventListener('click', e => {
const giveAction = e.target.closest(Selectors.actions.give);
if (giveAction) {
e.preventDefault();
giveFeedback()
.then(() => hideRoot(giveAction))
.then(recordAction)
.catch(Notification.exception);
}
const remindAction = e.target.closest(Selectors.actions.remind);
if (remindAction) {
e.preventDefault();
Promise.resolve(remindAction)
.then(hideRoot)
.then(recordAction)
.catch(Notification.exception);
}
});
};
/**
* The action function that is called when users choose to give feedback.
*
* @returns {Promise<void>}
*/
const giveFeedback = () => {
return Ajax.call([{
methodname: 'core_get_userfeedback_url',
args: {
contextid: M.cfg.contextid,
}
}])[0]
.then(url => {
if (!window.open(url)) {
throw new Error('Unable to open popup');
}
return;
});
};
/**
* Record the action that the user took.
*
* @param {HTMLElement} clickedItem The action element that the user chose.
* @returns {Promise}
*/
const recordAction = clickedItem => {
if (clickedItem.dataset.record) {
return Ajax.call([{
methodname: 'core_create_userfeedback_action_record',
args: {
action: clickedItem.dataset.action,
contextid: M.cfg.contextid,
}
}])[0];
}
return Promise.resolve();
};
/**
* Hide the root node of the CTA notification.
*
* @param {HTMLElement} clickedItem The action element that the user chose.
* @returns {HTMLElement}
*/
const hideRoot = clickedItem => {
if (clickedItem.dataset.hide) {
clickedItem.closest(Selectors.regions.root).remove();
}
return clickedItem;
};

View File

@ -0,0 +1,63 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Feedback given.
*
* @package core
* @copyright 2020 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\event;
defined('MOODLE_INTERNAL') || die();
/**
* Class userfeedback_give
*
* @package core
* @copyright 2020 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class userfeedback_give extends base {
/**
* Initialise required event data properties.
*/
protected function init() {
$this->data['crud'] = 'r';
$this->data['edulevel'] = self::LEVEL_OTHER;
}
/**
* Returns description of what happened.
*
* @return string
*/
public function get_description() {
return "The user with id '$this->userid' clicked on the give feedback link";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventuserfeedbackgiven');
}
}

View File

@ -0,0 +1,63 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Feedback remind.
*
* @package core
* @copyright 2020 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\event;
defined('MOODLE_INTERNAL') || die();
/**
* Class userfeedback_remind
*
* @package core
* @copyright 2020 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class userfeedback_remind extends base {
/**
* Initialise required event data properties.
*/
protected function init() {
$this->data['crud'] = 'r';
$this->data['edulevel'] = self::LEVEL_OTHER;
}
/**
* Returns description of what happened.
*
* @return string
*/
public function get_description() {
return "The user with id '$this->userid' clicked on the remind later to feedback link";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventuserfeedbackremind');
}
}

View File

@ -0,0 +1,81 @@
<?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/>.
/**
* External API to generate and return the URL of the feedback site.
*
* @package core
* @copyright 2020 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\external\userfeedback;
defined('MOODLE_INTERNAL') || die();
require_once("$CFG->libdir/externallib.php");
use external_api;
use external_function_parameters;
use external_value;
use external_single_structure;
use external_multiple_structure;
/**
* The external API to generate and return the feedback url.
*
* @copyright 2020 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class generate_url extends external_api {
/**
* Returns description of parameters.
*
* @return external_function_parameters
*/
public static function execute_parameters() {
return new external_function_parameters([
'contextid' => new external_value(PARAM_INT, 'The context id of the page the user is in'),
]);
}
/**
* Prepare and return the URL of the feedback site
*
* @param int $contextid The context id
* @return \stdClass
*/
public static function execute(int $contextid) {
global $PAGE;
external_api::validate_parameters(self::execute_parameters(), ['contextid' => $contextid]);
$context = \context::instance_by_id($contextid);
self::validate_context($context);
$PAGE->set_context($context);
return \core_userfeedback::make_link()->out(false);
}
/**
* Returns description of method result value
*
* @return external_value
*/
public static function execute_returns() {
return new external_value(PARAM_URL, 'Feedback site\'s URL');
}
}

View File

@ -0,0 +1,95 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* External API to record users action on the feedback notification.
*
* @package core
* @copyright 2020 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\external\userfeedback;
defined('MOODLE_INTERNAL') || die();
require_once("$CFG->libdir/externallib.php");
use external_api;
use external_function_parameters;
use external_value;
/**
* The external API to record users action on the feedback notification.
*
* @copyright 2020 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class record_action extends external_api {
/**
* Returns description of parameters.
*
* @return external_function_parameters
*/
public static function execute_parameters() {
return new external_function_parameters([
'action' => new external_value(PARAM_ALPHA, 'The action taken by user'),
'contextid' => new external_value(PARAM_INT, 'The context id of the page the user is in'),
]);
}
/**
* Record users action to the feedback CTA
*
* @param string $action The action the user took
* @param int $contextid The context id
* @throws \invalid_parameter_exception
*/
public static function execute(string $action, int $contextid) {
external_api::validate_parameters(self::execute_parameters(), [
'action' => $action,
'contextid' => $contextid,
]);
$context = \context::instance_by_id($contextid);
self::validate_context($context);
switch ($action) {
case 'give':
set_user_preference('core_userfeedback_give', time());
$event = \core\event\userfeedback_give::create(['context' => $context]);
$event->trigger();
break;
case 'remind':
set_user_preference('core_userfeedback_remind', time());
$event = \core\event\userfeedback_remind::create(['context' => $context]);
$event->trigger();
break;
default:
throw new \invalid_parameter_exception('Invalid value for action parameter (value: ' . $action . '),' .
'allowed values are: give,remind');
}
}
/**
* Returns description of method result value
*
* @return null
*/
public static function execute_returns() {
return null;
}
}

View File

@ -96,6 +96,51 @@ class notification {
);
}
/**
* @param string[] $icon The icon to use. Required keys are 'pix' and 'component'.
* @param string $message The message to display.
* @param array $actions An array of action links
* @param string $region Optional region name
* @throws \coding_exception
*/
public static function add_call_to_action(array $icon, string $message, array $actions, string $region = ''): void {
global $OUTPUT, $PAGE;
$context = new stdClass();
$context->icon = $icon;
$context->message = $message;
$context->region = $region;
$context->actions = array_map(function($action) {
$data = [];
foreach ($action['data'] as $name => $value) {
$data[] = ['name' => $name, 'value' => $value];
}
$action['data'] = $data;
return $action;
}, $actions);
$notification = $OUTPUT->render_from_template('core/local/notification/cta', $context);
if ($PAGE && $PAGE->state === \moodle_page::STATE_IN_BODY) {
$id = uniqid();
echo \html_writer::span($notification, '', ['id' => $id]);
echo \html_writer::script(
"(function() {" .
"var notificationHolder = document.getElementById('user-notifications');" .
"if (!notificationHolder) { return; }" .
"var thisNotification = document.getElementById('{$id}');" .
"if (!thisNotification) { return; }" .
"notificationHolder.insertBefore(thisNotification.firstChild, notificationHolder.firstChild);" .
"thisNotification.remove();" .
"})();"
);
} else {
throw new \coding_exception('You are calling add_call_to_action() either too early or too late.');
}
}
/**
* Fetch all of the notifications in the stack and clear the stack.
*

View File

@ -197,6 +197,7 @@ class icon_system_fontawesome extends icon_system_font {
'core:i/backup' => 'fa-file-zip-o',
'core:i/badge' => 'fa-shield',
'core:i/breadcrumbdivider' => 'fa-angle-right',
'core:i/bullhorn' => 'fa-bullhorn',
'core:i/calc' => 'fa-calculator',
'core:i/calendar' => 'fa-calendar',
'core:i/calendareventdescription' => 'fa-align-left',

View File

@ -0,0 +1,169 @@
<?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/>.
/**
* This file contains the core_userfeedback class
*
* @package core
* @copyright 2020 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* This Class contains helper functions for user feedback functionality.
*
* @copyright 2020 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class core_userfeedback {
/**
* @var int Ask user to give feedback a few days after each major upgrade.
*/
public const REMIND_AFTER_UPGRADE = 1;
/**
* @var int Ask user to give feedback periodically.
*/
public const REMIND_PERIODICALLY = 2;
/**
* @var int Do not ask user to give feedback.
*/
public const REMIND_NEVER = 3;
/**
* Displays the feedback reminder block.
*/
public static function print_reminder_block(): void {
global $PAGE;
static $jscalled = false;
$actions = [
[
'title' => get_string('calltofeedback_give'),
'url' => '#',
'data' => [
'action' => 'give',
'record' => 1,
'hide' => 1,
],
],
[
'title' => get_string('calltofeedback_remind'),
'url' => '#',
'data' => [
'action' => 'remind',
'record' => 1,
'hide' => 1,
],
],
];
$icon = [
'pix' => 'i/bullhorn',
'component' => 'core'
];
\core\notification::add_call_to_action($icon, get_string('calltofeedback'), $actions, 'core/userfeedback');
if (!$jscalled) {
$jscalled = true;
// Calling the following more than once will register event listeners twice.
$PAGE->requires->js_call_amd('core/userfeedback', 'registerEventListeners');
}
}
/**
* Indicates whether the feedback reminder block should be shown or not.
*
* @return bool
*/
public static function should_display_reminder(): bool {
global $CFG;
if ($CFG->enableuserfeedback && isloggedin() && !isguestuser()) {
$give = get_user_preferences('core_userfeedback_give');
$remind = get_user_preferences('core_userfeedback_remind');
$lastactiontime = max($give ?: 0, $remind ?: 0);
switch ($CFG->userfeedback_nextreminder) {
case self::REMIND_AFTER_UPGRADE:
$lastupgrade = self::last_major_upgrade_time();
if ($lastupgrade >= $lastactiontime) {
return $lastupgrade + ($CFG->userfeedback_remindafter * DAYSECS) < time();
}
break;
case self::REMIND_PERIODICALLY:
return $lastactiontime + ($CFG->userfeedback_remindafter * DAYSECS) < time();
break;
}
}
return false;
}
/**
* Prepare and return the URL of the feedback site
*
* @return moodle_url
*/
public static function make_link(): moodle_url {
global $CFG, $PAGE;
require_once($CFG->libdir . '/adminlib.php');
$baseurl = $CFG->userfeedback_url ?? 'https://feedback.moodle.org/lms';
$lang = clean_param(current_language(), PARAM_LANG); // Avoid breaking WS because of incorrect package langs.
$moodleurl = $CFG->wwwroot;
$moodleversion = $CFG->release;
$theme = $PAGE->theme->name;
$themeversion = get_component_version('theme_' . $theme);
$url = new moodle_url($baseurl, [
'lang' => $lang,
'moodle_url' => $moodleurl,
'moodle_version' => $moodleversion,
'theme' => $theme,
'theme_version' => $themeversion,
'newtest' => 'Y', // Respondents might be using the same device/browser to fill out the survey.
// The newtest param resets the session.
]);
return $url;
}
/**
* Returns the last major upgrade time
*
* @return int
*/
private static function last_major_upgrade_time(): int {
global $DB;
$targetversioncast = $DB->sql_cast_char2real('targetversion');
$versioncast = $DB->sql_cast_char2real('version');
// A time difference more than 3 months has to be a core upgrade.
$time = $DB->get_field_sql("SELECT timemodified
FROM {upgrade_log}
WHERE plugin = 'core' AND $targetversioncast - $versioncast > 30000
ORDER BY timemodified DESC
LIMIT 1");
return (int)$time;
}
}

View File

@ -2800,6 +2800,25 @@ $functions = array(
'ajax' => 'true',
'capabilities' => 'moodle/contentbank:manageowncontent',
],
'core_create_userfeedback_action_record' => [
'classname' => 'core\external\userfeedback\record_action',
'methodname' => 'execute',
'classpath' => '',
'description' => 'Record the action that the user takes in the user feedback notification for future use.',
'type' => 'write',
'ajax' => 'true',
'capabilities' => '',
],
'core_get_userfeedback_url' => [
'classname' => 'core\external\userfeedback\generate_url',
'methodname' => 'execute',
'classpath' => '',
'description' => 'Generate a dynamic URL for the external user feedback site.' .
' The URL includes some parameters to pre-fill the user feedback form.',
'type' => 'read',
'ajax' => 'true',
'capabilities' => '',
],
);
$services = array(

View File

@ -835,6 +835,12 @@ class core_renderer extends renderer_base {
}
}
if (isloggedin() && !isguestuser()) {
$output .= html_writer::div(
$this->render_from_template('core/userfeedback_footer_link', ['url' => core_userfeedback::make_link()->out(false)])
);
}
// This function is normally called from a layout.php file in {@link core_renderer::header()}
// but some of the content won't be known until later, so we return a placeholder
// for now. This will be replaced with the real content in {@link core_renderer::footer()}.

View File

@ -0,0 +1,68 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template core/local/notification/cta
Moodle cta notification template.
The purpose of this template is to render a call to action notification.
Classes required for JS:
* none
Data attributes required for JS:
* none
Context variables required for this template:
* message A cleaned string (use clean_text()) to display.
* extraclasses Additional classes to apply to the notification.
* actions List of action links.
* icon An icon.pix and icon.componrnt for the icon to be displauyed as the icon of CTA notification.
Example context (json):
{
"message": "What do you think about Moodle?",
"actions": [
{
"title": "Give feedback",
"url": "#",
"data": [
{"name": "action", "value": "give"},
{"name": "contextid", "value": "3"}
]
}
]
}
}}
<div class="cta alert alert-primary alert-block fade in {{ extraclasses }}" {{# region }}data-region="{{ region }}"{{/ region}}>
<div class="media">
<div class="mr-2 icon-size-5">
{{# pix }} {{ icon.pix }}, {{ icon.component }} {{/ pix }}
</div>
<div class="media-body align-self-center">
{{{ message }}}<br>
{{# actions }}
<a href="{{ url }}" class="link-underline aalink"
{{# data }}
data-{{ name }}="{{ value }}"
{{/ data }}
>{{ title }}</a>
{{/ actions }}
</div>
</div>
</div>

View File

@ -0,0 +1,27 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template core/userfeedback_footer_link
Adds the give feedback link.
Example context (json):
{}
}}
{{# pix }} i/bullhorn, core {{/ pix }}
<a href="{{url}}" target="_blank">{{#str}}calltofeedback_give{{/str}}</a>

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/>.
/**
* External functions test for generate_url.
*
* @package core
* @category test
* @copyright 2020 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\external\userfeedback;
defined('MOODLE_INTERNAL') || die();
use externallib_advanced_testcase;
use context_system;
use context_course;
use external_api;
global $CFG;
require_once($CFG->dirroot . '/webservice/tests/helpers.php');
/**
* Class generate_url_testcase
*
* @copyright 2020 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @coversDefaultClass generate_url
*/
class generate_url_testcase extends externallib_advanced_testcase {
/**
* Test the behaviour of generate_url().
*
* @covers ::execute
*/
public function test_record_action_system() {
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$user = $this->getDataGenerator()->create_and_enrol($course, 'student');
$context = context_system::instance();
$this->setUser($user);
// Call the WS and check the requested data is returned as expected.
$result = generate_url::execute($context->id);
$result = external_api::clean_returnvalue(generate_url::execute_returns(), $result);
$this->assertStringStartsWith('https://feedback.moodle.org/lms', $result);
$this->assertStringContainsString('?lang=en', $result);
$this->assertStringContainsString('&moodle_url=https%3A%2F%2Fwww.example.com%2Fmoodle', $result);
$this->assertStringContainsString('&theme=boost', $result);
}
/**
* Test the behaviour of generate_url() in a course with a course theme.
*
* @covers ::execute
*/
public function test_record_action_course_theme() {
$this->resetAfterTest();
// Enable course themes.
set_config('allowcoursethemes', 1);
$course = $this->getDataGenerator()->create_course(['theme' => 'classic']);
$user = $this->getDataGenerator()->create_and_enrol($course, 'student');
$context = context_course::instance($course->id);
$this->setUser($user);
// Call the WS and check the requested data is returned as expected.
$result = generate_url::execute($context->id);
$result = external_api::clean_returnvalue(generate_url::execute_returns(), $result);
$this->assertStringContainsString('&theme=classic', $result);
}
/**
* Test the behaviour of generate_url() when a custom feedback url is set.
*
* @covers ::execute
*/
public function test_record_action_custom_feedback_url() {
$this->resetAfterTest();
// Enable course themes.
set_config('userfeedback_url', 'https://feedback.moodle.org/abc');
$user = $this->getDataGenerator()->create_user();
$context = context_system::instance();
$this->setUser($user);
// Call the WS and check the requested data is returned as expected.
$result = generate_url::execute($context->id);
$result = external_api::clean_returnvalue(generate_url::execute_returns(), $result);
$this->assertStringStartsWith('https://feedback.moodle.org/abc', $result);
}
}

View File

@ -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/>.
/**
* External functions test for record_action.
*
* @package core
* @category test
* @copyright 2020 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\external\userfeedback;
defined('MOODLE_INTERNAL') || die();
use externallib_advanced_testcase;
use context_system;
global $CFG;
require_once($CFG->dirroot . '/webservice/tests/helpers.php');
/**
* Class record_action_testcase
*
* @copyright 2020 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @coversDefaultClass record_action
*/
class record_action_testcase extends externallib_advanced_testcase {
/**
* Data provider for test_record_action.
*
* @return array
*/
public function record_action_provider() {
return [
'give action' => ['give'],
'remind action' => ['remind'],
];
}
/**
* Test the behaviour of record_action().
*
* @dataProvider record_action_provider
* @param string $action The action taken by the user
*
* @covers ::execute
*/
public function test_record_action(string $action) {
$this->resetAfterTest();
$context = context_system::instance();
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$eventsink = $this->redirectEvents();
$now = time();
// Call the WS and check the action is recorded as expected.
$result = record_action::execute($action, $context->id);
$this->assertNull($result);
$preference = get_user_preferences('core_userfeedback_' . $action);
$this->assertGreaterThanOrEqual($now, $preference);
$events = $eventsink->get_events();
$this->assertCount(1, $events);
$this->assertInstanceOf('\core\event\userfeedback_' . $action, $events[0]);
$eventsink->clear();
}
}

View File

@ -163,6 +163,10 @@ if (empty($CFG->forcedefaultmymoodle) && $PAGE->user_allowed_editing()) {
echo $OUTPUT->header();
if (core_userfeedback::should_display_reminder()) {
core_userfeedback::print_reminder_block();
}
echo $OUTPUT->custom_block_region('content');
echo $OUTPUT->footer();

2
pix/i/bullhorn.svg Normal file
View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M1664 640q53 0 90.5 37.5t37.5 90.5-37.5 90.5-90.5 37.5v384q0 52-38 90t-90 38q-417-347-812-380-58 19-91 66t-31 100.5 40 92.5q-20 33-23 65.5t6 58 33.5 55 48 50 61.5 50.5q-29 58-111.5 83t-168.5 11.5-132-55.5q-7-23-29.5-87.5t-32-94.5-23-89-15-101 3.5-98.5 22-110.5h-122q-66 0-113-47t-47-113v-192q0-66 47-113t113-47h480q435 0 896-384 52 0 90 38t38 90v384zm-128 604v-954q-394 302-768 343v270q377 42 768 341z" fill="#999"/></svg>

After

Width:  |  Height:  |  Size: 561 B

View File

@ -2647,4 +2647,22 @@ $picker-emojis-per-row: 7 !default;
position: relative;
z-index: inherit;
}
}
}
.link-underline {
text-decoration: underline;
&:focus {
text-decoration: none;
}
}
.alert.cta {
.icon {
padding: 0.3rem;
&.fa {
border-radius: 50%;
border-style: solid;
border-width: 0.125rem;
}
}
}

View File

@ -11814,6 +11814,18 @@ body.h5p-embed .h5pmessages {
position: relative;
z-index: inherit; } }
.link-underline {
text-decoration: underline; }
.link-underline:focus {
text-decoration: none; }
.alert.cta .icon {
padding: 0.3rem; }
.alert.cta .icon.fa {
border-radius: 50%;
border-style: solid;
border-width: 0.125rem; }
.icon {
font-size: 16px;
width: 16px;

View File

@ -12028,6 +12028,18 @@ body.h5p-embed .h5pmessages {
position: relative;
z-index: inherit; } }
.link-underline {
text-decoration: underline; }
.link-underline:focus {
text-decoration: none; }
.alert.cta .icon {
padding: 0.3rem; }
.alert.cta .icon.fa {
border-radius: 50%;
border-style: solid;
border-width: 0.125rem; }
.icon {
font-size: 16px;
width: 16px;

View File

@ -29,7 +29,7 @@
defined('MOODLE_INTERNAL') || die();
$version = 2020060200.01; // YYYYMMDD = weekly release date of this DEV branch.
$version = 2020060400.00; // YYYYMMDD = weekly release date of this DEV branch.
// RR = release increments - 00 in DEV branches.
// .XX = incremental changes.
$release = '3.9dev+ (Build: 20200602)'; // Human-friendly version name