sql_reader and sql_internal_table_reader.
namespace logstore_database\log;
defined('MOODLE_INTERNAL') || die();
-class store implements \tool_log\log\writer, \core\log\sql_select_reader {
+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 {
$records = $this->extdb->get_records_select($dbtable, $selectwhere, $params, $sort, '*', $limitfrom, $limitnum);
foreach ($records as $data) {
- $extra = array('origin' => $data->origin, 'realuserid' => $data->realuserid, 'ip' => $data->ip);
- $data = (array)$data;
- $id = $data['id'];
- $data['other'] = unserialize($data['other']);
- if ($data['other'] === false) {
- $data['other'] = array();
- }
- unset($data['origin']);
- unset($data['ip']);
- unset($data['realuserid']);
- unset($data['id']);
-
- $event = \core\event\base::restore($data, $extra);
- // Add event to list if it's valid.
- if ($event) {
- $events[$id] = $event;
+ 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'] = unserialize($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.
*
defined('MOODLE_INTERNAL') || die();
-class store implements \tool_log\log\store, \core\log\sql_select_reader {
+class store implements \tool_log\log\store, \core\log\sql_reader {
use \tool_log\helper\store,
\tool_log\helper\reader;
const CRUD_REGEX = "/(crud).*?(<>|=|!=).*?'(.*?)'/s";
/**
- * This method contains mapping required for Moodle core to make legacy store compatible with other sql_select_reader based
+ * This method contains mapping required for Moodle core to make legacy store compatible with other sql_reader based
* queries.
*
* @param string $selectwhere Select statment
$events = array();
foreach ($records as $data) {
- $events[$data->id] = \logstore_legacy\event\legacy_logged::restore_legacy($data);
+ $events[$data->id] = $this->get_log_event($data);
}
$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 \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.
+ *
+ * @param stdClass $data Log data
+ * @return \core\event\base
+ */
+ public function get_log_event($data) {
+ return \logstore_legacy\event\legacy_logged::restore_legacy($data);
+ }
+
public function get_events_select_count($selectwhere, array $params) {
global $DB;
$this->assertEquals(array('logstore_legacy'), array_keys($stores));
$store = $stores['logstore_legacy'];
$this->assertInstanceOf('logstore_legacy\log\store', $store);
- $this->assertInstanceOf('core\log\sql_select_reader', $store);
+ $this->assertInstanceOf('core\log\sql_reader', $store);
$this->assertTrue($store->is_logging());
$logs = $DB->get_records('log', array(), 'id ASC');
defined('MOODLE_INTERNAL') || die();
-class store implements \tool_log\log\writer, \core\log\sql_internal_reader {
+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;
$records = $DB->get_recordset_select('logstore_standard_log', $selectwhere, $params, $sort, '*', $limitfrom, $limitnum);
foreach ($records as $data) {
- $extra = array('origin' => $data->origin, 'ip' => $data->ip, 'realuserid' => $data->realuserid);
- $data = (array)$data;
- $id = $data['id'];
- $data['other'] = unserialize($data['other']);
- if ($data['other'] === false) {
- $data['other'] = array();
- }
- unset($data['origin']);
- unset($data['ip']);
- unset($data['realuserid']);
- unset($data['id']);
-
- $event = \core\event\base::restore($data, $extra);
- // Add event to list if it's valid.
- if ($event) {
- $events[$id] = $event;
+ 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) {
+ 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'] = unserialize($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;
+ }
+
public function get_events_select_count($selectwhere, array $params) {
global $DB;
return $DB->count_records_select('logstore_standard_log', $selectwhere, $params);
$this->assertContains($expectedreport, $reports);
}
}
+
+ /**
+ * Test sql_reader::get_events_select_iterator.
+ * @return void
+ */
+ public function test_events_traversable() {
+ global $DB;
+
+ $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);
+ $delta1 = memory_get_usage() - $mem;
+ $events = $store->get_events_select_iterator('', array(), '', 0, 0);
+ $delta2 = memory_get_usage() - $mem;
+ $this->assertInstanceOf('\Traversable', $events);
+ $events->close();
+
+ $this->assertLessThan($delta1 / 10, $delta2);
+
+ set_config('enabled_stores', '', 'tool_log');
+ get_log_manager(true);
+ }
+
}
*/
public $sql = NULL;
/**
- * @var array Data fetched from the db.
+ * @var array|\Traversable Data fetched from the db.
*/
public $rawdata = NULL;
* processing each col using either col_{columnname} method or other_cols
* method or if other_cols returns NULL then put the data straight into the
* table.
+ *
+ * @return void
*/
function build_table() {
- if ($this->rawdata) {
- foreach ($this->rawdata as $row) {
- $formattedrow = $this->format_row($row);
- $this->add_data_keyed($formattedrow,
- $this->get_row_class($row));
- }
+
+ if ($this->rawdata instanceof \Traversable && !$this->rawdata->valid()) {
+ return;
+ }
+ if (!$this->rawdata) {
+ return;
+ }
+
+ foreach ($this->rawdata as $row) {
+ $formattedrow = $this->format_row($row);
+ $this->add_data_keyed($formattedrow,
+ $this->get_row_class($row));
+ }
+
+ if ($this->rawdata instanceof \core\dml\recordset_walk ||
+ $this->rawdata instanceof moodle_recordset) {
+ $this->rawdata->close();
}
}
/** @var array list of user fullnames shown in report */
private $userfullnames = array();
- /** @var array list of course short names shown in report */
+ /**
+ * @deprecated since Moodle 2.9 MDL-48595 - please do not use this argument any more.
+ * @todo MDL-49291 This will be deleted in 3.1
+ * @var array list of course short names shown in report.
+ */
private $courseshortnames = array();
/** @var array list of context name shown in report */
* - int userid: user id
* - int|string modid: Module id or "site_errors" to view site errors
* - int groupid: Group id
- * - \core\log\sql_select_reader logreader: reader from which data will be fetched.
+ * - \core\log\sql_reader logreader: reader from which data will be fetched.
* - int edulevel: educational level.
* - string action: view action
* - int date: Date from which logs to be viewed.
/**
* Generate the course column.
*
+ * @deprecated since Moodle 2.9 MDL-48595 - please do not use this function any more.
+ * @todo MDL-49291 This will be deleted in 3.1
* @param stdClass $event event data.
* @return string HTML for the course column.
*/
public function col_course($event) {
+
+ debugging('col_course() is deprecated, there is no such column', DEBUG_DEVELOPER);
+
if (empty($event->courseid) || empty($this->courseshortnames[$event->courseid])) {
return '-';
} else {
}
}
+ /**
+ * Gets the user full name.
+ *
+ * This function is useful because, in the unlikely case that the user is
+ * not already loaded in $this->userfullnames it will fetch it from db.
+ *
+ * @since Moodle 2.9
+ * @param int $userid
+ * @return string|false
+ */
+ protected function get_user_fullname($userid) {
+ global $DB;
+
+ if (!empty($this->userfullnames[$userid])) {
+ return $this->userfullnames[$userid];
+ }
+
+ // We already looked for the user and it does not exist.
+ if ($this->userfullnames[$userid] === false) {
+ return false;
+ }
+
+ // If we reach that point new users logs have been generated since the last users db query.
+ list($usql, $uparams) = $DB->get_in_or_equal($userid);
+ $sql = "SELECT id," . get_all_user_name_fields(true) . " FROM {user} WHERE id " . $usql;
+ if (!$user = $DB->get_records_sql($sql, $uparams)) {
+ return false;
+ }
+
+ $this->userfullnames[$userid] = fullname($user);
+ return $this->userfullnames[$userid];
+ }
+
/**
* Generate the time column.
*
// Add username who did the action.
if (!empty($logextra['realuserid'])) {
$a = new stdClass();
- $params = array('id' => $logextra['realuserid']);
- if ($event->courseid) {
- $params['course'] = $event->courseid;
+ if (!$a->realusername = $this->get_user_fullname($logextra['realuserid'])) {
+ $a->realusername = '-';
+ }
+ if (!$a->asusername = $this->get_user_fullname($event->userid)) {
+ $a->asusername = '-';
}
- $a->realusername = $this->userfullnames[$logextra['realuserid']];
- $a->asusername = $this->userfullnames[$event->userid];
if (empty($this->download)) {
+ $params = array('id' => $logextra['realuserid']);
+ if ($event->courseid) {
+ $params['course'] = $event->courseid;
+ }
$a->realusername = html_writer::link(new moodle_url('/user/view.php', $params), $a->realusername);
$params['id'] = $event->userid;
$a->asusername = html_writer::link(new moodle_url('/user/view.php', $params), $a->asusername);
}
$username = get_string('eventloggedas', 'report_log', $a);
- } else if (!empty($event->userid) && !empty($this->userfullnames[$event->userid])) {
- $params = array('id' => $event->userid);
- if ($event->courseid) {
- $params['course'] = $event->courseid;
- }
- $username = $this->userfullnames[$event->userid];
+
+ } else if (!empty($event->userid) && $username = $this->get_user_fullname($event->userid)) {
if (empty($this->download)) {
+ $params = array('id' => $event->userid);
+ if ($event->courseid) {
+ $params['course'] = $event->courseid;
+ }
$username = html_writer::link(new moodle_url('/user/view.php', $params), $username);
}
} else {
*/
public function col_relatedfullnameuser($event) {
// Add affected user.
- if (!empty($event->relateduserid) && isset($this->userfullnames[$event->relateduserid])) {
- $params = array('id' => $event->relateduserid);
- if ($event->courseid) {
- $params['course'] = $event->courseid;
- }
- $username = $this->userfullnames[$event->relateduserid];
+ if (!empty($event->relateduserid) && $username = $this->get_user_fullname($event->relateduserid)) {
if (empty($this->download)) {
+ $params = array('id' => $event->relateduserid);
+ if ($event->courseid) {
+ $params['course'] = $event->courseid;
+ }
$username = html_writer::link(new moodle_url('/user/view.php', $params), $username);
}
} else {
$this->pageable(false);
}
- $this->rawdata = $this->filterparams->logreader->get_events_select($selector, $params, $this->filterparams->orderby,
- $this->get_page_start(), $this->get_page_size());
+ // Get the users and course data.
+ $this->rawdata = $this->filterparams->logreader->get_events_select_iterator($selector, $params,
+ $this->filterparams->orderby, $this->get_page_start(), $this->get_page_size());
+
+ // Update list of users which will be displayed on log page.
+ $this->update_users_used();
+
+ // Get the events. Same query than before; even if it is not likely, logs from new users
+ // may be added since last query so we will need to work around later to prevent problems.
+ // In almost most of the cases this will be better than having two opened recordsets.
+ $this->rawdata = $this->filterparams->logreader->get_events_select_iterator($selector, $params,
+ $this->filterparams->orderby, $this->get_page_start(), $this->get_page_size());
// Set initial bars.
if ($useinitialsbar && !$this->is_downloading()) {
$this->initialbars($total > $pagesize);
}
- // Update list of users and courses list which will be displayed on log page.
- $this->update_users_and_courses_used();
}
/**
* Helper function to create list of course shortname and user fullname shown in log report.
+ *
* This will update $this->userfullnames and $this->courseshortnames array with userfullname and courseshortname (with link),
* which will be used to render logs in table.
+ *
+ * @deprecated since Moodle 2.9 MDL-48595 - please do not use this function any more.
+ * @todo MDL-49291 This will be deleted in 3.1
+ * @see self::update_users_used()
*/
public function update_users_and_courses_used() {
global $SITE, $DB;
+ debugging('update_users_and_courses_used() is deprecated, please use update_users_used() instead.', DEBUG_DEVELOPER);
+
+ // We should not call self::update_users_used() as would have to iterate twice around the list of logs.
+
$this->userfullnames = array();
$this->courseshortnames = array($SITE->id => $SITE->shortname);
$userids = array();
// Get list of userids and courseids which will be shown in log report.
foreach ($this->rawdata as $event) {
$logextra = $event->get_logextra();
- if (!empty($event->userid) && !in_array($event->userid, $userids)) {
- $userids[] = $event->userid;
+ if (!empty($event->userid) && empty($userids[$event->userid])) {
+ $userids[$event->userid] = $event->userid;
}
- if (!empty($logextra['realuserid']) && !in_array($logextra['realuserid'], $userids)) {
- $userids[] = $logextra['realuserid'];
+ if (!empty($logextra['realuserid']) && empty($userids[$logextra['realuserid']])) {
+ $userids[$logextra['realuserid']] = $logextra['realuserid'];
}
- if (!empty($event->relateduserid) && !in_array($event->relateduserid, $userids)) {
- $userids[] = $event->relateduserid;
+ if (!empty($event->relateduserid) && empty($userids[$event->relateduserid])) {
+ $userids[$event->relateduserid] = $event->relateduserid;
}
if (!empty($event->courseid) && ($event->courseid != $SITE->id) && !in_array($event->courseid, $courseids)) {
}
}
+ // Closing it just in case, we can not rewind moodle recordsets anyway.
+ if ($this->rawdata instanceof \core\dml\recordset_walk ||
+ $this->rawdata instanceof moodle_recordset) {
+ $this->rawdata->close();
+ }
+
// Get user fullname and put that in return list.
if (!empty($userids)) {
list($usql, $uparams) = $DB->get_in_or_equal($userids);
$uparams);
foreach ($users as $userid => $user) {
$this->userfullnames[$userid] = fullname($user);
+ unset($userids[$userid]);
+ }
+
+ // We fill the array with false values for the users that don't exist anymore
+ // in the database so we don't need to query the db again later.
+ foreach ($userids as $userid) {
+ $this->userfullnames[$userid] = false;
}
}
}
}
}
+
+ /**
+ * Helper function to create list of user fullnames shown in log report.
+ *
+ * This will update $this->userfullnames array with userfullname,
+ * which will be used to render logs in table.
+ *
+ * @since Moodle 2.9
+ * @return void
+ */
+ protected function update_users_used() {
+ global $DB;
+
+ $this->userfullnames = array();
+ $userids = array();
+
+ // For each event cache full username.
+ // Get list of userids which will be shown in log report.
+ foreach ($this->rawdata as $event) {
+ $logextra = $event->get_logextra();
+ if (!empty($event->userid) && empty($userids[$event->userid])) {
+ $userids[$event->userid] = $event->userid;
+ }
+ if (!empty($logextra['realuserid']) && empty($userids[$logextra['realuserid']])) {
+ $userids[$logextra['realuserid']] = $logextra['realuserid'];
+ }
+ if (!empty($event->relateduserid) && empty($userids[$event->relateduserid])) {
+ $userids[$event->relateduserid] = $event->relateduserid;
+ }
+ }
+ $this->rawdata->close();
+
+ // Get user fullname and put that in return list.
+ if (!empty($userids)) {
+ list($usql, $uparams) = $DB->get_in_or_equal($userids);
+ $users = $DB->get_records_sql("SELECT id," . get_all_user_name_fields(true) . " FROM {user} WHERE id " . $usql,
+ $uparams);
+ foreach ($users as $userid => $user) {
+ $this->userfullnames[$userid] = fullname($user);
+ unset($userids[$userid]);
+ }
+
+ // We fill the array with false values for the users that don't exist anymore
+ // in the database so we don't need to query the db again later.
+ foreach ($userids as $userid) {
+ $this->userfullnames[$userid] = false;
+ }
+ }
+ }
}