Skip to content
Permalink
Browse files
Merge branch 'MDL-60680-master' of git://github.com/jleyva/moodle
  • Loading branch information
David Monllaó committed Apr 25, 2019
2 parents 2fbc2e4 + a129ba0 commit b63c0b907980e43431675c867b57fdf2fdedc113
Showing with 556 additions and 36 deletions.
  1. +17 −1 badges/tests/badgeslib_test.php
  2. +13 −1 competency/lib.php
  3. +12 −2 competency/tests/lib_test.php
  4. +2 −0 lang/en/message.php
  5. +1 −0 lang/en/moodle.php
  6. +10 −0 lib/badgeslib.php
  7. +8 −0 lib/classes/message/manager.php
  8. +28 −3 lib/classes/message/message.php
  9. +3 −0 lib/db/install.xml
  10. +11 −2 lib/db/messages.php
  11. +30 −0 lib/db/upgrade.php
  12. +3 −2 lib/filelib.php
  13. +2 −0 lib/messagelib.php
  14. +2 −1 lib/outputcomponents.php
  15. +1 −1 lib/outputrenderers.php
  16. +13 −0 lib/tests/filelib_test.php
  17. +7 −0 lib/tests/messagelib_test.php
  18. +13 −1 lib/tests/outputcomponents_test.php
  19. +6 −0 lib/upgrade.txt
  20. +9 −2 lib/weblib.php
  21. +43 −3 message/classes/api.php
  22. +7 −3 message/classes/privacy/provider.php
  23. +6 −1 message/externallib.php
  24. +13 −1 message/lib.php
  25. +1 −1 message/output/airnotifier/tests/externallib_test.php
  26. +1 −1 message/output/popup/classes/api.php
  27. +2 −0 message/output/popup/externallib.php
  28. +1 −0 message/output/popup/tests/base.php
  29. +9 −0 message/output/popup/tests/externallib_test.php
  30. +86 −1 message/tests/api_test.php
  31. +5 −0 message/tests/externallib_test.php
  32. +4 −0 message/tests/privacy_provider_test.php
  33. +7 −0 message/upgrade.txt
  34. +15 −1 mod/assign/locallib.php
  35. +10 −0 mod/assign/tests/locallib_test.php
  36. +19 −1 mod/feedback/lib.php
  37. +12 −1 mod/feedback/tests/external_test.php
  38. +14 −1 mod/forum/classes/task/send_user_notifications.php
  39. +3 −0 mod/forum/db/messages.php
  40. +43 −0 mod/forum/tests/mail_test.php
  41. +3 −0 mod/lesson/db/messages.php
  42. +13 −2 mod/lesson/essay.php
  43. +1 −0 mod/lesson/lang/en/lesson.php
  44. +8 −2 mod/quiz/db/messages.php
  45. +22 −0 mod/quiz/locallib.php
  46. +16 −0 mod/quiz/tests/external_test.php
  47. +1 −1 version.php
@@ -290,13 +290,29 @@ public function test_delete_badge_criteria() {
}

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 @@
* @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 @@
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 @@ public function test_comment_add_user_competency() {
$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 @@ public function test_comment_add_plan() {
$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: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: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 @@ public static function send_message_to_conversation(message $eventdata, \stdClas

// 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 @@
* 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 @@ protected function get_smallmessage($processorname = '') {
}
}

/**
* 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 @@ public function __get($prop) {
* @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,19 +72,26 @@

// 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.
'badgerecipientnotice' => 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 @@
'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 @@
'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;

0 comments on commit b63c0b9

Please sign in to comment.