diff --git a/course/externallib.php b/course/externallib.php index 18633bdd2f6..29d066acc07 100644 --- a/course/externallib.php +++ b/course/externallib.php @@ -2122,6 +2122,200 @@ class core_course_external extends external_api { ); } + /** + * Returns description of method parameters + * + * @return external_function_parameters + * @since Moodle 3.0 + */ + public static function search_courses_parameters() { + return new external_function_parameters( + array( + 'criterianame' => new external_value(PARAM_ALPHA, 'criteria name + (search, modulelist (only admins), blocklist (only admins), tagid)'), + 'criteriavalue' => new external_value(PARAM_RAW, 'criteria value'), + 'page' => new external_value(PARAM_INT, 'page number (0 based)', VALUE_DEFAULT, 0), + 'perpage' => new external_value(PARAM_INT, 'items per page', VALUE_DEFAULT, 0) + ) + ); + } + + /** + * Search courses following the specified criteria. + * + * @param string $criterianame Criteria name (search, modulelist (only admins), blocklist (only admins), tagid) + * @param string $criteriavalue Criteria value + * @param int $page Page number (for pagination) + * @param int $perpage Items per page + * @return array of course objects and warnings + * @since Moodle 3.0 + * @throws moodle_exception + */ + public static function search_courses($criterianame, $criteriavalue, $page=0, $perpage=0) { + global $CFG; + require_once($CFG->libdir . '/coursecatlib.php'); + + $warnings = array(); + + $parameters = array( + 'criterianame' => $criterianame, + 'criteriavalue' => $criteriavalue, + 'page' => $page, + 'perpage' => $perpage + ); + $params = self::validate_parameters(self::search_courses_parameters(), $parameters); + + $allowedcriterianames = array('search', 'modulelist', 'blocklist', 'tagid'); + if (!in_array($params['criterianame'], $allowedcriterianames)) { + throw new invalid_parameter_exception('Invalid value for criterianame parameter (value: '.$params['criterianame'].'),' . + 'allowed values are: '.implode(',', $allowedcriterianames)); + } + + if ($params['criterianame'] == 'modulelist' or $params['criterianame'] == 'blocklist') { + require_capability('moodle/site:config', context_system::instance()); + } + + $paramtype = array( + 'search' => PARAM_RAW, + 'modulelist' => PARAM_PLUGIN, + 'blocklist' => PARAM_INT, + 'tagid' => PARAM_INT + ); + $params['criteriavalue'] = clean_param($params['criteriavalue'], $paramtype[$params['criterianame']]); + + // Prepare the search API options. + $searchcriteria = array(); + $searchcriteria[$params['criterianame']] = $params['criteriavalue']; + + $options = array(); + if ($params['perpage'] != 0) { + $offset = $params['page'] * $params['perpage']; + $options = array('offset' => $offset, 'limit' => $params['perpage']); + } + + // Search the courses. + $courses = coursecat::search_courses($searchcriteria, $options); + $totalcount = coursecat::search_courses_count($searchcriteria); + + $finalcourses = array(); + $categoriescache = array(); + + foreach ($courses as $course) { + + $coursecontext = context_course::instance($course->id); + + // Category information. + if (!isset($categoriescache[$course->category])) { + $categoriescache[$course->category] = coursecat::get($course->category); + } + $category = $categoriescache[$course->category]; + + // Retrieve course overfiew used files. + $files = array(); + foreach ($course->get_course_overviewfiles() as $file) { + $fileurl = moodle_url::make_webservice_pluginfile_url($file->get_contextid(), $file->get_component(), + $file->get_filearea(), null, $file->get_filepath(), + $file->get_filename())->out(false); + $files[] = array( + 'filename' => $file->get_filename(), + 'fileurl' => $fileurl, + 'filesize' => $file->get_filesize() + ); + } + + // Retrieve the course contacts, + // we need here the users fullname since if we are not enrolled can be difficult to obtain them via other Web Services. + $coursecontacts = array(); + foreach ($course->get_course_contacts() as $contact) { + $coursecontacts[] = array( + 'id' => $contact['user']->id, + 'fullname' => $contact['username'] + ); + } + + // Allowed enrolment methods (maybe we can self-enrol). + $enroltypes = array(); + $instances = enrol_get_instances($course->id, true); + foreach ($instances as $instance) { + $enroltypes[] = $instance->enrol; + } + + // Format summary. + list($summary, $summaryformat) = + external_format_text($course->summary, $course->summaryformat, $coursecontext->id, 'course', 'summary', null); + + $coursereturns = array(); + $coursereturns['id'] = $course->id; + $coursereturns['fullname'] = $course->get_formatted_fullname(); + $coursereturns['shortname'] = $course->get_formatted_shortname(); + $coursereturns['categoryid'] = $course->category; + $coursereturns['categoryname'] = $category->name; + $coursereturns['summary'] = $summary; + $coursereturns['summaryformat'] = $summaryformat; + $coursereturns['overviewfiles'] = $files; + $coursereturns['contacts'] = $coursecontacts; + $coursereturns['enrollmentmethods'] = $enroltypes; + $finalcourses[] = $coursereturns; + } + + return array( + 'total' => $totalcount, + 'courses' => $finalcourses, + 'warnings' => $warnings + ); + } + + /** + * Returns description of method result value + * + * @return external_description + * @since Moodle 3.0 + */ + public static function search_courses_returns() { + + return new external_single_structure( + array( + 'total' => new external_value(PARAM_INT, 'total course count'), + 'courses' => new external_multiple_structure( + new external_single_structure( + array( + 'id' => new external_value(PARAM_INT, 'course id'), + 'fullname' => new external_value(PARAM_TEXT, 'course full name'), + 'shortname' => new external_value(PARAM_TEXT, 'course short name'), + 'categoryid' => new external_value(PARAM_INT, 'category id'), + 'categoryname' => new external_value(PARAM_TEXT, 'category name'), + 'summary' => new external_value(PARAM_RAW, 'summary'), + 'summaryformat' => new external_format_value('summary'), + 'overviewfiles' => new external_multiple_structure( + new external_single_structure( + array( + 'filename' => new external_value(PARAM_FILE, 'overview file name'), + 'fileurl' => new external_value(PARAM_URL, 'overview file url'), + 'filesize' => new external_value(PARAM_INT, 'overview file size'), + ) + ), + 'additional overview files attached to this course' + ), + 'contacts' => new external_multiple_structure( + new external_single_structure( + array( + 'id' => new external_value(PARAM_INT, 'contact user id'), + 'fullname' => new external_value(PARAM_NOTAGS, 'contact user fullname'), + ) + ), + 'contact users' + ), + 'enrollmentmethods' => new external_multiple_structure( + new external_value(PARAM_PLUGIN, 'enrollment method'), + 'enrollment methods list' + ), + ) + ), 'course' + ), + 'warnings' => new external_warnings() + ) + ); + } } /** diff --git a/course/tests/externallib_test.php b/course/tests/externallib_test.php index 1b8bd90075e..668c2526c51 100644 --- a/course/tests/externallib_test.php +++ b/course/tests/externallib_test.php @@ -600,6 +600,71 @@ class core_course_externallib_testcase extends externallib_advanced_testcase { $this->assertEquals($DB->count_records('course'), count($courses)); } + /** + * Test search_courses + */ + public function test_search_courses () { + + global $DB, $CFG; + require_once($CFG->dirroot . '/tag/lib.php'); + + $this->resetAfterTest(true); + $this->setAdminUser(); + $generatedcourses = array(); + $coursedata1['fullname'] = 'FIRST COURSE'; + $course1 = self::getDataGenerator()->create_course($coursedata1); + $coursedata2['fullname'] = 'SECOND COURSE'; + $course2 = self::getDataGenerator()->create_course($coursedata2); + // Search by name. + $results = core_course_external::search_courses('search', 'FIRST'); + $results = external_api::clean_returnvalue(core_course_external::search_courses_returns(), $results); + $this->assertEquals($coursedata1['fullname'], $results['courses'][0]['fullname']); + $this->assertCount(1, $results['courses']); + + // Create the forum. + $record = new stdClass(); + $record->introformat = FORMAT_HTML; + $record->course = $course2->id; + // Set Aggregate type = Average of ratings. + $forum = self::getDataGenerator()->create_module('forum', $record); + + // Search by module. + $results = core_course_external::search_courses('modulelist', 'forum'); + $results = external_api::clean_returnvalue(core_course_external::search_courses_returns(), $results); + $this->assertEquals(1, $results['total']); + + // Enable coursetag option. + set_config('block_tags_showcoursetags', true); + // Add tag 'TAG-LABEL ON SECOND COURSE' to Course2. + tag_set('course', $course2->id, array('TAG-LABEL ON SECOND COURSE'), 'core', context_course::instance($course2->id)->id); + $taginstance = $DB->get_record('tag_instance', array('itemtype' => 'course', 'itemid' => $course2->id), '*', MUST_EXIST); + // Search by tagid. + $results = core_course_external::search_courses('tagid', $taginstance->tagid); + $results = external_api::clean_returnvalue(core_course_external::search_courses_returns(), $results); + $this->assertEquals($coursedata2['fullname'], $results['courses'][0]['fullname']); + + // Search by block (use news_items default block). + $blockid = $DB->get_field('block', 'id', array('name' => 'news_items')); + $results = core_course_external::search_courses('blocklist', $blockid); + $results = external_api::clean_returnvalue(core_course_external::search_courses_returns(), $results); + $this->assertEquals(2, $results['total']); + + // Now as a normal user. + $user = self::getDataGenerator()->create_user(); + $this->setUser($user); + + $results = core_course_external::search_courses('search', 'FIRST'); + $results = external_api::clean_returnvalue(core_course_external::search_courses_returns(), $results); + $this->assertCount(1, $results['courses']); + $this->assertEquals(1, $results['total']); + $this->assertEquals($coursedata1['fullname'], $results['courses'][0]['fullname']); + + // Search by block (use news_items default block). Should fail (only admins allowed). + $this->setExpectedException('required_capability_exception'); + $results = core_course_external::search_courses('blocklist', $blockid); + + } + /** * Create a course with contents * @return array A list with the course object and course modules objects diff --git a/lib/db/services.php b/lib/db/services.php index ba378420a7c..59239f6ea11 100644 --- a/lib/db/services.php +++ b/lib/db/services.php @@ -653,6 +653,15 @@ $functions = array( 'capabilities'=> 'moodle/course:view,moodle/course:update,moodle/course:viewhiddencourses', ), + 'core_course_search_courses' => array( + 'classname' => 'core_course_external', + 'methodname' => 'search_courses', + 'classpath' => 'course/externallib.php', + 'description' => 'Search courses by (name, module, block, tag)', + 'type' => 'read', + 'capabilities' => '', + ), + 'moodle_course_create_courses' => array( 'classname' => 'moodle_course_external', 'methodname' => 'create_courses', @@ -1143,6 +1152,7 @@ $services = array( 'core_comment_get_comments', 'mod_forum_view_forum', 'core_course_view_course', + 'core_course_search_courses', 'core_completion_get_activities_completion_status', 'core_notes_get_course_notes', 'core_completion_get_course_completion_status', diff --git a/version.php b/version.php index aaf23726acf..626a117a4d7 100644 --- a/version.php +++ b/version.php @@ -29,7 +29,7 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2015091000.00; // YYYYMMDD = weekly release date of this DEV branch. +$version = 2015091000.01; // YYYYMMDD = weekly release date of this DEV branch. // RR = release increments - 00 in DEV branches. // .XX = incremental changes.