Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.

Differences Between: [Versions 400 and 403] [Versions 401 and 403]

   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  declare(strict_types=1);
  18  
  19  namespace core_reportbuilder;
  20  
  21  use action_menu_filler;
  22  use coding_exception;
  23  use html_writer;
  24  use stdClass;
  25  use core\output\checkbox_toggleall;
  26  use core_reportbuilder\local\models\report;
  27  use core_reportbuilder\local\report\action;
  28  use core_reportbuilder\local\report\base;
  29  use core_reportbuilder\local\report\column;
  30  
  31  /**
  32   * Base class for system reports
  33   *
  34   * @package     core_reportbuilder
  35   * @copyright   2020 Paul Holden <paulh@moodle.com>
  36   * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  37   */
  38  abstract class system_report extends base {
  39  
  40      /** @var array $parameters */
  41      private $parameters;
  42  
  43      /** @var string[] $basefields List of base fields */
  44      private $basefields = [];
  45  
  46      /** @var callable $checkboxcallback */
  47      private $checkboxcallback = null;
  48  
  49      /** @var bool $filterformdefault Whether to use the default filters form */
  50      private $filterformdefault = true;
  51  
  52      /** @var action|action_menu_filler[] $actions */
  53      private $actions = [];
  54  
  55      /** @var column $initialsortcolumn */
  56      private $initialsortcolumn;
  57  
  58      /** @var int $initialsortdirection */
  59      private $initialsortdirection;
  60  
  61      /**
  62       * System report constructor.
  63       *
  64       * @param report $report
  65       * @param array $parameters
  66       */
  67      final public function __construct(report $report, array $parameters) {
  68          $this->parameters = $parameters;
  69  
  70          parent::__construct($report);
  71      }
  72  
  73      /**
  74       * Validates access to view this report
  75       *
  76       * This is necessary to implement independently of the page that would typically embed the report because
  77       * subsequent pages are requested via AJAX requests, and access should be validated each time
  78       *
  79       * @return bool
  80       */
  81      abstract protected function can_view(): bool;
  82  
  83      /**
  84       * Validate access to the report
  85       *
  86       * @throws report_access_exception
  87       */
  88      final public function require_can_view(): void {
  89          if (!$this->can_view()) {
  90              throw new report_access_exception();
  91          }
  92      }
  93  
  94      /**
  95       * Report validation
  96       *
  97       * @throws report_access_exception If user cannot access the report
  98       * @throws coding_exception If no default column are specified
  99       */
 100      protected function validate(): void {
 101          parent::validate();
 102  
 103          $this->require_can_view();
 104  
 105          // Ensure the report has some default columns specified.
 106          if (empty($this->get_columns())) {
 107              throw new coding_exception('No columns added');
 108          }
 109      }
 110  
 111      /**
 112       * Add list of fields that have to be always included in SQL query for actions and row classes
 113       *
 114       * Base fields are only available in system reports because they are not compatible with aggregation
 115       *
 116       * @param string $sql SQL clause for the list of fields that only uses main table or base joins
 117       */
 118      final protected function add_base_fields(string $sql): void {
 119          $this->basefields[] = $sql;
 120      }
 121  
 122      /**
 123       * Return report base fields
 124       *
 125       * @return array
 126       */
 127      final public function get_base_fields(): array {
 128          return $this->basefields;
 129      }
 130  
 131      /**
 132       * Define toggle all checkbox for the report, required row data should be defined by calling {@see add_base_fields}
 133       *
 134       * @param callable $callback Callback to return value/label for each checkbox, implementing the following signature:
 135       *      function(stdClass $row): array containing value/label pair
 136       */
 137      final protected function set_checkbox_toggleall(callable $callback): void {
 138          $this->checkboxcallback = $callback;
 139      }
 140  
 141      /**
 142       * Return instance of toggle all checkbox, if previously defined by {@see set_checkbox_toggleall}
 143       *
 144       * @param bool $ismaster
 145       * @param stdClass|null $row
 146       * @return checkbox_toggleall|null
 147       */
 148      final public function get_checkbox_toggleall(bool $ismaster, ?stdClass $row = null): ?checkbox_toggleall {
 149          if (!is_callable($this->checkboxcallback)) {
 150              return null;
 151          }
 152  
 153          // Generic content for the master checkbox, execute callback for those belonging to each row.
 154          if ($ismaster) {
 155              $value = '';
 156              $label = get_string('selectall');
 157          } else {
 158              [$value, $label] = ($this->checkboxcallback)($row);
 159          }
 160  
 161          return new checkbox_toggleall('report-select-all', $ismaster, [
 162              'id' => html_writer::random_id(),
 163              'name' => 'report-select-row[]',
 164              'value' => $value,
 165              'label' => $label,
 166              'labelclasses' => 'accesshide',
 167          ]);
 168      }
 169  
 170      /**
 171       * Override whether to use the default system report filters form, for instance this can be disabled if the UI requires
 172       * it's own custom filter management form for a specific report
 173       *
 174       * @param bool $filterformdefault
 175       */
 176      final public function set_filter_form_default(bool $filterformdefault = true): void {
 177          $this->filterformdefault = $filterformdefault;
 178      }
 179  
 180      /**
 181       * Whether to use the default filters form
 182       *
 183       * @return bool
 184       */
 185      final public function get_filter_form_default(): bool {
 186          return $this->filterformdefault;
 187      }
 188  
 189      /**
 190       * Adds an action to the report
 191       *
 192       * @param action $action
 193       */
 194      final public function add_action(action $action): void {
 195          $this->actions[] = $action;
 196      }
 197  
 198      /**
 199       * Adds action divider to the report
 200       *
 201       */
 202      final public function add_action_divider(): void {
 203          $divider = new action_menu_filler();
 204          // We need to set as not primary action because we just need add an action divider, not a new action item.
 205          $divider->primary = false;
 206          $this->actions[] = $divider;
 207      }
 208  
 209      /**
 210       * Whether report has any actions
 211       *
 212       * @return bool
 213       */
 214      final public function has_actions(): bool {
 215          return !empty($this->actions);
 216      }
 217  
 218      /**
 219       * Return report actions
 220       *
 221       * @return action|action_menu_filler[]
 222       */
 223      final public function get_actions(): array {
 224          return $this->actions;
 225      }
 226  
 227      /**
 228       * Set all report parameters
 229       *
 230       * @param array $parameters
 231       */
 232      final public function set_parameters(array $parameters): void {
 233          $this->parameters = $parameters;
 234      }
 235  
 236      /**
 237       * Return all report parameters
 238       *
 239       * @return array
 240       */
 241      final public function get_parameters(): array {
 242          return $this->parameters;
 243      }
 244  
 245      /**
 246       * Return specific report parameter
 247       *
 248       * @param string $param
 249       * @param mixed $default
 250       * @param string $type
 251       * @return mixed
 252       */
 253      final public function get_parameter(string $param, $default, string $type) {
 254          if (!array_key_exists($param, $this->parameters)) {
 255              return $default;
 256          }
 257  
 258          return clean_param($this->parameters[$param], $type);
 259      }
 260  
 261      /**
 262       * Output the report
 263       *
 264       * @uses \core_reportbuilder\output\renderer::render_system_report()
 265       *
 266       * @return string
 267       */
 268      final public function output(): string {
 269          global $PAGE;
 270  
 271          /** @var \core_reportbuilder\output\renderer $renderer */
 272          $renderer = $PAGE->get_renderer('core_reportbuilder');
 273          $report = new \core_reportbuilder\output\system_report($this->get_report_persistent(), $this, $this->parameters);
 274  
 275          return $renderer->render($report);
 276      }
 277  
 278      /**
 279       * CSS classes to add to the row. Can be overridden by system reports do define class to be added to output according to
 280       * content of each row
 281       *
 282       * @param stdClass $row
 283       * @return string
 284       */
 285      public function get_row_class(stdClass $row): string {
 286          return '';
 287      }
 288  
 289      /**
 290       * Called before rendering each row. Can be overridden to pre-fetch/create objects and store them in the class, which can
 291       * later be used in column and action callbacks
 292       *
 293       * @param stdClass $row
 294       */
 295      public function row_callback(stdClass $row): void {
 296          return;
 297      }
 298  
 299      /**
 300       * Validates access to download this report.
 301       *
 302       * @return bool
 303       */
 304      final public function can_be_downloaded(): bool {
 305          return $this->can_view() && $this->is_downloadable();
 306      }
 307  
 308      /**
 309       * Return list of column names that will be excluded when table is downloaded. Extending classes should override this method
 310       * as appropriate
 311       *
 312       * @return string[] Array of column unique identifiers
 313       */
 314      public function get_exclude_columns_for_download(): array {
 315          return [];
 316      }
 317  
 318      /**
 319       * Set initial sort column and sort direction for the report
 320       *
 321       * @param string $uniqueidentifier
 322       * @param int $sortdirection One of SORT_ASC or SORT_DESC
 323       * @throws coding_exception
 324       */
 325      public function set_initial_sort_column(string $uniqueidentifier, int $sortdirection): void {
 326          if (!$sortcolumn = $this->get_column($uniqueidentifier)) {
 327              throw new coding_exception('Unknown column identifier', $uniqueidentifier);
 328          }
 329  
 330          $this->initialsortcolumn = $sortcolumn;
 331          $this->initialsortdirection = $sortdirection;
 332      }
 333  
 334      /**
 335       * Get initial sort column
 336       *
 337       * @return column|null
 338       */
 339      public function get_initial_sort_column(): ?column {
 340          return $this->initialsortcolumn;
 341      }
 342  
 343      /**
 344       * Get initial sort column direction
 345       *
 346       * @return int
 347       */
 348      public function get_initial_sort_direction(): int {
 349          return $this->initialsortdirection;
 350      }
 351  }