diff --git a/lang/en/search.php b/lang/en/search.php index a5d25d75cf9..f48c7f42cfa 100644 --- a/lang/en/search.php +++ b/lang/en/search.php @@ -81,6 +81,8 @@ $string['runindexer'] = 'Run indexer (real)'; $string['runindexertest'] = 'Run indexer test'; $string['score'] = 'Score'; $string['search'] = 'Search'; +$string['search:message_received'] = 'Messages - Received'; +$string['search:message_sent'] = 'Messages - Sent'; $string['search:mycourse'] = 'My courses'; $string['search:user'] = 'Users'; $string['searcharea'] = 'Search area'; diff --git a/message/classes/search/base_message.php b/message/classes/search/base_message.php new file mode 100644 index 00000000000..9267c7d7561 --- /dev/null +++ b/message/classes/search/base_message.php @@ -0,0 +1,138 @@ +. + +/** + * Search area base class for messages. + * + * @package core_message + * @copyright 2016 Devang Gaur + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace core_message\search; + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->dirroot . '/message/lib.php'); + +/** + * Search area base class for messages. + * + * @package core_message + * @copyright 2016 Devang Gaur + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +abstract class base_message extends \core_search\base { + + /** + * The context levels the search area is working on. + * @var array + */ + protected static $levels = [CONTEXT_USER]; + + /** + * Returns recordset containing message records. + * + * @param int $modifiedfrom timestamp + * @return \moodle_recordset + */ + public function get_recordset_by_timestamp($modifiedfrom = 0) { + global $DB; + return $DB->get_recordset_select('message_read', 'timecreated >= ?', array($modifiedfrom), 'timecreated ASC'); + } + + /** + * Returns the document associated with this message record. + * + * @param stdClass $record + * @param array $options + * @return \core_search\document + */ + public function get_document($record, $options = array()) { + try { + $usercontext = \context_user::instance($options['user1id']); + } 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', content_to_text($record->subject, false)); + $doc->set('itemid', $record->id); + $doc->set('content', content_to_text($record->smallmessage, false)); + $doc->set('contextid', $usercontext->id); + $doc->set('courseid', SITEID); + $doc->set('owneruserid', $options['user1id']); + $doc->set('userid', $options['user2id']); + $doc->set('modified', $record->timecreated); + + // 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; + } + + /** + * Link to the message. + * + * @param \core_search\document $doc + * @return \moodle_url + */ + public function get_doc_url(\core_search\document $doc) { + $users = $this->get_current_other_users($doc); + $position = 'm'.$doc->get('itemid'); + return new \moodle_url('/message/index.php', array('history' => MESSAGE_HISTORY_ALL, + 'user1' => $users['currentuserid'], 'user2' => $users['otheruserid']), $position); + } + + /** + * Link to the conversation. + * + * @param \core_search\document $doc + * @return \moodle_url + */ + public function get_context_url(\core_search\document $doc) { + $users = $this->get_current_other_users($doc); + return new \moodle_url('/message/index.php', array('user1' => $users['currentuserid'], 'user2' => $users['otheruserid'])); + } + + /** + * Sorting the current(user1) and other(user2) user in the conversation. + * + * @param \core_search\document $doc + * @return array() + */ + protected function get_current_other_users($doc) { + global $USER; + + $users = array(); + if (($USER->id == $doc->get('owneruserid')) || (get_class($this) === 'message_sent')) { + $users['currentuserid'] = $doc->get('owneruserid'); + $users['otheruserid'] = $doc->get('userid'); + } else { + $users['currentuserid'] = $doc->get('userid'); + $users['otheruserid'] = $doc->get('owneruserid'); + } + + return $users; + } + +} \ No newline at end of file diff --git a/message/classes/search/message_received.php b/message/classes/search/message_received.php new file mode 100644 index 00000000000..eaf9d002e25 --- /dev/null +++ b/message/classes/search/message_received.php @@ -0,0 +1,85 @@ +. + +/** + * Search area for received messages. + * + * @package core_message + * @copyright 2016 Devang Gaur + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace core_message\search; + + +defined('MOODLE_INTERNAL') || die(); + +/** + * Search area for received messages. + * + * @package core_message + * @copyright 2016 Devang Gaur + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class message_received extends base_message { + + /** + * Returns the document associated with this message record. + * + * @param stdClass $record + * @param array $options + * @return \core_search\document + */ + public function get_document($record, $options = array()) { + return parent::get_document($record, array('user1id' => $record->useridto, 'user2id' => $record->useridfrom)); + } + + /** + * Whether the user can access the document or not. + * + * @param int $id The message instance id. + * @return int + */ + public function check_access($id) { + global $CFG, $DB, $USER; + + if (!$CFG->messaging) { + return \core_search\manager::ACCESS_DENIED; + } + + $message = $DB->get_record('message_read', array('id' => $id)); + if (!$message) { + return \core_search\manager::ACCESS_DELETED; + } + + $userfrom = $DB->get_record('user', array('id' => $message->useridfrom)); + $userto = $DB->get_record('user', array('id' => $message->useridto)); + + if (!$userfrom || !$userto || $userfrom->deleted || $userto->deleted) { + return \core_search\manager::ACCESS_DELETED; + } + + if ($USER->id != $userto->id) { + return \core_search\manager::ACCESS_DENIED; + } + + if ($message->timeusertodeleted != 0) { + return \core_search\manager::ACCESS_DELETED; + } + + return \core_search\manager::ACCESS_GRANTED; + } + +} \ No newline at end of file diff --git a/message/classes/search/message_sent.php b/message/classes/search/message_sent.php new file mode 100644 index 00000000000..b92e459b633 --- /dev/null +++ b/message/classes/search/message_sent.php @@ -0,0 +1,84 @@ +. + +/** + * Search area for sent messages. + * + * @package core_message + * @copyright 2016 Devang Gaur + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace core_message\search; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Search area for sent messages. + * + * @package core_message + * @copyright 2016 Devang Gaur + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class message_sent extends base_message { + + /** + * Returns the document associated with this message record. + * + * @param stdClass $record + * @param array $options + * @return \core_search\document + */ + public function get_document($record, $options = array()) { + return parent::get_document($record, array('user1id' => $record->useridfrom, 'user2id' => $record->useridto)); + } + + /** + * Whether the user can access the document or not. + * + * @param int $id The message instance id. + * @return int + */ + public function check_access($id) { + global $CFG, $DB, $USER; + + if (!$CFG->messaging) { + return \core_search\manager::ACCESS_DENIED; + } + + $message = $DB->get_record('message_read', array('id' => $id)); + if (!$message) { + return \core_search\manager::ACCESS_DELETED; + } + + $userfrom = $DB->get_record('user', array('id' => $message->useridfrom)); + $userto = $DB->get_record('user', array('id' => $message->useridto)); + + if (!$userfrom || !$userto || $userfrom->deleted || $userto->deleted) { + return \core_search\manager::ACCESS_DELETED; + } + + if ($USER->id != $userfrom->id) { + return \core_search\manager::ACCESS_DENIED; + } + + if ($message->timeuserfromdeleted != 0) { + return \core_search\manager::ACCESS_DELETED; + } + + return \core_search\manager::ACCESS_GRANTED; + } + +} \ No newline at end of file diff --git a/message/tests/search_test_received.php b/message/tests/search_test_received.php new file mode 100644 index 00000000000..7b4180320be --- /dev/null +++ b/message/tests/search_test_received.php @@ -0,0 +1,226 @@ +. + +/** + * received message global search unit tests. + * + * @package core + * @copyright 2016 Devang Gaur + * @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 received messages global search. + * + * @package core + * @copyright 2016 Devang Gaur + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class message_received_search_testcase extends advanced_testcase { + + /** + * @var string Area id + */ + protected $messagereceivedareaid = null; + + /** + * Setting up the test environment + * @return void + */ + public function setUp() { + $this->resetAfterTest(true); + set_config('enableglobalsearch', true); + + $this->messagereceivedareaid = \core_search\manager::generate_areaid('core_message', 'message_received'); + + // 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 messages contents. + * + * @return void + */ + public function test_message_received_indexing() { + + // Returns the instance as long as the area is supported. + $searcharea = \core_search\manager::get_search_area($this->messagereceivedareaid); + $this->assertInstanceOf('\core_message\search\message_received', $searcharea); + + $user1 = self::getDataGenerator()->create_user(); + $user2 = self::getDataGenerator()->create_user(); + + $this->preventResetByRollback(); + $sink = $this->redirectMessages(); + + $message = new StdClass(); + $message->userfrom = $user1; + $message->userto = $user2; + $message->subject = "Test Subject"; + $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); + + $messages = $sink->get_messages(); + + $this->assertEquals(1, count($messages)); + + // 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(1, $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_message_received_document() { + + // Returns the instance as long as the area is supported. + $searcharea = \core_search\manager::get_search_area($this->messagereceivedareaid); + $this->assertInstanceOf('\core_message\search\message_received', $searcharea); + + $user1 = self::getDataGenerator()->create_user(); + $user2 = self::getDataGenerator()->create_user(); + + $this->preventResetByRollback(); + $sink = $this->redirectMessages(); + + $message = new StdClass(); + $message->userfrom = $user1; + $message->userto = $user2; + $message->subject = "Test Subject"; + $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); + + $messages = $sink->get_messages(); + $message = $messages[0]; + + $doc = $searcharea->get_document($message); + $this->assertInstanceOf('\core_search\document', $doc); + $this->assertEquals($message->id, $doc->get('itemid')); + $this->assertEquals($this->messagereceivedareaid . '-' . $message->id, $doc->get('id')); + $this->assertEquals(SITEID, $doc->get('courseid')); + $this->assertEquals($message->useridfrom, $doc->get('userid')); + $this->assertEquals($message->useridto, $doc->get('owneruserid')); + $this->assertEquals(content_to_text($message->subject, false), $doc->get('title')); + $this->assertEquals(content_to_text($message->smallmessage, false), $doc->get('content')); + } + + /** + * Document accesses. + * + * @return void + */ + public function test_message_received_access() { + global $CFG; + + // Returns the instance as long as the area is supported. + $searcharea = \core_search\manager::get_search_area($this->messagereceivedareaid); + + $user1 = self::getDataGenerator()->create_user(); + $user2 = self::getDataGenerator()->create_user(); + $user3 = self::getDataGenerator()->create_user(); + + $this->preventResetByRollback(); + $sink = $this->redirectMessages(); + + $message = new StdClass(); + $message->userfrom = $user1; + $message->userto = $user2; + $message->subject = "Test Subject"; + $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"; + + $messageid = message_send($message); + + $messages = $sink->get_messages(); + $message = $messages[0]; + + $this->setUser($user1); + $this->assertEquals(\core_search\manager::ACCESS_DENIED, $searcharea->check_access($messageid)); + $this->assertEquals(\core_search\manager::ACCESS_DELETED, $searcharea->check_access(-123)); + + $this->setUser($user2); + $this->assertEquals(\core_search\manager::ACCESS_GRANTED, $searcharea->check_access($messageid)); + + if ($CFG->messaging) { + $this->assertEquals(\core_search\manager::ACCESS_GRANTED, $searcharea->check_access($messageid)); + } else { + $this->assertEquals(\core_search\manager::ACCESS_DENIED, $searcharea->check_access($messageid)); + } + + message_delete_message($message, $user2->id); + $this->assertEquals(\core_search\manager::ACCESS_DELETED, $searcharea->check_access($messageid)); + + $this->setUser($user3); + $this->assertEquals(\core_search\manager::ACCESS_DENIED, $searcharea->check_access($messageid)); + + $this->setGuestUser(); + $this->assertEquals(\core_search\manager::ACCESS_DENIED, $searcharea->check_access($messageid)); + + $this->setAdminUser(); + $this->assertEquals(\core_search\manager::ACCESS_DENIED, $searcharea->check_access($messageid)); + + delete_user($user1); + + $this->setUser($user2); + $this->assertEquals(\core_search\manager::ACCESS_DELETED, $searcharea->check_access($messageid)); + + } +} diff --git a/message/tests/search_test_sent.php b/message/tests/search_test_sent.php new file mode 100644 index 00000000000..9938ae1a496 --- /dev/null +++ b/message/tests/search_test_sent.php @@ -0,0 +1,226 @@ +. + +/** + * Sent message global search unit tests. + * + * @package core + * @copyright 2016 Devang Gaur + * @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 sent message global search. + * + * @package core + * @copyright 2016 Devang Gaur + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class message_sent_search_testcase extends advanced_testcase { + + /** + * @var string Area id + */ + protected $messagesentareaid = null; + + /** + * Setting up the test environment + * @return void + */ + public function setUp() { + $this->resetAfterTest(true); + set_config('enableglobalsearch', true); + + $this->messagesentareaid = \core_search\manager::generate_areaid('core_message', 'message_sent'); + + // 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 messages contents. + * + * @return void + */ + public function test_message_sent_indexing() { + + // Returns the instance as long as the area is supported. + $searcharea = \core_search\manager::get_search_area($this->messagesentareaid); + $this->assertInstanceOf('\core_message\search\message_sent', $searcharea); + + $user1 = self::getDataGenerator()->create_user(); + $user2 = self::getDataGenerator()->create_user(); + + $this->preventResetByRollback(); + $sink = $this->redirectMessages(); + + $message = new StdClass(); + $message->userfrom = $user1; + $message->userto = $user2; + $message->subject = "Test Subject"; + $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); + + $messages = $sink->get_messages(); + + $this->assertEquals(1, count($messages)); + + // 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(1, $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_message_sent_document() { + + // Returns the instance as long as the area is supported. + $searcharea = \core_search\manager::get_search_area($this->messagesentareaid); + $this->assertInstanceOf('\core_message\search\message_sent', $searcharea); + + $user1 = self::getDataGenerator()->create_user(); + $user2 = self::getDataGenerator()->create_user(); + + $this->preventResetByRollback(); + $sink = $this->redirectMessages(); + + $message = new StdClass(); + $message->userfrom = $user1; + $message->userto = $user2; + $message->subject = "Test Subject"; + $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); + + $messages = $sink->get_messages(); + $message = $messages[0]; + + $doc = $searcharea->get_document($message); + $this->assertInstanceOf('\core_search\document', $doc); + $this->assertEquals($message->id, $doc->get('itemid')); + $this->assertEquals($this->messagesentareaid . '-' . $message->id, $doc->get('id')); + $this->assertEquals(SITEID, $doc->get('courseid')); + $this->assertEquals($message->useridfrom, $doc->get('owneruserid')); + $this->assertEquals($message->useridto, $doc->get('userid')); + $this->assertEquals(content_to_text($message->subject, false), $doc->get('title')); + $this->assertEquals(content_to_text($message->smallmessage, false), $doc->get('content')); + } + + /** + * Document accesses. + * + * @return void + */ + public function test_message_sent_access() { + global $CFG; + + // Returns the instance as long as the area is supported. + $searcharea = \core_search\manager::get_search_area($this->messagesentareaid); + + $user1 = self::getDataGenerator()->create_user(); + $user2 = self::getDataGenerator()->create_user(); + $user3 = self::getDataGenerator()->create_user(); + + $this->preventResetByRollback(); + $sink = $this->redirectMessages(); + + $message = new StdClass(); + $message->userfrom = $user1; + $message->userto = $user2; + $message->subject = "Test Subject"; + $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"; + + $messageid = message_send($message); + + $messages = $sink->get_messages(); + $message = $messages[0]; + + $this->setUser($user1); + + if ($CFG->messaging) { + $this->assertEquals(\core_search\manager::ACCESS_GRANTED, $searcharea->check_access($messageid)); + } else { + $this->assertEquals(\core_search\manager::ACCESS_DENIED, $searcharea->check_access($messageid)); + } + + $this->assertEquals(\core_search\manager::ACCESS_DELETED, $searcharea->check_access(-123)); + + message_delete_message($message, $user1->id); + $this->assertEquals(\core_search\manager::ACCESS_DELETED, $searcharea->check_access($messageid)); + + $this->setUser($user2); + $this->assertEquals(\core_search\manager::ACCESS_DENIED, $searcharea->check_access($messageid)); + + $this->setUser($user3); + $this->assertEquals(\core_search\manager::ACCESS_DENIED, $searcharea->check_access($messageid)); + + $this->setGuestUser(); + $this->assertEquals(\core_search\manager::ACCESS_DENIED, $searcharea->check_access($messageid)); + + $this->setAdminUser(); + $this->assertEquals(\core_search\manager::ACCESS_DENIED, $searcharea->check_access($messageid)); + + delete_user($user2); + + $this->setUser($user1); + $this->assertEquals(\core_search\manager::ACCESS_DELETED, $searcharea->check_access($messageid)); + + } +} \ No newline at end of file