diff --git a/lib/db/services.php b/lib/db/services.php index 869a22f5c35..708f2ccdb80 100644 --- a/lib/db/services.php +++ b/lib/db/services.php @@ -1407,6 +1407,16 @@ $functions = array( 'ajax' => true, 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE), ), + 'core_message_delete_message_for_all_users' => array( + 'classname' => 'core_message_external', + 'methodname' => 'delete_message_for_all_users', + 'classpath' => 'message/externallib.php', + 'description' => 'Deletes a message for all users.', + 'type' => 'write', + 'capabilities' => 'moodle/site:deleteanymessage', + 'ajax' => true, + 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE), + ), 'core_notes_create_notes' => array( 'classname' => 'core_notes_external', 'methodname' => 'create_notes', diff --git a/message/externallib.php b/message/externallib.php index 4f23efa9425..0bddf88513b 100644 --- a/message/externallib.php +++ b/message/externallib.php @@ -159,6 +159,9 @@ class core_message_external extends external_api { self::validate_context($context); require_capability('moodle/site:sendmessage', $context); + // Ensure the current user is allowed to delete message for everyone. + $candeletemessagesforallusers = has_capability('moodle/site:deleteanymessage', $context); + $params = self::validate_parameters(self::send_instant_messages_parameters(), array('messages' => $messages)); //retrieve all tousers of the messages @@ -205,6 +208,7 @@ class core_message_external extends external_api { if ($success) { $resultmsg['msgid'] = $success; $resultmsg['timecreated'] = time(); + $resultmsg['candeletemessagesforallusers'] = $candeletemessagesforallusers; $messageids[] = $success; } else { // WARNINGS: for backward compatibility we return this errormessage. @@ -257,6 +261,8 @@ class core_message_external extends external_api { 'timecreated' => new external_value(PARAM_INT, 'The timecreated timestamp for the message', VALUE_OPTIONAL), 'conversationid' => new external_value(PARAM_INT, 'The conversation id for this message', VALUE_OPTIONAL), 'useridfrom' => new external_value(PARAM_INT, 'The user id who sent the message', VALUE_OPTIONAL), + 'candeletemessagesforallusers' => new external_value(PARAM_BOOL, + 'If the user can delete messages in the conversation for all users', VALUE_DEFAULT, false), ) ) ); @@ -1260,6 +1266,8 @@ class core_message_external extends external_api { 'messages' => new external_multiple_structure( self::get_conversation_message_structure() ), + 'candeletemessagesforallusers' => new external_value(PARAM_BOOL, + 'If the user can delete messages in the conversation for all users', VALUE_DEFAULT, false), ) ); } @@ -4806,4 +4814,68 @@ class core_message_external extends external_api { ] ); } + + /** + * Returns description of method parameters + * + * @return external_function_parameters + * @since 3.7 + */ + public static function delete_message_for_all_users_parameters() { + return new external_function_parameters( + array( + 'messageid' => new external_value(PARAM_INT, 'The message id'), + 'userid' => new external_value(PARAM_INT, 'The user id of who we want to delete the message for all users') + ) + ); + } + /** + * Deletes a message for all users + * + * @param int $messageid the message id + * @param int $userid the user id of who we want to delete the message for all users + * @return external_description + * @throws moodle_exception + * @since 3.7 + */ + public static function delete_message_for_all_users(int $messageid, int $userid) { + global $CFG; + + // Check if private messaging between users is allowed. + if (empty($CFG->messaging)) { + throw new moodle_exception('disabled', 'message'); + } + + // Validate params. + $params = array( + 'messageid' => $messageid, + 'userid' => $userid + ); + $params = self::validate_parameters(self::delete_message_for_all_users_parameters(), $params); + + // Validate context. + $context = context_system::instance(); + self::validate_context($context); + + $user = core_user::get_user($params['userid'], '*', MUST_EXIST); + core_user::require_active_user($user); + + // Checks if a user can delete a message for all users. + if (core_message\api::can_delete_message_for_all_users($user->id, $params['messageid'])) { + \core_message\api::delete_message_for_all_users($params['messageid']); + } else { + throw new moodle_exception('You do not have permission to delete this message for everyone.'); + } + + return []; + } + /** + * Returns description of method result value + * + * @return external_description + * @since 3.7 + */ + public static function delete_message_for_all_users_returns() { + return new external_warnings(); + } } diff --git a/message/tests/externallib_test.php b/message/tests/externallib_test.php index 5099995bafb..1c9ed72e224 100644 --- a/message/tests/externallib_test.php +++ b/message/tests/externallib_test.php @@ -7102,4 +7102,164 @@ class core_message_externallib_testcase extends externallib_advanced_testcase { $this->assertEquals($expectedunreadcounts['types'][\core_message\api::MESSAGE_CONVERSATION_TYPE_SELF], $counts['types'][\core_message\api::MESSAGE_CONVERSATION_TYPE_SELF]); } + + /** + * Test delete_message for all users. + */ + public function test_delete_message_for_all_users() { + global $DB; + + $this->resetAfterTest(true); + + // Create fake data to test it. + list($user1, $user2, $user3, $convgroup, $convindividual) = $this->create_delete_message_test_data(); + + // Send message as user1 to group conversation. + $messageid1 = testhelper::send_fake_message_to_conversation($user1, $convgroup->id); + $messageid2 = testhelper::send_fake_message_to_conversation($user2, $convgroup->id); + + // User1 deletes the first message for all users of group conversation. + // First, we have to allow user1 (Teacher) can delete messages for all users. + $editingteacher = $DB->get_record('role', ['shortname' => 'editingteacher']); + assign_capability('moodle/site:deleteanymessage', CAP_ALLOW, $editingteacher->id, context_system::instance()); + + $this->setUser($user1); + + // Now, user1 deletes message for all users. + $return = core_message_external::delete_message_for_all_users($messageid1, $user1->id); + $return = external_api::clean_returnvalue(core_message_external::delete_message_for_all_users_returns(), $return); + // Check if everything is ok. + $this->assertEquals(array(), $return); + + // Check we have 3 records on message_user_actions with the mark MESSAGE_ACTION_DELETED. + $muas = $DB->get_records('message_user_actions', array('messageid' => $messageid1), 'userid ASC'); + $this->assertCount(3, $muas); + $mua1 = array_shift($muas); + $mua2 = array_shift($muas); + $mua3 = array_shift($muas); + + $this->assertEquals($user1->id, $mua1->userid); + $this->assertEquals($messageid1, $mua1->messageid); + $this->assertEquals(\core_message\api::MESSAGE_ACTION_DELETED, $mua1->action); + $this->assertEquals($user2->id, $mua2->userid); + $this->assertEquals($messageid1, $mua2->messageid); + $this->assertEquals(\core_message\api::MESSAGE_ACTION_DELETED, $mua2->action); + $this->assertEquals($user3->id, $mua3->userid); + $this->assertEquals($messageid1, $mua3->messageid); + $this->assertEquals(\core_message\api::MESSAGE_ACTION_DELETED, $mua3->action); + } + + /** + * Test delete_message for all users with messaging disabled. + */ + public function test_delete_message_for_all_users_messaging_disabled() { + global $CFG; + + $this->resetAfterTest(); + + // Create fake data to test it. + list($user1, $user2, $user3, $convgroup, $convindividual) = $this->create_delete_message_test_data(); + + // Send message as user1 to group conversation. + $messageid = testhelper::send_fake_message_to_conversation($user1, $convgroup->id); + + $this->setUser($user1); + + // Disable messaging. + $CFG->messaging = 0; + + // Ensure an exception is thrown. + $this->expectException('moodle_exception'); + core_message_external::delete_message_for_all_users($messageid, $user1->id); + } + + /** + * Test delete_message for all users with no permission. + */ + public function test_delete_message_for_all_users_no_permission() { + $this->resetAfterTest(); + + // Create fake data to test it. + list($user1, $user2, $user3, $convgroup, $convindividual) = $this->create_delete_message_test_data(); + + // Send message as user1 to group conversation. + $messageid = testhelper::send_fake_message_to_conversation($user1, $convgroup->id); + + $this->setUser($user2); + + // Try as user2 to delete a message for all users without permission to do it. + $this->expectException('moodle_exception'); + $this->expectExceptionMessage('You do not have permission to delete this message for everyone.'); + core_message_external::delete_message_for_all_users($messageid, $user2->id); + } + + /** + * Test delete_message for all users in a private conversation. + */ + public function test_delete_message_for_all_users_private_conversation() { + global $DB; + + $this->resetAfterTest(); + + // Create fake data to test it. + list($user1, $user2, $user3, $convgroup, $convindividual) = $this->create_delete_message_test_data(); + + // Send message as user1 to private conversation. + $messageid = testhelper::send_fake_message_to_conversation($user1, $convindividual->id); + + // First, we have to allow user1 (Teacher) can delete messages for all users. + $editingteacher = $DB->get_record('role', ['shortname' => 'editingteacher']); + assign_capability('moodle/site:deleteanymessage', CAP_ALLOW, $editingteacher->id, context_system::instance()); + + $this->setUser($user1); + + // Try as user1 to delete a private message for all users on individual conversation. + // User1 should not delete message for all users in a private conversations despite being a teacher. + // Because is a teacher in a course and not in a system context. + $this->expectException('moodle_exception'); + $this->expectExceptionMessage('You do not have permission to delete this message for everyone.'); + core_message_external::delete_message_for_all_users($messageid, $user1->id); + } + + /** + * Helper to seed the database with initial state with data. + */ + protected function create_delete_message_test_data() { + // Create some users. + $user1 = self::getDataGenerator()->create_user(); + $user2 = self::getDataGenerator()->create_user(); + $user3 = self::getDataGenerator()->create_user(); + + // Create a course and enrol the users. + $course = $this->getDataGenerator()->create_course(); + $coursecontext = context_course::instance($course->id); + $this->getDataGenerator()->enrol_user($user1->id, $course->id, 'editingteacher'); + $this->getDataGenerator()->enrol_user($user2->id, $course->id, 'student'); + $this->getDataGenerator()->enrol_user($user3->id, $course->id, 'student'); + + // Create a group and added the users into. + $group1 = $this->getDataGenerator()->create_group(array('courseid' => $course->id)); + groups_add_member($group1->id, $user1->id); + groups_add_member($group1->id, $user2->id); + groups_add_member($group1->id, $user3->id); + + // Create a group conversation linked with the course. + $convgroup = \core_message\api::create_conversation( + \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP, + [$user1->id, $user2->id, $user3->id], + 'Group test delete for everyone', \core_message\api::MESSAGE_CONVERSATION_ENABLED, + 'core_group', + 'groups', + $group1->id, + context_course::instance($course->id)->id + ); + + // Create and individual conversation. + $convindividual = \core_message\api::create_conversation( + \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, + [$user1->id, $user2->id] + ); + + return [$user1, $user2, $user3, $convgroup, $convindividual]; + } } diff --git a/version.php b/version.php index a7b5515d297..9dbdfb4b4f6 100644 --- a/version.php +++ b/version.php @@ -29,7 +29,7 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2019050300.00; // YYYYMMDD = weekly release date of this DEV branch. +$version = 2019050300.01; // YYYYMMDD = weekly release date of this DEV branch. // RR = release increments - 00 in DEV branches. // .XX = incremental changes.