mirror of
https://github.com/moodle/moodle.git
synced 2025-04-21 00:12:56 +02:00
Merge branch 'MDL-60680-master' of git://github.com/jleyva/moodle
This commit is contained in:
commit
b63c0b9079
@ -290,13 +290,29 @@ class core_badges_badgeslib_testcase extends advanced_testcase {
|
||||
}
|
||||
|
||||
public function test_badge_awards() {
|
||||
global $DB;
|
||||
$this->preventResetByRollback(); // Messaging is not compatible with transactions.
|
||||
$badge = new badge($this->badgeid);
|
||||
$user1 = $this->getDataGenerator()->create_user();
|
||||
|
||||
$badge->issue($user1->id, true);
|
||||
$sink = $this->redirectMessages();
|
||||
|
||||
$DB->set_field_select('message_processors', 'enabled', 0, "name <> 'email'");
|
||||
set_user_preference('message_provider_moodle_badgerecipientnotice_loggedoff', 'email', $user1);
|
||||
|
||||
$badge->issue($user1->id, false);
|
||||
$this->assertDebuggingCalled(); // Expect debugging while baking a badge via phpunit.
|
||||
$this->assertTrue($badge->is_issued($user1->id));
|
||||
|
||||
$messages = $sink->get_messages();
|
||||
$sink->close();
|
||||
$this->assertCount(1, $messages);
|
||||
$message = array_pop($messages);
|
||||
// Check we have the expected data.
|
||||
$customdata = json_decode($message->customdata);
|
||||
$this->assertObjectHasAttribute('notificationiconurl', $customdata);
|
||||
$this->assertObjectHasAttribute('hash', $customdata);
|
||||
|
||||
$user2 = $this->getDataGenerator()->create_user();
|
||||
$badge->issue($user2->id, true);
|
||||
$this->assertTrue($badge->is_issued($user2->id));
|
||||
|
@ -38,7 +38,7 @@ use core_competency\user_evidence;
|
||||
* @return array
|
||||
*/
|
||||
function core_competency_comment_add($comment, $params) {
|
||||
global $USER;
|
||||
global $USER, $PAGE;
|
||||
|
||||
if (!get_config('core_competency', 'enabled')) {
|
||||
return;
|
||||
@ -132,10 +132,16 @@ function core_competency_comment_add($comment, $params) {
|
||||
$message->contexturl = $url->out(false);
|
||||
$message->contexturlname = $urlname;
|
||||
|
||||
$userpicture = new \user_picture($user);
|
||||
// Message each recipient.
|
||||
foreach ($recipients as $recipient) {
|
||||
$msgcopy = clone($message);
|
||||
$msgcopy->userto = $recipient;
|
||||
// Generate an out-of-session token for the user receiving the message.
|
||||
$userpicture->includetoken = $recipient;
|
||||
$msgcopy->customdata = [
|
||||
'notificationiconurl' => $userpicture->get_url($PAGE)->out(false),
|
||||
];
|
||||
message_send($msgcopy);
|
||||
}
|
||||
|
||||
@ -201,10 +207,16 @@ function core_competency_comment_add($comment, $params) {
|
||||
$message->contexturl = $url->out(false);
|
||||
$message->contexturlname = $urlname;
|
||||
|
||||
$userpicture = new \user_picture($user);
|
||||
// Message each recipient.
|
||||
foreach ($recipients as $recipient) {
|
||||
$msgcopy = clone($message);
|
||||
$msgcopy->userto = $recipient;
|
||||
// Generate an out-of-session token for the user receiving the message.
|
||||
$userpicture->includetoken = $recipient;
|
||||
$msgcopy->customdata = [
|
||||
'notificationiconurl' => $userpicture->get_url($PAGE)->out(false),
|
||||
];
|
||||
message_send($msgcopy);
|
||||
}
|
||||
}
|
||||
|
@ -40,12 +40,12 @@ global $CFG;
|
||||
class core_competency_lib_testcase extends advanced_testcase {
|
||||
|
||||
public function test_comment_add_user_competency() {
|
||||
global $DB;
|
||||
global $DB, $PAGE;
|
||||
$this->resetAfterTest();
|
||||
$dg = $this->getDataGenerator();
|
||||
$lpg = $dg->get_plugin_generator('core_competency');
|
||||
|
||||
$u1 = $dg->create_user();
|
||||
$u1 = $dg->create_user(['picture' => 1]);
|
||||
$u2 = $dg->create_user();
|
||||
$u3 = $dg->create_user();
|
||||
$reviewerroleid = $dg->create_role();
|
||||
@ -96,6 +96,13 @@ class core_competency_lib_testcase extends advanced_testcase {
|
||||
$this->assertEquals(FORMAT_MOODLE, $message->fullmessageformat);
|
||||
$this->assertEquals($expectedurl->out(false), $message->contexturl);
|
||||
$this->assertEquals($expectedurlname, $message->contexturlname);
|
||||
// Test customdata.
|
||||
$customdata = json_decode($message->customdata);
|
||||
$this->assertObjectHasAttribute('notificationiconurl', $customdata);
|
||||
$this->assertContains('tokenpluginfile.php', $customdata->notificationiconurl);
|
||||
$userpicture = new \user_picture($u1);
|
||||
$userpicture->includetoken = $u2->id;
|
||||
$this->assertEquals($userpicture->get_url($PAGE)->out(false), $customdata->notificationiconurl);
|
||||
|
||||
// Reviewer posts a comment for the user competency being in two plans. Owner is messaged.
|
||||
$this->setUser($u2);
|
||||
@ -218,6 +225,9 @@ class core_competency_lib_testcase extends advanced_testcase {
|
||||
$message = array_pop($messages);
|
||||
$this->assertEquals(core_user::get_noreply_user()->id, $message->useridfrom);
|
||||
$this->assertEquals($u1->id, $message->useridto);
|
||||
// Test customdata.
|
||||
$customdata = json_decode($message->customdata);
|
||||
$this->assertObjectHasAttribute('notificationiconurl', $customdata);
|
||||
|
||||
// Post a comment in a plan with reviewer. The reviewer is messaged.
|
||||
$p1->set('reviewerid', $u2->id);
|
||||
|
@ -157,6 +157,7 @@ $string['privacy:metadata:messages:useridfrom'] = 'The ID of the user who sent t
|
||||
$string['privacy:metadata:messages:smallmessage'] = 'A small version of the message';
|
||||
$string['privacy:metadata:messages:subject'] = 'The subject of the message';
|
||||
$string['privacy:metadata:messages:timecreated'] = 'The time when the message was created';
|
||||
$string['privacy:metadata:messages:customdata'] = 'Custom data, usually contains internal ids and a public URL of the sender image (user or group).';
|
||||
$string['privacy:metadata:message_contacts'] = 'The list of contacts';
|
||||
$string['privacy:metadata:message_contacts:contactid'] = 'The ID of the user who is a contact';
|
||||
$string['privacy:metadata:message_contacts:timecreated'] = 'The time when the contact was created';
|
||||
@ -197,6 +198,7 @@ $string['privacy:metadata:notifications:timeread'] = 'The time when the notifica
|
||||
$string['privacy:metadata:notifications:timecreated'] = 'The time when the notification was created';
|
||||
$string['privacy:metadata:notifications:useridfrom'] = 'The ID of the user who sent the notification';
|
||||
$string['privacy:metadata:notifications:useridto'] = 'The ID of the user who received the notification';
|
||||
$string['privacy:metadata:notifications:customdata'] = 'Custom data, usually contains internal ids and a public URL of the sender picture (if any).';
|
||||
$string['privacy:metadata:preference:core_message_settings'] = 'Settings related to messaging';
|
||||
$string['privacy:request:preference:set'] = 'The value of the setting \'{$a->name}\' was \'{$a->value}\'';
|
||||
$string['privacy:export:conversationprefix'] = 'Conversation: ';
|
||||
|
@ -23,6 +23,7 @@
|
||||
*/
|
||||
|
||||
$string['abouttobeinstalled'] = 'about to be installed';
|
||||
$string['accept'] = 'Accept';
|
||||
$string['action'] = 'Action';
|
||||
$string['actionchoice'] = 'What do you want to do with the file \'{$a}\'?';
|
||||
$string['actions'] = 'Actions';
|
||||
|
@ -1012,6 +1012,11 @@ function badges_notify_badge_award(badge $badge, $userid, $issued, $filepathhash
|
||||
$eventdata->fullmessageformat = FORMAT_HTML;
|
||||
$eventdata->fullmessagehtml = $message;
|
||||
$eventdata->smallmessage = '';
|
||||
$eventdata->customdata = [
|
||||
'notificationiconurl' => moodle_url::make_pluginfile_url(
|
||||
$badge->get_context()->id, 'badges', 'badgeimage', $badge->id, '/', 'f1')->out(),
|
||||
'hash' => $issued,
|
||||
];
|
||||
|
||||
// Attach badge image if possible.
|
||||
if (!empty($CFG->allowattachments) && $badge->attachment && is_string($filepathhash)) {
|
||||
@ -1049,6 +1054,11 @@ function badges_notify_badge_award(badge $badge, $userid, $issued, $filepathhash
|
||||
$eventdata->fullmessageformat = FORMAT_HTML;
|
||||
$eventdata->fullmessagehtml = $creatormessage;
|
||||
$eventdata->smallmessage = '';
|
||||
$eventdata->customdata = [
|
||||
'notificationiconurl' => moodle_url::make_pluginfile_url(
|
||||
$badge->get_context()->id, 'badges', 'badgeimage', $badge->id, '/', 'f1')->out(),
|
||||
'hash' => $issued,
|
||||
];
|
||||
|
||||
message_send($eventdata);
|
||||
$DB->set_field('badge_issued', 'issuernotified', time(), array('badgeid' => $badge->id, 'userid' => $userid));
|
||||
|
@ -169,6 +169,14 @@ class manager {
|
||||
|
||||
// Spoof the userto based on the current member id.
|
||||
$localisedeventdata->userto = $recipient;
|
||||
// Check if the notification is including images that will need a user token to be displayed outside Moodle.
|
||||
if (!empty($localisedeventdata->customdata)) {
|
||||
$customdata = json_decode($localisedeventdata->customdata);
|
||||
if (is_object($customdata) && !empty($customdata->notificationiconurl)) {
|
||||
$customdata->tokenpluginfile = get_user_key('core_files', $localisedeventdata->userto->id);
|
||||
$localisedeventdata->customdata = $customdata; // Message class will JSON encode again.
|
||||
}
|
||||
}
|
||||
|
||||
$s = new \stdClass();
|
||||
$s->sitename = format_string($SITE->shortname, true, array('context' => \context_course::instance(SITEID)));
|
||||
|
@ -52,6 +52,7 @@ defined('MOODLE_INTERNAL') || die();
|
||||
* replyto string An email address which can be used to send an reply.
|
||||
* attachment stored_file File instance that needs to be sent as attachment.
|
||||
* attachname string Name of the attachment.
|
||||
* customdata mixed Custom data to be passed to the message processor. Must be serialisable using json_encode().
|
||||
*
|
||||
* @package core_message
|
||||
* @since Moodle 2.9
|
||||
@ -125,9 +126,12 @@ class message {
|
||||
/** @var int The time the message was created.*/
|
||||
private $timecreated;
|
||||
|
||||
/** @var boolean Mark trust content. */
|
||||
/** @var boolean Mark trust content. */
|
||||
private $fullmessagetrust;
|
||||
|
||||
/** @var mixed Custom data to be passed to the message processor. Must be serialisable using json_encode(). */
|
||||
private $customdata;
|
||||
|
||||
/** @var array a list of properties that is allowed for each message. */
|
||||
private $properties = array(
|
||||
'courseid',
|
||||
@ -152,8 +156,9 @@ class message {
|
||||
'attachment',
|
||||
'attachname',
|
||||
'timecreated',
|
||||
'fullmessagetrust'
|
||||
);
|
||||
'fullmessagetrust',
|
||||
'customdata',
|
||||
);
|
||||
|
||||
/** @var array property to store any additional message processor specific content */
|
||||
private $additionalcontent = array();
|
||||
@ -203,6 +208,20 @@ class message {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Always JSON encode customdata.
|
||||
*
|
||||
* @param mixed $customdata a data structure that must be serialisable using json_encode().
|
||||
*/
|
||||
protected function set_customdata($customdata) {
|
||||
// Always include the courseid (because is not stored in the notifications or messages table).
|
||||
if (!empty($this->courseid) && (is_object($customdata) || is_array($customdata))) {
|
||||
$customdata = (array) $customdata;
|
||||
$customdata['courseid'] = $this->courseid;
|
||||
}
|
||||
$this->customdata = json_encode($customdata);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method used to get message content added with processor specific content.
|
||||
*
|
||||
@ -255,6 +274,12 @@ class message {
|
||||
* @throws \coding_exception
|
||||
*/
|
||||
public function __set($prop, $value) {
|
||||
|
||||
// Custom data must be JSON encoded always.
|
||||
if ($prop == 'customdata') {
|
||||
return $this->set_customdata($value);
|
||||
}
|
||||
|
||||
if (in_array($prop, $this->properties)) {
|
||||
return $this->$prop = $value;
|
||||
}
|
||||
|
@ -552,6 +552,7 @@
|
||||
<FIELD NAME="timeusertodeleted" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
<FIELD NAME="component" TYPE="char" LENGTH="100" NOTNULL="false" SEQUENCE="false"/>
|
||||
<FIELD NAME="eventtype" TYPE="char" LENGTH="100" NOTNULL="false" SEQUENCE="false"/>
|
||||
<FIELD NAME="customdata" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="Custom data to be passed to the message processor. Must be serialisable using json_encode()"/>
|
||||
</FIELDS>
|
||||
<KEYS>
|
||||
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
|
||||
@ -604,6 +605,7 @@
|
||||
<FIELD NAME="smallmessage" TYPE="text" NOTNULL="false" SEQUENCE="false"/>
|
||||
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="fullmessagetrust" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
<FIELD NAME="customdata" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="Custom data to be passed to the message processor. Must be serialisable using json_encode()"/>
|
||||
</FIELDS>
|
||||
<KEYS>
|
||||
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
|
||||
@ -698,6 +700,7 @@
|
||||
<FIELD NAME="contexturlname" TYPE="text" NOTNULL="false" SEQUENCE="false"/>
|
||||
<FIELD NAME="timeread" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false"/>
|
||||
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="customdata" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="Custom data to be passed to the message processor. Must be serialisable using json_encode()"/>
|
||||
</FIELDS>
|
||||
<KEYS>
|
||||
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
|
||||
|
@ -72,12 +72,18 @@ $messageproviders = array (
|
||||
|
||||
// Course request approval notification
|
||||
'courserequestapproved' => array (
|
||||
'capability' => 'moodle/course:request'
|
||||
'capability' => 'moodle/course:request',
|
||||
'defaults' => array(
|
||||
'airnotifier' => MESSAGE_PERMITTED + MESSAGE_DEFAULT_LOGGEDIN + MESSAGE_DEFAULT_LOGGEDOFF,
|
||||
),
|
||||
),
|
||||
|
||||
// Course request rejection notification
|
||||
'courserequestrejected' => array (
|
||||
'capability' => 'moodle/course:request'
|
||||
'capability' => 'moodle/course:request',
|
||||
'defaults' => array(
|
||||
'airnotifier' => MESSAGE_PERMITTED + MESSAGE_DEFAULT_LOGGEDIN + MESSAGE_DEFAULT_LOGGEDOFF,
|
||||
),
|
||||
),
|
||||
|
||||
// Badge award notification to a badge recipient.
|
||||
@ -85,6 +91,7 @@ $messageproviders = array (
|
||||
'defaults' => array(
|
||||
'popup' => MESSAGE_PERMITTED + MESSAGE_DEFAULT_LOGGEDIN + MESSAGE_DEFAULT_LOGGEDOFF,
|
||||
'email' => MESSAGE_PERMITTED + MESSAGE_DEFAULT_LOGGEDOFF,
|
||||
'airnotifier' => MESSAGE_PERMITTED + MESSAGE_DEFAULT_LOGGEDIN + MESSAGE_DEFAULT_LOGGEDOFF,
|
||||
),
|
||||
'capability' => 'moodle/badges:earnbadge'
|
||||
),
|
||||
@ -107,6 +114,7 @@ $messageproviders = array (
|
||||
'defaults' => [
|
||||
'popup' => MESSAGE_PERMITTED + MESSAGE_DEFAULT_LOGGEDIN + MESSAGE_DEFAULT_LOGGEDOFF,
|
||||
'email' => MESSAGE_PERMITTED + MESSAGE_DEFAULT_LOGGEDOFF,
|
||||
'airnotifier' => MESSAGE_PERMITTED + MESSAGE_DEFAULT_LOGGEDIN + MESSAGE_DEFAULT_LOGGEDOFF,
|
||||
]
|
||||
),
|
||||
|
||||
@ -115,6 +123,7 @@ $messageproviders = array (
|
||||
'defaults' => [
|
||||
'popup' => MESSAGE_PERMITTED + MESSAGE_DEFAULT_LOGGEDIN + MESSAGE_DEFAULT_LOGGEDOFF,
|
||||
'email' => MESSAGE_PERMITTED + MESSAGE_DEFAULT_LOGGEDOFF,
|
||||
'airnotifier' => MESSAGE_PERMITTED + MESSAGE_DEFAULT_LOGGEDIN + MESSAGE_DEFAULT_LOGGEDOFF,
|
||||
]
|
||||
],
|
||||
|
||||
|
@ -3240,5 +3240,35 @@ function xmldb_main_upgrade($oldversion) {
|
||||
upgrade_main_savepoint(true, 2019042300.01);
|
||||
}
|
||||
|
||||
if ($oldversion < 2019042300.03) {
|
||||
|
||||
// Add new customdata field to message table.
|
||||
$table = new xmldb_table('message');
|
||||
$field = new xmldb_field('customdata', XMLDB_TYPE_TEXT, null, null, null, null, null, 'eventtype');
|
||||
|
||||
// Conditionally launch add field output.
|
||||
if (!$dbman->field_exists($table, $field)) {
|
||||
$dbman->add_field($table, $field);
|
||||
}
|
||||
|
||||
// Add new customdata field to notifications and messages table.
|
||||
$table = new xmldb_table('notifications');
|
||||
$field = new xmldb_field('customdata', XMLDB_TYPE_TEXT, null, null, null, null, null, 'timecreated');
|
||||
|
||||
// Conditionally launch add field output.
|
||||
if (!$dbman->field_exists($table, $field)) {
|
||||
$dbman->add_field($table, $field);
|
||||
}
|
||||
|
||||
$table = new xmldb_table('messages');
|
||||
// Conditionally launch add field output.
|
||||
if (!$dbman->field_exists($table, $field)) {
|
||||
$dbman->add_field($table, $field);
|
||||
}
|
||||
|
||||
// Main savepoint reached.
|
||||
upgrade_main_savepoint(true, 2019042300.03);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -470,7 +470,7 @@ function file_prepare_draft_area(&$draftitemid, $contextid, $component, $fileare
|
||||
* @param array $options
|
||||
* bool $options.forcehttps Force the user of https
|
||||
* bool $options.reverse Reverse the behaviour of the function
|
||||
* bool $options.includetoken Use a token for authentication
|
||||
* mixed $options.includetoken Use a token for authentication. True for current user, int value for other user id.
|
||||
* string The processed text.
|
||||
*/
|
||||
function file_rewrite_pluginfile_urls($text, $file, $contextid, $component, $filearea, $itemid, array $options=null) {
|
||||
@ -483,7 +483,8 @@ function file_rewrite_pluginfile_urls($text, $file, $contextid, $component, $fil
|
||||
|
||||
$baseurl = "{$CFG->wwwroot}/{$file}";
|
||||
if (!empty($options['includetoken'])) {
|
||||
$token = get_user_key('core_files', $USER->id);
|
||||
$userid = $options['includetoken'] === true ? $USER->id : $options['includetoken'];
|
||||
$token = get_user_key('core_files', $userid);
|
||||
$finalfile = basename($file);
|
||||
$tokenfile = "token{$finalfile}";
|
||||
$file = substr($file, 0, strlen($file) - strlen($finalfile)) . $tokenfile;
|
||||
|
@ -165,6 +165,7 @@ function message_send(\core\message\message $eventdata) {
|
||||
$tabledata->fullmessagehtml = $eventdata->fullmessagehtml;
|
||||
$tabledata->smallmessage = $eventdata->smallmessage;
|
||||
$tabledata->timecreated = time();
|
||||
$tabledata->customdata = $eventdata->customdata;
|
||||
|
||||
// The Trusted Content system.
|
||||
// Texts created or uploaded by such users will be marked as trusted and will not be cleaned before display.
|
||||
@ -267,6 +268,7 @@ function message_send(\core\message\message $eventdata) {
|
||||
$tabledata->eventtype = $eventdata->name;
|
||||
$tabledata->component = $eventdata->component;
|
||||
$tabledata->timecreated = time();
|
||||
$tabledata->customdata = $eventdata->customdata;
|
||||
if (!empty($eventdata->contexturl)) {
|
||||
$tabledata->contexturl = (string)$eventdata->contexturl;
|
||||
} else {
|
||||
|
@ -207,7 +207,8 @@ class user_picture implements renderable {
|
||||
public $includefullname = false;
|
||||
|
||||
/**
|
||||
* @var bool Include user authentication token.
|
||||
* @var mixed Include user authentication token. True indicates to generate a token for current user, and integer value
|
||||
* indicates to generate a token for the user whose id is the value indicated.
|
||||
*/
|
||||
public $includetoken = false;
|
||||
|
||||
|
@ -2434,7 +2434,7 @@ class core_renderer extends renderer_base {
|
||||
* - class = image class attribute (default 'userpicture')
|
||||
* - visibletoscreenreaders=true (whether to be visible to screen readers)
|
||||
* - includefullname=false (whether to include the user's full name together with the user picture)
|
||||
* - includetoken = false
|
||||
* - includetoken = false (whether to use a token for authentication. True for current user, int value for other user id)
|
||||
* @return string HTML fragment
|
||||
*/
|
||||
public function user_picture(stdClass $user, array $options = null) {
|
||||
|
@ -1150,6 +1150,19 @@ EOF;
|
||||
|
||||
// Compare the final text is the same that the original.
|
||||
$this->assertEquals($originaltext, $finaltext);
|
||||
|
||||
// Now indicates a user different than $USER.
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$options = ['includetoken' => $user->id];
|
||||
|
||||
// Rewrite the content. This will generate a new token.
|
||||
$finaltext = file_rewrite_pluginfile_urls(
|
||||
$originaltext, 'pluginfile.php', $syscontext->id, 'user', 'private', 0, $options);
|
||||
|
||||
$token = get_user_key('core_files', $user->id);
|
||||
$expectedurl = new \moodle_url("/tokenpluginfile.php/{$token}/{$syscontext->id}/user/private/0/image.png");
|
||||
$expectedtext = "Fake test with an image <img src=\"{$expectedurl}\">";
|
||||
$this->assertEquals($expectedtext, $finaltext);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -202,6 +202,7 @@ class core_messagelib_testcase extends advanced_testcase {
|
||||
$message->fullmessagehtml = '<p>message body</p>';
|
||||
$message->smallmessage = 'small message';
|
||||
$message->notification = '0';
|
||||
$message->customdata = ['datakey' => 'data'];
|
||||
|
||||
$sink = $this->redirectMessages();
|
||||
$this->setCurrentTimeStart();
|
||||
@ -218,6 +219,12 @@ class core_messagelib_testcase extends advanced_testcase {
|
||||
$this->assertEquals($message->smallmessage, $savedmessage->smallmessage);
|
||||
$this->assertEquals($message->smallmessage, $savedmessage->smallmessage);
|
||||
$this->assertEquals($message->notification, $savedmessage->notification);
|
||||
$this->assertEquals($message->customdata, $savedmessage->customdata);
|
||||
$this->assertContains('datakey', $savedmessage->customdata);
|
||||
// Check it was a unserialisable json.
|
||||
$customdata = json_decode($savedmessage->customdata);
|
||||
$this->assertEquals('data', $customdata->datakey);
|
||||
$this->assertEquals(1, $customdata->courseid);
|
||||
$this->assertTimeCurrent($savedmessage->timecreated);
|
||||
$record = $DB->get_record('messages', array('id' => $savedmessage->id), '*', MUST_EXIST);
|
||||
unset($savedmessage->useridto);
|
||||
|
@ -113,7 +113,7 @@ class core_outputcomponents_testcase extends advanced_testcase {
|
||||
}
|
||||
|
||||
public function test_get_url() {
|
||||
global $DB, $CFG;
|
||||
global $DB, $CFG, $USER;
|
||||
|
||||
$this->resetAfterTest();
|
||||
|
||||
@ -219,6 +219,18 @@ class core_outputcomponents_testcase extends advanced_testcase {
|
||||
$up1 = new user_picture($user1);
|
||||
$this->assertSame($CFG->wwwroot.'/pluginfile.php/'.$context1->id.'/user/icon/boost/f2?rev=11', $up1->get_url($page, $renderer)->out(false));
|
||||
|
||||
// Uploaded image with token-based access for current user.
|
||||
$up1 = new user_picture($user1);
|
||||
$up1->includetoken = true;
|
||||
$token = get_user_key('core_files', $USER->id);
|
||||
$this->assertSame($CFG->wwwroot.'/tokenpluginfile.php/'.$token.'/'.$context1->id.'/user/icon/boost/f2?rev=11', $up1->get_url($page, $renderer)->out(false));
|
||||
|
||||
// Uploaded image with token-based access for other user.
|
||||
$up1 = new user_picture($user1);
|
||||
$up1->includetoken = $user2->id;
|
||||
$token = get_user_key('core_files', $user2->id);
|
||||
$this->assertSame($CFG->wwwroot.'/tokenpluginfile.php/'.$token.'/'.$context1->id.'/user/icon/boost/f2?rev=11', $up1->get_url($page, $renderer)->out(false));
|
||||
|
||||
// Https version.
|
||||
$CFG->wwwroot = str_replace('http:', 'https:', $CFG->wwwroot);
|
||||
|
||||
|
@ -42,6 +42,12 @@ is disabled).
|
||||
matching record found or when there are multiple records found for the given field value. If false, it will simply return false.
|
||||
Defaults to false when not set.
|
||||
* Exposed submit button to allow custom styling (via customclassoverride variable) which can override btn-primary/btn-secondary classes
|
||||
* `$includetoken` parameter type has been changed. Now supports:
|
||||
boolean: False indicates to not include the token, true indicates to generate a token for the current user ($USER).
|
||||
integer: Indicates to generate a token for the user whose id is the integer value.
|
||||
* The following functions have been updated to support the new usage:
|
||||
- make_pluginfile_url
|
||||
- file_rewrite_pluginfile_urls
|
||||
|
||||
=== 3.6 ===
|
||||
|
||||
|
@ -773,7 +773,9 @@ class moodle_url {
|
||||
* @param string $pathname
|
||||
* @param string $filename
|
||||
* @param bool $forcedownload
|
||||
* @param boolean $includetoken Whether to use a user token when displaying this group image.
|
||||
* @param mixed $includetoken Whether to use a user token when displaying this group image.
|
||||
* True indicates to generate a token for current user, and integer value indicates to generate a token for the
|
||||
* user whose id is the value indicated.
|
||||
* If the group picture is included in an e-mail or some other location where the audience is a specific
|
||||
* user who will not be logged in when viewing, then we use a token to authenticate the user.
|
||||
* @return moodle_url
|
||||
@ -786,7 +788,8 @@ class moodle_url {
|
||||
|
||||
if ($includetoken) {
|
||||
$urlbase = "$CFG->wwwroot/tokenpluginfile.php";
|
||||
$token = get_user_key('core_files', $USER->id);
|
||||
$userid = $includetoken === true ? $USER->id : $includetoken;
|
||||
$token = get_user_key('core_files', $userid);
|
||||
if ($CFG->slasharguments) {
|
||||
$path[] = $token;
|
||||
}
|
||||
@ -2491,6 +2494,8 @@ function print_collapsible_region_end($return = false) {
|
||||
* @param boolean $return If false print picture, otherwise return the output as string
|
||||
* @param boolean $link Enclose image in a link to view specified course?
|
||||
* @param boolean $includetoken Whether to use a user token when displaying this group image.
|
||||
* True indicates to generate a token for current user, and integer value indicates to generate a token for the
|
||||
* user whose id is the value indicated.
|
||||
* If the group picture is included in an e-mail or some other location where the audience is a specific
|
||||
* user who will not be logged in when viewing, then we use a token to authenticate the user.
|
||||
* @return string|void Depending on the setting of $return
|
||||
@ -2545,6 +2550,8 @@ function print_group_picture($group, $courseid, $large = false, $return = false,
|
||||
* @param int $courseid The course ID for the group.
|
||||
* @param bool $large A large or small group picture? Default is small.
|
||||
* @param boolean $includetoken Whether to use a user token when displaying this group image.
|
||||
* True indicates to generate a token for current user, and integer value indicates to generate a token for the
|
||||
* user whose id is the value indicated.
|
||||
* If the group picture is included in an e-mail or some other location where the audience is a specific
|
||||
* user who will not be logged in when viewing, then we use a token to authenticate the user.
|
||||
* @return moodle_url Returns the url for the group picture.
|
||||
|
@ -1929,7 +1929,7 @@ class api {
|
||||
*/
|
||||
public static function send_message_to_conversation(int $userid, int $conversationid, string $message,
|
||||
int $format) : \stdClass {
|
||||
global $DB;
|
||||
global $DB, $PAGE;
|
||||
|
||||
if (!self::can_send_message_to_conversation($userid, $conversationid)) {
|
||||
throw new \moodle_exception("User $userid cannot send a message to conversation $conversationid");
|
||||
@ -1939,7 +1939,7 @@ class api {
|
||||
$eventdata->courseid = 1;
|
||||
$eventdata->component = 'moodle';
|
||||
$eventdata->name = 'instantmessage';
|
||||
$eventdata->userfrom = $userid;
|
||||
$eventdata->userfrom = \core_user::get_user($userid);
|
||||
$eventdata->convid = $conversationid;
|
||||
|
||||
if ($format == FORMAT_HTML) {
|
||||
@ -1957,6 +1957,37 @@ class api {
|
||||
|
||||
$eventdata->timecreated = time();
|
||||
$eventdata->notification = 0;
|
||||
// Custom data for event.
|
||||
$customdata = [
|
||||
'actionbuttons' => [
|
||||
'send' => get_string('send', 'message'),
|
||||
],
|
||||
'placeholders' => [
|
||||
'send' => get_string('writeamessage', 'message'),
|
||||
],
|
||||
];
|
||||
|
||||
$conv = $DB->get_record('message_conversations', ['id' => $conversationid]);
|
||||
if ($conv->type == self::MESSAGE_CONVERSATION_TYPE_GROUP) {
|
||||
$convextrafields = self::get_linked_conversation_extra_fields([$conv]);
|
||||
// Conversation image.
|
||||
$imageurl = isset($convextrafields[$conv->id]) ? $convextrafields[$conv->id]['imageurl'] : null;
|
||||
if ($imageurl) {
|
||||
$customdata['notificationiconurl'] = $imageurl;
|
||||
}
|
||||
// Conversation name.
|
||||
if (is_null($conv->contextid)) {
|
||||
$convcontext = \context_user::instance($userid);
|
||||
} else {
|
||||
$convcontext = \context::instance_by_id($conv->contextid);
|
||||
}
|
||||
$customdata['conversationname'] = format_string($conv->name, true, ['context' => $convcontext]);
|
||||
} else if ($conv->type == self::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL) {
|
||||
$userpicture = new \user_picture($eventdata->userfrom);
|
||||
$customdata['notificationiconurl'] = $userpicture->get_url($PAGE)->out(false);
|
||||
}
|
||||
$eventdata->customdata = $customdata;
|
||||
|
||||
$messageid = message_send($eventdata);
|
||||
|
||||
$messagerecord = $DB->get_record('messages', ['id' => $messageid], 'id, useridfrom, fullmessage,
|
||||
@ -2578,7 +2609,7 @@ class api {
|
||||
* @return \stdClass the request
|
||||
*/
|
||||
public static function create_contact_request(int $userid, int $requesteduserid) : \stdClass {
|
||||
global $DB;
|
||||
global $DB, $PAGE;
|
||||
|
||||
$request = new \stdClass();
|
||||
$request->userid = $userid;
|
||||
@ -2609,6 +2640,15 @@ class api {
|
||||
$message->fullmessagehtml = $fullmessage;
|
||||
$message->smallmessage = '';
|
||||
$message->contexturl = $url->out(false);
|
||||
$userpicture = new \user_picture($userfrom);
|
||||
$userpicture->includetoken = $userto->id; // Generate an out-of-session token for the user receiving the message.
|
||||
$message->customdata = [
|
||||
'notificationiconurl' => $userpicture->get_url($PAGE)->out(false),
|
||||
'actionbuttons' => [
|
||||
'accept' => get_string_manager()->get_string('accept', 'moodle', null, $userto->lang),
|
||||
'reject' => get_string_manager()->get_string('reject', 'moodle', null, $userto->lang),
|
||||
],
|
||||
];
|
||||
|
||||
message_send($message);
|
||||
|
||||
|
@ -73,7 +73,8 @@ class provider implements
|
||||
'fullmessageformat' => 'privacy:metadata:messages:fullmessageformat',
|
||||
'fullmessagehtml' => 'privacy:metadata:messages:fullmessagehtml',
|
||||
'smallmessage' => 'privacy:metadata:messages:smallmessage',
|
||||
'timecreated' => 'privacy:metadata:messages:timecreated'
|
||||
'timecreated' => 'privacy:metadata:messages:timecreated',
|
||||
'customdata' => 'privacy:metadata:messages:customdata',
|
||||
],
|
||||
'privacy:metadata:messages'
|
||||
);
|
||||
@ -155,6 +156,7 @@ class provider implements
|
||||
'contexturlname' => 'privacy:metadata:notifications:contexturlname',
|
||||
'timeread' => 'privacy:metadata:notifications:timeread',
|
||||
'timecreated' => 'privacy:metadata:notifications:timecreated',
|
||||
'customdata' => 'privacy:metadata:notifications:customdata',
|
||||
],
|
||||
'privacy:metadata:notifications'
|
||||
);
|
||||
@ -930,7 +932,8 @@ class provider implements
|
||||
'issender' => transform::yesno($issender),
|
||||
'message' => message_format_message_text($message),
|
||||
'timecreated' => transform::datetime($message->timecreated),
|
||||
'timeread' => $timeread
|
||||
'timeread' => $timeread,
|
||||
'customdata' => $message->customdata,
|
||||
];
|
||||
if ($conversation->type == \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP && !$issender) {
|
||||
// Only export sender for group conversations when is not the current user.
|
||||
@ -1042,7 +1045,8 @@ class provider implements
|
||||
'contexturl' => $notification->contexturl,
|
||||
'contexturlname' => $notification->contexturlname,
|
||||
'timeread' => $timeread,
|
||||
'timecreated' => transform::datetime($notification->timecreated)
|
||||
'timecreated' => transform::datetime($notification->timecreated),
|
||||
'customdata' => $notification->customdata,
|
||||
];
|
||||
|
||||
$notificationdata[] = $data;
|
||||
|
@ -3208,7 +3208,12 @@ class core_message_external extends external_api {
|
||||
'timecreated' => new external_value(PARAM_INT, 'Time created'),
|
||||
'timeread' => new external_value(PARAM_INT, 'Time read'),
|
||||
'usertofullname' => new external_value(PARAM_TEXT, 'User to full name'),
|
||||
'userfromfullname' => new external_value(PARAM_TEXT, 'User from full name')
|
||||
'userfromfullname' => new external_value(PARAM_TEXT, 'User from full name'),
|
||||
'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'
|
||||
)
|
||||
),
|
||||
|
@ -325,7 +325,7 @@ function message_format_contexturl($message) {
|
||||
* @return int|false the ID of the new message or false
|
||||
*/
|
||||
function message_post_message($userfrom, $userto, $message, $format) {
|
||||
global $SITE, $CFG, $USER;
|
||||
global $PAGE;
|
||||
|
||||
$eventdata = new \core\message\message();
|
||||
$eventdata->courseid = 1;
|
||||
@ -351,6 +351,18 @@ function message_post_message($userfrom, $userto, $message, $format) {
|
||||
$eventdata->smallmessage = $message;//store the message unfiltered. Clean up on output.
|
||||
$eventdata->timecreated = time();
|
||||
$eventdata->notification = 0;
|
||||
// User image.
|
||||
$userpicture = new user_picture($userfrom);
|
||||
$userpicture->includetoken = $userto->id; // Generate an out-of-session token for the user receiving the message.
|
||||
$eventdata->customdata = [
|
||||
'notificationiconurl' => $userpicture->get_url($PAGE)->out(false),
|
||||
'actionbuttons' => [
|
||||
'send' => get_string_manager()->get_string('send', 'message', null, $eventdata->userto->lang),
|
||||
],
|
||||
'placeholders' => [
|
||||
'send' => get_string_manager()->get_string('writeamessage', 'message', null, $eventdata->userto->lang),
|
||||
],
|
||||
];
|
||||
return message_send($eventdata);
|
||||
}
|
||||
|
||||
|
@ -129,7 +129,7 @@ class message_airnotifier_external_testcase extends externallib_advanced_testcas
|
||||
$expected = array(
|
||||
array(
|
||||
'userid' => $user1->id,
|
||||
'configured' => 0
|
||||
'configured' => 1
|
||||
)
|
||||
);
|
||||
$this->assertEquals($expected, $preferences['users']);
|
||||
|
@ -72,7 +72,7 @@ class api {
|
||||
n.subject, n.fullmessage, n.fullmessageformat,
|
||||
n.fullmessagehtml, n.smallmessage, n.contexturl,
|
||||
n.contexturlname, n.timecreated, n.component,
|
||||
n.eventtype, n.timeread
|
||||
n.eventtype, n.timeread, n.customdata
|
||||
FROM {notifications} n
|
||||
WHERE n.id IN (SELECT notificationid FROM {message_popup_notifications})
|
||||
AND n.useridto = ?
|
||||
|
@ -165,6 +165,8 @@ class message_popup_external extends external_api {
|
||||
'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'
|
||||
)
|
||||
),
|
||||
|
@ -49,6 +49,7 @@ trait message_popup_test_helper {
|
||||
$record->fullmessage = $message;
|
||||
$record->smallmessage = $message;
|
||||
$record->timecreated = $timecreated ? $timecreated : time();
|
||||
$record->customdata = json_encode(['datakey' => 'data']);
|
||||
|
||||
$id = $DB->insert_record('notifications', $record);
|
||||
|
||||
|
@ -96,6 +96,15 @@ class message_popup_externallib_testcase extends advanced_testcase {
|
||||
$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->assertObjectHasAttribute('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);
|
||||
|
@ -4955,7 +4955,15 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
|
||||
$user1 = self::getDataGenerator()->create_user();
|
||||
$user2 = self::getDataGenerator()->create_user();
|
||||
|
||||
$sink = $this->redirectMessages();
|
||||
$request = \core_message\api::create_contact_request($user1->id, $user2->id);
|
||||
$messages = $sink->get_messages();
|
||||
$sink->close();
|
||||
// Test customdata.
|
||||
$customdata = json_decode($messages[0]->customdata);
|
||||
$this->assertObjectHasAttribute('notificationiconurl', $customdata);
|
||||
$this->assertObjectHasAttribute('actionbuttons', $customdata);
|
||||
$this->assertCount(2, (array) $customdata->actionbuttons);
|
||||
|
||||
$this->assertEquals($user1->id, $request->userid);
|
||||
$this->assertEquals($user2->id, $request->requesteduserid);
|
||||
@ -6007,8 +6015,17 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
|
||||
|
||||
// Send a message to an individual conversation.
|
||||
$sink = $this->redirectEvents();
|
||||
$messagessink = $this->redirectMessages();
|
||||
$message1 = \core_message\api::send_message_to_conversation($user1->id, $ic1->id, 'this is a message', FORMAT_MOODLE);
|
||||
$events = $sink->get_events();
|
||||
$messages = $messagessink->get_messages();
|
||||
// Test customdata.
|
||||
$customdata = json_decode($messages[0]->customdata);
|
||||
$this->assertObjectHasAttribute('notificationiconurl', $customdata);
|
||||
$this->assertObjectHasAttribute('actionbuttons', $customdata);
|
||||
$this->assertCount(1, (array) $customdata->actionbuttons);
|
||||
$this->assertObjectHasAttribute('placeholders', $customdata);
|
||||
$this->assertCount(1, (array) $customdata->placeholders);
|
||||
|
||||
// Verify the message returned.
|
||||
$this->assertInstanceOf(\stdClass::class, $message1);
|
||||
@ -6048,15 +6065,23 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
|
||||
|
||||
// Send a message to a group conversation.
|
||||
$sink = $this->redirectEvents();
|
||||
$messagessink = $this->redirectMessages();
|
||||
$message1 = \core_message\api::send_message_to_conversation($user1->id, $gc2->id, 'message to the group', FORMAT_MOODLE);
|
||||
$events = $sink->get_events();
|
||||
|
||||
$messages = $messagessink->get_messages();
|
||||
// Verify the message returned.
|
||||
$this->assertInstanceOf(\stdClass::class, $message1);
|
||||
$this->assertObjectHasAttribute('id', $message1);
|
||||
$this->assertAttributeEquals($user1->id, 'useridfrom', $message1);
|
||||
$this->assertAttributeEquals('message to the group', 'text', $message1);
|
||||
$this->assertObjectHasAttribute('timecreated', $message1);
|
||||
// Test customdata.
|
||||
$customdata = json_decode($messages[0]->customdata);
|
||||
$this->assertObjectHasAttribute('actionbuttons', $customdata);
|
||||
$this->assertCount(1, (array) $customdata->actionbuttons);
|
||||
$this->assertObjectHasAttribute('placeholders', $customdata);
|
||||
$this->assertCount(1, (array) $customdata->placeholders);
|
||||
$this->assertObjectNotHasAttribute('notificationiconurl', $customdata); // No group image means no image.
|
||||
|
||||
// Verify events. Note: the event is a message read event because of an if (PHPUNIT) conditional within message_send(),
|
||||
// however, we can still determine the number and ids of any recipients this way.
|
||||
@ -6067,6 +6092,66 @@ class core_message_api_testcase extends core_message_messagelib_testcase {
|
||||
$this->assertContains($user4->id, $userids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test verifying that messages can be sent to existing linked group conversations.
|
||||
*/
|
||||
public function test_send_message_to_conversation_linked_group_conversation() {
|
||||
global $CFG;
|
||||
|
||||
// Create some users.
|
||||
$user1 = self::getDataGenerator()->create_user();
|
||||
$user2 = self::getDataGenerator()->create_user();
|
||||
$user3 = self::getDataGenerator()->create_user();
|
||||
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
|
||||
// Create a group with a linked conversation and a valid image.
|
||||
$this->setAdminUser();
|
||||
$this->getDataGenerator()->enrol_user($user1->id, $course->id);
|
||||
$this->getDataGenerator()->enrol_user($user2->id, $course->id);
|
||||
$this->getDataGenerator()->enrol_user($user3->id, $course->id);
|
||||
$group = $this->getDataGenerator()->create_group([
|
||||
'courseid' => $course->id,
|
||||
'enablemessaging' => 1,
|
||||
'picturepath' => $CFG->dirroot . '/lib/tests/fixtures/gd-logo.png'
|
||||
]);
|
||||
|
||||
// Add users to group.
|
||||
$this->getDataGenerator()->create_group_member(array('groupid' => $group->id, 'userid' => $user1->id));
|
||||
$this->getDataGenerator()->create_group_member(array('groupid' => $group->id, 'userid' => $user2->id));
|
||||
|
||||
// Verify the group with the image works as expected.
|
||||
$conversations = \core_message\api::get_conversations($user1->id);
|
||||
$this->assertEquals(2, $conversations[0]->membercount);
|
||||
$this->assertEquals($course->shortname, $conversations[0]->subname);
|
||||
$groupimageurl = get_group_picture_url($group, $group->courseid, true);
|
||||
$this->assertEquals($groupimageurl, $conversations[0]->imageurl);
|
||||
|
||||
// Redirect messages.
|
||||
// This marks messages as read, but we can still observe and verify the number of conversation recipients,
|
||||
// based on the message_viewed events generated as part of marking the message as read for each user.
|
||||
$this->preventResetByRollback();
|
||||
$sink = $this->redirectMessages();
|
||||
|
||||
// Send a message to a group conversation.
|
||||
$messagessink = $this->redirectMessages();
|
||||
$message1 = \core_message\api::send_message_to_conversation($user1->id, $conversations[0]->id,
|
||||
'message to the group', FORMAT_MOODLE);
|
||||
$messages = $messagessink->get_messages();
|
||||
// Verify the message returned.
|
||||
$this->assertInstanceOf(\stdClass::class, $message1);
|
||||
$this->assertObjectHasAttribute('id', $message1);
|
||||
$this->assertAttributeEquals($user1->id, 'useridfrom', $message1);
|
||||
$this->assertAttributeEquals('message to the group', 'text', $message1);
|
||||
$this->assertObjectHasAttribute('timecreated', $message1);
|
||||
// Test customdata.
|
||||
$customdata = json_decode($messages[0]->customdata);
|
||||
$this->assertObjectHasAttribute('notificationiconurl', $customdata);
|
||||
$this->assertEquals($groupimageurl, $customdata->notificationiconurl);
|
||||
$this->assertEquals($group->name, $customdata->conversationname);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Test verifying that messages cannot be sent to conversations that don't exist.
|
||||
*/
|
||||
|
@ -1612,6 +1612,7 @@ class core_message_externallib_testcase extends externallib_advanced_testcase {
|
||||
$eventdata->fullmessageformat = FORMAT_PLAIN;
|
||||
$eventdata->fullmessagehtml = '<strong>Feedback submitted</strong>';
|
||||
$eventdata->smallmessage = '';
|
||||
$eventdata->customdata = ['datakey' => 'data'];
|
||||
message_send($eventdata);
|
||||
|
||||
$this->setUser($user1);
|
||||
@ -1644,6 +1645,10 @@ class core_message_externallib_testcase extends externallib_advanced_testcase {
|
||||
$messages = core_message_external::get_messages(0, $user1->id, 'notifications', true, true, 0, 0);
|
||||
$messages = external_api::clean_returnvalue(core_message_external::get_messages_returns(), $messages);
|
||||
$this->assertCount(1, $messages['messages']);
|
||||
// Check we receive custom data as a unserialisable json.
|
||||
$this->assertObjectHasAttribute('datakey', json_decode($messages['messages'][0]['customdata']));
|
||||
$this->assertEquals('mod_feedback', $messages['messages'][0]['component']);
|
||||
$this->assertEquals('submission', $messages['messages'][0]['eventtype']);
|
||||
|
||||
// Test warnings.
|
||||
$CFG->messaging = 0;
|
||||
|
@ -89,6 +89,7 @@ class core_message_privacy_provider_testcase extends \core_privacy\tests\provide
|
||||
$this->assertArrayHasKey('fullmessagehtml', $privacyfields);
|
||||
$this->assertArrayHasKey('smallmessage', $privacyfields);
|
||||
$this->assertArrayHasKey('timecreated', $privacyfields);
|
||||
$this->assertArrayHasKey('customdata', $privacyfields);
|
||||
$this->assertEquals('privacy:metadata:messages', $messagestable->get_summary());
|
||||
|
||||
$privacyfields = $messageuseractionstable->get_privacy_fields();
|
||||
@ -136,6 +137,7 @@ class core_message_privacy_provider_testcase extends \core_privacy\tests\provide
|
||||
$this->assertArrayHasKey('contexturlname', $privacyfields);
|
||||
$this->assertArrayHasKey('timeread', $privacyfields);
|
||||
$this->assertArrayHasKey('timecreated', $privacyfields);
|
||||
$this->assertArrayHasKey('customdata', $privacyfields);
|
||||
$this->assertEquals('privacy:metadata:notifications', $notificationstable->get_summary());
|
||||
}
|
||||
|
||||
@ -2724,6 +2726,7 @@ class core_message_privacy_provider_testcase extends \core_privacy\tests\provide
|
||||
$record->fullmessage = 'A rad message ' . $i;
|
||||
$record->smallmessage = 'A rad message ' . $i;
|
||||
$record->timecreated = $timecreated;
|
||||
$record->customdata = json_encode(['akey' => 'avalue']);
|
||||
|
||||
$i++;
|
||||
|
||||
@ -2763,6 +2766,7 @@ class core_message_privacy_provider_testcase extends \core_privacy\tests\provide
|
||||
$record->smallmessage = 'Yo homie, you got some stuff to do, yolo. ' . $i;
|
||||
$record->timeread = $timeread;
|
||||
$record->timecreated = $timecreated;
|
||||
$record->customdata = json_encode(['akey' => 'avalue']);
|
||||
|
||||
$i++;
|
||||
|
||||
|
@ -12,6 +12,13 @@ information provided here is intended especially for developers.
|
||||
* A new parameter 'mergeself' has been added to the methods \core_message\api::get_conversations() and
|
||||
core_message_external::get_conversations(), to decide whether the self-conversations should be included or not when the
|
||||
private ones are requested, to display them together.
|
||||
* A new 'customdata' field for both messages and notifications has been added. This new field can store any custom data
|
||||
serialised using json_encode().
|
||||
This new field can be used for storing any data not fitting in the current message structure. For example, it will be used
|
||||
to store additional information for the "Mobile notifications" processor.
|
||||
Existing external functions: core_message_get_messages and message_popup_get_popup_notifications has been udated to return the
|
||||
new field.
|
||||
* External function core_message_get_messages now returns the component and eventtype.
|
||||
|
||||
=== 3.6 ===
|
||||
|
||||
|
@ -6194,7 +6194,7 @@ class assign {
|
||||
$assignmentname,
|
||||
$blindmarking,
|
||||
$uniqueidforuser) {
|
||||
global $CFG;
|
||||
global $CFG, $PAGE;
|
||||
|
||||
$info = new stdClass();
|
||||
if ($blindmarking) {
|
||||
@ -6244,6 +6244,20 @@ class assign {
|
||||
$eventdata->notification = 1;
|
||||
$eventdata->contexturl = $info->url;
|
||||
$eventdata->contexturlname = $info->assignment;
|
||||
$customdata = [
|
||||
'cmid' => $coursemodule->id,
|
||||
'instance' => $coursemodule->instance,
|
||||
'messagetype' => $messagetype,
|
||||
'blindmarking' => $blindmarking,
|
||||
'uniqueidforuser' => $uniqueidforuser,
|
||||
];
|
||||
// Check if the userfrom is real and visible.
|
||||
if (!empty($userfrom->id) && core_user::is_real_user($userfrom->id)) {
|
||||
$userpicture = new user_picture($userfrom);
|
||||
$userpicture->includetoken = $userto->id; // Generate an out-of-session token for the user receiving the message.
|
||||
$customdata['notificationiconurl'] = $userpicture->get_url($PAGE)->out(false);
|
||||
}
|
||||
$eventdata->customdata = $customdata;
|
||||
|
||||
message_send($eventdata);
|
||||
}
|
||||
|
@ -1490,6 +1490,7 @@ class mod_assign_locallib_testcase extends advanced_testcase {
|
||||
}
|
||||
|
||||
public function test_cron() {
|
||||
global $PAGE;
|
||||
$this->resetAfterTest();
|
||||
|
||||
// First run cron so there are no messages waiting to be sent (from other tests).
|
||||
@ -1519,6 +1520,15 @@ class mod_assign_locallib_testcase extends advanced_testcase {
|
||||
$this->assertEquals(1, count($messages));
|
||||
$this->assertEquals(1, $messages[0]->notification);
|
||||
$this->assertEquals($assign->get_instance()->name, $messages[0]->contexturlname);
|
||||
// Test customdata.
|
||||
$customdata = json_decode($messages[0]->customdata);
|
||||
$this->assertEquals($assign->get_course_module()->id, $customdata->cmid);
|
||||
$this->assertEquals($assign->get_instance()->id, $customdata->instance);
|
||||
$this->assertEquals('feedbackavailable', $customdata->messagetype);
|
||||
$userpicture = new user_picture($teacher);
|
||||
$this->assertEquals($userpicture->get_url($PAGE)->out(false), $customdata->notificationiconurl);
|
||||
$this->assertEquals(0, $customdata->uniqueidforuser); // Not used in this case.
|
||||
$this->assertFalse($customdata->blindmarking);
|
||||
}
|
||||
|
||||
public function test_cron_without_notifications() {
|
||||
|
@ -2550,7 +2550,7 @@ function feedback_print_numeric_option_list() {
|
||||
* @return void
|
||||
*/
|
||||
function feedback_send_email($cm, $feedback, $course, $user, $completed = null) {
|
||||
global $CFG, $DB;
|
||||
global $CFG, $DB, $PAGE;
|
||||
|
||||
if ($feedback->email_notification == 0) { // No need to do anything
|
||||
return;
|
||||
@ -2617,6 +2617,10 @@ function feedback_send_email($cm, $feedback, $course, $user, $completed = null)
|
||||
$posthtml = '';
|
||||
}
|
||||
|
||||
$customdata = [
|
||||
'cmid' => $cm->id,
|
||||
'instance' => $feedback->id,
|
||||
];
|
||||
if ($feedback->anonymous == FEEDBACK_ANONYMOUS_NO) {
|
||||
$eventdata = new \core\message\message();
|
||||
$eventdata->courseid = $course->id;
|
||||
@ -2632,6 +2636,11 @@ function feedback_send_email($cm, $feedback, $course, $user, $completed = null)
|
||||
$eventdata->courseid = $course->id;
|
||||
$eventdata->contexturl = $info->url;
|
||||
$eventdata->contexturlname = $info->feedback;
|
||||
// User image.
|
||||
$userpicture = new user_picture($user);
|
||||
$userpicture->includetoken = $teacher->id; // Generate an out-of-session token for the user receiving the message.
|
||||
$customdata['notificationiconurl'] = $userpicture->get_url($PAGE)->out(false);
|
||||
$eventdata->customdata = $customdata;
|
||||
message_send($eventdata);
|
||||
} else {
|
||||
$eventdata = new \core\message\message();
|
||||
@ -2648,6 +2657,9 @@ function feedback_send_email($cm, $feedback, $course, $user, $completed = null)
|
||||
$eventdata->courseid = $course->id;
|
||||
$eventdata->contexturl = $info->url;
|
||||
$eventdata->contexturlname = $info->feedback;
|
||||
// Feedback icon if can be easily reachable.
|
||||
$customdata['notificationiconurl'] = ($cm instanceof cm_info) ? $cm->get_icon_url()->out() : '';
|
||||
$eventdata->customdata = $customdata;
|
||||
message_send($eventdata);
|
||||
}
|
||||
}
|
||||
@ -2710,6 +2722,12 @@ function feedback_send_email_anonym($cm, $feedback, $course) {
|
||||
$eventdata->courseid = $course->id;
|
||||
$eventdata->contexturl = $info->url;
|
||||
$eventdata->contexturlname = $info->feedback;
|
||||
$eventdata->customdata = [
|
||||
'cmid' => $cm->id,
|
||||
'instance' => $feedback->id,
|
||||
'notificationiconurl' => ($cm instanceof cm_info) ? $cm->get_icon_url()->out() : '', // Performance wise.
|
||||
];
|
||||
|
||||
message_send($eventdata);
|
||||
}
|
||||
}
|
||||
|
@ -54,7 +54,8 @@ class mod_feedback_external_testcase extends externallib_advanced_testcase {
|
||||
|
||||
// Setup test data.
|
||||
$this->course = $this->getDataGenerator()->create_course();
|
||||
$this->feedback = $this->getDataGenerator()->create_module('feedback', array('course' => $this->course->id));
|
||||
$this->feedback = $this->getDataGenerator()->create_module('feedback',
|
||||
array('course' => $this->course->id, 'email_notification' => 1));
|
||||
$this->context = context_module::instance($this->feedback->cmid);
|
||||
$this->cm = get_coursemodule_from_instance('feedback', $this->feedback->id);
|
||||
|
||||
@ -518,6 +519,7 @@ class mod_feedback_external_testcase extends externallib_advanced_testcase {
|
||||
$this->assertCount(7, $tmpitems); // 2 from the first page + 5 from the second page.
|
||||
|
||||
// And finally, save everything! We are going to modify one previous recorded value.
|
||||
$messagessink = $this->redirectMessages();
|
||||
$data[2]['value'] = 2; // 2 is value of the option 'b'.
|
||||
$secondpagedata = [$data[2], $data[3], $data[4], $data[5], $data[6]];
|
||||
$result = mod_feedback_external::process_page($this->feedback->id, 1, $secondpagedata);
|
||||
@ -540,6 +542,15 @@ class mod_feedback_external_testcase extends externallib_advanced_testcase {
|
||||
}
|
||||
$completed = $DB->get_record('feedback_completed', []);
|
||||
$this->assertEquals(0, $completed->courseid);
|
||||
|
||||
// Test notifications sent.
|
||||
$messages = $messagessink->get_messages();
|
||||
$messagessink->close();
|
||||
// Test customdata.
|
||||
$customdata = json_decode($messages[0]->customdata);
|
||||
$this->assertEquals($this->feedback->id, $customdata->instance);
|
||||
$this->assertEquals($this->feedback->cmid, $customdata->cmid);
|
||||
$this->assertObjectHasAttribute('notificationiconurl', $customdata);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -273,7 +273,7 @@ class send_user_notifications extends \core\task\adhoc_task {
|
||||
* @param \context $context
|
||||
*/
|
||||
protected function send_post($course, $forum, $discussion, $post, $cm, $context) {
|
||||
global $CFG;
|
||||
global $CFG, $PAGE;
|
||||
|
||||
$author = $this->get_post_author($post->userid, $course, $forum, $cm, $context);
|
||||
if (empty($author)) {
|
||||
@ -351,6 +351,19 @@ class send_user_notifications extends \core\task\adhoc_task {
|
||||
$contexturl = new \moodle_url('/mod/forum/discuss.php', ['d' => $discussion->id], "p{$post->id}");
|
||||
$eventdata->contexturl = $contexturl->out();
|
||||
$eventdata->contexturlname = $discussion->name;
|
||||
// User image.
|
||||
$userpicture = new \user_picture($author);
|
||||
$userpicture->includetoken = $this->recipient->id; // Generate an out-of-session token for the user receiving the message.
|
||||
$eventdata->customdata = [
|
||||
'cmid' => $cm->id,
|
||||
'instance' => $forum->id,
|
||||
'discussionid' => $discussion->id,
|
||||
'postid' => $post->id,
|
||||
'notificationiconurl' => $userpicture->get_url($PAGE)->out(false),
|
||||
'actionbuttons' => [
|
||||
'reply' => get_string_manager()->get_string('reply', 'forum', null, $eventdata->userto->lang),
|
||||
],
|
||||
];
|
||||
|
||||
return message_send($eventdata);
|
||||
}
|
||||
|
@ -26,6 +26,9 @@
|
||||
$messageproviders = array (
|
||||
// Ordinary single forum posts.
|
||||
'posts' => array(
|
||||
'defaults' => array(
|
||||
'airnotifier' => MESSAGE_PERMITTED + MESSAGE_DEFAULT_LOGGEDIN + MESSAGE_DEFAULT_LOGGEDOFF,
|
||||
),
|
||||
),
|
||||
|
||||
// Forum digest messages.
|
||||
|
@ -1496,4 +1496,47 @@ class mod_forum_mail_testcase extends advanced_testcase {
|
||||
$this->send_notifications_and_assert($recipient, [$post]);
|
||||
$this->send_notifications_and_assert($editor, [$post]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test notification comes with customdata.
|
||||
*/
|
||||
public function test_notification_customdata() {
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
|
||||
$options = array('course' => $course->id, 'forcesubscribe' => FORUM_FORCESUBSCRIBE);
|
||||
$forum = $this->getDataGenerator()->create_module('forum', $options);
|
||||
|
||||
list($author) = $this->helper_create_users($course, 1);
|
||||
list($commenter) = $this->helper_create_users($course, 1);
|
||||
|
||||
$strre = get_string('re', 'forum');
|
||||
|
||||
// New posts should not have Re: in the subject.
|
||||
list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
|
||||
$expect = [
|
||||
'author' => (object) [
|
||||
'userid' => $author->id,
|
||||
'messages' => 1,
|
||||
],
|
||||
'commenter' => (object) [
|
||||
'userid' => $commenter->id,
|
||||
'messages' => 1,
|
||||
],
|
||||
];
|
||||
$this->queue_tasks_and_assert($expect);
|
||||
|
||||
$this->send_notifications_and_assert($author, [$post]);
|
||||
$this->send_notifications_and_assert($commenter, [$post]);
|
||||
$messages = $this->messagesink->get_messages();
|
||||
$customdata = json_decode($messages[0]->customdata);
|
||||
$this->assertEquals($forum->id, $customdata->instance);
|
||||
$this->assertEquals($forum->cmid, $customdata->cmid);
|
||||
$this->assertEquals($post->id, $customdata->postid);
|
||||
$this->assertEquals($discussion->id, $customdata->discussionid);
|
||||
$this->assertObjectHasAttribute('notificationiconurl', $customdata);
|
||||
$this->assertObjectHasAttribute('actionbuttons', $customdata);
|
||||
$this->assertCount(1, (array) $customdata->actionbuttons);
|
||||
}
|
||||
}
|
||||
|
@ -27,6 +27,9 @@ $messageproviders = array (
|
||||
|
||||
// essay graded notification
|
||||
'graded_essay' => array (
|
||||
'defaults' => array(
|
||||
'airnotifier' => MESSAGE_PERMITTED + MESSAGE_DEFAULT_LOGGEDIN + MESSAGE_DEFAULT_LOGGEDOFF,
|
||||
),
|
||||
)
|
||||
|
||||
);
|
||||
|
@ -224,6 +224,7 @@ switch ($mode) {
|
||||
print_error('cannotfindanswer', 'lesson');
|
||||
}
|
||||
|
||||
$userpicture = new user_picture($USER);
|
||||
foreach ($attempts as $attempt) {
|
||||
$essayinfo = lesson_page_type_essay::extract_useranswer($attempt->useranswer);
|
||||
if ($essayinfo->graded && !$essayinfo->sent) {
|
||||
@ -261,6 +262,9 @@ switch ($mode) {
|
||||
$message = get_string('essayemailmessage2', 'lesson', $a);
|
||||
$plaintext = format_text_email($message, FORMAT_HTML);
|
||||
|
||||
$smallmessage = get_string('essayemailmessagesmall', 'lesson', $a);
|
||||
$smallmessage = format_text_email($smallmessage, FORMAT_HTML);
|
||||
|
||||
// Subject
|
||||
$subject = get_string('essayemailsubject', 'lesson');
|
||||
|
||||
@ -276,8 +280,15 @@ switch ($mode) {
|
||||
$eventdata->fullmessage = $plaintext;
|
||||
$eventdata->fullmessageformat = FORMAT_PLAIN;
|
||||
$eventdata->fullmessagehtml = $message;
|
||||
$eventdata->smallmessage = '';
|
||||
$eventdata->contexturl = $contexturl;
|
||||
$eventdata->smallmessage = $smallmessage;
|
||||
$eventdata->contexturl = $contexturl->out(false);
|
||||
$userpicture->includetoken = $attempt->userid; // Generate an out-of-session token for the destinatary.
|
||||
$eventdata->customdata = [
|
||||
'cmid' => $cm->id,
|
||||
'instance' => $lesson->id,
|
||||
'retake' => $lesson->id,
|
||||
'notificationiconurl' => $userpicture->get_url($PAGE)->out(false),
|
||||
];
|
||||
|
||||
// Required for messaging framework
|
||||
$eventdata->component = 'mod_lesson';
|
||||
|
@ -204,6 +204,7 @@ $string['eolstudentoutoftime'] = 'Attention: You ran out of time for this lesso
|
||||
$string['eolstudentoutoftimenoanswers'] = 'You did not answer any questions. You have received a 0 for this lesson.';
|
||||
$string['essay'] = 'Essay';
|
||||
$string['essayemailmessage2'] = '<p>Essay prompt: {$a->question}</p><p>Your response: <em>{$a->response}</em></p><p>Grader\'s comments: <em>{$a->comment}</em></p><p>You have received {$a->earned} out of {$a->outof} for this essay question.</p><p>Your grade for the {$a->lesson} lesson has been changed to {$a->newgrade}%.</p>';
|
||||
$string['essayemailmessagesmall'] = '<p>You have received {$a->earned} out of {$a->outof} for this essay question.</p><p>Your grade for the {$a->lesson} lesson has been changed to {$a->newgrade}%.</p>';
|
||||
$string['essayemailsubject'] = 'Grade available for lesson question';
|
||||
$string['essayresponses'] = 'Essay responses';
|
||||
$string['essays'] = 'Essays';
|
||||
|
@ -32,12 +32,18 @@ $messageproviders = array(
|
||||
|
||||
// Confirm a student's quiz attempt.
|
||||
'confirmation' => array(
|
||||
'capability' => 'mod/quiz:emailconfirmsubmission'
|
||||
'capability' => 'mod/quiz:emailconfirmsubmission',
|
||||
'defaults' => array(
|
||||
'airnotifier' => MESSAGE_PERMITTED + MESSAGE_DEFAULT_LOGGEDIN + MESSAGE_DEFAULT_LOGGEDOFF,
|
||||
),
|
||||
),
|
||||
|
||||
// Warning to the student that their quiz attempt is now overdue, if the quiz
|
||||
// has a grace period.
|
||||
'attempt_overdue' => array(
|
||||
'capability' => 'mod/quiz:emailwarnoverdue'
|
||||
'capability' => 'mod/quiz:emailwarnoverdue',
|
||||
'defaults' => array(
|
||||
'airnotifier' => MESSAGE_PERMITTED + MESSAGE_DEFAULT_LOGGEDIN + MESSAGE_DEFAULT_LOGGEDOFF,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -1544,6 +1544,11 @@ function quiz_send_confirmation($recipient, $a) {
|
||||
$eventdata->smallmessage = get_string('emailconfirmsmall', 'quiz', $a);
|
||||
$eventdata->contexturl = $a->quizurl;
|
||||
$eventdata->contexturlname = $a->quizname;
|
||||
$eventdata->customdata = [
|
||||
'cmid' => $a->quizcmid,
|
||||
'instance' => $a->quizid,
|
||||
'attemptid' => $a->attemptid,
|
||||
];
|
||||
|
||||
// ... and send it.
|
||||
return message_send($eventdata);
|
||||
@ -1558,6 +1563,7 @@ function quiz_send_confirmation($recipient, $a) {
|
||||
* @return int|false as for {@link message_send()}.
|
||||
*/
|
||||
function quiz_send_notification($recipient, $submitter, $a) {
|
||||
global $PAGE;
|
||||
|
||||
// Recipient info for template.
|
||||
$a->useridnumber = $recipient->idnumber;
|
||||
@ -1581,6 +1587,14 @@ function quiz_send_notification($recipient, $submitter, $a) {
|
||||
$eventdata->smallmessage = get_string('emailnotifysmall', 'quiz', $a);
|
||||
$eventdata->contexturl = $a->quizreviewurl;
|
||||
$eventdata->contexturlname = $a->quizname;
|
||||
$userpicture = new user_picture($submitter);
|
||||
$userpicture->includetoken = $recipient->id; // Generate an out-of-session token for the user receiving the message.
|
||||
$eventdata->customdata = [
|
||||
'cmid' => $a->quizcmid,
|
||||
'instance' => $a->quizid,
|
||||
'attemptid' => $a->attemptid,
|
||||
'notificationiconurl' => $userpicture->get_url($PAGE)->out(false),
|
||||
];
|
||||
|
||||
// ... and send it.
|
||||
return message_send($eventdata);
|
||||
@ -1649,12 +1663,15 @@ function quiz_send_notification_messages($course, $quiz, $attempt, $context, $cm
|
||||
format_string($quiz->name) . ' report</a>';
|
||||
$a->quizurl = $CFG->wwwroot . '/mod/quiz/view.php?id=' . $cm->id;
|
||||
$a->quizlink = '<a href="' . $a->quizurl . '">' . format_string($quiz->name) . '</a>';
|
||||
$a->quizid = $quiz->id;
|
||||
$a->quizcmid = $cm->id;
|
||||
// Attempt info.
|
||||
$a->submissiontime = userdate($attempt->timefinish);
|
||||
$a->timetaken = format_time($attempt->timefinish - $attempt->timestart);
|
||||
$a->quizreviewurl = $CFG->wwwroot . '/mod/quiz/review.php?attempt=' . $attempt->id;
|
||||
$a->quizreviewlink = '<a href="' . $a->quizreviewurl . '">' .
|
||||
format_string($quiz->name) . ' review</a>';
|
||||
$a->attemptid = $attempt->id;
|
||||
// Student who sat the quiz info.
|
||||
$a->studentidnumber = $submitter->idnumber;
|
||||
$a->studentname = fullname($submitter);
|
||||
@ -1748,6 +1765,11 @@ function quiz_send_overdue_message($attemptobj) {
|
||||
$eventdata->smallmessage = get_string('emailoverduesmall', 'quiz', $a);
|
||||
$eventdata->contexturl = $a->quizurl;
|
||||
$eventdata->contexturlname = $a->quizname;
|
||||
$eventdata->customdata = [
|
||||
'cmid' => $attemptobj->get_cmid(),
|
||||
'instance' => $attemptobj->get_quizid(),
|
||||
'attemptid' => $attemptobj->get_attemptid(),
|
||||
];
|
||||
|
||||
// Send the message.
|
||||
return message_send($eventdata);
|
||||
|
@ -95,6 +95,10 @@ class mod_quiz_external_testcase extends externallib_advanced_testcase {
|
||||
// Users enrolments.
|
||||
$this->studentrole = $DB->get_record('role', array('shortname' => 'student'));
|
||||
$this->teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
|
||||
// Allow student to receive messages.
|
||||
$coursecontext = context_course::instance($this->course->id);
|
||||
assign_capability('mod/quiz:emailnotifysubmission', CAP_ALLOW, $this->teacherrole->id, $coursecontext, true);
|
||||
|
||||
$this->getDataGenerator()->enrol_user($this->student->id, $this->course->id, $this->studentrole->id, 'manual');
|
||||
$this->getDataGenerator()->enrol_user($this->teacher->id, $this->course->id, $this->teacherrole->id, 'manual');
|
||||
}
|
||||
@ -1184,9 +1188,21 @@ class mod_quiz_external_testcase extends externallib_advanced_testcase {
|
||||
$this->assertTrue($result['questions'][1]['flagged']);
|
||||
|
||||
// Finish the attempt.
|
||||
$sink = $this->redirectMessages();
|
||||
$result = mod_quiz_external::process_attempt($attempt->id, array(), true);
|
||||
$result = external_api::clean_returnvalue(mod_quiz_external::process_attempt_returns(), $result);
|
||||
$this->assertEquals(quiz_attempt::FINISHED, $result['state']);
|
||||
$messages = $sink->get_messages();
|
||||
$message = reset($messages);
|
||||
$sink->close();
|
||||
// Test customdata.
|
||||
if (!empty($message->customdata)) {
|
||||
$customdata = json_decode($message->customdata);
|
||||
$this->assertEquals($quizobj->get_quizid(), $customdata->instance);
|
||||
$this->assertEquals($quizobj->get_cmid(), $customdata->cmid);
|
||||
$this->assertEquals($attempt->id, $customdata->attemptid);
|
||||
$this->assertObjectHasAttribute('notificationiconurl', $customdata);
|
||||
}
|
||||
|
||||
// Start new attempt.
|
||||
$quba = question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj->get_context());
|
||||
|
@ -29,7 +29,7 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$version = 2019042300.01; // YYYYMMDD = weekly release date of this DEV branch.
|
||||
$version = 2019042300.03; // YYYYMMDD = weekly release date of this DEV branch.
|
||||
// RR = release increments - 00 in DEV branches.
|
||||
// .XX = incremental changes.
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user