From 0a063e6dea06f84f3f10dd708a47bec3af414349 Mon Sep 17 00:00:00 2001 From: Juan Leyva Date: Wed, 18 Sep 2019 17:18:23 +0200 Subject: [PATCH] MDL-64254 mod_forum: New WS mod_forum_update_discussion_post --- mod/forum/db/services.php | 8 ++ mod/forum/externallib.php | 204 +++++++++++++++++++++++++++++++++++ mod/forum/lib.php | 39 +++++++ mod/forum/post.php | 25 +---- mod/forum/tests/externallib_test.php | 166 ++++++++++++++++++++++++++++ mod/forum/version.php | 2 +- 6 files changed, 419 insertions(+), 25 deletions(-) diff --git a/mod/forum/db/services.php b/mod/forum/db/services.php index 98103df7983..b0f2f328603 100644 --- a/mod/forum/db/services.php +++ b/mod/forum/db/services.php @@ -199,4 +199,12 @@ $functions = array( 'type' => 'write', 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE) ), + 'mod_forum_update_discussion_post' => array( + 'classname' => 'mod_forum_external', + 'methodname' => 'update_discussion_post', + 'classpath' => 'mod/forum/externallib.php', + 'description' => 'Updates a post or a discussion topic post.', + 'type' => 'write', + 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE) + ), ); diff --git a/mod/forum/externallib.php b/mod/forum/externallib.php index bab13ed1cf6..c3570c863ec 100644 --- a/mod/forum/externallib.php +++ b/mod/forum/externallib.php @@ -2394,4 +2394,208 @@ class mod_forum_external extends external_api { ) ); } + + /** + * Returns description of method parameters + * + * @return external_function_parameters + * @since Moodle 3.8 + */ + public static function update_discussion_post_parameters() { + return new external_function_parameters( + [ + 'postid' => new external_value(PARAM_INT, 'Post to be updated. It can be a discussion topic post.'), + 'subject' => new external_value(PARAM_TEXT, 'Updated post subject', VALUE_DEFAULT, ''), + 'message' => new external_value(PARAM_RAW, 'Updated post message (HTML assumed if messageformat is not provided)', + VALUE_DEFAULT, ''), + 'messageformat' => new external_format_value('message', VALUE_DEFAULT), + 'options' => new external_multiple_structure ( + new external_single_structure( + [ + 'name' => new external_value( + PARAM_ALPHANUM, + 'The allowed keys (value format) are: + pinned (bool); (only for discussions) whether to pin this discussion or not + discussionsubscribe (bool); whether to subscribe to the post or not + inlineattachmentsid (int); the draft file area id for inline attachments in the text + attachmentsid (int); the draft file area id for attachments' + ), + 'value' => new external_value(PARAM_RAW, 'The value of the option.') + ] + ), + 'Configuration options for the post.', + VALUE_DEFAULT, + [] + ), + ] + ); + } + + /** + * Updates a post or a discussion post topic. + * + * @param int $postid post to be updated, it can be a discussion topic post. + * @param string $subject updated post subject + * @param string $message updated post message (HTML assumed if messageformat is not provided) + * @param int $messageformat The format of the message, defaults to FORMAT_HTML + * @param array $options different configuration options for the post to be updated. + * @return array of warnings and the status (true if the post/discussion was deleted) + * @since Moodle 3.8 + * @throws moodle_exception + * @todo support more options: timed posts, groups change and tags. + */ + public static function update_discussion_post($postid, $subject = '', $message = '', $messageformat = FORMAT_HTML, + $options = []) { + global $CFG, $USER; + require_once($CFG->dirroot . "/mod/forum/lib.php"); + + $params = self::validate_parameters(self::add_discussion_post_parameters(), + [ + 'postid' => $postid, + 'subject' => $subject, + 'message' => $message, + 'options' => $options, + 'messageformat' => $messageformat, + ] + ); + $warnings = []; + + // Validate options. + $options = []; + foreach ($params['options'] as $option) { + $name = trim($option['name']); + switch ($name) { + case 'pinned': + $value = clean_param($option['value'], PARAM_BOOL); + break; + case 'discussionsubscribe': + $value = clean_param($option['value'], PARAM_BOOL); + break; + case 'inlineattachmentsid': + $value = clean_param($option['value'], PARAM_INT); + break; + case 'attachmentsid': + $value = clean_param($option['value'], PARAM_INT); + break; + default: + throw new moodle_exception('errorinvalidparam', 'webservice', '', $name); + } + $options[$name] = $value; + } + + $managerfactory = mod_forum\local\container::get_manager_factory(); + $vaultfactory = mod_forum\local\container::get_vault_factory(); + $forumvault = $vaultfactory->get_forum_vault(); + $discussionvault = $vaultfactory->get_discussion_vault(); + $postvault = $vaultfactory->get_post_vault(); + $legacydatamapperfactory = mod_forum\local\container::get_legacy_data_mapper_factory(); + $forumdatamapper = $legacydatamapperfactory->get_forum_data_mapper(); + $discussiondatamapper = $legacydatamapperfactory->get_discussion_data_mapper(); + $postdatamapper = $legacydatamapperfactory->get_post_data_mapper(); + + $postentity = $postvault->get_from_id($params['postid']); + if (empty($postentity)) { + throw new moodle_exception('invalidpostid', 'forum'); + } + $discussionentity = $discussionvault->get_from_id($postentity->get_discussion_id()); + if (empty($discussionentity)) { + throw new moodle_exception('notpartofdiscussion', 'forum'); + } + $forumentity = $forumvault->get_from_id($discussionentity->get_forum_id()); + if (empty($forumentity)) { + throw new moodle_exception('invalidforumid', 'forum'); + } + $forum = $forumdatamapper->to_legacy_object($forumentity); + $capabilitymanager = $managerfactory->get_capability_manager($forumentity); + + $modcontext = $forumentity->get_context(); + self::validate_context($modcontext); + + if (!$capabilitymanager->can_edit_post($USER, $discussionentity, $postentity)) { + throw new moodle_exception('cannotupdatepost', 'forum'); + } + + // Get the original post. + $updatepost = $postdatamapper->to_legacy_object($postentity); + $updatepost->itemid = IGNORE_FILE_MERGE; + $updatepost->attachments = IGNORE_FILE_MERGE; + + // Prepare the post to be updated. + if (!empty($params['subject'])) { + $updatepost->subject = $params['subject']; + } + + if (!empty($params['message']) && !empty($params['messageformat'])) { + $updatepost->message = $params['message']; + $updatepost->messageformat = $params['messageformat']; + $updatepost->messagetrust = trusttext_trusted($modcontext); + // Clean message text. + $updatepost = trusttext_pre_edit($updatepost, 'message', $modcontext); + } + + if (isset($options['discussionsubscribe'])) { + // No need to validate anything here, forum_post_subscription will do. + $updatepost->discussionsubscribe = $options['discussionsubscribe']; + } + + // When editing first post/discussion. + if (!$postentity->has_parent()) { + // Defaults for discussion topic posts. + $updatepost->name = $discussionentity->get_name(); + $updatepost->timestart = $discussionentity->get_time_start(); + $updatepost->timeend = $discussionentity->get_time_end(); + + if (isset($options['pinned'])) { + if ($capabilitymanager->can_pin_discussions($USER)) { + // Can change pinned if we have capability. + $updatepost->pinned = !empty($options['pinned']) ? FORUM_DISCUSSION_PINNED : FORUM_DISCUSSION_UNPINNED; + } + } + } + + if (isset($options['inlineattachmentsid'])) { + $updatepost->itemid = $options['inlineattachmentsid']; + } + + if (isset($options['attachmentsid']) && forum_can_create_attachment($forum, $modcontext)) { + $updatepost->attachments = $options['attachmentsid']; + } + + // Update the post. + $fakemform = $updatepost->id; + if (forum_update_post($updatepost, $fakemform)) { + $discussion = $discussiondatamapper->to_legacy_object($discussionentity); + + forum_trigger_post_updated_event($updatepost, $discussion, $modcontext, $forum); + + forum_post_subscription( + $updatepost, + $forum, + $discussion + ); + $status = true; + } else { + $status = false; + } + + return [ + 'status' => $status, + 'warnings' => $warnings, + ]; + } + + /** + * Returns description of method result value + * + * @return external_description + * @since Moodle 3.8 + */ + public static function update_discussion_post_returns() { + return new external_single_structure( + [ + 'status' => new external_value(PARAM_BOOL, 'True if the post/discussion was updated, false otherwise.'), + 'warnings' => new external_warnings() + ] + ); + } } diff --git a/mod/forum/lib.php b/mod/forum/lib.php index 546c5079189..d2ebfb21701 100644 --- a/mod/forum/lib.php +++ b/mod/forum/lib.php @@ -2975,6 +2975,38 @@ function forum_add_new_post($post, $mform, $unused = null) { } /** + * Trigger post updated event. + * + * @param object $post forum post object + * @param object $discussion discussion object + * @param object $context forum context object + * @param object $forum forum object + * @since Moodle 3.8 + * @return void + */ +function forum_trigger_post_updated_event($post, $discussion, $context, $forum) { + global $USER; + + $params = array( + 'context' => $context, + 'objectid' => $post->id, + 'other' => array( + 'discussionid' => $discussion->id, + 'forumid' => $forum->id, + 'forumtype' => $forum->type, + ) + ); + + if ($USER->id !== $post->userid) { + $params['relateduserid'] = $post->userid; + } + + $event = \mod_forum\event\post_updated::create($params); + $event->add_record_snapshot('forum_discussions', $discussion); + $event->trigger(); +} + +/** * Update a post. * * @param stdClass $newpost The post to update @@ -3027,6 +3059,13 @@ function forum_update_post($newpost, $mform, $unused = null) { forum_add_attachment($post, $forum, $cm, $mform); + if ($forum->type == 'single' && $post->parent == '0') { + // Updating first post of single discussion type -> updating forum intro. + $forum->intro = $post->message; + $forum->timemodified = time(); + $DB->update_record("forum", $forum); + } + if (isset($newpost->tags)) { core_tag_tag::set_item_tags('mod_forum', 'forum_posts', $post->id, $context, $newpost->tags); } diff --git a/mod/forum/post.php b/mod/forum/post.php index 2b9501392b5..d265d9af80b 100644 --- a/mod/forum/post.php +++ b/mod/forum/post.php @@ -846,12 +846,7 @@ if ($mformpost->is_cancelled()) { print_error("couldnotupdate", "forum", $errordestination); } - if ('single' == $forumentity->get_type() && !$postentity->has_parent()) { - // Updating first post of single discussion type -> updating forum intro. - $forum->intro = $updatepost->message; - $forum->timemodified = time(); - $DB->update_record("forum", $forum); - } + forum_trigger_post_updated_event($post, $discussion, $modcontext, $forum); if ($USER->id === $postentity->get_author_id()) { $message .= get_string("postupdated", "forum"); @@ -869,24 +864,6 @@ if ($mformpost->is_cancelled()) { $discussionurl = $urlfactory->get_view_post_url_from_post($postentity); } - $params = array( - 'context' => $modcontext, - 'objectid' => $fromform->id, - 'other' => array( - 'discussionid' => $discussion->id, - 'forumid' => $forum->id, - 'forumtype' => $forum->type, - ) - ); - - if ($USER->id !== $postentity->get_author_id()) { - $params['relateduserid'] = $postentity->get_author_id(); - } - - $event = \mod_forum\event\post_updated::create($params); - $event->add_record_snapshot('forum_discussions', $discussion); - $event->trigger(); - redirect( forum_go_back_to($discussionurl), $message . $subscribemessage, diff --git a/mod/forum/tests/externallib_test.php b/mod/forum/tests/externallib_test.php index 7a52a209140..3857f862318 100644 --- a/mod/forum/tests/externallib_test.php +++ b/mod/forum/tests/externallib_test.php @@ -2781,4 +2781,170 @@ class mod_forum_external_testcase extends externallib_advanced_testcase { $this->assertEquals($post->message, $result['messagetext']); } + /** + * Test update_discussion_post with a discussion. + */ + public function test_update_discussion_post_discussion() { + global $DB, $USER; + $this->resetAfterTest(true); + // Setup test data. + $course = $this->getDataGenerator()->create_course(); + $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id)); + + $this->setAdminUser(); + + // Add a discussion. + $record = new stdClass(); + $record->course = $course->id; + $record->userid = $USER->id; + $record->forum = $forum->id; + $record->pinned = FORUM_DISCUSSION_UNPINNED; + $discussion = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); + + $subject = 'Hey subject updated'; + $message = 'Hey message updated'; + $messageformat = FORMAT_HTML; + $options = [ + ['name' => 'pinned', 'value' => true], + ]; + + $result = mod_forum_external::update_discussion_post($discussion->firstpost, $subject, $message, $messageformat, + $options); + $result = external_api::clean_returnvalue(mod_forum_external::update_discussion_post_returns(), $result); + $this->assertTrue($result['status']); + + // Get the post from WS. + $result = mod_forum_external::get_discussion_post($discussion->firstpost); + $result = external_api::clean_returnvalue(mod_forum_external::get_discussion_post_returns(), $result); + $this->assertEquals($subject, $result['post']['subject']); + $this->assertEquals($message, $result['post']['message']); + $this->assertEquals($messageformat, $result['post']['messageformat']); + + // Get discussion object from DB. + $discussion = $DB->get_record('forum_discussions', ['id' => $discussion->id]); + $this->assertEquals($subject, $discussion->name); // Check discussion subject. + $this->assertEquals(FORUM_DISCUSSION_PINNED, $discussion->pinned); // Check discussion pinned. + } + + /** + * Test update_discussion_post with a post. + */ + public function test_update_discussion_post_post() { + global $DB, $USER; + $this->resetAfterTest(true); + // Setup test data. + $course = $this->getDataGenerator()->create_course(); + $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id)); + $cm = get_coursemodule_from_id('forum', $forum->cmid, 0, false, MUST_EXIST); + $user = $this->getDataGenerator()->create_user(); + $role = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST); + self::getDataGenerator()->enrol_user($user->id, $course->id, $role->id); + + $this->setUser($user); + // Enable auto subscribe discussion. + $USER->autosubscribe = true; + + // Add a discussion. + $record = new stdClass(); + $record->course = $course->id; + $record->userid = $user->id; + $record->forum = $forum->id; + $discussion = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); + + // Add a post via WS (so discussion subscription works). + $result = mod_forum_external::add_discussion_post($discussion->firstpost, 'some subject', 'some text here...'); + $newpost = $result['post']; + $this->assertTrue(\mod_forum\subscriptions::is_subscribed($user->id, $forum, $discussion->id, $cm)); + + // Add files in the different areas. + $draftidattach = file_get_unused_draft_itemid(); + $filerecordinline = array( + 'contextid' => context_user::instance($user->id)->id, + 'component' => 'user', + 'filearea' => 'draft', + 'itemid' => $draftidattach, + 'filepath' => '/', + 'filename' => 'faketxt.txt', + ); + $fs = get_file_storage(); + $fs->create_file_from_string($filerecordinline, 'fake txt contents 1.'); + + // Create files in post area (inline). + $draftidinlineattach = file_get_unused_draft_itemid(); + $filerecordinline['itemid'] = $draftidinlineattach; + $filerecordinline['filename'] = 'fakeimage.png'; + $fs->create_file_from_string($filerecordinline, 'img...'); + + // Do not update subject. + $message = 'Hey message updated'; + $messageformat = FORMAT_HTML; + $options = [ + ['name' => 'discussionsubscribe', 'value' => false], + ['name' => 'inlineattachmentsid', 'value' => $draftidinlineattach], + ['name' => 'attachmentsid', 'value' => $draftidattach], + ]; + + $result = mod_forum_external::update_discussion_post($newpost->id, '', $message, $messageformat, $options); + $result = external_api::clean_returnvalue(mod_forum_external::update_discussion_post_returns(), $result); + $this->assertTrue($result['status']); + // Check subscription status. + $this->assertFalse(\mod_forum\subscriptions::is_subscribed($user->id, $forum, $discussion->id, $cm)); + + // Get the post from WS. + $result = mod_forum_external::get_forum_discussion_posts($discussion->id); + $result = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $result); + $found = false; + foreach ($result['posts'] as $post) { + if ($post['id'] == $newpost->id) { + $this->assertEquals($newpost->subject, $post['subject']); + $this->assertEquals($message, $post['message']); + $this->assertEquals($messageformat, $post['messageformat']); + $this->assertCount(1, $post['messageinlinefiles']); + $this->assertEquals('fakeimage.png', $post['messageinlinefiles'][0]['filename']); + $this->assertCount(1, $post['attachments']); + $this->assertEquals('faketxt.txt', $post['attachments'][0]['filename']); + $found = true; + } + } + $this->assertTrue($found); + } + + /** + * Test update_discussion_post with other user post (no permissions). + */ + public function test_update_discussion_post_other_user_post() { + global $DB, $USER; + $this->resetAfterTest(true); + // Setup test data. + $course = $this->getDataGenerator()->create_course(); + $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id)); + $user = $this->getDataGenerator()->create_user(); + $role = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST); + self::getDataGenerator()->enrol_user($user->id, $course->id, $role->id); + + $this->setAdminUser(); + // Add a discussion. + $record = new stdClass(); + $record->course = $course->id; + $record->userid = $USER->id; + $record->forum = $forum->id; + $discussion = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); + + // Add a post. + $record = new stdClass(); + $record->course = $course->id; + $record->userid = $USER->id; + $record->forum = $forum->id; + $record->discussion = $discussion->id; + $record->parent = $discussion->firstpost; + $newpost = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record); + + $this->setUser($user); + $subject = 'Hey subject updated'; + $message = 'Hey message updated'; + $messageformat = FORMAT_HTML; + + $this->expectExceptionMessage(get_string('cannotupdatepost', 'forum')); + mod_forum_external::update_discussion_post($newpost->id, $subject, $message, $messageformat); + } } diff --git a/mod/forum/version.php b/mod/forum/version.php index 89aebb9ef95..b86dd1f2dfb 100644 --- a/mod/forum/version.php +++ b/mod/forum/version.php @@ -24,6 +24,6 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2019071903; // The current module version (Date: YYYYMMDDXX) +$plugin->version = 2019071904; // The current module version (Date: YYYYMMDDXX) $plugin->requires = 2019051100; // Requires this Moodle version $plugin->component = 'mod_forum'; // Full name of the plugin (used for diagnostics) -- 2.11.4.GIT