From 9cafa794c4a0724d47ef99bba29e50f77db17e83 Mon Sep 17 00:00:00 2001 From: Frederic Massart Date: Mon, 12 Oct 2015 17:42:58 +0800 Subject: [PATCH] MDL-49231 mod_glossary: External function get_entries_by_category --- mod/glossary/classes/external.php | 169 ++++++++++++++++++++++++++- mod/glossary/tests/external_test.php | 81 +++++++++++++ 2 files changed, 247 insertions(+), 3 deletions(-) diff --git a/mod/glossary/classes/external.php b/mod/glossary/classes/external.php index e211cd55dd4..58538c9c8a7 100644 --- a/mod/glossary/classes/external.php +++ b/mod/glossary/classes/external.php @@ -73,10 +73,11 @@ class mod_glossary_external extends external_api { /** * Get the return value of an entry. * + * @param bool $includecat Whether the definition should include category info. * @return external_definition */ - public static function get_entry_return_structure() { - return new external_single_structure(array( + public static function get_entry_return_structure($includecat = false) { + $params = array( 'id' => new external_value(PARAM_INT, 'The entry ID'), 'glossaryid' => new external_value(PARAM_INT, 'The glossary ID'), 'userid' => new external_value(PARAM_INT, 'Author ID'), @@ -102,7 +103,17 @@ class mod_glossary_external extends external_api { 'casesensitive' => new external_value(PARAM_BOOL, 'When true, the matching is case sensitive'), 'fullmatch' => new external_value(PARAM_BOOL, 'When true, the matching is done on full words only'), 'approved' => new external_value(PARAM_BOOL, 'Whether the entry was approved'), - )); + ); + + if ($includecat) { + $params['categoryid'] = new external_value(PARAM_INT, 'The category ID. This may be' . + ' \''. GLOSSARY_SHOW_NOT_CATEGORISED . '\' when the entry is not categorised', VALUE_DEFAULT, + GLOSSARY_SHOW_NOT_CATEGORISED); + $params['categoryname'] = new external_value(PARAM_RAW, 'The category name. May be empty when the entry is' . + ' not categorised, or the request was limited to one category.', VALUE_DEFAULT, ''); + } + + return new external_single_structure($params); } /** @@ -740,4 +751,156 @@ class mod_glossary_external extends external_api { )); } + /** + * Returns the description of the external function parameters. + * + * @return external_function_parameters + * @since Moodle 3.1 + */ + public static function get_entries_by_category_parameters() { + return new external_function_parameters(array( + 'id' => new external_value(PARAM_INT, 'Glossary entry ID.'), + 'categoryid' => new external_value(PARAM_INT, 'The category ID. Use \'' . GLOSSARY_SHOW_ALL_CATEGORIES . '\' for all' . + ' categories, or \'' . GLOSSARY_SHOW_NOT_CATEGORISED . '\' for uncategorised entries.'), + 'from' => new external_value(PARAM_INT, 'Start returning records from here', VALUE_DEFAULT, 0), + 'limit' => new external_value(PARAM_INT, 'Number of records to return', VALUE_DEFAULT, 20), + 'options' => new external_single_structure(array( + 'includenotapproved' => new external_value(PARAM_BOOL, 'When false, includes the non-approved entries created by' . + ' the user. When true, also includes the ones that the user has the permission to approve.', VALUE_DEFAULT, 0) + ), 'An array of options', VALUE_DEFAULT, array()) + )); + } + + /** + * Browse a glossary entries by category. + * + * @param int $id The glossary ID. + * @param int $categoryid The category ID. + * @param int $from Start returning records from here. + * @param int $limit Number of records to return. + * @param array $options Array of options. + * @return array of warnings and status result + * @since Moodle 3.1 + * @throws moodle_exception + */ + public static function get_entries_by_category($id, $categoryid, $from = 0, $limit = 20, $options = array()) { + global $DB, $USER; + + $params = self::validate_parameters(self::get_entries_by_category_parameters(), array( + 'id' => $id, + 'categoryid' => $categoryid, + 'from' => $from, + 'limit' => $limit, + 'options' => $options, + )); + $id = $params['id']; + $categoryid = $params['categoryid']; + $from = $params['from']; + $limit = $params['limit']; + $options = $params['options']; + $warnings = array(); + + // Fetch and confirm. + $glossary = $DB->get_record('glossary', array('id' => $id), '*', MUST_EXIST); + list($course, $cm) = get_course_and_cm_from_instance($glossary, 'glossary'); + $context = context_module::instance($cm->id); + self::validate_context($context); + + // Validate the mode. + $modes = self::get_browse_modes_from_display_format($glossary->displayformat); + if (!in_array('cat', $modes)) { + throw new invalid_parameter_exception('invalidbrowsemode'); + } + + // Validate the category. + if (in_array($categoryid, array(GLOSSARY_SHOW_ALL_CATEGORIES, GLOSSARY_SHOW_NOT_CATEGORISED))) { + // All good. + } else if ($DB->count_records('glossary_categories', array('id' => $categoryid, 'glossaryid' => $id)) < 1) { + throw new invalid_parameter_exception('invalidcategory'); + } + + // Preparing the query. + $params = array(); + $glossarysql = '(ge.glossaryid = :gid1 OR ge.sourceglossaryid = :gid2)'; + $params['gid1'] = $glossary->id; + $params['gid2'] = $glossary->id; + + $approvedsql = '(ge.approved <> 0 OR ge.userid = :myid)'; + $params['myid'] = $USER->id; + if (!empty($options['includenotapproved']) && has_capability('mod/glossary:approve', $context)) { + $approvedsql = '1 = 1'; + } + + $userfields = user_picture::fields('u', null, 'userdataid', 'userdata'); + $sqlfields = "ge.*, gec.categoryid, $userfields"; + $sqlorderfields = 'ge.concept'; + + if ($categoryid === GLOSSARY_SHOW_ALL_CATEGORIES) { + $sqlfields .= ', gc.name AS categoryname'; + $sqlorderfields = 'gc.name, ge.concept'; + $sql = " FROM {glossary_entries} ge + JOIN {glossary_entries_categories} gec ON ge.id = gec.entryid + JOIN {glossary_categories} gc ON gc.id = gec.categoryid + LEFT JOIN {user} u ON ge.userid = u.id + WHERE $glossarysql + AND $approvedsql"; + + } else if ($categoryid === GLOSSARY_SHOW_NOT_CATEGORISED) { + $sql = " FROM {glossary_entries} ge + LEFT JOIN {glossary_entries_categories} gec ON ge.id = gec.entryid + LEFT JOIN {user} u ON ge.userid = u.id + WHERE $glossarysql + AND $approvedsql + AND gec.categoryid IS NULL"; + + } else { + $sql = " FROM {glossary_entries} ge + JOIN {glossary_entries_categories} gec + ON gec.entryid = ge.id + AND gec.categoryid = :categoryid + LEFT JOIN {user} u ON ge.userid = u.id + WHERE $glossarysql + AND $approvedsql"; + $params['categoryid'] = $categoryid; + } + + $sqlselectcount = "SELECT COUNT('x')"; + $sqlselect = "SELECT $sqlfields"; + $sqlorder = ' ORDER BY ' . $sqlorderfields; + + // Fetching the entries. + $count = $DB->count_records_sql($sqlselectcount . $sql, $params); + $entries = $DB->get_records_sql($sqlselect . $sql . $sqlorder, $params, $from, $limit); + foreach ($entries as $key => $entry) { + self::fill_entry_details($entry, $context); + if ($entry->categoryid === null) { + $entry->categoryid = GLOSSARY_SHOW_NOT_CATEGORISED; + } + if (isset($entry->categoryname)) { + $entry->categoryname = external_format_string($entry->categoryname, $context->id); + } + } + + return array( + 'count' => $count, + 'entries' => $entries, + 'warnings' => $warnings + ); + } + + /** + * Returns the description of the external function return value. + * + * @return external_description + * @since Moodle 3.1 + */ + public static function get_entries_by_category_returns() { + return new external_single_structure(array( + 'count' => new external_value(PARAM_INT, 'The total number of records matching the request.'), + 'entries' => new external_multiple_structure( + self::get_entry_return_structure(true) + ), + 'warnings' => new external_warnings() + )); + } } diff --git a/mod/glossary/tests/external_test.php b/mod/glossary/tests/external_test.php index 29e55fec4ce..73dc6fe7d50 100644 --- a/mod/glossary/tests/external_test.php +++ b/mod/glossary/tests/external_test.php @@ -404,4 +404,85 @@ class mod_glossary_external_testcase extends externallib_advanced_testcase { $this->assertEquals($cat1c->id, $return['categories'][1]['id']); } + public function test_get_entries_by_category() { + $this->resetAfterTest(true); + + $gg = $this->getDataGenerator()->get_plugin_generator('mod_glossary'); + $c1 = $this->getDataGenerator()->create_course(); + $g1 = $this->getDataGenerator()->create_module('glossary', array('course' => $c1->id, 'displayformat' => 'entrylist')); + $g2 = $this->getDataGenerator()->create_module('glossary', array('course' => $c1->id, 'displayformat' => 'entrylist')); + $u1 = $this->getDataGenerator()->create_user(); + $ctx = context_module::instance($g1->cmid); + + $e1a1 = $gg->create_content($g1, array('approved' => 1, 'userid' => $u1->id)); + $e1a2 = $gg->create_content($g1, array('approved' => 1, 'userid' => $u1->id)); + $e1a3 = $gg->create_content($g1, array('approved' => 1, 'userid' => $u1->id)); + $e1b1 = $gg->create_content($g1, array('approved' => 1, 'userid' => $u1->id)); + $e1b2 = $gg->create_content($g1, array('approved' => 0, 'userid' => $u1->id)); + $e1x1 = $gg->create_content($g1, array('approved' => 1, 'userid' => $u1->id)); + $e1x2 = $gg->create_content($g1, array('approved' => 0, 'userid' => $u1->id)); + $e2a1 = $gg->create_content($g2, array('approved' => 1, 'userid' => $u1->id)); + $e2a2 = $gg->create_content($g2, array('approved' => 1, 'userid' => $u1->id)); + + $cat1a = $gg->create_category($g1, array('name' => 'Fish'), array($e1a1, $e1a2, $e1a3)); + $cat1b = $gg->create_category($g1, array('name' => 'Cat'), array($e1b1, $e1b2)); + $cat1c = $gg->create_category($g1, array('name' => 'Zebra'), array($e1b1)); // $e1b1 is in two categories. + $cat2a = $gg->create_category($g2, array(), array($e2a1, $e2a2)); + + $this->setAdminUser(); + + // Browse one category. + $return = mod_glossary_external::get_entries_by_category($g1->id, $cat1a->id, 0, 20, array()); + $return = external_api::clean_returnvalue(mod_glossary_external::get_entries_by_category_returns(), $return); + $this->assertCount(3, $return['entries']); + $this->assertEquals(3, $return['count']); + $this->assertEquals($e1a1->id, $return['entries'][0]['id']); + $this->assertEquals($e1a2->id, $return['entries'][1]['id']); + $this->assertEquals($e1a3->id, $return['entries'][2]['id']); + + // Browse all categories. + $return = mod_glossary_external::get_entries_by_category($g1->id, GLOSSARY_SHOW_ALL_CATEGORIES, 0, 20, array()); + $return = external_api::clean_returnvalue(mod_glossary_external::get_entries_by_category_returns(), $return); + $this->assertCount(5, $return['entries']); + $this->assertEquals(5, $return['count']); + $this->assertEquals($e1b1->id, $return['entries'][0]['id']); + $this->assertEquals($e1a1->id, $return['entries'][1]['id']); + $this->assertEquals($e1a2->id, $return['entries'][2]['id']); + $this->assertEquals($e1a3->id, $return['entries'][3]['id']); + $this->assertEquals($e1b1->id, $return['entries'][4]['id']); + + // Browse uncategorised. + $return = mod_glossary_external::get_entries_by_category($g1->id, GLOSSARY_SHOW_NOT_CATEGORISED, 0, 20, array()); + $return = external_api::clean_returnvalue(mod_glossary_external::get_entries_by_category_returns(), $return); + $this->assertCount(1, $return['entries']); + $this->assertEquals(1, $return['count']); + $this->assertEquals($e1x1->id, $return['entries'][0]['id']); + + // Including to approve. + $return = mod_glossary_external::get_entries_by_category($g1->id, $cat1b->id, 0, 20, + array('includenotapproved' => true)); + $return = external_api::clean_returnvalue(mod_glossary_external::get_entries_by_category_returns(), $return); + $this->assertCount(2, $return['entries']); + $this->assertEquals(2, $return['count']); + $this->assertEquals($e1b1->id, $return['entries'][0]['id']); + $this->assertEquals($e1b2->id, $return['entries'][1]['id']); + + // Using limit. + $return = mod_glossary_external::get_entries_by_category($g1->id, GLOSSARY_SHOW_ALL_CATEGORIES, 0, 3, + array('includenotapproved' => true)); + $return = external_api::clean_returnvalue(mod_glossary_external::get_entries_by_category_returns(), $return); + $this->assertCount(3, $return['entries']); + $this->assertEquals(6, $return['count']); + $this->assertEquals($e1b1->id, $return['entries'][0]['id']); + $this->assertEquals($e1b2->id, $return['entries'][1]['id']); + $this->assertEquals($e1a1->id, $return['entries'][2]['id']); + $return = mod_glossary_external::get_entries_by_category($g1->id, GLOSSARY_SHOW_ALL_CATEGORIES, 3, 2, + array('includenotapproved' => true)); + $return = external_api::clean_returnvalue(mod_glossary_external::get_entries_by_category_returns(), $return); + $this->assertCount(2, $return['entries']); + $this->assertEquals(6, $return['count']); + $this->assertEquals($e1a2->id, $return['entries'][0]['id']); + $this->assertEquals($e1a3->id, $return['entries'][1]['id']); + } + }