From 8e4a9ed8544af3657cced293ed1faed7fcd762da Mon Sep 17 00:00:00 2001 From: Juan Leyva Date: Wed, 13 Feb 2019 15:12:33 +0100 Subject: [PATCH] MDL-64588 comment: New WebService core_comment_add_comment --- comment/classes/external.php | 115 ++++++++++++++ comment/tests/externallib_test.php | 232 +++++++++++++++++++++++++---- lib/db/services.php | 7 + version.php | 2 +- 4 files changed, 327 insertions(+), 29 deletions(-) diff --git a/comment/classes/external.php b/comment/classes/external.php index 22995e7d12e..d19e2c48eba 100644 --- a/comment/classes/external.php +++ b/comment/classes/external.php @@ -170,4 +170,119 @@ class core_comment_external extends external_api { ) ); } + + /** + * Helper to get the structure of a single comment. + * + * @return external_single_structure the comment structure. + */ + protected static function get_comment_structure() { + return new external_single_structure( + array( + 'id' => new external_value(PARAM_INT, 'Comment ID'), + 'content' => new external_value(PARAM_RAW, 'The content text formatted'), + 'format' => new external_format_value('content'), + 'timecreated' => new external_value(PARAM_INT, 'Time created (timestamp)'), + 'strftimeformat' => new external_value(PARAM_NOTAGS, 'Time format'), + 'profileurl' => new external_value(PARAM_URL, 'URL profile'), + 'fullname' => new external_value(PARAM_NOTAGS, 'fullname'), + 'time' => new external_value(PARAM_NOTAGS, 'Time in human format'), + 'avatar' => new external_value(PARAM_RAW, 'HTML user picture'), + 'userid' => new external_value(PARAM_INT, 'User ID'), + 'delete' => new external_value(PARAM_BOOL, 'Permission to delete=true/false', VALUE_OPTIONAL) + ), 'comment' + ); + } + + /** + * Returns description of method parameters for the add_comments method. + * + * @return external_function_parameters + */ + public static function add_comments_parameters() { + return new external_function_parameters( + [ + 'comments' => new external_multiple_structure( + new external_single_structure( + [ + 'contextlevel' => new external_value(PARAM_ALPHA, 'contextlevel system, course, user...'), + 'instanceid' => new external_value(PARAM_INT, 'the id of item associated with the contextlevel'), + 'component' => new external_value(PARAM_COMPONENT, 'component'), + 'content' => new external_value(PARAM_RAW, 'component'), + 'itemid' => new external_value(PARAM_INT, 'associated id'), + 'area' => new external_value(PARAM_AREA, 'string comment area', VALUE_DEFAULT, ''), + ] + ) + ) + ] + ); + } + + /** + * Add a comment or comments. + * + * @param array $comments the array of comments to create. + * @return array the array containing those comments created. + * @throws comment_exception + */ + public static function add_comments($comments) { + global $CFG, $SITE; + + if (empty($CFG->usecomments)) { + throw new comment_exception('commentsnotenabled', 'moodle'); + } + + $params = self::validate_parameters(self::add_comments_parameters(), ['comments' => $comments]); + + // Validate every intended comment before creating anything, storing the validated comment for use below. + foreach ($params['comments'] as $index => $comment) { + $context = self::get_context_from_params($comment); + self::validate_context($context); + + list($context, $course, $cm) = get_context_info_array($context->id); + if ($context->id == SYSCONTEXTID) { + $course = $SITE; + } + + // Initialising comment object. + $args = new stdClass(); + $args->context = $context; + $args->course = $course; + $args->cm = $cm; + $args->component = $comment['component']; + $args->itemid = $comment['itemid']; + $args->area = $comment['area']; + + $manager = new comment($args); + if (!$manager->can_post()) { + throw new comment_exception('nopermissiontocomment'); + } + + $params['comments'][$index]['preparedcomment'] = $manager; + } + + // Create the comments. + $results = []; + foreach ($params['comments'] as $comment) { + $manager = $comment['preparedcomment']; + $newcomment = $manager->add($comment['content']); + if (!empty($newcomment) && is_object($newcomment)) { + $results[] = $newcomment; + } + $newcomment->delete = true; // USER created the comment, so they can delete it. + } + + return $results; + } + + /** + * Returns description of method result value for the add_comments method. + * + * @return external_description + */ + public static function add_comments_returns() { + return new external_multiple_structure( + self::get_comment_structure() + ); + } } diff --git a/comment/tests/externallib_test.php b/comment/tests/externallib_test.php index b34a553720b..b43347967b8 100644 --- a/comment/tests/externallib_test.php +++ b/comment/tests/externallib_test.php @@ -45,34 +45,25 @@ class core_comment_externallib_testcase extends externallib_advanced_testcase { * Tests set up */ protected function setUp() { - global $CFG; + global $CFG, $DB; require_once($CFG->dirroot . '/comment/lib.php'); - } - - /** - * Test get_comments - */ - public function test_get_comments() { - global $DB, $CFG; - - $this->resetAfterTest(true); $CFG->usecomments = true; - $user = $this->getDataGenerator()->create_user(); - $course = $this->getDataGenerator()->create_course(array('enablecomment' => 1)); + $this->student = $this->getDataGenerator()->create_user(); + $this->course = $this->getDataGenerator()->create_course(array('enablecomment' => 1)); $studentrole = $DB->get_record('role', array('shortname' => 'student')); - $this->getDataGenerator()->enrol_user($user->id, $course->id, $studentrole->id); + $this->getDataGenerator()->enrol_user($this->student->id, $this->course->id, $studentrole->id); $record = new stdClass(); - $record->course = $course->id; + $record->course = $this->course->id; $record->name = "Mod data test"; $record->intro = "Some intro of some sort"; $record->comments = 1; - $module = $this->getDataGenerator()->create_module('data', $record); - $field = data_get_field_new('text', $module); + $this->module = $this->getDataGenerator()->create_module('data', $record); + $field = data_get_field_new('text', $this->module); $fielddetail = new stdClass(); $fielddetail->name = 'Name'; @@ -80,28 +71,37 @@ class core_comment_externallib_testcase extends externallib_advanced_testcase { $field->define_field($fielddetail); $field->insert_field(); - $recordid = data_add_record($module); + $this->recordid = data_add_record($this->module); $datacontent = array(); $datacontent['fieldid'] = $field->field->id; - $datacontent['recordid'] = $recordid; + $datacontent['recordid'] = $this->recordid; $datacontent['content'] = 'Asterix'; $contentid = $DB->insert_record('data_content', $datacontent); - $cm = get_coursemodule_from_instance('data', $module->id, $course->id); + $this->cm = get_coursemodule_from_instance('data', $this->module->id, $this->course->id); - $context = context_module::instance($module->cmid); + $this->context = context_module::instance($this->module->cmid); + } - $this->setUser($user); + /** + * Test get_comments + */ + public function test_get_comments() { + global $DB; + + $this->resetAfterTest(true); + + $this->setUser($this->student); // We need to add the comments manually, the comment API uses the global OUTPUT and this is going to make the WS to fail. $newcmt = new stdClass; - $newcmt->contextid = $context->id; + $newcmt->contextid = $this->context->id; $newcmt->commentarea = 'database_entry'; - $newcmt->itemid = $recordid; + $newcmt->itemid = $this->recordid; $newcmt->content = 'New comment'; $newcmt->format = 0; - $newcmt->userid = $user->id; + $newcmt->userid = $this->student->id; $newcmt->timecreated = time(); $cmtid1 = $DB->insert_record('comments', $newcmt); @@ -110,9 +110,9 @@ class core_comment_externallib_testcase extends externallib_advanced_testcase { $cmtid2 = $DB->insert_record('comments', $newcmt); $contextlevel = 'module'; - $instanceid = $cm->id; + $instanceid = $this->cm->id; $component = 'mod_data'; - $itemid = $recordid; + $itemid = $this->recordid; $area = 'database_entry'; $page = 0; @@ -127,8 +127,8 @@ class core_comment_externallib_testcase extends externallib_advanced_testcase { $this->assertEquals(15, $result['perpage']); $this->assertTrue($result['canpost']); - $this->assertEquals($user->id, $result['comments'][0]['userid']); - $this->assertEquals($user->id, $result['comments'][1]['userid']); + $this->assertEquals($this->student->id, $result['comments'][0]['userid']); + $this->assertEquals($this->student->id, $result['comments'][1]['userid']); $this->assertEquals($cmtid2, $result['comments'][0]['id']); // Default ordering newer first. $this->assertEquals($cmtid1, $result['comments'][1]['id']); @@ -154,4 +154,180 @@ class core_comment_externallib_testcase extends externallib_advanced_testcase { $this->assertEquals($CFG->commentsperpage, $result['perpage']); $this->assertEquals($cmtid2, $result['comments'][0]['id']); } + + /** + * Test add_comments not enabled site level + */ + public function test_add_comments_not_enabled_site_level() { + global $CFG; + $this->resetAfterTest(true); + + $CFG->usecomments = false; + $this->setUser($this->student); + $this->expectException(comment_exception::class); + core_comment_external::add_comments([ + [ + 'contextlevel' => 'module', + 'instanceid' => $this->cm->id, + 'component' => 'mod_data', + 'content' => 'abc', + 'itemid' => $this->recordid, + 'area' => 'database_entry' + ] + ]); + } + + /** + * Test add_comments not enabled module level + */ + public function test_add_comments_not_enabled_module_level() { + global $DB; + $this->resetAfterTest(true); + + $DB->set_field('data', 'comments', 0, array('id' => $this->module->id)); + $this->setUser($this->student); + $this->expectException(comment_exception::class); + core_comment_external::add_comments([ + [ + 'contextlevel' => 'module', + 'instanceid' => $this->cm->id, + 'component' => 'mod_data', + 'content' => 'abc', + 'itemid' => $this->recordid, + 'area' => 'database_entry' + ] + ]); + } + + /** + * Test add_comments + */ + public function test_add_comments_single() { + $this->resetAfterTest(true); + $this->setUser($this->student); + + $result = core_comment_external::add_comments([ + [ + 'contextlevel' => 'module', + 'instanceid' => $this->cm->id, + 'component' => 'mod_data', + 'content' => 'abc', + 'itemid' => $this->recordid, + 'area' => 'database_entry' + ] + ]); + $result = external_api::clean_returnvalue(core_comment_external::add_comments_returns(), $result); + + $expectedkeys = [ + 'id', + 'content', + 'format', + 'timecreated', + 'strftimeformat', + 'profileurl', + 'fullname', + 'time', + 'avatar', + 'userid', + 'delete', + ]; + + // Verify the result contains 1 result having the correct structure. + $this->assertCount(1, $result); + foreach ($expectedkeys as $key) { + $this->assertArrayHasKey($key, $result[0]); + } + } + + /** + * Test add_comments when one of the comments contains invalid data and cannot be created. + * + * This simply verifies that the entire operation fails. + */ + public function test_add_comments_multiple_contains_invalid() { + $this->resetAfterTest(true); + $this->setUser($this->student); + + $this->expectException(comment_exception::class); + core_comment_external::add_comments([ + [ + 'contextlevel' => 'module', + 'instanceid' => $this->cm->id, + 'component' => 'mod_data', + 'content' => 'abc', + 'itemid' => $this->recordid, + 'area' => 'database_entry' + ], + [ + 'contextlevel' => 'module', + 'instanceid' => $this->cm->id, + 'component' => 'mod_data', + 'content' => 'abc', + 'itemid' => $this->recordid, + 'area' => 'areanotfound' + ], + ]); + } + + /** + * Test add_comments when one of the comments contains invalid data and cannot be created. + * + * This simply verifies that the entire operation fails. + */ + public function test_add_comments_multiple_all_valid() { + $this->resetAfterTest(true); + $this->setUser($this->student); + + $inputdata = [ + [ + 'contextlevel' => 'module', + 'instanceid' => $this->cm->id, + 'component' => 'mod_data', + 'content' => 'cat', + 'itemid' => $this->recordid, + 'area' => 'database_entry' + ], + [ + 'contextlevel' => 'module', + 'instanceid' => $this->cm->id, + 'component' => 'mod_data', + 'content' => 'dog', + 'itemid' => $this->recordid, + 'area' => 'database_entry' + ] + ]; + $result = core_comment_external::add_comments($inputdata); + $result = external_api::clean_returnvalue(core_comment_external::add_comments_returns(), $result); + + // Two comments should have been created. + $this->assertCount(2, $result); + + // The content for each comment should come back formatted. + foreach ($result as $index => $comment) { + $formatoptions = array('overflowdiv' => true, 'blanktarget' => true); + $expectedcontent = format_text($inputdata[$index]['content'], FORMAT_MOODLE, $formatoptions); + $this->assertEquals($expectedcontent, $comment['content']); + } + } + + /** + * Test add_comments invalid area + */ + public function test_add_comments_invalid_area() { + $this->resetAfterTest(true); + $this->setUser($this->student); + + $comments = [ + [ + 'contextlevel' => 'module', + 'instanceid' => $this->cm->id, + 'component' => 'mod_data', + 'content' => 'abc', + 'itemid' => $this->recordid, + 'area' => 'rhomboid' + ] + ]; + $this->expectException(comment_exception::class); + core_comment_external::add_comments($comments); + } } diff --git a/lib/db/services.php b/lib/db/services.php index fd519a10501..7b3cf3edf66 100644 --- a/lib/db/services.php +++ b/lib/db/services.php @@ -334,6 +334,13 @@ $functions = array( 'capabilities' => 'moodle/comment:view', 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE), ), + 'core_comment_add_comments' => array( + 'classname' => 'core_comment_external', + 'methodname' => 'add_comments', + 'description' => 'Adds a comment or comments.', + 'type' => 'write', + 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE), + ), 'core_completion_get_activities_completion_status' => array( 'classname' => 'core_completion_external', 'methodname' => 'get_activities_completion_status', diff --git a/version.php b/version.php index 32852abd11a..00e2a0063c4 100644 --- a/version.php +++ b/version.php @@ -29,7 +29,7 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2019092700.00; // YYYYMMDD = weekly release date of this DEV branch. +$version = 2019092700.01; // YYYYMMDD = weekly release date of this DEV branch. // RR = release increments - 00 in DEV branches. // .XX = incremental changes.