MDL-30811 output: Add support for session notifications

This commit is contained in:
Andrew Nicols 2016-02-16 08:48:39 +08:00
parent 243468030a
commit 0346323cec
16 changed files with 622 additions and 55 deletions

View File

@ -1 +1 @@
define(["core/yui"],function(a){return{alert:function(b,c,d){a.use("moodle-core-notification-alert",function(){var a=new M.core.alert({title:b,message:c,yesLabel:d});a.show()})},confirm:function(b,c,d,e,f){a.use("moodle-core-notification-confirm",function(){var a=new M.core.confirm({title:b,question:c,yesLabel:d,noLabel:e});a.on("complete-yes",function(){f()}),a.show()})},exception:function(b){b.backtrace&&(b.lineNumber=b.backtrace[0].line,b.fileName=b.backtrace[0].file,b.fileName="..."+b.fileName.substr(b.fileName.length-20),b.stack=b.debuginfo,b.name=b.errorcode),a.use("moodle-core-notification-exception",function(){var a=new M.core.exception(b);a.show()})}}});
define(["core/yui","jquery","theme_bootstrapbase/bootstrap","core/templates","core/ajax","core/log"],function(a,b,c,d,e,f){var g={types:{success:"core/notification_success",info:"core/notification_info",warning:"core/notification_warning",error:"core/notification_error"},fieldName:"user-notifications",fetchNotifications:function(){var a=e.call([{methodname:"core_fetch_notifications",args:{contextid:g.contextid}}]);a[0].done(g.addNotifications)},addNotifications:function(a){a||(a=[]),b.each(a,function(a,b){g.renderNotification(b.template,b.variables)})},setupTargetRegion:function(){var a=b("#"+g.fieldName);if(!a.length){var c=b("<span>").attr("id",g.fieldName);return a=b("#region-main"),a.length?a.prepend(c):(a=b('[role="main"]'),a.length?a.prepend(c):(a=b("body"),a.prepend(c)))}},addNotification:function(a){var c=g.types.error;return a=b.extend({closebutton:!0,announce:!0,type:"error"},a),a.template?(c=a.template,delete a.template):a.type&&("undefined"!=typeof g.types[a.type]&&(c=g.types[a.type]),delete a.type),g.renderNotification(c,a)},renderNotification:function(a,c){return"undefined"!=typeof c.message&&c.message?void d.render(a,c).done(function(a){b("#"+g.fieldName).prepend(a)}).fail(g.exception):void f.debug("Notification received without content. Skipping.")},alert:function(b,c,d){a.use("moodle-core-notification-alert",function(){var a=new M.core.alert({title:b,message:c,yesLabel:d});a.show()})},confirm:function(b,c,d,e,f){a.use("moodle-core-notification-confirm",function(){var a=new M.core.confirm({title:b,question:c,yesLabel:d,noLabel:e});a.on("complete-yes",function(){f()}),a.show()})},exception:function(b){b.backtrace&&(b.lineNumber=b.backtrace[0].line,b.fileName=b.backtrace[0].file,b.fileName="..."+b.fileName.substr(b.fileName.length-20),b.stack=b.debuginfo,b.name=b.errorcode),a.use("moodle-core-notification-exception",function(){var a=new M.core.exception(b);a.show()})}};return{init:function(a,c){g.contextid=a,g.setupTargetRegion(),b().alert(),g.addNotifications(c),g.fetchNotifications()},fetchNotifications:g.fetchNotifications,addNotification:g.addNotification,alert:g.alert,confirm:g.confirm,exception:g.exception}});

View File

@ -14,6 +14,8 @@
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* A system for displaying notifications to users from the session.
*
* Wrapper for the YUI M.core.notification class. Allows us to
* use the YUI version in AMD code until it is replaced.
*
@ -24,20 +26,99 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 2.9
*/
define(['core/yui'], function(Y) {
define(['core/yui', 'jquery', 'theme_bootstrapbase/bootstrap', 'core/templates', 'core/ajax', 'core/log'],
function(Y, $, bootstrap, templates, ajax, log) {
var notificationModule = {
types: {
'success': 'core/notification_success',
'info': 'core/notification_info',
'warning': 'core/notification_warning',
'error': 'core/notification_error',
},
// Private variables and functions.
fieldName: 'user-notifications',
fetchNotifications: function() {
var promises = ajax.call([{
methodname: 'core_fetch_notifications',
args: {
contextid: notificationModule.contextid
}
}]);
promises[0]
.done(notificationModule.addNotifications)
;
},
addNotifications: function(notifications) {
if (!notifications) {
notifications = [];
}
$.each(notifications, function(i, notification) {
notificationModule.renderNotification(notification.template, notification.variables);
});
},
setupTargetRegion: function() {
var targetRegion = $('#' + notificationModule.fieldName);
if (targetRegion.length) {
return;
}
var newRegion = $('<span>').attr('id', notificationModule.fieldName);
targetRegion = $('#region-main');
if (targetRegion.length) {
return targetRegion.prepend(newRegion);
}
targetRegion = $('[role="main"]');
if (targetRegion.length) {
return targetRegion.prepend(newRegion);
}
targetRegion = $('body');
return targetRegion.prepend(newRegion);
},
addNotification: function(notification) {
var template = notificationModule.types.error;
notification = $.extend({
closebutton: true,
announce: true,
type: 'error'
}, notification);
if (notification.template) {
template = notification.template;
delete notification.template;
} else if (notification.type){
if (typeof notificationModule.types[notification.type] !== 'undefined') {
template = notificationModule.types[notification.type];
}
delete notification.type;
}
return notificationModule.renderNotification(template, notification);
},
renderNotification: function(template, variables) {
if (typeof variables.message === 'undefined' || !variables.message) {
log.debug('Notification received without content. Skipping.');
return;
}
templates.render(template, variables)
.done(function(html) {
$('#' + notificationModule.fieldName).prepend(html);
})
.fail(notificationModule.exception)
;
},
return /** @alias module:core/notification */ {
// Public variables and functions.
/**
* Wrap M.core.alert.
*
* @method alert
* @param {string} title
* @param {string} message
* @param {string} yesLabel
*/
alert: function(title, message, yesLabel) {
// Here we are wrapping YUI. This allows us to start transitioning, but
// wait for a good alternative without having inconsistent dialogues.
@ -52,16 +133,6 @@ define(['core/yui'], function(Y) {
});
},
/**
* Wrap M.core.confirm.
*
* @method confirm
* @param {string} title
* @param {string} question
* @param {string} yesLabel
* @param {string} noLabel
* @param {function} callback
*/
confirm: function(title, question, yesLabel, noLabel, callback) {
// Here we are wrapping YUI. This allows us to start transitioning, but
// wait for a good alternative without having inconsistent dialogues.
@ -80,12 +151,6 @@ define(['core/yui'], function(Y) {
});
},
/**
* Wrap M.core.exception.
*
* @method exception
* @param {Error} ex
*/
exception: function(ex) {
// Fudge some parameters.
if (ex.backtrace) {
@ -102,4 +167,73 @@ define(['core/yui'], function(Y) {
});
}
};
return /** @alias module:core/notification */{
init: function(contextid, notifications) {
notificationModule.contextid = contextid;
// Setup the message target region if it isn't setup already
notificationModule.setupTargetRegion();
// Setup closing of bootstrap alerts.
$().alert();
// Add provided notifications.
notificationModule.addNotifications(notifications);
// Poll for any new notifications.
notificationModule.fetchNotifications();
},
/**
* Poll the server for any new notifications.
*
* @method fetchNotifications
*/
fetchNotifications: notificationModule.fetchNotifications,
/**
* Add a notification to the page.
*
* Note: This does not cause the notification to be added to the session.
*
* @method addNotification
* @param {Object} notification The notification to add.
* @param {string} notification.message The body of the notification
* @param {string} notification.type The type of notification to add (error, warning, info, success).
* @param {Boolean} notification.closebutton Whether to show the close button.
* @param {Boolean} notification.announce Whether to announce to screen readers.
*/
addNotification: notificationModule.addNotification,
/**
* Wrap M.core.alert.
*
* @method alert
* @param {string} title
* @param {string} message
* @param {string} yesLabel
*/
alert: notificationModule.alert,
/**
* Wrap M.core.confirm.
*
* @method confirm
* @param {string} title
* @param {string} question
* @param {string} yesLabel
* @param {string} noLabel
* @param {function} callback
*/
confirm: notificationModule.confirm,
/**
* Wrap M.core.exception.
*
* @method exception
* @param {Error} ex
*/
exception: notificationModule.exception
};
});

View File

@ -0,0 +1,165 @@
<?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 core;
/**
* User Alert notifications.
*
* @package core
* @copyright 2016 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
class notification {
/**
* A notification of level 'success'.
*/
const SUCCESS = 'success';
/**
* A notification of level 'warning'.
*/
const WARNING = 'warning';
/**
* A notification of level 'info'.
*/
const INFO = 'info';
/**
* A notification of level 'error'.
*/
const ERROR = 'error';
/**
* Add a message to the session notification stack.
*
* @param string $message The message to add to the stack
* @param string $level The type of message to add to the stack
*/
public static function add($message, $level = null) {
global $PAGE, $SESSION;
if ($PAGE && $PAGE->state === \moodle_page::STATE_IN_BODY) {
// Currently in the page body - just render and exit immediately.
// We insert some code to immediately insert this into the user-notifications created by the header.
$id = uniqid();
echo \html_writer::span(
$PAGE->get_renderer('core')->render(new \core\output\notification($message, $level)),
'', array('id' => $id));
// Insert this JS here using a script directly rather than waiting for the page footer to load to avoid
// ensure that the message is added to the user-notifications section as soon as possible after it is created.
echo \html_writer::script(
"(function() {" .
"var notificationHolder = document.getElementById('user-notifications');" .
"if (!notificationHolder) { return; }" .
"var thisNotification = document.getElementById('{$id}');" .
"if (!thisNotification) { return; }" .
"notificationHolder.appendChild(thisNotification.firstChild);" .
"thisNotification.remove();" .
"})();"
);
return;
}
// Add the notification directly to the session.
// This will either be fetched in the header, or by JS in the footer.
$SESSION->notifications[] = (object) array(
'message' => $message,
'type' => $level,
);
}
/**
* Fetch all of the notifications in the stack and clear the stack.
*
* @return array All of the notifications in the stack
*/
public static function fetch() {
global $SESSION;
if (!isset($SESSION) || !isset($SESSION->notifications)) {
return [];
}
$notifications = $SESSION->notifications;
$SESSION->notifications = [];
$renderables = [];
foreach ($notifications as $notification) {
$renderable = new \core\output\notification($notification->message, $notification->type);
$renderables[] = $renderable;
}
return $renderables;
}
/**
* Fetch all of the notifications in the stack and clear the stack.
*
* @return array All of the notifications in the stack
*/
public static function fetch_as_array(\renderer_base $renderer) {
$notifications = [];
foreach (self::fetch() as $notification) {
$notifications[] = [
'template' => $notification->get_template_name(),
'variables' => $notification->export_for_template($renderer),
];
}
return $notifications;
}
/**
* Add a success message to the notification stack.
*
* @param string $message The message to add to the stack
*/
public static function success($message) {
return self::add($message, self::SUCCESS);
}
/**
* Add a info message to the notification stack.
*
* @param string $message The message to add to the stack
*/
public static function info($message) {
return self::add($message, self::INFO);
}
/**
* Add a warning message to the notification stack.
*
* @param string $message The message to add to the stack
*/
public static function warning($message) {
return self::add($message, self::WARNING);
}
/**
* Add a error message to the notification stack.
*
* @param string $message The message to add to the stack
*/
public static function error($message) {
return self::add($message, self::ERROR);
}
}

View File

@ -83,6 +83,16 @@ class notification implements \renderable, \templatable {
*/
protected $messagetype = self::NOTIFY_WARNING;
/**
* @var bool $announce Whether this notification should be announced assertively to screen readers.
*/
protected $announce = true;
/**
* @var bool $closebutton Whether this notification should inlcude a button to dismiss itself.
*/
protected $closebutton = true;
/**
* @var array $extraclasses A list of any extra classes that may be required.
*/
@ -111,6 +121,30 @@ class notification implements \renderable, \templatable {
}
}
/**
* Set whether this notification should be announced assertively to screen readers.
*
* @param bool $announce
* @return $this
*/
public function set_announce($announce = false) {
$this->announce = (bool) $announce;
return $this;
}
/**
* Set whether this notification should include a button to disiss itself.
*
* @param bool $button
* @return $this
*/
public function set_show_closebutton($button = false) {
$this->closebutton = (bool) $button;
return $this;
}
/**
* Add any extra classes that this notification requires.
*
@ -133,6 +167,8 @@ class notification implements \renderable, \templatable {
return array(
'message' => clean_text($this->message),
'extraclasses' => implode(' ', $this->extraclasses),
'announce' => $this->announce,
'closebutton' => $this->closebutton,
);
}

View File

@ -157,10 +157,18 @@ class manager {
public static function init_empty_session() {
global $CFG;
// Backup notifications. These should be preserved across session changes until the user fetches and clears them.
$notifications = [];
if (isset($GLOBALS['SESSION']->notifications)) {
$notifications = $GLOBALS['SESSION']->notifications;
}
$GLOBALS['SESSION'] = new \stdClass();
$GLOBALS['USER'] = new \stdClass();
$GLOBALS['USER']->id = 0;
// Restore notifications.
$GLOBALS['SESSION']->notifications = $notifications;
if (isset($CFG->mnet_localhost_id)) {
$GLOBALS['USER']->mnethostid = $CFG->mnet_localhost_id;
} else {

View File

@ -1067,7 +1067,17 @@ $functions = array(
'description' => 'Generic service to update title',
'type' => 'write',
'loginrequired' => true,
'ajax' => true
'ajax' => true,
),
'core_fetch_notifications' => array(
'classname' => 'core_external',
'methodname' => 'fetch_notifications',
'classpath' => 'lib/external/externallib.php',
'description' => 'Return a list of notifications for the current session',
'type' => 'read',
'loginrequired' => false,
'ajax' => true,
),
// === Calendar related functions ===

View File

@ -407,4 +407,58 @@ class core_external extends external_api {
)
);
}
/**
* Returns description of fetch_notifications() parameters.
*
* @return external_function_parameters
* @since Moodle 3.1
*/
public static function fetch_notifications_parameters() {
return new external_function_parameters(
array(
'contextid' => new external_value(PARAM_INT, 'Context ID', VALUE_REQUIRED),
));
}
/**
* Returns description of fetch_notifications() result value.
*
* @return external_description
* @since Moodle 3.1
*/
public static function fetch_notifications_returns() {
return new external_multiple_structure(
new external_single_structure(
array(
'template' => new external_value(PARAM_RAW, 'Name of the template'),
'variables' => new external_single_structure(array(
'message' => new external_value(PARAM_RAW, 'HTML content of the Notification'),
'extraclasses' => new external_value(PARAM_RAW, 'Extra classes to provide to the tmeplate'),
'announce' => new external_value(PARAM_RAW, 'Whether to announce'),
'closebutton' => new external_value(PARAM_RAW, 'Whether to close'),
)),
)
)
);
}
/**
* Returns the list of notifications against the current session.
*
* @return array
* @since Moodle 3.1
*/
public static function fetch_notifications($contextid) {
global $PAGE;
self::validate_parameters(self::fetch_notifications_parameters(), [
'contextid' => $contextid,
]);
$context = \context::instance_by_id($contextid);
$PAGE->set_context($context);
return \core\notification::fetch_as_array($PAGE->get_renderer('core'));
}
}

View File

@ -1032,7 +1032,7 @@ class core_renderer extends renderer_base {
* @return string HTML fragment
*/
public function footer() {
global $CFG, $DB;
global $CFG, $DB, $PAGE;
$output = $this->container_end_all(true);
@ -1057,6 +1057,7 @@ class core_renderer extends renderer_base {
}
$footer = str_replace($this->unique_performance_info_token, $performanceinfo, $footer);
$this->page->requires->js_call_amd('core/notification', 'init', array($PAGE->context->id, \core\notification::fetch_as_array($this)));
$footer = str_replace($this->unique_end_html_token, $this->page->requires->get_end_code(), $footer);
$this->page->set_state(moodle_page::STATE_DONE);
@ -1086,22 +1087,37 @@ class core_renderer extends renderer_base {
*/
public function course_content_header($onlyifnotcalledbefore = false) {
global $CFG;
if ($this->page->course->id == SITEID) {
// return immediately and do not include /course/lib.php if not necessary
return '';
}
static $functioncalled = false;
if ($functioncalled && $onlyifnotcalledbefore) {
// we have already output the content header
return '';
}
// Output any session notification.
$notifications = \core\notification::fetch();
$bodynotifications = '';
foreach ($notifications as $notification) {
$bodynotifications .= $this->render_from_template(
$notification->get_template_name(),
$notification->export_for_template($this)
);
}
$output = html_writer::span($bodynotifications, 'notifications', array('id' => 'user-notifications'));
if ($this->page->course->id == SITEID) {
// return immediately and do not include /course/lib.php if not necessary
return $output;
}
require_once($CFG->dirroot.'/course/lib.php');
$functioncalled = true;
$courseformat = course_get_format($this->page->course);
if (($obj = $courseformat->course_content_header()) !== null) {
return html_writer::div($courseformat->get_renderer($this->page)->render($obj), 'course-content-header');
$output .= html_writer::div($courseformat->get_renderer($this->page)->render($obj), 'course-content-header');
}
return '';
return $output;
}
/**
@ -2780,6 +2796,8 @@ EOD;
/**
* Output a notification (that is, a status message about something that has just happened).
*
* Note: \core\notification::add() may be more suitable for your usage.
*
* @param string $message The message to print out.
* @param string $type The type of notification. See constants on \core\output\notification.
* @return string the HTML to output.
@ -2848,7 +2866,7 @@ EOD;
*/
public function notify_problem($message) {
debugging(__FUNCTION__ . ' is deprecated.' .
'Please use notification() or \core\output\notification as required',
'Please use \core\notification::add, or \core\output\notification as required',
DEBUG_DEVELOPER);
$n = new \core\output\notification($message, \core\output\notification::NOTIFY_ERROR);
return $this->render($n);
@ -2865,7 +2883,7 @@ EOD;
*/
public function notify_success($message) {
debugging(__FUNCTION__ . ' is deprecated.' .
'Please use notification() or \core\output\notification as required',
'Please use \core\notification::add, or \core\output\notification as required',
DEBUG_DEVELOPER);
$n = new \core\output\notification($message, \core\output\notification::NOTIFY_SUCCESS);
return $this->render($n);
@ -2882,7 +2900,7 @@ EOD;
*/
public function notify_message($message) {
debugging(__FUNCTION__ . ' is deprecated.' .
'Please use notification() or \core\output\notification as required',
'Please use \core\notification::add, or \core\output\notification as required',
DEBUG_DEVELOPER);
$n = new \core\output\notification($message, \core\output\notification::NOTIFY_INFO);
return $this->render($n);
@ -2899,7 +2917,7 @@ EOD;
*/
public function notify_redirect($message) {
debugging(__FUNCTION__ . ' is deprecated.' .
'Please use notification() or \core\output\notification as required',
'Please use \core\notification::add, or \core\output\notification as required',
DEBUG_DEVELOPER);
$n = new \core\output\notification($message, \core\output\notification::NOTIFY_INFO);
return $this->render($n);

View File

@ -30,10 +30,15 @@
Context variables required for this template:
* message A cleaned string (use clean_text()) to display.
* extraclasses Additional classes to apply to the notification.
* closebutton Whether a close button should be displayed to dismiss the message.
* announce Whether the notification should be announced to screen readers.
Example context (json):
{ "message": "Your pants are on fire!", "extraclasses": "foo bar"}
{ "message": "Your pants are on fire!", "closebutton": 1, "announce": 1, "extraclasses": "foo bar"}
}}
<div class="alert alert-error alert-block fade in {{ extraclasses }}">
<div class="alert alert-error alert-block fade in {{ extraclasses }}" {{!
}}{{# announce }} aria-live="assertive"{{/ announce }}{{!
}}>
{{# closebutton }}<button type="button" class="close" data-dismiss="alert">&times;</button>{{/ closebutton }}
{{{ message }}}
</div>

View File

@ -30,10 +30,15 @@
Context variables required for this template:
* message A cleaned string (use clean_text()) to display.
* extraclasses Additional classes to apply to the notification.
* closebutton Whether a close button should be displayed to dismiss the message.
* announce Whether the notification should be announced to screen readers.
Example context (json):
{ "message": "Your pants are on fire!", "extraclasses": "foo bar"}
{ "message": "Your pants are on fire!", "closebutton": 1, "announce": 1, "extraclasses": "foo bar"}
}}
<div class="alert alert-info alert-block fade in {{ extraclasses }}">
<div class="alert alert-info alert-block fade in {{ extraclasses }}" {{!
}}{{# announce }} aria-live="assertive"{{/ announce }}{{!
}}>
{{# closebutton }}<button type="button" class="close" data-dismiss="alert">&times;</button>{{/ closebutton }}
{{{ message }}}
</div>

View File

@ -30,10 +30,15 @@
Context variables required for this template:
* message A cleaned string (use clean_text()) to display.
* extraclasses Additional classes to apply to the notification.
* closebutton Whether a close button should be displayed to dismiss the message.
* announce Whether the notification should be announced to screen readers.
Example context (json):
{ "message": "Your pants are on fire!", "extraclasses": "foo bar"}
{ "message": "Your pants are on fire!", "closebutton": 1, "announce": 1, "extraclasses": "foo bar"}
}}
<div class="alert alert-success alert-block fade in {{ extraclasses }}">
<div class="alert alert-success alert-block fade in {{ extraclasses }}" {{!
}}{{# announce }} aria-live="assertive"{{/ announce }}{{!
}}>
{{# closebutton }}<button type="button" class="close" data-dismiss="alert">&times;</button>{{/ closebutton }}
{{{ message }}}
</div>

View File

@ -30,10 +30,15 @@
Context variables required for this template:
* message A cleaned string (use clean_text()) to display.
* extraclasses Additional classes to apply to the notification.
* closebutton Whether a close button should be displayed to dismiss the message.
* announce Whether the notification should be announced to screen readers.
Example context (json):
{ "message": "Your pants are on fire!", "extraclasses": "foo bar"}
{ "message": "Your pants are on fire!", "closebutton": 1, "announce": 1, "extraclasses": "foo bar"}
}}
<div class="alert alert-warning alert-block fade in {{ extraclasses }}">
<div class="alert alert-warning alert-block fade in {{ extraclasses }}" {{!
}}{{# announce }} aria-live="assertive"{{/ announce }}{{!
}}>
{{# closebutton }}<button type="button" class="close" data-dismiss="alert">&times;</button>{{/ closebutton }}
{{{ message }}}
</div>

View File

@ -0,0 +1,122 @@
<?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/>.
/**
* Unit tests for core\notification.
*
* @package core
* @category phpunit
* @copyright 2016 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Unit tests for core\notification.
*
* @package core
* @category phpunit
* @category phpunit
* @copyright 2016 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class core_notification_testcase extends advanced_testcase {
/**
* Setup required for all notification tests.
*
* This includes emptying the list of notifications on the session, resetting any session which exists, and setting
* up a new moodle_page object.
*/
public function setUp() {
global $PAGE, $SESSION;
parent::setUp();
$PAGE = new moodle_page();
\core\session\manager::init_empty_session();
$SESSION->notifications = [];
}
/**
* Tear down required for all notification tests.
*
* This includes emptying the list of notifications on the session, resetting any session which exists, and setting
* up a new moodle_page object.
*/
public function tearDown() {
global $PAGE, $SESSION;
$PAGE = null;
\core\session\manager::init_empty_session();
$SESSION->notifications = [];
parent::tearDown();
}
/**
* Test the way in which notifications are added to the session in different stages of the page load.
*/
public function test_add_during_output_stages() {
global $PAGE, $SESSION;
\core\notification::add('Example before header', \core\notification::INFO);
$this->assertCount(1, $SESSION->notifications);
$PAGE->set_state(\moodle_page::STATE_PRINTING_HEADER);
\core\notification::add('Example during header', \core\notification::INFO);
$this->assertCount(2, $SESSION->notifications);
$PAGE->set_state(\moodle_page::STATE_IN_BODY);
\core\notification::add('Example in body', \core\notification::INFO);
$this->expectOutputRegex('/Example in body/');
$this->assertCount(2, $SESSION->notifications);
$PAGE->set_state(\moodle_page::STATE_DONE);
\core\notification::add('Example after page', \core\notification::INFO);
$this->assertCount(3, $SESSION->notifications);
}
/**
* Test fetching of notifications from the session.
*/
public function test_fetch() {
// Initially there won't be any notifications.
$this->assertCount(0, \core\notification::fetch());
// Adding a notification should make one available to fetch.
\core\notification::success('Notification created');
$this->assertCount(1, \core\notification::fetch());
$this->assertCount(0, \core\notification::fetch());
}
/**
* Test that session notifications are persisted across session clears.
*/
public function test_session_persistance() {
global $PAGE, $SESSION;
// Initially there won't be any notifications.
$this->assertCount(0, $SESSION->notifications);
// Adding a notification should make one available to fetch.
\core\notification::success('Notification created');
$this->assertCount(1, $SESSION->notifications);
// Re-creating the session will not empty the notification bag.
\core\session\manager::init_empty_session();
$this->assertCount(1, $SESSION->notifications);
}
}

View File

@ -59,7 +59,7 @@ class core_session_manager_testcase extends advanced_testcase {
\core\session\manager::init_empty_session();
$this->assertInstanceOf('stdClass', $SESSION);
$this->assertEmpty((array)$SESSION);
$this->assertCount(1, (array)$SESSION);
$this->assertSame($GLOBALS['SESSION'], $_SESSION['SESSION']);
$this->assertSame($GLOBALS['SESSION'], $SESSION);
@ -149,7 +149,7 @@ class core_session_manager_testcase extends advanced_testcase {
$this->assertEquals(0, $USER->id);
$this->assertInstanceOf('stdClass', $SESSION);
$this->assertEmpty((array)$SESSION);
$this->assertCount(1, (array)$SESSION);
$this->assertSame($GLOBALS['SESSION'], $_SESSION['SESSION']);
$this->assertSame($GLOBALS['SESSION'], $SESSION);

View File

@ -76,7 +76,7 @@ class core_sessionlib_testcase extends advanced_testcase {
$this->assertSame($PAGE->context, context_course::instance($SITE->id));
$this->assertNotSame($adminsession, $SESSION);
$this->assertObjectNotHasAttribute('test1', $SESSION);
$this->assertEmpty((array)$SESSION);
$this->assertCount(1, (array)$SESSION);
$usersession1 = $SESSION;
$SESSION->test2 = true;
$this->assertSame($GLOBALS['SESSION'], $_SESSION['SESSION']);
@ -99,7 +99,7 @@ class core_sessionlib_testcase extends advanced_testcase {
$this->assertSame($PAGE->context, context_course::instance($SITE->id));
$this->assertNotSame($adminsession, $SESSION);
$this->assertNotSame($usersession1, $SESSION);
$this->assertEmpty((array)$SESSION);
$this->assertCount(1, (array)$SESSION);
$usersession2 = $SESSION;
$usersession2->test3 = true;
$this->assertSame($GLOBALS['SESSION'], $_SESSION['SESSION']);
@ -123,7 +123,7 @@ class core_sessionlib_testcase extends advanced_testcase {
$this->assertSame($PAGE->context, context_course::instance($SITE->id));
$this->assertNotSame($adminsession, $SESSION);
$this->assertNotSame($usersession1, $SESSION);
$this->assertEmpty((array)$SESSION);
$this->assertCount(1, (array)$SESSION);
$this->assertSame($GLOBALS['SESSION'], $_SESSION['SESSION']);
$this->assertSame($GLOBALS['SESSION'], $SESSION);
$this->assertSame($GLOBALS['USER'], $_SESSION['USER']);

View File

@ -29,7 +29,7 @@
defined('MOODLE_INTERNAL') || die();
$version = 2016022500.00; // YYYYMMDD = weekly release date of this DEV branch.
$version = 2016030100.00; // YYYYMMDD = weekly release date of this DEV branch.
// RR = release increments - 00 in DEV branches.
// .XX = incremental changes.