MDL-68076 core: display call to feedback on user dashboard
authorShamim Rezaie <shamim@moodle.com>
Thu, 30 Apr 2020 05:36:31 +0000 (15:36 +1000)
committerShamim Rezaie <shamim@moodle.com>
Thu, 4 Jun 2020 01:38:49 +0000 (11:38 +1000)
lib/amd/build/userfeedback.min.js [new file with mode: 0644]
lib/amd/build/userfeedback.min.js.map [new file with mode: 0644]
lib/amd/src/userfeedback.js [new file with mode: 0644]
lib/classes/userfeedback.php
my/index.php

diff --git a/lib/amd/build/userfeedback.min.js b/lib/amd/build/userfeedback.min.js
new file mode 100644 (file)
index 0000000..8706659
Binary files /dev/null and b/lib/amd/build/userfeedback.min.js differ
diff --git a/lib/amd/build/userfeedback.min.js.map b/lib/amd/build/userfeedback.min.js.map
new file mode 100644 (file)
index 0000000..897ef98
Binary files /dev/null and b/lib/amd/build/userfeedback.min.js.map differ
diff --git a/lib/amd/src/userfeedback.js b/lib/amd/src/userfeedback.js
new file mode 100644 (file)
index 0000000..20d93e5
--- /dev/null
@@ -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 <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(() => {
+                    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<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 {string} action The action that the user took. Either give or remind.
+ * @returns {Promise<null>}
+ */
+const recordAction = action => {
+    return Ajax.call([{
+        methodname: 'core_create_userfeedback_action_record',
+        args: {
+            action,
+        }
+    }])[0];
+};
index ec2c995..6ede895 100644 (file)
@@ -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;
+    }
 }
index 7e3957e..9722450 100644 (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();