diff --git a/mod/forum/db/services.php b/mod/forum/db/services.php index 9f72919b3e8..98103df7983 100644 --- a/mod/forum/db/services.php +++ b/mod/forum/db/services.php @@ -191,4 +191,12 @@ $functions = array( 'type' => 'read', 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE) ), + 'mod_forum_prepare_draft_area_for_post' => array( + 'classname' => 'mod_forum_external', + 'methodname' => 'prepare_draft_area_for_post', + 'classpath' => 'mod/forum/externallib.php', + 'description' => 'Prepares a draft area for editing a post.', + 'type' => 'write', + 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE) + ), ); diff --git a/mod/forum/externallib.php b/mod/forum/externallib.php index 8cca27d7063..bab13ed1cf6 100644 --- a/mod/forum/externallib.php +++ b/mod/forum/externallib.php @@ -2232,4 +2232,166 @@ class mod_forum_external extends external_api { ) ); } + + /** + * Returns description of method parameters + * + * @return external_function_parameters + * @since Moodle 3.8 + */ + public static function prepare_draft_area_for_post_parameters() { + return new external_function_parameters( + array( + 'postid' => new external_value(PARAM_INT, 'Post to prepare the draft area for.'), + 'area' => new external_value(PARAM_ALPHA, 'Area to prepare: attachment or post.'), + 'draftitemid' => new external_value(PARAM_INT, 'The draft item id to use. 0 to generate one.', + VALUE_DEFAULT, 0), + 'filestokeep' => new external_multiple_structure( + new external_single_structure( + array( + 'filename' => new external_value(PARAM_FILE, 'File name.'), + 'filepath' => new external_value(PARAM_PATH, 'File path.'), + ) + ), 'Only keep these files in the draft file area. Empty for keeping all.', VALUE_DEFAULT, [] + ), + ) + ); + } + /** + * Prepares a draft area for editing a post. + * + * @param int $postid post to prepare the draft area for + * @param string $area area to prepare attachment or post + * @param int $draftitemid the draft item id to use. 0 to generate a new one. + * @param array $filestokeep only keep these files in the draft file area. Empty for keeping all. + * @return array of files in the area, the area options and the draft item id + * @since Moodle 3.8 + * @throws moodle_exception + */ + public static function prepare_draft_area_for_post($postid, $area, $draftitemid = 0, $filestokeep = []) { + global $USER; + + $params = self::validate_parameters( + self::prepare_draft_area_for_post_parameters(), + array( + 'postid' => $postid, + 'area' => $area, + 'draftitemid' => $draftitemid, + 'filestokeep' => $filestokeep, + ) + ); + $directionallowedvalues = ['ASC', 'DESC']; + + $allowedareas = ['attachment', 'post']; + if (!in_array($params['area'], $allowedareas)) { + throw new invalid_parameter_exception('Invalid value for area parameter + (value: ' . $params['area'] . '),' . 'allowed values are: ' . implode(', ', $allowedareas)); + } + + $warnings = array(); + $vaultfactory = mod_forum\local\container::get_vault_factory(); + $forumvault = $vaultfactory->get_forum_vault(); + $discussionvault = $vaultfactory->get_discussion_vault(); + $postvault = $vaultfactory->get_post_vault(); + + $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'); + } + + $context = $forumentity->get_context(); + self::validate_context($context); + + $managerfactory = mod_forum\local\container::get_manager_factory(); + $capabilitymanager = $managerfactory->get_capability_manager($forumentity); + + if (!$capabilitymanager->can_edit_post($USER, $discussionentity, $postentity)) { + throw new moodle_exception('noviewdiscussionspermission', 'forum'); + } + + if ($params['area'] == 'attachment') { + $legacydatamapperfactory = mod_forum\local\container::get_legacy_data_mapper_factory(); + $forumdatamapper = $legacydatamapperfactory->get_forum_data_mapper(); + $forum = $forumdatamapper->to_legacy_object($forumentity); + + $areaoptions = mod_forum_post_form::attachment_options($forum); + $messagetext = null; + } else { + $areaoptions = mod_forum_post_form::editor_options($context, $postentity->get_id()); + $messagetext = $postentity->get_message(); + } + + $draftitemid = empty($params['draftitemid']) ? 0 : $params['draftitemid']; + $messagetext = file_prepare_draft_area($draftitemid, $context->id, 'mod_forum', $params['area'], + $postentity->get_id(), $areaoptions, $messagetext); + + // Just get a structure compatible with external API. + array_walk($areaoptions, function(&$item, $key) { + $item = ['name' => $key, 'value' => $item]; + }); + + // Do we need to keep only the given files? + $usercontext = context_user::instance($USER->id); + if (!empty($params['filestokeep'])) { + $fs = get_file_storage(); + + if ($areafiles = $fs->get_area_files($usercontext->id, 'user', 'draft', $draftitemid)) { + $filestokeep = []; + foreach ($params['filestokeep'] as $ftokeep) { + $filestokeep[$ftokeep['filepath']][$ftokeep['filename']] = $ftokeep; + } + + foreach ($areafiles as $file) { + if ($file->is_directory()) { + continue; + } + if (!isset($filestokeep[$file->get_filepath()][$file->get_filename()])) { + $file->delete(); // Not in the list to be kept. + } + } + } + } + + $result = array( + 'draftitemid' => $draftitemid, + 'files' => external_util::get_area_files($usercontext->id, 'user', 'draft', + $draftitemid), + 'areaoptions' => $areaoptions, + 'messagetext' => $messagetext, + 'warnings' => $warnings, + ); + return $result; + } + /** + * Returns description of method result value + * + * @return external_description + * @since Moodle 3.8 + */ + public static function prepare_draft_area_for_post_returns() { + return new external_single_structure( + array( + 'draftitemid' => new external_value(PARAM_INT, 'Draft item id for the file area.'), + 'files' => new external_files('Draft area files.', VALUE_OPTIONAL), + 'areaoptions' => new external_multiple_structure( + new external_single_structure( + array( + 'name' => new external_value(PARAM_RAW, 'Name of option.'), + 'value' => new external_value(PARAM_RAW, 'Value of option.'), + ) + ), 'Draft file area options.' + ), + 'messagetext' => new external_value(PARAM_RAW, 'Message text with URLs rewritten.'), + 'warnings' => new external_warnings(), + ) + ); + } } diff --git a/mod/forum/tests/externallib_test.php b/mod/forum/tests/externallib_test.php index f6e81fb5a5c..7a52a209140 100644 --- a/mod/forum/tests/externallib_test.php +++ b/mod/forum/tests/externallib_test.php @@ -2703,4 +2703,82 @@ class mod_forum_external_testcase extends externallib_advanced_testcase { $this->assertTrue($result['post']['hasparent']); $this->assertEquals($post->message, $result['post']['message']); } + + /** + * Test prepare_draft_area_for_post a different user post. + */ + public function test_prepare_draft_area_for_post() { + global $DB; + $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); + // Add a discussion. + $record = array(); + $record['course'] = $course->id; + $record['forum'] = $forum->id; + $record['userid'] = $user->id; + $discussion = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); + $parentpost = $DB->get_record('forum_posts', array('discussion' => $discussion->id)); + // Add a post. + $record = new stdClass(); + $record->course = $course->id; + $record->userid = $user->id; + $record->forum = $forum->id; + $record->discussion = $discussion->id; + $record->parent = $parentpost->id; + $post = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record); + + // Add some files only in the attachment area. + $filename = 'faketxt.txt'; + $filerecordinline = array( + 'contextid' => context_module::instance($forum->cmid)->id, + 'component' => 'mod_forum', + 'filearea' => 'attachment', + 'itemid' => $post->id, + 'filepath' => '/', + 'filename' => $filename, + ); + $fs = get_file_storage(); + $fs->create_file_from_string($filerecordinline, 'fake txt contents 1.'); + $filerecordinline['filename'] = 'otherfaketxt.txt'; + $fs->create_file_from_string($filerecordinline, 'fake txt contents 2.'); + + $this->setUser($user); + + // Check attachment area. + $result = mod_forum_external::prepare_draft_area_for_post($post->id, 'attachment'); + $result = external_api::clean_returnvalue(mod_forum_external::prepare_draft_area_for_post_returns(), $result); + $this->assertCount(2, $result['files']); + $this->assertEquals($filename, $result['files'][0]['filename']); + $this->assertCount(5, $result['areaoptions']); + $this->assertEmpty($result['messagetext']); + + // Check again using existing draft item id. + $result = mod_forum_external::prepare_draft_area_for_post($post->id, 'attachment', $result['draftitemid']); + $result = external_api::clean_returnvalue(mod_forum_external::prepare_draft_area_for_post_returns(), $result); + $this->assertCount(2, $result['files']); + + // Keep only certain files in the area. + $filestokeep = array(array('filename' => $filename, 'filepath' => '/')); + $result = mod_forum_external::prepare_draft_area_for_post($post->id, 'attachment', $result['draftitemid'], $filestokeep); + $result = external_api::clean_returnvalue(mod_forum_external::prepare_draft_area_for_post_returns(), $result); + $this->assertCount(1, $result['files']); + $this->assertEquals($filename, $result['files'][0]['filename']); + + // Check editor (post) area. + $filerecordinline['filearea'] = 'post'; + $filerecordinline['filename'] = 'fakeimage.png'; + $fs->create_file_from_string($filerecordinline, 'fake image.'); + $result = mod_forum_external::prepare_draft_area_for_post($post->id, 'post'); + $result = external_api::clean_returnvalue(mod_forum_external::prepare_draft_area_for_post_returns(), $result); + $this->assertCount(1, $result['files']); + $this->assertEquals($filerecordinline['filename'], $result['files'][0]['filename']); + $this->assertCount(5, $result['areaoptions']); + $this->assertEquals($post->message, $result['messagetext']); + } + }