Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.0.x will end 8 May 2023 (12 months).
  • Bug fixes for security issues in 4.0.x will end 13 November 2023 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: the minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is also supported.

Differences Between: [Versions 400 and 401] [Versions 400 and 402] [Versions 400 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\local\helpers;
  20  
  21  use core_reportbuilder\local\filters\boolean_select;
  22  use core_reportbuilder\local\filters\date;
  23  use core_reportbuilder\local\filters\number;
  24  use core_reportbuilder\local\filters\select;
  25  use core_reportbuilder\local\filters\text;
  26  use core_reportbuilder\local\report\column;
  27  use core_reportbuilder\local\report\filter;
  28  use lang_string;
  29  use stdClass;
  30  use core_customfield\data_controller;
  31  use core_customfield\field_controller;
  32  use core_customfield\handler;
  33  
  34  /**
  35   * Helper class for course custom fields.
  36   *
  37   * @package   core_reportbuilder
  38   * @copyright 2021 Sara Arjona <sara@moodle.com> based on David Matamoros <davidmc@moodle.com> code.
  39   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  40   */
  41  class custom_fields {
  42  
  43      /** @var string $entityname Name of the entity */
  44      private $entityname;
  45  
  46      /** @var handler $handler The handler for the customfields */
  47      private $handler;
  48  
  49      /** @var int $tablefieldalias The table alias and the field name (table.field) that matches the customfield instanceid. */
  50      private $tablefieldalias;
  51  
  52      /** @var array additional joins */
  53      private $joins = [];
  54  
  55      /**
  56       * Class customfields constructor.
  57       *
  58       * @param string $tablefieldalias table alias and the field name (table.field) that matches the customfield instanceid.
  59       * @param string $entityname name of the entity in the report where we add custom fields.
  60       * @param string $component component name of full frankenstyle plugin name.
  61       * @param string $area name of the area (each component/plugin may define handlers for multiple areas).
  62       * @param int $itemid item id if the area uses them (usually not used).
  63       */
  64      public function __construct(string $tablefieldalias, string $entityname, string $component, string $area, int $itemid = 0) {
  65          $this->tablefieldalias = $tablefieldalias;
  66          $this->entityname = $entityname;
  67          $this->handler = handler::get_handler($component, $area, $itemid);
  68      }
  69  
  70      /**
  71       * Additional join that is needed.
  72       *
  73       * @param string $join
  74       * @return self
  75       */
  76      public function add_join(string $join): self {
  77          $this->joins[trim($join)] = trim($join);
  78          return $this;
  79      }
  80  
  81      /**
  82       * Additional joins that are needed.
  83       *
  84       * @param array $joins
  85       * @return self
  86       */
  87      public function add_joins(array $joins): self {
  88          foreach ($joins as $join) {
  89              $this->add_join($join);
  90          }
  91          return $this;
  92      }
  93  
  94      /**
  95       * Return joins
  96       *
  97       * @return string[]
  98       */
  99      private function get_joins(): array {
 100          return array_values($this->joins);
 101      }
 102  
 103      /**
 104       * Gets the custom fields columns for the report.
 105       *
 106       * Column will be named as 'customfield_' + customfield shortname.
 107       *
 108       * @return column[]
 109       */
 110      public function get_columns(): array {
 111          $columns = [];
 112  
 113          $categorieswithfields = $this->handler->get_categories_with_fields();
 114          foreach ($categorieswithfields as $fieldcategory) {
 115              $categoryfields = $fieldcategory->get_fields();
 116              foreach ($categoryfields as $field) {
 117                  $customdatatablealias = database::generate_alias();
 118  
 119                  $datacontroller = data_controller::create(0, null, $field);
 120                  $datafield = $datacontroller->datafield();
 121  
 122                  // Select enough fields to re-create and format each custom field instance value.
 123                  $selectfields = "{$customdatatablealias}.{$datafield}, {$customdatatablealias}.id,
 124                      {$customdatatablealias}.contextid";
 125                  if ($datafield === 'value') {
 126                      // We will take the format into account when displaying the individual values.
 127                      $selectfields .= ", {$customdatatablealias}.valueformat";
 128                  }
 129  
 130                  $columntype = $this->get_column_type($field, $datafield);
 131  
 132                  $newcolumn = (new column(
 133                      'customfield_' . $field->get('shortname'),
 134                      new lang_string('customfieldcolumn', 'core_reportbuilder', $field->get_formatted_name()),
 135                      $this->entityname
 136                  ))
 137                      ->add_joins($this->get_joins())
 138                      ->add_join("LEFT JOIN {customfield_data} {$customdatatablealias} " .
 139                          "ON {$customdatatablealias}.fieldid = " . $field->get('id') . " " .
 140                          "AND {$customdatatablealias}.instanceid = {$this->tablefieldalias}")
 141                      ->add_fields($selectfields)
 142                      ->set_type($columntype)
 143                      ->set_is_sortable($columntype !== column::TYPE_LONGTEXT)
 144                      ->add_callback([$this, 'customfield_value'], $field)
 145                      // Important. If the handler implements can_view() function, it will be called with parameter $instanceid=0.
 146                      // This means that per-instance access validation will be ignored.
 147                      ->set_is_available($this->handler->can_view($field, 0));
 148  
 149                  $columns[] = $newcolumn;
 150              }
 151          }
 152          return $columns;
 153      }
 154  
 155      /**
 156       * Returns the column type
 157       *
 158       * @param field_controller $field
 159       * @param string $datafield
 160       * @return int
 161       */
 162      private function get_column_type(field_controller $field, string $datafield): int {
 163          if ($field->get('type') === 'checkbox') {
 164              return column::TYPE_BOOLEAN;
 165          }
 166  
 167          if ($field->get('type') === 'date') {
 168              return column::TYPE_TIMESTAMP;
 169          }
 170  
 171          if ($datafield === 'intvalue') {
 172              return column::TYPE_INTEGER;
 173          }
 174  
 175          if ($datafield === 'decvalue') {
 176              return column::TYPE_FLOAT;
 177          }
 178  
 179          if ($datafield === 'value') {
 180              return column::TYPE_LONGTEXT;
 181          }
 182  
 183          return column::TYPE_TEXT;
 184      }
 185  
 186      /**
 187       * Returns all available filters on custom fields.
 188       *
 189       * Filter will be named as 'customfield_' + customfield shortname.
 190       *
 191       * @return filter[]
 192       */
 193      public function get_filters(): array {
 194          $filters = [];
 195  
 196          $categorieswithfields = $this->handler->get_categories_with_fields();
 197          foreach ($categorieswithfields as $fieldcategory) {
 198              $categoryfields = $fieldcategory->get_fields();
 199              foreach ($categoryfields as $field) {
 200                  $customdatatablealias = database::generate_alias();
 201  
 202                  $datacontroller = data_controller::create(0, null, $field);
 203                  $datafield = $datacontroller->datafield();
 204                  $typeclass = $this->get_filter_class_type($datacontroller);
 205  
 206                  $filter = (new filter(
 207                      $typeclass,
 208                      'customfield_' . $field->get('shortname'),
 209                      new lang_string('customfieldcolumn', 'core_reportbuilder', $field->get_formatted_name()),
 210                      $this->entityname,
 211                      "{$customdatatablealias}.{$datafield}"
 212                  ))
 213                      ->add_joins($this->get_joins())
 214                      ->add_join("LEFT JOIN {customfield_data} {$customdatatablealias} " .
 215                          "ON {$customdatatablealias}.fieldid = " . $field->get('id') . " " .
 216                          "AND {$customdatatablealias}.instanceid = {$this->tablefieldalias}");
 217  
 218                  // Options are stored inside configdata json string and we need to convert it to array.
 219                  if ($field->get('type') === 'select') {
 220                      $filter->set_options_callback(static function() use ($field): array {
 221                          $options = explode("\r\n", $field->get_configdata_property('options'));
 222                          // Method set_options starts using array at index 1. we shift one position on this array.
 223                          // In course settings this menu has an empty option and we need to respect that.
 224                          array_unshift($options, " ");
 225                          unset($options[0]);
 226                          return $options;
 227                      });
 228                  }
 229  
 230                  $filters[] = $filter;
 231              }
 232          }
 233          return $filters;
 234      }
 235  
 236      /**
 237       * Returns class for the filter element that should be used for the field
 238       *
 239       * In some situation we can assume what kind of data is stored in the customfield plugin and we can
 240       * display appropriate filter form element. For all others assume text filter.
 241       *
 242       * @param data_controller $datacontroller
 243       * @return string
 244       */
 245      private function get_filter_class_type(data_controller $datacontroller): string {
 246          $type = $datacontroller->get_field()->get('type');
 247  
 248          switch ($type) {
 249              case 'checkbox':
 250                  $classtype = boolean_select::class;
 251                  break;
 252              case 'date':
 253                  $classtype = date::class;
 254                  break;
 255              case 'select':
 256                  $classtype = select::class;
 257                  break;
 258              default:
 259                  // To support third party field type we need to account for stored numbers.
 260                  $datafield = $datacontroller->datafield();
 261                  if ($datafield === 'intvalue' || $datafield === 'decvalue') {
 262                      $classtype = number::class;
 263                  } else {
 264                      $classtype = text::class;
 265                  }
 266                  break;
 267          }
 268  
 269          return $classtype;
 270      }
 271  
 272      /**
 273       * Format for custom fields value. We get the correct custom field value using export_value method.
 274       *
 275       * @param mixed $value Current value.
 276       * @param stdClass $row Full row.
 277       * @param field_controller $field Field controller object.
 278       * @return mixed|null
 279       */
 280      public function customfield_value($value, stdClass $row, field_controller $field) {
 281          $data = data_controller::create(0, (object)$row, $field);
 282          return $data->export_value();
 283      }
 284  }