mirror of
https://github.com/moodle/moodle.git
synced 2025-08-04 16:36:37 +02:00
MDL-55356 core_search: Change existing search areas to new API
This change considers all existing search areas in Moodle and makes necessary changes. Custom change to course search, supported by helper in base.php: * course/classes/search/mycourse.php Custom change to message search: * message/classes/search/message_received.php * message/classes/search/message_sent.php Custom change to user search: * user/classes/search/user.php Custom changes to module areas, supported by helper in base_mod.php: * mod/book/classes/search/chapter.php * mod/data/classes/search/entry.php * mod/forum/classes/search/post.php * mod/glossary/classes/search/entry.php * mod/survey/classes/search/activity.php * mod/wiki/classes/search/collaborative_page.php (Note: the unit tests do not exhaustively check every context type for these, given that's mainly handled by the helper function which was already tested in the base_activity test.) Handled by block base class (no change): * blocks/html/classes/search/content.php Handled by activity base class (no change): * mod/assign/classes/search/activity.php * mod/book/classes/search/activity.php * mod/chat/classes/search/activity.php * mod/choice/classes/search/activity.php * mod/data/classes/search/activity.php * mod/feedback/classes/search/activity.php * mod/folder/classes/search/activity.php * mod/forum/classes/search/activity.php * mod/glossary/classes/search/activity.php * mod/imscp/classes/search/activity.php * mod/label/classes/search/activity.php * mod/lesson/classes/search/activity.php * mod/lti/classes/search/activity.php * mod/page/classes/search/activity.php * mod/quiz/classes/search/activity.php * mod/resource/classes/search/activity.php * mod/scorm/classes/search/activity.php * mod/url/classes/search/activity.php * mod/wiki/classes/search/activity.php * mod/workshop/classes/search/activity.php
This commit is contained in:
parent
81a988833e
commit
66e3702680
21 changed files with 584 additions and 58 deletions
|
@ -45,11 +45,24 @@ class mycourse extends \core_search\base {
|
||||||
* Returns recordset containing required data for indexing courses.
|
* Returns recordset containing required data for indexing courses.
|
||||||
*
|
*
|
||||||
* @param int $modifiedfrom timestamp
|
* @param int $modifiedfrom timestamp
|
||||||
* @return \moodle_recordset
|
* @param \context|null $context Restriction context
|
||||||
|
* @return \moodle_recordset|null Recordset or null if no change possible
|
||||||
*/
|
*/
|
||||||
public function get_recordset_by_timestamp($modifiedfrom = 0) {
|
public function get_document_recordset($modifiedfrom = 0, \context $context = null) {
|
||||||
global $DB;
|
global $DB;
|
||||||
return $DB->get_recordset_select('course', 'timemodified >= ?', array($modifiedfrom), 'timemodified ASC');
|
|
||||||
|
list ($contextjoin, $contextparams) = $this->get_course_level_context_restriction_sql(
|
||||||
|
$context, 'c');
|
||||||
|
if ($contextjoin === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $DB->get_recordset_sql("
|
||||||
|
SELECT c.*
|
||||||
|
FROM {course} c
|
||||||
|
$contextjoin
|
||||||
|
WHERE c.timemodified >= ?
|
||||||
|
ORDER BY c.timemodified ASC", array_merge($contextparams, [$modifiedfrom]));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -98,6 +98,85 @@ class course_search_testcase extends advanced_testcase {
|
||||||
$recordset->close();
|
$recordset->close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests course indexing support for contexts.
|
||||||
|
*/
|
||||||
|
public function test_mycourses_indexing_contexts() {
|
||||||
|
global $DB, $USER, $SITE;
|
||||||
|
|
||||||
|
$searcharea = \core_search\manager::get_search_area($this->mycoursesareaid);
|
||||||
|
|
||||||
|
// Create some courses in categories, and a forum.
|
||||||
|
$generator = $this->getDataGenerator();
|
||||||
|
$cat1 = $generator->create_category();
|
||||||
|
$course1 = $generator->create_course(['category' => $cat1->id]);
|
||||||
|
$cat2 = $generator->create_category(['parent' => $cat1->id]);
|
||||||
|
$course2 = $generator->create_course(['category' => $cat2->id]);
|
||||||
|
$cat3 = $generator->create_category();
|
||||||
|
$course3 = $generator->create_course(['category' => $cat3->id]);
|
||||||
|
$forum = $generator->create_module('forum', ['course' => $course1->id]);
|
||||||
|
$DB->set_field('course', 'timemodified', 0, ['id' => $SITE->id]);
|
||||||
|
$DB->set_field('course', 'timemodified', 1, ['id' => $course1->id]);
|
||||||
|
$DB->set_field('course', 'timemodified', 2, ['id' => $course2->id]);
|
||||||
|
$DB->set_field('course', 'timemodified', 3, ['id' => $course3->id]);
|
||||||
|
|
||||||
|
// Find the first block to use for a block context.
|
||||||
|
$blockid = array_values($DB->get_records('block_instances', null, 'id', 'id', 0, 1))[0]->id;
|
||||||
|
$blockcontext = context_block::instance($blockid);
|
||||||
|
|
||||||
|
// Check with block context - should be null.
|
||||||
|
$this->assertNull($searcharea->get_document_recordset(0, $blockcontext));
|
||||||
|
|
||||||
|
// Check with user context - should be null.
|
||||||
|
$this->setAdminUser();
|
||||||
|
$usercontext = context_user::instance($USER->id);
|
||||||
|
$this->assertNull($searcharea->get_document_recordset(0, $usercontext));
|
||||||
|
|
||||||
|
// Check with module context - should be null.
|
||||||
|
$modcontext = context_module::instance($forum->cmid);
|
||||||
|
$this->assertNull($searcharea->get_document_recordset(0, $modcontext));
|
||||||
|
|
||||||
|
// Check with course context - should return specified course if timestamp allows.
|
||||||
|
$coursecontext = context_course::instance($course3->id);
|
||||||
|
$results = self::recordset_to_ids($searcharea->get_document_recordset(3, $coursecontext));
|
||||||
|
$this->assertEquals([$course3->id], $results);
|
||||||
|
$results = self::recordset_to_ids($searcharea->get_document_recordset(4, $coursecontext));
|
||||||
|
$this->assertEquals([], $results);
|
||||||
|
|
||||||
|
// Check with category context - should return course in categories and subcategories.
|
||||||
|
$catcontext = context_coursecat::instance($cat1->id);
|
||||||
|
$results = self::recordset_to_ids($searcharea->get_document_recordset(0, $catcontext));
|
||||||
|
$this->assertEquals([$course1->id, $course2->id], $results);
|
||||||
|
$results = self::recordset_to_ids($searcharea->get_document_recordset(2, $catcontext));
|
||||||
|
$this->assertEquals([$course2->id], $results);
|
||||||
|
|
||||||
|
// Check with system context and null - should return all these courses + site course.
|
||||||
|
$systemcontext = context_system::instance();
|
||||||
|
$results = self::recordset_to_ids($searcharea->get_document_recordset(0, $systemcontext));
|
||||||
|
$this->assertEquals([$SITE->id, $course1->id, $course2->id, $course3->id], $results);
|
||||||
|
$results = self::recordset_to_ids($searcharea->get_document_recordset(0, null));
|
||||||
|
$this->assertEquals([$SITE->id, $course1->id, $course2->id, $course3->id], $results);
|
||||||
|
$results = self::recordset_to_ids($searcharea->get_document_recordset(3, $systemcontext));
|
||||||
|
$this->assertEquals([$course3->id], $results);
|
||||||
|
$results = self::recordset_to_ids($searcharea->get_document_recordset(3, null));
|
||||||
|
$this->assertEquals([$course3->id], $results);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility function to convert recordset to array of IDs for testing.
|
||||||
|
*
|
||||||
|
* @param moodle_recordset $rs Recordset to convert (and close)
|
||||||
|
* @return array Array of IDs from records indexed by number (0, 1, 2, ...)
|
||||||
|
*/
|
||||||
|
protected static function recordset_to_ids(moodle_recordset $rs) {
|
||||||
|
$results = [];
|
||||||
|
foreach ($rs as $rec) {
|
||||||
|
$results[] = $rec->id;
|
||||||
|
}
|
||||||
|
$rs->close();
|
||||||
|
return $results;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Document contents.
|
* Document contents.
|
||||||
*
|
*
|
||||||
|
|
|
@ -132,4 +132,53 @@ abstract class base_message extends \core_search\base {
|
||||||
return $users;
|
return $users;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to implement get_document_recordset for subclasses.
|
||||||
|
*
|
||||||
|
* @param int $modifiedfrom Modified from date
|
||||||
|
* @param \context|null $context Context or null
|
||||||
|
* @param string $userfield Name of user field (from or to) being considered
|
||||||
|
* @return \moodle_recordset|null Recordset or null if no results possible
|
||||||
|
* @throws \coding_exception If context invalid
|
||||||
|
*/
|
||||||
|
protected function get_document_recordset_helper($modifiedfrom, \context $context = null,
|
||||||
|
$userfield) {
|
||||||
|
global $DB;
|
||||||
|
|
||||||
|
// Set up basic query.
|
||||||
|
$where = $userfield . ' != :noreplyuser AND ' . $userfield .
|
||||||
|
' != :supportuser AND timecreated >= :modifiedfrom';
|
||||||
|
$params = [
|
||||||
|
'noreplyuser' => \core_user::NOREPLY_USER,
|
||||||
|
'supportuser' => \core_user::SUPPORT_USER,
|
||||||
|
'modifiedfrom' => $modifiedfrom
|
||||||
|
];
|
||||||
|
|
||||||
|
// Check context to see whether to add other restrictions.
|
||||||
|
if ($context === null) {
|
||||||
|
$context = \context_system::instance();
|
||||||
|
}
|
||||||
|
switch ($context->contextlevel) {
|
||||||
|
case CONTEXT_COURSECAT:
|
||||||
|
case CONTEXT_COURSE:
|
||||||
|
case CONTEXT_MODULE:
|
||||||
|
case CONTEXT_BLOCK:
|
||||||
|
// There are no messages in any of these contexts so nothing can be found.
|
||||||
|
return null;
|
||||||
|
|
||||||
|
case CONTEXT_USER:
|
||||||
|
// Add extra restriction to specific user context.
|
||||||
|
$where .= ' AND ' . $userfield . ' = :userid';
|
||||||
|
$params['userid'] = $context->instanceid;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CONTEXT_SYSTEM:
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new \coding_exception('Unexpected contextlevel: ' . $context->contextlevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $DB->get_recordset_select('message_read', $where, $params, 'timeread ASC');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,19 +36,14 @@ defined('MOODLE_INTERNAL') || die();
|
||||||
class message_received extends base_message {
|
class message_received extends base_message {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns recordset containing message records.
|
* Returns a recordset with the messages for indexing.
|
||||||
*
|
*
|
||||||
* @param int $modifiedfrom timestamp
|
* @param int $modifiedfrom
|
||||||
* @return \moodle_recordset
|
* @param \context|null $context Optional context to restrict scope of returned results
|
||||||
|
* @return moodle_recordset|null Recordset (or null if no results)
|
||||||
*/
|
*/
|
||||||
public function get_recordset_by_timestamp($modifiedfrom = 0) {
|
public function get_document_recordset($modifiedfrom = 0, \context $context = null) {
|
||||||
global $DB;
|
return $this->get_document_recordset_helper($modifiedfrom, $context, 'useridto');
|
||||||
|
|
||||||
// We don't want to index messages received from noreply and support users.
|
|
||||||
$params = array('modifiedfrom' => $modifiedfrom, 'noreplyuser' => \core_user::NOREPLY_USER,
|
|
||||||
'supportuser' => \core_user::SUPPORT_USER);
|
|
||||||
return $DB->get_recordset_select('message_read', 'timeread >= :modifiedfrom AND
|
|
||||||
useridto != :noreplyuser AND useridto != :supportuser', $params, 'timeread ASC');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -35,19 +35,14 @@ defined('MOODLE_INTERNAL') || die();
|
||||||
class message_sent extends base_message {
|
class message_sent extends base_message {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns recordset containing message records.
|
* Returns a recordset with the messages for indexing.
|
||||||
*
|
*
|
||||||
* @param int $modifiedfrom timestamp
|
* @param int $modifiedfrom
|
||||||
* @return \moodle_recordset
|
* @param \context|null $context Optional context to restrict scope of returned results
|
||||||
|
* @return moodle_recordset|null Recordset (or null if no results)
|
||||||
*/
|
*/
|
||||||
public function get_recordset_by_timestamp($modifiedfrom = 0) {
|
public function get_document_recordset($modifiedfrom = 0, \context $context = null) {
|
||||||
global $DB;
|
return $this->get_document_recordset_helper($modifiedfrom, $context, 'useridfrom');
|
||||||
|
|
||||||
// We don't want to index messages sent by noreply and support users.
|
|
||||||
$params = array('modifiedfrom' => $modifiedfrom, 'noreplyuser' => \core_user::NOREPLY_USER,
|
|
||||||
'supportuser' => \core_user::SUPPORT_USER);
|
|
||||||
return $DB->get_recordset_select('message_read', 'timeread >= :modifiedfrom AND
|
|
||||||
useridfrom != :noreplyuser AND useridfrom != :supportuser', $params, 'timeread ASC');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -113,6 +113,71 @@ class message_received_search_testcase extends advanced_testcase {
|
||||||
$recordset->close();
|
$recordset->close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indexing messages, with restricted contexts.
|
||||||
|
*/
|
||||||
|
public function test_message_received_indexing_contexts() {
|
||||||
|
global $SITE;
|
||||||
|
require_once(__DIR__ . '/search_sent_test.php');
|
||||||
|
|
||||||
|
$searcharea = \core_search\manager::get_search_area($this->messagereceivedareaid);
|
||||||
|
|
||||||
|
$user1 = self::getDataGenerator()->create_user();
|
||||||
|
$user2 = self::getDataGenerator()->create_user();
|
||||||
|
|
||||||
|
$this->preventResetByRollback();
|
||||||
|
$sink = $this->redirectMessages();
|
||||||
|
|
||||||
|
// Send first message.
|
||||||
|
$message = new \core\message\message();
|
||||||
|
$message->courseid = SITEID;
|
||||||
|
$message->userfrom = $user1;
|
||||||
|
$message->userto = $user2;
|
||||||
|
$message->subject = 'Test1';
|
||||||
|
$message->smallmessage = 'Test small messsage';
|
||||||
|
$message->fullmessage = 'Test full messsage';
|
||||||
|
$message->fullmessageformat = 0;
|
||||||
|
$message->fullmessagehtml = null;
|
||||||
|
$message->notification = 0;
|
||||||
|
$message->component = 'moodle';
|
||||||
|
$message->name = 'instantmessage';
|
||||||
|
message_send($message);
|
||||||
|
|
||||||
|
// Ensure that ordering by timestamp will return in consistent order.
|
||||||
|
$this->waitForSecond();
|
||||||
|
|
||||||
|
// Send second message in opposite direction.
|
||||||
|
$message = new \core\message\message();
|
||||||
|
$message->courseid = SITEID;
|
||||||
|
$message->userfrom = $user2;
|
||||||
|
$message->userto = $user1;
|
||||||
|
$message->subject = 'Test2';
|
||||||
|
$message->smallmessage = 'Test small messsage';
|
||||||
|
$message->fullmessage = 'Test full messsage';
|
||||||
|
$message->fullmessageformat = 0;
|
||||||
|
$message->fullmessagehtml = null;
|
||||||
|
$message->notification = 0;
|
||||||
|
$message->component = 'moodle';
|
||||||
|
$message->name = 'instantmessage';
|
||||||
|
message_send($message);
|
||||||
|
|
||||||
|
// Test function with null context and system context (same).
|
||||||
|
$rs = $searcharea->get_document_recordset(0, null);
|
||||||
|
$this->assertEquals(['Test1', 'Test2'], message_sent_search_testcase::recordset_to_subjects($rs));
|
||||||
|
$rs = $searcharea->get_document_recordset(0, context_system::instance());
|
||||||
|
$this->assertEquals(['Test1', 'Test2'], message_sent_search_testcase::recordset_to_subjects($rs));
|
||||||
|
|
||||||
|
// Test with user context for each user.
|
||||||
|
$rs = $searcharea->get_document_recordset(0, \context_user::instance($user1->id));
|
||||||
|
$this->assertEquals(['Test2'], message_sent_search_testcase::recordset_to_subjects($rs));
|
||||||
|
$rs = $searcharea->get_document_recordset(0, \context_user::instance($user2->id));
|
||||||
|
$this->assertEquals(['Test1'], message_sent_search_testcase::recordset_to_subjects($rs));
|
||||||
|
|
||||||
|
// Test with a course context (should return null).
|
||||||
|
$this->assertNull($searcharea->get_document_recordset(0,
|
||||||
|
context_course::instance($SITE->id)));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Document contents.
|
* Document contents.
|
||||||
*
|
*
|
||||||
|
|
|
@ -113,6 +113,85 @@ class message_sent_search_testcase extends advanced_testcase {
|
||||||
$recordset->close();
|
$recordset->close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indexing messages, with restricted contexts.
|
||||||
|
*/
|
||||||
|
public function test_message_sent_indexing_contexts() {
|
||||||
|
global $SITE;
|
||||||
|
|
||||||
|
$searcharea = \core_search\manager::get_search_area($this->messagesentareaid);
|
||||||
|
|
||||||
|
$user1 = self::getDataGenerator()->create_user();
|
||||||
|
$user2 = self::getDataGenerator()->create_user();
|
||||||
|
|
||||||
|
$this->preventResetByRollback();
|
||||||
|
$sink = $this->redirectMessages();
|
||||||
|
|
||||||
|
// Send first message.
|
||||||
|
$message = new \core\message\message();
|
||||||
|
$message->courseid = SITEID;
|
||||||
|
$message->userfrom = $user1;
|
||||||
|
$message->userto = $user2;
|
||||||
|
$message->subject = 'Test1';
|
||||||
|
$message->smallmessage = 'Test small messsage';
|
||||||
|
$message->fullmessage = 'Test full messsage';
|
||||||
|
$message->fullmessageformat = 0;
|
||||||
|
$message->fullmessagehtml = null;
|
||||||
|
$message->notification = 0;
|
||||||
|
$message->component = 'moodle';
|
||||||
|
$message->name = 'instantmessage';
|
||||||
|
message_send($message);
|
||||||
|
|
||||||
|
// Ensure that ordering by timestamp will return in consistent order.
|
||||||
|
$this->waitForSecond();
|
||||||
|
|
||||||
|
// Send second message in opposite direction.
|
||||||
|
$message = new \core\message\message();
|
||||||
|
$message->courseid = SITEID;
|
||||||
|
$message->userfrom = $user2;
|
||||||
|
$message->userto = $user1;
|
||||||
|
$message->subject = 'Test2';
|
||||||
|
$message->smallmessage = 'Test small messsage';
|
||||||
|
$message->fullmessage = 'Test full messsage';
|
||||||
|
$message->fullmessageformat = 0;
|
||||||
|
$message->fullmessagehtml = null;
|
||||||
|
$message->notification = 0;
|
||||||
|
$message->component = 'moodle';
|
||||||
|
$message->name = 'instantmessage';
|
||||||
|
message_send($message);
|
||||||
|
|
||||||
|
// Test function with null context and system context (same).
|
||||||
|
$rs = $searcharea->get_document_recordset(0, null);
|
||||||
|
$this->assertEquals(['Test1', 'Test2'], self::recordset_to_subjects($rs));
|
||||||
|
$rs = $searcharea->get_document_recordset(0, context_system::instance());
|
||||||
|
$this->assertEquals(['Test1', 'Test2'], self::recordset_to_subjects($rs));
|
||||||
|
|
||||||
|
// Test with user context for each user.
|
||||||
|
$rs = $searcharea->get_document_recordset(0, \context_user::instance($user1->id));
|
||||||
|
$this->assertEquals(['Test1'], self::recordset_to_subjects($rs));
|
||||||
|
$rs = $searcharea->get_document_recordset(0, \context_user::instance($user2->id));
|
||||||
|
$this->assertEquals(['Test2'], self::recordset_to_subjects($rs));
|
||||||
|
|
||||||
|
// Test with a course context (should return null).
|
||||||
|
$this->assertNull($searcharea->get_document_recordset(0,
|
||||||
|
context_course::instance($SITE->id)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility function to convert recordset to array of message subjects for testing.
|
||||||
|
*
|
||||||
|
* @param moodle_recordset $rs Recordset to convert (and close)
|
||||||
|
* @return array Array of IDs from records indexed by number (0, 1, 2, ...)
|
||||||
|
*/
|
||||||
|
public static function recordset_to_subjects(moodle_recordset $rs) {
|
||||||
|
$results = [];
|
||||||
|
foreach ($rs as $rec) {
|
||||||
|
$results[] = $rec->subject;
|
||||||
|
}
|
||||||
|
$rs->close();
|
||||||
|
return $results;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Document contents.
|
* Document contents.
|
||||||
*
|
*
|
||||||
|
|
|
@ -43,16 +43,24 @@ class chapter extends \core_search\base_mod {
|
||||||
* Returns a recordset with all required chapter information.
|
* Returns a recordset with all required chapter information.
|
||||||
*
|
*
|
||||||
* @param int $modifiedfrom
|
* @param int $modifiedfrom
|
||||||
* @return moodle_recordset
|
* @param \context|null $context Optional context to restrict scope of returned results
|
||||||
|
* @return moodle_recordset|null Recordset (or null if no results)
|
||||||
*/
|
*/
|
||||||
public function get_recordset_by_timestamp($modifiedfrom = 0) {
|
public function get_document_recordset($modifiedfrom = 0, \context $context = null) {
|
||||||
global $DB;
|
global $DB;
|
||||||
|
|
||||||
$sql = 'SELECT c.*, b.id AS bookid, b.course AS courseid
|
list ($contextjoin, $contextparams) = $this->get_context_restriction_sql(
|
||||||
|
$context, 'book', 'b');
|
||||||
|
if ($contextjoin === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = "SELECT c.*, b.id AS bookid, b.course AS courseid
|
||||||
FROM {book_chapters} c
|
FROM {book_chapters} c
|
||||||
JOIN {book} b ON b.id = c.bookid
|
JOIN {book} b ON b.id = c.bookid
|
||||||
WHERE c.timemodified >= ? ORDER BY c.timemodified ASC';
|
$contextjoin
|
||||||
return $DB->get_recordset_sql($sql, array($modifiedfrom));
|
WHERE c.timemodified >= ? ORDER BY c.timemodified ASC";
|
||||||
|
return $DB->get_recordset_sql($sql, array_merge($contextparams, [$modifiedfrom]));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -118,6 +118,26 @@ class mod_book_search_testcase extends advanced_testcase {
|
||||||
// No new records.
|
// No new records.
|
||||||
$this->assertFalse($recordset->valid());
|
$this->assertFalse($recordset->valid());
|
||||||
$recordset->close();
|
$recordset->close();
|
||||||
|
|
||||||
|
// Create another book and chapter.
|
||||||
|
$book2 = $this->getDataGenerator()->create_module('book', array('course' => $course1->id));
|
||||||
|
$bookgenerator->create_chapter(array('bookid' => $book2->id,
|
||||||
|
'content' => 'Chapter3', 'title' => 'Title3'));
|
||||||
|
|
||||||
|
// Query by context, first book.
|
||||||
|
$recordset = $searcharea->get_document_recordset(0, \context_module::instance($book->cmid));
|
||||||
|
$this->assertEquals(2, iterator_count($recordset));
|
||||||
|
$recordset->close();
|
||||||
|
|
||||||
|
// Second book.
|
||||||
|
$recordset = $searcharea->get_document_recordset(0, \context_module::instance($book2->cmid));
|
||||||
|
$this->assertEquals(1, iterator_count($recordset));
|
||||||
|
$recordset->close();
|
||||||
|
|
||||||
|
// Course.
|
||||||
|
$recordset = $searcharea->get_document_recordset(0, \context_course::instance($course1->id));
|
||||||
|
$this->assertEquals(3, iterator_count($recordset));
|
||||||
|
$recordset->close();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -47,16 +47,25 @@ class entry extends \core_search\base_mod {
|
||||||
* Returns recordset containing required data for indexing database entries.
|
* Returns recordset containing required data for indexing database entries.
|
||||||
*
|
*
|
||||||
* @param int $modifiedfrom timestamp
|
* @param int $modifiedfrom timestamp
|
||||||
* @return moodle_recordset
|
* @param \context|null $context Optional context to restrict scope of returned results
|
||||||
|
* @return moodle_recordset|null Recordset (or null if no results)
|
||||||
*/
|
*/
|
||||||
public function get_recordset_by_timestamp($modifiedfrom = 0) {
|
public function get_document_recordset($modifiedfrom = 0, \context $context = null) {
|
||||||
global $DB;
|
global $DB;
|
||||||
|
|
||||||
|
list ($contextjoin, $contextparams) = $this->get_context_restriction_sql(
|
||||||
|
$context, 'data', 'd', SQL_PARAMS_NAMED);
|
||||||
|
if ($contextjoin === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
$sql = "SELECT dr.*, d.course
|
$sql = "SELECT dr.*, d.course
|
||||||
FROM {data_records} dr
|
FROM {data_records} dr
|
||||||
JOIN {data} d ON d.id = dr.dataid
|
JOIN {data} d ON d.id = dr.dataid
|
||||||
|
$contextjoin
|
||||||
WHERE dr.timemodified >= :timemodified";
|
WHERE dr.timemodified >= :timemodified";
|
||||||
return $DB->get_recordset_sql($sql, array('timemodified' => $modifiedfrom));
|
return $DB->get_recordset_sql($sql,
|
||||||
|
array_merge($contextparams, ['timemodified' => $modifiedfrom]));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -314,6 +314,22 @@ class mod_data_search_test extends advanced_testcase {
|
||||||
// No new records.
|
// No new records.
|
||||||
$this->assertFalse($recordset->valid());
|
$this->assertFalse($recordset->valid());
|
||||||
$recordset->close();
|
$recordset->close();
|
||||||
|
|
||||||
|
// Create a second database, also with one record.
|
||||||
|
$data2 = $this->getDataGenerator()->create_module('data', ['course' => $course1->id]);
|
||||||
|
$this->create_default_data_fields($fieldtypes, $data2);
|
||||||
|
$this->create_default_data_record($data2);
|
||||||
|
|
||||||
|
// Test indexing with contexts.
|
||||||
|
$rs = $searcharea->get_document_recordset(0, context_module::instance($data1->cmid));
|
||||||
|
$this->assertEquals(1, iterator_count($rs));
|
||||||
|
$rs->close();
|
||||||
|
$rs = $searcharea->get_document_recordset(0, context_module::instance($data2->cmid));
|
||||||
|
$this->assertEquals(1, iterator_count($rs));
|
||||||
|
$rs->close();
|
||||||
|
$rs = $searcharea->get_document_recordset(0, context_course::instance($course1->id));
|
||||||
|
$this->assertEquals(2, iterator_count($rs));
|
||||||
|
$rs->close();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -56,17 +56,25 @@ class post extends \core_search\base_mod {
|
||||||
* Returns recordset containing required data for indexing forum posts.
|
* Returns recordset containing required data for indexing forum posts.
|
||||||
*
|
*
|
||||||
* @param int $modifiedfrom timestamp
|
* @param int $modifiedfrom timestamp
|
||||||
* @return moodle_recordset
|
* @param \context|null $context Optional context to restrict scope of returned results
|
||||||
|
* @return moodle_recordset|null Recordset (or null if no results)
|
||||||
*/
|
*/
|
||||||
public function get_recordset_by_timestamp($modifiedfrom = 0) {
|
public function get_document_recordset($modifiedfrom = 0, \context $context = null) {
|
||||||
global $DB;
|
global $DB;
|
||||||
|
|
||||||
$sql = 'SELECT fp.*, f.id AS forumid, f.course AS courseid
|
list ($contextjoin, $contextparams) = $this->get_context_restriction_sql(
|
||||||
|
$context, 'forum', 'f');
|
||||||
|
if ($contextjoin === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = "SELECT fp.*, f.id AS forumid, f.course AS courseid
|
||||||
FROM {forum_posts} fp
|
FROM {forum_posts} fp
|
||||||
JOIN {forum_discussions} fd ON fd.id = fp.discussion
|
JOIN {forum_discussions} fd ON fd.id = fp.discussion
|
||||||
JOIN {forum} f ON f.id = fd.forum
|
JOIN {forum} f ON f.id = fd.forum
|
||||||
WHERE fp.modified >= ? ORDER BY fp.modified ASC';
|
$contextjoin
|
||||||
return $DB->get_recordset_sql($sql, array($modifiedfrom));
|
WHERE fp.modified >= ? ORDER BY fp.modified ASC";
|
||||||
|
return $DB->get_recordset_sql($sql, array_merge($contextparams, [$modifiedfrom]));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -144,6 +144,26 @@ class mod_forum_search_testcase extends advanced_testcase {
|
||||||
// No new records.
|
// No new records.
|
||||||
$this->assertFalse($recordset->valid());
|
$this->assertFalse($recordset->valid());
|
||||||
$recordset->close();
|
$recordset->close();
|
||||||
|
|
||||||
|
// Context test: create another forum with 1 post.
|
||||||
|
$forum2 = self::getDataGenerator()->create_module('forum', ['course' => $course1->id]);
|
||||||
|
$record = new stdClass();
|
||||||
|
$record->course = $course1->id;
|
||||||
|
$record->userid = $user1->id;
|
||||||
|
$record->forum = $forum2->id;
|
||||||
|
$record->message = 'discussion';
|
||||||
|
self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
|
||||||
|
|
||||||
|
// Test indexing with each forum then combined course context.
|
||||||
|
$rs = $searcharea->get_document_recordset(0, context_module::instance($forum1->cmid));
|
||||||
|
$this->assertEquals(2, iterator_count($rs));
|
||||||
|
$rs->close();
|
||||||
|
$rs = $searcharea->get_document_recordset(0, context_module::instance($forum2->cmid));
|
||||||
|
$this->assertEquals(1, iterator_count($rs));
|
||||||
|
$rs->close();
|
||||||
|
$rs = $searcharea->get_document_recordset(0, context_course::instance($course1->id));
|
||||||
|
$this->assertEquals(3, iterator_count($rs));
|
||||||
|
$rs->close();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -46,15 +46,23 @@ class entry extends \core_search\base_mod {
|
||||||
* Returns recordset containing required data for indexing glossary entries.
|
* Returns recordset containing required data for indexing glossary entries.
|
||||||
*
|
*
|
||||||
* @param int $modifiedfrom timestamp
|
* @param int $modifiedfrom timestamp
|
||||||
* @return moodle_recordset
|
* @param \context|null $context Optional context to restrict scope of returned results
|
||||||
|
* @return moodle_recordset|null Recordset (or null if no results)
|
||||||
*/
|
*/
|
||||||
public function get_recordset_by_timestamp($modifiedfrom = 0) {
|
public function get_document_recordset($modifiedfrom = 0, \context $context = null) {
|
||||||
global $DB;
|
global $DB;
|
||||||
|
|
||||||
|
list ($contextjoin, $contextparams) = $this->get_context_restriction_sql(
|
||||||
|
$context, 'glossary', 'g');
|
||||||
|
if ($contextjoin === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
$sql = "SELECT ge.*, g.course FROM {glossary_entries} ge
|
$sql = "SELECT ge.*, g.course FROM {glossary_entries} ge
|
||||||
JOIN {glossary} g ON g.id = ge.glossaryid
|
JOIN {glossary} g ON g.id = ge.glossaryid
|
||||||
|
$contextjoin
|
||||||
WHERE ge.timemodified >= ? ORDER BY ge.timemodified ASC";
|
WHERE ge.timemodified >= ? ORDER BY ge.timemodified ASC";
|
||||||
return $DB->get_recordset_sql($sql, array($modifiedfrom));
|
return $DB->get_recordset_sql($sql, array_merge($contextparams, [$modifiedfrom]));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -132,6 +132,21 @@ class mod_glossary_search_testcase extends advanced_testcase {
|
||||||
// No new records.
|
// No new records.
|
||||||
$this->assertFalse($recordset->valid());
|
$this->assertFalse($recordset->valid());
|
||||||
$recordset->close();
|
$recordset->close();
|
||||||
|
|
||||||
|
// Create a second glossary with one entry.
|
||||||
|
$glossary2 = self::getDataGenerator()->create_module('glossary', ['course' => $course1->id]);
|
||||||
|
self::getDataGenerator()->get_plugin_generator('mod_glossary')->create_content($glossary2);
|
||||||
|
|
||||||
|
// Test indexing with each activity then combined course context.
|
||||||
|
$rs = $searcharea->get_document_recordset(0, context_module::instance($glossary1->cmid));
|
||||||
|
$this->assertEquals(2, iterator_count($rs));
|
||||||
|
$rs->close();
|
||||||
|
$rs = $searcharea->get_document_recordset(0, context_module::instance($glossary2->cmid));
|
||||||
|
$this->assertEquals(1, iterator_count($rs));
|
||||||
|
$rs->close();
|
||||||
|
$rs = $searcharea->get_document_recordset(0, context_course::instance($course1->id));
|
||||||
|
$this->assertEquals(3, iterator_count($rs));
|
||||||
|
$rs->close();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -47,16 +47,24 @@ class activity extends \core_search\base_activity {
|
||||||
/**
|
/**
|
||||||
* Returns recordset containing required data for indexing activities.
|
* Returns recordset containing required data for indexing activities.
|
||||||
*
|
*
|
||||||
* Overwritten to discard records with courseid = 0.
|
* Overridden to discard records with courseid = 0.
|
||||||
*
|
*
|
||||||
* @param int $modifiedfrom timestamp
|
* @param int $modifiedfrom timestamp
|
||||||
* @return \moodle_recordset
|
* @param \context|null $context Context
|
||||||
|
* @return \moodle_recordset|null Recordset, or null if no possible activities in given context
|
||||||
*/
|
*/
|
||||||
public function get_recordset_by_timestamp($modifiedfrom = 0) {
|
public function get_document_recordset($modifiedfrom = 0, \context $context = null) {
|
||||||
global $DB;
|
global $DB;
|
||||||
$select = 'course != ? AND ' . static::MODIFIED_FIELD_NAME . ' >= ?';
|
list ($contextjoin, $contextparams) = $this->get_context_restriction_sql(
|
||||||
return $DB->get_recordset_select($this->get_module_name(), $select, array(0, $modifiedfrom),
|
$context, $this->get_module_name(), 'modtable');
|
||||||
static::MODIFIED_FIELD_NAME . ' ASC');
|
if ($contextjoin === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return $DB->get_recordset_sql('SELECT modtable.* FROM {' . $this->get_module_name() .
|
||||||
|
'} modtable ' . $contextjoin . ' WHERE modtable.' . static::MODIFIED_FIELD_NAME .
|
||||||
|
' >= ? AND modtable.course != ? ORDER BY modtable.' . static::MODIFIED_FIELD_NAME .
|
||||||
|
' ASC',
|
||||||
|
array_merge($contextparams, [$modifiedfrom, 0]));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
75
mod/survey/tests/search_test.php
Normal file
75
mod/survey/tests/search_test.php
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
<?php
|
||||||
|
// This file is part of Moodle - http://moodle.org/
|
||||||
|
//
|
||||||
|
// Moodle is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Moodle is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit test for mod_survey searching.
|
||||||
|
*
|
||||||
|
* This is needed because the activity.php class overrides default behaviour.
|
||||||
|
*
|
||||||
|
* @package mod_survey
|
||||||
|
* @category test
|
||||||
|
* @copyright 2017 The Open University
|
||||||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||||
|
*/
|
||||||
|
|
||||||
|
defined('MOODLE_INTERNAL') || die();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit test for mod_survey searching.
|
||||||
|
*
|
||||||
|
* This is needed because the activity.php class overrides default behaviour.
|
||||||
|
*
|
||||||
|
* @package mod_survey
|
||||||
|
* @category test
|
||||||
|
* @copyright 2017 The Open University
|
||||||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||||
|
*/
|
||||||
|
class mod_survey_search_testcase extends advanced_testcase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test survey_view
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function test_survey_indexing() {
|
||||||
|
global $CFG;
|
||||||
|
|
||||||
|
$this->resetAfterTest();
|
||||||
|
|
||||||
|
require_once($CFG->dirroot . '/search/tests/fixtures/testable_core_search.php');
|
||||||
|
testable_core_search::instance();
|
||||||
|
$area = \core_search\manager::get_search_area('mod_survey-activity');
|
||||||
|
|
||||||
|
// Setup test data.
|
||||||
|
$generator = $this->getDataGenerator();
|
||||||
|
$course = $generator->create_course();
|
||||||
|
$survey1 = $generator->create_module('survey', ['course' => $course->id]);
|
||||||
|
$survey2 = $generator->create_module('survey', ['course' => $course->id]);
|
||||||
|
|
||||||
|
// Get all surveys for indexing - note that there are special entries in the table with
|
||||||
|
// course zero which should not be returned.
|
||||||
|
$rs = $area->get_document_recordset();
|
||||||
|
$this->assertEquals(2, iterator_count($rs));
|
||||||
|
$rs->close();
|
||||||
|
|
||||||
|
// Test specific context and course context.
|
||||||
|
$rs = $area->get_document_recordset(0, context_module::instance($survey1->cmid));
|
||||||
|
$this->assertEquals(1, iterator_count($rs));
|
||||||
|
$rs->close();
|
||||||
|
$rs = $area->get_document_recordset(0, context_course::instance($course->id));
|
||||||
|
$this->assertEquals(2, iterator_count($rs));
|
||||||
|
$rs->close();
|
||||||
|
}
|
||||||
|
}
|
|
@ -45,19 +45,28 @@ class collaborative_page extends \core_search\base_mod {
|
||||||
* Returns a recordset with all required page information.
|
* Returns a recordset with all required page information.
|
||||||
*
|
*
|
||||||
* @param int $modifiedfrom
|
* @param int $modifiedfrom
|
||||||
* @return moodle_recordset
|
* @param \context|null $context Optional context to restrict scope of returned results
|
||||||
|
* @return moodle_recordset|null Recordset (or null if no results)
|
||||||
*/
|
*/
|
||||||
public function get_recordset_by_timestamp($modifiedfrom = 0) {
|
public function get_document_recordset($modifiedfrom = 0, \context $context = null) {
|
||||||
global $DB;
|
global $DB;
|
||||||
|
|
||||||
$sql = 'SELECT p.*, w.id AS wikiid, w.course AS courseid
|
list ($contextjoin, $contextparams) = $this->get_context_restriction_sql(
|
||||||
|
$context, 'wiki', 'w');
|
||||||
|
if ($contextjoin === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = "SELECT p.*, w.id AS wikiid, w.course AS courseid
|
||||||
FROM {wiki_pages} p
|
FROM {wiki_pages} p
|
||||||
JOIN {wiki_subwikis} s ON s.id = p.subwikiid
|
JOIN {wiki_subwikis} s ON s.id = p.subwikiid
|
||||||
JOIN {wiki} w ON w.id = s.wikiid
|
JOIN {wiki} w ON w.id = s.wikiid
|
||||||
|
$contextjoin
|
||||||
WHERE p.timemodified >= ?
|
WHERE p.timemodified >= ?
|
||||||
AND w.wikimode = ?
|
AND w.wikimode = ?
|
||||||
ORDER BY p.timemodified ASC';
|
ORDER BY p.timemodified ASC";
|
||||||
return $DB->get_recordset_sql($sql, array($modifiedfrom, 'collaborative'));
|
return $DB->get_recordset_sql($sql, array_merge($contextparams,
|
||||||
|
[$modifiedfrom, 'collaborative']));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -126,6 +126,21 @@ class mod_wiki_search_testcase extends advanced_testcase {
|
||||||
// No new records.
|
// No new records.
|
||||||
$this->assertFalse($recordset->valid());
|
$this->assertFalse($recordset->valid());
|
||||||
$recordset->close();
|
$recordset->close();
|
||||||
|
|
||||||
|
// Add another wiki with one page.
|
||||||
|
$collabwiki2 = $this->getDataGenerator()->create_module('wiki', ['course' => $course1->id]);
|
||||||
|
$wikigenerator->create_first_page($collabwiki2);
|
||||||
|
|
||||||
|
// Test indexing contexts.
|
||||||
|
$rs = $searcharea->get_document_recordset(0, context_module::instance($collabwiki->cmid));
|
||||||
|
$this->assertEquals(3, iterator_count($rs));
|
||||||
|
$rs->close();
|
||||||
|
$rs = $searcharea->get_document_recordset(0, context_module::instance($collabwiki2->cmid));
|
||||||
|
$this->assertEquals(1, iterator_count($rs));
|
||||||
|
$rs->close();
|
||||||
|
$rs = $searcharea->get_document_recordset(0, context_course::instance($course1->id));
|
||||||
|
$this->assertEquals(4, iterator_count($rs));
|
||||||
|
$rs->close();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -41,12 +41,42 @@ class user extends \core_search\base {
|
||||||
* Returns recordset containing required data attributes for indexing.
|
* Returns recordset containing required data attributes for indexing.
|
||||||
*
|
*
|
||||||
* @param number $modifiedfrom
|
* @param number $modifiedfrom
|
||||||
* @return \moodle_recordset
|
* @param \context|null $context Optional context to restrict scope of returned results
|
||||||
|
* @return \moodle_recordset|null Recordset (or null if no results)
|
||||||
*/
|
*/
|
||||||
public function get_recordset_by_timestamp($modifiedfrom = 0) {
|
public function get_document_recordset($modifiedfrom = 0, \context $context = null) {
|
||||||
global $DB;
|
global $DB;
|
||||||
return $DB->get_recordset_select('user', 'timemodified >= ? AND deleted = ? AND
|
|
||||||
confirmed = ?', array($modifiedfrom, 0, 1));
|
// Prepare query conditions.
|
||||||
|
$where = 'timemodified >= ? AND deleted = ? AND confirmed = ?';
|
||||||
|
$params = [$modifiedfrom, 0, 1];
|
||||||
|
|
||||||
|
// Handle context types.
|
||||||
|
if (!$context) {
|
||||||
|
$context = \context_system::instance();
|
||||||
|
}
|
||||||
|
switch ($context->contextlevel) {
|
||||||
|
case CONTEXT_MODULE:
|
||||||
|
case CONTEXT_BLOCK:
|
||||||
|
case CONTEXT_COURSE:
|
||||||
|
case CONTEXT_COURSECAT:
|
||||||
|
// These contexts cannot contain any users.
|
||||||
|
return null;
|
||||||
|
|
||||||
|
case CONTEXT_USER:
|
||||||
|
// Restrict to specific user.
|
||||||
|
$where .= ' AND id = ?';
|
||||||
|
$params[] = $context->instanceid;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CONTEXT_SYSTEM:
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new \coding_exception('Unexpected contextlevel: ' . $context->contextlevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $DB->get_recordset_select('user', $where, $params);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -57,6 +57,7 @@ class user_search_testcase extends advanced_testcase {
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function test_users_indexing() {
|
public function test_users_indexing() {
|
||||||
|
global $SITE;
|
||||||
|
|
||||||
// Returns the instance as long as the area is supported.
|
// Returns the instance as long as the area is supported.
|
||||||
$searcharea = \core_search\manager::get_search_area($this->userareaid);
|
$searcharea = \core_search\manager::get_search_area($this->userareaid);
|
||||||
|
@ -87,6 +88,15 @@ class user_search_testcase extends advanced_testcase {
|
||||||
// No new records.
|
// No new records.
|
||||||
$this->assertFalse($recordset->valid());
|
$this->assertFalse($recordset->valid());
|
||||||
$recordset->close();
|
$recordset->close();
|
||||||
|
|
||||||
|
// Context support; first, try an unsupported context type.
|
||||||
|
$coursecontext = context_course::instance($SITE->id);
|
||||||
|
$this->assertNull($searcharea->get_document_recordset(0, $coursecontext));
|
||||||
|
|
||||||
|
// Try a specific user, will only return 1 record (that user).
|
||||||
|
$rs = $searcharea->get_document_recordset(0, context_user::instance($user1->id));
|
||||||
|
$this->assertEquals(1, iterator_count($rs));
|
||||||
|
$rs->close();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue