Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.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_cache\output;
  18  
  19  use cache_factory;
  20  use cache_store;
  21  use context;
  22  use core_collator;
  23  use html_table;
  24  use html_table_cell;
  25  use html_table_row;
  26  use html_writer;
  27  use lang_string;
  28  use moodle_url;
  29  use single_select;
  30  
  31  /**
  32   * The cache renderer (mainly admin interfaces).
  33   *
  34   * @package    core_cache
  35   * @copyright  2012 Sam Hemelryk
  36   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  37   */
  38  class renderer extends \plugin_renderer_base {
  39  
  40      /**
  41       * Displays store summaries.
  42       *
  43       * @param array $storeinstancesummaries information about each store instance,
  44       *      as returned by core_cache\administration_helper::get_store_instance_summaries().
  45       * @param array $storepluginsummaries information about each store plugin as
  46       *      returned by core_cache\administration_helper::get_store_plugin_summaries().
  47       * @return string HTML
  48       */
  49      public function store_instance_summariers(array $storeinstancesummaries, array $storepluginsummaries) {
  50          $table = new html_table();
  51          $table->head = array(
  52              get_string('storename', 'cache'),
  53              get_string('plugin', 'cache'),
  54              get_string('storeready', 'cache'),
  55              get_string('mappings', 'cache'),
  56              get_string('modes', 'cache'),
  57              get_string('supports', 'cache'),
  58              get_string('locking', 'cache') . ' ' . $this->output->help_icon('locking', 'cache'),
  59              get_string('actions', 'cache'),
  60          );
  61          $table->colclasses = array(
  62              'storename',
  63              'plugin',
  64              'storeready',
  65              'mappings',
  66              'modes',
  67              'supports',
  68              'locking',
  69              'actions'
  70          );
  71          $table->data = array();
  72  
  73          $defaultstoreactions = get_string('defaultstoreactions', 'cache');
  74  
  75          foreach ($storeinstancesummaries as $name => $storesummary) {
  76              $htmlactions = cache_factory::get_administration_display_helper()->get_store_instance_actions($name, $storesummary);
  77              $modes = array();
  78              foreach ($storesummary['modes'] as $mode => $enabled) {
  79                  if ($enabled) {
  80                      $modes[] = get_string('mode_'.$mode, 'cache');
  81                  }
  82              }
  83  
  84              $supports = array();
  85              foreach ($storesummary['supports'] as $support => $enabled) {
  86                  if ($enabled) {
  87                      $supports[] = get_string('supports_'.$support, 'cache');
  88                  }
  89              }
  90  
  91              $info = '';
  92              if (!empty($storesummary['default'])) {
  93                  $info = $this->output->pix_icon('i/info', $defaultstoreactions, '', array('class' => 'icon'));
  94              }
  95  
  96              $isready = $storesummary['isready'] && $storesummary['requirementsmet'];
  97              $readycell = new html_table_cell;
  98              if ($isready) {
  99                  $readycell->text = $this->output->pix_icon('i/valid', '1');
 100              }
 101  
 102              $storename = $storesummary['name'];
 103              if (!empty($storesummary['default'])) {
 104                  $storename = get_string('store_'.$storesummary['name'], 'cache');
 105              }
 106              if (!$isready && (int)$storesummary['mappings'] > 0) {
 107                  $readycell->text = $this->output->help_icon('storerequiresattention', 'cache');
 108                  $readycell->attributes['class'] = 'store-requires-attention';
 109              }
 110  
 111              $lock = $storesummary['lock']['name'];
 112              if (!empty($storesummary['lock']['default'])) {
 113                  $lock = get_string($storesummary['lock']['name'], 'cache');
 114              }
 115  
 116              $row = new html_table_row(array(
 117                  $storename,
 118                  get_string('pluginname', 'cachestore_'.$storesummary['plugin']),
 119                  $readycell,
 120                  $storesummary['mappings'],
 121                  join(', ', $modes),
 122                  join(', ', $supports),
 123                  $lock,
 124                  $info.join(', ', $htmlactions)
 125              ));
 126              $row->attributes['class'] = 'store-'.$name;
 127              if ($storesummary['default']) {
 128                  $row->attributes['class'] .= ' default-store';
 129              }
 130              $table->data[] = $row;
 131          }
 132  
 133          $html  = html_writer::start_tag('div', array('id' => 'core-cache-store-summaries'));
 134          $html .= $this->output->heading(get_string('storesummaries', 'cache'), 3);
 135          $html .= html_writer::table($table);
 136          $html .= html_writer::end_tag('div');
 137          return $html;
 138      }
 139  
 140      /**
 141       * Displays plugin summaries.
 142       *
 143       * @param array $storepluginsummaries information about each store plugin as
 144       *      returned by core_cache\administration_helper::get_store_plugin_summaries().
 145       * @return string HTML
 146       */
 147      public function store_plugin_summaries(array $storepluginsummaries) {
 148          $table = new html_table();
 149          $table->head = array(
 150              get_string('plugin', 'cache'),
 151              get_string('storeready', 'cache'),
 152              get_string('stores', 'cache'),
 153              get_string('modes', 'cache'),
 154              get_string('supports', 'cache'),
 155              get_string('actions', 'cache'),
 156          );
 157          $table->colclasses = array(
 158              'plugin',
 159              'storeready',
 160              'stores',
 161              'modes',
 162              'supports',
 163              'actions'
 164          );
 165          $table->data = array();
 166  
 167          foreach ($storepluginsummaries as $name => $plugin) {
 168              $htmlactions = cache_factory::get_administration_display_helper()->get_store_plugin_actions($name, $plugin);
 169  
 170              $modes = array();
 171              foreach ($plugin['modes'] as $mode => $enabled) {
 172                  if ($enabled) {
 173                      $modes[] = get_string('mode_'.$mode, 'cache');
 174                  }
 175              }
 176  
 177              $supports = array();
 178              foreach ($plugin['supports'] as $support => $enabled) {
 179                  if ($enabled) {
 180                      $supports[] = get_string('supports_'.$support, 'cache');
 181                  }
 182              }
 183  
 184              $row = new html_table_row(array(
 185                  $plugin['name'],
 186                  ($plugin['requirementsmet']) ? $this->output->pix_icon('i/valid', '1') : '',
 187                  $plugin['instances'],
 188                  join(', ', $modes),
 189                  join(', ', $supports),
 190                  join(', ', $htmlactions)
 191              ));
 192  
 193              $row->attributes['class'] = 'plugin-'.$name;
 194              $table->data[] = $row;
 195          }
 196  
 197          $html  = html_writer::start_tag('div', array('id' => 'core-cache-plugin-summaries'));
 198          $html .= $this->output->heading(get_string('pluginsummaries', 'cache'), 3);
 199          $html .= html_writer::table($table);
 200          $html .= html_writer::end_tag('div');
 201          return $html;
 202      }
 203  
 204      /**
 205       * Displays definition summaries.
 206       *
 207       * @param array $definitionsummaries information about each definition, as returned by
 208       *      core_cache\administration_helper::get_definition_summaries().
 209       * @param context $context the system context.
 210       *
 211       * @return string HTML.
 212       */
 213      public function definition_summaries(array $definitionsummaries, context $context) {
 214          $table = new html_table();
 215          $table->head = array(
 216              get_string('definition', 'cache'),
 217              get_string('mode', 'cache'),
 218              get_string('component', 'cache'),
 219              get_string('area', 'cache'),
 220              get_string('mappings', 'cache'),
 221              get_string('sharing', 'cache'),
 222              get_string('canuselocalstore', 'cache'),
 223              get_string('actions', 'cache')
 224          );
 225          $table->colclasses = array(
 226              'definition',
 227              'mode',
 228              'component',
 229              'area',
 230              'mappings',
 231              'sharing',
 232              'canuselocalstore',
 233              'actions'
 234          );
 235          $table->data = array();
 236  
 237          core_collator::asort_array_of_arrays_by_key($definitionsummaries, 'name');
 238  
 239          $none = new lang_string('none', 'cache');
 240          foreach ($definitionsummaries as $id => $definition) {
 241              $htmlactions = cache_factory::get_administration_display_helper()->get_definition_actions($context, $definition);
 242              if (!empty($definition['mappings'])) {
 243                  $mapping = join(', ', $definition['mappings']);
 244              } else {
 245                  $mapping = '<em>'.$none.'</em>';
 246              }
 247  
 248              $uselocalcachecol = get_string('no');
 249              if ($definition['mode'] != cache_store::MODE_REQUEST) {
 250                  if (isset($definition['canuselocalstore']) && $definition['canuselocalstore']) {
 251                      $uselocalcachecol = get_string('yes');
 252                  }
 253              }
 254  
 255              $row = new html_table_row(array(
 256                  $definition['name'],
 257                  get_string('mode_'.$definition['mode'], 'cache'),
 258                  $definition['component'],
 259                  $definition['area'],
 260                  $mapping,
 261                  join(', ', $definition['selectedsharingoption']),
 262                  $uselocalcachecol,
 263                  join(', ', $htmlactions)
 264              ));
 265              $row->attributes['class'] = 'definition-'.$definition['component'].'-'.$definition['area'];
 266              $table->data[] = $row;
 267          }
 268  
 269          $html  = html_writer::start_tag('div', array('id' => 'core-cache-definition-summaries'));
 270          $html .= $this->output->heading(get_string('definitionsummaries', 'cache'), 3);
 271          $html .= html_writer::table($table);
 272  
 273          $url = new moodle_url('/cache/admin.php', array('action' => 'rescandefinitions', 'sesskey' => sesskey()));
 274          $link = html_writer::link($url, get_string('rescandefinitions', 'cache'));
 275          $html .= html_writer::tag('div', $link, array('id' => 'core-cache-rescan-definitions'));
 276  
 277          $html .= html_writer::end_tag('div');
 278          return $html;
 279      }
 280  
 281      /**
 282       * Displays mode mappings
 283       *
 284       * @param string $applicationstore
 285       * @param string $sessionstore
 286       * @param string $requeststore
 287       * @param moodle_url $editurl
 288       * @return string HTML
 289       */
 290      public function mode_mappings($applicationstore, $sessionstore, $requeststore, moodle_url $editurl) {
 291          $table = new html_table();
 292          $table->colclasses = array(
 293              'mode',
 294              'mapping',
 295          );
 296          $table->rowclasses = array(
 297              'mode_application',
 298              'mode_session',
 299              'mode_request'
 300          );
 301          $table->head = array(
 302              get_string('mode', 'cache'),
 303              get_string('mappings', 'cache'),
 304          );
 305          $table->data = array(
 306              array(get_string('mode_'.cache_store::MODE_APPLICATION, 'cache'), $applicationstore),
 307              array(get_string('mode_'.cache_store::MODE_SESSION, 'cache'), $sessionstore),
 308              array(get_string('mode_'.cache_store::MODE_REQUEST, 'cache'), $requeststore)
 309          );
 310  
 311          $html = html_writer::start_tag('div', array('id' => 'core-cache-mode-mappings'));
 312          $html .= $this->output->heading(get_string('defaultmappings', 'cache'), 3);
 313          $html .= html_writer::table($table);
 314          $link = html_writer::link($editurl, get_string('editmappings', 'cache'));
 315          $html .= html_writer::tag('div', $link, array('class' => 'edit-link'));
 316          $html .= html_writer::end_tag('div');
 317          return $html;
 318      }
 319  
 320      /**
 321       * Display basic information about lock instances.
 322       *
 323       * @todo Add some actions so that people can configure lock instances.
 324       *
 325       * @param array $locks
 326       * @return string
 327       */
 328      public function lock_summaries(array $locks) {
 329          $table = new html_table();
 330          $table->colclasses = array(
 331              'name',
 332              'type',
 333              'default',
 334              'uses',
 335              'actions'
 336          );
 337          $table->rowclasses = array(
 338              'lock_name',
 339              'lock_type',
 340              'lock_default',
 341              'lock_uses',
 342              'lock_actions',
 343          );
 344          $table->head = array(
 345              get_string('lockname', 'cache'),
 346              get_string('locktype', 'cache'),
 347              get_string('lockdefault', 'cache'),
 348              get_string('lockuses', 'cache'),
 349              get_string('actions', 'cache')
 350          );
 351          $table->data = array();
 352          $tick = $this->output->pix_icon('i/valid', '');
 353          foreach ($locks as $lock) {
 354              $actions = array();
 355              if ($lock['uses'] === 0 && !$lock['default']) {
 356                  $url = new moodle_url('/cache/admin.php', array('lock' => $lock['name'], 'action' => 'deletelock'));
 357                  $actions[] = html_writer::link($url, get_string('delete', 'cache'));
 358              }
 359              $table->data[] = new html_table_row(array(
 360                  new html_table_cell($lock['name']),
 361                  new html_table_cell($lock['type']),
 362                  new html_table_cell($lock['default'] ? $tick : ''),
 363                  new html_table_cell($lock['uses']),
 364                  new html_table_cell(join(' ', $actions))
 365              ));
 366          }
 367  
 368          $html = html_writer::start_tag('div', array('id' => 'core-cache-lock-summary'));
 369          $html .= $this->output->heading(get_string('locksummary', 'cache'), 3);
 370          $html .= html_writer::table($table);
 371          $html .= html_writer::end_tag('div');
 372          return $html;
 373      }
 374  
 375      /**
 376       * Renders additional actions for locks, such as Add.
 377       *
 378       * @return string
 379       */
 380      public function additional_lock_actions() : string {
 381          $url = new moodle_url('/cache/admin.php', array('action' => 'newlockinstance'));
 382          $select = new single_select($url, 'lock', cache_factory::get_administration_display_helper()->get_addable_lock_options());
 383          $select->label = get_string('addnewlockinstance', 'cache');
 384  
 385          $html = html_writer::start_tag('div', array('id' => 'core-cache-lock-additional-actions'));
 386          $html .= html_writer::tag('div', $this->output->render($select), array('class' => 'new-instance'));
 387          $html .= html_writer::end_tag('div');
 388          return $html;
 389      }
 390  
 391      /**
 392       * Renders an array of notifications for the cache configuration screen.
 393       *
 394       * Takes an array of notifications with the form:
 395       * $notifications = array(
 396       *     array('This is a success message', true),
 397       *     array('This is a failure message', false),
 398       * );
 399       *
 400       * @param array $notifications
 401       * @return string
 402       */
 403      public function notifications(array $notifications = array()) {
 404          if (count($notifications) === 0) {
 405              // There are no notifications to render.
 406              return '';
 407          }
 408          $html = html_writer::start_div('notifications');
 409          foreach ($notifications as $notification) {
 410              list($message, $notifysuccess) = $notification;
 411              $html .= $this->notification($message, ($notifysuccess) ? 'notifysuccess' : 'notifyproblem');
 412          }
 413          $html .= html_writer::end_div();
 414          return $html;
 415      }
 416  
 417      /**
 418       * Creates the two tables which display on the usage page.
 419       *
 420       * @param array $usage Usage information (from cache_helper::usage)
 421       * @return array Array of 2 tables (main and summary table)
 422       * @throws \coding_exception
 423       */
 424      public function usage_tables(array $usage): array {
 425          $table = new \html_table();
 426          $table->id = 'usage_main';
 427          $table->head = [
 428              get_string('definition', 'cache'),
 429              get_string('storename', 'cache'),
 430              get_string('plugin', 'cache'),
 431              get_string('usage_items', 'cache'),
 432              get_string('usage_mean', 'cache'),
 433              get_string('usage_sd', 'cache'),
 434              get_string('usage_total', 'cache'),
 435              get_string('usage_totalmargin', 'cache')];
 436          $table->align = [
 437              'left', 'left', 'left',
 438              'right', 'right', 'right', 'right', 'right'
 439          ];
 440          $table->data = [];
 441  
 442          $summarytable = new \html_table();
 443          $summarytable->id = 'usage_summary';
 444          $summarytable->head = [
 445              get_string('storename', 'cache'),
 446              get_string('plugin', 'cache'),
 447              get_string('usage_total', 'cache'),
 448              get_string('usage_realtotal', 'cache')
 449          ];
 450          $summarytable->align = [
 451              'left', 'left',
 452              'right', 'right',
 453          ];
 454          $summarytable->data = [];
 455          $summarytable->attributes['class'] = 'generaltable w-auto';
 456          $storetotals = [];
 457  
 458          // We will highlight all cells that are more than 2% of total size, so work that out first.
 459          $total = 0;
 460          foreach ($usage as $definition) {
 461              foreach ($definition->stores as $storedata) {
 462                  $total += $storedata->items * $storedata->mean;
 463              }
 464          }
 465          $highlightover = round($total / 50);
 466  
 467          foreach ($usage as $definition) {
 468              foreach ($definition->stores as $storedata) {
 469                  $row = [];
 470                  $row[] = s($definition->cacheid);
 471                  $row[] = s($storedata->name);
 472                  $row[] = s($storedata->class);
 473                  if (!$storedata->supported) {
 474                      // We don't have data for this store because it isn't searchable.
 475                      $row[] = '-';
 476                  } else {
 477                      $row[] = $storedata->items;
 478                  }
 479                  if ($storedata->items) {
 480                      $row[] = display_size(round($storedata->mean));
 481                      if ($storedata->items > 1) {
 482                          $row[] = display_size(round($storedata->sd));
 483                      } else {
 484                          $row[] = '';
 485                      }
 486                      $cellsize = round($storedata->items * $storedata->mean);
 487                      $row[] = display_size($cellsize, 1, 'MB');
 488  
 489                      if (!array_key_exists($storedata->name, $storetotals)) {
 490                          $storetotals[$storedata->name] = (object)[
 491                              'plugin' => $storedata->class,
 492                              'total' => 0,
 493                              'storetotal' => $storedata->storetotal,
 494                          ];
 495                      }
 496                      $storetotals[$storedata->name]->total += $cellsize;
 497                  } else {
 498                      $row[] = '';
 499                      $row[] = '';
 500                      $cellsize = 0;
 501                      $row[] = '';
 502                  }
 503                  if ($storedata->margin) {
 504                      // Plus or minus.
 505                      $row[] = '&#xb1;' . display_size($storedata->margin * $storedata->items, 1, 'MB');
 506                  } else {
 507                      $row[] = '';
 508                  }
 509                  $htmlrow = new \html_table_row($row);
 510                  if ($cellsize > $highlightover) {
 511                      $htmlrow->attributes = ['class' => 'table-warning'];
 512                  }
 513                  $table->data[] = $htmlrow;
 514              }
 515          }
 516  
 517          ksort($storetotals);
 518  
 519          foreach ($storetotals as $storename => $storedetails) {
 520              $row = [s($storename), s($storedetails->plugin)];
 521              $row[] = display_size($storedetails->total, 1, 'MB');
 522              if ($storedetails->storetotal !== null) {
 523                  $row[] = display_size($storedetails->storetotal, 1, 'MB');
 524              } else {
 525                  $row[] = '-';
 526              }
 527              $summarytable->data[] = $row;
 528          }
 529  
 530          return [$table, $summarytable];
 531      }
 532  
 533      /**
 534       * Renders the usage page.
 535       *
 536       * @param \html_table $maintable Main table
 537       * @param \html_table $summarytable Summary table
 538       * @param \moodleform $samplesform Form to select number of samples
 539       * @return string HTML for page
 540       */
 541      public function usage_page(\html_table $maintable, \html_table $summarytable, \moodleform $samplesform): string {
 542          $data = [
 543              'maintable' => \html_writer::table($maintable),
 544              'summarytable' => \html_writer::table($summarytable),
 545              'samplesform' => $samplesform->render()
 546          ];
 547  
 548          return $this->render_from_template('core_cache/usage', $data);
 549      }
 550  }