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.

Differences Between: [Versions 310 and 402] [Versions 311 and 402] [Versions 39 and 402] [Versions 400 and 402] [Versions 401 and 402]

   1  <?php
   2  
   3  // This file is part of Moodle - http://moodle.org/
   4  //
   5  // Moodle is free software: you can redistribute it and/or modify
   6  // it under the terms of the GNU General Public License as published by
   7  // the Free Software Foundation, either version 3 of the License, or
   8  // (at your option) any later version.
   9  //
  10  // Moodle is distributed in the hope that it will be useful,
  11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13  // GNU General Public License for more details.
  14  //
  15  // You should have received a copy of the GNU General Public License
  16  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  17  
  18  /**
  19   * Output rendering for the plugin.
  20   *
  21   * @package     tool_task
  22   * @copyright   2014 Damyon Wiese
  23   * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  
  26  defined('MOODLE_INTERNAL') || die();
  27  
  28  use core\task\scheduled_task;
  29  
  30  
  31  /**
  32   * Implements the plugin renderer
  33   *
  34   * @copyright 2014 Damyon Wiese
  35   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  36   */
  37  class tool_task_renderer extends plugin_renderer_base {
  38  
  39      /**
  40       * This function will render a table with the summary of all adhoc tasks.
  41       *
  42       * @param array $summary
  43       * @return string HTML to output.
  44       */
  45      public function adhoc_tasks_summary_table(array $summary): string {
  46          $adhocurl = '/admin/tool/task/adhoctasks.php';
  47          $adhocrunurl = '/admin/tool/task/run_adhoctasks.php';
  48  
  49          // Main tasks table.
  50          $table = new html_table();
  51          $table->caption = get_string('adhoctasks', 'tool_task');
  52          $table->head = [
  53              get_string('component', 'tool_task') . ' / ' . get_string('classname', 'tool_task'),
  54              get_string('adhoctasksrunning', 'tool_task'),
  55              get_string('adhoctasksdue', 'tool_task'),
  56              get_string('adhoctasksfuture', 'tool_task'),
  57              get_string('adhoctasksfailed', 'tool_task'),
  58              get_string('nextruntime', 'tool_task'),
  59          ];
  60  
  61          $table->attributes['class'] = 'admintable generaltable';
  62          $table->colclasses = [];
  63  
  64          // For each task entry (row) show action buttons/logs link depending on the user permissions.
  65          $data = [];
  66          $canruntasks = \core\task\manager::is_runnable() && get_config('tool_task', 'enablerunnow');
  67          foreach ($summary as $component => $classes) {
  68              // Component cell.
  69              $componentcell = new html_table_cell($component);
  70              $componentcell->header = true;
  71              $componentcell->id = "tasks-$component";
  72              $componentcell->colspan = 6;
  73  
  74              $data[] = new html_table_row([$componentcell]);
  75  
  76              foreach ($classes as $classname => $stats) {
  77                  // Task class cell.
  78                  $classbits = explode('\\',  $classname);
  79                  $classcontent = html_writer::link(
  80                      new moodle_url($adhocurl, ['classname' => $classname]),
  81                      end($classbits)
  82                  );
  83                  $classcell = new html_table_cell($classcontent);
  84                  $classcell->header = true;
  85                  $classcell->attributes['class'] = "task-class-summary text-ltr";
  86  
  87                  $duecontent = $stats['due'];
  88                  if ($canruntasks && ($stats['due'] > 0 || $stats['failed'] > 0)) {
  89                      $duecontent .= html_writer::div(
  90                          html_writer::link(
  91                              new moodle_url(
  92                                  $adhocrunurl,
  93                                  ['classname' => $classname]
  94                              ),
  95                              get_string('runclassname', 'tool_task')
  96                          ),
  97                          'task-runnow'
  98                      );
  99                  }
 100  
 101                  // Mark cell if has failed tasks.
 102                  $failed = $stats['failed'];
 103                  if ($canruntasks && $failed > 0) {
 104                      $failed .= html_writer::div(
 105                          html_writer::link(
 106                              new moodle_url(
 107                                  $adhocrunurl,
 108                                  ['classname' => $classname, 'failedonly' => 1]
 109                              ),
 110                              get_string('runclassnamefailedonly', 'tool_task')
 111                          ),
 112                          'task-runnow'
 113                      );
 114                  }
 115                  $failedcell = new html_table_cell($failed);
 116                  if ($failed > 0) {
 117                      $failedcell->attributes['class'] = 'table-danger';
 118                  }
 119  
 120                  // Prepares the next run time cell contents.
 121                  $nextrun = '';
 122                  if ($stats['due'] > 0) {
 123                      $nextrun = get_string('asap', 'tool_task');
 124                  } else if ($stats['nextruntime']) {
 125                      $nextrun = userdate($stats['nextruntime']);
 126                  }
 127  
 128                  $data[] = new html_table_row([
 129                      $classcell,
 130                      new html_table_cell($stats['running']),
 131                      new html_table_cell($duecontent),
 132                      new html_table_cell($stats['count'] - $stats['running'] - $stats['due']),
 133                      $failedcell,
 134                      new html_table_cell($nextrun),
 135                  ]);
 136              }
 137          }
 138          $table->data = $data;
 139          return html_writer::table($table);
 140      }
 141  
 142      /**
 143       * This function will render a table with all the adhoc tasks for the class.
 144       *
 145       * @param string $classname
 146       * @param array $tasks - list of all adhoc tasks.
 147       * @param array|null $params
 148       * @return string HTML to output.
 149       */
 150      public function adhoc_tasks_class_table(string $classname, array $tasks, ?array $params = []): string {
 151          $adhocurl = '/admin/tool/task/adhoctasks.php';
 152          $adhocrunurl = '/admin/tool/task/run_adhoctasks.php';
 153          $showloglink = \core\task\logmanager::has_log_report();
 154          $failedonly = !empty($params['failedonly']);
 155          $canruntasks = \core\task\manager::is_runnable() && get_config('tool_task', 'enablerunnow');
 156  
 157          // Depending on the currently set parameters, set up toggle buttons.
 158          $failedorall = html_writer::link(
 159              new moodle_url(
 160                  $adhocurl,
 161                  array_merge($params, ['classname' => $classname, 'failedonly' => !$failedonly])
 162              ),
 163              get_string($failedonly ? 'showall' : 'showfailedonly', 'tool_task')
 164          );
 165  
 166          // Main tasks table.
 167          $table = $this->generate_adhoc_tasks_simple_table($tasks, $canruntasks);
 168  
 169          $table->caption = s($classname) . " "
 170              . get_string($failedonly ? 'adhoctasksfailed' : 'adhoctasks', 'tool_task');
 171          $table->head[3] .= " $failedorall"; // Spice up faildelay heading.
 172  
 173          if ($showloglink) {
 174              // Insert logs as the second col.
 175              array_splice($table->head, 1, 0, [get_string('logs')]);
 176              array_walk($table->data, function ($row, $idx) use ($classname) {
 177                  $loglink = '';
 178                  $faildelaycell = $row->cells[3];
 179                  if ($faildelaycell->attributes['class'] == 'table-danger') {
 180                      // Failed task.
 181                      $loglink = $this->output->action_icon(
 182                          \core\task\logmanager::get_url_for_task_class($classname),
 183                          new pix_icon('e/file-text', get_string('viewlogs', 'tool_task', $classname)
 184                      ));
 185                  }
 186  
 187                  array_splice($row->cells, 1, 0, [new html_table_cell($loglink)]);
 188              });
 189          }
 190  
 191          return html_writer::table($table)
 192              . html_writer::div(
 193                  html_writer::link(
 194                      new moodle_url(
 195                          $adhocrunurl,
 196                          array_merge($params, ['classname' => $classname])
 197                      ),
 198                      get_string('runclassname', 'tool_task')
 199                  ),
 200                  'task-runnow'
 201              )
 202              . html_writer::div(
 203                  html_writer::link(
 204                      new moodle_url(
 205                          $adhocurl
 206                      ),
 207                      get_string('showsummary', 'tool_task')
 208                  ),
 209                  'task-show-summary'
 210              );
 211      }
 212  
 213      /**
 214       * This function will render a plain adhoc tasks table.
 215       *
 216       * @param array $tasks - list of adhoc tasks.
 217       * @return string HTML to output.
 218       */
 219      public function adhoc_tasks_simple_table(array $tasks): string {
 220          $table = $this->generate_adhoc_tasks_simple_table($tasks);
 221  
 222          return html_writer::table($table);
 223      }
 224  
 225      /**
 226       * This function will render a plain adhoc tasks table.
 227       *
 228       * @param array $tasks - list of adhoc tasks.
 229       * @param bool $wantruntasks add 'Run now' link
 230       * @return html_table
 231       */
 232      private function generate_adhoc_tasks_simple_table(array $tasks, bool $wantruntasks = false): html_table {
 233          $adhocrunurl = '/admin/tool/task/run_adhoctasks.php';
 234          $now = time();
 235          $failedstr = get_string('failed', 'tool_task');
 236  
 237          // Main tasks table.
 238          $table = new html_table();
 239          $table->caption = get_string('adhoctasks', 'tool_task');
 240          $table->head = [
 241              get_string('taskid', 'tool_task'),
 242              get_string('nextruntime', 'tool_task'),
 243              get_string('payload', 'tool_task'),
 244              $failedstr
 245          ];
 246  
 247          $table->attributes['class'] = 'generaltable';
 248          $table->colclasses = [];
 249  
 250          // For each task entry (row) show action buttons/logs link depending on the user permissions.
 251          $data = [];
 252          foreach ($tasks as $task) {
 253              $taskid = $task->get_id();
 254              $started = $task->get_timestarted();
 255  
 256              // Task id cell.
 257              $taskidcellcontent = html_writer::span($taskid, 'task-id');
 258              $taskidcell = new html_table_cell($taskidcellcontent);
 259              $taskidcell->header = true;
 260              $taskidcell->id = "task-$taskid";
 261  
 262              // Mark cell if task has failed.
 263              $faildelay = $task->get_fail_delay();
 264              $faildelaycell = new html_table_cell($faildelay ? $failedstr : '');
 265              if ($faildelay) {
 266                  $faildelaycell->attributes['class'] = 'table-danger';
 267              }
 268  
 269              // Prepares the next run time cell contents.
 270              $nextrun = get_string('started', 'tool_task');
 271              if (!$started) {
 272                  $nextruntime = $task->get_next_run_time();
 273                  $due = $nextruntime < $now;
 274                  $nextrun = $due ? userdate($nextruntime) : get_string('asap', 'tool_task');
 275  
 276                  if ($wantruntasks && ($faildelay || $due)) {
 277                      $nextrun .= ' '.html_writer::div(
 278                          html_writer::link(
 279                              new moodle_url(
 280                                  $adhocrunurl,
 281                                  ['id' => $taskid]
 282                              ),
 283                              get_string('runnow', 'tool_task')
 284                          ),
 285                          'task-runnow'
 286                      );
 287                  }
 288              }
 289  
 290              $data[] = new html_table_row([
 291                  $taskidcell,
 292                  new html_table_cell($nextrun),
 293                  new html_table_cell($task->get_custom_data_as_string()),
 294                  $faildelaycell,
 295              ]);
 296          }
 297          $table->data = $data;
 298  
 299          return $table;
 300      }
 301  
 302      /**
 303       * Displays a notification on ad hoc task run request.
 304       *
 305       * @return string HTML notification block for task initiated message
 306       */
 307      public function adhoc_task_run(): string {
 308          return $this->output->notification(get_string('adhoctaskrun', 'tool_task'), 'info');
 309      }
 310  
 311      /**
 312       * This function will render one beautiful table with all the scheduled tasks.
 313       *
 314       * @param \core\task\scheduled_task[] $tasks - list of all scheduled tasks.
 315       * @param string $lastchanged (optional) the last task edited. Gets highlighted in teh table.
 316       * @return string HTML to output.
 317       */
 318      public function scheduled_tasks_table($tasks, $lastchanged = '') {
 319          global $CFG;
 320  
 321          $showloglink = \core\task\logmanager::has_log_report();
 322  
 323          $table = new html_table();
 324          $table->caption = get_string('scheduledtasks', 'tool_task');
 325          $table->head = [
 326              get_string('name'),
 327              get_string('component', 'tool_task'),
 328              get_string('edit'),
 329              get_string('logs'),
 330              get_string('lastruntime', 'tool_task'),
 331              get_string('nextruntime', 'tool_task'),
 332              get_string('taskscheduleminute', 'tool_task'),
 333              get_string('taskschedulehour', 'tool_task'),
 334              get_string('taskscheduleday', 'tool_task'),
 335              get_string('taskscheduledayofweek', 'tool_task'),
 336              get_string('taskschedulemonth', 'tool_task'),
 337              get_string('faildelay', 'tool_task'),
 338              get_string('default', 'tool_task'),
 339          ];
 340  
 341          $table->attributes['class'] = 'admintable generaltable';
 342          $table->colclasses = [];
 343  
 344          if (!$showloglink) {
 345              // Hide the log links.
 346              $table->colclasses['3'] = 'hidden';
 347          }
 348  
 349          $data = [];
 350          $yes = get_string('yes');
 351          $no = get_string('no');
 352          $canruntasks = \core\task\manager::is_runnable() && get_config('tool_task', 'enablerunnow');
 353          foreach ($tasks as $task) {
 354              $classname = get_class($task);
 355              $defaulttask = \core\task\manager::get_default_scheduled_task($classname, false);
 356  
 357              $customised = $task->is_customised() ? $no : $yes;
 358              if (empty($CFG->preventscheduledtaskchanges) && !$task->is_overridden()) {
 359                  $configureurl = new moodle_url('/admin/tool/task/scheduledtasks.php',
 360                          ['action' => 'edit', 'task' => $classname]);
 361                  $editlink = $this->output->action_icon($configureurl, new pix_icon('t/edit',
 362                          get_string('edittaskschedule', 'tool_task', $task->get_name())));
 363              } else {
 364                  $editlink = $this->render(new pix_icon('t/locked',
 365                          get_string('scheduledtaskchangesdisabled', 'tool_task')));
 366              }
 367  
 368              $loglink = '';
 369              if ($showloglink) {
 370                  $loglink = $this->output->action_icon(
 371                      \core\task\logmanager::get_url_for_task_class($classname),
 372                      new pix_icon('e/file-text', get_string('viewlogs', 'tool_task', $task->get_name())
 373                  ));
 374              }
 375  
 376              $namecellcontent = $task->get_name() . "\n" .
 377                  html_writer::span('\\' . $classname, 'task-class text-ltr');
 378              if ($task->is_overridden()) {
 379                  // Let the user know the scheduled task is defined in config.
 380                  $namecellcontent .= "\n" . html_writer::div(get_string('configoverride', 'admin'), 'alert-info');
 381              }
 382              $namecell = new html_table_cell($namecellcontent);
 383              $namecell->header = true;
 384              $namecell->id = scheduled_task::get_html_id($classname);
 385  
 386              $runnow = '';
 387              $canrunthistask = $canruntasks && $task->can_run();
 388              if ($canrunthistask) {
 389                  $runnow = html_writer::div(html_writer::link(
 390                          new moodle_url('/admin/tool/task/schedule_task.php',
 391                              ['task' => $classname]),
 392                          get_string('runnow', 'tool_task')), 'task-runnow');
 393              }
 394  
 395              $faildelaycell = new html_table_cell($task->get_fail_delay());
 396              if ($task->get_fail_delay()) {
 397                  $faildelaycell->text .= html_writer::div(
 398                      $this->output->single_button(
 399                          new moodle_url('/admin/tool/task/clear_fail_delay.php',
 400                                  ['task' => $classname]),
 401                          get_string('clear')
 402                      ),
 403                      'task-runnow'
 404                  );
 405                  $faildelaycell->attributes['class'] = 'table-danger';
 406              }
 407  
 408              $row = new html_table_row([
 409                          $namecell,
 410                          new html_table_cell($this->component_name($task->get_component())),
 411                          new html_table_cell($editlink),
 412                          new html_table_cell($loglink),
 413                          new html_table_cell($this->last_run_time($task) . $runnow),
 414                          new html_table_cell($this->next_run_time($task)),
 415                          $this->time_cell($task->get_minute(), $defaulttask->get_minute()),
 416                          $this->time_cell($task->get_hour(), $defaulttask->get_hour()),
 417                          $this->time_cell($task->get_day(), $defaulttask->get_day()),
 418                          $this->time_cell($task->get_day_of_week(), $defaulttask->get_day_of_week()),
 419                          $this->time_cell($task->get_month(), $defaulttask->get_month()),
 420                          $faildelaycell,
 421                          new html_table_cell($customised)]);
 422  
 423              $classes = [];
 424              if (!$task->is_enabled()) {
 425                  $classes[] = 'disabled';
 426              }
 427              if (get_class($task) == $lastchanged) {
 428                  $classes[] = 'table-primary';
 429              }
 430              $row->attributes['class'] = implode(' ', $classes);
 431              $data[] = $row;
 432          }
 433          $table->data = $data;
 434          if ($lastchanged) {
 435              // IE does not support this, and the ancient version of Firefox we use for Behat
 436              // has the method, but then errors on 'centre'. So, just try to scroll, and if it fails, don't care.
 437              $this->page->requires->js_init_code(
 438                      'try{document.querySelector("tr.table-primary").scrollIntoView({block: "center"});}catch(e){}');
 439          }
 440          return html_writer::table($table);
 441      }
 442  
 443      /**
 444       * Nicely display the name of a component, with its disabled status and internal name.
 445       *
 446       * @param string $component component name, e.g. 'core' or 'mod_forum'.
 447       * @return string HTML.
 448       */
 449      public function component_name(string $component): string {
 450          list($type) = core_component::normalize_component($component);
 451          if ($type === 'core') {
 452              return get_string('corecomponent', 'tool_task');
 453          }
 454  
 455          $plugininfo = core_plugin_manager::instance()->get_plugin_info($component);
 456          if (!$plugininfo) {
 457              return $component;
 458          }
 459  
 460          $plugininfo->init_display_name();
 461  
 462          $componentname = $plugininfo->displayname;
 463          if ($plugininfo->is_enabled() === false) {
 464              $componentname .= ' ' . html_writer::span(
 465                              get_string('disabled', 'tool_task'), 'badge badge-secondary');
 466          }
 467          $componentname .= "\n" . html_writer::span($plugininfo->component, 'task-class text-ltr');
 468  
 469          return $componentname;
 470      }
 471  
 472      /**
 473       * Standard display of a tasks last run time.
 474       *
 475       * @param scheduled_task $task
 476       * @return string HTML.
 477       */
 478      public function last_run_time(scheduled_task $task): string {
 479          if ($task->get_last_run_time()) {
 480              return userdate($task->get_last_run_time());
 481          } else {
 482              return get_string('never');
 483          }
 484      }
 485  
 486      /**
 487       * Standard display of a tasks next run time.
 488       *
 489       * @param scheduled_task $task
 490       * @return string HTML.
 491       */
 492      public function next_run_time(scheduled_task $task): string {
 493          $nextrun = $task->get_next_run_time();
 494  
 495          if (!$task->is_component_enabled() && !$task->get_run_if_component_disabled()) {
 496              $nextrun = get_string('plugindisabled', 'tool_task');
 497          } else if ($task->get_disabled()) {
 498              $nextrun = get_string('taskdisabled', 'tool_task');
 499          } else if ($nextrun > time()) {
 500              $nextrun = userdate($nextrun);
 501          } else {
 502              $nextrun = get_string('asap', 'tool_task');
 503          }
 504  
 505          return $nextrun;
 506      }
 507  
 508      /**
 509       * Get a table cell to show one time, comparing it to the default.
 510       *
 511       * @param string $current the current setting.
 512       * @param string $default the default setting from the db/tasks.php file.
 513       * @return html_table_cell for use in the table.
 514       */
 515      protected function time_cell(string $current, string $default): html_table_cell {
 516          $cell = new html_table_cell($current);
 517          // Cron-style values must always be LTR.
 518          $cell->attributes['class'] = 'text-ltr';
 519  
 520          // If the current value is default, that is all we want to do.
 521          if ($default === '*') {
 522              if ($current === '*') {
 523                  return $cell;
 524              }
 525          } else if ($default === 'R' ) {
 526              if (is_numeric($current)) {
 527                  return $cell;
 528              }
 529          } else {
 530              if ($default === $current) {
 531                  return $cell;
 532              }
 533          }
 534  
 535          // Otherwise, highlight and show the default.
 536          $cell->attributes['class'] .= ' table-warning';
 537          $cell->text .= ' ' . html_writer::span(
 538                  get_string('defaultx', 'tool_task', $default), 'task-class');
 539          return $cell;
 540      }
 541  
 542      /**
 543       * Displays a warning on the page if cron is disabled.
 544       *
 545       * @return string HTML code for information about cron being disabled
 546       * @throws moodle_exception
 547       */
 548      public function cron_disabled(): string {
 549          return $this->output->notification(get_string('crondisabled', 'tool_task'), 'warning');
 550      }
 551  
 552      /**
 553       * Renders a link back to the scheduled tasks page (used from the 'run now' screen).
 554       *
 555       * @param string $taskclassname if specified, the list of tasks will scroll to show this task.
 556       * @return string HTML code
 557       */
 558      public function link_back($taskclassname = '') {
 559          $url = new moodle_url('/admin/tool/task/scheduledtasks.php');
 560          if ($taskclassname) {
 561              $url->param('lastchanged', $taskclassname);
 562          }
 563          return $this->render_from_template('tool_task/link_back', ['url' => $url]);
 564      }
 565  }