���ѧۧݧ�ӧ�� �ާ֧ߧ֧էا֧� - ���֧էѧܧ�ڧ��ӧѧ�� - /home3/cpr76684/public_html/store.tar
���ѧ٧ѧ�
legacy/db/tasks.php 0000644 00000002227 15151765340 0010245 0 ustar 00 <?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/>. /** * Legacy log reader cron task. * * @package logstore_legacy * @copyright 2014 Petr Skoda {@link http://skodak.org} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $tasks = array( array( 'classname' => '\logstore_legacy\task\cleanup_task', 'blocking' => 0, 'minute' => 'R', 'hour' => '5', 'day' => '*', 'dayofweek' => '*', 'month' => '*' ), ); legacy/tests/fixtures/event.php 0000644 00000003665 15151765340 0012676 0 ustar 00 <?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/>. /** * Fixtures for legacy logging testing. * * @package logstore_legacy * @copyright 2014 Petr Skoda * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace logstore_legacy\event; defined('MOODLE_INTERNAL') || die(); class unittest_executed extends \core\event\base { public static function get_name() { return 'xxx'; } public function get_description() { return 'yyy'; } protected function init() { $this->data['crud'] = 'u'; $this->data['edulevel'] = self::LEVEL_PARTICIPATING; } public function get_url() { return new \moodle_url('/somepath/somefile.php', array('id' => $this->data['other']['sample'])); } public static function get_legacy_eventname() { return 'test_legacy'; } protected function get_legacy_eventdata() { return array($this->data['courseid'], $this->data['other']['sample']); } protected function get_legacy_logdata() { $cmid = 0; if ($this->contextlevel == CONTEXT_MODULE) { $cmid = $this->contextinstanceid; } return array($this->data['courseid'], 'core_unittest', 'view', 'unittest.php?id=' . $this->data['other']['sample'], 'bbb', $cmid); } } legacy/tests/fixtures/store.php 0000644 00000002752 15151765340 0012705 0 ustar 00 <?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/>. /** * Fixtures for legacy logging testing. * * @package logstore_legacy * @copyright 2014 onwards Ankit Agarwal <ankit.agrr@gmail.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace logstore_legacy\test; defined('MOODLE_INTERNAL') || die(); class unittest_logstore_legacy extends \logstore_legacy\log\store { /** * Wrapper to make protected method accessible during testing. * * @param string $select sql predicate. * @param array $params sql params. * @param string $sort sort options. * * @return array returns array of sql predicate, params and sorting criteria. */ public static function replace_sql_legacy($select, array $params, $sort = '') { return parent::replace_sql_legacy($select, $params, $sort); } } legacy/tests/privacy/provider_test.php 0000644 00000054265 15151765340 0014254 0 ustar 00 <?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/>. /** * Data provider tests. * * @package logstore_legacy * @category test * @copyright 2018 Frédéric Massart * @author Frédéric Massart <fred@branchup.tech> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace logstore_legacy\privacy; defined('MOODLE_INTERNAL') || die(); global $CFG; use core_privacy\tests\provider_testcase; use core_privacy\local\request\contextlist; use core_privacy\local\request\approved_contextlist; use core_privacy\local\request\transform; use core_privacy\local\request\writer; use logstore_legacy\privacy\provider; use logstore_legacy\event\unittest_executed; require_once(__DIR__ . '/../fixtures/event.php'); /** * Data provider testcase class. * * @package logstore_legacy * @category test * @copyright 2018 Frédéric Massart * @author Frédéric Massart <fred@branchup.tech> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider_test extends provider_testcase { public function setUp(): void { $this->resetAfterTest(); } public function test_get_contexts_for_userid() { $u1 = $this->getDataGenerator()->create_user(); $u2 = $this->getDataGenerator()->create_user(); $u3 = $this->getDataGenerator()->create_user(); $c1 = $this->getDataGenerator()->create_course(); $cm1 = $this->getDataGenerator()->create_module('url', ['course' => $c1]); $sysctx = \context_system::instance(); $c1ctx = \context_course::instance($c1->id); $cm1ctx = \context_module::instance($cm1->cmid); $this->enable_logging(); $manager = get_log_manager(true); // User 1 is the author. $this->setUser($u1); $this->assert_contextlist_equals($this->get_contextlist_for_user($u1), []); $e = unittest_executed::create(['context' => $cm1ctx, 'other' => ['sample' => 1]]); $e->trigger(); $this->assert_contextlist_equals($this->get_contextlist_for_user($u1), [$cm1ctx]); // User 2 is the author. $this->setUser($u2); $this->assert_contextlist_equals($this->get_contextlist_for_user($u2), []); $e = unittest_executed::create(['context' => $cm1ctx, 'other' => ['sample' => 2]]); $e->trigger(); $this->assert_contextlist_equals($this->get_contextlist_for_user($u2), [$cm1ctx]); // User 3 is the author. $this->setUser($u3); $this->assert_contextlist_equals($this->get_contextlist_for_user($u3), []); $e = unittest_executed::create(['context' => $sysctx, 'other' => ['sample' => 3]]); $e->trigger(); $this->assert_contextlist_equals($this->get_contextlist_for_user($u3), [$sysctx]); } /** * Test returning user IDs for a given context. */ public function test_add_userids_for_context() { $u1 = $this->getDataGenerator()->create_user(); $u2 = $this->getDataGenerator()->create_user(); $u3 = $this->getDataGenerator()->create_user(); $course = $this->getDataGenerator()->create_course(); $module = $this->getDataGenerator()->create_module('url', ['course' => $course]); $sysctx = \context_system::instance(); $c1ctx = \context_course::instance($course->id); $cm1ctx = \context_module::instance($module->cmid); $userctx = \context_user::instance($u1->id); $this->enable_logging(); $manager = get_log_manager(true); $this->setUser($u1); $e = unittest_executed::create(['context' => $sysctx, 'other' => ['sample' => 1]]); $e->trigger(); $this->setUser($u2); $e = unittest_executed::create(['context' => $sysctx, 'other' => ['sample' => 2]]); $e->trigger(); $e = unittest_executed::create(['context' => $sysctx, 'other' => ['sample' => 3]]); $e->trigger(); $this->setUser($u3); $e = unittest_executed::create(['context' => $c1ctx, 'other' => ['sample' => 4]]); $e->trigger(); $this->setUser($u1); $e = unittest_executed::create(['context' => $c1ctx, 'other' => ['sample' => 5]]); $e->trigger(); $e = unittest_executed::create(['context' => $cm1ctx, 'other' => ['sample' => 6]]); $e->trigger(); $this->setUser($u2); $e = unittest_executed::create(['context' => $cm1ctx, 'other' => ['sample' => 7]]); $e->trigger(); $this->setUser($u3); $e = unittest_executed::create(['context' => $cm1ctx, 'other' => ['sample' => 8]]); $e->trigger(); // Start with system and check that each of the contexts returns what we expected. $userlist = new \core_privacy\local\request\userlist($sysctx, 'logstore_legacy'); provider::add_userids_for_context($userlist); $systemuserids = $userlist->get_userids(); $this->assertCount(2, $systemuserids); $this->assertNotFalse(array_search($u1->id, $systemuserids)); $this->assertNotFalse(array_search($u2->id, $systemuserids)); // Check the course context. $userlist = new \core_privacy\local\request\userlist($c1ctx, 'logstore_legacy'); provider::add_userids_for_context($userlist); $courseuserids = $userlist->get_userids(); $this->assertCount(2, $courseuserids); $this->assertNotFalse(array_search($u1->id, $courseuserids)); $this->assertNotFalse(array_search($u3->id, $courseuserids)); // Check the module context. $userlist = new \core_privacy\local\request\userlist($cm1ctx, 'logstore_legacy'); provider::add_userids_for_context($userlist); $moduleuserids = $userlist->get_userids(); $this->assertCount(3, $moduleuserids); $this->assertNotFalse(array_search($u1->id, $moduleuserids)); $this->assertNotFalse(array_search($u2->id, $moduleuserids)); $this->assertNotFalse(array_search($u3->id, $moduleuserids)); } public function test_delete_data_for_user() { global $DB; $u1 = $this->getDataGenerator()->create_user(); $u2 = $this->getDataGenerator()->create_user(); $u3 = $this->getDataGenerator()->create_user(); $c1 = $this->getDataGenerator()->create_course(); $c2 = $this->getDataGenerator()->create_course(); $cm1 = $this->getDataGenerator()->create_module('url', ['course' => $c1]); $sysctx = \context_system::instance(); $c1ctx = \context_course::instance($c1->id); $c2ctx = \context_course::instance($c2->id); $cm1ctx = \context_module::instance($cm1->cmid); $this->enable_logging(); $manager = get_log_manager(true); // User 1 is the author. $this->setUser($u1); $e = unittest_executed::create(['context' => $cm1ctx, 'other' => ['sample' => 1]]); $e->trigger(); $e = unittest_executed::create(['context' => $cm1ctx, 'other' => ['sample' => 2]]); $e->trigger(); $e = unittest_executed::create(['context' => $c1ctx, 'other' => ['sample' => 3]]); $e->trigger(); $e = unittest_executed::create(['context' => $sysctx, 'other' => ['sample' => 4]]); $e->trigger(); // User 2 is the author. $this->setUser($u2); $e = unittest_executed::create(['context' => $cm1ctx, 'other' => ['sample' => 5]]); $e->trigger(); $e = unittest_executed::create(['context' => $c1ctx, 'other' => ['sample' => 6]]); $e->trigger(); $e = unittest_executed::create(['context' => $sysctx, 'other' => ['sample' => 7]]); $e->trigger(); // Assert what we have. $this->assertTrue($DB->record_exists('log', ['userid' => $u1->id, 'cmid' => $cm1->cmid, 'course' => $c1->id])); $this->assertTrue($DB->record_exists('log', ['userid' => $u1->id, 'cmid' => 0, 'course' => $c1->id])); $this->assertTrue($DB->record_exists('log', ['userid' => $u1->id, 'cmid' => 0, 'course' => 0])); $this->assertEquals(4, $DB->count_records('log', ['userid' => $u1->id])); $this->assertEquals(3, $DB->count_records('log', ['userid' => $u2->id])); // Delete other context. provider::delete_data_for_user(new approved_contextlist($u1, 'logstore_legacy', [$c2ctx->id])); $this->assertTrue($DB->record_exists('log', ['userid' => $u1->id, 'cmid' => $cm1->cmid, 'course' => $c1->id])); $this->assertTrue($DB->record_exists('log', ['userid' => $u1->id, 'cmid' => 0, 'course' => $c1->id])); $this->assertTrue($DB->record_exists('log', ['userid' => $u1->id, 'cmid' => 0, 'course' => 0])); $this->assertEquals(4, $DB->count_records('log', ['userid' => $u1->id])); $this->assertEquals(3, $DB->count_records('log', ['userid' => $u2->id])); // Delete system. provider::delete_data_for_user(new approved_contextlist($u1, 'logstore_legacy', [$sysctx->id])); $this->assertTrue($DB->record_exists('log', ['userid' => $u1->id, 'cmid' => $cm1->cmid, 'course' => $c1->id])); $this->assertTrue($DB->record_exists('log', ['userid' => $u1->id, 'cmid' => 0, 'course' => $c1->id])); $this->assertFalse($DB->record_exists('log', ['userid' => $u1->id, 'cmid' => 0, 'course' => 0])); $this->assertEquals(3, $DB->count_records('log', ['userid' => $u1->id])); $this->assertEquals(3, $DB->count_records('log', ['userid' => $u2->id])); // Delete course. provider::delete_data_for_user(new approved_contextlist($u1, 'logstore_legacy', [$c1ctx->id])); $this->assertTrue($DB->record_exists('log', ['userid' => $u1->id, 'cmid' => $cm1->cmid, 'course' => $c1->id])); $this->assertFalse($DB->record_exists('log', ['userid' => $u1->id, 'cmid' => 0, 'course' => $c1->id])); $this->assertFalse($DB->record_exists('log', ['userid' => $u1->id, 'cmid' => 0, 'course' => 0])); $this->assertEquals(2, $DB->count_records('log', ['userid' => $u1->id])); $this->assertEquals(3, $DB->count_records('log', ['userid' => $u2->id])); // Delete course. provider::delete_data_for_user(new approved_contextlist($u1, 'logstore_legacy', [$cm1ctx->id])); $this->assertFalse($DB->record_exists('log', ['userid' => $u1->id, 'cmid' => $cm1->cmid, 'course' => $c1->id])); $this->assertFalse($DB->record_exists('log', ['userid' => $u1->id, 'cmid' => 0, 'course' => $c1->id])); $this->assertFalse($DB->record_exists('log', ['userid' => $u1->id, 'cmid' => 0, 'course' => 0])); $this->assertEquals(0, $DB->count_records('log', ['userid' => $u1->id])); $this->assertEquals(3, $DB->count_records('log', ['userid' => $u2->id])); } public function test_delete_data_for_all_users_in_context() { global $DB; $u1 = $this->getDataGenerator()->create_user(); $u2 = $this->getDataGenerator()->create_user(); $u3 = $this->getDataGenerator()->create_user(); $c1 = $this->getDataGenerator()->create_course(); $c2 = $this->getDataGenerator()->create_course(); $cm1 = $this->getDataGenerator()->create_module('url', ['course' => $c1]); $sysctx = \context_system::instance(); $c1ctx = \context_course::instance($c1->id); $c2ctx = \context_course::instance($c2->id); $cm1ctx = \context_module::instance($cm1->cmid); $this->enable_logging(); $manager = get_log_manager(true); // User 1 is the author. $this->setUser($u1); $e = unittest_executed::create(['context' => $cm1ctx, 'other' => ['sample' => 1]]); $e->trigger(); $e = unittest_executed::create(['context' => $cm1ctx, 'other' => ['sample' => 2]]); $e->trigger(); $e = unittest_executed::create(['context' => $c1ctx, 'other' => ['sample' => 3]]); $e->trigger(); $e = unittest_executed::create(['context' => $sysctx, 'other' => ['sample' => 4]]); $e->trigger(); // User 2 is the author. $this->setUser($u2); $e = unittest_executed::create(['context' => $cm1ctx, 'other' => ['sample' => 5]]); $e->trigger(); $e = unittest_executed::create(['context' => $c1ctx, 'other' => ['sample' => 6]]); $e->trigger(); $e = unittest_executed::create(['context' => $sysctx, 'other' => ['sample' => 7]]); $e->trigger(); // Assert what we have. $this->assertTrue($DB->record_exists('log', ['cmid' => $cm1->cmid, 'course' => $c1->id])); $this->assertTrue($DB->record_exists('log', ['cmid' => 0, 'course' => $c1->id])); $this->assertTrue($DB->record_exists('log', ['cmid' => 0, 'course' => 0])); $this->assertEquals(4, $DB->count_records('log', ['userid' => $u1->id])); $this->assertEquals(3, $DB->count_records('log', ['userid' => $u2->id])); // Delete other context. provider::delete_data_for_all_users_in_context($c2ctx); $this->assertTrue($DB->record_exists('log', ['cmid' => $cm1->cmid, 'course' => $c1->id])); $this->assertTrue($DB->record_exists('log', ['cmid' => 0, 'course' => $c1->id])); $this->assertTrue($DB->record_exists('log', ['cmid' => 0, 'course' => 0])); $this->assertEquals(4, $DB->count_records('log', ['userid' => $u1->id])); $this->assertEquals(3, $DB->count_records('log', ['userid' => $u2->id])); // Delete system. provider::delete_data_for_all_users_in_context($sysctx); $this->assertTrue($DB->record_exists('log', ['cmid' => $cm1->cmid, 'course' => $c1->id])); $this->assertTrue($DB->record_exists('log', ['cmid' => 0, 'course' => $c1->id])); $this->assertFalse($DB->record_exists('log', ['cmid' => 0, 'course' => 0])); $this->assertEquals(3, $DB->count_records('log', ['userid' => $u1->id])); $this->assertEquals(2, $DB->count_records('log', ['userid' => $u2->id])); // Delete course. provider::delete_data_for_all_users_in_context($c1ctx); $this->assertTrue($DB->record_exists('log', ['cmid' => $cm1->cmid, 'course' => $c1->id])); $this->assertFalse($DB->record_exists('log', ['cmid' => 0, 'course' => $c1->id])); $this->assertFalse($DB->record_exists('log', ['cmid' => 0, 'course' => 0])); $this->assertEquals(2, $DB->count_records('log', ['userid' => $u1->id])); $this->assertEquals(1, $DB->count_records('log', ['userid' => $u2->id])); // Delete course. provider::delete_data_for_all_users_in_context($cm1ctx); $this->assertFalse($DB->record_exists('log', ['cmid' => $cm1->cmid, 'course' => $c1->id])); $this->assertFalse($DB->record_exists('log', ['cmid' => 0, 'course' => $c1->id])); $this->assertFalse($DB->record_exists('log', ['cmid' => 0, 'course' => 0])); $this->assertEquals(0, $DB->count_records('log', ['userid' => $u1->id])); $this->assertEquals(0, $DB->count_records('log', ['userid' => $u2->id])); } /** * Test the deletion of data for a list of users in a context. */ public function test_delete_data_for_userlist() { global $DB; $u1 = $this->getDataGenerator()->create_user(); $u2 = $this->getDataGenerator()->create_user(); $u3 = $this->getDataGenerator()->create_user(); $course = $this->getDataGenerator()->create_course(); $module = $this->getDataGenerator()->create_module('url', ['course' => $course]); $sysctx = \context_system::instance(); $c1ctx = \context_course::instance($course->id); $cm1ctx = \context_module::instance($module->cmid); $userctx = \context_user::instance($u1->id); $this->enable_logging(); $manager = get_log_manager(true); $this->setUser($u1); $e = unittest_executed::create(['context' => $sysctx, 'other' => ['sample' => 1]]); $e->trigger(); $this->setUser($u2); $e = unittest_executed::create(['context' => $sysctx, 'other' => ['sample' => 2]]); $e->trigger(); $e = unittest_executed::create(['context' => $sysctx, 'other' => ['sample' => 3]]); $e->trigger(); $this->setUser($u3); $e = unittest_executed::create(['context' => $c1ctx, 'other' => ['sample' => 4]]); $e->trigger(); $this->setUser($u1); $e = unittest_executed::create(['context' => $c1ctx, 'other' => ['sample' => 5]]); $e->trigger(); $e = unittest_executed::create(['context' => $cm1ctx, 'other' => ['sample' => 6]]); $e->trigger(); $this->setUser($u2); $e = unittest_executed::create(['context' => $cm1ctx, 'other' => ['sample' => 7]]); $e->trigger(); $this->setUser($u3); $e = unittest_executed::create(['context' => $cm1ctx, 'other' => ['sample' => 8]]); $e->trigger(); // System context deleting one user. $this->assertEquals(3, $DB->count_records('log', ['cmid' => 0, 'course' => 0])); $userlist = new \core_privacy\local\request\approved_userlist($sysctx, 'logstore_legacy', [$u2->id]); provider::delete_data_for_userlist($userlist); $this->assertEquals(1, $DB->count_records('log', ['cmid' => 0, 'course' => 0])); // Course context deleting one user. $this->assertEquals(2, $DB->count_records('log', ['cmid' => 0, 'course' => $course->id])); $userlist = new \core_privacy\local\request\approved_userlist($c1ctx, 'logstore_legacy', [$u1->id]); provider::delete_data_for_userlist($userlist); $this->assertEquals(1, $DB->count_records('log', ['cmid' => 0, 'course' => $course->id])); // Module context deleting two users. $this->assertEquals(3, $DB->count_records('log', ['cmid' => $module->cmid, 'course' => $course->id])); $userlist = new \core_privacy\local\request\approved_userlist($cm1ctx, 'logstore_legacy', [$u1->id, $u3->id]); provider::delete_data_for_userlist($userlist); $this->assertEquals(1, $DB->count_records('log', ['cmid' => $module->cmid, 'course' => $course->id])); } public function test_export_data_for_user() { global $DB; $u1 = $this->getDataGenerator()->create_user(); $u2 = $this->getDataGenerator()->create_user(); $u3 = $this->getDataGenerator()->create_user(); $c1 = $this->getDataGenerator()->create_course(); $c2 = $this->getDataGenerator()->create_course(); $cm1 = $this->getDataGenerator()->create_module('url', ['course' => $c1]); $sysctx = \context_system::instance(); $c1ctx = \context_course::instance($c1->id); $c2ctx = \context_course::instance($c2->id); $cm1ctx = \context_module::instance($cm1->cmid); $this->enable_logging(); $manager = get_log_manager(true); $path = [get_string('privacy:path:logs', 'tool_log'), get_string('pluginname', 'logstore_legacy')]; // User 1 is the author. $this->setUser($u1); $e = unittest_executed::create(['context' => $cm1ctx, 'other' => ['sample' => 1]]); $e->trigger(); $e = unittest_executed::create(['context' => $cm1ctx, 'other' => ['sample' => 2]]); $e->trigger(); $e = unittest_executed::create(['context' => $c1ctx, 'other' => ['sample' => 3]]); $e->trigger(); $e = unittest_executed::create(['context' => $sysctx, 'other' => ['sample' => 4]]); $e->trigger(); // User 2 is the author. $this->setUser($u2); $e = unittest_executed::create(['context' => $cm1ctx, 'other' => ['sample' => 5]]); $e->trigger(); $e = unittest_executed::create(['context' => $c1ctx, 'other' => ['sample' => 6]]); $e->trigger(); $e = unittest_executed::create(['context' => $sysctx, 'other' => ['sample' => 7]]); $e->trigger(); // Test export. provider::export_user_data(new approved_contextlist($u1, 'logstore_legacy', [$cm1ctx->id])); $data = writer::with_context($c1ctx)->get_data($path); $this->assertEmpty($data); $data = writer::with_context($cm1ctx)->get_data($path); $this->assertCount(2, $data->logs); writer::reset(); provider::export_user_data(new approved_contextlist($u1, 'logstore_legacy', [$c1ctx->id])); $data = writer::with_context($cm1ctx)->get_data($path); $this->assertEmpty($data); $data = writer::with_context($c1ctx)->get_data($path); $this->assertCount(1, $data->logs); writer::reset(); provider::export_user_data(new approved_contextlist($u1, 'logstore_legacy', [$sysctx->id])); $data = writer::with_context($sysctx)->get_data($path); $this->assertCount(1, $data->logs); } /** * Assert the content of a context list. * * @param contextlist $contextlist The collection. * @param array $expected List of expected contexts or IDs. * @return void */ protected function assert_contextlist_equals($contextlist, array $expected) { $expectedids = array_map(function($context) { if (is_object($context)) { return $context->id; } return $context; }, $expected); $contextids = array_map('intval', $contextlist->get_contextids()); sort($contextids); sort($expectedids); $this->assertEquals($expectedids, $contextids); } /** * Enable logging. * * @return void */ protected function enable_logging() { set_config('enabled_stores', 'logstore_legacy', 'tool_log'); set_config('loglegacy', 1, 'logstore_legacy'); get_log_manager(true); } /** * Get the contextlist for a user. * * @param object $user The user. * @return contextlist */ protected function get_contextlist_for_user($user) { $contextlist = new contextlist(); provider::add_contexts_for_userid($contextlist, $user->id); return $contextlist; } } legacy/tests/store_test.php 0000644 00000030620 15151765340 0012066 0 ustar 00 <?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/>. namespace logstore_legacy; defined('MOODLE_INTERNAL') || die(); require_once(__DIR__ . '/fixtures/event.php'); require_once(__DIR__ . '/fixtures/store.php'); /** * Legacy log store tests. * * @package logstore_legacy * @copyright 2014 Petr Skoda {@link http://skodak.org/} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class store_test extends \advanced_testcase { public function test_log_writing() { global $DB; $this->resetAfterTest(); $this->setAdminUser(); $user1 = $this->getDataGenerator()->create_user(); $course1 = $this->getDataGenerator()->create_course(); $module1 = $this->getDataGenerator()->create_module('resource', array('course' => $course1)); $course2 = $this->getDataGenerator()->create_course(); // Enable legacy logging plugin. set_config('enabled_stores', 'logstore_legacy', 'tool_log'); set_config('loglegacy', 1, 'logstore_legacy'); $manager = get_log_manager(true); $stores = $manager->get_readers(); $this->assertCount(1, $stores); $this->assertEquals(array('logstore_legacy'), array_keys($stores)); $store = $stores['logstore_legacy']; $this->assertInstanceOf('logstore_legacy\log\store', $store); $this->assertInstanceOf('core\log\sql_reader', $store); $this->assertTrue($store->is_logging()); $logs = $DB->get_records('log', array(), 'id ASC'); $this->assertCount(0, $logs); $this->setCurrentTimeStart(); $this->setUser(0); $event1 = \logstore_legacy\event\unittest_executed::create( array('context' => \context_module::instance($module1->cmid), 'other' => array('sample' => 5, 'xx' => 10))); $event1->trigger(); $this->setUser($user1); $event2 = \logstore_legacy\event\unittest_executed::create( array('context' => \context_course::instance($course2->id), 'other' => array('sample' => 6, 'xx' => 11))); $event2->trigger(); $logs = $DB->get_records('log', array(), 'id ASC'); $this->assertCount(2, $logs); $log = array_shift($logs); $this->assertNotEmpty($log->id); $this->assertTimeCurrent($log->time); $this->assertEquals(0, $log->userid); $this->assertSame('0.0.0.0', $log->ip); $this->assertEquals($course1->id, $log->course); $this->assertSame('core_unittest', $log->module); $this->assertEquals($module1->cmid, $log->cmid); $this->assertSame('view', $log->action); $this->assertSame('unittest.php?id=5', $log->url); $this->assertSame('bbb', $log->info); $oldlogid = $log->id; $log = array_shift($logs); $this->assertGreaterThan($oldlogid, $log->id); $this->assertNotEmpty($log->id); $this->assertTimeCurrent($log->time); $this->assertEquals($user1->id, $log->userid); $this->assertSame('0.0.0.0', $log->ip); $this->assertEquals($course2->id, $log->course); $this->assertSame('core_unittest', $log->module); $this->assertEquals(0, $log->cmid); $this->assertSame('view', $log->action); $this->assertSame('unittest.php?id=6', $log->url); $this->assertSame('bbb', $log->info); // Test if disabling works. set_config('enabled_stores', 'logstore_legacy', 'tool_log'); set_config('loglegacy', 0, 'logstore_legacy'); $manager = get_log_manager(true); $stores = $manager->get_readers(); $store = $stores['logstore_legacy']; $this->assertFalse($store->is_logging()); \logstore_legacy\event\unittest_executed::create( array('context' => \context_system::instance(), 'other' => array('sample' => 5, 'xx' => 10)))->trigger(); $this->assertEquals(2, $DB->count_records('log')); // Another way to disable legacy completely. set_config('enabled_stores', 'logstore_standard', 'tool_log'); set_config('loglegacy', 1, 'logstore_legacy'); get_log_manager(true); \logstore_legacy\event\unittest_executed::create( array('context' => \context_system::instance(), 'other' => array('sample' => 5, 'xx' => 10)))->trigger(); $this->assertEquals(2, $DB->count_records('log')); // Set everything back. set_config('enabled_stores', '', 'tool_log'); set_config('loglegacy', 0, 'logstore_legacy'); get_log_manager(true); } /** * Test replace_sql_legacy() */ public function test_replace_sql_legacy() { $selectwhere = "userid = :userid AND courseid = :courseid AND eventname = :eventname AND timecreated > :since"; $params = array('userid' => 2, 'since' => 3, 'courseid' => 4, 'eventname' => '\core\event\course_created'); $expectedselect = "module = 'course' AND action = 'new' AND userid = :userid AND url = :url AND time > :since"; $expectedparams = $params + array('url' => "view.php?id=4"); list($replaceselect, $replaceparams) = \logstore_legacy\test\unittest_logstore_legacy::replace_sql_legacy($selectwhere, $params); $this->assertEquals($replaceselect, $expectedselect); $this->assertEquals($replaceparams, $expectedparams); // Test CRUD related changes. $selectwhere = "edulevel = 0"; list($updatewhere, $replaceparams) = \logstore_legacy\test\unittest_logstore_legacy::replace_sql_legacy($selectwhere, $params); $this->assertEquals($selectwhere, $updatewhere); $selectwhere = "edulevel = 0 and crud = 'u'"; list($updatewhere, $replaceparams) = \logstore_legacy\test\unittest_logstore_legacy::replace_sql_legacy($selectwhere, $params); $this->assertEquals("edulevel = 0 and action LIKE '%update%'", $updatewhere); $selectwhere = "edulevel = 0 and crud != 'u'"; list($updatewhere, $replaceparams) = \logstore_legacy\test\unittest_logstore_legacy::replace_sql_legacy($selectwhere, $params); $this->assertEquals("edulevel = 0 and action NOT LIKE '%update%'", $updatewhere); $selectwhere = "edulevel = 0 and crud <> 'u'"; list($updatewhere, $replaceparams) = \logstore_legacy\test\unittest_logstore_legacy::replace_sql_legacy($selectwhere, $params); $this->assertEquals("edulevel = 0 and action NOT LIKE '%update%'", $updatewhere); $selectwhere = "edulevel = 0 and crud = 'r'"; list($updatewhere, $replaceparams) = \logstore_legacy\test\unittest_logstore_legacy::replace_sql_legacy($selectwhere, $params); $this->assertEquals("edulevel = 0 and action LIKE '%view%' OR action LIKE '%report%'", $updatewhere); $selectwhere = "edulevel = 0 and crud != 'r'"; list($updatewhere, $replaceparams) = \logstore_legacy\test\unittest_logstore_legacy::replace_sql_legacy($selectwhere, $params); $this->assertEquals("edulevel = 0 and action NOT LIKE '%view%' AND action NOT LIKE '%report%'", $updatewhere); $selectwhere = "edulevel = 0 and crud <> 'r'"; list($updatewhere, $replaceparams) = \logstore_legacy\test\unittest_logstore_legacy::replace_sql_legacy($selectwhere, $params); $this->assertEquals("edulevel = 0 and action NOT LIKE '%view%' AND action NOT LIKE '%report%'", $updatewhere); // The slq is incorrect, since quotes must not be present. Make sure this is not parsed. $selectwhere = "edulevel = 0 and 'crud' != 'u'"; list($updatewhere, $replaceparams) = \logstore_legacy\test\unittest_logstore_legacy::replace_sql_legacy($selectwhere, $params); $this->assertNotEquals("edulevel = 0 and action NOT LIKE '%update%'", $updatewhere); $selectwhere = "edulevel = 0 and crud = 'u' OR crud != 'r' or crud <> 'd'"; list($updatewhere, $replaceparams) = \logstore_legacy\test\unittest_logstore_legacy::replace_sql_legacy($selectwhere, $params); $this->assertEquals("edulevel = 0 and action LIKE '%update%' OR action NOT LIKE '%view%' AND action NOT LIKE '%report%' or action NOT LIKE '%delete%'", $updatewhere); // The anonymous select returns all data. $selectwhere = "anonymous = 0"; list($updatewhere, $replaceparams) = \logstore_legacy\test\unittest_logstore_legacy::replace_sql_legacy($selectwhere, $params); $this->assertSame("0 = 0", $updatewhere); // Test legacy field names are mapped. $selectwhere = "timecreated = :timecreated and courseid = :courseid and contextinstanceid = :contextinstanceid and origin = :origin"; $params = array('timecreated' => 2, 'courseid' => 3, 'contextinstanceid' => 4, 'origin' => 5 ); $expectedparams = array('time' => 2, 'course' => 3, 'cmid' => 4, 'ip' => 5); list($updatewhere, $replaceparams) = \logstore_legacy\test\unittest_logstore_legacy::replace_sql_legacy($selectwhere, $params); $this->assertEquals("time = :time and course = :course and cmid = :cmid and ip = :ip", $updatewhere); $this->assertSame($expectedparams, $replaceparams); // Test sorting parameters. $selectwhere = "courseid = 3"; $params = array(); $sort = 'timecreated DESC'; list($updatewhere, $replaceparams, $sort) = \logstore_legacy\test\unittest_logstore_legacy::replace_sql_legacy($selectwhere, $params, $sort); $this->assertSame('time DESC', $sort); $sort = 'courseid DESC'; list($updatewhere, $replaceparams, $sort) = \logstore_legacy\test\unittest_logstore_legacy::replace_sql_legacy($selectwhere, $params, $sort); $this->assertSame('course DESC', $sort); $sort = 'contextinstanceid DESC'; list($updatewhere, $replaceparams, $sort) = \logstore_legacy\test\unittest_logstore_legacy::replace_sql_legacy($selectwhere, $params, $sort); $this->assertSame('cmid DESC', $sort); $sort = 'origin DESC'; list($updatewhere, $replaceparams, $sort) = \logstore_legacy\test\unittest_logstore_legacy::replace_sql_legacy($selectwhere, $params, $sort); $this->assertSame('ip DESC', $sort); } /* * Test logmanager::get_supported_reports returns all reports that require this store. */ public function test_get_supported_reports() { $logmanager = get_log_manager(); $allreports = \core_component::get_plugin_list('report'); // Make sure all supported reports are installed. $expectedreports = array_intersect_key([ 'log' => 'report_log', 'loglive' => 'report_loglive', 'outline' => 'report_outline', 'participation' => 'report_participation', 'stats' => 'report_stats' ], $allreports); $supportedreports = $logmanager->get_supported_reports('logstore_legacy'); foreach ($expectedreports as $expectedreport) { $this->assertArrayHasKey($expectedreport, $supportedreports); } } /** * Test that the legacy log cleanup works correctly. */ public function test_cleanup_task() { global $DB; $this->resetAfterTest(); // Create some records spread over various days; test multiple iterations in cleanup. $record = (object) array('time' => time()); $DB->insert_record('log', $record); $record->time -= 3600 * 24 * 30; $DB->insert_record('log', $record); $record->time -= 3600 * 24 * 30; $DB->insert_record('log', $record); $record->time -= 3600 * 24 * 30; $DB->insert_record('log', $record); $this->assertEquals(4, $DB->count_records('log')); // Remove all logs before "today". set_config('loglifetime', 1); $this->expectOutputString(" Deleted old legacy log records\n"); $clean = new \logstore_legacy\task\cleanup_task(); $clean->execute(); $this->assertEquals(1, $DB->count_records('log')); } } legacy/settings.php 0000644 00000004372 15151765340 0010376 0 ustar 00 <?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/>. /** * Legacy logging settings. * * @package logstore_legacy * @copyright 2014 Petr Skoda {@link http://skodak.org} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); if ($hassiteconfig) { $settings->add(new admin_setting_configcheckbox('logstore_legacy/loglegacy', new lang_string('loglegacy', 'logstore_legacy'), new lang_string('loglegacy_help', 'logstore_legacy'), 0)); $settings->add(new admin_setting_configcheckbox('logguests', new lang_string('logguests', 'admin'), new lang_string('logguests_help', 'admin'), 1)); $options = array(0 => new lang_string('neverdeletelogs'), 1000 => new lang_string('numdays', '', 1000), 365 => new lang_string('numdays', '', 365), 180 => new lang_string('numdays', '', 180), 150 => new lang_string('numdays', '', 150), 120 => new lang_string('numdays', '', 120), 90 => new lang_string('numdays', '', 90), 60 => new lang_string('numdays', '', 60), 35 => new lang_string('numdays', '', 35), 10 => new lang_string('numdays', '', 10), 5 => new lang_string('numdays', '', 5), 2 => new lang_string('numdays', '', 2)); $settings->add(new admin_setting_configselect('loglifetime', new lang_string('loglifetime', 'admin'), new lang_string('configloglifetime', 'admin'), 0, $options)); } legacy/version.php 0000644 00000002207 15151765340 0010216 0 ustar 00 <?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/>. /** * Legacy log reader. * * @package logstore_legacy * @copyright 2013 Petr Skoda {@link http://skodak.org} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2022112800; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2022111800; // Requires this Moodle version. $plugin->component = 'logstore_legacy'; // Full name of the plugin (used for diagnostics). legacy/classes/privacy/provider.php 0000644 00000022331 15151765340 0013475 0 ustar 00 <?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/>. /** * Data provider. * * @package logstore_legacy * @copyright 2018 Frédéric Massart * @author Frédéric Massart <fred@branchup.tech> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace logstore_legacy\privacy; defined('MOODLE_INTERNAL') || die(); use context; use core_privacy\local\metadata\collection; use core_privacy\local\request\approved_contextlist; use core_privacy\local\request\contextlist; use core_privacy\local\request\transform; use core_privacy\local\request\writer; use tool_log\local\privacy\helper; /** * Data provider class. * * @package logstore_legacy * @copyright 2018 Frédéric Massart * @author Frédéric Massart <fred@branchup.tech> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\provider, \tool_log\local\privacy\logstore_provider, \tool_log\local\privacy\logstore_userlist_provider { /** * Returns metadata. * * @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_external_location_link('log', [ 'time' => 'privacy:metadata:log:time', 'userid' => 'privacy:metadata:log:userid', 'ip' => 'privacy:metadata:log:ip', 'action' => 'privacy:metadata:log:action', 'url' => 'privacy:metadata:log:url', 'info' => 'privacy:metadata:log:info', ], 'privacy:metadata:log'); return $collection; } /** * Add contexts that contain user information for the specified user. * * @param contextlist $contextlist The contextlist to add the contexts to. * @param int $userid The user to find the contexts for. * @return void */ public static function add_contexts_for_userid(contextlist $contextlist, $userid) { $sql = " SELECT ctx.id FROM {context} ctx JOIN {log} l ON (l.cmid = 0 AND l.course = ctx.instanceid AND ctx.contextlevel = :courselevel) OR (l.cmid > 0 AND l.cmid = ctx.instanceid AND ctx.contextlevel = :modulelevel) OR (l.course <= 0 AND ctx.id = :syscontextid) WHERE l.userid = :userid"; $params = [ 'courselevel' => CONTEXT_COURSE, 'modulelevel' => CONTEXT_MODULE, 'syscontextid' => SYSCONTEXTID, 'userid' => $userid, ]; $contextlist->add_from_sql($sql, $params); } /** * Add user IDs that contain user information for the specified context. * * @param \core_privacy\local\request\userlist $userlist The userlist to add the users to. * @return void */ public static function add_userids_for_context(\core_privacy\local\request\userlist $userlist) { $context = $userlist->get_context(); list($insql, $params) = static::get_sql_where_from_contexts([$context]); $sql = "SELECT l.userid FROM {log} l WHERE $insql"; $userlist->add_from_sql('userid', $sql, $params); } /** * 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; $userid = $contextlist->get_user()->id; list($insql, $inparams) = static::get_sql_where_from_contexts($contextlist->get_contexts()); if (empty($insql)) { return; } $sql = "userid = :userid AND $insql"; $params = array_merge($inparams, ['userid' => $userid]); $path = [get_string('privacy:path:logs', 'tool_log'), get_string('pluginname', 'logstore_legacy')]; $flush = function($lastcontextid, $data) use ($path) { $context = context::instance_by_id($lastcontextid); writer::with_context($context)->export_data($path, (object) ['logs' => $data]); }; $lastcontextid = null; $data = []; $recordset = $DB->get_recordset_select('log', $sql, $params, 'course, cmid, time, id'); foreach ($recordset as $record) { $event = \logstore_legacy\event\legacy_logged::restore_legacy($record); $context = $event->get_context(); if ($lastcontextid && $lastcontextid != $context->id) { $flush($lastcontextid, $data); $data = []; } $extra = $event->get_logextra(); $data[] = [ 'name' => $event->get_name(), 'description' => $event->get_description(), 'timecreated' => transform::datetime($event->timecreated), 'ip' => $extra['ip'], 'origin' => helper::transform_origin($extra['origin']), ]; $lastcontextid = $context->id; } if ($lastcontextid) { $flush($lastcontextid, $data); } $recordset->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; list($sql, $params) = static::get_sql_where_from_contexts([$context]); if (empty($sql)) { return; } $DB->delete_records_select('log', $sql, $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; list($sql, $params) = static::get_sql_where_from_contexts($contextlist->get_contexts()); if (empty($sql)) { return; } $userid = $contextlist->get_user()->id; $DB->delete_records_select('log', "$sql AND userid = :userid", array_merge($params, ['userid' => $userid])); } /** * Delete all data for a list of users in the specified context. * * @param \core_privacy\local\request\approved_userlist $userlist The specific context and users to delete data for. * @return void */ public static function delete_data_for_userlist(\core_privacy\local\request\approved_userlist $userlist) { global $DB; list($sql, $params) = static::get_sql_where_from_contexts([$userlist->get_context()]); if (empty($sql)) { return; } list($usersql, $userparams) = $DB->get_in_or_equal($userlist->get_userids(), SQL_PARAMS_NAMED); $params = array_merge($params, $userparams); $DB->delete_records_select('log', "$sql AND userid $usersql", $params); } /** * Get an SQL where statement from a list of contexts. * * @param array $contexts The contexts. * @return array [$sql, $params] */ protected static function get_sql_where_from_contexts(array $contexts) { global $DB; $sorted = array_reduce($contexts, function ($carry, $context) { $level = $context->contextlevel; if ($level == CONTEXT_MODULE || $level == CONTEXT_COURSE) { $carry[$level][] = $context->instanceid; } else if ($level == CONTEXT_SYSTEM) { $carry[$level] = $context->id; } return $carry; }, [ CONTEXT_COURSE => [], CONTEXT_MODULE => [], CONTEXT_SYSTEM => null, ]); $sqls = []; $params = []; if (!empty($sorted[CONTEXT_MODULE])) { list($insql, $inparams) = $DB->get_in_or_equal($sorted[CONTEXT_MODULE], SQL_PARAMS_NAMED); $sqls[] = "cmid $insql"; $params = array_merge($params, $inparams); } if (!empty($sorted[CONTEXT_COURSE])) { list($insql, $inparams) = $DB->get_in_or_equal($sorted[CONTEXT_COURSE], SQL_PARAMS_NAMED); $sqls[] = "cmid = 0 AND course $insql"; $params = array_merge($params, $inparams); } if (!empty($sorted[CONTEXT_SYSTEM])) { $sqls[] = "course <= 0"; } if (empty($sqls)) { return [null, null]; } return ['((' . implode(') OR (', $sqls) . '))', $params]; } } legacy/classes/log/store.php 0000644 00000041701 15151765340 0012105 0 ustar 00 <?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/>. /** * Legacy log reader. * @deprecated since Moodle 3.6 MDL-52953 - Please use supported log stores such as "standard" or "external" instead. * @todo MDL-52805 This is to be removed in Moodle 3.10 * * @package logstore_legacy * @copyright 2013 Petr Skoda {@link http://skodak.org} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace logstore_legacy\log; defined('MOODLE_INTERNAL') || die(); class store implements \tool_log\log\store, \core\log\sql_reader { use \tool_log\helper\store, \tool_log\helper\reader; /** * @deprecated since Moodle 3.6 MDL-52953 - Please use supported log stores such as "standard" or "external" instead. * @todo MDL-52805 This is to be removed in Moodle 3.10 * * @param \tool_log\log\manager $manager */ public function __construct(\tool_log\log\manager $manager) { $this->helper_setup($manager); } /** @var array list of db fields which needs to be replaced for legacy log query */ protected static $standardtolegacyfields = array( 'timecreated' => 'time', 'courseid' => 'course', 'contextinstanceid' => 'cmid', 'origin' => 'ip', 'anonymous' => 0, ); /** @var string Regex to replace the crud params */ const CRUD_REGEX = "/(crud).*?(<>|=|!=).*?'(.*?)'/s"; /** * This method contains mapping required for Moodle core to make legacy store compatible with other sql_reader based * queries. * * @param string $selectwhere Select statment * @param array $params params for the sql * @param string $sort sort fields * * @return array returns an array containing the sql predicate, an array of params and sorting parameter. */ protected static function replace_sql_legacy($selectwhere, array $params, $sort = '') { // Following mapping is done to make can_delete_course() compatible with legacy store. if ($selectwhere == "userid = :userid AND courseid = :courseid AND eventname = :eventname AND timecreated > :since" and empty($sort)) { $replace = "module = 'course' AND action = 'new' AND userid = :userid AND url = :url AND time > :since"; $params += array('url' => "view.php?id={$params['courseid']}"); return array($replace, $params, $sort); } // Replace db field names to make it compatible with legacy log. foreach (self::$standardtolegacyfields as $from => $to) { $selectwhere = str_replace($from, $to, $selectwhere); if (!empty($sort)) { $sort = str_replace($from, $to, $sort); } if (isset($params[$from])) { $params[$to] = $params[$from]; unset($params[$from]); } } // Replace crud fields. $selectwhere = preg_replace_callback("/(crud).*?(<>|=|!=).*?'(.*?)'/s", 'self::replace_crud', $selectwhere); return array($selectwhere, $params, $sort); } /** * @deprecated since Moodle 3.6 MDL-52953 - Please use supported log stores such as "standard" or "external" instead. * @todo MDL-52805 This will be removed in Moodle 3.10 * * @param string $selectwhere * @param array $params * @param string $sort * @param int $limitfrom * @param int $limitnum * @return array */ public function get_events_select($selectwhere, array $params, $sort, $limitfrom, $limitnum) { global $DB; $sort = self::tweak_sort_by_id($sort); // Replace the query with hardcoded mappings required for core. list($selectwhere, $params, $sort) = self::replace_sql_legacy($selectwhere, $params, $sort); $records = array(); try { // A custom report + on the fly SQL rewriting = a possible exception. $records = $DB->get_recordset_select('log', $selectwhere, $params, $sort, '*', $limitfrom, $limitnum); } catch (\moodle_exception $ex) { debugging("error converting legacy event data " . $ex->getMessage() . $ex->debuginfo, DEBUG_DEVELOPER); return array(); } $events = array(); foreach ($records as $data) { $events[$data->id] = $this->get_log_event($data); } $records->close(); return $events; } /** * Get whether events are present for the given select clause. * @deprecated since Moodle 3.6 MDL-52953 - Please use supported log stores such as "standard" or "external" instead. * * @param string $selectwhere select conditions. * @param array $params params. * * @return bool Whether events available for the given conditions */ public function get_events_select_exists(string $selectwhere, array $params): bool { global $DB; // Replace the query with hardcoded mappings required for core. list($selectwhere, $params) = self::replace_sql_legacy($selectwhere, $params); try { return $DB->record_exists_select('log', $selectwhere, $params); } catch (\moodle_exception $ex) { debugging("error converting legacy event data " . $ex->getMessage() . $ex->debuginfo, DEBUG_DEVELOPER); return false; } } /** * Fetch records using given criteria returning a Traversable object. * @deprecated since Moodle 3.6 MDL-52953 - Please use supported log stores such as "standard" or "external" instead. * @todo MDL-52805 This will be removed in Moodle 3.10 * * Note that the traversable object contains a moodle_recordset, so * remember that is important that you call close() once you finish * using it. * * @param string $selectwhere * @param array $params * @param string $sort * @param int $limitfrom * @param int $limitnum * @return \Traversable|\core\event\base[] */ public function get_events_select_iterator($selectwhere, array $params, $sort, $limitfrom, $limitnum) { global $DB; $sort = self::tweak_sort_by_id($sort); // Replace the query with hardcoded mappings required for core. list($selectwhere, $params, $sort) = self::replace_sql_legacy($selectwhere, $params, $sort); try { $recordset = $DB->get_recordset_select('log', $selectwhere, $params, $sort, '*', $limitfrom, $limitnum); } catch (\moodle_exception $ex) { debugging("error converting legacy event data " . $ex->getMessage() . $ex->debuginfo, DEBUG_DEVELOPER); return new \EmptyIterator; } return new \core\dml\recordset_walk($recordset, array($this, 'get_log_event')); } /** * Returns an event from the log data. * @deprecated since Moodle 3.6 MDL-52953 - Please use supported log stores such as "standard" or "external" instead. * @todo MDL-52805 This will be removed in Moodle 3.10 * * @param stdClass $data Log data * @return \core\event\base */ public function get_log_event($data) { return \logstore_legacy\event\legacy_logged::restore_legacy($data); } /** * @deprecated since Moodle 3.6 MDL-52953 - Please use supported log stores such as "standard" or "external" instead. * @todo MDL-52805 This will be removed in Moodle 3.10 * * @param string $selectwhere * @param array $params * @return int */ public function get_events_select_count($selectwhere, array $params) { global $DB; // Replace the query with hardcoded mappings required for core. list($selectwhere, $params) = self::replace_sql_legacy($selectwhere, $params); try { return $DB->count_records_select('log', $selectwhere, $params); } catch (\moodle_exception $ex) { debugging("error converting legacy event data " . $ex->getMessage() . $ex->debuginfo, DEBUG_DEVELOPER); return 0; } } /** * Are the new events appearing in the reader? * @deprecated since Moodle 3.6 MDL-52953 - Please use supported log stores such as "standard" or "external" instead. * @todo MDL-52805 This will be removed in Moodle 3.10 * * @return bool true means new log events are being added, false means no new data will be added */ public function is_logging() { return (bool)$this->get_config('loglegacy', true); } /** * @deprecated since Moodle 3.6 MDL-52953 - Please use supported log stores such as "standard" or "external" instead. * @todo MDL-52805 This will be removed in Moodle 3.10 */ public function dispose() { } /** * Legacy add_to_log() code. * @deprecated since Moodle 3.1 MDL-45104 - Please use supported log stores such as "standard" or "external" instead. * @todo MDL-52805 This will be removed in Moodle 3.3 * * @param int $courseid The course id * @param string $module The module name e.g. forum, journal, resource, course, user etc * @param string $action 'view', 'update', 'add' or 'delete', possibly followed by another word to clarify. * @param string $url The file and parameters used to see the results of the action * @param string $info Additional description information * @param int $cm The course_module->id if there is one * @param int|\stdClass $user If log regards $user other than $USER * @param string $ip Override the IP, should only be used for restore. * @param int $time Override the log time, should only be used for restore. */ public function legacy_add_to_log($courseid, $module, $action, $url, $info, $cm, $user, $ip = null, $time = null) { // Note that this function intentionally does not follow the normal Moodle DB access idioms. // This is for a good reason: it is the most frequently used DB update function, // so it has been optimised for speed. global $DB, $CFG, $USER; if (!$this->is_logging()) { return; } if ($cm === '' || is_null($cm)) { // Postgres won't translate empty string to its default. $cm = 0; } if ($user) { $userid = $user; } else { if (\core\session\manager::is_loggedinas()) { // Don't log. return; } $userid = empty($USER->id) ? '0' : $USER->id; } if (isset($CFG->logguests) and !$CFG->logguests) { if (!$userid or isguestuser($userid)) { return; } } $remoteaddr = (is_null($ip)) ? getremoteaddr() : $ip; $timenow = (is_null($time)) ? time() : $time; if (!empty($url)) { // Could break doing html_entity_decode on an empty var. $url = html_entity_decode($url, ENT_QUOTES, 'UTF-8'); } else { $url = ''; } // Restrict length of log lines to the space actually available in the // database so that it doesn't cause a DB error. Log a warning so that // developers can avoid doing things which are likely to cause this on a // routine basis. if (\core_text::strlen($action) > 40) { $action = \core_text::substr($action, 0, 37) . '...'; debugging('Warning: logged very long action', DEBUG_DEVELOPER); } if (!empty($info) && \core_text::strlen($info) > 255) { $info = \core_text::substr($info, 0, 252) . '...'; debugging('Warning: logged very long info', DEBUG_DEVELOPER); } // If the 100 field size is changed, also need to alter print_log in course/lib.php. if (!empty($url) && \core_text::strlen($url) > 100) { $url = \core_text::substr($url, 0, 97) . '...'; debugging('Warning: logged very long URL', DEBUG_DEVELOPER); } if (defined('MDL_PERFDB')) { global $PERF; $PERF->logwrites++; }; $log = array('time' => $timenow, 'userid' => $userid, 'course' => $courseid, 'ip' => $remoteaddr, 'module' => $module, 'cmid' => $cm, 'action' => $action, 'url' => $url, 'info' => $info); try { $DB->insert_record_raw('log', $log, false); } catch (\dml_exception $e) { debugging('Error: Could not insert a new entry to the Moodle log. ' . $e->errorcode, DEBUG_ALL); // MDL-11893, alert $CFG->supportemail if insert into log failed. if ($CFG->supportemail and empty($CFG->noemailever)) { // Function email_to_user is not usable because email_to_user tries to write to the logs table, // and this will get caught in an infinite loop, if disk is full. $site = get_site(); $subject = 'Insert into log failed at your moodle site ' . $site->fullname; $message = "Insert into log table failed at " . date('l dS \of F Y h:i:s A') . ".\n It is possible that your disk is full.\n\n"; $message .= "The failed query parameters are:\n\n" . var_export($log, true); $lasttime = get_config('admin', 'lastloginserterrormail'); if (empty($lasttime) || time() - $lasttime > 60 * 60 * 24) { // Limit to 1 email per day. // Using email directly rather than messaging as they may not be able to log in to access a message. mail($CFG->supportemail, $subject, $message); set_config('lastloginserterrormail', time(), 'admin'); } } } } /** * Generate a replace string for crud related sql conditions. This function is called as callback to preg_replace_callback() * on the actual sql. * * @param array $match matched string for the passed pattern * * @return string The sql string to use instead of original */ protected static function replace_crud($match) { $return = ''; unset($match[0]); // The first entry is the whole string. foreach ($match as $m) { // We hard code LIKE here because we are not worried about case sensitivity and want this to be fast. switch ($m) { case 'crud' : $replace = 'action'; break; case 'c' : switch ($match[2]) { case '=' : $replace = " LIKE '%add%'"; break; case '!=' : case '<>' : $replace = " NOT LIKE '%add%'"; break; default: $replace = ''; } break; case 'r' : switch ($match[2]) { case '=' : $replace = " LIKE '%view%' OR action LIKE '%report%'"; break; case '!=' : case '<>' : $replace = " NOT LIKE '%view%' AND action NOT LIKE '%report%'"; break; default: $replace = ''; } break; case 'u' : switch ($match[2]) { case '=' : $replace = " LIKE '%update%'"; break; case '!=' : case '<>' : $replace = " NOT LIKE '%update%'"; break; default: $replace = ''; } break; case 'd' : switch ($match[2]) { case '=' : $replace = " LIKE '%delete%'"; break; case '!=' : case '<>' : $replace = " NOT LIKE '%delete%'"; break; default: $replace = ''; } break; default : $replace = ''; } $return .= $replace; } return $return; } } legacy/classes/event/legacy_logged.php 0000644 00000003230 15151765340 0014071 0 ustar 00 <?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/>. namespace logstore_legacy\event; defined('MOODLE_INTERNAL') || die(); /** * Legacy log emulation event class. * * @package core * @since Moodle 2.7 * @copyright 2013 Petr Skoda {@link http://skodak.org} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class legacy_logged extends \core\event\base { public function init() { throw new \coding_exception('legacy events cannot be triggered'); } public static function get_name() { return get_string('eventlegacylogged', 'logstore_legacy'); } public function get_description() { return $this->other['module'] . ' ' . $this->other['action'] . ' ' . $this->other['info']; } public function get_url() { global $CFG; require_once("$CFG->dirroot/course/lib.php"); $url = \make_log_url($this->other['module'], $this->other['url']); if (!$url) { return null; } return new \moodle_url($url); } } legacy/classes/task/cleanup_task.php 0000644 00000004514 15151765340 0013604 0 ustar 00 <?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/>. /** * Legacy log reader. * * @package logstore_legacy * @copyright 2014 Petr Skoda {@link http://skodak.org} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace logstore_legacy\task; defined('MOODLE_INTERNAL') || die(); class cleanup_task extends \core\task\scheduled_task { /** * Get a descriptive name for this task (shown to admins). * * @return string */ public function get_name() { return get_string('taskcleanup', 'logstore_legacy'); } /** * Do the job. * Throw exceptions on errors (the job will be retried). */ public function execute() { global $CFG, $DB; if (empty($CFG->loglifetime)) { return; } $loglifetime = time() - ($CFG->loglifetime * 3600 * 24); // Value in days. $lifetimep = array($loglifetime); $start = time(); while ($min = $DB->get_field_select("log", "MIN(time)", "time < ?", $lifetimep)) { // Break this down into chunks to avoid transaction for too long and generally thrashing database. // Experiments suggest deleting one day takes up to a few seconds; probably a reasonable chunk size usually. // If the cleanup has just been enabled, it might take e.g a month to clean the years of logs. $params = array(min($min + 3600 * 24, $loglifetime)); $DB->delete_records_select("log", "time < ?", $params); if (time() > $start + 300) { // Do not churn on log deletion for too long each run. break; } } mtrace(" Deleted old legacy log records"); } } legacy/lang/en/logstore_legacy.php 0000644 00000004127 15151765341 0013242 0 ustar 00 <?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/>. /** * Legacy log reader lang strings. * * @package logstore_legacy * @copyright 2013 Petr Skoda {@link http://skodak.org} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['eventlegacylogged'] = 'Legacy event logged'; $string['loglegacy'] = 'Log legacy data'; $string['loglegacy_help'] = 'This plugin records log data to the legacy log table (mdl_log). This functionality has been replaced by newer, richer and more efficient logging plugins, so you should only run this plugin if you have old custom reports that directly query the old log table. Writing to the legacy logs will increase load, so it is recommended that you disable this plugin for performance reasons when it is not needed.'; $string['pluginname'] = 'Legacy log'; $string['pluginname_desc'] = 'A log plugin that stores log entries in the legacy log table.'; $string['privacy:metadata:log'] = 'A collection of past events'; $string['privacy:metadata:log:action'] = 'A description of the action'; $string['privacy:metadata:log:info'] = 'Additional information'; $string['privacy:metadata:log:ip'] = 'The IP address used at the time of the event'; $string['privacy:metadata:log:time'] = 'The time when the action took place'; $string['privacy:metadata:log:url'] = 'The URL related to the event'; $string['privacy:metadata:log:userid'] = 'The ID of the user who performed the action'; $string['taskcleanup'] = 'Legacy log table cleanup'; standard/db/upgrade.php 0000644 00000004677 15151765341 0011117 0 ustar 00 <?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/>. /** * Standard log store upgrade. * * @package logstore_standard * @copyright 2014 Petr Skoda * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); function xmldb_logstore_standard_upgrade($oldversion) { global $CFG, $DB; require_once($CFG->libdir.'/db/upgradelib.php'); // Core Upgrade-related functions. $dbman = $DB->get_manager(); // Loads ddl manager and xmldb classes. // Automatically generated Moodle v3.9.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.0.0 release upgrade line. // Put any upgrade step following this. if ($oldversion < 2022053000) { // Define index relateduserid (not unique) to be added to logstore_standard_log. $table = new xmldb_table('logstore_standard_log'); // Launch add key userid. $key = new xmldb_key('userid', XMLDB_KEY_FOREIGN, ['userid'], 'user', ['id']); $dbman->add_key($table, $key); // Launch add key courseid. $key = new xmldb_key('courseid', XMLDB_KEY_FOREIGN, ['courseid'], 'course', ['id']); $dbman->add_key($table, $key); // Launch add key realuserid. $key = new xmldb_key('realuserid', XMLDB_KEY_FOREIGN, ['realuserid'], 'user', ['id']); $dbman->add_key($table, $key); // Launch add key relateduserid. $key = new xmldb_key('relateduserid', XMLDB_KEY_FOREIGN, ['relateduserid'], 'user', ['id']); $dbman->add_key($table, $key); // Standard savepoint reached. upgrade_plugin_savepoint(true, 2022053000, 'logstore', 'standard'); } // Automatically generated Moodle v4.1.0 release upgrade line. // Put any upgrade step following this. return true; } standard/db/tasks.php 0000644 00000002244 15151765341 0010601 0 ustar 00 <?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/>. /** * Standard log reader/writer cron task. * * @package logstore_standard * @copyright 2014 Petr Skoda {@link http://skodak.org} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $tasks = array( array( 'classname' => '\logstore_standard\task\cleanup_task', 'blocking' => 0, 'minute' => 'R', 'hour' => '4', 'day' => '*', 'dayofweek' => '*', 'month' => '*' ), ); standard/db/install.xml 0000644 00000006455 15151765341 0011143 0 ustar 00 <?xml version="1.0" encoding="UTF-8" ?> <XMLDB PATH="admin/tool/log/store/standard/db" VERSION="20220616" COMMENT="XMLDB file for Moodle admin/tool/log/store/standard" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../lib/xmldb/xmldb.xsd" > <TABLES> <TABLE NAME="logstore_standard_log" COMMENT="Standard log table"> <FIELDS> <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/> <FIELD NAME="eventname" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/> <FIELD NAME="component" TYPE="char" LENGTH="100" NOTNULL="true" SEQUENCE="false"/> <FIELD NAME="action" TYPE="char" LENGTH="100" NOTNULL="true" SEQUENCE="false"/> <FIELD NAME="target" TYPE="char" LENGTH="100" NOTNULL="true" SEQUENCE="false"/> <FIELD NAME="objecttable" TYPE="char" LENGTH="50" NOTNULL="false" SEQUENCE="false"/> <FIELD NAME="objectid" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false"/> <FIELD NAME="crud" TYPE="char" LENGTH="1" NOTNULL="true" SEQUENCE="false"/> <FIELD NAME="edulevel" TYPE="int" LENGTH="1" NOTNULL="true" SEQUENCE="false"/> <FIELD NAME="contextid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/> <FIELD NAME="contextlevel" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/> <FIELD NAME="contextinstanceid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/> <FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/> <FIELD NAME="courseid" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false"/> <FIELD NAME="relateduserid" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false"/> <FIELD NAME="anonymous" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Was this event anonymous at the time of triggering?"/> <FIELD NAME="other" TYPE="text" NOTNULL="false" SEQUENCE="false"/> <FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/> <FIELD NAME="origin" TYPE="char" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="cli, cron, ws, etc."/> <FIELD NAME="ip" TYPE="char" LENGTH="45" NOTNULL="false" SEQUENCE="false" COMMENT="IP address"/> <FIELD NAME="realuserid" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="real user id when logged-in-as"/> </FIELDS> <KEYS> <KEY NAME="primary" TYPE="primary" FIELDS="id"/> <KEY NAME="contextid" TYPE="foreign" FIELDS="contextid" REFTABLE="context" REFFIELDS="id"/> <KEY NAME="userid" TYPE="foreign" FIELDS="userid" REFTABLE="user" REFFIELDS="id"/> <KEY NAME="courseid" TYPE="foreign" FIELDS="courseid" REFTABLE="course" REFFIELDS="id"/> <KEY NAME="realuserid" TYPE="foreign" FIELDS="realuserid" REFTABLE="user" REFFIELDS="id"/> <KEY NAME="relateduserid" TYPE="foreign" FIELDS="relateduserid" REFTABLE="user" REFFIELDS="id"/> </KEYS> <INDEXES> <INDEX NAME="timecreated" UNIQUE="false" FIELDS="timecreated"/> <INDEX NAME="course-time" UNIQUE="false" FIELDS="courseid, anonymous, timecreated"/> <INDEX NAME="user-module" UNIQUE="false" FIELDS="userid, contextlevel, contextinstanceid, crud, edulevel, timecreated"/> </INDEXES> </TABLE> </TABLES> </XMLDB> standard/tests/fixtures/event.php 0000644 00000003304 15151765341 0013221 0 ustar 00 <?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/>. /** * Fixtures for standard log storage testing. * * @package logstore_standard * @copyright 2014 Petr Skoda * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace logstore_standard\event; defined('MOODLE_INTERNAL') || die(); class unittest_executed extends \core\event\base { public static function get_name() { return 'xxx'; } public function get_description() { return 'yyy'; } protected function init() { $this->data['crud'] = 'u'; $this->data['edulevel'] = self::LEVEL_PARTICIPATING; } public function get_url() { return new \moodle_url('/somepath/somefile.php', array('id' => $this->data['other']['sample'])); } /** * The 'other' fields for this event do not need to mapped during backup and restore as they * only contain test values, not IDs for anything on the course. * * @return array Empty array */ public static function get_other_mapping(): array { return []; } } standard/tests/fixtures/restore_hack.php 0000644 00000002170 15151765341 0014551 0 ustar 00 <?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/>. /** * Restore controller hackery. * * @package tool_log * @copyright 2014 Petr Skoda * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); global $CFG; require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php'); class logstore_standard_restore extends restore_controller { public static function hack_executing($state) { self::$executing = $state; } } standard/tests/privacy/provider_test.php 0000644 00000053035 15151765341 0014603 0 ustar 00 <?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/>. /** * Data provider tests. * * @package logstore_standard * @category test * @copyright 2018 Frédéric Massart * @author Frédéric Massart <fred@branchup.tech> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace logstore_standard\privacy; defined('MOODLE_INTERNAL') || die(); global $CFG; use core_privacy\tests\provider_testcase; use core_privacy\local\request\contextlist; use core_privacy\local\request\approved_contextlist; use core_privacy\local\request\transform; use core_privacy\local\request\writer; use logstore_standard\privacy\provider; require_once(__DIR__ . '/../fixtures/event.php'); /** * Data provider testcase class. * * @package logstore_standard * @category test * @copyright 2018 Frédéric Massart * @author Frédéric Massart <fred@branchup.tech> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider_test extends provider_testcase { public function setUp(): void { $this->resetAfterTest(); $this->preventResetByRollback(); // Logging waits till the transaction gets committed. } public function test_get_contexts_for_userid() { $admin = \core_user::get_user(2); $u1 = $this->getDataGenerator()->create_user(); $u2 = $this->getDataGenerator()->create_user(); $u3 = $this->getDataGenerator()->create_user(); $c1 = $this->getDataGenerator()->create_course(); $cm1 = $this->getDataGenerator()->create_module('url', ['course' => $c1]); $c2 = $this->getDataGenerator()->create_course(); $cm2 = $this->getDataGenerator()->create_module('url', ['course' => $c2]); $sysctx = \context_system::instance(); $c1ctx = \context_course::instance($c1->id); $c2ctx = \context_course::instance($c2->id); $cm1ctx = \context_module::instance($cm1->cmid); $cm2ctx = \context_module::instance($cm2->cmid); $this->enable_logging(); $manager = get_log_manager(true); // User 1 is the author. $this->setUser($u1); $this->assert_contextlist_equals($this->get_contextlist_for_user($u1), []); $e = \logstore_standard\event\unittest_executed::create(['context' => $cm1ctx]); $e->trigger(); $this->assert_contextlist_equals($this->get_contextlist_for_user($u1), [$cm1ctx]); // User 2 is the related user. $this->setUser(0); $this->assert_contextlist_equals($this->get_contextlist_for_user($u2), []); $e = \logstore_standard\event\unittest_executed::create(['context' => $cm2ctx, 'relateduserid' => $u2->id]); $e->trigger(); $this->assert_contextlist_equals($this->get_contextlist_for_user($u2), [$cm2ctx]); // Admin user is the real user. $this->assert_contextlist_equals($this->get_contextlist_for_user($admin), []); $this->assert_contextlist_equals($this->get_contextlist_for_user($u3), []); $this->setAdminUser(); \core\session\manager::loginas($u3->id, $sysctx); $e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx]); $e->trigger(); $this->assert_contextlist_equals($this->get_contextlist_for_user($admin), [$sysctx, $c1ctx]); $this->assert_contextlist_equals($this->get_contextlist_for_user($u3), [$sysctx, $c1ctx]); // By admin user masquerading u1 related to u3. $this->assert_contextlist_equals($this->get_contextlist_for_user($u1), [$cm1ctx]); $this->assert_contextlist_equals($this->get_contextlist_for_user($u3), [$sysctx, $c1ctx]); $this->assert_contextlist_equals($this->get_contextlist_for_user($admin), [$sysctx, $c1ctx]); $this->setAdminUser(); \core\session\manager::loginas($u1->id, \context_system::instance()); $e = \logstore_standard\event\unittest_executed::create(['context' => $c2ctx, 'relateduserid' => $u3->id]); $e->trigger(); $this->assert_contextlist_equals($this->get_contextlist_for_user($u1), [$sysctx, $cm1ctx, $c2ctx]); $this->assert_contextlist_equals($this->get_contextlist_for_user($u3), [$sysctx, $c1ctx, $c2ctx]); $this->assert_contextlist_equals($this->get_contextlist_for_user($admin), [$sysctx, $c1ctx, $c2ctx]); } public function test_add_userids_for_context() { $admin = \core_user::get_user(2); $u1 = $this->getDataGenerator()->create_user(); $u2 = $this->getDataGenerator()->create_user(); $u3 = $this->getDataGenerator()->create_user(); $u4 = $this->getDataGenerator()->create_user(); $c1 = $this->getDataGenerator()->create_course(); $sysctx = \context_system::instance(); $c1ctx = \context_course::instance($c1->id); $this->enable_logging(); $manager = get_log_manager(true); $userlist = new \core_privacy\local\request\userlist($sysctx, 'logstore_standard_log'); $userids = $userlist->get_userids(); $this->assertEmpty($userids); provider::add_userids_for_context($userlist); $userids = $userlist->get_userids(); $this->assertEmpty($userids); // User one should be added (userid). $this->setUser($u1); $e = \logstore_standard\event\unittest_executed::create(['context' => $sysctx]); $e->trigger(); // User two (userids) and three (relateduserid) should be added. $this->setUser($u2); $e = \logstore_standard\event\unittest_executed::create(['context' => $sysctx, 'relateduserid' => $u3->id]); $e->trigger(); // The admin user should be added (realuserid). $this->setAdminUser(); \core\session\manager::loginas($u2->id, \context_system::instance()); $e = \logstore_standard\event\unittest_executed::create(['context' => $sysctx]); $e->trigger(); // Set off an event in a different context. User 4 should not be returned below. $this->setUser($u4); $e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx]); $e->trigger(); provider::add_userids_for_context($userlist); $userids = $userlist->get_userids(); $this->assertCount(4, $userids); $expectedresult = [$admin->id, $u1->id, $u2->id, $u3->id]; $this->assertEmpty(array_diff($expectedresult, $userids)); } public function test_delete_data_for_user() { global $DB; $u1 = $this->getDataGenerator()->create_user(); $u2 = $this->getDataGenerator()->create_user(); $c1 = $this->getDataGenerator()->create_course(); $c2 = $this->getDataGenerator()->create_course(); $sysctx = \context_system::instance(); $c1ctx = \context_course::instance($c1->id); $c2ctx = \context_course::instance($c2->id); $this->enable_logging(); $manager = get_log_manager(true); // User 1 is the author. $this->setUser($u1); $e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx]); $e->trigger(); $e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx]); $e->trigger(); $e = \logstore_standard\event\unittest_executed::create(['context' => $c2ctx]); $e->trigger(); // User 2 is the author. $this->setUser($u2); $e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx]); $e->trigger(); $e = \logstore_standard\event\unittest_executed::create(['context' => $c2ctx]); $e->trigger(); // Confirm data present. $this->assertTrue($DB->record_exists('logstore_standard_log', ['userid' => $u1->id, 'contextid' => $c1ctx->id])); $this->assertEquals(3, $DB->count_records('logstore_standard_log', ['userid' => $u1->id])); $this->assertEquals(2, $DB->count_records('logstore_standard_log', ['userid' => $u2->id])); // Delete all the things! provider::delete_data_for_user(new approved_contextlist($u1, 'logstore_standard', [$c1ctx->id])); $this->assertFalse($DB->record_exists('logstore_standard_log', ['userid' => $u1->id, 'contextid' => $c1ctx->id])); $this->assertEquals(1, $DB->count_records('logstore_standard_log', ['userid' => $u1->id])); $this->assertEquals(2, $DB->count_records('logstore_standard_log', ['userid' => $u2->id])); } public function test_delete_data_for_all_users_in_context() { global $DB; $u1 = $this->getDataGenerator()->create_user(); $u2 = $this->getDataGenerator()->create_user(); $c1 = $this->getDataGenerator()->create_course(); $c2 = $this->getDataGenerator()->create_course(); $sysctx = \context_system::instance(); $c1ctx = \context_course::instance($c1->id); $c2ctx = \context_course::instance($c2->id); $this->enable_logging(); $manager = get_log_manager(true); // User 1 is the author. $this->setUser($u1); $e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx]); $e->trigger(); $e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx]); $e->trigger(); $e = \logstore_standard\event\unittest_executed::create(['context' => $c2ctx]); $e->trigger(); // User 2 is the author. $this->setUser($u2); $e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx]); $e->trigger(); $e = \logstore_standard\event\unittest_executed::create(['context' => $c2ctx]); $e->trigger(); // Confirm data present. $this->assertTrue($DB->record_exists('logstore_standard_log', ['contextid' => $c1ctx->id])); $this->assertEquals(3, $DB->count_records('logstore_standard_log', ['userid' => $u1->id])); $this->assertEquals(2, $DB->count_records('logstore_standard_log', ['userid' => $u2->id])); // Delete all the things! provider::delete_data_for_all_users_in_context($c1ctx); $this->assertFalse($DB->record_exists('logstore_standard_log', ['contextid' => $c1ctx->id])); $this->assertEquals(1, $DB->count_records('logstore_standard_log', ['userid' => $u1->id])); $this->assertEquals(1, $DB->count_records('logstore_standard_log', ['userid' => $u2->id])); } public function test_delete_data_for_userlist() { global $DB; $u1 = $this->getDataGenerator()->create_user(); $u2 = $this->getDataGenerator()->create_user(); $u3 = $this->getDataGenerator()->create_user(); $u4 = $this->getDataGenerator()->create_user(); $course = $this->getDataGenerator()->create_course(); $sysctx = \context_system::instance(); $c1ctx = \context_course::instance($course->id); $this->enable_logging(); $manager = get_log_manager(true); $this->setUser($u1); $e = \logstore_standard\event\unittest_executed::create(['context' => $sysctx]); $e->trigger(); $this->setUser($u2); $e = \logstore_standard\event\unittest_executed::create(['context' => $sysctx]); $e->trigger(); $this->setUser($u3); $e = \logstore_standard\event\unittest_executed::create(['context' => $sysctx]); $e->trigger(); $this->setUser($u4); $e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx]); $e->trigger(); // Check that four records were created. $this->assertEquals(4, $DB->count_records('logstore_standard_log')); $userlist = new \core_privacy\local\request\approved_userlist($sysctx, 'logstore_standard_log', [$u1->id, $u3->id]); provider::delete_data_for_userlist($userlist); // We should have a record for u2 and u4. $this->assertEquals(2, $DB->count_records('logstore_standard_log')); $records = $DB->get_records('logstore_standard_log', ['contextid' => $sysctx->id]); $this->assertCount(1, $records); $currentrecord = array_shift($records); $this->assertEquals($u2->id, $currentrecord->userid); } public function test_export_data_for_user() { $admin = \core_user::get_user(2); $u1 = $this->getDataGenerator()->create_user(); $u2 = $this->getDataGenerator()->create_user(); $u3 = $this->getDataGenerator()->create_user(); $u4 = $this->getDataGenerator()->create_user(); $c1 = $this->getDataGenerator()->create_course(); $cm1 = $this->getDataGenerator()->create_module('url', ['course' => $c1]); $c2 = $this->getDataGenerator()->create_course(); $cm2 = $this->getDataGenerator()->create_module('url', ['course' => $c2]); $sysctx = \context_system::instance(); $c1ctx = \context_course::instance($c1->id); $c2ctx = \context_course::instance($c2->id); $cm1ctx = \context_module::instance($cm1->cmid); $cm2ctx = \context_module::instance($cm2->cmid); $path = [get_string('privacy:path:logs', 'tool_log'), get_string('pluginname', 'logstore_standard')]; $this->enable_logging(); $manager = get_log_manager(true); // User 1 is the author. $this->setUser($u1); $e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx, 'other' => ['i' => 0]]); $e->trigger(); // User 2 is related. $this->setUser(0); $e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx, 'relateduserid' => $u2->id, 'other' => ['i' => 1]]); $e->trigger(); // Admin user masquerades u3, which is related to u4. $this->setAdminUser(); \core\session\manager::loginas($u3->id, $sysctx); $e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx, 'relateduserid' => $u4->id, 'other' => ['i' => 2]]); $e->trigger(); // Confirm data present for u1. provider::export_user_data(new approved_contextlist($u1, 'logstore_standard', [$c2ctx->id, $c1ctx->id])); $data = writer::with_context($c2ctx)->get_data($path); $this->assertEmpty($data); $data = writer::with_context($c1ctx)->get_data($path); $this->assertCount(1, $data->logs); $this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_you']); $this->assertSame(0, $data->logs[0]['other']['i']); // Confirm data present for u2. writer::reset(); provider::export_user_data(new approved_contextlist($u2, 'logstore_standard', [$c2ctx->id, $c1ctx->id])); $data = writer::with_context($c2ctx)->get_data($path); $this->assertEmpty($data); $data = writer::with_context($c1ctx)->get_data($path); $this->assertCount(1, $data->logs); $this->assertEquals(transform::yesno(false), $data->logs[0]['author_of_the_action_was_you']); $this->assertEquals(transform::yesno(true), $data->logs[0]['related_user_was_you']); $this->assertSame(1, $data->logs[0]['other']['i']); // Confirm data present for u3. writer::reset(); provider::export_user_data(new approved_contextlist($u3, 'logstore_standard', [$c2ctx->id, $c1ctx->id])); $data = writer::with_context($c2ctx)->get_data($path); $this->assertEmpty($data); $data = writer::with_context($c1ctx)->get_data($path); $this->assertCount(1, $data->logs); $this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_you']); $this->assertEquals(transform::yesno(false), $data->logs[0]['related_user_was_you']); $this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_masqueraded']); $this->assertEquals(transform::yesno(false), $data->logs[0]['masquerading_user_was_you']); $this->assertSame(2, $data->logs[0]['other']['i']); // Confirm data present for u4. writer::reset(); provider::export_user_data(new approved_contextlist($u4, 'logstore_standard', [$c2ctx->id, $c1ctx->id])); $data = writer::with_context($c2ctx)->get_data($path); $this->assertEmpty($data); $data = writer::with_context($c1ctx)->get_data($path); $this->assertCount(1, $data->logs); $this->assertEquals(transform::yesno(false), $data->logs[0]['author_of_the_action_was_you']); $this->assertEquals(transform::yesno(true), $data->logs[0]['related_user_was_you']); $this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_masqueraded']); $this->assertEquals(transform::yesno(false), $data->logs[0]['masquerading_user_was_you']); $this->assertSame(2, $data->logs[0]['other']['i']); // Add anonymous events. $this->setUser($u1); $e = \logstore_standard\event\unittest_executed::create(['context' => $c2ctx, 'relateduserid' => $u2->id, 'anonymous' => true]); $e->trigger(); $this->setAdminUser(); \core\session\manager::loginas($u3->id, $sysctx); $e = \logstore_standard\event\unittest_executed::create(['context' => $c2ctx, 'relateduserid' => $u4->id, 'anonymous' => true]); $e->trigger(); // Confirm data present for u1. provider::export_user_data(new approved_contextlist($u1, 'logstore_standard', [$c2ctx->id])); $data = writer::with_context($c2ctx)->get_data($path); $this->assertCount(1, $data->logs); $this->assertEquals(transform::yesno(true), $data->logs[0]['action_was_done_anonymously']); $this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_you']); // Confirm data present for u2. writer::reset(); provider::export_user_data(new approved_contextlist($u2, 'logstore_standard', [$c2ctx->id])); $data = writer::with_context($c2ctx)->get_data($path); $this->assertCount(1, $data->logs); $this->assertEquals(transform::yesno(true), $data->logs[0]['action_was_done_anonymously']); $this->assertArrayNotHasKey('author_of_the_action_was_you', $data->logs[0]); $this->assertArrayNotHasKey('authorid', $data->logs[0]); $this->assertEquals(transform::yesno(true), $data->logs[0]['related_user_was_you']); // Confirm data present for u3. writer::reset(); provider::export_user_data(new approved_contextlist($u3, 'logstore_standard', [$c2ctx->id])); $data = writer::with_context($c2ctx)->get_data($path); $this->assertCount(1, $data->logs); $this->assertEquals(transform::yesno(true), $data->logs[0]['action_was_done_anonymously']); $this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_you']); $this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_masqueraded']); $this->assertArrayNotHasKey('masquerading_user_was_you', $data->logs[0]); $this->assertArrayNotHasKey('masqueradinguserid', $data->logs[0]); // Confirm data present for u4. writer::reset(); provider::export_user_data(new approved_contextlist($u4, 'logstore_standard', [$c2ctx->id])); $data = writer::with_context($c2ctx)->get_data($path); $this->assertCount(1, $data->logs); $this->assertEquals(transform::yesno(true), $data->logs[0]['action_was_done_anonymously']); $this->assertArrayNotHasKey('author_of_the_action_was_you', $data->logs[0]); $this->assertArrayNotHasKey('authorid', $data->logs[0]); $this->assertEquals(transform::yesno(true), $data->logs[0]['related_user_was_you']); $this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_masqueraded']); $this->assertArrayNotHasKey('masquerading_user_was_you', $data->logs[0]); $this->assertArrayNotHasKey('masqueradinguserid', $data->logs[0]); } /** * Assert the content of a context list. * * @param contextlist $contextlist The collection. * @param array $expected List of expected contexts or IDs. * @return void */ protected function assert_contextlist_equals($contextlist, array $expected) { $expectedids = array_map(function($context) { if (is_object($context)) { return $context->id; } return $context; }, $expected); $contextids = array_map('intval', $contextlist->get_contextids()); sort($contextids); sort($expectedids); $this->assertEquals($expectedids, $contextids); } /** * Enable logging. * * @return void */ protected function enable_logging() { set_config('enabled_stores', 'logstore_standard', 'tool_log'); set_config('buffersize', 0, 'logstore_standard'); set_config('logguests', 1, 'logstore_standard'); } /** * Get the contextlist for a user. * * @param object $user The user. * @return contextlist */ protected function get_contextlist_for_user($user) { $contextlist = new contextlist(); provider::add_contexts_for_userid($contextlist, $user->id); return $contextlist; } } standard/tests/store_test.php 0000644 00000054512 15151765341 0012431 0 ustar 00 <?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/>. namespace logstore_standard; defined('MOODLE_INTERNAL') || die(); require_once(__DIR__ . '/fixtures/event.php'); require_once(__DIR__ . '/fixtures/restore_hack.php'); /** * Standard log store tests. * * @package logstore_standard * @copyright 2014 Petr Skoda {@link http://skodak.org/} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class store_test extends \advanced_testcase { /** * @var bool Determine if we disabled the GC, so it can be re-enabled in tearDown. */ private $wedisabledgc = false; /** * Tests log writing. * * @param bool $jsonformat True to test with JSON format * @dataProvider log_writing_provider */ public function test_log_writing(bool $jsonformat) { global $DB; $this->resetAfterTest(); $this->preventResetByRollback(); // Logging waits till the transaction gets committed. // Apply JSON format system setting. set_config('jsonformat', $jsonformat ? 1 : 0, 'logstore_standard'); $this->setAdminUser(); $user1 = $this->getDataGenerator()->create_user(); $user2 = $this->getDataGenerator()->create_user(); $course1 = $this->getDataGenerator()->create_course(); $module1 = $this->getDataGenerator()->create_module('resource', array('course' => $course1)); $course2 = $this->getDataGenerator()->create_course(); $module2 = $this->getDataGenerator()->create_module('resource', array('course' => $course2)); // Test all plugins are disabled by this command. set_config('enabled_stores', '', 'tool_log'); $manager = get_log_manager(true); $stores = $manager->get_readers(); $this->assertCount(0, $stores); // Enable logging plugin. set_config('enabled_stores', 'logstore_standard', 'tool_log'); set_config('buffersize', 0, 'logstore_standard'); set_config('logguests', 1, 'logstore_standard'); $manager = get_log_manager(true); $stores = $manager->get_readers(); $this->assertCount(1, $stores); $this->assertEquals(array('logstore_standard'), array_keys($stores)); /** @var \logstore_standard\log\store $store */ $store = $stores['logstore_standard']; $this->assertInstanceOf('logstore_standard\log\store', $store); $this->assertInstanceOf('tool_log\log\writer', $store); $this->assertTrue($store->is_logging()); $logs = $DB->get_records('logstore_standard_log', array(), 'id ASC'); $this->assertCount(0, $logs); $exists = $store->get_events_select_exists('', array(), 'timecreated ASC', 0, 0); $this->assertFalse($exists); $this->setCurrentTimeStart(); $this->setUser(0); $event1 = \logstore_standard\event\unittest_executed::create( array('context' => \context_module::instance($module1->cmid), 'other' => array('sample' => 5, 'xx' => 10))); $event1->trigger(); $logs = $DB->get_records('logstore_standard_log', array(), 'id ASC'); $this->assertCount(1, $logs); $log1 = reset($logs); unset($log1->id); if ($jsonformat) { $log1->other = json_decode($log1->other, true); } else { $log1->other = unserialize($log1->other); } $log1 = (array)$log1; $data = $event1->get_data(); $data['origin'] = 'cli'; $data['ip'] = null; $data['realuserid'] = null; $this->assertEquals($data, $log1); $this->setAdminUser(); \core\session\manager::loginas($user1->id, \context_system::instance()); $this->assertEquals(2, $DB->count_records('logstore_standard_log')); \logstore_standard_restore::hack_executing(1); $event2 = \logstore_standard\event\unittest_executed::create( array('context' => \context_module::instance($module2->cmid), 'other' => array('sample' => 6, 'xx' => 9))); $event2->trigger(); \logstore_standard_restore::hack_executing(0); \core\session\manager::init_empty_session(); $this->assertFalse(\core\session\manager::is_loggedinas()); $logs = $DB->get_records('logstore_standard_log', array(), 'id ASC'); $this->assertCount(3, $logs); array_shift($logs); $log2 = array_shift($logs); $this->assertSame('\core\event\user_loggedinas', $log2->eventname); $this->assertSame('cli', $log2->origin); $log3 = array_shift($logs); unset($log3->id); if ($jsonformat) { $log3->other = json_decode($log3->other, true); } else { $log3->other = unserialize($log3->other); } $log3 = (array)$log3; $data = $event2->get_data(); $data['origin'] = 'restore'; $data['ip'] = null; $data['realuserid'] = 2; $this->assertEquals($data, $log3); // Test table exists. $tablename = $store->get_internal_log_table_name(); $this->assertTrue($DB->get_manager()->table_exists($tablename)); // Test reading. $this->assertSame(3, $store->get_events_select_count('', array())); $events = $store->get_events_select('', array(), 'timecreated ASC', 0, 0); // Is actually sorted by "timecreated ASC, id ASC". $this->assertCount(3, $events); $exists = $store->get_events_select_exists('', array(), 'timecreated ASC', 0, 0); $this->assertTrue($exists); $resev1 = array_shift($events); array_shift($events); $resev2 = array_shift($events); $this->assertEquals($event1->get_data(), $resev1->get_data()); $this->assertEquals($event2->get_data(), $resev2->get_data()); // Test buffering. set_config('buffersize', 3, 'logstore_standard'); $manager = get_log_manager(true); $stores = $manager->get_readers(); /** @var \logstore_standard\log\store $store */ $store = $stores['logstore_standard']; $DB->delete_records('logstore_standard_log'); \logstore_standard\event\unittest_executed::create( array('context' => \context_module::instance($module1->cmid), 'other' => array('sample' => 5, 'xx' => 10)))->trigger(); $this->assertEquals(0, $DB->count_records('logstore_standard_log')); \logstore_standard\event\unittest_executed::create( array('context' => \context_module::instance($module1->cmid), 'other' => array('sample' => 5, 'xx' => 10)))->trigger(); $this->assertEquals(0, $DB->count_records('logstore_standard_log')); $store->flush(); $this->assertEquals(2, $DB->count_records('logstore_standard_log')); \logstore_standard\event\unittest_executed::create( array('context' => \context_module::instance($module1->cmid), 'other' => array('sample' => 5, 'xx' => 10)))->trigger(); $this->assertEquals(2, $DB->count_records('logstore_standard_log')); \logstore_standard\event\unittest_executed::create( array('context' => \context_module::instance($module1->cmid), 'other' => array('sample' => 5, 'xx' => 10)))->trigger(); $this->assertEquals(2, $DB->count_records('logstore_standard_log')); \logstore_standard\event\unittest_executed::create( array('context' => \context_module::instance($module1->cmid), 'other' => array('sample' => 5, 'xx' => 10)))->trigger(); $this->assertEquals(5, $DB->count_records('logstore_standard_log')); \logstore_standard\event\unittest_executed::create( array('context' => \context_module::instance($module1->cmid), 'other' => array('sample' => 5, 'xx' => 10)))->trigger(); $this->assertEquals(5, $DB->count_records('logstore_standard_log')); \logstore_standard\event\unittest_executed::create( array('context' => \context_module::instance($module1->cmid), 'other' => array('sample' => 5, 'xx' => 10)))->trigger(); $this->assertEquals(5, $DB->count_records('logstore_standard_log')); \logstore_standard\event\unittest_executed::create( array('context' => \context_module::instance($module1->cmid), 'other' => array('sample' => 5, 'xx' => 10)))->trigger(); $this->assertEquals(8, $DB->count_records('logstore_standard_log')); // Test guest logging setting. set_config('logguests', 0, 'logstore_standard'); set_config('buffersize', 0, 'logstore_standard'); get_log_manager(true); $DB->delete_records('logstore_standard_log'); get_log_manager(true); $this->setUser(null); \logstore_standard\event\unittest_executed::create( array('context' => \context_module::instance($module1->cmid), 'other' => array('sample' => 5, 'xx' => 10)))->trigger(); $this->assertEquals(0, $DB->count_records('logstore_standard_log')); $this->setGuestUser(); \logstore_standard\event\unittest_executed::create( array('context' => \context_module::instance($module1->cmid), 'other' => array('sample' => 5, 'xx' => 10)))->trigger(); $this->assertEquals(0, $DB->count_records('logstore_standard_log')); $this->setUser($user1); \logstore_standard\event\unittest_executed::create( array('context' => \context_module::instance($module1->cmid), 'other' => array('sample' => 5, 'xx' => 10)))->trigger(); $this->assertEquals(1, $DB->count_records('logstore_standard_log')); $this->setUser($user2); \logstore_standard\event\unittest_executed::create( array('context' => \context_module::instance($module1->cmid), 'other' => array('sample' => 5, 'xx' => 10)))->trigger(); $this->assertEquals(2, $DB->count_records('logstore_standard_log')); set_config('enabled_stores', '', 'tool_log'); get_log_manager(true); } /** * Returns different JSON format settings so the test can be run with JSON format either on or * off. * * @return bool[] Array of true/false */ public static function log_writing_provider(): array { return [ [false], [true] ]; } /** * Test logmanager::get_supported_reports returns all reports that require this store. */ public function test_get_supported_reports() { $logmanager = get_log_manager(); $allreports = \core_component::get_plugin_list('report'); // Make sure all supported reports are installed. $expectedreports = array_intersect_key([ 'log' => 'report_log', 'loglive' => 'report_loglive', 'outline' => 'report_outline', 'participation' => 'report_participation', 'stats' => 'report_stats' ], $allreports); $supportedreports = $logmanager->get_supported_reports('logstore_standard'); foreach ($expectedreports as $expectedreport) { $this->assertArrayHasKey($expectedreport, $supportedreports); } } /** * Verify that gc disabling works */ public function test_gc_enabled_as_expected() { if (!gc_enabled()) { $this->markTestSkipped('Garbage collector (gc) is globally disabled.'); } $this->disable_gc(); $this->assertTrue($this->wedisabledgc); $this->assertFalse(gc_enabled()); } /** * Test sql_reader::get_events_select_iterator. * @return void */ public function test_events_traversable() { global $DB; $this->disable_gc(); $this->resetAfterTest(); $this->preventResetByRollback(); $this->setAdminUser(); set_config('enabled_stores', 'logstore_standard', 'tool_log'); $manager = get_log_manager(true); $stores = $manager->get_readers(); $store = $stores['logstore_standard']; $events = $store->get_events_select_iterator('', array(), '', 0, 0); $this->assertFalse($events->valid()); // Here it should be already closed, but we should be allowed to // over-close it without exception. $events->close(); $user = $this->getDataGenerator()->create_user(); for ($i = 0; $i < 1000; $i++) { \core\event\user_created::create_from_userid($user->id)->trigger(); } $store->flush(); // Check some various sizes get the right number of elements. $this->assertEquals(1, iterator_count($store->get_events_select_iterator('', array(), '', 0, 1))); $this->assertEquals(2, iterator_count($store->get_events_select_iterator('', array(), '', 0, 2))); $iterator = $store->get_events_select_iterator('', array(), '', 0, 500); $this->assertInstanceOf('\core\event\base', $iterator->current()); $this->assertEquals(500, iterator_count($iterator)); $iterator->close(); // Look for non-linear memory usage for the iterator version. $mem = memory_get_usage(); $events = $store->get_events_select('', array(), '', 0, 0); $arraymemusage = memory_get_usage() - $mem; $mem = memory_get_usage(); $eventsit = $store->get_events_select_iterator('', array(), '', 0, 0); $eventsit->close(); $itmemusage = memory_get_usage() - $mem; $this->assertInstanceOf('\Traversable', $eventsit); $this->assertLessThan($arraymemusage / 10, $itmemusage); set_config('enabled_stores', '', 'tool_log'); get_log_manager(true); } /** * Test that the standard log cleanup works correctly. */ public function test_cleanup_task() { global $DB; $this->resetAfterTest(); // Create some records spread over various days; test multiple iterations in cleanup. $ctx = \context_course::instance(1); $record = (object) array( 'edulevel' => 0, 'contextid' => $ctx->id, 'contextlevel' => $ctx->contextlevel, 'contextinstanceid' => $ctx->instanceid, 'userid' => 1, 'timecreated' => time(), ); $DB->insert_record('logstore_standard_log', $record); $record->timecreated -= 3600 * 24 * 30; $DB->insert_record('logstore_standard_log', $record); $record->timecreated -= 3600 * 24 * 30; $DB->insert_record('logstore_standard_log', $record); $record->timecreated -= 3600 * 24 * 30; $DB->insert_record('logstore_standard_log', $record); $this->assertEquals(4, $DB->count_records('logstore_standard_log')); // Remove all logs before "today". set_config('loglifetime', 1, 'logstore_standard'); $this->expectOutputString(" Deleted old log records from standard store.\n"); $clean = new \logstore_standard\task\cleanup_task(); $clean->execute(); $this->assertEquals(1, $DB->count_records('logstore_standard_log')); } /** * Tests the decode_other function can cope with both JSON and PHP serialized format. * * @param mixed $value Value to encode and decode * @dataProvider decode_other_provider */ public function test_decode_other($value) { $this->assertEquals($value, \logstore_standard\log\store::decode_other(serialize($value))); $this->assertEquals($value, \logstore_standard\log\store::decode_other(json_encode($value))); } public function test_decode_other_with_wrongly_encoded_contents() { $this->assertSame(null, \logstore_standard\log\store::decode_other(null)); } /** * List of possible values for 'other' field. * * I took these types from our logs based on the different first character of PHP serialized * data - my query found only these types. The normal case is an array. * * @return array Array of parameters */ public function decode_other_provider(): array { return [ [['info' => 'd2819896', 'logurl' => 'discuss.php?d=2819896']], [null], ['just a string'], [32768] ]; } /** * Checks that backup and restore of log data works correctly. * * @param bool $jsonformat True to test with JSON format * @dataProvider log_writing_provider */ public function test_backup_restore(bool $jsonformat) { global $DB; $this->resetAfterTest(); $this->preventResetByRollback(); // Enable logging plugin. set_config('enabled_stores', 'logstore_standard', 'tool_log'); set_config('buffersize', 0, 'logstore_standard'); $manager = get_log_manager(true); // User must be enrolled in course. $generator = $this->getDataGenerator(); $course = $generator->create_course(); $user = $generator->create_user(); $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student'); $this->setUser($user); // Apply JSON format system setting. set_config('jsonformat', $jsonformat ? 1 : 0, 'logstore_standard'); // Create some log data in a course - one with other data, one without. \logstore_standard\event\unittest_executed::create([ 'context' => \context_course::instance($course->id), 'other' => ['sample' => 5, 'xx' => 10]])->trigger(); $this->waitForSecond(); \logstore_standard\event\unittest_executed::create([ 'context' => \context_course::instance($course->id)])->trigger(); $records = array_values($DB->get_records('logstore_standard_log', ['courseid' => $course->id, 'target' => 'unittest'], 'timecreated')); $this->assertCount(2, $records); // Work out expected 'other' values based on JSON format. $expected0 = [ false => 'a:2:{s:6:"sample";i:5;s:2:"xx";i:10;}', true => '{"sample":5,"xx":10}' ]; $expected1 = [ false => 'N;', true => 'null' ]; // Backup the course twice, including log data. $this->setAdminUser(); $backupid1 = $this->backup($course); $backupid2 = $this->backup($course); // Restore it with same jsonformat. $newcourseid = $this->restore($backupid1, $course, '_A'); // Check entries are correctly encoded. $records = array_values($DB->get_records('logstore_standard_log', ['courseid' => $newcourseid, 'target' => 'unittest'], 'timecreated')); $this->assertCount(2, $records); $this->assertEquals($expected0[$jsonformat], $records[0]->other); $this->assertEquals($expected1[$jsonformat], $records[1]->other); // Change JSON format to opposite value and restore again. set_config('jsonformat', $jsonformat ? 0 : 1, 'logstore_standard'); $newcourseid = $this->restore($backupid2, $course, '_B'); // Check entries are correctly encoded in other format. $records = array_values($DB->get_records('logstore_standard_log', ['courseid' => $newcourseid, 'target' => 'unittest'], 'timecreated')); $this->assertEquals($expected0[!$jsonformat], $records[0]->other); $this->assertEquals($expected1[!$jsonformat], $records[1]->other); } /** * Backs a course up to temp directory. * * @param \stdClass $course Course object to backup * @return string ID of backup */ protected function backup($course): string { global $USER, $CFG; require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php'); // Turn off file logging, otherwise it can't delete the file (Windows). $CFG->backup_file_logger_level = \backup::LOG_NONE; // Do backup with default settings. MODE_IMPORT means it will just // create the directory and not zip it. $bc = new \backup_controller(\backup::TYPE_1COURSE, $course->id, \backup::FORMAT_MOODLE, \backup::INTERACTIVE_NO, \backup::MODE_IMPORT, $USER->id); $bc->get_plan()->get_setting('users')->set_status(\backup_setting::NOT_LOCKED); $bc->get_plan()->get_setting('users')->set_value(true); $bc->get_plan()->get_setting('logs')->set_value(true); $backupid = $bc->get_backupid(); $bc->execute_plan(); $bc->destroy(); return $backupid; } /** * Restores a course from temp directory. * * @param string $backupid Backup id * @param \stdClass $course Original course object * @param string $suffix Suffix to add after original course shortname and fullname * @return int New course id * @throws \restore_controller_exception */ protected function restore(string $backupid, $course, string $suffix): int { global $USER, $CFG; require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php'); // Do restore to new course with default settings. $newcourseid = \restore_dbops::create_new_course( $course->fullname . $suffix, $course->shortname . $suffix, $course->category); $rc = new \restore_controller($backupid, $newcourseid, \backup::INTERACTIVE_NO, \backup::MODE_GENERAL, $USER->id, \backup::TARGET_NEW_COURSE); $rc->get_plan()->get_setting('logs')->set_value(true); $rc->get_plan()->get_setting('users')->set_value(true); $this->assertTrue($rc->execute_precheck()); $rc->execute_plan(); $rc->destroy(); return $newcourseid; } /** * Disable the garbage collector if it's enabled to ensure we don't adjust memory statistics. */ private function disable_gc() { if (gc_enabled()) { $this->wedisabledgc = true; gc_disable(); } } /** * Reset any garbage collector changes to the previous state at the end of the test. */ public function tearDown(): void { if ($this->wedisabledgc) { gc_enable(); } $this->wedisabledgc = false; } } standard/backup/moodle2/backup_logstore_standard_subplugin.class.php 0000644 00000004254 15151765341 0022137 0 ustar 00 <?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/>. /** * Backup implementation for the (tool_log) logstore_standard subplugin. * * @package logstore_standard * @category backup * @copyright 2015 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); class backup_logstore_standard_subplugin extends backup_tool_log_logstore_subplugin { /** * Returns the subplugin structure to attach to the 'logstore' XML element. * * @return backup_subplugin_element the subplugin structure to be attached. */ protected function define_logstore_subplugin_structure() { $subplugin = $this->get_subplugin_element(); $subpluginwrapper = new backup_nested_element($this->get_recommended_name()); // Create the custom (base64 encoded, xml safe) 'other' final element. $otherelement = new base64_encode_final_element('other'); $subpluginlog = new backup_nested_element('logstore_standard_log', array('id'), array( 'eventname', 'component', 'action', 'target', 'objecttable', 'objectid', 'crud', 'edulevel', 'contextid', 'userid', 'relateduserid', 'anonymous', $otherelement, 'timecreated', 'ip', 'realuserid')); $subplugin->add_child($subpluginwrapper); $subpluginwrapper->add_child($subpluginlog); $subpluginlog->set_source_table('logstore_standard_log', array('contextid' => backup::VAR_CONTEXTID)); return $subplugin; } } standard/backup/moodle2/restore_logstore_standard_subplugin.class.php 0000644 00000004544 15151765341 0022357 0 ustar 00 <?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/>. /** * Restore implementation for the (tool_log) logstore_standard subplugin. * * @package logstore_standard * @category backup * @copyright 2015 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); class restore_logstore_standard_subplugin extends restore_tool_log_logstore_subplugin { /** * Returns the subplugin structure to attach to the 'logstore' XML element. * * @return restore_path_element[] array of elements to be processed on restore. */ protected function define_logstore_subplugin_structure() { // If the logstore is not enabled we don't add structures for it. $enabledlogstores = explode(',', get_config('tool_log', 'enabled_stores')); if (!in_array('logstore_standard', $enabledlogstores)) { return array(); // The logstore is not enabled, nothing to restore. } $paths = array(); $elename = $this->get_namefor('log'); $elepath = $this->get_pathfor('/logstore_standard_log'); $paths[] = new restore_path_element($elename, $elepath); return $paths; } /** * Process logstore_standard_log entries. * * This method proceeds to read, complete, remap and, finally, * discard or save every log entry. * * @param array() $data log entry. */ public function process_logstore_standard_log($data) { global $DB; $data = $this->process_log($data, get_config('logstore_standard', 'jsonformat')); if ($data) { $DB->insert_record('logstore_standard_log', $data); } } } standard/settings.php 0000644 00000004552 15151765342 0010734 0 ustar 00 <?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/>. /** * Standard log store settings. * * @package logstore_standard * @copyright 2013 Petr Skoda {@link http://skodak.org} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); if ($hassiteconfig) { $settings->add(new admin_setting_configcheckbox('logstore_standard/logguests', new lang_string('logguests', 'core_admin'), new lang_string('logguests_help', 'core_admin'), 1)); $settings->add(new admin_setting_configcheckbox('logstore_standard/jsonformat', new lang_string('jsonformat', 'logstore_standard'), new lang_string('jsonformat_desc', 'logstore_standard'), 1)); $options = array( 0 => new lang_string('neverdeletelogs'), 1000 => new lang_string('numdays', '', 1000), 365 => new lang_string('numdays', '', 365), 180 => new lang_string('numdays', '', 180), 150 => new lang_string('numdays', '', 150), 120 => new lang_string('numdays', '', 120), 90 => new lang_string('numdays', '', 90), 60 => new lang_string('numdays', '', 60), 35 => new lang_string('numdays', '', 35), 10 => new lang_string('numdays', '', 10), 5 => new lang_string('numdays', '', 5), 2 => new lang_string('numdays', '', 2)); $settings->add(new admin_setting_configselect('logstore_standard/loglifetime', new lang_string('loglifetime', 'core_admin'), new lang_string('configloglifetime', 'core_admin'), 0, $options)); $settings->add(new admin_setting_configtext('logstore_standard/buffersize', get_string('buffersize', 'logstore_standard'), '', '50', PARAM_INT)); } standard/version.php 0000644 00000002214 15151765342 0010552 0 ustar 00 <?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/>. /** * Standard log store. * * @package logstore_standard * @copyright 2013 Petr Skoda {@link http://skodak.org} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2022112800; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2022111800; // Requires this Moodle version. $plugin->component = 'logstore_standard'; // Full name of the plugin (used for diagnostics). standard/classes/privacy/provider.php 0000644 00000010624 15151765342 0014035 0 ustar 00 <?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/>. /** * Data provider. * * @package logstore_standard * @copyright 2018 Frédéric Massart * @author Frédéric Massart <fred@branchup.tech> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace logstore_standard\privacy; defined('MOODLE_INTERNAL') || die(); use context; use core_privacy\local\metadata\collection; use core_privacy\local\request\contextlist; /** * Data provider class. * * @package logstore_standard * @copyright 2018 Frédéric Massart * @author Frédéric Massart <fred@branchup.tech> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\provider, \tool_log\local\privacy\logstore_provider, \tool_log\local\privacy\logstore_userlist_provider { use \tool_log\local\privacy\moodle_database_export_and_delete; /** * Returns metadata. * * @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('logstore_standard_log', [ 'eventname' => 'privacy:metadata:log:eventname', 'userid' => 'privacy:metadata:log:userid', 'relateduserid' => 'privacy:metadata:log:relateduserid', 'anonymous' => 'privacy:metadata:log:anonymous', 'other' => 'privacy:metadata:log:other', 'timecreated' => 'privacy:metadata:log:timecreated', 'origin' => 'privacy:metadata:log:origin', 'ip' => 'privacy:metadata:log:ip', 'realuserid' => 'privacy:metadata:log:realuserid', ], 'privacy:metadata:log'); return $collection; } /** * Add contexts that contain user information for the specified user. * * @param contextlist $contextlist The contextlist to add the contexts to. * @param int $userid The user to find the contexts for. * @return void */ public static function add_contexts_for_userid(contextlist $contextlist, $userid) { $sql = " SELECT l.contextid FROM {logstore_standard_log} l WHERE l.userid = :userid1 OR l.relateduserid = :userid2 OR l.realuserid = :userid3"; $contextlist->add_from_sql($sql, [ 'userid1' => $userid, 'userid2' => $userid, 'userid3' => $userid, ]); } /** * Add user IDs that contain user information for the specified context. * * @param \core_privacy\local\request\userlist $userlist The userlist to add the users to. * @return void */ public static function add_userids_for_context(\core_privacy\local\request\userlist $userlist) { $params = ['contextid' => $userlist->get_context()->id]; $sql = "SELECT userid, relateduserid, realuserid FROM {logstore_standard_log} WHERE contextid = :contextid"; $userlist->add_from_sql('userid', $sql, $params); $userlist->add_from_sql('relateduserid', $sql, $params); $userlist->add_from_sql('realuserid', $sql, $params); } /** * Get the database object. * * @return array Containing moodle_database, string, or null values. */ protected static function get_database_and_table() { global $DB; return [$DB, 'logstore_standard_log']; } /** * Get the path to export the logs to. * * @return array */ protected static function get_export_subcontext() { return [get_string('privacy:path:logs', 'tool_log'), get_string('pluginname', 'logstore_standard')]; } } standard/classes/log/store.php 0000644 00000013606 15151765342 0012446 0 ustar 00 <?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/>. /** * Standard log reader/writer. * * @package logstore_standard * @copyright 2013 Petr Skoda {@link http://skodak.org} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace logstore_standard\log; defined('MOODLE_INTERNAL') || die(); class store implements \tool_log\log\writer, \core\log\sql_internal_table_reader { use \tool_log\helper\store, \tool_log\helper\buffered_writer, \tool_log\helper\reader; /** @var string $logguests true if logging guest access */ protected $logguests; public function __construct(\tool_log\log\manager $manager) { $this->helper_setup($manager); // Log everything before setting is saved for the first time. $this->logguests = $this->get_config('logguests', 1); // JSON writing defaults to false (table format compatibility with older versions). // Note: This variable is defined in the buffered_writer trait. $this->jsonformat = (bool)$this->get_config('jsonformat', false); } /** * Should the event be ignored (== not logged)? * @param \core\event\base $event * @return bool */ protected function is_event_ignored(\core\event\base $event) { if ((!CLI_SCRIPT or PHPUNIT_TEST) and !$this->logguests) { // Always log inside CLI scripts because we do not login there. if (!isloggedin() or isguestuser()) { return true; } } return false; } /** * Finally store the events into the database. * * @param array $evententries raw event data */ protected function insert_event_entries($evententries) { global $DB; $DB->insert_records('logstore_standard_log', $evententries); } public function get_events_select($selectwhere, array $params, $sort, $limitfrom, $limitnum) { global $DB; $sort = self::tweak_sort_by_id($sort); $events = array(); $records = $DB->get_recordset_select('logstore_standard_log', $selectwhere, $params, $sort, '*', $limitfrom, $limitnum); foreach ($records as $data) { if ($event = $this->get_log_event($data)) { $events[$data->id] = $event; } } $records->close(); return $events; } /** * Fetch records using given criteria returning a Traversable object. * * Note that the traversable object contains a moodle_recordset, so * remember that is important that you call close() once you finish * using it. * * @param string $selectwhere * @param array $params * @param string $sort * @param int $limitfrom * @param int $limitnum * @return \core\dml\recordset_walk|\core\event\base[] */ public function get_events_select_iterator($selectwhere, array $params, $sort, $limitfrom, $limitnum) { global $DB; $sort = self::tweak_sort_by_id($sort); $recordset = $DB->get_recordset_select('logstore_standard_log', $selectwhere, $params, $sort, '*', $limitfrom, $limitnum); return new \core\dml\recordset_walk($recordset, array($this, 'get_log_event')); } /** * Returns an event from the log data. * * @param stdClass $data Log data * @return \core\event\base */ public function get_log_event($data) { $extra = array('origin' => $data->origin, 'ip' => $data->ip, 'realuserid' => $data->realuserid); $data = (array)$data; $id = $data['id']; $data['other'] = self::decode_other($data['other']); if ($data['other'] === false) { $data['other'] = array(); } unset($data['origin']); unset($data['ip']); unset($data['realuserid']); unset($data['id']); if (!$event = \core\event\base::restore($data, $extra)) { return null; } return $event; } /** * Get number of events present for the given select clause. * * @param string $selectwhere select conditions. * @param array $params params. * * @return int Number of events available for the given conditions */ public function get_events_select_count($selectwhere, array $params) { global $DB; return $DB->count_records_select('logstore_standard_log', $selectwhere, $params); } /** * Get whether events are present for the given select clause. * * @param string $selectwhere select conditions. * @param array $params params. * * @return bool Whether events available for the given conditions */ public function get_events_select_exists(string $selectwhere, array $params): bool { global $DB; return $DB->record_exists_select('logstore_standard_log', $selectwhere, $params); } public function get_internal_log_table_name() { return 'logstore_standard_log'; } /** * Are the new events appearing in the reader? * * @return bool true means new log events are being added, false means no new data will be added */ public function is_logging() { // Only enabled stpres are queried, // this means we can return true here unless store has some extra switch. return true; } } standard/classes/task/cleanup_task.php 0000644 00000004763 15151765342 0014150 0 ustar 00 <?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/>. /** * Standard log reader/writer. * * @package logstore_standard * @copyright 2014 Petr Skoda {@link http://skodak.org} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace logstore_standard\task; defined('MOODLE_INTERNAL') || die(); class cleanup_task extends \core\task\scheduled_task { /** * Get a descriptive name for this task (shown to admins). * * @return string */ public function get_name() { return get_string('taskcleanup', 'logstore_standard'); } /** * Do the job. * Throw exceptions on errors (the job will be retried). */ public function execute() { global $DB; $loglifetime = (int)get_config('logstore_standard', 'loglifetime'); if (empty($loglifetime) || $loglifetime < 0) { return; } $loglifetime = time() - ($loglifetime * 3600 * 24); // Value in days. $lifetimep = array($loglifetime); $start = time(); while ($min = $DB->get_field_select("logstore_standard_log", "MIN(timecreated)", "timecreated < ?", $lifetimep)) { // Break this down into chunks to avoid transaction for too long and generally thrashing database. // Experiments suggest deleting one day takes up to a few seconds; probably a reasonable chunk size usually. // If the cleanup has just been enabled, it might take e.g a month to clean the years of logs. $params = array(min($min + 3600 * 24, $loglifetime)); $DB->delete_records_select("logstore_standard_log", "timecreated < ?", $params); if (time() > $start + 300) { // Do not churn on log deletion for too long each run. break; } } mtrace(" Deleted old log records from standard store."); } } standard/lang/en/logstore_standard.php 0000644 00000004066 15151765342 0014135 0 ustar 00 <?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/>. /** * Log store lang strings. * * @package logstore_standard * @copyright 2013 Petr Skoda {@link http://skodak.org} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['buffersize'] = 'Write buffer size'; $string['jsonformat'] = 'JSON format'; $string['jsonformat_desc'] = 'Use standard JSON format instead of PHP serialised data in the \'other\' database field.'; $string['pluginname'] = 'Standard log'; $string['pluginname_desc'] = 'A log plugin stores log entries in a Moodle database table.'; $string['privacy:metadata:log'] = 'A collection of past events'; $string['privacy:metadata:log:anonymous'] = 'Whether the event was flagged as anonymous'; $string['privacy:metadata:log:eventname'] = 'The event name'; $string['privacy:metadata:log:ip'] = 'The IP address used at the time of the event'; $string['privacy:metadata:log:origin'] = 'The origin of the event'; $string['privacy:metadata:log:other'] = 'Additional information about the event'; $string['privacy:metadata:log:realuserid'] = 'The ID of the real user behind the event, when masquerading a user.'; $string['privacy:metadata:log:relateduserid'] = 'The ID of a user related to this event'; $string['privacy:metadata:log:timecreated'] = 'The time when the event occurred'; $string['privacy:metadata:log:userid'] = 'The ID of the user who triggered this event'; $string['taskcleanup'] = 'Log table cleanup'; database/db/upgrade.php 0000644 00000002532 15151765342 0011050 0 ustar 00 <?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/>. /** * Database log store upgrade. * * @package logstore_database * @copyright 2014 onwards Ankit Agarwal <ankit.agrr@gmail.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); function xmldb_logstore_database_upgrade($oldversion) { global $CFG; // Automatically generated Moodle v3.9.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.0.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.1.0 release upgrade line. // Put any upgrade step following this. return true; } database/test_settings.php 0000644 00000007530 15151765342 0011736 0 ustar 00 <?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/>. /** * Filter form. * * @package logstore_database * @copyright 2014 onwards Ankit Agarwal * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ require_once('../../../../../config.php'); require_once($CFG->dirroot . '/lib/adminlib.php'); require_sesskey(); navigation_node::override_active_url(new moodle_url('/admin/settings.php', array('section' => 'logsettingdatabase'))); admin_externalpage_setup('logstoredbtestsettings'); echo $OUTPUT->header(); echo $OUTPUT->heading(get_string('testingsettings', 'logstore_database')); // NOTE: this is not localised intentionally, admins are supposed to understand English at least a bit... raise_memory_limit(MEMORY_HUGE); $dbtable = get_config('logstore_database', 'dbtable'); if (empty($dbtable)) { echo $OUTPUT->notification('External table not specified.', 'notifyproblem'); die(); } $dbdriver = get_config('logstore_database', 'dbdriver'); list($dblibrary, $dbtype) = explode('/', $dbdriver); if (!$db = \moodle_database::get_driver_instance($dbtype, $dblibrary, true)) { echo $OUTPUT->notification("Unknown driver $dblibrary/$dbtype", "notifyproblem"); die(); } $olddebug = $CFG->debug; $olddisplay = ini_get('display_errors'); ini_set('display_errors', '1'); $CFG->debug = DEBUG_DEVELOPER; error_reporting($CFG->debug); $dboptions = array(); $dboptions['dbpersist'] = get_config('logstore_database', 'dbpersist'); $dboptions['dbsocket'] = get_config('logstore_database', 'dbsocket'); $dboptions['dbport'] = get_config('logstore_database', 'dbport'); $dboptions['dbschema'] = get_config('logstore_database', 'dbschema'); $dboptions['dbcollation'] = get_config('logstore_database', 'dbcollation'); $dboptions['dbhandlesoptions'] = get_config('logstore_database', 'dbhandlesoptions'); try { $db->connect(get_config('logstore_database', 'dbhost'), get_config('logstore_database', 'dbuser'), get_config('logstore_database', 'dbpass'), get_config('logstore_database', 'dbname'), false, $dboptions); } catch (\moodle_exception $e) { echo $OUTPUT->notification('Cannot connect to the database.', 'notifyproblem'); $CFG->debug = $olddebug; ini_set('display_errors', $olddisplay); error_reporting($CFG->debug); ob_end_flush(); echo $OUTPUT->footer(); die(); } echo $OUTPUT->notification('Connection made.', 'notifysuccess'); $tables = $db->get_tables(); if (!in_array($dbtable, $tables)) { echo $OUTPUT->notification('Cannot find the specified table ' . $dbtable, 'notifyproblem'); $CFG->debug = $olddebug; ini_set('display_errors', $olddisplay); error_reporting($CFG->debug); ob_end_flush(); echo $OUTPUT->footer(); die(); } echo $OUTPUT->notification('Table ' . $dbtable . ' found.', 'notifysuccess'); $cols = $db->get_columns($dbtable); if (empty($cols)) { echo $OUTPUT->notification('Can not read external table.', 'notifyproblem'); } else { $columns = array_keys((array)$cols); echo $OUTPUT->notification('External table contains following columns:<br />' . implode(', ', $columns), 'notifysuccess'); } $db->dispose(); $CFG->debug = $olddebug; ini_set('display_errors', $olddisplay); error_reporting($CFG->debug); ob_end_flush(); echo $OUTPUT->footer(); database/tests/fixtures/event.php 0000644 00000002624 15151765342 0013172 0 ustar 00 <?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/>. /** * Fixtures for database log storage testing. * * @package logstore_database * @copyright 2014 Petr Skoda * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace logstore_database\event; defined('MOODLE_INTERNAL') || die(); class unittest_executed extends \core\event\base { public static function get_name() { return 'xxx'; } public function get_description() { return 'yyy'; } protected function init() { $this->data['crud'] = 'u'; $this->data['edulevel'] = self::LEVEL_PARTICIPATING; } public function get_url() { return new \moodle_url('/somepath/somefile.php', array('id' => $this->data['other']['sample'])); } } database/tests/fixtures/store.php 0000644 00000002356 15151765342 0013207 0 ustar 00 <?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/>. /** * Fixtures for database log storage testing. * * @package logstore_database * @copyright 2014 onwards Ankit Agarwal * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace logstore_database\test; defined('MOODLE_INTERNAL') || die(); class store extends \logstore_database\log\store { /** * Public wrapper for testing. * * @param \core\event\base $event * * @return bool */ public function is_event_ignored(\core\event\base $event) { return parent::is_event_ignored($event); } } database/tests/privacy/provider_test.php 0000644 00000057340 15151765342 0014553 0 ustar 00 <?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/>. /** * Data provider tests. * * @package logstore_database * @category test * @copyright 2018 Frédéric Massart * @author Frédéric Massart <fred@branchup.tech> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace logstore_database\privacy; defined('MOODLE_INTERNAL') || die(); global $CFG; use core_privacy\tests\provider_testcase; use core_privacy\local\request\contextlist; use core_privacy\local\request\approved_contextlist; use core_privacy\local\request\transform; use core_privacy\local\request\writer; use logstore_database\privacy\provider; require_once(__DIR__ . '/../fixtures/event.php'); /** * Data provider testcase class. * * This testcase is almost identical to the logstore_standard testcase, aside from the * initialisation of the relevant logstore obviously. * * @package logstore_database * @category test * @copyright 2018 Frédéric Massart * @author Frédéric Massart <fred@branchup.tech> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider_test extends provider_testcase { public function setUp(): void { global $CFG; $this->resetAfterTest(); $this->preventResetByRollback(); // Logging waits till the transaction gets committed. // Fake the settings, we will abuse the standard plugin table here... set_config('dbdriver', $CFG->dblibrary . '/' . $CFG->dbtype, 'logstore_database'); set_config('dbhost', $CFG->dbhost, 'logstore_database'); set_config('dbuser', $CFG->dbuser, 'logstore_database'); set_config('dbpass', $CFG->dbpass, 'logstore_database'); set_config('dbname', $CFG->dbname, 'logstore_database'); set_config('dbtable', $CFG->prefix . 'logstore_standard_log', 'logstore_database'); if (!empty($CFG->dboptions['dbpersist'])) { set_config('dbpersist', 1, 'logstore_database'); } else { set_config('dbpersist', 0, 'logstore_database'); } if (!empty($CFG->dboptions['dbsocket'])) { set_config('dbsocket', $CFG->dboptions['dbsocket'], 'logstore_database'); } else { set_config('dbsocket', '', 'logstore_database'); } if (!empty($CFG->dboptions['dbport'])) { set_config('dbport', $CFG->dboptions['dbport'], 'logstore_database'); } else { set_config('dbport', '', 'logstore_database'); } if (!empty($CFG->dboptions['dbschema'])) { set_config('dbschema', $CFG->dboptions['dbschema'], 'logstore_database'); } else { set_config('dbschema', '', 'logstore_database'); } if (!empty($CFG->dboptions['dbcollation'])) { set_config('dbcollation', $CFG->dboptions['dbcollation'], 'logstore_database'); } else { set_config('dbcollation', '', 'logstore_database'); } if (!empty($CFG->dboptions['dbhandlesoptions'])) { set_config('dbhandlesoptions', $CFG->dboptions['dbhandlesoptions'], 'logstore_database'); } else { set_config('dbhandlesoptions', false, 'logstore_database'); } } public function test_get_contexts_for_userid() { $admin = \core_user::get_user(2); $u1 = $this->getDataGenerator()->create_user(); $u2 = $this->getDataGenerator()->create_user(); $u3 = $this->getDataGenerator()->create_user(); $c1 = $this->getDataGenerator()->create_course(); $cm1 = $this->getDataGenerator()->create_module('url', ['course' => $c1]); $c2 = $this->getDataGenerator()->create_course(); $cm2 = $this->getDataGenerator()->create_module('url', ['course' => $c2]); $sysctx = \context_system::instance(); $c1ctx = \context_course::instance($c1->id); $c2ctx = \context_course::instance($c2->id); $cm1ctx = \context_module::instance($cm1->cmid); $cm2ctx = \context_module::instance($cm2->cmid); $this->enable_logging(); $manager = get_log_manager(true); // User 1 is the author. $this->setUser($u1); $this->assert_contextlist_equals($this->get_contextlist_for_user($u1), []); $e = \logstore_database\event\unittest_executed::create(['context' => $cm1ctx]); $e->trigger(); $this->assert_contextlist_equals($this->get_contextlist_for_user($u1), [$cm1ctx]); // User 2 is the related user. $this->setUser(0); $this->assert_contextlist_equals($this->get_contextlist_for_user($u2), []); $e = \logstore_database\event\unittest_executed::create(['context' => $cm2ctx, 'relateduserid' => $u2->id]); $e->trigger(); $this->assert_contextlist_equals($this->get_contextlist_for_user($u2), [$cm2ctx]); // Admin user is the real user. $this->assert_contextlist_equals($this->get_contextlist_for_user($admin), []); $this->assert_contextlist_equals($this->get_contextlist_for_user($u3), []); $this->setAdminUser(); \core\session\manager::loginas($u3->id, $sysctx); $e = \logstore_database\event\unittest_executed::create(['context' => $c1ctx]); $e->trigger(); $this->assert_contextlist_equals($this->get_contextlist_for_user($admin), [$sysctx, $c1ctx]); $this->assert_contextlist_equals($this->get_contextlist_for_user($u3), [$sysctx, $c1ctx]); // By admin user masquerading u1 related to u3. $this->assert_contextlist_equals($this->get_contextlist_for_user($u1), [$cm1ctx]); $this->assert_contextlist_equals($this->get_contextlist_for_user($u3), [$sysctx, $c1ctx]); $this->assert_contextlist_equals($this->get_contextlist_for_user($admin), [$sysctx, $c1ctx]); $this->setAdminUser(); \core\session\manager::loginas($u1->id, \context_system::instance()); $e = \logstore_database\event\unittest_executed::create(['context' => $c2ctx, 'relateduserid' => $u3->id]); $e->trigger(); $this->assert_contextlist_equals($this->get_contextlist_for_user($u1), [$sysctx, $cm1ctx, $c2ctx]); $this->assert_contextlist_equals($this->get_contextlist_for_user($u3), [$sysctx, $c1ctx, $c2ctx]); $this->assert_contextlist_equals($this->get_contextlist_for_user($admin), [$sysctx, $c1ctx, $c2ctx]); } /** * Check that user IDs are returned for a given context. */ public function test_add_userids_for_context() { $admin = \core_user::get_user(2); $u1 = $this->getDataGenerator()->create_user(); $u2 = $this->getDataGenerator()->create_user(); $u3 = $this->getDataGenerator()->create_user(); $u4 = $this->getDataGenerator()->create_user(); $c1 = $this->getDataGenerator()->create_course(); $sysctx = \context_system::instance(); $c1ctx = \context_course::instance($c1->id); $this->enable_logging(); $manager = get_log_manager(true); $userlist = new \core_privacy\local\request\userlist($sysctx, 'logstore_database'); $userids = $userlist->get_userids(); $this->assertEmpty($userids); provider::add_userids_for_context($userlist); $userids = $userlist->get_userids(); $this->assertEmpty($userids); // User one should be added (userid). $this->setUser($u1); $e = \logstore_database\event\unittest_executed::create(['context' => $sysctx]); $e->trigger(); // User two (userids) and three (relateduserid) should be added. $this->setUser($u2); $e = \logstore_database\event\unittest_executed::create(['context' => $sysctx, 'relateduserid' => $u3->id]); $e->trigger(); // The admin user should be added (realuserid). $this->setAdminUser(); \core\session\manager::loginas($u2->id, \context_system::instance()); $e = \logstore_database\event\unittest_executed::create(['context' => $sysctx]); $e->trigger(); // Set off an event in a different context. User 4 should not be returned below. $this->setUser($u4); $e = \logstore_database\event\unittest_executed::create(['context' => $c1ctx]); $e->trigger(); provider::add_userids_for_context($userlist); $userids = $userlist->get_userids(); $this->assertCount(4, $userids); $expectedresult = [$admin->id, $u1->id, $u2->id, $u3->id]; $this->assertEmpty(array_diff($expectedresult, $userids)); } public function test_delete_data_for_user() { global $DB; $u1 = $this->getDataGenerator()->create_user(); $u2 = $this->getDataGenerator()->create_user(); $c1 = $this->getDataGenerator()->create_course(); $c2 = $this->getDataGenerator()->create_course(); $sysctx = \context_system::instance(); $c1ctx = \context_course::instance($c1->id); $c2ctx = \context_course::instance($c2->id); $this->enable_logging(); $manager = get_log_manager(true); // User 1 is the author. $this->setUser($u1); $e = \logstore_database\event\unittest_executed::create(['context' => $c1ctx]); $e->trigger(); $e = \logstore_database\event\unittest_executed::create(['context' => $c1ctx]); $e->trigger(); $e = \logstore_database\event\unittest_executed::create(['context' => $c2ctx]); $e->trigger(); // User 2 is the author. $this->setUser($u2); $e = \logstore_database\event\unittest_executed::create(['context' => $c1ctx]); $e->trigger(); $e = \logstore_database\event\unittest_executed::create(['context' => $c2ctx]); $e->trigger(); // Confirm data present. $this->assertTrue($DB->record_exists('logstore_standard_log', ['userid' => $u1->id, 'contextid' => $c1ctx->id])); $this->assertEquals(3, $DB->count_records('logstore_standard_log', ['userid' => $u1->id])); $this->assertEquals(2, $DB->count_records('logstore_standard_log', ['userid' => $u2->id])); // Delete all the things! provider::delete_data_for_user(new approved_contextlist($u1, 'logstore_database', [$c1ctx->id])); $this->assertFalse($DB->record_exists('logstore_standard_log', ['userid' => $u1->id, 'contextid' => $c1ctx->id])); $this->assertEquals(1, $DB->count_records('logstore_standard_log', ['userid' => $u1->id])); $this->assertEquals(2, $DB->count_records('logstore_standard_log', ['userid' => $u2->id])); } public function test_delete_data_for_all_users_in_context() { global $DB; $u1 = $this->getDataGenerator()->create_user(); $u2 = $this->getDataGenerator()->create_user(); $c1 = $this->getDataGenerator()->create_course(); $c2 = $this->getDataGenerator()->create_course(); $sysctx = \context_system::instance(); $c1ctx = \context_course::instance($c1->id); $c2ctx = \context_course::instance($c2->id); $this->enable_logging(); $manager = get_log_manager(true); // User 1 is the author. $this->setUser($u1); $e = \logstore_database\event\unittest_executed::create(['context' => $c1ctx]); $e->trigger(); $e = \logstore_database\event\unittest_executed::create(['context' => $c1ctx]); $e->trigger(); $e = \logstore_database\event\unittest_executed::create(['context' => $c2ctx]); $e->trigger(); // User 2 is the author. $this->setUser($u2); $e = \logstore_database\event\unittest_executed::create(['context' => $c1ctx]); $e->trigger(); $e = \logstore_database\event\unittest_executed::create(['context' => $c2ctx]); $e->trigger(); // Confirm data present. $this->assertTrue($DB->record_exists('logstore_standard_log', ['contextid' => $c1ctx->id])); $this->assertEquals(3, $DB->count_records('logstore_standard_log', ['userid' => $u1->id])); $this->assertEquals(2, $DB->count_records('logstore_standard_log', ['userid' => $u2->id])); // Delete all the things! provider::delete_data_for_all_users_in_context($c1ctx); $this->assertFalse($DB->record_exists('logstore_standard_log', ['contextid' => $c1ctx->id])); $this->assertEquals(1, $DB->count_records('logstore_standard_log', ['userid' => $u1->id])); $this->assertEquals(1, $DB->count_records('logstore_standard_log', ['userid' => $u2->id])); } /** * Check that data is removed for the listed users in a given context. */ public function test_delete_data_for_userlist() { global $DB; $u1 = $this->getDataGenerator()->create_user(); $u2 = $this->getDataGenerator()->create_user(); $u3 = $this->getDataGenerator()->create_user(); $u4 = $this->getDataGenerator()->create_user(); $course = $this->getDataGenerator()->create_course(); $sysctx = \context_system::instance(); $c1ctx = \context_course::instance($course->id); $this->enable_logging(); $manager = get_log_manager(true); $this->setUser($u1); $e = \logstore_database\event\unittest_executed::create(['context' => $sysctx]); $e->trigger(); $this->setUser($u2); $e = \logstore_database\event\unittest_executed::create(['context' => $sysctx]); $e->trigger(); $this->setUser($u3); $e = \logstore_database\event\unittest_executed::create(['context' => $sysctx]); $e->trigger(); $this->setUser($u4); $e = \logstore_database\event\unittest_executed::create(['context' => $c1ctx]); $e->trigger(); // Check that four records were created. $this->assertEquals(4, $DB->count_records('logstore_standard_log')); $userlist = new \core_privacy\local\request\approved_userlist($sysctx, 'logstore_database', [$u1->id, $u3->id]); provider::delete_data_for_userlist($userlist); // We should have a record for u2 and u4. $this->assertEquals(2, $DB->count_records('logstore_standard_log')); $records = $DB->get_records('logstore_standard_log', ['contextid' => $sysctx->id]); $this->assertCount(1, $records); $currentrecord = array_shift($records); $this->assertEquals($u2->id, $currentrecord->userid); } public function test_export_data_for_user() { $admin = \core_user::get_user(2); $u1 = $this->getDataGenerator()->create_user(); $u2 = $this->getDataGenerator()->create_user(); $u3 = $this->getDataGenerator()->create_user(); $u4 = $this->getDataGenerator()->create_user(); $c1 = $this->getDataGenerator()->create_course(); $cm1 = $this->getDataGenerator()->create_module('url', ['course' => $c1]); $c2 = $this->getDataGenerator()->create_course(); $cm2 = $this->getDataGenerator()->create_module('url', ['course' => $c2]); $sysctx = \context_system::instance(); $c1ctx = \context_course::instance($c1->id); $c2ctx = \context_course::instance($c2->id); $cm1ctx = \context_module::instance($cm1->cmid); $cm2ctx = \context_module::instance($cm2->cmid); $path = [get_string('privacy:path:logs', 'tool_log'), get_string('pluginname', 'logstore_database')]; $this->enable_logging(); $manager = get_log_manager(true); // User 1 is the author. $this->setUser($u1); $e = \logstore_database\event\unittest_executed::create(['context' => $c1ctx, 'other' => ['i' => 0]]); $e->trigger(); // User 2 is related. $this->setUser(0); $e = \logstore_database\event\unittest_executed::create(['context' => $c1ctx, 'relateduserid' => $u2->id, 'other' => ['i' => 1]]); $e->trigger(); // Admin user masquerades u3, which is related to u4. $this->setAdminUser(); \core\session\manager::loginas($u3->id, $sysctx); $e = \logstore_database\event\unittest_executed::create(['context' => $c1ctx, 'relateduserid' => $u4->id, 'other' => ['i' => 2]]); $e->trigger(); // Confirm data present for u1. provider::export_user_data(new approved_contextlist($u1, 'logstore_database', [$c2ctx->id, $c1ctx->id])); $data = writer::with_context($c2ctx)->get_data($path); $this->assertEmpty($data); $data = writer::with_context($c1ctx)->get_data($path); $this->assertCount(1, $data->logs); $this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_you']); $this->assertSame(0, $data->logs[0]['other']['i']); // Confirm data present for u2. writer::reset(); provider::export_user_data(new approved_contextlist($u2, 'logstore_database', [$c2ctx->id, $c1ctx->id])); $data = writer::with_context($c2ctx)->get_data($path); $this->assertEmpty($data); $data = writer::with_context($c1ctx)->get_data($path); $this->assertCount(1, $data->logs); $this->assertEquals(transform::yesno(false), $data->logs[0]['author_of_the_action_was_you']); $this->assertEquals(transform::yesno(true), $data->logs[0]['related_user_was_you']); $this->assertSame(1, $data->logs[0]['other']['i']); // Confirm data present for u3. writer::reset(); provider::export_user_data(new approved_contextlist($u3, 'logstore_database', [$c2ctx->id, $c1ctx->id])); $data = writer::with_context($c2ctx)->get_data($path); $this->assertEmpty($data); $data = writer::with_context($c1ctx)->get_data($path); $this->assertCount(1, $data->logs); $this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_you']); $this->assertEquals(transform::yesno(false), $data->logs[0]['related_user_was_you']); $this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_masqueraded']); $this->assertEquals(transform::yesno(false), $data->logs[0]['masquerading_user_was_you']); $this->assertSame(2, $data->logs[0]['other']['i']); // Confirm data present for u4. writer::reset(); provider::export_user_data(new approved_contextlist($u4, 'logstore_database', [$c2ctx->id, $c1ctx->id])); $data = writer::with_context($c2ctx)->get_data($path); $this->assertEmpty($data); $data = writer::with_context($c1ctx)->get_data($path); $this->assertCount(1, $data->logs); $this->assertEquals(transform::yesno(false), $data->logs[0]['author_of_the_action_was_you']); $this->assertEquals(transform::yesno(true), $data->logs[0]['related_user_was_you']); $this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_masqueraded']); $this->assertEquals(transform::yesno(false), $data->logs[0]['masquerading_user_was_you']); $this->assertSame(2, $data->logs[0]['other']['i']); // Add anonymous events. $this->setUser($u1); $e = \logstore_database\event\unittest_executed::create(['context' => $c2ctx, 'relateduserid' => $u2->id, 'anonymous' => true]); $e->trigger(); $this->setAdminUser(); \core\session\manager::loginas($u3->id, $sysctx); $e = \logstore_database\event\unittest_executed::create(['context' => $c2ctx, 'relateduserid' => $u4->id, 'anonymous' => true]); $e->trigger(); // Confirm data present for u1. provider::export_user_data(new approved_contextlist($u1, 'logstore_database', [$c2ctx->id])); $data = writer::with_context($c2ctx)->get_data($path); $this->assertCount(1, $data->logs); $this->assertEquals(transform::yesno(true), $data->logs[0]['action_was_done_anonymously']); $this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_you']); // Confirm data present for u2. writer::reset(); provider::export_user_data(new approved_contextlist($u2, 'logstore_database', [$c2ctx->id])); $data = writer::with_context($c2ctx)->get_data($path); $this->assertCount(1, $data->logs); $this->assertEquals(transform::yesno(true), $data->logs[0]['action_was_done_anonymously']); $this->assertArrayNotHasKey('author_of_the_action_was_you', $data->logs[0]); $this->assertArrayNotHasKey('authorid', $data->logs[0]); $this->assertEquals(transform::yesno(true), $data->logs[0]['related_user_was_you']); // Confirm data present for u3. writer::reset(); provider::export_user_data(new approved_contextlist($u3, 'logstore_database', [$c2ctx->id])); $data = writer::with_context($c2ctx)->get_data($path); $this->assertCount(1, $data->logs); $this->assertEquals(transform::yesno(true), $data->logs[0]['action_was_done_anonymously']); $this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_you']); $this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_masqueraded']); $this->assertArrayNotHasKey('masquerading_user_was_you', $data->logs[0]); $this->assertArrayNotHasKey('masqueradinguserid', $data->logs[0]); // Confirm data present for u4. writer::reset(); provider::export_user_data(new approved_contextlist($u4, 'logstore_database', [$c2ctx->id])); $data = writer::with_context($c2ctx)->get_data($path); $this->assertCount(1, $data->logs); $this->assertEquals(transform::yesno(true), $data->logs[0]['action_was_done_anonymously']); $this->assertArrayNotHasKey('author_of_the_action_was_you', $data->logs[0]); $this->assertArrayNotHasKey('authorid', $data->logs[0]); $this->assertEquals(transform::yesno(true), $data->logs[0]['related_user_was_you']); $this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_masqueraded']); $this->assertArrayNotHasKey('masquerading_user_was_you', $data->logs[0]); $this->assertArrayNotHasKey('masqueradinguserid', $data->logs[0]); } /** * Assert the content of a context list. * * @param contextlist $contextlist The collection. * @param array $expected List of expected contexts or IDs. * @return void */ protected function assert_contextlist_equals($contextlist, array $expected) { $expectedids = array_map(function($context) { if (is_object($context)) { return $context->id; } return $context; }, $expected); $contextids = array_map('intval', $contextlist->get_contextids()); sort($contextids); sort($expectedids); $this->assertEquals($expectedids, $contextids); } /** * Enable logging. * * @return void */ protected function enable_logging() { set_config('enabled_stores', 'logstore_database', 'tool_log'); set_config('buffersize', 0, 'logstore_database'); set_config('logguests', 1, 'logstore_database'); get_log_manager(true); } /** * Get the contextlist for a user. * * @param object $user The user. * @return contextlist */ protected function get_contextlist_for_user($user) { $contextlist = new contextlist(); provider::add_contexts_for_userid($contextlist, $user->id); return $contextlist; } } database/tests/store_test.php 0000644 00000035056 15151765342 0012400 0 ustar 00 <?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/>. namespace logstore_database; defined('MOODLE_INTERNAL') || die(); require_once(__DIR__ . '/fixtures/event.php'); require_once(__DIR__ . '/fixtures/store.php'); /** * External database log store tests. * * @package logstore_database * @copyright 2014 Petr Skoda {@link http://skodak.org/} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class store_test extends \advanced_testcase { /** * Tests log writing. * * @param bool $jsonformat True to test with JSON format * @dataProvider log_writing_provider */ public function test_log_writing(bool $jsonformat) { global $DB, $CFG; $this->resetAfterTest(); $this->preventResetByRollback(); // Logging waits till the transaction gets committed. // Apply JSON format system setting. set_config('jsonformat', $jsonformat ? 1 : 0, 'logstore_database'); $dbman = $DB->get_manager(); $this->assertTrue($dbman->table_exists('logstore_standard_log')); $DB->delete_records('logstore_standard_log'); $this->setAdminUser(); $user1 = $this->getDataGenerator()->create_user(); $user2 = $this->getDataGenerator()->create_user(); $course1 = $this->getDataGenerator()->create_course(); $module1 = $this->getDataGenerator()->create_module('resource', array('course' => $course1)); $course2 = $this->getDataGenerator()->create_course(); $module2 = $this->getDataGenerator()->create_module('resource', array('course' => $course2)); // Test all plugins are disabled by this command. set_config('enabled_stores', '', 'tool_log'); $manager = get_log_manager(true); $stores = $manager->get_readers(); $this->assertCount(0, $stores); // Fake the settings, we will abuse the standard plugin table here... set_config('dbdriver', $CFG->dblibrary . '/' . $CFG->dbtype, 'logstore_database'); set_config('dbhost', $CFG->dbhost, 'logstore_database'); set_config('dbuser', $CFG->dbuser, 'logstore_database'); set_config('dbpass', $CFG->dbpass, 'logstore_database'); set_config('dbname', $CFG->dbname, 'logstore_database'); set_config('dbtable', $CFG->prefix . 'logstore_standard_log', 'logstore_database'); if (!empty($CFG->dboptions['dbpersist'])) { set_config('dbpersist', 1, 'logstore_database'); } else { set_config('dbpersist', 0, 'logstore_database'); } if (!empty($CFG->dboptions['dbsocket'])) { set_config('dbsocket', $CFG->dboptions['dbsocket'], 'logstore_database'); } else { set_config('dbsocket', '', 'logstore_database'); } if (!empty($CFG->dboptions['dbport'])) { set_config('dbport', $CFG->dboptions['dbport'], 'logstore_database'); } else { set_config('dbport', '', 'logstore_database'); } if (!empty($CFG->dboptions['dbschema'])) { set_config('dbschema', $CFG->dboptions['dbschema'], 'logstore_database'); } else { set_config('dbschema', '', 'logstore_database'); } if (!empty($CFG->dboptions['dbcollation'])) { set_config('dbcollation', $CFG->dboptions['dbcollation'], 'logstore_database'); } else { set_config('dbcollation', '', 'logstore_database'); } if (!empty($CFG->dboptions['dbhandlesoptions'])) { set_config('dbhandlesoptions', $CFG->dboptions['dbhandlesoptions'], 'logstore_database'); } else { set_config('dbhandlesoptions', false, 'logstore_database'); } // Enable logging plugin. set_config('enabled_stores', 'logstore_database', 'tool_log'); set_config('buffersize', 0, 'logstore_database'); set_config('logguests', 1, 'logstore_database'); $manager = get_log_manager(true); $stores = $manager->get_readers(); $this->assertCount(1, $stores); $this->assertEquals(array('logstore_database'), array_keys($stores)); $store = $stores['logstore_database']; $this->assertInstanceOf('logstore_database\log\store', $store); $this->assertInstanceOf('tool_log\log\writer', $store); $this->assertTrue($store->is_logging()); $logs = $DB->get_records('logstore_standard_log', array(), 'id ASC'); $this->assertCount(0, $logs); $this->setCurrentTimeStart(); $this->setUser(0); $event1 = \logstore_database\event\unittest_executed::create( array('context' => \context_module::instance($module1->cmid), 'other' => array('sample' => 5, 'xx' => 10))); $event1->trigger(); $logs = $DB->get_records('logstore_standard_log', array(), 'id ASC'); $this->assertCount(1, $logs); $log1 = reset($logs); unset($log1->id); if ($jsonformat) { $log1->other = json_decode($log1->other, true); } else { $log1->other = unserialize($log1->other); } $log1 = (array)$log1; $data = $event1->get_data(); $data['origin'] = 'cli'; $data['ip'] = null; $data['realuserid'] = null; $this->assertEquals($data, $log1); $this->setAdminUser(); \core\session\manager::loginas($user1->id, \context_system::instance()); $this->assertEquals(2, $DB->count_records('logstore_standard_log')); $event2 = \logstore_database\event\unittest_executed::create( array('context' => \context_module::instance($module2->cmid), 'other' => array('sample' => 6, 'xx' => 9))); $event2->trigger(); \core\session\manager::init_empty_session(); $this->assertFalse(\core\session\manager::is_loggedinas()); $logs = $DB->get_records('logstore_standard_log', array(), 'id ASC'); $this->assertCount(3, $logs); array_shift($logs); $log2 = array_shift($logs); $this->assertSame('\core\event\user_loggedinas', $log2->eventname); $log3 = array_shift($logs); unset($log3->id); if ($jsonformat) { $log3->other = json_decode($log3->other, true); } else { $log3->other = unserialize($log3->other); } $log3 = (array)$log3; $data = $event2->get_data(); $data['origin'] = 'cli'; $data['ip'] = null; $data['realuserid'] = 2; $this->assertEquals($data, $log3); // Test reading. $this->assertSame(3, $store->get_events_select_count('', array())); $events = $store->get_events_select('', array(), 'timecreated ASC', 0, 0); // Is actually sorted by "timecreated ASC, id ASC". $this->assertCount(3, $events); $resev1 = array_shift($events); array_shift($events); $resev2 = array_shift($events); $this->assertEquals($event1->get_data(), $resev1->get_data()); $this->assertEquals($event2->get_data(), $resev2->get_data()); // Test buffering. set_config('buffersize', 3, 'logstore_database'); $manager = get_log_manager(true); $stores = $manager->get_readers(); /** @var \logstore_database\log\store $store */ $store = $stores['logstore_database']; $DB->delete_records('logstore_standard_log'); \logstore_database\event\unittest_executed::create( array('context' => \context_module::instance($module1->cmid), 'other' => array('sample' => 5, 'xx' => 10)))->trigger(); $this->assertEquals(0, $DB->count_records('logstore_standard_log')); \logstore_database\event\unittest_executed::create( array('context' => \context_module::instance($module1->cmid), 'other' => array('sample' => 5, 'xx' => 10)))->trigger(); $this->assertEquals(0, $DB->count_records('logstore_standard_log')); $store->flush(); $this->assertEquals(2, $DB->count_records('logstore_standard_log')); \logstore_database\event\unittest_executed::create( array('context' => \context_module::instance($module1->cmid), 'other' => array('sample' => 5, 'xx' => 10)))->trigger(); $this->assertEquals(2, $DB->count_records('logstore_standard_log')); \logstore_database\event\unittest_executed::create( array('context' => \context_module::instance($module1->cmid), 'other' => array('sample' => 5, 'xx' => 10)))->trigger(); $this->assertEquals(2, $DB->count_records('logstore_standard_log')); \logstore_database\event\unittest_executed::create( array('context' => \context_module::instance($module1->cmid), 'other' => array('sample' => 5, 'xx' => 10)))->trigger(); $this->assertEquals(5, $DB->count_records('logstore_standard_log')); \logstore_database\event\unittest_executed::create( array('context' => \context_module::instance($module1->cmid), 'other' => array('sample' => 5, 'xx' => 10)))->trigger(); $this->assertEquals(5, $DB->count_records('logstore_standard_log')); \logstore_database\event\unittest_executed::create( array('context' => \context_module::instance($module1->cmid), 'other' => array('sample' => 5, 'xx' => 10)))->trigger(); $this->assertEquals(5, $DB->count_records('logstore_standard_log')); \logstore_database\event\unittest_executed::create( array('context' => \context_module::instance($module1->cmid), 'other' => array('sample' => 5, 'xx' => 10)))->trigger(); $this->assertEquals(8, $DB->count_records('logstore_standard_log')); // Test guest logging setting. set_config('logguests', 0, 'logstore_database'); set_config('buffersize', 0, 'logstore_database'); get_log_manager(true); $DB->delete_records('logstore_standard_log'); get_log_manager(true); $this->setUser(null); \logstore_database\event\unittest_executed::create( array('context' => \context_module::instance($module1->cmid), 'other' => array('sample' => 5, 'xx' => 10)))->trigger(); $this->assertEquals(0, $DB->count_records('logstore_standard_log')); $this->setGuestUser(); \logstore_database\event\unittest_executed::create( array('context' => \context_module::instance($module1->cmid), 'other' => array('sample' => 5, 'xx' => 10)))->trigger(); $this->assertEquals(0, $DB->count_records('logstore_standard_log')); $this->setUser($user1); \logstore_database\event\unittest_executed::create( array('context' => \context_module::instance($module1->cmid), 'other' => array('sample' => 5, 'xx' => 10)))->trigger(); $this->assertEquals(1, $DB->count_records('logstore_standard_log')); $this->setUser($user2); \logstore_database\event\unittest_executed::create( array('context' => \context_module::instance($module1->cmid), 'other' => array('sample' => 5, 'xx' => 10)))->trigger(); $this->assertEquals(2, $DB->count_records('logstore_standard_log')); set_config('enabled_stores', '', 'tool_log'); get_log_manager(true); } /** * Returns different JSON format settings so the test can be run with JSON format either on or * off. * * @return bool[] Array of true/false */ public static function log_writing_provider(): array { return [ [false], [true] ]; } /** * Test method is_event_ignored. */ public function test_is_event_ignored() { $this->resetAfterTest(); // Test guest filtering. set_config('logguests', 0, 'logstore_database'); $this->setGuestUser(); $event = \logstore_database\event\unittest_executed::create( array('context' => \context_system::instance(), 'other' => array('sample' => 5, 'xx' => 10))); $logmanager = get_log_manager(); $store = new \logstore_database\test\store($logmanager); $this->assertTrue($store->is_event_ignored($event)); set_config('logguests', 1, 'logstore_database'); $store = new \logstore_database\test\store($logmanager); // Reload. $this->assertFalse($store->is_event_ignored($event)); // Test action/level filtering. set_config('includelevels', '', 'logstore_database'); set_config('includeactions', '', 'logstore_database'); $store = new \logstore_database\test\store($logmanager); // Reload. $this->assertTrue($store->is_event_ignored($event)); set_config('includelevels', '0,1', 'logstore_database'); $store = new \logstore_database\test\store($logmanager); // Reload. $this->assertTrue($store->is_event_ignored($event)); set_config('includelevels', '0,1,2', 'logstore_database'); $store = new \logstore_database\test\store($logmanager); // Reload. $this->assertFalse($store->is_event_ignored($event)); set_config('includelevels', '', 'logstore_database'); set_config('includeactions', 'c,r,d', 'logstore_database'); $store = new \logstore_database\test\store($logmanager); // Reload. $this->assertTrue($store->is_event_ignored($event)); set_config('includeactions', 'c,r,u,d', 'logstore_database'); $store = new \logstore_database\test\store($logmanager); // Reload. $this->assertFalse($store->is_event_ignored($event)); } /** * Test logmanager::get_supported_reports returns all reports that require this store. */ public function test_get_supported_reports() { $logmanager = get_log_manager(); $allreports = \core_component::get_plugin_list('report'); // Make sure all supported reports are installed. $expectedreports = array_intersect_key([ 'log' => 'report_log', 'loglive' => 'report_loglive', ], $allreports); $supportedreports = $logmanager->get_supported_reports('logstore_database'); foreach ($expectedreports as $expectedreport) { $this->assertArrayHasKey($expectedreport, $supportedreports); } } } database/backup/moodle2/backup_logstore_database_subplugin.class.php 0000644 00000005036 15151765343 0022050 0 ustar 00 <?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/>. /** * Backup implementation for the (tool_log) logstore_database subplugin. * * @package logstore_database * @category backup * @copyright 2015 Mark Nelson <markn@moodle.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); require_once('backup_logstore_database_nested_element.php'); class backup_logstore_database_subplugin extends backup_tool_log_logstore_subplugin { /** * Returns the subplugin structure to attach to the 'logstore' XML element. * * @return backup_subplugin_element the subplugin structure to be attached. */ protected function define_logstore_subplugin_structure() { $subplugin = $this->get_subplugin_element(); $subpluginwrapper = new backup_nested_element($this->get_recommended_name()); // Create the custom (base64 encoded, xml safe) 'other' final element. $otherelement = new base64_encode_final_element('other'); $subpluginlog = new backup_logstore_database_nested_element('logstore_database_log', array('id'), array( 'eventname', 'component', 'action', 'target', 'objecttable', 'objectid', 'crud', 'edulevel', 'contextid', 'userid', 'relateduserid', 'anonymous', $otherelement, 'timecreated', 'ip', 'realuserid')); $subplugin->add_child($subpluginwrapper); $subpluginwrapper->add_child($subpluginlog); // Get the details for the external database. $manager = new \tool_log\log\manager(); $store = new \logstore_database\log\store($manager); $extdb = $store->get_extdb(); if (!$extdb) { return false; } $subpluginlog->set_source_db($extdb); $subpluginlog->set_source_table($store->get_config_value('dbtable'), array('contextid' => backup::VAR_CONTEXTID)); return $subplugin; } } database/backup/moodle2/restore_logstore_database_subplugin.class.php 0000644 00000007051 15151765343 0022265 0 ustar 00 <?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/>. /** * Restore implementation for the (tool_log) logstore_database subplugin. * * @package logstore_database * @category backup * @copyright 2015 Mark Nelson <markn@moodle.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); class restore_logstore_database_subplugin extends restore_tool_log_logstore_subplugin { /** * @var moodle_database the external database. */ private static $extdb = null; /** * @var string the external database table name. */ private static $extdbtablename = null; /** * The constructor for this logstore. * * @param string $subplugintype the subplugin type. * @param string $subpluginname the subplugin name. * @param restore_structure_step $step. */ public function __construct($subplugintype, $subpluginname, $step) { // Check that the logstore is enabled before setting variables. $enabledlogstores = explode(',', get_config('tool_log', 'enabled_stores')); if (in_array('logstore_database', $enabledlogstores)) { $manager = new \tool_log\log\manager(); $store = new \logstore_database\log\store($manager); self::$extdb = $store->get_extdb(); self::$extdbtablename = $store->get_config_value('dbtable'); } parent::__construct($subplugintype, $subpluginname, $step); } /** * Returns the subplugin structure to attach to the 'logstore' XML element. * * @return restore_path_element[] array of elements to be processed on restore. */ protected function define_logstore_subplugin_structure() { // If the logstore is not enabled we don't add structures for it. $enabledlogstores = explode(',', get_config('tool_log', 'enabled_stores')); if (!in_array('logstore_database', $enabledlogstores)) { return array(); // The logstore is not enabled, nothing to restore. } $paths = array(); $elename = $this->get_namefor('log'); $elepath = $this->get_pathfor('/logstore_database_log'); $paths[] = new restore_path_element($elename, $elepath); return $paths; } /** * Process logstore_database_log entries. * * This method proceeds to read, complete, remap and, finally, * discard or save every log entry. * * @param array() $data log entry. * @return null if we are not restoring the log. */ public function process_logstore_database_log($data) { // Do not bother processing if we can not add it to a database. if (!self::$extdb || !self::$extdbtablename) { return; } $data = $this->process_log($data, get_config('logstore_database', 'jsonformat')); if ($data) { self::$extdb->insert_record(self::$extdbtablename, $data); } } } database/backup/moodle2/backup_logstore_database_nested_element.php 0000644 00000006470 15151765343 0021732 0 ustar 00 <?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/>. /** * Backup implementation for the (tool_log) logstore_database nested element. * * @package logstore_database * @category backup * @copyright 2015 Damyon Wiese <damyon@moodle.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); /** * Custom subclass of backup_nested_element that iterates over an external DB connection. * * @package logstore_database * @category backup * @copyright 2015 Damyon Wiese <damyon@moodle.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class backup_logstore_database_nested_element extends backup_nested_element { /** * @var \moodle_database $sourcedb */ protected $sourcedb; /** * Constructor - instantiates one backup_nested_element, specifying its basic info. * * @param string $name name of the element * @param array $attributes attributes this element will handle (optional, defaults to null) * @param array $finalelements this element will handle (optional, defaults to null) */ public function __construct($name, $attributes = null, $finalelements = null) { global $DB; parent::__construct($name, $attributes, $finalelements); $this->sourcedb = $DB; } /** * For sql or table datasources, this will iterate over the "external" DB connection * stored in this class instead of the default $DB. All other cases use the parent default. * @param object $processor the processor */ protected function get_iterator($processor) { if ($this->get_source_table() !== null) { // It's one table, return recordset iterator. return $this->get_source_db()->get_recordset( $this->get_source_table(), backup_structure_dbops::convert_params_to_values($this->procparams, $processor), $this->get_source_table_sortby() ); } else if ($this->get_source_sql() !== null) { // It's one sql, return recordset iterator. return $this->get_source_db()->get_recordset_sql( $this->get_source_sql(), backup_structure_dbops::convert_params_to_values($this->procparams, $processor) ); } return parent::get_iterator($processor); } /** * Set the database we want to use. * * @param \moodle_database $db */ public function set_source_db($db) { $this->sourcedb = $db; } /** * Get the database we want to use. * * @return \moodle_database $db */ public function get_source_db() { return $this->sourcedb; } } database/settings.php 0000644 00000011114 15151765343 0010671 0 ustar 00 <?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/>. /** * External database log store settings. * * @package logstore_database * @copyright 2013 Petr Skoda {@link http://skodak.org} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); if ($hassiteconfig) { $testurl = new moodle_url('/admin/tool/log/store/database/test_settings.php', array('sesskey' => sesskey())); $test = new admin_externalpage('logstoredbtestsettings', get_string('testsettings', 'logstore_database'), $testurl, 'moodle/site:config', true); $ADMIN->add('logging', $test); $drivers = \logstore_database\helper::get_drivers(); // Database settings. $link = html_writer::link($testurl, get_string('testsettings', 'logstore_database'), array('target' => '_blank')); $settings->add(new admin_setting_heading('dbsettings', get_string('databasesettings', 'logstore_database'), get_string('databasesettings_help', 'logstore_database', $link))); $settings->add(new admin_setting_configselect('logstore_database/dbdriver', get_string('databasetypehead', 'install'), '', '', $drivers)); $settings->add(new admin_setting_configtext('logstore_database/dbhost', get_string('databasehost', 'install'), '', '')); $settings->add(new admin_setting_configtext('logstore_database/dbuser', get_string('databaseuser', 'install'), '', '')); $settings->add(new admin_setting_configpasswordunmask('logstore_database/dbpass', get_string('databasepass', 'install'), '', '')); $settings->add(new admin_setting_configtext('logstore_database/dbname', get_string('databasename', 'install'), '', '')); $settings->add(new admin_setting_configtext('logstore_database/dbtable', get_string('databasetable', 'logstore_database'), get_string('databasetable_help', 'logstore_database'), '')); $settings->add(new admin_setting_configcheckbox('logstore_database/dbpersist', get_string('databasepersist', 'logstore_database'), '', '0')); $settings->add(new admin_setting_configtext('logstore_database/dbsocket', get_string('databasesocket', 'install'), '', '')); $settings->add(new admin_setting_configtext('logstore_database/dbport', get_string('databaseport', 'install'), '', '')); $settings->add(new admin_setting_configtext('logstore_database/dbschema', get_string('databaseschema', 'logstore_database'), '', '')); $settings->add(new admin_setting_configtext('logstore_database/dbcollation', get_string('databasecollation', 'logstore_database'), '', '')); $settings->add(new admin_setting_configcheckbox('logstore_database/dbhandlesoptions', get_string('databasehandlesoptions', 'logstore_database'), get_string('databasehandlesoptions_help', 'logstore_database'), '0')); $settings->add(new admin_setting_configtext('logstore_database/buffersize', get_string('buffersize', 'logstore_database'), get_string('buffersize_help', 'logstore_database'), 50)); $settings->add(new admin_setting_configcheckbox('logstore_database/jsonformat', new lang_string('jsonformat', 'logstore_database'), new lang_string('jsonformat_desc', 'logstore_database'), 1)); // Filters. $settings->add(new admin_setting_heading('filters', get_string('filters', 'logstore_database'), get_string('filters_help', 'logstore_database'))); $settings->add(new admin_setting_configcheckbox('logstore_database/logguests', get_string('logguests', 'logstore_database'), '', '0')); $levels = \logstore_database\helper::get_level_options(); $settings->add(new admin_setting_configmulticheckbox('logstore_database/includelevels', get_string('includelevels', 'logstore_database'), '', $levels, $levels)); $actions = \logstore_database\helper::get_action_options(); $settings->add(new admin_setting_configmulticheckbox('logstore_database/includeactions', get_string('includeactions', 'logstore_database'), '', $actions, $actions)); } database/upgrade.txt 0000644 00000001271 15151765343 0010513 0 ustar 00 This files describes API changes in the logstore_database code. === 3.4 === * PostgreSQL connections now use advanced options to reduce connection overhead. These options are not compatible with some connection poolers. The dbhandlesoptions parameter has been added to allow the database to configure the required defaults. The parameters that are required in the database are; ALTER DATABASE moodle SET client_encoding = UTF8; ALTER DATABASE moodle SET standard_conforming_strings = on; ALTER DATABASE moodle SET search_path = 'moodle,public'; -- Optional, if you wish to use a custom schema. You can set these options against the database or the moodle user who connects. database/version.php 0000644 00000002225 15151765343 0010521 0 ustar 00 <?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/>. /** * External database log store. * * @package logstore_database * @copyright 2013 Petr Skoda {@link http://skodak.org} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2022112800; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2022111800; // Requires this Moodle version. $plugin->component = 'logstore_database'; // Full name of the plugin (used for diagnostics). database/classes/privacy/provider.php 0000644 00000012245 15151765343 0014003 0 ustar 00 <?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/>. /** * Data provider. * * @package logstore_database * @copyright 2018 Frédéric Massart * @author Frédéric Massart <fred@branchup.tech> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace logstore_database\privacy; defined('MOODLE_INTERNAL') || die(); use context; use core_privacy\local\metadata\collection; use core_privacy\local\request\contextlist; /** * Data provider class. * * @package logstore_database * @copyright 2018 Frédéric Massart * @author Frédéric Massart <fred@branchup.tech> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\provider, \tool_log\local\privacy\logstore_provider, \tool_log\local\privacy\logstore_userlist_provider { use \tool_log\local\privacy\moodle_database_export_and_delete; /** * Returns metadata. * * @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_external_location_link('log', [ 'eventname' => 'privacy:metadata:log:eventname', 'userid' => 'privacy:metadata:log:userid', 'relateduserid' => 'privacy:metadata:log:relateduserid', 'anonymous' => 'privacy:metadata:log:anonymous', 'other' => 'privacy:metadata:log:other', 'timecreated' => 'privacy:metadata:log:timecreated', 'origin' => 'privacy:metadata:log:origin', 'ip' => 'privacy:metadata:log:ip', 'realuserid' => 'privacy:metadata:log:realuserid', ], 'privacy:metadata:log'); return $collection; } /** * Add contexts that contain user information for the specified user. * * @param contextlist $contextlist The contextlist to add the contexts to. * @param int $userid The user to find the contexts for. * @return void */ public static function add_contexts_for_userid(contextlist $contextlist, $userid) { list($db, $table) = static::get_database_and_table(); if (!$db || !$table) { return; } $sql = 'userid = :userid1 OR relateduserid = :userid2 OR realuserid = :userid3'; $params = ['userid1' => $userid, 'userid2' => $userid, 'userid3' => $userid]; $contextids = $db->get_fieldset_select($table, 'DISTINCT contextid', $sql, $params); if (empty($contextids)) { return; } $sql = implode(' UNION ', array_map(function($id) use ($db) { return 'SELECT ' . $id . $db->sql_null_from_clause(); }, $contextids)); $contextlist->add_from_sql($sql, []); } /** * Add user IDs that contain user information for the specified context. * * @param \core_privacy\local\request\userlist $userlist The userlist to add the users to. * @return void */ public static function add_userids_for_context(\core_privacy\local\request\userlist $userlist) { list($db, $table) = static::get_database_and_table(); if (!$db || !$table) { return; } $userids = []; $records = $db->get_records($table, ['contextid' => $userlist->get_context()->id], '', 'id, userid, relateduserid, realuserid'); if (empty($records)) { return; } foreach ($records as $record) { $userids[] = $record->userid; if (!empty($record->relateduserid)) { $userids[] = $record->relateduserid; } if (!empty($record->realuserid)) { $userids[] = $record->realuserid; } } $userids = array_unique($userids); $userlist->add_users($userids); } /** * Get the database object. * * @return array Containing moodle_database, string, or null values. */ protected static function get_database_and_table() { $manager = get_log_manager(); $store = new \logstore_database\log\store($manager); $db = $store->get_extdb(); return $db ? [$db, $store->get_config_value('dbtable')] : [null, null]; } /** * Get the path to export the logs to. * * @return array */ protected static function get_export_subcontext() { return [get_string('privacy:path:logs', 'tool_log'), get_string('pluginname', 'logstore_database')]; } } database/classes/log/store.php 0000644 00000024607 15151765343 0012416 0 ustar 00 <?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/>. /** * External database store. * * @package logstore_database * @copyright 2013 Petr Skoda {@link http://skodak.org} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace logstore_database\log; defined('MOODLE_INTERNAL') || die(); class store implements \tool_log\log\writer, \core\log\sql_reader { use \tool_log\helper\store, \tool_log\helper\reader, \tool_log\helper\buffered_writer { dispose as helper_dispose; } /** @var \moodle_database $extdb */ protected $extdb; /** @var bool $logguests true if logging guest access */ protected $logguests; /** @var array $includelevels An array of education levels to include */ protected $includelevels = array(); /** @var array $includeactions An array of actions types to include */ protected $includeactions = array(); /** * Construct * * @param \tool_log\log\manager $manager */ public function __construct(\tool_log\log\manager $manager) { $this->helper_setup($manager); $this->buffersize = $this->get_config('buffersize', 50); $this->logguests = $this->get_config('logguests', 1); $actions = $this->get_config('includeactions', ''); $levels = $this->get_config('includelevels', ''); $this->includeactions = $actions === '' ? array() : explode(',', $actions); $this->includelevels = $levels === '' ? array() : explode(',', $levels); // JSON writing defaults to false (table format compatibility with older versions). // Note: This variable is defined in the buffered_writer trait. $this->jsonformat = (bool)$this->get_config('jsonformat', false); } /** * Setup the Database. * * @return bool */ protected function init() { if (isset($this->extdb)) { return !empty($this->extdb); } $dbdriver = $this->get_config('dbdriver'); if (empty($dbdriver)) { $this->extdb = false; return false; } list($dblibrary, $dbtype) = explode('/', $dbdriver); if (!$db = \moodle_database::get_driver_instance($dbtype, $dblibrary, true)) { debugging("Unknown driver $dblibrary/$dbtype", DEBUG_DEVELOPER); $this->extdb = false; return false; } $dboptions = array(); $dboptions['dbpersist'] = $this->get_config('dbpersist', '0'); $dboptions['dbsocket'] = $this->get_config('dbsocket', ''); $dboptions['dbport'] = $this->get_config('dbport', ''); $dboptions['dbschema'] = $this->get_config('dbschema', ''); $dboptions['dbcollation'] = $this->get_config('dbcollation', ''); $dboptions['dbhandlesoptions'] = $this->get_config('dbhandlesoptions', false); try { $db->connect($this->get_config('dbhost'), $this->get_config('dbuser'), $this->get_config('dbpass'), $this->get_config('dbname'), false, $dboptions); $tables = $db->get_tables(); if (!in_array($this->get_config('dbtable'), $tables)) { debugging('Cannot find the specified table', DEBUG_DEVELOPER); $this->extdb = false; return false; } } catch (\moodle_exception $e) { debugging('Cannot connect to external database: ' . $e->getMessage(), DEBUG_DEVELOPER); $this->extdb = false; return false; } $this->extdb = $db; return true; } /** * Should the event be ignored (== not logged)? * @param \core\event\base $event * @return bool */ protected function is_event_ignored(\core\event\base $event) { if (!in_array($event->crud, $this->includeactions) && !in_array($event->edulevel, $this->includelevels) ) { // Ignore event if the store settings do not want to store it. return true; } if ((!CLI_SCRIPT or PHPUNIT_TEST) and !$this->logguests) { // Always log inside CLI scripts because we do not login there. if (!isloggedin() or isguestuser()) { return true; } } return false; } /** * Insert events in bulk to the database. * * @param array $evententries raw event data */ protected function insert_event_entries($evententries) { if (!$this->init()) { return; } if (!$dbtable = $this->get_config('dbtable')) { return; } try { $this->extdb->insert_records($dbtable, $evententries); } catch (\moodle_exception $e) { debugging('Cannot write to external database: ' . $e->getMessage(), DEBUG_DEVELOPER); } } /** * Get an array of events based on the passed on params. * * @param string $selectwhere select conditions. * @param array $params params. * @param string $sort sortorder. * @param int $limitfrom limit constraints. * @param int $limitnum limit constraints. * * @return array|\core\event\base[] array of events. */ public function get_events_select($selectwhere, array $params, $sort, $limitfrom, $limitnum) { if (!$this->init()) { return array(); } if (!$dbtable = $this->get_config('dbtable')) { return array(); } $sort = self::tweak_sort_by_id($sort); $events = array(); $records = $this->extdb->get_records_select($dbtable, $selectwhere, $params, $sort, '*', $limitfrom, $limitnum); foreach ($records as $data) { if ($event = $this->get_log_event($data)) { $events[$data->id] = $event; } } return $events; } /** * Fetch records using given criteria returning a Traversable object. * * Note that the traversable object contains a moodle_recordset, so * remember that is important that you call close() once you finish * using it. * * @param string $selectwhere * @param array $params * @param string $sort * @param int $limitfrom * @param int $limitnum * @return \core\dml\recordset_walk|\core\event\base[] */ public function get_events_select_iterator($selectwhere, array $params, $sort, $limitfrom, $limitnum) { if (!$this->init()) { return array(); } if (!$dbtable = $this->get_config('dbtable')) { return array(); } $sort = self::tweak_sort_by_id($sort); $recordset = $this->extdb->get_recordset_select($dbtable, $selectwhere, $params, $sort, '*', $limitfrom, $limitnum); return new \core\dml\recordset_walk($recordset, array($this, 'get_log_event')); } /** * Returns an event from the log data. * * @param stdClass $data Log data * @return \core\event\base */ public function get_log_event($data) { $extra = array('origin' => $data->origin, 'ip' => $data->ip, 'realuserid' => $data->realuserid); $data = (array)$data; $id = $data['id']; $data['other'] = self::decode_other($data['other']); if ($data['other'] === false) { $data['other'] = array(); } unset($data['origin']); unset($data['ip']); unset($data['realuserid']); unset($data['id']); if (!$event = \core\event\base::restore($data, $extra)) { return null; } return $event; } /** * Get number of events present for the given select clause. * * @param string $selectwhere select conditions. * @param array $params params. * * @return int Number of events available for the given conditions */ public function get_events_select_count($selectwhere, array $params) { if (!$this->init()) { return 0; } if (!$dbtable = $this->get_config('dbtable')) { return 0; } return $this->extdb->count_records_select($dbtable, $selectwhere, $params); } /** * Get whether events are present for the given select clause. * * @param string $selectwhere select conditions. * @param array $params params. * * @return bool Whether events available for the given conditions */ public function get_events_select_exists(string $selectwhere, array $params): bool { if (!$this->init()) { return false; } if (!$dbtable = $this->get_config('dbtable')) { return false; } return $this->extdb->record_exists_select($dbtable, $selectwhere, $params); } /** * Get a config value for the store. * * @param string $name Config name * @param mixed $default default value * @return mixed config value if set, else the default value. */ public function get_config_value($name, $default = null) { return $this->get_config($name, $default); } /** * Get the external database object. * * @return \moodle_database $extdb */ public function get_extdb() { if (!$this->init()) { return false; } return $this->extdb; } /** * Are the new events appearing in the reader? * * @return bool true means new log events are being added, false means no new data will be added */ public function is_logging() { if (!$this->init()) { return false; } return true; } /** * Dispose off database connection after pushing any buffered events to the database. */ public function dispose() { $this->helper_dispose(); if ($this->extdb) { $this->extdb->dispose(); } $this->extdb = null; } } database/classes/helper.php 0000644 00000005263 15151765343 0011755 0 ustar 00 <?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/>. /** * Helper class locally used. * * @package logstore_database * @copyright 2014 onwards Ankit Agarwal * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace logstore_database; defined('MOODLE_INTERNAL') || die(); /** * Helper class locally used. * * @copyright 2014 onwards Ankit Agarwal * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class helper { /** * Returns list of fully working database drivers present in system. * @return array */ public static function get_drivers() { return array( '' => get_string('choosedots'), 'native/mysqli' => \moodle_database::get_driver_instance('mysqli', 'native')->get_name(), 'native/mariadb' => \moodle_database::get_driver_instance('mariadb', 'native')->get_name(), 'native/pgsql' => \moodle_database::get_driver_instance('pgsql', 'native')->get_name(), 'native/oci' => \moodle_database::get_driver_instance('oci', 'native')->get_name(), 'native/sqlsrv' => \moodle_database::get_driver_instance('sqlsrv', 'native')->get_name() ); } /** * Get a list of edu levels. * * @return array */ public static function get_level_options() { return array( \core\event\base::LEVEL_TEACHING => get_string('teaching', 'logstore_database'), \core\event\base::LEVEL_PARTICIPATING => get_string('participating', 'logstore_database'), \core\event\base::LEVEL_OTHER => get_string('other', 'logstore_database'), ); } /** * Get a list of database actions. * * @return array */ public static function get_action_options() { return array( 'c' => get_string('create', 'logstore_database'), 'r' => get_string('read', 'logstore_database'), 'u' => get_string('update', 'logstore_database'), 'd' => get_string('delete') ); } } database/lang/en/logstore_database.php 0000644 00000007046 15151765343 0014047 0 ustar 00 <?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/>. /** * Log store lang strings. * * @package logstore_database * @copyright 2013 Petr Skoda {@link http://skodak.org} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['buffersize'] = 'Buffer size'; $string['buffersize_help'] = 'Number of log entries inserted in one batch database operation, which improves performance.'; $string['conectexception'] = 'Cannot connect to the database.'; $string['create'] = 'Create'; $string['databasesettings'] = 'Database settings'; $string['databasesettings_help'] = 'Connection details for the external log database: {$a}'; $string['databasepersist'] = 'Persistent database connections'; $string['databaseschema'] = 'Database schema'; $string['databasecollation'] = 'Database collation'; $string['databasehandlesoptions'] = 'Database handles options'; $string['databasehandlesoptions_help'] = 'Does the remote database handle its own options.'; $string['databasetable'] = 'Database table'; $string['databasetable_help'] = 'Name of the table where logs will be stored. This table should have a structure identical to the one used by logstore_standard (mdl_logstore_standard_log).'; $string['includeactions'] = 'Include actions of these types'; $string['includelevels'] = 'Include actions with these educational levels'; $string['filters'] = 'Filter logs'; $string['filters_help'] = 'Enable filters that exclude some actions from being logged.'; $string['jsonformat'] = 'JSON format'; $string['jsonformat_desc'] = 'Use standard JSON format instead of PHP serialised data in the \'other\' database field.'; $string['logguests'] = 'Log guest actions'; $string['other'] = 'Other'; $string['participating'] = 'Participating'; $string['pluginname'] = 'External database log'; $string['pluginname_desc'] = 'A log plugin that stores log entries in an external database table.'; $string['privacy:metadata:log'] = 'A collection of past events'; $string['privacy:metadata:log:anonymous'] = 'Whether the event was flagged as anonymous'; $string['privacy:metadata:log:eventname'] = 'The event name'; $string['privacy:metadata:log:ip'] = 'The IP address used at the time of the event'; $string['privacy:metadata:log:origin'] = 'The origin of the event'; $string['privacy:metadata:log:other'] = 'Additional information about the event'; $string['privacy:metadata:log:realuserid'] = 'The ID of the real user behind the event, when masquerading a user.'; $string['privacy:metadata:log:relateduserid'] = 'The ID of a user related to this event'; $string['privacy:metadata:log:timecreated'] = 'The time when the event occurred'; $string['privacy:metadata:log:userid'] = 'The ID of the user who triggered this event'; $string['read'] = 'Read'; $string['tablenotfound'] = 'Specified table was not found'; $string['teaching'] = 'Teaching'; $string['testsettings'] = 'Test connection'; $string['testingsettings'] = 'Testing database settings...'; $string['update'] = 'Update';
| ver. 1.4 |
Github
|
.
| PHP 7.4.33 | ���֧ߧ֧�ѧ�ڧ� ����ѧߧڧ��: 0 |
proxy
|
phpinfo
|
���ѧ����ۧܧ�