From 56b8edcb1ea480edf2d078fbf48813f74db57dd0 Mon Sep 17 00:00:00 2001 From: Juan Leyva Date: Mon, 13 Feb 2017 14:13:23 +0100 Subject: [PATCH] MDL-57920 mod_data: New WS mod_data_search_entries --- mod/data/classes/external.php | 168 ++++++++++++++++++++++++++++ mod/data/db/services.php | 8 ++ mod/data/tests/externallib_test.php | 90 +++++++++++++++ mod/data/version.php | 2 +- 4 files changed, 267 insertions(+), 1 deletion(-) diff --git a/mod/data/classes/external.php b/mod/data/classes/external.php index c2b67ac9bc9..6f6829e6929 100644 --- a/mod/data/classes/external.php +++ b/mod/data/classes/external.php @@ -642,4 +642,172 @@ class mod_data_external extends external_api { ) ); } + + /** + * Returns description of method parameters + * + * @return external_function_parameters + * @since Moodle 3.3 + */ + public static function search_entries_parameters() { + return new external_function_parameters( + array( + 'databaseid' => new external_value(PARAM_INT, 'data instance id'), + 'groupid' => new external_value(PARAM_INT, 'Group id, 0 means that the function will determine the user group', + VALUE_DEFAULT, 0), + 'returncontents' => new external_value(PARAM_BOOL, 'Whether to return contents or not.', VALUE_DEFAULT, false), + 'search' => new external_value(PARAM_NOTAGS, 'search string (empty when using advanced)', VALUE_DEFAULT, ''), + 'advsearch' => new external_multiple_structure( + new external_single_structure( + array( + 'name' => new external_value(PARAM_ALPHANUMEXT, 'Field key for search. + Use fn or ln for first or last name'), + 'value' => new external_value(PARAM_RAW, 'JSON encoded value for search'), + ) + ), 'Advanced search', VALUE_DEFAULT, array() + ), + 'sort' => new external_value(PARAM_INT, 'Sort the records by this field id, reserved ids are: + 0: timeadded + -1: firstname + -2: lastname + -3: approved + -4: timemodified. + Empty for using the default database setting.', VALUE_DEFAULT, null), + 'order' => new external_value(PARAM_ALPHA, 'The direction of the sorting: \'ASC\' or \'DESC\'. + Empty for using the default database setting.', VALUE_DEFAULT, null), + 'page' => new external_value(PARAM_INT, 'The page of records to return.', VALUE_DEFAULT, 0), + 'perpage' => new external_value(PARAM_INT, 'The number of records to return per page', VALUE_DEFAULT, 0), + ) + ); + } + + /** + * Return access information for a given feedback + * + * @param int $databaseid the data instance id + * @param int $groupid (optional) group id, 0 means that the function will determine the user group + * @param bool $returncontents whether to return contents or not + * @param str $search search text + * @param array $advsearch advanced search data + * @param str $sort sort by this field + * @param int $order the direction of the sorting + * @param int $page page of records to return + * @param int $perpage number of records to return per page + * @return array of warnings and the entries + * @since Moodle 3.3 + * @throws moodle_exception + */ + public static function search_entries($databaseid, $groupid = 0, $returncontents = false, $search = '', $advsearch = [], + $sort = null, $order = null, $page = 0, $perpage = 0) { + global $PAGE, $DB; + + $params = array('databaseid' => $databaseid, 'groupid' => $groupid, 'returncontents' => $returncontents, 'search' => $search, + 'advsearch' => $advsearch, 'sort' => $sort, 'order' => $order, 'page' => $page, 'perpage' => $perpage); + $params = self::validate_parameters(self::search_entries_parameters(), $params); + $warnings = array(); + + if (!empty($params['order'])) { + $params['order'] = strtoupper($params['order']); + if ($params['order'] != 'ASC' && $params['order'] != 'DESC') { + throw new invalid_parameter_exception('Invalid value for sortdirection parameter (value: ' . $params['order'] . ')'); + } + } + + list($database, $course, $cm, $context) = self::validate_database($params['databaseid']); + // Check database is open in time. + data_require_time_available($database, null, $context); + + if (!empty($params['groupid'])) { + $groupid = $params['groupid']; + // Determine is the group is visible to user. + if (!groups_group_visible($groupid, $course, $cm)) { + throw new moodle_exception('notingroup'); + } + } else { + // Check to see if groups are being used here. + if ($groupmode = groups_get_activity_groupmode($cm)) { + $groupid = groups_get_activity_group($cm); + // Determine is the group is visible to user (this is particullary for the group 0 -> all groups). + if (!groups_group_visible($groupid, $course, $cm)) { + throw new moodle_exception('notingroup'); + } + } else { + $groupid = 0; + } + } + + if (!empty($params['advsearch'])) { + $advanced = true; + $defaults = []; + $fn = $ln = ''; // Defaults for first and last name. + // Force defaults for advanced search. + foreach ($params['advsearch'] as $adv) { + if ($adv['name'] == 'fn' || $adv['name'] == 'ln') { + $$adv['name'] = json_decode($adv['value']); + continue; + } + $defaults[$adv['name']] = json_decode($adv['value']); + } + list($searcharray, $params['search']) = data_build_search_array($database, false, [], $defaults, $fn, $ln); + } else { + $advanced = null; + $searcharray = null; + } + + list($records, $maxcount, $totalcount, $page, $nowperpage, $sort, $mode) = + data_search_entries($database, $cm, $context, 'list', $groupid, $params['search'], $params['sort'], $params['order'], + $params['page'], $params['perpage'], $advanced, $searcharray); + + $entries = []; + foreach ($records as $record) { + $user = user_picture::unalias($record, null, 'userid'); + $related = array('context' => $context, 'database' => $database, 'user' => $user); + if ($params['returncontents']) { + $related['contents'] = $DB->get_records('data_content', array('recordid' => $record->id)); + } else { + $related['contents'] = null; + } + + $exporter = new record_exporter($record, $related); + $entries[] = $exporter->export($PAGE->get_renderer('core')); + } + + $result = array( + 'entries' => $entries, + 'totalcount' => $totalcount, + 'maxcount' => $maxcount, + 'warnings' => $warnings + ); + + // Check if we should return the list rendered. + if ($params['returncontents']) { + ob_start(); + // The return parameter stops the execution after the first record. + data_print_template('listtemplate', $records, $database, '', $page, false); + $result['listviewcontents'] = ob_get_contents(); + ob_end_clean(); + } + + return $result; + } + + /** + * Returns description of method result value + * + * @return external_description + * @since Moodle 3.3 + */ + public static function search_entries_returns() { + return new external_single_structure( + array( + 'entries' => new external_multiple_structure( + record_exporter::get_read_structure() + ), + 'totalcount' => new external_value(PARAM_INT, 'Total count of records.'), + 'listviewcontents' => new external_value(PARAM_RAW, 'The list view contents as is rendered in the site.', + VALUE_OPTIONAL), + 'warnings' => new external_warnings() + ) + ); + } } diff --git a/mod/data/db/services.php b/mod/data/db/services.php index c98e19633ad..d95612daee1 100644 --- a/mod/data/db/services.php +++ b/mod/data/db/services.php @@ -75,4 +75,12 @@ $functions = array( 'capabilities' => 'mod/data:viewentry', 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE) ), + 'mod_data_search_entries' => array( + 'classname' => 'mod_data_external', + 'methodname' => 'search_entries', + 'description' => 'Search for entries in the given database.', + 'type' => 'read', + 'capabilities' => 'mod/data:viewentry', + 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE) + ), ); diff --git a/mod/data/tests/externallib_test.php b/mod/data/tests/externallib_test.php index 30ec6d854b6..99e549f9f88 100644 --- a/mod/data/tests/externallib_test.php +++ b/mod/data/tests/externallib_test.php @@ -619,4 +619,94 @@ class mod_data_external_testcase extends externallib_advanced_testcase { $this->assertEquals($field, (array) $fields[$field['id']]); } } + + /** + * Test search_entries. + */ + public function test_search_entries() { + global $DB; + list($entry11, $entry12, $entry13, $entry21) = self::populate_database_with_entries(); + + // First do a normal text search as student 1. I should see my two group entries. + $this->setUser($this->student1); + $result = mod_data_external::search_entries($this->data->id, 0, false, 'text'); + $result = external_api::clean_returnvalue(mod_data_external::search_entries_returns(), $result); + $this->assertCount(2, $result['entries']); + $this->assertEquals(2, $result['totalcount']); + + // Now as the other student I should receive my not approved entry. Apply ordering here. + $this->setUser($this->student2); + $result = mod_data_external::search_entries($this->data->id, 0, false, 'text', [], DATA_APPROVED, 'ASC'); + $result = external_api::clean_returnvalue(mod_data_external::search_entries_returns(), $result); + $this->assertCount(3, $result['entries']); + $this->assertEquals(3, $result['totalcount']); + // The not approved one should be the first. + $this->assertEquals($entry13, $result['entries'][0]['id']); + + // Now as the other group student. + $this->setUser($this->student3); + $result = mod_data_external::search_entries($this->data->id, 0, false, 'text'); + $result = external_api::clean_returnvalue(mod_data_external::search_entries_returns(), $result); + $this->assertCount(1, $result['entries']); + $this->assertEquals(1, $result['totalcount']); + $this->assertEquals($this->student3->id, $result['entries'][0]['userid']); + + // Same normal text search as teacher. + $this->setUser($this->teacher); + $result = mod_data_external::search_entries($this->data->id, 0, false, 'text'); + $result = external_api::clean_returnvalue(mod_data_external::search_entries_returns(), $result); + $this->assertCount(4, $result['entries']); // I can see all groups and non approved. + $this->assertEquals(4, $result['totalcount']); + + // Pagination. + $this->setUser($this->teacher); + $result = mod_data_external::search_entries($this->data->id, 0, false, 'text', [], DATA_TIMEADDED, 'ASC', 0, 2); + $result = external_api::clean_returnvalue(mod_data_external::search_entries_returns(), $result); + $this->assertCount(2, $result['entries']); // Only 2 per page. + $this->assertEquals(4, $result['totalcount']); + + // Now advanced search or not dinamic fields (user firstname for example). + $this->setUser($this->student1); + $advsearch = [ + ['name' => 'fn', 'value' => json_encode($this->student2->firstname)] + ]; + $result = mod_data_external::search_entries($this->data->id, 0, false, '', $advsearch); + $result = external_api::clean_returnvalue(mod_data_external::search_entries_returns(), $result); + $this->assertCount(1, $result['entries']); + $this->assertEquals(1, $result['totalcount']); + $this->assertEquals($this->student2->id, $result['entries'][0]['userid']); // I only found mine! + + // Advanced search for fields. + $field = $DB->get_record('data_fields', array('type' => 'url')); + $advsearch = [ + ['name' => 'f_' . $field->id , 'value' => 'sampleurl'] + ]; + $result = mod_data_external::search_entries($this->data->id, 0, false, '', $advsearch); + $result = external_api::clean_returnvalue(mod_data_external::search_entries_returns(), $result); + $this->assertCount(2, $result['entries']); // Found two entries matching this. + $this->assertEquals(2, $result['totalcount']); + + // Combined search. + $field2 = $DB->get_record('data_fields', array('type' => 'number')); + $advsearch = [ + ['name' => 'f_' . $field->id , 'value' => 'sampleurl'], + ['name' => 'f_' . $field2->id , 'value' => '12345'], + ['name' => 'ln', 'value' => json_encode($this->student2->lastname)] + ]; + $result = mod_data_external::search_entries($this->data->id, 0, false, '', $advsearch); + $result = external_api::clean_returnvalue(mod_data_external::search_entries_returns(), $result); + $this->assertCount(1, $result['entries']); // Only one matching everything. + $this->assertEquals(1, $result['totalcount']); + + // Combined search (no results). + $field2 = $DB->get_record('data_fields', array('type' => 'number')); + $advsearch = [ + ['name' => 'f_' . $field->id , 'value' => 'sampleurl'], + ['name' => 'f_' . $field2->id , 'value' => '98780333'], // Non existent number. + ]; + $result = mod_data_external::search_entries($this->data->id, 0, false, '', $advsearch); + $result = external_api::clean_returnvalue(mod_data_external::search_entries_returns(), $result); + $this->assertCount(0, $result['entries']); // Only one matching everything. + $this->assertEquals(0, $result['totalcount']); + } } diff --git a/mod/data/version.php b/mod/data/version.php index f317280d5e0..bc85847f93d 100644 --- a/mod/data/version.php +++ b/mod/data/version.php @@ -24,7 +24,7 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2016120505; // The current module version (Date: YYYYMMDDXX) +$plugin->version = 2016120506; // The current module version (Date: YYYYMMDDXX) $plugin->requires = 2016112900; // Requires this Moodle version $plugin->component = 'mod_data'; // Full name of the plugin (used for diagnostics) $plugin->cron = 0;