diff --git a/mod/wiki/classes/search/collaborative_page.php b/mod/wiki/classes/search/collaborative_page.php new file mode 100644 index 0000000000000..8ed300e09675e --- /dev/null +++ b/mod/wiki/classes/search/collaborative_page.php @@ -0,0 +1,179 @@ +. + +/** + * Search area for mod_wiki collaborative pages. + * + * @package mod_wiki + * @copyright 2016 Eric Merrill {@link http://www.merrilldigital.com} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace mod_wiki\search; + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->dirroot . '/mod/wiki/locallib.php'); + +/** + * Search area for mod_wiki collaborative pages. + * + * @package mod_wiki + * @copyright 2016 Eric Merrill {@link http://www.merrilldigital.com} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class collaborative_page extends \core_search\area\base_mod { + /** + * @var array Cache of wiki records. + */ + protected $wikiscache = array(); + + /** + * Returns a recordset with all required page information. + * + * @param int $modifiedfrom + * @return moodle_recordset + */ + public function get_recordset_by_timestamp($modifiedfrom = 0) { + global $DB; + + $sql = 'SELECT p.*, w.id AS wikiid, w.course AS courseid + FROM {wiki_pages} p + JOIN {wiki_subwikis} s ON s.id = p.subwikiid + JOIN {wiki} w ON w.id = s.wikiid + WHERE p.timemodified >= ? + AND w.wikimode = ? + ORDER BY p.timemodified ASC'; + return $DB->get_recordset_sql($sql, array($modifiedfrom, 'collaborative')); + } + + /** + * Returns the document for a particular page. + * + * @param \stdClass $record A record containing, at least, the indexed document id and a modified timestamp + * @param array $options Options for document creation + * @return \core_search\document + */ + public function get_document($record, $options = array()) { + try { + $cm = $this->get_cm('wiki', $record->wikiid, $record->courseid); + $context = \context_module::instance($cm->id); + } catch (\dml_missing_record_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; + } catch (\dml_exception $ex) { + // Notify it as we run here as admin, we should see everything. + debugging('Error retrieving ' . $this->areaid . ' ' . $record->id . ' document: ' . $ex->getMessage(), DEBUG_DEVELOPER); + return false; + } + + // Make a page object without extra fields. + $page = clone $record; + unset($page->courseid); + unset($page->wikiid); + + // Conversion based wiki_print_page_content(). + // Check if we have passed the cache time. + if ($page->timerendered + WIKI_REFRESH_CACHE_TIME < time()) { + $content = wiki_refresh_cachedcontent($page); + $page = $content['page']; + } + // Convert to HTML, then to text. Makes sure content is cleaned. + $html = format_text($page->cachedcontent, FORMAT_MOODLE, array('overflowdiv' => true, 'allowid' => true)); + $content = content_to_text($page->cachedcontent, FORMAT_HTML); + + // Prepare associative array with data from DB. + $doc = \core_search\document_factory::instance($record->id, $this->componentname, $this->areaname); + $doc->set('title', $record->title); + $doc->set('content', $content); + $doc->set('contextid', $context->id); + $doc->set('courseid', $record->courseid); + $doc->set('owneruserid', \core_search\manager::NO_OWNER_ID); + $doc->set('modified', $record->timemodified); + + // 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; + } + + /** + * Can the current user see the document. + * + * @param int $id The internal search area entity id. + * @return bool True if the user can see it, false otherwise + */ + public function check_access($id) { + global $DB; + + try { + $page = $DB->get_record('wiki_pages', array('id' => $id), '*', MUST_EXIST); + if (!isset($this->wikiscache[$page->subwikiid])) { + $sql = 'SELECT w.* + FROM {wiki_subwikis} s + JOIN {wiki} w ON w.id = s.wikiid + WHERE s.id = ?'; + $this->wikiscache[$page->subwikiid] = $DB->get_record_sql($sql, array('id' => $page->subwikiid), MUST_EXIST); + } + $wiki = $this->wikiscache[$page->subwikiid]; + $cminfo = $this->get_cm('wiki', $wiki->id, $wiki->course); + } catch (\dml_missing_record_exception $ex) { + return \core_search\manager::ACCESS_DELETED; + } catch (\dml_exception $ex) { + return \core_search\manager::ACCESS_DENIED; + } + + // Recheck uservisible although it should have already been checked in core_search. + if ($cminfo->uservisible === false) { + return \core_search\manager::ACCESS_DENIED; + } + + $context = \context_module::instance($cminfo->id); + + if (!has_capability('mod/wiki:viewpage', $context)) { + return \core_search\manager::ACCESS_DENIED; + } + + return \core_search\manager::ACCESS_GRANTED; + } + + /** + * Returns a url to the page. + * + * @param \core_search\document $doc + * @return \moodle_url + */ + public function get_doc_url(\core_search\document $doc) { + $params = array('pageid' => $doc->get('itemid')); + return new \moodle_url('/mod/wiki/view.php', $params); + } + + /** + * Returns a url to the wiki. + * + * @param \core_search\document $doc + * @return \moodle_url + */ + public function get_context_url(\core_search\document $doc) { + $contextmodule = \context::instance_by_id($doc->get('contextid')); + return new \moodle_url('/mod/wiki/view.php', array('id' => $contextmodule->instanceid)); + } +} diff --git a/mod/wiki/lang/en/wiki.php b/mod/wiki/lang/en/wiki.php index 65bf37fb6c634..3b9b707060bb3 100644 --- a/mod/wiki/lang/en/wiki.php +++ b/mod/wiki/lang/en/wiki.php @@ -214,6 +214,7 @@ $string['saving'] = 'Saving wiki page'; $string['savingerror'] = 'Saving error'; $string['search:activity'] = 'Wiki activities'; +$string['search:collaborative_page'] = 'Wiki Pages - Collaborative'; $string['searchcontent'] = 'Search in page content'; $string['searchresult'] = 'Search results:'; $string['searchterms'] = 'Search terms'; diff --git a/mod/wiki/tests/search_test.php b/mod/wiki/tests/search_test.php new file mode 100644 index 0000000000000..424348c56e909 --- /dev/null +++ b/mod/wiki/tests/search_test.php @@ -0,0 +1,160 @@ +. + +/** + * Wiki global search unit tests. + * + * @package mod_wiki + * @category test + * @copyright 2016 Eric Merrill {@link http://www.merrilldigital.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 wiki global search. + * + * @package mod_wiki + * @category test + * @copyright 2016 Eric Merrill {@link http://www.merrilldigital.com} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class mod_wiki_search_testcase extends advanced_testcase { + + /** + * @var string Area id + */ + protected $wikicollabpageareaid = null; + + public function setUp() { + $this->resetAfterTest(true); + $this->setAdminUser(); + set_config('enableglobalsearch', true); + + $this->wikicollabpageareaid = \core_search\manager::generate_areaid('mod_wiki', 'collaborative_page'); + + // 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(); + } + + /** + * Availability. + * + * @return void + */ + public function test_search_enabled() { + $searcharea = \core_search\manager::get_search_area($this->wikicollabpageareaid); + list($componentname, $varname) = $searcharea->get_config_var_name(); + + // Enabled by default once global search is enabled. + $this->assertTrue($searcharea->is_enabled()); + + set_config($varname . '_enabled', false, $componentname); + $this->assertFalse($searcharea->is_enabled()); + + set_config($varname . '_enabled', true, $componentname); + $this->assertTrue($searcharea->is_enabled()); + } + + /** + * Indexing collaborative page contents. + * + * @return void + */ + public function test_collaborative_page_indexing() { + global $DB; + + // Returns the instance as long as the area is supported. + $searcharea = \core_search\manager::get_search_area($this->wikicollabpageareaid); + $this->assertInstanceOf('\mod_wiki\search\collaborative_page', $searcharea); + + $wikigenerator = $this->getDataGenerator()->get_plugin_generator('mod_wiki'); + $course1 = self::getDataGenerator()->create_course(); + + $collabwiki = $this->getDataGenerator()->create_module('wiki', array('course' => $course1->id)); + $cpage1 = $wikigenerator->create_first_page($collabwiki); + $cpage2 = $wikigenerator->create_content($collabwiki); + $cpage3 = $wikigenerator->create_content($collabwiki); + + $indwiki = $this->getDataGenerator()->create_module('wiki', array('course' => $course1->id, 'wikimode' => 'individual')); + $ipage1 = $wikigenerator->create_first_page($indwiki); + $ipage2 = $wikigenerator->create_content($indwiki); + $ipage3 = $wikigenerator->create_content($indwiki); + + // 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); + + // Static caches are working. + $dbreads = $DB->perf_get_reads(); + $doc = $searcharea->get_document($record); + $this->assertEquals($dbreads, $DB->perf_get_reads()); + $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(); + + // We expect 3 (not 6) pages. + $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(); + } + + /** + * Check collaborative_page check access. + * + * @return void + */ + public function test_collaborative_page_check_access() { + global $DB; + + // Returns the instance as long as the area is supported. + $searcharea = \core_search\manager::get_search_area($this->wikicollabpageareaid); + $this->assertInstanceOf('\mod_wiki\search\collaborative_page', $searcharea); + + $user1 = self::getDataGenerator()->create_user(); + $course1 = self::getDataGenerator()->create_course(); + $this->getDataGenerator()->enrol_user($user1->id, $course1->id, 'student'); + + $wikigenerator = $this->getDataGenerator()->get_plugin_generator('mod_wiki'); + + $collabwiki = $this->getDataGenerator()->create_module('wiki', array('course' => $course1->id)); + $cpage1 = $wikigenerator->create_first_page($collabwiki); + + $this->setAdminUser(); + $this->assertEquals(\core_search\manager::ACCESS_GRANTED, $searcharea->check_access($cpage1->id)); + + $this->setUser($user1); + $this->assertEquals(\core_search\manager::ACCESS_GRANTED, $searcharea->check_access($cpage1->id)); + + $this->assertEquals(\core_search\manager::ACCESS_DELETED, $searcharea->check_access($cpage1->id + 10)); + } +}