Merge branch 'MDL-62217_master' of git://github.com/dmonllao/moodle

This commit is contained in:
Andrew Nicols 2018-05-09 18:41:11 +08:00 committed by Jun Pataleta
commit 0f125a45f4
9 changed files with 553 additions and 1 deletions

View File

@ -92,6 +92,7 @@ $string['order_relevance'] = 'Most relevant results first';
$string['priority'] = 'Priority';
$string['priority_reindexing'] = 'Reindexing';
$string['priority_normal'] = 'Normal';
$string['privacy:metadata'] = 'The search subsystem does not store any personal data.';
$string['progress'] = 'Progress';
$string['queryerror'] = 'The query you provided could not be parsed by the search engine: {$a}';
$string['queueheading'] = 'Additional indexing queue ({$a} items)';

View File

@ -0,0 +1,46 @@
<?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/>.
/**
* Privacy Subsystem implementation for core_search.
*
* @package core_search
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_search\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for core_search implementing null_provider.
*
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason() : string {
return 'privacy:metadata';
}
}

View File

@ -0,0 +1,162 @@
<?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/>.
/**
* Privacy class for requesting user data.
*
* @package search_simpledb
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace search_simpledb\privacy;
defined('MOODLE_INTERNAL') || die();
use core_privacy\local\metadata\collection;
use core_privacy\local\request\writer;
use core_privacy\local\request\transform;
use core_privacy\local\request\contextlist;
use core_privacy\local\request\approved_contextlist;
/**
* Provider for the search_simpledb plugin.
*
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements
\core_privacy\local\metadata\provider,
\core_privacy\local\request\plugin\provider {
/**
* Returns meta data about this system.
*
* @param collection $collection The initialised collection to add items to.
* @return collection A listing of user data stored through this system.
*/
public static function get_metadata(collection $collection) : collection {
$collection->add_database_table(
'search_simpledb_index',
[
'docid' => 'privacy:metadata:index:docid',
'itemid' => 'privacy:metadata:index:itemid',
'title' => 'privacy:metadata:index:title',
'content' => 'privacy:metadata:index:content',
'contextid' => 'privacy:metadata:index:contextid',
'areaid' => 'privacy:metadata:index:areaid',
'type' => 'privacy:metadata:index:type',
'courseid' => 'privacy:metadata:index:courseid',
'owneruserid' => 'privacy:metadata:index:owneruserid',
'modified' => 'privacy:metadata:index:modified',
'userid' => 'privacy:metadata:index:userid',
'description1' => 'privacy:metadata:index:description1',
'description2' => 'privacy:metadata:index:description2',
],
'privacy:metadata:index'
);
return $collection;
}
/**
* Get the list of contexts that contain user information for the specified user.
*
* @param int $userid The user to search.
* @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin.
*/
public static function get_contexts_for_userid(int $userid) : contextlist {
$contextlist = new \core_privacy\local\request\contextlist();
$params = ['userid' => $userid, 'owneruserid' => $userid];
$sql = "SELECT DISTINCT contextid FROM {search_simpledb_index} WHERE (userid = :userid OR owneruserid = :owneruserid)";
$contextlist->add_from_sql($sql, $params);
return $contextlist;
}
/**
* Export all user data for the specified user, in the specified contexts.
*
* @param approved_contextlist $contextlist The approved contexts to export information for.
*/
public static function export_user_data(approved_contextlist $contextlist) {
global $DB;
// Plugin search_simpledb uses the default document object (core_search\document) which uses FORMAT_PLAIN.
$textformat = FORMAT_PLAIN;
$userid = $contextlist->get_user()->id;
$ctxfields = \context_helper::get_preload_record_columns_sql('ctx');
list($contextsql, $contextparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
$sql = "SELECT ssi.*, $ctxfields FROM {search_simpledb_index} ssi
JOIN {context} ctx ON ctx.id = ssi.contextid
WHERE ssi.contextid $contextsql AND (ssi.userid = :userid OR ssi.owneruserid = :owneruserid)";
$params = ['userid' => $userid, 'owneruserid' => $userid] + $contextparams;
$records = $DB->get_recordset_sql($sql, $params);
foreach ($records as $record) {
\context_helper::preload_from_record($record);
$context = \context::instance_by_id($record->contextid);
$document = (object)[
'title' => format_string($record->title, true, ['context' => $context]),
'content' => format_text($record->content, $textformat, ['context' => $context]),
'description1' => format_text($record->description1, $textformat, ['context' => $context]),
'description2' => format_text($record->description2, $textformat, ['context' => $context]),
'context' => $context->get_context_name(true, true),
'modified' => transform::datetime($record->modified),
];
$path = [get_string('search', 'search'), $record->docid];
writer::with_context($context)->export_data($path, $document);
}
$records->close();
}
/**
* Delete all data for all users in the specified context.
*
* @param context $context The specific context to delete data for.
*/
public static function delete_data_for_all_users_in_context(\context $context) {
global $DB;
$DB->delete_records('search_simpledb_index', ['contextid' => $context->id]);
if ($context->contextlevel == CONTEXT_USER) {
$select = "userid = :userid OR owneruserid = :owneruserid";
$params = ['userid' => $context->instanceid, 'owneruserid' => $context->instanceid];
$DB->delete_records_select('search_simpledb_index', $select, $params);
}
}
/**
* Delete all user data for the specified user, in the specified contexts.
*
* @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
*/
public static function delete_data_for_user(approved_contextlist $contextlist) {
global $DB;
$userid = $contextlist->get_user()->id;
list($contextsql, $contextparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
$select = "contextid $contextsql AND (userid = :userid OR owneruserid = :owneruserid)";
$params = ['userid' => $userid, 'owneruserid' => $userid] + $contextparams;
$DB->delete_records_select('search_simpledb_index', $select, $params);
}
}

View File

@ -23,5 +23,19 @@
*/
$string['pluginname'] = 'Simple search';
$string['privacy:metadata:index'] = 'Indexed contents';
$string['privacy:metadata:index:docid'] = 'Document id (unique)';
$string['privacy:metadata:index:itemid'] = 'Item identifier (in search area scope)';
$string['privacy:metadata:index:title'] = 'Title';
$string['privacy:metadata:index:content'] = 'Contents';
$string['privacy:metadata:index:contextid'] = 'Document context id';
$string['privacy:metadata:index:areaid'] = 'Search area id';
$string['privacy:metadata:index:type'] = 'Document type';
$string['privacy:metadata:index:courseid'] = 'Course id';
$string['privacy:metadata:index:owneruserid'] = 'Document owner user id';
$string['privacy:metadata:index:modified'] = 'Last modification time';
$string['privacy:metadata:index:userid'] = 'Document user id';
$string['privacy:metadata:index:description1'] = 'Extra description field';
$string['privacy:metadata:index:description2'] = 'Extra description field';
$string['searchinfo'] = 'Search queries';
$string['searchinfo_help'] = 'Enter the search query.';

View File

@ -0,0 +1,194 @@
<?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 tests for privacy.
*
* @package search_simpledb
* @copyright 2018 David Monllaó {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use \search_simpledb\privacy\provider;
use core_privacy\local\request\transform;
use core_privacy\local\request\writer;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/search/tests/fixtures/testable_core_search.php');
require_once($CFG->dirroot . '/search/tests/fixtures/mock_search_area.php');
/**
* Unit tests for privacy.
*
* @package search_simpledb
* @copyright 2018 David Monllaó {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class privacy_model_testcase extends \core_privacy\tests\provider_testcase {
public function setUp() {
global $DB;
if ($this->requires_manual_index_update()) {
// We need to update fulltext index manually, which requires an alter table statement.
$this->preventResetByRollback();
}
$this->resetAfterTest();
set_config('enableglobalsearch', true);
// Inject search_simpledb engine into the testable core search as we need to add the mock
// search component to it.
$this->engine = new \search_simpledb\engine();
$this->search = testable_core_search::instance($this->engine);
$areaid = \core_search\manager::generate_areaid('core_mocksearch', 'mock_search_area');
$this->search->add_search_area($areaid, new core_mocksearch\search\mock_search_area());
$this->generator = self::getDataGenerator()->get_plugin_generator('core_search');
$this->generator->setup();
$this->c1 = $this->getDataGenerator()->create_course();
$this->c2 = $this->getDataGenerator()->create_course();
$this->c1context = \context_course::instance($this->c1->id);
$this->c2context = \context_course::instance($this->c2->id);
$this->u1 = $this->getDataGenerator()->create_user();
$this->u2 = $this->getDataGenerator()->create_user();
$this->getDataGenerator()->enrol_user($this->u1->id, $this->c1->id, 'student');
$this->getDataGenerator()->enrol_user($this->u1->id, $this->c2->id, 'student');
$this->getDataGenerator()->enrol_user($this->u2->id, $this->c1->id, 'student');
$this->getDataGenerator()->enrol_user($this->u2->id, $this->c2->id, 'student');
$record = (object)[
'userid' => $this->u1->id,
'contextid' => $this->c1context->id,
'title' => 'vi',
'content' => 'va',
'description1' => 'san',
'description2' => 'jose'
];
$this->generator->create_record($record);
$this->generator->create_record((object)['userid' => $this->u1->id, 'contextid' => $this->c2context->id]);
$this->generator->create_record((object)['userid' => $this->u2->id, 'contextid' => $this->c2context->id]);
$this->generator->create_record((object)['userid' => $this->u2->id, 'contextid' => $this->c1context->id]);
$this->generator->create_record((object)['owneruserid' => $this->u1->id, 'contextid' => $this->c1context->id]);
$this->generator->create_record((object)['owneruserid' => $this->u1->id, 'contextid' => $this->c2context->id]);
$this->generator->create_record((object)['owneruserid' => $this->u2->id, 'contextid' => $this->c1context->id]);
$this->generator->create_record((object)['owneruserid' => $this->u2->id, 'contextid' => $this->c2context->id]);
$this->search->index();
$this->setAdminUser();
}
/**
* tearDown
*
* @return void
*/
public function tearDown() {
// For unit tests before PHP 7, teardown is called even on skip. So only do our teardown if we did setup.
if ($this->generator) {
// Moodle DML freaks out if we don't teardown the temp table after each run.
$this->generator->teardown();
$this->generator = null;
}
}
/**
* Test export user data.
*
* @return null
*/
public function test_export_user_data() {
global $DB;
$contextlist = new \core_privacy\local\request\approved_contextlist($this->u1, 'search_simpledb',
[$this->c1context->id]);
provider::export_user_data($contextlist);
$writer = \core_privacy\local\request\writer::with_context($this->c1context);
$this->assertTrue($writer->has_any_data());
$u1c1 = $DB->get_record('search_simpledb_index', ['userid' => $this->u1->id, 'contextid' => $this->c1context->id]);
$data = $writer->get_data([get_string('search', 'search'), $u1c1->docid]);
$this->assertEquals($this->c1context->get_context_name(true, true), $data->context);
$this->assertEquals('vi', $data->title);
$this->assertEquals('va', $data->content);
$this->assertEquals('san', $data->description1);
$this->assertEquals('jose', $data->description2);
}
/**
* Test delete search for context.
*
* @return null
*/
public function test_delete_data_for_all_users() {
global $DB;
$this->assertEquals(8, $DB->count_records('search_simpledb_index'));
provider::delete_data_for_all_users_in_context($this->c1context);
$this->assertEquals(0, $DB->count_records('search_simpledb_index', ['contextid' => $this->c1context->id]));
$this->assertEquals(4, $DB->count_records('search_simpledb_index'));
$u2context = \context_user::instance($this->u2->id);
provider::delete_data_for_all_users_in_context($u2context);
$this->assertEquals(0, $DB->count_records('search_simpledb_index', ['contextid' => $u2context->id]));
$this->assertEquals(2, $DB->count_records('search_simpledb_index'));
}
/**
* Test delete search for user.
*
* @return null
*/
public function test_delete_data_for_user() {
global $DB;
$contextlist = new \core_privacy\local\request\approved_contextlist($this->u1, 'search_simpledb',
[$this->c1context->id]);
provider::delete_data_for_user($contextlist);
$select = 'contextid = :contextid AND (owneruserid = :owneruserid OR userid = :userid)';
$params = ['contextid' => $this->c1context->id, 'owneruserid' => $this->u1->id, 'userid' => $this->u1->id];
$this->assertEquals(0, $DB->count_records_select('search_simpledb_index', $select, $params));
$this->assertEquals(2, $DB->count_records('search_simpledb_index', ['contextid' => $this->c1context->id]));
$this->assertEquals(6, $DB->count_records('search_simpledb_index'));
$contextlist = new \core_privacy\local\request\approved_contextlist($this->u2, 'search_simpledb',
[$this->c2context->id]);
provider::delete_data_for_user($contextlist);
$select = 'contextid = :contextid AND (owneruserid = :owneruserid OR userid = :userid)';
$params = ['contextid' => $this->c2context->id, 'owneruserid' => $this->u2->id, 'userid' => $this->u2->id];
$this->assertEquals(0, $DB->count_records_select('search_simpledb_index', $select, $params));
$this->assertEquals(2, $DB->count_records('search_simpledb_index', ['contextid' => $this->c2context->id]));
$this->assertEquals(4, $DB->count_records('search_simpledb_index'));
}
/**
* Mssql with fulltext support requires manual updates.
*
* @return bool
*/
private function requires_manual_index_update() {
global $DB;
return ($DB->get_dbfamily() === 'mssql' && $DB->is_fulltext_search_supported());
}
}

View File

@ -0,0 +1,87 @@
<?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/>.
/**
* Privacy class for requesting user data.
*
* @package search_solr
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace search_solr\privacy;
defined('MOODLE_INTERNAL') || die();
use core_privacy\local\metadata\collection;
use core_privacy\local\request\contextlist;
use core_privacy\local\request\approved_contextlist;
/**
* Provider for the search_solr plugin.
*
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements
// This search engine plugin does not store any data itself.
// It has no database tables, and it purely acts as a conduit, sending data externally.
\core_privacy\local\metadata\provider, \core_privacy\local\request\plugin\provider {
/**
* Returns meta data about this system.
*
* @param collection $collection The initialised collection to add items to.
* @return collection A listing of user data stored through this system.
*/
public static function get_metadata(collection $collection) : collection {
return $collection->add_external_location_link('solr', ['data' => 'privacy:metadata:data'],
'privacy:metadata');
}
/**
* Get the list of contexts that contain user information for the specified user.
*
* @param int $userid The user to search.
* @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin.
*/
public static function get_contexts_for_userid(int $userid) : contextlist {
return new contextlist();
}
/**
* Export all user data for the specified user, in the specified contexts.
*
* @param approved_contextlist $contextlist The approved contexts to export information for.
*/
public static function export_user_data(approved_contextlist $contextlist) {
}
/**
* Delete all data for all users in the specified context.
*
* @param context $context The specific context to delete data for.
*/
public static function delete_data_for_all_users_in_context(\context $context) {
}
/**
* Delete all user data for the specified user, in the specified contexts.
*
* @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
*/
public static function delete_data_for_user(approved_contextlist $contextlist) {
}
}

View File

@ -38,6 +38,8 @@ $string['missingconfig'] = 'Your Apache Solr server is not yet configured in Moo
$string['multivaluedfield'] = 'Field "{$a}" returned an array instead of a scalar. Please delete the current index, create a new one and run setup_schema.php before indexing data in Solr.';
$string['nodatafromserver'] = 'No data from server';
$string['pluginname'] = 'Solr';
$string['privacy:metadata'] = 'This plugin sends data externally to a linked Solr search engine. It does not store data locally.';
$string['privacy:metadata:data'] = 'Personal data passed through from the search subsystem.';
$string['schemafieldautocreated'] = 'Field "{$a}" already exists in Solr schema. You probably forgot to run this script before indexing data and fields were autocreated by Solr. Please delete the current index, create a new one and run setup_schema.php again before indexing data in Solr.';
$string['schemasetupfromsolr5'] = 'Your Solr server version is lower than 5.0. This script can only set your schema if your Solr version is 5.0 or higher. You need to manually set the fields in your schema according to \\search_solr\\document::get_default_fields_definition().';
$string['searchinfo'] = 'Search queries';

View File

@ -0,0 +1,46 @@
<?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/>.
/**
* Privacy provider tests.
*
* @package search_solr
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Privacy provider tests class.
*
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class search_solr_privacy_test extends \core_privacy\tests\provider_testcase {
/**
* Verify that a collection of metadata is returned for this component and that it just links to an external location.
*/
public function test_get_metadata() {
$collection = new \core_privacy\local\metadata\collection('search_solr');
$collection = \search_solr\privacy\provider::get_metadata($collection);
$this->assertNotEmpty($collection);
$items = $collection->get_collection();
$this->assertEquals(1, count($items));
$this->assertInstanceOf(\core_privacy\local\metadata\types\external_location::class, $items[0]);
}
}

View File

@ -82,7 +82,7 @@ class mock_search_area extends \core_search\base {
$doc->set('title', $info->title);
$doc->set('content', $info->content);
$doc->set('description1', $info->description1);
$doc->set('description1', $info->description2);
$doc->set('description2', $info->description2);
$doc->set('contextid', $info->contextid);
$doc->set('courseid', $info->courseid);
$doc->set('userid', $info->userid);