Merge branch 'MDL-42892-master_42890' of git://github.com/ankitagarwal/moodle

This commit is contained in:
Marina Glancy 2014-03-18 18:00:42 +08:00
commit efd727063f
5 changed files with 286 additions and 29 deletions

View File

@ -35,26 +35,59 @@ class store implements \tool_log\log\store, \core\log\sql_select_reader {
}
/** @var array list of db fields which needs to be replaced for legacy log query */
protected $standardtolegacyfields = array(
protected static $standardtolegacyfields = array(
'timecreated' => 'time',
'courseid' => 'course',
'contextinstanceid' => 'cmid',
'origin' => 'ip'
);
public function get_events_select($selectwhere, array $params, $sort, $limitfrom, $limitnum) {
global $DB;
/** @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_select_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 ($this->standardtolegacyfields as $from => $to) {
foreach (self::$standardtolegacyfields as $from => $to) {
$selectwhere = str_replace($from, $to, $selectwhere);
$sort = str_replace($from, $to, $sort);
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);
}
public function get_events_select($selectwhere, array $params, $sort, $limitfrom, $limitnum) {
global $DB;
// Replace the query with hardcoded mappings required for core.
list($selectwhere, $params, $sort) = self::replace_sql_legacy($selectwhere, $params, $sort);
$events = array();
$records = array();
@ -74,14 +107,8 @@ class store implements \tool_log\log\store, \core\log\sql_select_reader {
public function get_events_select_count($selectwhere, array $params) {
global $DB;
// Replace db field names to make it compatible with legacy log.
foreach ($this->standardtolegacyfields as $from => $to) {
$selectwhere = str_replace($from, $to, $selectwhere);
if (isset($params[$from])) {
$params[$to] = $params[$from];
unset($params[$from]);
}
}
// 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);
@ -198,4 +225,81 @@ class store implements \tool_log\log\store, \core\log\sql_select_reader {
}
}
}
/**
* 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;
}
}

View File

@ -0,0 +1,43 @@
<?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);
}
}

View File

@ -25,6 +25,7 @@
defined('MOODLE_INTERNAL') || die();
require_once(__DIR__ . '/fixtures/event.php');
require_once(__DIR__ . '/fixtures/store.php');
class logstore_legacy_store_testcase extends advanced_testcase {
public function test_log_writing() {
@ -160,4 +161,87 @@ class logstore_legacy_store_testcase extends advanced_testcase {
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);
// 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);
}
}

View File

@ -153,14 +153,9 @@ abstract class backup_cron_automated_helper {
// If config backup_auto_skip_modif_days is set to true, skip courses
// that have not been modified since the number of days defined.
if ($shouldrunnow && !$skipped && $lastbackupwassuccessful && $config->backup_auto_skip_modif_days) {
$sqlwhere = "course=:courseid AND time>:time AND ".$DB->sql_like('action', ':action', false, true, true);
$timenotmodifsincedays = $now - ($config->backup_auto_skip_modif_days * DAYSECS);
// Check log if there were any modifications to the course content.
$params = array('courseid' => $course->id,
'time' => $timenotmodifsincedays,
'action' => '%view%');
$logexists = $DB->record_exists_select('log', $sqlwhere, $params);
$logexists = self::is_course_modified($course->id, $timenotmodifsincedays);
$skipped = ($course->timemodified <= $timenotmodifsincedays && !$logexists);
$skippedmessage = 'Not modified in the past '.$config->backup_auto_skip_modif_days.' days';
}
@ -169,12 +164,7 @@ abstract class backup_cron_automated_helper {
// that have not been modified since previous backup.
if ($shouldrunnow && !$skipped && $lastbackupwassuccessful && $config->backup_auto_skip_modif_prev) {
// Check log if there were any modifications to the course content.
$sqlwhere = "course=:courseid AND time>:time AND ".$DB->sql_like('action', ':action', false, true, true);
$params = array('courseid' => $course->id,
'time' => $backupcourse->laststarttime,
'action' => '%view%');
$logexists = $DB->record_exists_select('log', $sqlwhere, $params);
$logexists = self::is_course_modified($course->id, $backupcourse->laststarttime);
$skipped = ($course->timemodified <= $backupcourse->laststarttime && !$logexists);
$skippedmessage = 'Not modified since previous backup';
}
@ -668,4 +658,26 @@ abstract class backup_cron_automated_helper {
return true;
}
/**
* Check logs to find out if a course was modified since the given time.
*
* @param int $courseid course id to check
* @param int $since timestamp, from which to check
*
* @return bool true if the course was modified, false otherwise. This also returns false if no readers are enabled. This is
* intentional, since we cannot reliably determine if any modification was made or not.
*/
protected static function is_course_modified($courseid, $since) {
$logmang = get_log_manager();
$readers = $logmang->get_readers('core\log\sql_select_reader');
$where = "courseid = :courseid and timecreated > :since and crud <> 'r'";
$params = array('courseid' => $courseid, 'since' => $since);
foreach ($readers as $reader) {
if ($reader->get_events_select_count($where, $params)) {
return true;
}
}
return false;
}
}

View File

@ -2353,7 +2353,7 @@ function course_format_ajax_support($format) {
* @return boolean
*/
function can_delete_course($courseid) {
global $USER, $DB;
global $USER;
$context = context_course::instance($courseid);
@ -2367,11 +2367,25 @@ function can_delete_course($courseid) {
}
$since = time() - 60*60*24;
$course = get_course($courseid);
$params = array('userid'=>$USER->id, 'url'=>"view.php?id=$courseid", 'since'=>$since);
$select = "module = 'course' AND action = 'new' AND userid = :userid AND url = :url AND time > :since";
if ($course->timecreated < $since) {
return false; // Return if the course was not created in last 24 hours.
}
return $DB->record_exists_select('log', $select, $params);
$logmanger = get_log_manager();
$readers = $logmanger->get_readers('\core\log\sql_select_reader');
$reader = reset($readers);
if (empty($reader)) {
return false; // No log reader found.
}
// A proper reader.
$select = "userid = :userid AND courseid = :courseid AND eventname = :eventname AND timecreated > :since";
$params = array('userid' => $USER->id, 'since' => $since, 'courseid' => $course->id, 'eventname' => '\core\event\course_created');
return (bool)$reader->get_events_select_count($select, $params);
}
/**