From d74a8268297a441b598393bca6f707c01a8e8ea8 Mon Sep 17 00:00:00 2001 From: Shamim Rezaie Date: Thu, 30 Apr 2020 15:36:31 +1000 Subject: [PATCH] MDL-68076 core: display call to feedback on user dashboard --- lib/amd/build/userfeedback.min.js | 2 + lib/amd/build/userfeedback.min.js.map | 1 + lib/amd/src/userfeedback.js | 103 ++++++++++++++++++++++++++ lib/classes/userfeedback.php | 88 ++++++++++++++++++++++ my/index.php | 4 + 5 files changed, 198 insertions(+) create mode 100644 lib/amd/build/userfeedback.min.js create mode 100644 lib/amd/build/userfeedback.min.js.map create mode 100644 lib/amd/src/userfeedback.js diff --git a/lib/amd/build/userfeedback.min.js b/lib/amd/build/userfeedback.min.js new file mode 100644 index 00000000000..87066590936 --- /dev/null +++ b/lib/amd/build/userfeedback.min.js @@ -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 h("give")}).then(function(){var a=b.closest(f.regions.root);a.remove()}).catch(c.default.exception)}var d=a.target.closest(f.actions.remind);if(d){a.preventDefault();h("remind").then(function(){var a=d.closest(f.regions.root);a.remove()}).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){return b.default.call([{methodname:"core_create_userfeedback_action_record",args:{action:a}}])[0]}}); +//# sourceMappingURL=userfeedback.min.js.map diff --git a/lib/amd/build/userfeedback.min.js.map b/lib/amd/build/userfeedback.min.js.map new file mode 100644 index 00000000000..897ef98bd4d --- /dev/null +++ b/lib/amd/build/userfeedback.min.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../src/userfeedback.js"],"names":["Selectors","regions","root","actions","give","remind","registerEventListeners","document","addEventListener","e","giveAction","target","closest","preventDefault","giveFeedback","then","recordAction","remove","catch","Notification","exception","remindAction","Ajax","call","methodname","args","contextid","M","cfg","url","window","open","Error","action"],"mappings":"sLAuBA,OACA,O,mDAEA,GAAMA,CAAAA,CAAS,CAAG,CACdC,OAAO,CAAE,CACLC,IAAI,CAAE,qCADD,CADK,CAIdC,OAAO,CAAE,EAJK,CAAlB,CAMAH,CAAS,CAACG,OAAV,CAAkBC,IAAlB,WAA4BJ,CAAS,CAACC,OAAV,CAAkBC,IAA9C,4BACAF,CAAS,CAACG,OAAV,CAAkBE,MAAlB,WAA8BL,CAAS,CAACC,OAAV,CAAkBC,IAAhD,8B,yBAKsC,QAAzBI,CAAAA,sBAAyB,EAAM,CACxCC,QAAQ,CAACC,gBAAT,CAA0B,OAA1B,CAAmC,SAAAC,CAAC,CAAI,CACpC,GAAMC,CAAAA,CAAU,CAAGD,CAAC,CAACE,MAAF,CAASC,OAAT,CAAiBZ,CAAS,CAACG,OAAV,CAAkBC,IAAnC,CAAnB,CACA,GAAIM,CAAJ,CAAgB,CACZD,CAAC,CAACI,cAAF,GACAC,CAAY,GACPC,IADL,CACU,UAAM,CACR,MAAOC,CAAAA,CAAY,CAAC,MAAD,CACtB,CAHL,EAIKD,IAJL,CAIU,UAAM,CACR,GAAMb,CAAAA,CAAI,CAAGQ,CAAU,CAACE,OAAX,CAAmBZ,CAAS,CAACC,OAAV,CAAkBC,IAArC,CAAb,CACAA,CAAI,CAACe,MAAL,EAEH,CARL,EASKC,KATL,CASWC,UAAaC,SATxB,CAUH,CAED,GAAMC,CAAAA,CAAY,CAAGZ,CAAC,CAACE,MAAF,CAASC,OAAT,CAAiBZ,CAAS,CAACG,OAAV,CAAkBE,MAAnC,CAArB,CACA,GAAIgB,CAAJ,CAAkB,CACdZ,CAAC,CAACI,cAAF,GACAG,CAAY,CAAC,QAAD,CAAZ,CACKD,IADL,CACU,UAAM,CACR,GAAMb,CAAAA,CAAI,CAAGmB,CAAY,CAACT,OAAb,CAAqBZ,CAAS,CAACC,OAAV,CAAkBC,IAAvC,CAAb,CACAA,CAAI,CAACe,MAAL,EAEH,CALL,EAMKC,KANL,CAMWC,UAAaC,SANxB,CAOH,CACJ,CA3BD,CA4BH,C,IAOKN,CAAAA,CAAY,CAAG,UAAM,CACvB,MAAOQ,WAAKC,IAAL,CAAU,CAAC,CACdC,UAAU,CAAE,2BADE,CAEdC,IAAI,CAAE,CACFC,SAAS,CAAEC,CAAC,CAACC,GAAF,CAAMF,SADf,CAFQ,CAAD,CAAV,EAKH,CALG,EAMFX,IANE,CAMG,SAAAc,CAAG,CAAI,CACT,GAAI,CAACC,MAAM,CAACC,IAAP,CAAYF,CAAZ,CAAL,CAAuB,CACnB,KAAM,IAAIG,CAAAA,KAAJ,CAAU,sBAAV,CACT,CAEJ,CAXE,CAYV,C,CAQKhB,CAAY,CAAG,SAAAiB,CAAM,CAAI,CAC3B,MAAOX,WAAKC,IAAL,CAAU,CAAC,CACdC,UAAU,CAAE,wCADE,CAEdC,IAAI,CAAE,CACFQ,MAAM,CAANA,CADE,CAFQ,CAAD,CAAV,EAKH,CALG,CAMV,C","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Handle clicking on action links of the feedback alert.\n *\n * @module core/cta_feedback\n * @copyright 2020 Shamim Rezaie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Ajax from 'core/ajax';\nimport Notification from 'core/notification';\n\nconst Selectors = {\n regions: {\n root: '[data-region=\"core/userfeedback\"]',\n },\n actions: {},\n};\nSelectors.actions.give = `${Selectors.regions.root} [data-action=\"give\"]`;\nSelectors.actions.remind = `${Selectors.regions.root} [data-action=\"remind\"]`;\n\n/**\n * Attach the necessary event handlers to the action links\n */\nexport const registerEventListeners = () => {\n document.addEventListener('click', e => {\n const giveAction = e.target.closest(Selectors.actions.give);\n if (giveAction) {\n e.preventDefault();\n giveFeedback()\n .then(() => {\n return recordAction('give');\n })\n .then(() => {\n const root = giveAction.closest(Selectors.regions.root);\n root.remove();\n return;\n })\n .catch(Notification.exception);\n }\n\n const remindAction = e.target.closest(Selectors.actions.remind);\n if (remindAction) {\n e.preventDefault();\n recordAction('remind')\n .then(() => {\n const root = remindAction.closest(Selectors.regions.root);\n root.remove();\n return;\n })\n .catch(Notification.exception);\n }\n });\n};\n\n/**\n * The action function that is called when users choose to give feedback.\n *\n * @returns {Promise}\n */\nconst giveFeedback = () => {\n return Ajax.call([{\n methodname: 'core_get_userfeedback_url',\n args: {\n contextid: M.cfg.contextid,\n }\n }])[0]\n .then(url => {\n if (!window.open(url)) {\n throw new Error('Unable to open popup');\n }\n return;\n });\n};\n\n/**\n * Record the action that the user took.\n *\n * @param {string} action The action that the user took. Either give or remind.\n * @returns {Promise}\n */\nconst recordAction = action => {\n return Ajax.call([{\n methodname: 'core_create_userfeedback_action_record',\n args: {\n action,\n }\n }])[0];\n};\n"],"file":"userfeedback.min.js"} \ No newline at end of file diff --git a/lib/amd/src/userfeedback.js b/lib/amd/src/userfeedback.js new file mode 100644 index 00000000000..20d93e50b13 --- /dev/null +++ b/lib/amd/src/userfeedback.js @@ -0,0 +1,103 @@ +// 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 . + +/** + * Handle clicking on action links of the feedback alert. + * + * @module core/cta_feedback + * @copyright 2020 Shamim Rezaie + * @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(() => { + return recordAction('give'); + }) + .then(() => { + const root = giveAction.closest(Selectors.regions.root); + root.remove(); + return; + }) + .catch(Notification.exception); + } + + const remindAction = e.target.closest(Selectors.actions.remind); + if (remindAction) { + e.preventDefault(); + recordAction('remind') + .then(() => { + const root = remindAction.closest(Selectors.regions.root); + root.remove(); + return; + }) + .catch(Notification.exception); + } + }); +}; + +/** + * The action function that is called when users choose to give feedback. + * + * @returns {Promise} + */ +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 {string} action The action that the user took. Either give or remind. + * @returns {Promise} + */ +const recordAction = action => { + return Ajax.call([{ + methodname: 'core_create_userfeedback_action_record', + args: { + action, + } + }])[0]; +}; diff --git a/lib/classes/userfeedback.php b/lib/classes/userfeedback.php index ec2c9953506..6ede8955820 100644 --- a/lib/classes/userfeedback.php +++ b/lib/classes/userfeedback.php @@ -45,4 +45,92 @@ class core_userfeedback { * @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', + ], + ], + [ + 'title' => get_string('calltofeedback_remind'), + 'url' => '#', + 'data' => [ + 'action' => 'remind', + ], + ], + ]; + $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; + } + + /** + * 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; + } } diff --git a/my/index.php b/my/index.php index 7e3957e041c..9722450fe89 100644 --- a/my/index.php +++ b/my/index.php @@ -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();