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
@@ -0,0 +1,11 @@
/**
* Controls the content area of the notification area on the
* notification page.
*
* @module message_popup/notification_area_content_area
* @copyright 2016 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define("message_popup/notification_area_content_area",["jquery","core/templates","core/notification","core/custom_interaction_events","message_popup/notification_repository","message_popup/notification_area_events"],(function($,Templates,DebugNotification,CustomEvents,NotificationRepo,NotificationAreaEvents){var SELECTORS={CONTAINER:'[data-region="notification-area"]',CONTENT:'[data-region="content"]',HEADER:'[data-region="header"]',FOOTER:'[data-region="footer"]',TOGGLE_MODE:'[data-action="toggle-mode"]'},TEMPLATES_HEADER="message_popup/notification_area_content_area_header",TEMPLATES_CONTENT="message_popup/notification_area_content_area_content",TEMPLATES_FOOTER="message_popup/notification_area_content_area_footer",ContentArea=function(root,userId){this.root=$(root),this.container=this.root.closest(SELECTORS.CONTAINER),this.userId=userId,this.header=this.root.find(SELECTORS.HEADER),this.content=this.root.find(SELECTORS.CONTENT),this.footer=this.root.find(SELECTORS.FOOTER),this.registerEventListeners()};return ContentArea.prototype.getRoot=function(){return this.root},ContentArea.prototype.getContainer=function(){return this.container},ContentArea.prototype.getUserId=function(){return this.userId},ContentArea.prototype.getHeader=function(){return this.header},ContentArea.prototype.getContent=function(){return this.content},ContentArea.prototype.getFooter=function(){return this.footer},ContentArea.prototype.show=function(){this.getContainer().addClass("show-content-area")},ContentArea.prototype.hide=function(){this.getContainer().removeClass("show-content-area")},ContentArea.prototype.setHeaderHTML=function(html){this.getHeader().empty().html(html)},ContentArea.prototype.setContentHTML=function(html){this.getContent().empty().html(html)},ContentArea.prototype.setFooterHTML=function(html){this.getFooter().empty().html(html)},ContentArea.prototype.showNotification=function(notification){var headerPromise=Templates.render(TEMPLATES_HEADER,notification).done(function(html){this.setHeaderHTML(html)}.bind(this)),contentPromise=Templates.render(TEMPLATES_CONTENT,notification).done(function(html){this.setContentHTML(html)}.bind(this)),footerPromise=Templates.render(TEMPLATES_FOOTER,notification).done(function(html){this.setFooterHTML(html)}.bind(this));return $.when(headerPromise,contentPromise,footerPromise).done(function(){this.show(),this.getContainer().trigger(NotificationAreaEvents.notificationShown,[notification])}.bind(this))},ContentArea.prototype.registerEventListeners=function(){CustomEvents.define(this.getRoot(),[CustomEvents.events.activate]),this.getRoot().on(CustomEvents.events.activate,SELECTORS.VIEW_TOGGLE,function(){this.hide()}.bind(this)),this.getContainer().on(NotificationAreaEvents.showNotification,function(e,notification){this.showNotification(notification)}.bind(this))},ContentArea}));
//# sourceMappingURL=notification_area_content_area.min.js.map
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,3 @@
define("message_popup/notification_area_events",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;return _exports.default={showNotification:"notification-area-events:showNotification",notificationShown:"notification-area-events:notificationShown"},_exports.default}));
//# sourceMappingURL=notification_area_events.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"notification_area_events.min.js","sources":["../src/notification_area_events.js"],"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 <http://www.gnu.org/licenses/>.\n\n/**\n * Contain the events that can be fired in the notification area on\n * the notifications page.\n *\n * @module message_popup/notification_area_events\n * @copyright 2016 Ryan Wyllie <ryan@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default {\n showNotification: 'notification-area-events:showNotification',\n notificationShown: 'notification-area-events:notificationShown',\n};\n"],"names":["showNotification","notificationShown"],"mappings":"wLAuBe,CACXA,iBAAkB,4CAClBC,kBAAmB"}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,10 @@
/**
* Retrieves notifications from the server.
*
* @module message_popup/notification_repository
* @copyright 2016 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define("message_popup/notification_repository",["core/ajax","core/notification"],(function(Ajax,Notification){return{query:function(args){void 0===args.limit&&(args.limit=20),void 0===args.offset&&(args.offset=0);var request={methodname:"message_popup_get_popup_notifications",args:args},promise=Ajax.call([request])[0];return promise.fail(Notification.exception),promise},countUnread:function(args){var request={methodname:"message_popup_get_unread_popup_notification_count",args:args},promise=Ajax.call([request])[0];return promise.fail(Notification.exception),promise},markAllAsRead:function(args){var request={methodname:"core_message_mark_all_notifications_as_read",args:args},promise=Ajax.call([request])[0];return promise.fail(Notification.exception),promise},markAsRead:function(id,timeread){var args={notificationid:id};timeread&&(args.timeread=timeread);var request={methodname:"core_message_mark_notification_read",args:args},promise=Ajax.call([request])[0];return promise.fail(Notification.exception),promise}}}));
//# sourceMappingURL=notification_repository.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"notification_repository.min.js","sources":["../src/notification_repository.js"],"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 <http://www.gnu.org/licenses/>.\n\n/**\n * Retrieves notifications from the server.\n *\n * @module message_popup/notification_repository\n * @copyright 2016 Ryan Wyllie <ryan@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(['core/ajax', 'core/notification'], function(Ajax, Notification) {\n /**\n * Retrieve a list of notifications from the server.\n *\n * @param {object} args The request arguments\n * @return {object} jQuery promise\n */\n var query = function(args) {\n if (typeof args.limit === 'undefined') {\n args.limit = 20;\n }\n\n if (typeof args.offset === 'undefined') {\n args.offset = 0;\n }\n\n var request = {\n methodname: 'message_popup_get_popup_notifications',\n args: args\n };\n\n var promise = Ajax.call([request])[0];\n\n promise.fail(Notification.exception);\n\n return promise;\n };\n\n /**\n * Get the number of unread notifications from the server.\n *\n * @param {object} args The request arguments\n * @return {object} jQuery promise\n */\n var countUnread = function(args) {\n var request = {\n methodname: 'message_popup_get_unread_popup_notification_count',\n args: args\n };\n\n var promise = Ajax.call([request])[0];\n\n promise.fail(Notification.exception);\n\n return promise;\n };\n\n /**\n * Mark all notifications for the given user as read.\n *\n * @param {object} args The request arguments:\n * @return {object} jQuery promise\n */\n var markAllAsRead = function(args) {\n var request = {\n methodname: 'core_message_mark_all_notifications_as_read',\n args: args\n };\n\n var promise = Ajax.call([request])[0];\n\n promise.fail(Notification.exception);\n\n return promise;\n };\n\n /**\n * Mark a specific notification as read.\n *\n * @param {int} id The notification id\n * @param {int} timeread The read timestamp (optional)\n * @return {object} jQuery promise\n */\n var markAsRead = function(id, timeread) {\n var args = {\n notificationid: id,\n };\n\n if (timeread) {\n args.timeread = timeread;\n }\n\n var request = {\n methodname: 'core_message_mark_notification_read',\n args: args\n };\n\n var promise = Ajax.call([request])[0];\n\n promise.fail(Notification.exception);\n\n return promise;\n };\n\n return {\n query: query,\n countUnread: countUnread,\n markAllAsRead: markAllAsRead,\n markAsRead: markAsRead,\n };\n});\n"],"names":["define","Ajax","Notification","query","args","limit","offset","request","methodname","promise","call","fail","exception","countUnread","markAllAsRead","markAsRead","id","timeread","notificationid"],"mappings":";;;;;;;AAsBAA,+CAAO,CAAC,YAAa,sBAAsB,SAASC,KAAMC,oBA8F/C,CACHC,MAxFQ,SAASC,WACS,IAAfA,KAAKC,QACZD,KAAKC,MAAQ,SAGU,IAAhBD,KAAKE,SACZF,KAAKE,OAAS,OAGdC,QAAU,CACVC,WAAY,wCACZJ,KAAMA,MAGNK,QAAUR,KAAKS,KAAK,CAACH,UAAU,UAEnCE,QAAQE,KAAKT,aAAaU,WAEnBH,SAuEPI,YA9Dc,SAAST,UACnBG,QAAU,CACVC,WAAY,oDACZJ,KAAMA,MAGNK,QAAUR,KAAKS,KAAK,CAACH,UAAU,UAEnCE,QAAQE,KAAKT,aAAaU,WAEnBH,SAqDPK,cA5CgB,SAASV,UACrBG,QAAU,CACVC,WAAY,8CACZJ,KAAMA,MAGNK,QAAUR,KAAKS,KAAK,CAACH,UAAU,UAEnCE,QAAQE,KAAKT,aAAaU,WAEnBH,SAmCPM,WAzBa,SAASC,GAAIC,cACtBb,KAAO,CACPc,eAAgBF,IAGhBC,WACAb,KAAKa,SAAWA,cAGhBV,QAAU,CACVC,WAAY,sCACZJ,KAAMA,MAGNK,QAAUR,KAAKS,KAAK,CAACH,UAAU,UAEnCE,QAAQE,KAAKT,aAAaU,WAEnBH"}
@@ -0,0 +1,216 @@
// 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/>.
/**
* Controls the content area of the notification area on the
* notification page.
*
* @module message_popup/notification_area_content_area
* @copyright 2016 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery', 'core/templates', 'core/notification', 'core/custom_interaction_events',
'message_popup/notification_repository', 'message_popup/notification_area_events'],
function($, Templates, DebugNotification, CustomEvents, NotificationRepo, NotificationAreaEvents) {
var SELECTORS = {
CONTAINER: '[data-region="notification-area"]',
CONTENT: '[data-region="content"]',
HEADER: '[data-region="header"]',
FOOTER: '[data-region="footer"]',
TOGGLE_MODE: '[data-action="toggle-mode"]',
};
var TEMPLATES = {
HEADER: 'message_popup/notification_area_content_area_header',
CONTENT: 'message_popup/notification_area_content_area_content',
FOOTER: 'message_popup/notification_area_content_area_footer',
};
/**
* Constructor for the ContentArea
*
* @class
* @param {object} root The root element for the content area
* @param {int} userId The user id of the current user
*/
var ContentArea = function(root, userId) {
this.root = $(root);
this.container = this.root.closest(SELECTORS.CONTAINER);
this.userId = userId;
this.header = this.root.find(SELECTORS.HEADER);
this.content = this.root.find(SELECTORS.CONTENT);
this.footer = this.root.find(SELECTORS.FOOTER);
this.registerEventListeners();
};
/**
* Get the root element.
*
* @method getRoot
* @return {object} jQuery element
*/
ContentArea.prototype.getRoot = function() {
return this.root;
};
/**
* Get the container element (which the content area is within).
*
* @method getContainer
* @return {object} jQuery element
*/
ContentArea.prototype.getContainer = function() {
return this.container;
};
/**
* Get the user id.
*
* @method getUserId
* @return {int}
*/
ContentArea.prototype.getUserId = function() {
return this.userId;
};
/**
* Get the content area header element.
*
* @method getHeader
* @return {object} jQuery element
*/
ContentArea.prototype.getHeader = function() {
return this.header;
};
/**
* Get the content area content element.
*
* @method getContent
* @return {object} jQuery element
*/
ContentArea.prototype.getContent = function() {
return this.content;
};
/**
* Get the content area footer element.
*
* @method getFooter
* @return {object} jQuery element
*/
ContentArea.prototype.getFooter = function() {
return this.footer;
};
/**
* Display the content area. Typically used with responsive
* styling on smaller screens.
*
* @method show
*/
ContentArea.prototype.show = function() {
this.getContainer().addClass('show-content-area');
};
/**
* Hide the content area. Typically used with responsive
* styling on smaller screens.
*
* @method hide
*/
ContentArea.prototype.hide = function() {
this.getContainer().removeClass('show-content-area');
};
/**
* Change the HTML in the content area header element.
*
* @method setHeaderHTML
* @param {string} html The HTML to be set
*/
ContentArea.prototype.setHeaderHTML = function(html) {
this.getHeader().empty().html(html);
};
/**
* Change the HTML in the content area content element.
*
* @method setContentHMTL
* @param {string} html The HTML to be set.
*/
ContentArea.prototype.setContentHTML = function(html) {
this.getContent().empty().html(html);
};
/**
* Change the HTML in the content area footer element.
*
* @method setFooterHTML
* @param {string} html The HTML to be set.
*/
ContentArea.prototype.setFooterHTML = function(html) {
this.getFooter().empty().html(html);
};
/**
* Render the given notification context in the content area.
*
* @method showNotification
* @param {object} notification The notification context (from a webservice)
* @return {object} jQuery promise
*/
ContentArea.prototype.showNotification = function(notification) {
var headerPromise = Templates.render(TEMPLATES.HEADER, notification).done(function(html) {
this.setHeaderHTML(html);
}.bind(this));
var contentPromise = Templates.render(TEMPLATES.CONTENT, notification).done(function(html) {
this.setContentHTML(html);
}.bind(this));
var footerPromise = Templates.render(TEMPLATES.FOOTER, notification).done(function(html) {
this.setFooterHTML(html);
}.bind(this));
return $.when(headerPromise, contentPromise, footerPromise).done(function() {
this.show();
this.getContainer().trigger(NotificationAreaEvents.notificationShown, [notification]);
}.bind(this));
};
/**
* Create the event listeners for the content area.
*
* @method registerEventListeners
*/
ContentArea.prototype.registerEventListeners = function() {
CustomEvents.define(this.getRoot(), [
CustomEvents.events.activate
]);
this.getRoot().on(CustomEvents.events.activate, SELECTORS.VIEW_TOGGLE, function() {
this.hide();
}.bind(this));
this.getContainer().on(NotificationAreaEvents.showNotification, function(e, notification) {
this.showNotification(notification);
}.bind(this));
};
return ContentArea;
});
@@ -0,0 +1,443 @@
// 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/>.
/**
* Controls the notification area on the notification page.
*
* @module message_popup/notification_area_control_area
* @copyright 2016 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery', 'core/templates', 'core/notification', 'core/custom_interaction_events',
'message_popup/notification_repository', 'message_popup/notification_area_events'],
function($, Templates, DebugNotification, CustomEvents, NotificationRepo, NotificationAreaEvents) {
var SELECTORS = {
CONTAINER: '[data-region="notification-area"]',
CONTENT: '[data-region="content"]',
NOTIFICATION: '[data-region="notification-content-item-container"]',
CAN_RECEIVE_FOCUS: 'input:not([type="hidden"]), a[href], button, textarea, select, [tabindex]',
};
var TEMPLATES = {
NOTIFICATION: 'message_popup/notification_content_item',
};
/**
* Constructor for ControlArea
*
* @class
* @param {object} root The root element for the content area
* @param {int} userId The user id of the current user
*/
var ControlArea = function(root, userId) {
this.root = $(root);
this.container = this.root.closest(SELECTORS.CONTAINER);
this.userId = userId;
this.content = this.root.find(SELECTORS.CONTENT);
this.offset = 0;
this.limit = 20;
this.initialLoad = false;
this.isLoading = false;
this.loadedAll = false;
this.notifications = {};
this.registerEventListeners();
};
/**
* Get the root element.
*
* @method getRoot
* @return {object} jQuery element
*/
ControlArea.prototype.getRoot = function() {
return this.root;
};
/**
* Get the container element (which the control area is within).
*
* @method getContainer
* @return {object} jQuery element
*/
ControlArea.prototype.getContainer = function() {
return this.container;
};
/**
* Get the user id.
*
* @method getUserId
* @return {int}
*/
ControlArea.prototype.getUserId = function() {
return this.userId;
};
/**
* Get the control area content element.
*
* @method getContent
* @return {object} jQuery element
*/
ControlArea.prototype.getContent = function() {
return this.content;
};
/**
* Get the offset value for paginated loading of the
* notifications.
*
* @method getOffset
* @return {int}
*/
ControlArea.prototype.getOffset = function() {
return this.offset;
};
/**
* Get the limit value for the paginated loading of the
* notifications.
*
* @method getLimit
* @return {int}
*/
ControlArea.prototype.getLimit = function() {
return this.limit;
};
/**
* Set the offset value for the paginated loading of the
* notifications.
*
* @method setOffset
* @param {int} value The new offset value
*/
ControlArea.prototype.setOffset = function(value) {
this.offset = value;
};
/**
* Set the limit value for the paginated loading of the
* notifications.
*
* @method setLimit
* @param {int} value The new limit value
*/
ControlArea.prototype.setLimit = function(value) {
this.limit = value;
};
/**
* Increment the offset by the limit amount.
*
* @method incrementOffset
*/
ControlArea.prototype.incrementOffset = function() {
this.offset += this.limit;
};
/**
* Flag the control area as loading.
*
* @method startLoading
*/
ControlArea.prototype.startLoading = function() {
this.isLoading = true;
this.getRoot().addClass('loading');
};
/**
* Remove the loading flag from the control area.
*
* @method stopLoading
*/
ControlArea.prototype.stopLoading = function() {
this.isLoading = false;
this.getRoot().removeClass('loading');
};
/**
* Check if the first load of notifications has been triggered.
*
* @method hasDoneInitialLoad
* @return {bool} true if first notification loaded, false otherwise
*/
ControlArea.prototype.hasDoneInitialLoad = function() {
return this.initialLoad;
};
/**
* Check if all of the notifications have been loaded.
*
* @method hasLoadedAllContent
* @return {bool}
*/
ControlArea.prototype.hasLoadedAllContent = function() {
return this.loadedAll;
};
/**
* Set the state of the loaded all content property.
*
* @method setLoadedAllContent
* @param {bool} val True if all content is loaded, false otherwise
*/
ControlArea.prototype.setLoadedAllContent = function(val) {
this.loadedAll = val;
};
/**
* Save a notification in the cache.
*
* @method setCacheNotification
* @param {object} notification A notification returned by a webservice
*/
ControlArea.prototype.setCacheNotification = function(notification) {
this.notifications[notification.id] = notification;
};
/**
* Retrieve a notification from the cache.
*
* @method getCacheNotification
* @param {int} id The id for the notification you wish to retrieve
* @return {object} A notification (as returned by a webservice)
*/
ControlArea.prototype.getCacheNotification = function(id) {
return this.notifications[id];
};
/**
* Find the notification element in the control area for the given id.
*
* @method getNotificationElement
* @param {int} id The notification id
* @return {(object|null)} jQuery element or null
*/
ControlArea.prototype.getNotificationElement = function(id) {
var element = this.getRoot().find(SELECTORS.NOTIFICATION + '[data-id="' + id + '"]');
return element.length == 1 ? element : null;
};
/**
* Scroll the notification element into view within the control area, if it
* isn't already visible.
*
* @method scrollNotificationIntoView
* @param {object} notificationElement The jQuery notification element
*/
ControlArea.prototype.scrollNotificationIntoView = function(notificationElement) {
var position = notificationElement.position();
var container = this.getRoot();
var relativeTop = position.top - container.scrollTop();
// If the element isn't in the view window.
if (relativeTop > container.innerHeight()) {
var height = notificationElement.outerHeight();
// Offset enough to make sure the notification will be in view.
height = height * 4;
var scrollTo = position.top - height;
container.scrollTop(scrollTo);
}
};
/**
* Show the full notification for the given notification element. The notification
* context is retrieved from the cache and send as data with an event to be
* rendered in the content area.
*
* @method showNotification
* @param {(int|object)} notificationElement The notification id or jQuery notification element
*/
ControlArea.prototype.showNotification = function(notificationElement) {
if (typeof notificationElement !== 'object') {
// Assume it's an ID if it's not an object.
notificationElement = this.getNotificationElement(notificationElement);
}
if (notificationElement && notificationElement.length) {
this.getRoot().find(SELECTORS.NOTIFICATION).removeClass('selected');
notificationElement.addClass('selected').find(SELECTORS.CAN_RECEIVE_FOCUS).focus();
var notificationId = notificationElement.attr('data-id');
var notification = this.getCacheNotification(notificationId);
this.scrollNotificationIntoView(notificationElement);
// Create a new version of the notification to send with the notification so
// this copy isn't modified.
this.getContainer().trigger(NotificationAreaEvents.showNotification, [$.extend({}, notification)]);
}
};
/**
* Send a request to mark the notification as read in the server and remove the unread
* status from the element.
*
* @method markNotificationAsRead
* @param {object} notificationElement The jQuery notification element
* @return {object} jQuery promise
*/
ControlArea.prototype.markNotificationAsRead = function(notificationElement) {
return NotificationRepo.markAsRead(notificationElement.attr('data-id')).done(function() {
notificationElement.removeClass('unread');
});
};
/**
* Render the notification data with the appropriate template and add it to the DOM.
*
* @method renderNotifications
* @param {array} notifications Array of notification data
* @return {object} jQuery promise that is resolved when all notifications have been
* rendered and added to the DOM
*/
ControlArea.prototype.renderNotifications = function(notifications) {
var promises = [];
var container = this.getContent();
$.each(notifications, function(index, notification) {
// Need to remove the contexturl so the item isn't rendered
// as a link.
var contextUrl = notification.contexturl;
delete notification.contexturl;
var promise = Templates.render(TEMPLATES.NOTIFICATION, notification)
.then(function(html, js) {
// Restore it for the cache.
notification.contexturl = contextUrl;
this.setCacheNotification(notification);
// Pass the Rendered content out.
return {html: html, js: js};
}.bind(this));
promises.push(promise);
}.bind(this));
return $.when.apply($, promises).then(function() {
// Each of the promises in the when will pass its results as an argument to the function.
// The order of the arguments will be the order that the promises are passed to when()
// i.e. the first promise's results will be in the first argument.
$.each(arguments, function(index, argument) {
container.append(argument.html);
Templates.runTemplateJS(argument.js);
});
return;
});
};
/**
* Load notifications from the server and render them.
*
* @method loadMoreNotifications
* @return {object} jQuery promise
*/
ControlArea.prototype.loadMoreNotifications = function() {
if (this.isLoading || this.hasLoadedAllContent()) {
return $.Deferred().resolve();
}
this.startLoading();
var request = {
limit: this.getLimit(),
offset: this.getOffset(),
useridto: this.getUserId(),
};
if (!this.initialLoad) {
// If this is the first load we may have been given a non-zero offset,
// in which case we need to load all notifications preceeding that offset
// to make sure the full list is rendered.
request.limit = this.getOffset() + this.getLimit();
request.offset = 0;
}
var promise = NotificationRepo.query(request).then(function(result) {
var notifications = result.notifications;
this.unreadCount = result.unreadcount;
this.setLoadedAllContent(!notifications.length || notifications.length < this.getLimit());
this.initialLoad = true;
if (notifications.length) {
this.incrementOffset();
return this.renderNotifications(notifications);
}
return false;
}.bind(this))
.always(function() {
this.stopLoading();
}.bind(this));
return promise;
};
/**
* Create the event listeners for the control area.
*
* @method registerEventListeners
*/
ControlArea.prototype.registerEventListeners = function() {
CustomEvents.define(this.getRoot(), [
CustomEvents.events.activate,
CustomEvents.events.scrollBottom,
CustomEvents.events.scrollLock,
CustomEvents.events.up,
CustomEvents.events.down,
]);
this.getRoot().on(CustomEvents.events.scrollBottom, function() {
this.loadMoreNotifications();
}.bind(this));
this.getRoot().on(CustomEvents.events.activate, SELECTORS.NOTIFICATION, function(e) {
var notificationElement = $(e.target).closest(SELECTORS.NOTIFICATION);
this.showNotification(notificationElement);
}.bind(this));
// Show the previous notification in the list.
this.getRoot().on(CustomEvents.events.up, SELECTORS.NOTIFICATION, function(e, data) {
var notificationElement = $(e.target).closest(SELECTORS.NOTIFICATION);
this.showNotification(notificationElement.prev());
data.originalEvent.preventDefault();
}.bind(this));
// Show the next notification in the list.
this.getRoot().on(CustomEvents.events.down, SELECTORS.NOTIFICATION, function(e, data) {
var notificationElement = $(e.target).closest(SELECTORS.NOTIFICATION);
this.showNotification(notificationElement.next());
data.originalEvent.preventDefault();
}.bind(this));
this.getContainer().on(NotificationAreaEvents.notificationShown, function(e, notification) {
if (!notification.read) {
var element = this.getNotificationElement(notification.id);
if (element) {
this.markNotificationAsRead(element);
}
var cachedNotification = this.getCacheNotification(notification.id);
if (cachedNotification) {
cachedNotification.read = true;
}
}
}.bind(this));
};
return ControlArea;
});
@@ -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/>.
/**
* Contain the events that can be fired in the notification area on
* the notifications page.
*
* @module message_popup/notification_area_events
* @copyright 2016 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
export default {
showNotification: 'notification-area-events:showNotification',
notificationShown: 'notification-area-events:notificationShown',
};
@@ -0,0 +1,408 @@
// 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/>.
/**
* Controls the notification popover in the nav bar.
*
* See template: message_popup/notification_popover
*
* @module message_popup/notification_popover_controller
* @class notification_popover_controller
* @copyright 2016 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery', 'core/ajax', 'core/templates', 'core/str', 'core/url',
'core/notification', 'core/custom_interaction_events', 'core/popover_region_controller',
'message_popup/notification_repository', 'message_popup/notification_area_events'],
function($, Ajax, Templates, Str, URL, DebugNotification, CustomEvents,
PopoverController, NotificationRepo, NotificationAreaEvents) {
var SELECTORS = {
MARK_ALL_READ_BUTTON: '[data-action="mark-all-read"]',
ALL_NOTIFICATIONS_CONTAINER: '[data-region="all-notifications"]',
NOTIFICATION: '[data-region="notification-content-item-container"]',
UNREAD_NOTIFICATION: '[data-region="notification-content-item-container"].unread',
NOTIFICATION_LINK: '[data-action="content-item-link"]',
EMPTY_MESSAGE: '[data-region="empty-message"]',
COUNT_CONTAINER: '[data-region="count-container"]',
};
/**
* Constructor for the NotificationPopoverController.
* Extends PopoverRegionController.
*
* @param {object} element jQuery object root element of the popover
*/
var NotificationPopoverController = function(element) {
// Initialise base class.
PopoverController.call(this, element);
this.markAllReadButton = this.root.find(SELECTORS.MARK_ALL_READ_BUTTON);
this.unreadCount = 0;
this.lastQueried = 0;
this.userId = this.root.attr('data-userid');
this.container = this.root.find(SELECTORS.ALL_NOTIFICATIONS_CONTAINER);
this.limit = 20;
this.offset = 0;
this.loadedAll = false;
this.initialLoad = false;
// Let's find out how many unread notifications there are.
this.unreadCount = this.root.find(SELECTORS.COUNT_CONTAINER).html();
};
/**
* Clone the parent prototype.
*/
NotificationPopoverController.prototype = Object.create(PopoverController.prototype);
/**
* Make sure the constructor is set correctly.
*/
NotificationPopoverController.prototype.constructor = NotificationPopoverController;
/**
* Set the correct aria label on the menu toggle button to be read out by screen
* readers. The message will indicate the state of the unread notifications.
*
* @method updateButtonAriaLabel
*/
NotificationPopoverController.prototype.updateButtonAriaLabel = function() {
if (this.isMenuOpen()) {
Str.get_string('hidenotificationwindow', 'message').done(function(string) {
this.menuToggle.attr('aria-label', string);
}.bind(this));
} else {
if (this.unreadCount) {
Str.get_string('shownotificationwindowwithcount', 'message', this.unreadCount).done(function(string) {
this.menuToggle.attr('aria-label', string);
}.bind(this));
} else {
Str.get_string('shownotificationwindownonew', 'message').done(function(string) {
this.menuToggle.attr('aria-label', string);
}.bind(this));
}
}
};
/**
* Return the jQuery element with the content. This will return either
* the unread notification container or the all notification container
* depending on which is currently visible.
*
* @method getContent
* @return {object} jQuery object currently visible content contianer
*/
NotificationPopoverController.prototype.getContent = function() {
return this.container;
};
/**
* Get the offset value for the current state of the popover in order
* to sent to the backend to correctly paginate the notifications.
*
* @method getOffset
* @return {int} current offset
*/
NotificationPopoverController.prototype.getOffset = function() {
return this.offset;
};
/**
* Increment the offset for the current state, if required.
*
* @method incrementOffset
*/
NotificationPopoverController.prototype.incrementOffset = function() {
this.offset += this.limit;
};
/**
* Check if the first load of notification has been triggered for the current
* state of the popover.
*
* @method hasDoneInitialLoad
* @return {bool} true if first notification loaded, false otherwise
*/
NotificationPopoverController.prototype.hasDoneInitialLoad = function() {
return this.initialLoad;
};
/**
* Check if we've loaded all of the notifications for the current popover
* state.
*
* @method hasLoadedAllContent
* @return {bool} true if all notifications loaded, false otherwise
*/
NotificationPopoverController.prototype.hasLoadedAllContent = function() {
return this.loadedAll;
};
/**
* Set the state of the loaded all content property for the current state
* of the popover.
*
* @method setLoadedAllContent
* @param {bool} val True if all content is loaded, false otherwise
*/
NotificationPopoverController.prototype.setLoadedAllContent = function(val) {
this.loadedAll = val;
};
/**
* Show the unread notification count badge on the menu toggle if there
* are unread notifications, otherwise hide it.
*
* @method renderUnreadCount
*/
NotificationPopoverController.prototype.renderUnreadCount = function() {
var element = this.root.find(SELECTORS.COUNT_CONTAINER);
if (this.unreadCount) {
element.text(this.unreadCount);
element.removeClass('hidden');
} else {
element.addClass('hidden');
}
};
/**
* Hide the unread notification count badge on the menu toggle.
*
* @method hideUnreadCount
*/
NotificationPopoverController.prototype.hideUnreadCount = function() {
this.root.find(SELECTORS.COUNT_CONTAINER).addClass('hidden');
};
/**
* Find the notification element for the given id.
*
* @param {int} id
* @method getNotificationElement
* @return {object|null} The notification element
*/
NotificationPopoverController.prototype.getNotificationElement = function(id) {
var element = this.root.find(SELECTORS.NOTIFICATION + '[data-id="' + id + '"]');
return element.length == 1 ? element : null;
};
/**
* Render the notification data with the appropriate template and add it to the DOM.
*
* @method renderNotifications
* @param {array} notifications Notification data
* @param {object} container jQuery object the container to append the rendered notifications
* @return {object} jQuery promise that is resolved when all notifications have been
* rendered and added to the DOM
*/
NotificationPopoverController.prototype.renderNotifications = function(notifications, container) {
var promises = [];
$.each(notifications, function(index, notification) {
// Determine what the offset was when loading this notification.
var offset = this.getOffset() - this.limit;
// Update the view more url to contain the offset to allow the notifications
// page to load to the correct position in the list of notifications.
notification.viewmoreurl = URL.relativeUrl('/message/output/popup/notifications.php', {
notificationid: notification.id,
offset: offset,
});
// Link to mark read page before loading the actual link.
var notificationurlparams = {
notificationid: notification.id
};
notification.contexturl = URL.relativeUrl('message/output/popup/mark_notification_read.php', notificationurlparams);
var promise = Templates.render('message_popup/notification_content_item', notification)
.then(function(html, js) {
return {html: html, js: js};
});
promises.push(promise);
}.bind(this));
return $.when.apply($, promises).then(function() {
// Each of the promises in the when will pass its results as an argument to the function.
// The order of the arguments will be the order that the promises are passed to when()
// i.e. the first promise's results will be in the first argument.
$.each(arguments, function(index, argument) {
container.append(argument.html);
Templates.runTemplateJS(argument.js);
});
return;
});
};
/**
* Send a request for more notifications from the server, if we aren't already
* loading some and haven't already loaded all of them.
*
* Takes into account the current mode of the popover and will request only
* unread notifications if required.
*
* All notifications are marked as read by the server when they are returned.
*
* @method loadMoreNotifications
* @return {object} jQuery promise that is resolved when notifications have been
* retrieved and added to the DOM
*/
NotificationPopoverController.prototype.loadMoreNotifications = function() {
if (this.isLoading || this.hasLoadedAllContent()) {
return $.Deferred().resolve();
}
this.startLoading();
var request = {
limit: this.limit,
offset: this.getOffset(),
useridto: this.userId,
};
var container = this.getContent();
return NotificationRepo.query(request).then(function(result) {
var notifications = result.notifications;
this.unreadCount = result.unreadcount;
this.lastQueried = Math.floor(new Date().getTime() / 1000);
this.setLoadedAllContent(!notifications.length || notifications.length < this.limit);
this.initialLoad = true;
this.updateButtonAriaLabel();
if (notifications.length) {
this.incrementOffset();
return this.renderNotifications(notifications, container);
}
return false;
}.bind(this))
.always(function() {
this.stopLoading();
}.bind(this));
};
/**
* Send a request to the server to mark all unread notifications as read and update
* the unread count and unread notification elements appropriately.
*
* @return {Promise}
* @method markAllAsRead
*/
NotificationPopoverController.prototype.markAllAsRead = function() {
this.markAllReadButton.addClass('loading');
var request = {
useridto: this.userId,
timecreatedto: this.lastQueried,
};
return NotificationRepo.markAllAsRead(request)
.then(function() {
this.unreadCount = 0;
this.root.find(SELECTORS.UNREAD_NOTIFICATION).removeClass('unread');
}.bind(this))
.always(function() {
this.markAllReadButton.removeClass('loading');
}.bind(this));
};
/**
* Add all of the required event listeners for this notification popover.
*
* @method registerEventListeners
*/
NotificationPopoverController.prototype.registerEventListeners = function() {
CustomEvents.define(this.root, [
CustomEvents.events.activate,
]);
// Mark all notifications read if the user activates the mark all as read button.
this.root.on(CustomEvents.events.activate, SELECTORS.MARK_ALL_READ_BUTTON, function(e, data) {
this.markAllAsRead();
e.stopPropagation();
data.originalEvent.preventDefault();
}.bind(this));
// Mark individual notification read if the user activates it.
this.root.on(CustomEvents.events.activate, SELECTORS.NOTIFICATION_LINK, function(e) {
var element = $(e.target).closest(SELECTORS.NOTIFICATION);
if (element.hasClass('unread')) {
this.unreadCount--;
element.removeClass('unread');
}
e.stopPropagation();
}.bind(this));
// Update the notification information when the menu is opened.
this.root.on(this.events().menuOpened, function() {
this.hideUnreadCount();
this.updateButtonAriaLabel();
if (!this.hasDoneInitialLoad()) {
this.loadMoreNotifications();
}
}.bind(this));
// Update the unread notification count when the menu is closed.
this.root.on(this.events().menuClosed, function() {
this.renderUnreadCount();
this.updateButtonAriaLabel();
}.bind(this));
// Set aria attributes when popover is loading.
this.root.on(this.events().startLoading, function() {
this.getContent().attr('aria-busy', 'true');
}.bind(this));
// Set aria attributes when popover is finished loading.
this.root.on(this.events().stopLoading, function() {
this.getContent().attr('aria-busy', 'false');
}.bind(this));
// Load more notifications if the user has scrolled to the end of content
// item list.
this.getContentContainer().on(CustomEvents.events.scrollBottom, function() {
if (!this.isLoading && !this.hasLoadedAllContent()) {
this.loadMoreNotifications();
}
}.bind(this));
// Stop mouse scroll from propagating to the window element and
// scrolling the page.
CustomEvents.define(this.getContentContainer(), [
CustomEvents.events.scrollLock
]);
// Listen for when a notification is shown in the notifications page and mark
// it as read, if it's unread.
$(document).on(NotificationAreaEvents.notificationShown, function(e, notification) {
if (!notification.read) {
var element = this.getNotificationElement(notification.id);
if (element) {
element.removeClass('unread');
}
this.unreadCount--;
this.renderUnreadCount();
}
}.bind(this));
};
return NotificationPopoverController;
});
@@ -0,0 +1,123 @@
// 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/>.
/**
* Retrieves notifications from the server.
*
* @module message_popup/notification_repository
* @copyright 2016 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['core/ajax', 'core/notification'], function(Ajax, Notification) {
/**
* Retrieve a list of notifications from the server.
*
* @param {object} args The request arguments
* @return {object} jQuery promise
*/
var query = function(args) {
if (typeof args.limit === 'undefined') {
args.limit = 20;
}
if (typeof args.offset === 'undefined') {
args.offset = 0;
}
var request = {
methodname: 'message_popup_get_popup_notifications',
args: args
};
var promise = Ajax.call([request])[0];
promise.fail(Notification.exception);
return promise;
};
/**
* Get the number of unread notifications from the server.
*
* @param {object} args The request arguments
* @return {object} jQuery promise
*/
var countUnread = function(args) {
var request = {
methodname: 'message_popup_get_unread_popup_notification_count',
args: args
};
var promise = Ajax.call([request])[0];
promise.fail(Notification.exception);
return promise;
};
/**
* Mark all notifications for the given user as read.
*
* @param {object} args The request arguments:
* @return {object} jQuery promise
*/
var markAllAsRead = function(args) {
var request = {
methodname: 'core_message_mark_all_notifications_as_read',
args: args
};
var promise = Ajax.call([request])[0];
promise.fail(Notification.exception);
return promise;
};
/**
* Mark a specific notification as read.
*
* @param {int} id The notification id
* @param {int} timeread The read timestamp (optional)
* @return {object} jQuery promise
*/
var markAsRead = function(id, timeread) {
var args = {
notificationid: id,
};
if (timeread) {
args.timeread = timeread;
}
var request = {
methodname: 'core_message_mark_notification_read',
args: args
};
var promise = Ajax.call([request])[0];
promise.fail(Notification.exception);
return promise;
};
return {
query: query,
countUnread: countUnread,
markAllAsRead: markAllAsRead,
markAsRead: markAsRead,
};
});
+114
View File
@@ -0,0 +1,114 @@
<?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/>.
/**
* Contains class used to return information to display for the message popup.
*
* @package message_popup
* @copyright 2016 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace message_popup;
defined('MOODLE_INTERNAL') || die();
/**
* Class used to return information to display for the message popup.
*
* @copyright 2016 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class api {
/**
* Get popup notifications for the specified users. Nothing is returned if notifications are disabled.
*
* @param int $useridto the user id who received the notification
* @param string $sort the column name to order by including optionally direction
* @param int $limit limit the number of result returned
* @param int $offset offset the result set by this amount
* @return array notification records
* @throws \moodle_exception
* @since 3.2
*/
public static function get_popup_notifications($useridto = 0, $sort = 'DESC', $limit = 0, $offset = 0) {
global $DB, $USER;
$sort = strtoupper($sort);
if ($sort != 'DESC' && $sort != 'ASC') {
throw new \moodle_exception('invalid parameter: sort: must be "DESC" or "ASC"');
}
if (empty($useridto)) {
$useridto = $USER->id;
}
// Is notification enabled ?
if ($useridto == $USER->id) {
$disabled = $USER->emailstop;
} else {
$user = \core_user::get_user($useridto, "emailstop", MUST_EXIST);
$disabled = $user->emailstop;
}
if ($disabled) {
// Notifications are disabled.
return array();
}
$sql = "SELECT n.id, n.useridfrom, n.useridto,
n.subject, n.fullmessage, n.fullmessageformat,
n.fullmessagehtml, n.smallmessage, n.contexturl,
n.contexturlname, n.timecreated, n.component,
n.eventtype, n.timeread, n.customdata
FROM {notifications} n
WHERE n.id IN (SELECT notificationid FROM {message_popup_notifications})
AND n.useridto = ?
ORDER BY timecreated $sort, timeread $sort, id $sort";
$notifications = [];
$records = $DB->get_recordset_sql($sql, [$useridto], $offset, $limit);
foreach ($records as $record) {
$notifications[] = (object) $record;
}
$records->close();
return $notifications;
}
/**
* Count the unread notifications for a user.
*
* @param int $useridto the user id who received the notification
* @return int count of the unread notifications
* @since 3.2
*/
public static function count_unread_popup_notifications($useridto = 0) {
global $USER, $DB;
if (empty($useridto)) {
$useridto = $USER->id;
}
return $DB->count_records_sql(
"SELECT count(id)
FROM {notifications}
WHERE id IN (SELECT notificationid FROM {message_popup_notifications})
AND useridto = ?
AND timeread is NULL",
[$useridto]
);
}
}
@@ -0,0 +1,80 @@
<?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/>.
/**
* Contains class used to prepare a popup notification for display.
*
* @package message_popup
* @copyright 2016 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace message_popup\output;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/message/lib.php');
use renderable;
use templatable;
use moodle_url;
use core_user;
/**
* Class to prepare a popup notification for display.
*
* @package message_popup
* @copyright 2016 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class popup_notification implements templatable, renderable {
/**
* @var \stdClass The notification.
*/
protected $notification;
/**
* Constructor.
*
* @param \stdClass $notification
*/
public function __construct($notification) {
$this->notification = $notification;
}
public function export_for_template(\renderer_base $output) {
$context = clone $this->notification;
$context->timecreatedpretty = get_string('ago', 'message', format_time(time() - $context->timecreated));
$context->text = message_format_message_text($context);
$context->read = $context->timeread ? true : false;
// Need to strip any HTML from these.
$context->subject = clean_param($context->subject, PARAM_TEXT);
$context->contexturlname = clean_param($context->contexturlname, PARAM_TEXT);
$context->shortenedsubject = shorten_text($context->subject, 125);
if (!empty($context->component) && substr($context->component, 0, 4) == 'mod_') {
$iconurl = $output->image_url('monologo', $context->component);
} else {
$iconurl = $output->image_url('i/marker', 'core');
}
$context->iconurl = $iconurl->out();
return $context;
}
}
@@ -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/>.
/**
* Privacy Subsystem implementation for message_popup.
*
* @package message_popup
* @copyright 2018 Mark Nelson <markn@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace message_popup\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for message_popup implementing null_provider.
*
* @copyright 2018 Mark Nelson <markn@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason(): string {
return 'privacy:metadata';
}
}
+37
View File
@@ -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/>.
/**
* Installation code for the popup message processor
*
* @package message_popup
* @copyright 2009 Dongsheng Cai <dongsheng@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Install the popup message processor
*/
function xmldb_message_popup_install() {
global $DB;
$result = true;
$provider = new stdClass();
$provider->name = 'popup';
$DB->insert_record('message_processors', $provider);
return $result;
}
+32
View File
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8" ?>
<XMLDB PATH="message/output/popup/db" VERSION="20161221" COMMENT="XMLDB file for Moodle message/output/popup"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../../../lib/xmldb/xmldb.xsd"
>
<TABLES>
<TABLE NAME="message_popup" COMMENT="Keep state of notifications for the popup message processor">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="messageid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="isread" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
</KEYS>
<INDEXES>
<INDEX NAME="messageid-isread" UNIQUE="true" FIELDS="messageid, isread"/>
<INDEX NAME="isread" UNIQUE="false" FIELDS="isread"/>
</INDEXES>
</TABLE>
<TABLE NAME="message_popup_notifications" COMMENT="List of notifications to display in the message output popup">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="notificationid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="notificationid" TYPE="foreign" FIELDS="notificationid" REFTABLE="notifications" REFFIELDS="id"/>
</KEYS>
</TABLE>
</TABLES>
</XMLDB>
+46
View File
@@ -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/>.
defined('MOODLE_INTERNAL') || die();
/**
* External functions and service definitions.
*
* @package message_popup
* @copyright 2016 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$functions = array(
'message_popup_get_popup_notifications' => array(
'classname' => 'message_popup_external',
'methodname' => 'get_popup_notifications',
'classpath' => 'message/output/popup/externallib.php',
'description' => 'Retrieve a list of popup notifications for a user',
'type' => 'read',
'ajax' => true,
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
),
'message_popup_get_unread_popup_notification_count' => array(
'classname' => 'message_popup_external',
'methodname' => 'get_unread_popup_notification_count',
'classpath' => 'message/output/popup/externallib.php',
'description' => 'Retrieve the count of unread popup notifications for a given user',
'type' => 'read',
'ajax' => true,
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
'readonlysession' => true,
),
);
+44
View File
@@ -0,0 +1,44 @@
<?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/>.
/**
* Upgrade code for popup message processor
*
* @package message_popup
* @copyright 2008 Luis Rodrigues
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
*/
/**
* Upgrade code for the popup message processor
*
* @param int $oldversion The version that we are upgrading from
*/
function xmldb_message_popup_upgrade($oldversion) {
// Automatically generated Moodle v4.1.0 release upgrade line.
// Put any upgrade step following this.
// Automatically generated Moodle v4.2.0 release upgrade line.
// Put any upgrade step following this.
// Automatically generated Moodle v4.3.0 release upgrade line.
// Put any upgrade step following this.
// Automatically generated Moodle v4.4.0 release upgrade line.
// Put any upgrade step following this.
return true;
}
+245
View File
@@ -0,0 +1,245 @@
<?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/>.
use core_external\external_api;
use core_external\external_format_value;
use core_external\external_function_parameters;
use core_external\external_multiple_structure;
use core_external\external_single_structure;
use core_external\external_value;
defined('MOODLE_INTERNAL') || die();
/**
* External message popup API
*
* @package message_popup
* @category external
* @copyright 2016 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once($CFG->dirroot . "/message/lib.php");
/**
* Message external functions
*
* @package message_popup
* @category external
* @copyright 2016 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class message_popup_external extends external_api {
/**
* Get popup notifications parameters description.
*
* @return external_function_parameters
* @since 3.2
*/
public static function get_popup_notifications_parameters() {
return new external_function_parameters(
array(
'useridto' => new external_value(PARAM_INT, 'the user id who received the message, 0 for current user'),
'newestfirst' => new external_value(
PARAM_BOOL, 'true for ordering by newest first, false for oldest first',
VALUE_DEFAULT, true),
'limit' => new external_value(PARAM_INT, 'the number of results to return', VALUE_DEFAULT, 0),
'offset' => new external_value(PARAM_INT, 'offset the result set by a given amount', VALUE_DEFAULT, 0)
)
);
}
/**
* Get notifications function.
*
* @since 3.2
* @throws invalid_parameter_exception
* @throws moodle_exception
* @param int $useridto the user id who received the message
* @param bool $newestfirst true for ordering by newest first, false for oldest first
* @param int $limit the number of results to return
* @param int $offset offset the result set by a given amount
* @return \core_external\external_description
*/
public static function get_popup_notifications($useridto, $newestfirst, $limit, $offset) {
global $USER, $PAGE;
$params = self::validate_parameters(
self::get_popup_notifications_parameters(),
array(
'useridto' => $useridto,
'newestfirst' => $newestfirst,
'limit' => $limit,
'offset' => $offset,
)
);
$context = context_system::instance();
self::validate_context($context);
$useridto = $params['useridto'];
$newestfirst = $params['newestfirst'];
$limit = $params['limit'];
$offset = $params['offset'];
$issuperuser = has_capability('moodle/site:readallmessages', $context);
$renderer = $PAGE->get_renderer('core_message');
if (empty($useridto)) {
$useridto = $USER->id;
}
// Check if the current user is the sender/receiver or just a privileged user.
if ($useridto != $USER->id and !$issuperuser) {
throw new moodle_exception('accessdenied', 'admin');
}
if (!empty($useridto)) {
if (!core_user::is_real_user($useridto)) {
throw new moodle_exception('invaliduser');
}
}
$sort = $newestfirst ? 'DESC' : 'ASC';
$notifications = \message_popup\api::get_popup_notifications($useridto, $sort, $limit, $offset);
$notificationcontexts = [];
if ($notifications) {
foreach ($notifications as $notification) {
$notificationoutput = new \message_popup\output\popup_notification($notification);
$notificationcontext = $notificationoutput->export_for_template($renderer);
// Keep this for BC.
$notificationcontext->deleted = false;
$notificationcontexts[] = $notificationcontext;
}
}
return array(
'notifications' => $notificationcontexts,
'unreadcount' => \message_popup\api::count_unread_popup_notifications($useridto),
);
}
/**
* Get notifications return description.
*
* @return external_single_structure
* @since 3.2
*/
public static function get_popup_notifications_returns() {
return new external_single_structure(
array(
'notifications' => new external_multiple_structure(
new external_single_structure(
array(
'id' => new external_value(PARAM_INT, 'Notification id (this is not guaranteed to be unique
within this result set)'),
'useridfrom' => new external_value(PARAM_INT, 'User from id'),
'useridto' => new external_value(PARAM_INT, 'User to id'),
'subject' => new external_value(PARAM_TEXT, 'The notification subject'),
'shortenedsubject' => new external_value(PARAM_TEXT, 'The notification subject shortened
with ellipsis'),
'text' => new external_value(PARAM_RAW, 'The message text formated'),
'fullmessage' => new external_value(PARAM_RAW, 'The message'),
'fullmessageformat' => new external_format_value('fullmessage'),
'fullmessagehtml' => new external_value(PARAM_RAW, 'The message in html'),
'smallmessage' => new external_value(PARAM_RAW, 'The shorten message'),
'contexturl' => new external_value(PARAM_RAW, 'Context URL'),
'contexturlname' => new external_value(PARAM_TEXT, 'Context URL link name'),
'timecreated' => new external_value(PARAM_INT, 'Time created'),
'timecreatedpretty' => new external_value(PARAM_TEXT, 'Time created in a pretty format'),
'timeread' => new external_value(PARAM_INT, 'Time read'),
'read' => new external_value(PARAM_BOOL, 'notification read status'),
'deleted' => new external_value(PARAM_BOOL, 'notification deletion status'),
'iconurl' => new external_value(PARAM_URL, 'URL for notification icon'),
'component' => new external_value(PARAM_TEXT, 'The component that generated the notification',
VALUE_OPTIONAL),
'eventtype' => new external_value(PARAM_TEXT, 'The type of notification', VALUE_OPTIONAL),
'customdata' => new external_value(PARAM_RAW, 'Custom data to be passed to the message processor.
The data here is serialised using json_encode().', VALUE_OPTIONAL),
), 'message'
)
),
'unreadcount' => new external_value(PARAM_INT, 'the number of unread message for the given user'),
)
);
}
/**
* Get unread notification count parameters description.
*
* @return external_function_parameters
* @since 3.2
*/
public static function get_unread_popup_notification_count_parameters() {
return new external_function_parameters(
array(
'useridto' => new external_value(PARAM_INT, 'the user id who received the message, 0 for any user', VALUE_REQUIRED),
)
);
}
/**
* Get unread notification count function.
*
* @since 3.2
* @throws invalid_parameter_exception
* @throws moodle_exception
* @param int $useridto the user id who received the message
* @return \core_external\external_description
*/
public static function get_unread_popup_notification_count($useridto) {
global $USER;
$params = self::validate_parameters(
self::get_unread_popup_notification_count_parameters(),
array('useridto' => $useridto)
);
$context = context_system::instance();
self::validate_context($context);
$useridto = $params['useridto'];
if (!empty($useridto)) {
if (core_user::is_real_user($useridto)) {
$userto = core_user::get_user($useridto, '*', MUST_EXIST);
} else {
throw new moodle_exception('invaliduser');
}
}
// Check if the current user is the sender/receiver or just a privileged user.
if ($useridto != $USER->id and !has_capability('moodle/site:readallmessages', $context)) {
throw new moodle_exception('accessdenied', 'admin');
}
return \message_popup\api::count_unread_popup_notifications($useridto);
}
/**
* Get unread popup notification count return description.
*
* @return external_single_structure
* @since 3.2
*/
public static function get_unread_popup_notification_count_returns() {
return new external_value(PARAM_INT, 'The count of unread popup notifications');
}
}
@@ -0,0 +1,26 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Strings for component 'message_popup', language 'en'
*
* @package message_popup
* @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['pluginname'] = 'Web';
$string['privacy:metadata'] = 'The messaging Web plugin does not store any personal data.';
+72
View File
@@ -0,0 +1,72 @@
<?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/>.
/**
* Contains standard functions for message_popup.
*
* @package message_popup
* @copyright 2016 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Renders the popup.
*
* @param renderer_base $renderer
* @return string The HTML
*/
function message_popup_render_navbar_output(\renderer_base $renderer) {
global $USER, $CFG;
// Early bail out conditions.
if (!isloggedin() || isguestuser() || \core_user::awaiting_action()) {
return '';
}
$output = '';
// Add the notifications popover.
$enabled = \core_message\api::is_processor_enabled("popup");
if ($enabled) {
$unreadcount = \message_popup\api::count_unread_popup_notifications($USER->id);
$caneditownmessageprofile = has_capability('moodle/user:editownmessageprofile', context_system::instance());
$preferencesurl = $caneditownmessageprofile ? new moodle_url('/message/notificationpreferences.php') : null;
$context = [
'userid' => $USER->id,
'unreadcount' => $unreadcount,
'urls' => [
'seeall' => (new moodle_url('/message/output/popup/notifications.php'))->out(),
'preferences' => $preferencesurl ? $preferencesurl->out() : null,
],
];
$output .= $renderer->render_from_template('message_popup/notification_popover', $context);
}
// Add the messages popover.
if (!empty($CFG->messaging)) {
$unreadcount = \core_message\api::count_unread_conversations($USER);
$requestcount = \core_message\api::get_received_contact_requests_count($USER->id);
$context = [
'userid' => $USER->id,
'unreadcount' => $unreadcount + $requestcount
];
$output .= $renderer->render_from_template('core_message/message_popover', $context);
}
return $output;
}
@@ -0,0 +1,50 @@
<?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/>.
/**
* Mark a notification read and redirect to the relevant content.
*
* @package message_popup
* @copyright 2018 Michael Hawkins
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once(__DIR__ . '/../../../config.php');
require_login(null, false);
if (isguestuser()) {
redirect($CFG->wwwroot);
}
$notificationid = required_param('notificationid', PARAM_INT);
$notification = $DB->get_record('notifications', array('id' => $notificationid));
// If the redirect URL after filtering is empty, or it was never passed, then redirect to the notification page.
if (!empty($notification->contexturl)) {
$redirecturl = new moodle_url($notification->contexturl);
} else {
$redirecturl = new moodle_url('/message/output/popup/notifications.php', ['notificationid' => $notificationid]);
}
// Check notification belongs to this user.
if ($USER->id != $notification->useridto) {
redirect($CFG->wwwroot);
}
\core_message\api::mark_notification_as_read($notification);
redirect($redirecturl);
@@ -0,0 +1,143 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 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/>.
/**
* Popup message processor
*
* @package message_popup
* @copyright 2008 Luis Rodrigues
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v2 or later
*/
require_once(__DIR__ . '/../../../config.php'); //included from messagelib (how to fix?)
require_once($CFG->dirroot.'/message/output/lib.php');
/**
* The popup message processor
*
* @package message_popup
* @copyright 2008 Luis Rodrigues and Martin Dougiamas
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class message_output_popup extends message_output {
/**
* Adds notifications to the 'message_popup_notifications' table if applicable.
*
* The reason for this is because we may not want to show all notifications in the notification popover. This
* can happen if the popup processor was disabled when the notification was sent. If the processor is disabled this
* function is never called so the notification will never be added to the 'message_popup_notifications' table.
* Essentially this table is used to filter what notifications to display from the 'notifications' table.
*
* @param object $eventdata the event data submitted by the message sender plus $eventdata->savedmessageid
* @return true if ok, false if error
*/
public function send_message($eventdata) {
global $DB;
// Prevent users from getting popup notifications from themselves (happens with forum notifications).
if ($eventdata->notification) {
if (($eventdata->userfrom->id != $eventdata->userto->id) ||
(isset($eventdata->anonymous) && $eventdata->anonymous)) {
if (!$DB->record_exists('message_popup_notifications', ['notificationid' => $eventdata->savedmessageid])) {
$record = new stdClass();
$record->notificationid = $eventdata->savedmessageid;
$DB->insert_record('message_popup_notifications', $record);
}
}
}
return true;
}
/**
* Creates necessary fields in the messaging config form.
*
* @param array $preferences An array of user preferences
*/
function config_form($preferences) {
return null;
}
/**
* Parses the submitted form data and saves it into preferences array.
*
* @param stdClass $form preferences form class
* @param array $preferences preferences array
*/
public function process_form($form, &$preferences) {
return true;
}
/**
* Loads the config data from database to put on the form during initial form display
*
* @param array $preferences preferences array
* @param int $userid the user id
*/
public function load_data(&$preferences, $userid) {
global $USER;
return true;
}
/**
* Don't show this processor on the message preferences page. The user can't disable
* the notifications for user-to-user messaging.
*
* @return bool
*/
public function has_message_preferences() {
return false;
}
/**
* Determines if this processor should process a message regardless of user preferences or site settings.
*
* @return bool
*/
public function force_process_messages() {
global $CFG;
return !empty($CFG->messaging);
}
/**
* Remove all popup notifications up to specified time
*
* @param int $notificationdeletetime
* @return void
*/
public function cleanup_all_notifications(int $notificationdeletetime): void {
global $DB;
$DB->delete_records_select('message_popup_notifications',
'notificationid IN (SELECT id FROM {notifications} WHERE timecreated < ?)', [$notificationdeletetime]);
}
/**
* Remove read popup notifications up to specified time
*
* @param int $notificationdeletetime
* @return void
*/
public function cleanup_read_notifications(int $notificationdeletetime): void {
global $DB;
$DB->delete_records_select('message_popup_notifications',
'notificationid IN (SELECT id FROM {notifications} WHERE timeread < ?)', [$notificationdeletetime]);
}
}
+78
View File
@@ -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/>.
/**
* View a user's notifications.
*
* @package message_popup
* @copyright 2016 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once(__DIR__ . '/../../../config.php');
$notificationid = optional_param('notificationid', 0, PARAM_INT);
$offset = optional_param('offset', 0, PARAM_INT);
$limit = optional_param('limit', 0, PARAM_INT);
$userid = $USER->id;
$url = new moodle_url('/message/output/popup/notifications.php');
$url->param('id', $notificationid);
$PAGE->set_url($url);
require_login();
if (isguestuser()) {
throw new \moodle_exception('guestnoeditmessage', 'message');
}
if (!$user = $DB->get_record('user', ['id' => $userid])) {
throw new \moodle_exception('invaliduserid');
}
$personalcontext = context_user::instance($user->id);
$PAGE->set_context($personalcontext);
$PAGE->set_pagelayout('admin');
// Display page header.
$title = get_string('notifications', 'message');
$PAGE->set_title($title);
$PAGE->set_heading(fullname($user));
// Grab the renderer.
$renderer = $PAGE->get_renderer('core', 'message');
$context = [
'notificationid' => $notificationid,
'userid' => $userid,
'limit' => $limit,
'offset' => $offset,
];
echo $OUTPUT->header();
echo $OUTPUT->heading(get_string('notifications', 'message'));
// Display a message if the notifications have not been migrated yet.
if (!get_user_preferences('core_message_migrate_data', false, $userid)) {
$notify = new \core\output\notification(get_string('notificationdatahasnotbeenmigrated', 'message'),
\core\output\notification::NOTIFY_WARNING);
echo $OUTPUT->render($notify);
}
echo $renderer->render_from_template('message_popup/notification_area', $context);
echo $OUTPUT->footer();
@@ -0,0 +1,75 @@
{{!
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 message_popup/message_content_item
This template will render the message content item for the
navigation bar message menu.
Classes required for JS:
* none
Data attributes required for JS:
* All data attributes are required
Context variables required for this template:
* isread If the message is read or not
* contexturl The link to the message on the messages page
* fullname The name of the sender
* profileimageurl The URL for the sender's profile image
* sentfromcurrentuser Was the last message sent by the current user
* lastmessage The message text
* unreadcount The number of unread messages in this conversation
Example context (json):
{
"isread": true,
"contexturl": "http://www.moodle.com",
"fullname": "Some Person",
"profileimageurl": "http://www.moodle.com",
"sentfromcurrentuser": false,
"lastmessage": "Hello, this is Some Person!",
"unreadcount": 1
}
}}
<a class="content-item-container {{^isread}}unread{{/isread}}"
data-region="message-content-item-container"
role="listitem"
href="{{{contexturl}}}"
{{^isread}}aria-label="{{#str}} viewunreadmessageswith, message, {{fullname}} {{/str}}"{{/isread}}
{{#isread}}aria-label="{{#str}} viewmessageswith, message, {{fullname}} {{/str}}"{{/isread}}
tabindex="0">
<div class="content-item">
<div class="profile-image-container">
<img src="{{{profileimageurl}}}" />
</div>
<div class="content-item-body">
<h3>{{fullname}}</h3>
<p>
{{#sentfromcurrentuser}}
<span data-region="last-message-user">{{#str}}you, message{{/str}}</span>
{{/sentfromcurrentuser}}
{{lastmessage}}
</p>
</div>
<div class="unread-count-container">
<span data-region="unread-count" class="badge bg-danger text-white">{{unreadcount}}</span>
</div>
</div>
</a>
@@ -0,0 +1,81 @@
{{!
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 message_popup/notification_area
This template will render the notification area on the notifications
page.
Classes required for JS:
* none
Data attributes required for JS:
* All data attributes are required
Context variables required for this template:
* userid The logged in user id
* offset The offset amound of the notification list
* limit The limit amount for the notification list
* notificationid The specific notification to select
Example context (json):
{
"userid":3
}
}}
<div class="notification-area" data-region="notification-area" data-user-id="{{userid}}">
<div class="control-area" data-region="control-area">
<div class="header"></div>
<div class="content" data-region="content"></div>
<div class="empty-text">{{#str}} nonotifications, message {{/str}}</div>
{{> core/loading }}
</div>
<div class="content-area" data-region="content-area">
<div class="toggle-mode">
<button class="btn btn-link" data-action="toggle-mode">{{#str}} back {{/str}}</button>
</div>
<div class="header" data-region="header"></div>
<div class="content" data-region="content"></div>
<div class="empty-text">{{#str}} selectnotificationtoview, message {{/str}}</div>
<div class="footer" data-region="footer"></div>
</div>
</div>
{{#js}}
require(['jquery', 'message_popup/notification_area_control_area', 'message_popup/notification_area_content_area'],
function($, ControlArea, ContentArea) {
var userId = {{userid}};
var controlArea = new ControlArea($('[data-region="control-area"]'), userId);
{{#offset}}
controlArea.setOffset({{.}});
{{/offset}}
{{#limit}}
controlArea.setLimit({{.}});
{{/limit}}
{{#notificationid}}
controlArea.loadMoreNotifications().done(function() {
controlArea.showNotification({{.}});
});
{{/notificationid}}
{{^notificationid}}
controlArea.loadMoreNotifications();
{{/notificationid}}
new ContentArea($('[data-region="content-area"]'), userId);
});
{{/js}}
@@ -0,0 +1,39 @@
{{!
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 message_popup/notification_area_content_area_content
This template will render the content for the content area in the
notification area on the notifications page.
Classes required for JS:
* none
Data attributes required for JS:
* none
Context variables required for this template:
* fullmessagehtml The notification in HTML format
* fullmessage The notification in plain text (if no HTML)
Example context (json):
{
}
}}
{{#fullmessagehtml}}{{{.}}}{{/fullmessagehtml}}
{{^fullmessagehtml}}<pre>{{fullmessage}}</pre>{{/fullmessagehtml}}
@@ -0,0 +1,38 @@
{{!
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 message_popup/notification_area_content_area_footer
This template will render the footer for the content area in the
notification area on the notifications page.
Classes required for JS:
* none
Data attributes required for JS:
* none
Context variables required for this template:
* contexturl The URL for the associated resource
* contexturlname Human readable name for the URL
Example context (json):
{
}
}}
<a href="{{{contexturl}}}">{{#str}} viewnotificationresource, message, {{contexturlname}}{{/str}}</a>
@@ -0,0 +1,41 @@
{{!
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 message_popup/notification_area_content_area_header
This template will render the header for the content area in the
notification area on the notifications page.
Classes required for JS:
* none
Data attributes required for JS:
* none
Context variables required for this template:
* iconurl The URL for the notification icon
* subject The notification subject
* timecreatedpretty Pretty format when the notification was created
Example context (json):
{
}
}}
<div class="image-container">{{#pix}} e/text_highlight, core, {{#str}} notificationimage, message {{/str}} {{/pix}}</div>
<div class="subject-container">{{subject}}</div>
<div class="timestamp">{{timecreatedpretty}}</div>
@@ -0,0 +1,85 @@
{{!
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 message_popup/notification_content_item
This template will render the notification content item for the
navigation bar notification menu.
Classes required for JS:
* none
Data attributes required for JS:
* All data attributes are required
Context variables required for this template:
* contexturl A link to the notification resource
* read If the notification is read or not
* subject The subject text
* id Notification id
* iconurl The URL for the notification icon
* shortenedsubject A shortened version of subject text
* timecreatedpretty Pretty formatted time stamp
* viewmoreurl The link to the full notification
Example context (json):
{
"contexturl": "http://www.moodle.com",
"read": true,
"subject": "You have a notification",
"id": 1,
"iconurl": "http://www.moodle.com",
"shortenedsubject": "You have a...",
"timecreatedpretty": "5 minutes ago",
"viewmoreurl": "http://www.moodle.com"
}
}}
<div class="content-item-container notification {{^read}}unread{{/read}}"
data-region="notification-content-item-container"
data-id="{{id}}"
role="listitem">
{{#contexturl}}
<a class="context-link" href="{{{.}}}" data-action="content-item-link"
{{/contexturl}}
{{^contexturl}}
<div tabindex="0"
{{/contexturl}}
{{#read}}aria-label="{{subject}}"{{/read}}
{{^read}}aria-label="{{#str}} unreadnotification, message, {{subject}} {{/str}}"{{/read}}>
<div class="content-item-body">
<div class="notification-image">
{{#pix}} e/text_highlight, core, {{#str}} notificationimage, message {{/str}} {{/pix}}
</div>
<div class="notification-message">{{shortenedsubject}}</div>
</div>
<div class="content-item-footer">
<div class="timestamp">{{timecreatedpretty}}</div>
</div>
{{#viewmoreurl}}
<a href="{{{.}}}" class="view-more" data-action="view-more">{{#str}} viewfullnotification, message {{/str}}</a>
{{/viewmoreurl}}
{{#contexturl}}
</a>
{{/contexturl}}
{{^contexturl}}
</div>
{{/contexturl}}
</div>
@@ -0,0 +1,98 @@
{{!
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 message_popup/notification_popover
This template will render the notification popover for the navigation bar.
Classes required for JS:
* none
Data attributes required for JS:
* All data attributes are required
Context variables required for this template:
* userid the logged in user id
* urls The URLs for the popover
Example context (json):
{
"userid": 3,
"urls": {
"preferences": "http://www.moodle.com"
}
}
}}
{{< core/popover_region }}
{{$classes}}popover-region-notifications{{/classes}}
{{$attributes}}id="nav-notification-popover-container" data-userid="{{userid}}"{{/attributes}}
{{$togglelabel}}{{!
}}{{^unreadcount}} {{#str}} shownotificationwindownonew, message {{/str}} {{/unreadcount}} {{!
}}{{#unreadcount}} {{#str}} shownotificationwindowwithcount, message, {{.}} {{/str}} {{/unreadcount}} {{!
}}{{/togglelabel}}
{{$togglecontent}}
{{#pix}} i/notifications, core, {{#str}} togglenotificationmenu, message {{/str}} {{/pix}}
<div
class="count-container {{^unreadcount}}hidden{{/unreadcount}}"
data-region="count-container"
aria-hidden=true
>
{{unreadcount}}
</div>
{{/togglecontent}}
{{$containerlabel}}{{#str}} notificationwindow, message {{/str}}{{/containerlabel}}
{{$headertext}}{{#str}} notifications, message {{/str}}{{/headertext}}
{{$headeractions}}
<a class="mark-all-read-button"
href="#"
title="{{#str}} markallread {{/str}}"
data-action="mark-all-read"
role="button"
aria-label="{{#str}} markallread {{/str}}">
<span class="normal-icon">{{#pix}} t/markasread, core {{/pix}}</span>
{{> core/loading }}
</a>
{{# urls.preferences }}
<a href="{{{ . }}}"
title="{{#str}} notificationpreferences, message {{/str}}"
aria-label="{{#str}} notificationpreferences, message {{/str}}">
{{#pix}} i/settings, core {{/pix}}</a>
{{/ urls.preferences }}
{{/headeractions}}
{{$content}}
<div class="all-notifications"
data-region="all-notifications"
role="log"
aria-busy="false"
aria-atomic="false"
aria-relevant="additions"></div>
<div class="empty-message" tabindex="0" data-region="empty-message">{{#str}} nonotifications, message {{/str}}</div>
{{/content}}
{{/ core/popover_region }}
{{#js}}
require(['jquery', 'message_popup/notification_popover_controller'], function($, Controller) {
var container = $('#nav-notification-popover-container');
var controller = new Controller(container);
controller.registerEventListeners();
controller.registerListNavigationEventListeners();
});
{{/js}}
+126
View File
@@ -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 message_popup;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/message/tests/messagelib_test.php');
require_once($CFG->dirroot . '/message/output/popup/tests/base.php');
/**
* Test message popup API.
*
* @package message_popup
* @category test
* @copyright 2016 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class api_test extends \advanced_testcase {
use \message_popup_test_helper;
/** @var \phpunit_message_sink message redirection. */
public $messagesink;
/**
* Test set up.
*
* This is executed before running any test in this file.
*/
public function setUp(): void {
$this->preventResetByRollback(); // Messaging is not compatible with transactions.
$this->messagesink = $this->redirectMessages();
$this->resetAfterTest();
}
/**
* Test that the get_popup_notifications function will return the correct notifications.
*/
public function test_message_get_popup_notifications(): void {
$sender = $this->getDataGenerator()->create_user(array('firstname' => 'Test1', 'lastname' => 'User1'));
$recipient = $this->getDataGenerator()->create_user(array('firstname' => 'Test2', 'lastname' => 'User2'));
$this->send_fake_read_popup_notification($sender, $recipient, 'Message 1', 1);
$this->send_fake_unread_popup_notification($sender, $recipient, 'Message 2', 2);
$this->send_fake_read_popup_notification($sender, $recipient, 'Message 3', 3, 1);
$this->send_fake_read_popup_notification($sender, $recipient, 'Message 4', 3, 2);
$this->send_fake_unread_popup_notification($sender, $recipient, 'Message 5', 4);
$notifications = \message_popup\api::get_popup_notifications($recipient->id);
$this->assertEquals($notifications[0]->fullmessage, 'Message 5');
$this->assertEquals($notifications[1]->fullmessage, 'Message 4');
$this->assertEquals($notifications[2]->fullmessage, 'Message 3');
$this->assertEquals($notifications[3]->fullmessage, 'Message 2');
$this->assertEquals($notifications[4]->fullmessage, 'Message 1');
}
/**
* Test that the get_popup_notifications function works correctly with limiting and offsetting
* the result set if requested.
*/
public function test_message_get_popup_notifications_all_limit_and_offset(): void {
$sender = $this->getDataGenerator()->create_user(array('firstname' => 'Test1', 'lastname' => 'User1'));
$recipient = $this->getDataGenerator()->create_user(array('firstname' => 'Test2', 'lastname' => 'User2'));
$this->send_fake_read_popup_notification($sender, $recipient, 'Message 1', 1);
$this->send_fake_unread_popup_notification($sender, $recipient, 'Message 2', 2);
$this->send_fake_read_popup_notification($sender, $recipient, 'Message 3', 3, 1);
$this->send_fake_read_popup_notification($sender, $recipient, 'Message 4', 3, 2);
$this->send_fake_unread_popup_notification($sender, $recipient, 'Message 5', 4);
$this->send_fake_unread_popup_notification($sender, $recipient, 'Message 6', 5);
$notifications = \message_popup\api::get_popup_notifications($recipient->id, 'DESC', 2, 0);
$this->assertEquals($notifications[0]->fullmessage, 'Message 6');
$this->assertEquals($notifications[1]->fullmessage, 'Message 5');
$notifications = \message_popup\api::get_popup_notifications($recipient->id, 'DESC', 2, 2);
$this->assertEquals($notifications[0]->fullmessage, 'Message 4');
$this->assertEquals($notifications[1]->fullmessage, 'Message 3');
$notifications = \message_popup\api::get_popup_notifications($recipient->id, 'DESC', 0, 3);
$this->assertEquals($notifications[0]->fullmessage, 'Message 3');
$this->assertEquals($notifications[1]->fullmessage, 'Message 2');
$this->assertEquals($notifications[2]->fullmessage, 'Message 1');
}
/**
* Test count_unread_popup_notifications.
*/
public function test_message_count_unread_popup_notifications(): void {
$sender1 = $this->getDataGenerator()->create_user(array('firstname' => 'Test1', 'lastname' => 'User1'));
$sender2 = $this->getDataGenerator()->create_user(array('firstname' => 'Test2', 'lastname' => 'User2'));
$recipient1 = $this->getDataGenerator()->create_user(array('firstname' => 'Test3', 'lastname' => 'User3'));
$recipient2 = $this->getDataGenerator()->create_user(array('firstname' => 'Test4', 'lastname' => 'User4'));
$this->send_fake_unread_popup_notification($sender1, $recipient1);
$this->send_fake_unread_popup_notification($sender1, $recipient1);
$this->send_fake_unread_popup_notification($sender2, $recipient1);
$this->send_fake_unread_popup_notification($sender1, $recipient2);
$this->send_fake_unread_popup_notification($sender2, $recipient2);
$this->send_fake_unread_popup_notification($sender2, $recipient2);
$this->send_fake_unread_popup_notification($sender2, $recipient2);
$this->send_fake_unread_popup_notification($sender2, $recipient2);
$this->assertEquals(\message_popup\api::count_unread_popup_notifications($recipient1->id), 3);
$this->assertEquals(\message_popup\api::count_unread_popup_notifications($recipient2->id), 5);
}
}
+84
View File
@@ -0,0 +1,84 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Base trait for message popup tests.
*
* @package message_popup
* @copyright 2016 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
use \core_message\tests\helper as testhelper;
trait message_popup_test_helper {
/**
* Send a fake unread popup notification.
*
* {@link message_send()} does not support transaction, this function will simulate a message
* sent from a user to another. We should stop using it once {@link message_send()} will support
* transactions. This is not clean at all, this is just used to add rows to the table.
*
* @param stdClass $userfrom user object of the one sending the message.
* @param stdClass $userto user object of the one receiving the message.
* @param string $message message to send.
* @param int $timecreated time the message was created.
* @return int the id of the message
*/
protected function send_fake_unread_popup_notification(\stdClass $userfrom, \stdClass $userto,
string $message = 'Hello world!', int $timecreated = 0): int {
global $DB;
$id = testhelper::send_fake_unread_notification($userfrom, $userto, $message, $timecreated);
$popup = new stdClass();
$popup->notificationid = $id;
$DB->insert_record('message_popup_notifications', $popup);
return $id;
}
/**
* Send a fake read popup notification.
*
* {@link message_send()} does not support transaction, this function will simulate a message
* sent from a user to another. We should stop using it once {@link message_send()} will support
* transactions. This is not clean at all, this is just used to add rows to the table.
*
* @param stdClass $userfrom user object of the one sending the message.
* @param stdClass $userto user object of the one receiving the message.
* @param string $message message to send.
* @param int $timecreated time the message was created.
* @param int $timeread the the message was read
* @return int the id of the message
*/
protected function send_fake_read_popup_notification(\stdClass $userfrom, \stdClass $userto, string $message = 'Hello world!',
int $timecreated = 0, int $timeread = 0): int {
global $DB;
$id = testhelper::send_fake_read_notification($userfrom, $userto, $message, $timecreated, $timeread);
$popup = new stdClass();
$popup->notificationid = $id;
$DB->insert_record('message_popup_notifications', $popup);
return $id;
}
}
@@ -0,0 +1,66 @@
<?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/>.
/**
* Behat message popup related steps definitions.
*
* @package message_popup
* @category test
* @copyright 2016 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
// NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
require_once(__DIR__ . '/../../../../../lib/behat/behat_base.php');
/**
* Message popup steps definitions.
*
* @package message_popup
* @category test
* @copyright 2016 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class behat_message_popup extends behat_base {
/**
* Open the notification popover in the nav bar.
*
* @Given /^I open the notification popover$/
*/
public function i_open_the_notification_popover() {
$this->execute('behat_general::i_click_on',
array("#nav-notification-popover-container [data-region='popover-region-toggle']", 'css_element'));
$node = $this->get_selected_node('css_element',
'#nav-notification-popover-container [data-region="popover-region-content"]');
$this->ensure_node_is_visible($node);
}
/**
* Open the message popover in the nav bar.
*
* @Given /^I open the message popover$/
*/
public function i_open_the_message_popover() {
$this->execute('behat_general::i_click_on',
array("#nav-message-popover-container [data-region='popover-region-toggle']", 'css_element'));
$node = $this->get_selected_node('css_element', '#nav-message-popover-container [data-region="popover-region-content"]');
$this->ensure_node_is_visible($node);
}
}
@@ -0,0 +1,17 @@
@core_message @message_popup
Feature: Notification popover preferences
In order to modify my notification preferences
As a user
I can navigate to the preferences page from the popover
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| user1 | User | 1 | user1@example.com |
@javascript
Scenario: User navigates to preferences page
Given I log in as "user1"
And I open the notification popover
When I follow "Notification preferences"
Then I should see "Notification preferences"
@@ -0,0 +1,47 @@
@core_message @message_popup @javascript
Feature: Notification popover unread notifications
In order to be kept informed
As a user
I am notified about relevant events in Moodle
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| student1 | Student | 1 | student1@example.com |
| student2 | Student | 2 | student2@example.com |
# This should generate some notifications
And the following "notifications" exist:
| subject | userfrom | userto | timecreated | timeread |
| Test 01 | student2 | student1 | 1654587996 | null |
| Test 02 | student2 | student1 | 1654587997 | null |
Scenario: Notification popover shows correct unread count
Given I log in as "student1"
# Confirm the popover is saying 1 unread notifications.
And I should see "2" in the "#nav-notification-popover-container [data-region='count-container']" "css_element"
# Open the popover.
When I open the notification popover
# Confirm the notifications are visible.
Then I should see "Test 01" in the "#nav-notification-popover-container" "css_element"
And I should see "Test 02" in the "#nav-notification-popover-container" "css_element"
@_bug_phantomjs
Scenario: Clicking a notification marks it as read
Given I log in as "student1"
# Open the notifications.
When I open the notification popover
And I follow "Test 01"
And I open the notification popover
And I follow "Test 02"
# Confirm the count element is hidden (i.e. there are no unread notifications).
Then "[data-region='count-container']" "css_element" in the "#nav-notification-popover-container" "css_element" should not be visible
Scenario: Mark all notifications as read
Given I log in as "student1"
When I open the notification popover
And I click on "Mark all as read" "link" in the "#nav-notification-popover-container" "css_element"
# Refresh the page to make sure we send a new request for the unread count.
And I reload the page
# Confirm the count element is hidden (i.e. there are no unread notifications).
Then "[data-region='count-container']" "css_element" in the "#nav-notification-popover-container" "css_element" should not be visible
@@ -0,0 +1,198 @@
<?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 message_popup;
use message_popup_external;
use message_popup_test_helper;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/webservice/tests/helpers.php');
require_once($CFG->dirroot . '/message/output/popup/externallib.php');
require_once($CFG->dirroot . '/message/output/popup/tests/base.php');
/**
* Class for external message popup functions unit tests.
*
* @package message_popup
* @copyright 2016 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class externallib_test extends \advanced_testcase {
use message_popup_test_helper;
/** @var \phpunit_message_sink message redirection. */
public $messagesink;
/**
* Test set up.
*
* This is executed before running any test in this file.
*/
public function setUp(): void {
$this->preventResetByRollback(); // Messaging is not compatible with transactions.
$this->messagesink = $this->redirectMessages();
$this->resetAfterTest();
}
/**
* Test that get_popup_notifications throws an exception if the user
* doesn't exist.
*/
public function test_get_popup_notifications_no_user_exception(): void {
$this->resetAfterTest(true);
$this->expectException('moodle_exception');
$result = message_popup_external::get_popup_notifications(-2132131, false, 0, 0);
}
/**
* get_popup_notifications should throw exception if user isn't logged in
* user.
*/
public function test_get_popup_notifications_access_denied_exception(): void {
$this->resetAfterTest(true);
$sender = $this->getDataGenerator()->create_user();
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$this->expectException('moodle_exception');
$result = message_popup_external::get_popup_notifications($sender->id, false, 0, 0);
}
/**
* get_popup_notifications should return notifications for the recipient.
*/
public function test_get_popup_notifications_as_recipient(): void {
$this->resetAfterTest(true);
$sender = $this->getDataGenerator()->create_user(array('firstname' => 'Sendy', 'lastname' => 'Sender'));
$recipient = $this->getDataGenerator()->create_user(array('firstname' => 'Recipy', 'lastname' => 'Recipient'));
$notificationids = array(
$this->send_fake_unread_popup_notification($sender, $recipient),
$this->send_fake_unread_popup_notification($sender, $recipient),
$this->send_fake_read_popup_notification($sender, $recipient),
$this->send_fake_read_popup_notification($sender, $recipient),
);
// Confirm that admin has super powers to retrieve any notifications.
$this->setAdminUser();
$result = message_popup_external::get_popup_notifications($recipient->id, false, 0, 0);
$this->assertCount(4, $result['notifications']);
// Check we receive custom data as a unserialisable json.
$found = 0;
foreach ($result['notifications'] as $notification) {
if (!empty($notification->customdata)) {
$this->assertObjectHasProperty('datakey', json_decode($notification->customdata));
$found++;
}
}
$this->assertEquals(2, $found);
$this->setUser($recipient);
$result = message_popup_external::get_popup_notifications($recipient->id, false, 0, 0);
$this->assertCount(4, $result['notifications']);
}
/**
* get_popup_notifications result set should work with limit and offset.
*/
public function test_get_popup_notification_limit_offset(): void {
$this->resetAfterTest(true);
$sender = $this->getDataGenerator()->create_user(array('firstname' => 'Sendy', 'lastname' => 'Sender'));
$recipient = $this->getDataGenerator()->create_user(array('firstname' => 'Recipy', 'lastname' => 'Recipient'));
$this->setUser($recipient);
$notificationids = array(
$this->send_fake_unread_popup_notification($sender, $recipient, 'Notification 1', 1),
$this->send_fake_unread_popup_notification($sender, $recipient, 'Notification 2', 2),
$this->send_fake_unread_popup_notification($sender, $recipient, 'Notification 3', 3),
$this->send_fake_unread_popup_notification($sender, $recipient, 'Notification 4', 4),
$this->send_fake_read_popup_notification($sender, $recipient, 'Notification 5', 5),
$this->send_fake_read_popup_notification($sender, $recipient, 'Notification 6', 6),
$this->send_fake_read_popup_notification($sender, $recipient, 'Notification 7', 7),
$this->send_fake_read_popup_notification($sender, $recipient, 'Notification 8', 8),
);
$result = message_popup_external::get_popup_notifications($recipient->id, true, 2, 0);
$this->assertEquals($result['notifications'][0]->id, $notificationids[7]);
$this->assertEquals($result['notifications'][1]->id, $notificationids[6]);
$result = message_popup_external::get_popup_notifications($recipient->id, true, 2, 2);
$this->assertEquals($result['notifications'][0]->id, $notificationids[5]);
$this->assertEquals($result['notifications'][1]->id, $notificationids[4]);
}
/**
* get_unread_popup_notification should throw an exception for an invalid user.
*/
public function test_get_unread_popup_notification_count_invalid_user_exception(): void {
$this->resetAfterTest(true);
$this->expectException('moodle_exception');
$result = message_popup_external::get_unread_popup_notification_count(-2132131, 0);
}
/**
* get_unread_popup_notification_count should throw exception if being requested for
* non-logged in user.
*/
public function test_get_unread_popup_notification_count_access_denied_exception(): void {
$this->resetAfterTest(true);
$sender = $this->getDataGenerator()->create_user();
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$this->expectException('moodle_exception');
$result = message_popup_external::get_unread_popup_notification_count($sender->id, 0);
}
/**
* Test get_unread_popup_notification_count.
*/
public function test_get_unread_popup_notification_count(): void {
$this->resetAfterTest(true);
$sender1 = $this->getDataGenerator()->create_user();
$sender2 = $this->getDataGenerator()->create_user();
$sender3 = $this->getDataGenerator()->create_user();
$recipient = $this->getDataGenerator()->create_user();
$this->setUser($recipient);
$notificationids = array(
$this->send_fake_unread_popup_notification($sender1, $recipient, 'Notification', 1),
$this->send_fake_unread_popup_notification($sender1, $recipient, 'Notification', 2),
$this->send_fake_unread_popup_notification($sender2, $recipient, 'Notification', 3),
$this->send_fake_unread_popup_notification($sender2, $recipient, 'Notification', 4),
$this->send_fake_unread_popup_notification($sender3, $recipient, 'Notification', 5),
$this->send_fake_unread_popup_notification($sender3, $recipient, 'Notification', 6),
);
$count = message_popup_external::get_unread_popup_notification_count($recipient->id);
$this->assertEquals($count, 6);
}
}
@@ -0,0 +1,101 @@
<?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 message_popup;
use core\task\messaging_cleanup_task;
use message_popup_test_helper;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/message/output/popup/tests/base.php');
/**
* Test class
*
* @package message_popup
* @category test
* @copyright 2020 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class messaging_cleanup_test extends \advanced_testcase {
// Helper trait for sending fake popup notifications.
use message_popup_test_helper;
/**
* Test that all popup notifications are cleaned up
*
* @return void
*/
public function test_cleanup_all_notifications(): void {
global $DB;
$this->resetAfterTest();
$userfrom = $this->getDataGenerator()->create_user();
$userto = $this->getDataGenerator()->create_user();
$now = time();
$this->send_fake_unread_popup_notification($userfrom, $userto, 'Message 1', $now - 10);
$notificationid = $this->send_fake_unread_popup_notification($userfrom, $userto, 'Message 2', $now);
// Sanity check.
$this->assertEquals(2, $DB->count_records('message_popup_notifications'));
// Delete all notifications >5 seconds old.
set_config('messagingdeleteallnotificationsdelay', 5);
(new messaging_cleanup_task())->execute();
// We should have just one record now, matching the second notification we sent.
$records = $DB->get_records('message_popup_notifications');
$this->assertCount(1, $records);
$this->assertEquals($notificationid, reset($records)->notificationid);
}
/**
* Test that read popup notifications are cleaned up
*
* @return void
*/
public function test_cleanup_read_notifications(): void {
global $DB;
$this->resetAfterTest();
$userfrom = $this->getDataGenerator()->create_user();
$userto = $this->getDataGenerator()->create_user();
$now = time();
$this->send_fake_read_popup_notification($userfrom, $userto, 'Message 1', $now - 20, $now - 10);
$notificationid = $this->send_fake_read_popup_notification($userfrom, $userto, 'Message 2', $now - 15, $now);
// Sanity check.
$this->assertEquals(2, $DB->count_records('message_popup_notifications'));
// Delete read notifications >5 seconds old.
set_config('messagingdeletereadnotificationsdelay', 5);
(new messaging_cleanup_task())->execute();
// We should have just one record now, matching the second notification we sent.
$records = $DB->get_records('message_popup_notifications');
$this->assertCount(1, $records);
$this->assertEquals($notificationid, reset($records)->notificationid);
}
}
+29
View File
@@ -0,0 +1,29 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Popup message processor version information
*
* @package message_popup
* @copyright 2008 Luis Rodrigues
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2024042200; // The current plugin version (Date: YYYYMMDDXX).
$plugin->requires = 2024041600; // Requires this Moodle version.
$plugin->component = 'message_popup'; // Full name of the plugin (used for diagnostics)