Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.x is supported too.
   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/>.
  16  
  17  namespace core_admin\reportbuilder\local\entities;
  18  
  19  use core_reportbuilder\local\filters\date;
  20  use core_reportbuilder\local\filters\duration;
  21  use core_reportbuilder\local\filters\number;
  22  use core_reportbuilder\local\filters\select;
  23  use core_reportbuilder\local\filters\text;
  24  use core_reportbuilder\local\filters\autocomplete;
  25  use core_reportbuilder\local\helpers\format;
  26  use lang_string;
  27  use core_reportbuilder\local\entities\base;
  28  use core_reportbuilder\local\report\column;
  29  use core_reportbuilder\local\report\filter;
  30  use stdClass;
  31  use core_collator;
  32  
  33  /**
  34   * Task log entity class implementation
  35   *
  36   * @package    core_admin
  37   * @copyright  2021 David Matamoros <davidmc@moodle.com>
  38   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  39   */
  40  class task_log extends base {
  41  
  42      /** @var int Result success */
  43      protected const SUCCESS = 0;
  44  
  45      /** @var int Result failed */
  46      protected const FAILED = 1;
  47  
  48      /**
  49       * Database tables that this entity uses and their default aliases
  50       *
  51       * @return array
  52       */
  53      protected function get_default_table_aliases(): array {
  54          return ['task_log' => 'tl'];
  55      }
  56  
  57      /**
  58       * The default title for this entity in the list of columns/conditions/filters in the report builder
  59       *
  60       * @return lang_string
  61       */
  62      protected function get_default_entity_title(): lang_string {
  63          return new lang_string('entitytasklog', 'admin');
  64      }
  65  
  66      /**
  67       * Initialise the entity
  68       *
  69       * @return base
  70       */
  71      public function initialise(): base {
  72          $columns = $this->get_all_columns();
  73          foreach ($columns as $column) {
  74              $this->add_column($column);
  75          }
  76  
  77          // All the filters defined by the entity can also be used as conditions.
  78          $filters = $this->get_all_filters();
  79          foreach ($filters as $filter) {
  80              $this
  81                  ->add_filter($filter)
  82                  ->add_condition($filter);
  83          }
  84  
  85          return $this;
  86      }
  87  
  88      /**
  89       * Returns list of all available columns
  90       *
  91       * @return column[]
  92       */
  93      protected function get_all_columns(): array {
  94          global $DB;
  95  
  96          $tablealias = $this->get_table_alias('task_log');
  97  
  98          // Name column.
  99          $columns[] = (new column(
 100              'name',
 101              new lang_string('name'),
 102              $this->get_entity_name()
 103          ))
 104              ->add_joins($this->get_joins())
 105              ->set_type(column::TYPE_TEXT)
 106              ->add_field("$tablealias.classname")
 107              ->set_is_sortable(true)
 108              ->add_callback(static function(string $classname): string {
 109                  $output = '';
 110                  if (class_exists($classname)) {
 111                      $task = new $classname;
 112                      if ($task instanceof \core\task\task_base) {
 113                          $output = $task->get_name();
 114                      }
 115                  }
 116                  $output .= \html_writer::tag('div', "\\{$classname}", [
 117                      'class' => 'small text-muted',
 118                  ]);
 119                  return $output;
 120              });
 121  
 122          // Component column.
 123          $columns[] = (new column(
 124              'component',
 125              new lang_string('plugin'),
 126              $this->get_entity_name()
 127          ))
 128              ->add_joins($this->get_joins())
 129              ->set_type(column::TYPE_TEXT)
 130              ->add_field("{$tablealias}.component")
 131              ->set_is_sortable(true);
 132  
 133          // Type column.
 134          $columns[] = (new column(
 135              'type',
 136              new lang_string('tasktype', 'admin'),
 137              $this->get_entity_name()
 138          ))
 139              ->add_joins($this->get_joins())
 140              ->set_type(column::TYPE_TEXT)
 141              ->add_field("{$tablealias}.type")
 142              ->set_is_sortable(true)
 143              ->add_callback(static function($value): string {
 144                  if (\core\task\database_logger::TYPE_SCHEDULED === (int) $value) {
 145                      return get_string('task_type:scheduled', 'admin');
 146                  }
 147                  return get_string('task_type:adhoc', 'admin');
 148              });
 149  
 150          // Start time column.
 151          $columns[] = (new column(
 152              'starttime',
 153              new lang_string('task_starttime', 'admin'),
 154              $this->get_entity_name()
 155          ))
 156              ->add_joins($this->get_joins())
 157              ->set_type(column::TYPE_TIMESTAMP)
 158              ->add_field("{$tablealias}.timestart")
 159              ->set_is_sortable(true)
 160              ->add_callback([format::class, 'userdate'], get_string('strftimedatetimeshortaccurate', 'core_langconfig'));
 161  
 162          // End time column.
 163          $columns[] = (new column(
 164              'endtime',
 165              new lang_string('task_endtime', 'admin'),
 166              $this->get_entity_name()
 167          ))
 168              ->add_joins($this->get_joins())
 169              ->set_type(column::TYPE_TIMESTAMP)
 170              ->add_field("{$tablealias}.timeend")
 171              ->set_is_sortable(true)
 172              ->add_callback([format::class, 'userdate'], get_string('strftimedatetimeshortaccurate', 'core_langconfig'));
 173  
 174          // Duration column.
 175          $columns[] = (new column(
 176              'duration',
 177              new lang_string('task_duration', 'admin'),
 178              $this->get_entity_name()
 179          ))
 180              ->add_joins($this->get_joins())
 181              ->set_type(column::TYPE_FLOAT)
 182              ->add_field("{$tablealias}.timeend - {$tablealias}.timestart", 'duration')
 183              ->set_is_sortable(true)
 184              ->add_callback(static function(float $value): string {
 185                  $duration = round($value, 2);
 186                  if (empty($duration)) {
 187                      // The format_time function returns 'now' when the difference is exactly 0.
 188                      // Note: format_time performs concatenation in exactly this fashion so we should do this for consistency.
 189                      return '0 ' . get_string('secs', 'moodle');
 190                  }
 191                  return format_time($duration);
 192              });
 193  
 194          // Hostname column.
 195          $columns[] = (new column(
 196              'hostname',
 197              new lang_string('hostname', 'admin'),
 198              $this->get_entity_name()
 199          ))
 200              ->add_joins($this->get_joins())
 201              ->set_type(column::TYPE_TEXT)
 202              ->add_field("$tablealias.hostname")
 203              ->set_is_sortable(true);
 204  
 205          // PID column.
 206          $columns[] = (new column(
 207              'pid',
 208              new lang_string('pid', 'admin'),
 209              $this->get_entity_name()
 210          ))
 211              ->add_joins($this->get_joins())
 212              ->set_type(column::TYPE_INTEGER)
 213              ->add_field("{$tablealias}.pid")
 214              ->set_is_sortable(true)
 215              // Although this is an integer column, it doesn't make sense to perform numeric aggregation on it.
 216              ->set_disabled_aggregation(['avg', 'count', 'countdistinct', 'max', 'min', 'sum']);
 217  
 218          // Database column.
 219          $columns[] = (new column(
 220              'database',
 221              new lang_string('task_dbstats', 'admin'),
 222              $this->get_entity_name()
 223          ))
 224              ->add_joins($this->get_joins())
 225              ->set_type(column::TYPE_INTEGER)
 226              ->add_fields("{$tablealias}.dbreads, {$tablealias}.dbwrites")
 227              ->set_is_sortable(true, ["{$tablealias}.dbreads", "{$tablealias}.dbwrites"])
 228              ->add_callback(static function(int $value, stdClass $row): string {
 229                  $output = '';
 230                  $output .= \html_writer::div(get_string('task_stats:dbreads', 'admin', $row->dbreads));
 231                  $output .= \html_writer::div(get_string('task_stats:dbwrites', 'admin', $row->dbwrites));
 232                  return $output;
 233              })
 234              // Although this is an integer column, it doesn't make sense to perform numeric aggregation on it.
 235              ->set_disabled_aggregation(['avg', 'count', 'countdistinct', 'max', 'min', 'sum']);
 236  
 237          // Database reads column.
 238          $columns[] = (new column(
 239              'dbreads',
 240              new lang_string('task_dbreads', 'admin'),
 241              $this->get_entity_name()
 242          ))
 243              ->add_joins($this->get_joins())
 244              ->set_type(column::TYPE_INTEGER)
 245              ->add_fields("{$tablealias}.dbreads")
 246              ->set_is_sortable(true);
 247  
 248          // Database writes column.
 249          $columns[] = (new column(
 250              'dbwrites',
 251              new lang_string('task_dbwrites', 'admin'),
 252              $this->get_entity_name()
 253          ))
 254              ->add_joins($this->get_joins())
 255              ->set_type(column::TYPE_INTEGER)
 256              ->add_fields("{$tablealias}.dbwrites")
 257              ->set_is_sortable(true);
 258  
 259          // Result column.
 260          $columns[] = (new column(
 261              'result',
 262              new lang_string('task_result', 'admin'),
 263              $this->get_entity_name()
 264          ))
 265              ->add_joins($this->get_joins())
 266              ->set_type(column::TYPE_BOOLEAN)
 267              // For accurate aggregation, we need to return boolean success = true by xor'ing the field value.
 268              ->add_field($DB->sql_bitxor("{$tablealias}.result", 1), 'success')
 269              ->set_is_sortable(true)
 270              ->add_callback(static function(bool $success): string {
 271                  if (!$success) {
 272                      return get_string('task_result:failed', 'admin');
 273                  }
 274                  return get_string('success');
 275              });
 276  
 277          return $columns;
 278      }
 279  
 280      /**
 281       * Return list of all available filters
 282       *
 283       * @return filter[]
 284       */
 285      protected function get_all_filters(): array {
 286          global $DB;
 287  
 288          $tablealias = $this->get_table_alias('task_log');
 289  
 290          // Name filter (Filter by classname).
 291          $filters[] = (new filter(
 292              autocomplete::class,
 293              'name',
 294              new lang_string('classname', 'tool_task'),
 295              $this->get_entity_name(),
 296              "{$tablealias}.classname"
 297          ))
 298              ->add_joins($this->get_joins())
 299              ->set_options_callback(static function(): array {
 300                  global $DB;
 301                  $classnames = $DB->get_fieldset_sql('SELECT DISTINCT classname FROM {task_log} ORDER BY classname ASC');
 302  
 303                  $options = [];
 304                  foreach ($classnames as $classname) {
 305                      if (class_exists($classname)) {
 306                          $task = new $classname;
 307                          $options[$classname] = $task->get_name();
 308                      }
 309                  }
 310  
 311                  core_collator::asort($options);
 312                  return $options;
 313              });
 314  
 315          // Component filter.
 316          $filters[] = (new filter(
 317              text::class,
 318              'component',
 319              new lang_string('plugin'),
 320              $this->get_entity_name(),
 321              "{$tablealias}.component"
 322          ))
 323              ->add_joins($this->get_joins());
 324  
 325          // Type filter.
 326          $filters[] = (new filter(
 327              select::class,
 328              'type',
 329              new lang_string('tasktype', 'admin'),
 330              $this->get_entity_name(),
 331              "{$tablealias}.type"
 332          ))
 333              ->add_joins($this->get_joins())
 334              ->set_options([
 335                  \core\task\database_logger::TYPE_ADHOC => new lang_string('task_type:adhoc', 'admin'),
 336                  \core\task\database_logger::TYPE_SCHEDULED => new lang_string('task_type:scheduled', 'admin'),
 337              ]);
 338  
 339          // Output filter (Filter by task output).
 340          $filters[] = (new filter(
 341              text::class,
 342              'output',
 343              new lang_string('task_logoutput', 'admin'),
 344              $this->get_entity_name(),
 345              $DB->sql_cast_to_char("{$tablealias}.output")
 346          ))
 347              ->add_joins($this->get_joins());
 348  
 349          // Start time filter.
 350          $filters[] = (new filter(
 351              date::class,
 352              'timestart',
 353              new lang_string('task_starttime', 'admin'),
 354              $this->get_entity_name(),
 355              "{$tablealias}.timestart"
 356          ))
 357              ->add_joins($this->get_joins())
 358              ->set_limited_operators([
 359                  date::DATE_ANY,
 360                  date::DATE_RANGE,
 361                  date::DATE_PREVIOUS,
 362                  date::DATE_CURRENT,
 363              ]);
 364  
 365          // End time.
 366          $filters[] = (new filter(
 367              date::class,
 368              'timeend',
 369              new lang_string('task_endtime', 'admin'),
 370              $this->get_entity_name(),
 371              "{$tablealias}.timeend"
 372          ))
 373              ->add_joins($this->get_joins())
 374              ->set_limited_operators([
 375                  date::DATE_ANY,
 376                  date::DATE_RANGE,
 377                  date::DATE_PREVIOUS,
 378                  date::DATE_CURRENT,
 379              ]);
 380  
 381          // Duration filter.
 382          $filters[] = (new filter(
 383              duration::class,
 384              'duration',
 385              new lang_string('task_duration', 'admin'),
 386              $this->get_entity_name(),
 387              "{$tablealias}.timeend - {$tablealias}.timestart"
 388          ))
 389              ->add_joins($this->get_joins());
 390  
 391          // Database reads.
 392          $filters[] = (new filter(
 393              number::class,
 394              'dbreads',
 395              new lang_string('task_dbreads', 'admin'),
 396              $this->get_entity_name(),
 397              "{$tablealias}.dbreads"
 398          ))
 399              ->add_joins($this->get_joins());
 400  
 401          // Database writes.
 402          $filters[] = (new filter(
 403              number::class,
 404              'dbwrites',
 405              new lang_string('task_dbwrites', 'admin'),
 406              $this->get_entity_name(),
 407              "{$tablealias}.dbwrites"
 408          ))
 409              ->add_joins($this->get_joins());
 410  
 411          // Result filter.
 412          $filters[] = (new filter(
 413              select::class,
 414              'result',
 415              new lang_string('task_result', 'admin'),
 416              $this->get_entity_name(),
 417              "{$tablealias}.result"
 418          ))
 419              ->add_joins($this->get_joins())
 420              ->set_options([
 421                  self::SUCCESS => get_string('success'),
 422                  self::FAILED => get_string('task_result:failed', 'admin'),
 423              ]);
 424  
 425          return $filters;
 426      }
 427  }