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.

Differences Between: [Versions 400 and 401] [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 coding_exception;
  22  use core_reportbuilder\local\helpers\report;
  23  use core_reportbuilder\local\models\column as column_model;
  24  use core_reportbuilder\local\models\filter as filter_model;
  25  use core_reportbuilder\local\report\base;
  26  use core_reportbuilder\local\report\column;
  27  use core_reportbuilder\local\report\filter;
  28  
  29  /**
  30   * Class datasource
  31   *
  32   * @package   core_reportbuilder
  33   * @copyright 2021 David Matamoros <davidmc@moodle.com>
  34   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  35   */
  36  abstract class datasource extends base {
  37  
  38      /** @var float[] $elementsmodified Track the time elements of specific reports have been added, updated, removed */
  39      private static $elementsmodified = [];
  40  
  41      /** @var array $activecolumns */
  42      private $activecolumns;
  43  
  44      /** @var array $activefilters */
  45      private $activefilters;
  46  
  47      /** @var array $activeconditions */
  48      private $activeconditions;
  49  
  50      /**
  51       * Return user friendly name of the datasource
  52       *
  53       * @return string
  54       */
  55      abstract public static function get_name(): string;
  56  
  57      /**
  58       * Add columns from the given entity name to be available to use in a custom report
  59       *
  60       * @param string $entityname
  61       * @param array $include Include only these columns, if omitted then include all
  62       * @param array $exclude Exclude these columns, if omitted then exclude none
  63       * @throws coding_exception If both $include and $exclude are non-empty
  64       */
  65      final protected function add_columns_from_entity(string $entityname, array $include = [], array $exclude = []): void {
  66          if (!empty($include) && !empty($exclude)) {
  67              throw new coding_exception('Cannot specify columns to include and exclude simultaneously');
  68          }
  69  
  70          $entity = $this->get_entity($entityname);
  71  
  72          // Retrieve filtered columns from entity, respecting given $include/$exclude parameters.
  73          $columns = array_filter($entity->get_columns(), static function(column $column) use ($include, $exclude): bool {
  74              if (!empty($include)) {
  75                  return in_array($column->get_name(), $include);
  76              }
  77  
  78              if (!empty($exclude)) {
  79                  return !in_array($column->get_name(), $exclude);
  80              }
  81  
  82              return true;
  83          });
  84  
  85          foreach ($columns as $column) {
  86              $this->add_column($column);
  87          }
  88      }
  89  
  90      /**
  91       * Add default datasource columns to the report
  92       *
  93       * This method is optional and can be called when the report is created to add the default columns defined in the
  94       * selected datasource.
  95       */
  96      public function add_default_columns(): void {
  97          $reportid = $this->get_report_persistent()->get('id');
  98  
  99          // Retrieve default column sorting, and track index of both sorted/non-sorted columns.
 100          $columnidentifiers = $this->get_default_columns();
 101          $defaultcolumnsorting = array_intersect_key($this->get_default_column_sorting(),
 102              array_fill_keys($columnidentifiers, 1));
 103          $columnnonsortingindex = count($defaultcolumnsorting) + 1;
 104  
 105          foreach ($columnidentifiers as $uniqueidentifier) {
 106              $column = report::add_report_column($reportid, $uniqueidentifier);
 107  
 108              // After adding the column, toggle sorting according to defaults provided by the datasource.
 109              $sortorder = array_search($uniqueidentifier, array_keys($defaultcolumnsorting));
 110              if ($sortorder !== false) {
 111                  $column->set_many([
 112                      'sortenabled' => true,
 113                      'sortdirection' => $defaultcolumnsorting[$uniqueidentifier],
 114                      'sortorder' => $sortorder + 1,
 115                  ])->update();
 116              } else if (!empty($defaultcolumnsorting)) {
 117                  $column->set('sortorder', $columnnonsortingindex++)->update();
 118              }
 119          }
 120      }
 121  
 122      /**
 123       * Return the columns that will be added to the report once is created
 124       *
 125       * @return string[]
 126       */
 127      abstract public function get_default_columns(): array;
 128  
 129      /**
 130       * Return the default sorting that will be added to the report once it is created
 131       *
 132       * @return int[] array [column identifier => SORT_ASC/SORT_DESC]
 133       */
 134      public function get_default_column_sorting(): array {
 135          return [];
 136      }
 137  
 138      /**
 139       * Return all configured report columns
 140       *
 141       * @return column[]
 142       */
 143      public function get_active_columns(): array {
 144          $reportid = $this->get_report_persistent()->get('id');
 145  
 146          // Determine whether we already retrieved the columns since the report was last modified.
 147          self::$elementsmodified += [$reportid => -1];
 148          if ($this->activecolumns !== null && $this->activecolumns['builttime'] > self::$elementsmodified[$reportid]) {
 149              return $this->activecolumns['values'];
 150          }
 151  
 152          $this->activecolumns = ['builttime' => microtime(true), 'values' => []];
 153  
 154          $activecolumns = column_model::get_records(['reportid' => $reportid], 'columnorder');
 155          foreach ($activecolumns as $index => $column) {
 156              $instance = $this->get_column($column->get('uniqueidentifier'));
 157  
 158              // Ensure the column is still present and available.
 159              if ($instance !== null && $instance->get_is_available()) {
 160  
 161                  // We should clone the report column to ensure if it's added twice to a report, each operates independently.
 162                  $this->activecolumns['values'][] = clone $instance
 163                      ->set_index($index)
 164                      ->set_persistent($column)
 165                      ->set_aggregation($column->get('aggregation'));
 166              }
 167          }
 168  
 169          return $this->activecolumns['values'];
 170      }
 171  
 172      /**
 173       * Add filters from the given entity name to be available to use in a custom report
 174       *
 175       * @param string $entityname
 176       * @param array $include Include only these filters, if omitted then include all
 177       * @param array $exclude Exclude these filters, if omitted then exclude none
 178       * @throws coding_exception If both $include and $exclude are non-empty
 179       */
 180      final protected function add_filters_from_entity(string $entityname, array $include = [], array $exclude = []): void {
 181          if (!empty($include) && !empty($exclude)) {
 182              throw new coding_exception('Cannot specify filters to include and exclude simultaneously');
 183          }
 184  
 185          $entity = $this->get_entity($entityname);
 186  
 187          // Retrieve filtered filters from entity, respecting given $include/$exclude parameters.
 188          $filters = array_filter($entity->get_filters(), static function(filter $filter) use ($include, $exclude): bool {
 189              if (!empty($include)) {
 190                  return in_array($filter->get_name(), $include);
 191              }
 192  
 193              if (!empty($exclude)) {
 194                  return !in_array($filter->get_name(), $exclude);
 195              }
 196  
 197              return true;
 198          });
 199  
 200          foreach ($filters as $filter) {
 201              $this->add_filter($filter);
 202          }
 203      }
 204  
 205      /**
 206       * Add default datasource filters to the report
 207       *
 208       * This method is optional and can be called when the report is created to add the default filters defined in the
 209       * selected datasource.
 210       */
 211      public function add_default_filters(): void {
 212          $reportid = $this->get_report_persistent()->get('id');
 213          $filteridentifiers = $this->get_default_filters();
 214          foreach ($filteridentifiers as $uniqueidentifier) {
 215              report::add_report_filter($reportid, $uniqueidentifier);
 216          }
 217      }
 218  
 219      /**
 220       * Return the filters that will be added to the report once is created
 221       *
 222       * @return string[]
 223       */
 224      abstract public function get_default_filters(): array;
 225  
 226      /**
 227       * Return all configured report filters
 228       *
 229       * @return filter[]
 230       */
 231      public function get_active_filters(): array {
 232          $reportid = $this->get_report_persistent()->get('id');
 233  
 234          // Determine whether we already retrieved the filters since the report was last modified.
 235          self::$elementsmodified += [$reportid => -1];
 236          if ($this->activefilters !== null && $this->activefilters['builttime'] > self::$elementsmodified[$reportid]) {
 237              return $this->activefilters['values'];
 238          }
 239  
 240          $this->activefilters = ['builttime' => microtime(true), 'values' => []];
 241  
 242          $activefilters = filter_model::get_filter_records($reportid, 'filterorder');
 243          foreach ($activefilters as $filter) {
 244              $instance = $this->get_filter($filter->get('uniqueidentifier'));
 245  
 246              // Ensure the filter is still present and available.
 247              if ($instance !== null && $instance->get_is_available()) {
 248                  $this->activefilters['values'][$instance->get_unique_identifier()] =
 249                      $instance->set_persistent($filter);
 250              }
 251          }
 252  
 253          return $this->activefilters['values'];
 254      }
 255  
 256      /**
 257       * Add conditions from the given entity name to be available to use in a custom report
 258       *
 259       * @param string $entityname
 260       * @param array $include Include only these conditions, if omitted then include all
 261       * @param array $exclude Exclude these conditions, if omitted then exclude none
 262       * @throws coding_exception If both $include and $exclude are non-empty
 263       */
 264      final protected function add_conditions_from_entity(string $entityname, array $include = [], array $exclude = []): void {
 265          if (!empty($include) && !empty($exclude)) {
 266              throw new coding_exception('Cannot specify conditions to include and exclude simultaneously');
 267          }
 268  
 269          $entity = $this->get_entity($entityname);
 270  
 271          // Retrieve filtered conditions from entity, respecting given $include/$exclude parameters.
 272          $conditions = array_filter($entity->get_conditions(), static function(filter $condition) use ($include, $exclude): bool {
 273              if (!empty($include)) {
 274                  return in_array($condition->get_name(), $include);
 275              }
 276  
 277              if (!empty($exclude)) {
 278                  return !in_array($condition->get_name(), $exclude);
 279              }
 280  
 281              return true;
 282          });
 283  
 284          foreach ($conditions as $condition) {
 285              $this->add_condition($condition);
 286          }
 287      }
 288  
 289      /**
 290       * Add default datasource conditions to the report
 291       *
 292       * This method is optional and can be called when the report is created to add the default conditions defined in the
 293       * selected datasource.
 294       */
 295      public function add_default_conditions(): void {
 296          $reportid = $this->get_report_persistent()->get('id');
 297          $conditionidentifiers = $this->get_default_conditions();
 298          foreach ($conditionidentifiers as $uniqueidentifier) {
 299              report::add_report_condition($reportid, $uniqueidentifier);
 300          }
 301  
 302          // Set the default condition values if they have been set in the datasource.
 303          $this->set_condition_values($this->get_default_condition_values());
 304      }
 305  
 306      /**
 307       * Return the conditions that will be added to the report once is created
 308       *
 309       * @return string[]
 310       */
 311      abstract public function get_default_conditions(): array;
 312  
 313      /**
 314       * Return the default condition values that will be added to the report once is created
 315       *
 316       * For any of the default conditions returned by the method {@see get_default_conditions} is
 317       * possible to set the initial values.
 318       *
 319       * @return array
 320       */
 321      public function get_default_condition_values(): array {
 322          return [];
 323      }
 324  
 325      /**
 326       * Return all configured report conditions
 327       *
 328       * @return filter[]
 329       */
 330      public function get_active_conditions(): array {
 331          $reportid = $this->get_report_persistent()->get('id');
 332  
 333          // Determine whether we already retrieved the conditions since the report was last modified.
 334          self::$elementsmodified += [$reportid => -1];
 335          if ($this->activeconditions !== null && $this->activeconditions['builttime'] > self::$elementsmodified[$reportid]) {
 336              return $this->activeconditions['values'];
 337          }
 338  
 339          $this->activeconditions = ['builttime' => microtime(true), 'values' => []];
 340  
 341          $activeconditions = filter_model::get_condition_records($reportid, 'filterorder');
 342          foreach ($activeconditions as $condition) {
 343              $instance = $this->get_condition($condition->get('uniqueidentifier'));
 344  
 345              // Ensure the condition is still present and available.
 346              if ($instance !== null && $instance->get_is_available()) {
 347                  $this->activeconditions['values'][$instance->get_unique_identifier()] =
 348                      $instance->set_persistent($condition);
 349              }
 350          }
 351  
 352          return $this->activeconditions['values'];
 353      }
 354  
 355      /**
 356       * Adds all columns/filters/conditions from the given entity to the report at once
 357       *
 358       * @param string $entityname
 359       */
 360      final protected function add_all_from_entity(string $entityname): void {
 361          $this->add_columns_from_entity($entityname);
 362          $this->add_filters_from_entity($entityname);
 363          $this->add_conditions_from_entity($entityname);
 364      }
 365  
 366      /**
 367       * Adds all columns/filters/conditions from all the entities added to the report at once
 368       */
 369      final protected function add_all_from_entities(): void {
 370          foreach ($this->get_entities() as $entity) {
 371              $this->add_all_from_entity($entity->get_entity_name());
 372          }
 373      }
 374  
 375      /**
 376       * Indicate that report elements have been modified, e.g. columns/filters/conditions have been added, removed or updated
 377       *
 378       * @param int $reportid
 379       */
 380      final public static function report_elements_modified(int $reportid): void {
 381          self::$elementsmodified[$reportid] = microtime(true);
 382      }
 383  }