MDL-67274 Tasks: Log display fails with memory errors
[moodle.git] / admin / classes / task_log_table.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /**
18  * Task log table.
19  *
20  * @package    core_admin
21  * @copyright  2018 Andrew Nicols <andrew@nicols.co.uk>
22  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23  */
25 namespace core_admin;
27 defined('MOODLE_INTERNAL') || die();
29 require_once($CFG->libdir . '/tablelib.php');
31 /**
32  * Table to display list of task logs.
33  *
34  * @copyright  2018 Andrew Nicols <andrew@nicols.co.uk>
35  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
36  */
37 class task_log_table extends \table_sql {
39     /**
40      * Constructor for the task_log table.
41      *
42      * @param   string      $filter
43      * @param   int         $resultfilter
44      */
45     public function __construct(string $filter = '', int $resultfilter = null) {
46         global $DB;
48         if (-1 === $resultfilter) {
49             $resultfilter = null;
50         }
52         parent::__construct('tasklogs');
54         $columnheaders = [
55             'classname'  => get_string('name'),
56             'type'       => get_string('tasktype', 'admin'),
57             'userid'     => get_string('user', 'admin'),
58             'timestart'  => get_string('task_starttime', 'admin'),
59             'duration'   => get_string('task_duration', 'admin'),
60             'db'         => get_string('task_dbstats', 'admin'),
61             'result'     => get_string('task_result', 'admin'),
62             'actions'    => '',
63         ];
64         $this->define_columns(array_keys($columnheaders));
65         $this->define_headers(array_values($columnheaders));
67         // The name column is a header.
68         $this->define_header_column('classname');
70         // This table is not collapsible.
71         $this->collapsible(false);
73         // The actions class should not wrap. Use the BS text utility class.
74         $this->column_class('actions', 'text-nowrap');
76         // Allow pagination.
77         $this->pageable(true);
79         // Allow sorting. Default to sort by timestarted DESC.
80         $this->sortable(true, 'timestart', SORT_DESC);
82         // Add filtering.
83         $where = [];
84         $params = [];
85         if (!empty($filter)) {
86             $orwhere = [];
87             $filter = str_replace('\\', '\\\\', $filter);
89             // Check the class name.
90             $orwhere[] = $DB->sql_like('classname', ':classfilter', false, false);
91             $params['classfilter'] = '%' . $DB->sql_like_escape($filter) . '%';
93             $orwhere[] = $DB->sql_like('output', ':outputfilter', false, false);
94             $params['outputfilter'] = '%' . $DB->sql_like_escape($filter) . '%';
96             $where[] = "(" . implode(' OR ', $orwhere) . ")";
97         }
99         if (null !== $resultfilter) {
100             $where[] = 'tl.result = :result';
101             $params['result'] = $resultfilter;
102         }
104         $where = implode(' AND ', $where);
106         $this->set_sql('', '', $where, $params);
107     }
109     /**
110      * Query the db. Store results in the table object for use by build_table.
111      *
112      * @param int $pagesize size of page for paginated displayed table.
113      * @param bool $useinitialsbar do you want to use the initials bar. Bar
114      * will only be used if there is a fullname column defined for the table.
115      */
116     public function query_db($pagesize, $useinitialsbar = true) {
117         global $DB;
119         // Fetch the attempts.
120         $sort = $this->get_sql_sort();
121         if ($sort) {
122             $sort = "ORDER BY $sort";
123         }
125         $extrafields = get_extra_user_fields(\context_system::instance());
126         $userfields = \user_picture::fields('u', $extrafields, 'userid2', 'user');
128         $where = '';
129         if (!empty($this->sql->where)) {
130             $where = "WHERE {$this->sql->where}";
131         }
133         $sql = "SELECT
134                     tl.id, tl.type, tl.component, tl.classname, tl.userid, tl.timestart, tl.timeend,
135                     tl.dbreads, tl.dbwrites, tl.result,
136                     tl.dbreads + tl.dbwrites AS db,
137                     tl.timeend - tl.timestart AS duration,
138                     {$userfields}
139                 FROM {task_log} tl
140            LEFT JOIN {user} u ON u.id = tl.userid
141                 {$where}
142                 {$sort}";
144         $this->pagesize($pagesize, $DB->count_records_sql("SELECT COUNT('x') FROM {task_log} tl {$where}", $this->sql->params));
145         if (!$this->is_downloading()) {
146             $this->rawdata = $DB->get_records_sql($sql, $this->sql->params, $this->get_page_start(), $this->get_page_size());
147         } else {
148             $this->rawdata = $DB->get_records_sql($sql, $this->sql->params);
149         }
150     }
152     /**
153      * Format the name cell.
154      *
155      * @param   \stdClass $row
156      * @return  string
157      */
158     public function col_classname($row) : string {
159         $output = '';
160         if (class_exists($row->classname)) {
161             $task = new $row->classname;
162             if ($task instanceof \core\task\scheduled_task) {
163                 $output = $task->get_name();
164             }
165         }
167         $output .= \html_writer::tag('div', "\\{$row->classname}", [
168                 'class' => 'task-class',
169             ]);
170         return $output;
171     }
173     /**
174      * Format the type cell.
175      *
176      * @param   \stdClass $row
177      * @return  string
178      */
179     public function col_type($row) : string {
180         if (\core\task\database_logger::TYPE_SCHEDULED == $row->type) {
181             return get_string('task_type:scheduled', 'admin');
182         } else {
183             return get_string('task_type:adhoc', 'admin');
184         }
185     }
187     /**
188      * Format the timestart cell.
189      *
190      * @param   \stdClass $row
191      * @return  string
192      */
193     public function col_result($row) : string {
194         if ($row->result) {
195             return get_string('task_result:failed', 'admin');
196         } else {
197             return get_string('success');
198         }
199     }
201     /**
202      * Format the timestart cell.
203      *
204      * @param   \stdClass $row
205      * @return  string
206      */
207     public function col_timestart($row) : string {
208         return userdate($row->timestart, get_string('strftimedatetimeshort', 'langconfig'));
209     }
211     /**
212      * Format the duration cell.
213      *
214      * @param   \stdClass $row
215      * @return  string
216      */
217     public function col_duration($row) : string {
218         $duration = round($row->timeend - $row->timestart, 2);
220         if (empty($duration)) {
221             // The format_time function returns 'now' when the difference is exactly 0.
222             // Note: format_time performs concatenation in exactly this fashion so we should do this for consistency.
223             return '0 ' . get_string('secs', 'moodle');
224         }
226         return format_time($duration);
227     }
229     /**
230      * Format the DB details cell.
231      *
232      * @param   \stdClass $row
233      * @return  string
234      */
235     public function col_db($row) : string {
236         $output = '';
238         $output .= \html_writer::div(get_string('task_stats:dbreads', 'admin', $row->dbreads));
239         $output .= \html_writer::div(get_string('task_stats:dbwrites', 'admin', $row->dbwrites));
241         return $output;
242     }
244     /**
245      * Format the actions cell.
246      *
247      * @param   \stdClass $row
248      * @return  string
249      */
250     public function col_actions($row) : string {
251         global $OUTPUT;
253         $actions = [];
255         $url = new \moodle_url('/admin/tasklogs.php', ['logid' => $row->id]);
257         // Quick view.
258         $actions[] = $OUTPUT->action_icon(
259             $url,
260             new \pix_icon('e/search', get_string('view')),
261             new \popup_action('click', $url)
262         );
264         // Download.
265         $actions[] = $OUTPUT->action_icon(
266             new \moodle_url($url, ['download' => true]),
267             new \pix_icon('t/download', get_string('download'))
268         );
270         return implode('&nbsp;', $actions);
271     }
273     /**
274      * Format the user cell.
275      *
276      * @param   \stdClass $row
277      * @return  string
278      */
279     public function col_userid($row) : string {
280         if (empty($row->userid)) {
281             return '';
282         }
284         $user = (object) [];
285         username_load_fields_from_object($user, $row, 'user');
287         return fullname($user);
288     }