diff --git a/course/classes/search/mycourse.php b/course/classes/search/mycourse.php new file mode 100644 index 00000000000..a2161f60eaf --- /dev/null +++ b/course/classes/search/mycourse.php @@ -0,0 +1,120 @@ +. + +/** + * Search area for Moodle courses I can access. + * + * @package core_course + * @copyright 2016 Skylar Kelty + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace core_course\search; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Search area for Moodle courses I can access. + * + * @package core_course + * @copyright 2016 Skylar Kelty + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class mycourse extends \core_search\area\base { + + /** + * Returns recordset containing required data for indexing courses. + * + * @param int $modifiedfrom timestamp + * @return \moodle_recordset + */ + public function get_recordset_by_timestamp($modifiedfrom = 0) { + global $DB; + return $DB->get_recordset_select('course', 'timemodified >= ?', array($modifiedfrom)); + } + + /** + * Returns the document associated with this course. + * + * @param stdClass $record + * @param array $options + * @return \core_search\document + */ + public function get_document($record, $options = array()) { + try { + $context = \context_course::instance($record->id); + } catch (\moodle_exception $ex) { + // Notify it as we run here as admin, we should see everything. + debugging('Error retrieving ' . $this->areaid . ' ' . $record->id . ' document, not all required data is available: ' . + $ex->getMessage(), DEBUG_DEVELOPER); + return false; + } + // Prepare associative array with data from DB. + $doc = \core_search\document_factory::instance($record->id, $this->componentname, $this->areaname); + $doc->set('title', $record->fullname); + $doc->set('content', content_to_text($record->summary, $record->summaryformat)); + $doc->set('contextid', $context->id); + $doc->set('courseid', $record->id); + $doc->set('owneruserid', \core_search\manager::NO_OWNER_ID); + $doc->set('modified', $record->timemodified); + $doc->set('description1', $record->shortname); + + // Check if this document should be considered new. + if (isset($options['lastindexedtime']) && $options['lastindexedtime'] < $record->timecreated) { + // If the document was created after the last index time, it must be new. + $doc->set_is_new(true); + } + + return $doc; + } + + /** + * Whether the user can access the document or not. + * + * @param int $id The course instance id. + * @return int + */ + public function check_access($id) { + global $DB; + $course = $DB->get_record('course', array('id' => $id)); + if (!$course) { + return \core_search\manager::ACCESS_DELETED; + } + if (can_access_course($course)) { + return \core_search\manager::ACCESS_GRANTED; + } + return \core_search\manager::ACCESS_DENIED; + } + + /** + * Link to the course. + * + * @param \core_search\document $doc + * @return \moodle_url + */ + public function get_doc_url(\core_search\document $doc) { + return $this->get_context_url($doc); + } + + /** + * Link to the course. + * + * @param \core_search\document $doc + * @return \moodle_url + */ + public function get_context_url(\core_search\document $doc) { + return new \moodle_url('/course/view.php', array('id' => $doc->get('courseid'))); + } +} diff --git a/course/tests/search_test.php b/course/tests/search_test.php new file mode 100644 index 00000000000..6750fbfb1bb --- /dev/null +++ b/course/tests/search_test.php @@ -0,0 +1,164 @@ +. + +/** + * Course global search unit tests. + * + * @package core + * @category phpunit + * @copyright 2016 David Monllao {@link http://www.davidmonllao.com} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once($CFG->dirroot . '/search/tests/fixtures/testable_core_search.php'); + +/** + * Provides the unit tests for course global search. + * + * @package core + * @category phpunit + * @copyright 2016 David Monllao {@link http://www.davidmonllao.com} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class course_search_testcase extends advanced_testcase { + + /** + * @var string Area id + */ + protected $mycoursesareaid = null; + + public function setUp() { + $this->resetAfterTest(true); + set_config('enableglobalsearch', true); + + $this->mycoursesareaid = \core_search\manager::generate_areaid('core_course', 'mycourse'); + + // Set \core_search::instance to the mock_search_engine as we don't require the search engine to be working to test this. + $search = testable_core_search::instance(); + } + + /** + * Indexing my courses contents. + * + * @return void + */ + public function test_mycourses_indexing() { + + // Returns the instance as long as the area is supported. + $searcharea = \core_search\manager::get_search_area($this->mycoursesareaid); + $this->assertInstanceOf('\core_course\search\mycourse', $searcharea); + + $user1 = self::getDataGenerator()->create_user(); + $user2 = self::getDataGenerator()->create_user(); + + $course1 = self::getDataGenerator()->create_course(); + $course2 = self::getDataGenerator()->create_course(); + + $this->getDataGenerator()->enrol_user($user1->id, $course1->id, 'student'); + $this->getDataGenerator()->enrol_user($user2->id, $course1->id, 'student'); + + $record = new stdClass(); + $record->course = $course1->id; + + // All records. + $recordset = $searcharea->get_recordset_by_timestamp(0); + $this->assertTrue($recordset->valid()); + $nrecords = 0; + foreach ($recordset as $record) { + $this->assertInstanceOf('stdClass', $record); + $doc = $searcharea->get_document($record); + $this->assertInstanceOf('\core_search\document', $doc); + $nrecords++; + } + // If there would be an error/failure in the foreach above the recordset would be closed on shutdown. + $recordset->close(); + $this->assertEquals(3, $nrecords); + + // The +2 is to prevent race conditions. + $recordset = $searcharea->get_recordset_by_timestamp(time() + 2); + + // No new records. + $this->assertFalse($recordset->valid()); + $recordset->close(); + } + + /** + * Document contents. + * + * @return void + */ + public function test_mycourses_document() { + + // Returns the instance as long as the area is supported. + $searcharea = \core_search\manager::get_search_area($this->mycoursesareaid); + $this->assertInstanceOf('\core_course\search\mycourse', $searcharea); + + $user = self::getDataGenerator()->create_user(); + $course = self::getDataGenerator()->create_course(); + $this->getDataGenerator()->enrol_user($user->id, $course->id, 'teacher'); + + $doc = $searcharea->get_document($course); + $this->assertInstanceOf('\core_search\document', $doc); + $this->assertEquals($course->id, $doc->get('itemid')); + $this->assertEquals($this->mycoursesareaid . '-' . $course->id, $doc->get('id')); + $this->assertEquals($course->id, $doc->get('courseid')); + $this->assertFalse($doc->is_set('userid')); + $this->assertEquals(\core_search\manager::NO_OWNER_ID, $doc->get('owneruserid')); + $this->assertEquals($course->fullname, $doc->get('title')); + + // Not nice. Applying \core_search\document::set line breaks clean up. + $summary = preg_replace("/\s+/", ' ', trim(content_to_text($course->summary, $course->summaryformat), "\r\n")); + $this->assertEquals($summary, $doc->get('content')); + $this->assertEquals($course->shortname, $doc->get('description1')); + } + + /** + * Document accesses. + * + * @return void + */ + public function test_mycourses_access() { + + // Returns the instance as long as the area is supported. + $searcharea = \core_search\manager::get_search_area($this->mycoursesareaid); + + $user1 = self::getDataGenerator()->create_user(); + $user2 = self::getDataGenerator()->create_user(); + + $course1 = self::getDataGenerator()->create_course(); + $course2 = self::getDataGenerator()->create_course(array('visible' => 0)); + $course3 = self::getDataGenerator()->create_course(); + + $this->getDataGenerator()->enrol_user($user1->id, $course1->id, 'teacher'); + $this->getDataGenerator()->enrol_user($user2->id, $course1->id, 'student'); + $this->getDataGenerator()->enrol_user($user1->id, $course2->id, 'teacher'); + $this->getDataGenerator()->enrol_user($user2->id, $course2->id, 'student'); + + $this->setUser($user1); + $this->assertEquals(\core_search\manager::ACCESS_GRANTED, $searcharea->check_access($course1->id)); + $this->assertEquals(\core_search\manager::ACCESS_GRANTED, $searcharea->check_access($course2->id)); + $this->assertEquals(\core_search\manager::ACCESS_DENIED, $searcharea->check_access($course3->id)); + $this->assertEquals(\core_search\manager::ACCESS_DELETED, $searcharea->check_access(-123)); + + $this->setUser($user2); + $this->assertEquals(\core_search\manager::ACCESS_GRANTED, $searcharea->check_access($course1->id)); + $this->assertEquals(\core_search\manager::ACCESS_DENIED, $searcharea->check_access($course2->id)); + $this->assertEquals(\core_search\manager::ACCESS_DENIED, $searcharea->check_access($course3->id)); + } +} diff --git a/lang/en/search.php b/lang/en/search.php index be993f38a6a..c7219ec99d6 100644 --- a/lang/en/search.php +++ b/lang/en/search.php @@ -80,6 +80,7 @@ $string['runindexer'] = 'Run indexer (real)'; $string['runindexertest'] = 'Run indexer test'; $string['score'] = 'Score'; $string['search'] = 'Search'; +$string['search:mycourse'] = 'My courses'; $string['searcharea'] = 'Search area'; $string['searching'] = 'Searching in ...'; $string['searchnotpermitted'] = 'You are not allowed to do a search'; diff --git a/search/classes/area/base.php b/search/classes/area/base.php index 78aa3b840b6..e7caa34061d 100644 --- a/search/classes/area/base.php +++ b/search/classes/area/base.php @@ -136,7 +136,14 @@ abstract class base { * @return string */ public function get_visible_name($lazyload = false) { - return get_string('search:' . $this->areaname, $this->componentname, null, $lazyload); + + $component = $this->componentname; + + // Core subsystem strings go to lang/XX/search.php. + if ($this->componenttype === 'core') { + $component = 'search'; + } + return get_string('search:' . $this->areaname, $component, null, $lazyload); } /** @@ -150,8 +157,9 @@ abstract class base { public function get_config_var_name() { if ($this->componenttype === 'core') { - // Core subsystems config in search. - return array('search', $this->areaid); + // Core subsystems config in core_search and setting name using only [a-zA-Z0-9_]+. + $parts = \core_search\manager::extract_areaid_parts($this->areaid); + return array('core_search', $parts[0] . '_' . $parts[1]); } // Plugins config in the plugin scope. diff --git a/version.php b/version.php index 27988f2f4b1..aa11e662f2f 100644 --- a/version.php +++ b/version.php @@ -29,7 +29,7 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2016051000.00; // YYYYMMDD = weekly release date of this DEV branch. +$version = 2016051000.01; // YYYYMMDD = weekly release date of this DEV branch. // RR = release increments - 00 in DEV branches. // .XX = incremental changes.