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 401 and 403] [Versions 402 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_files\reportbuilder\local\entities;
  20  
  21  use context;
  22  use context_helper;
  23  use lang_string;
  24  use license_manager;
  25  use html_writer;
  26  use stdClass;
  27  use core_reportbuilder\local\entities\base;
  28  use core_reportbuilder\local\helpers\format;
  29  use core_reportbuilder\local\filters\{boolean_select, date, number, select, text};
  30  use core_reportbuilder\local\report\{column, filter};
  31  
  32  /**
  33   * File entity
  34   *
  35   * @package     core_files
  36   * @copyright   2022 Paul Holden <paulh@moodle.com>
  37   * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  38   */
  39  class file extends base {
  40  
  41      /**
  42       * Database tables that this entity uses and their default aliases
  43       *
  44       * @return array
  45       */
  46      protected function get_default_table_aliases(): array {
  47          return [
  48              'files' => 'f',
  49              'context' => 'fctx',
  50          ];
  51      }
  52  
  53      /**
  54       * The default title for this entity
  55       *
  56       * @return lang_string
  57       */
  58      protected function get_default_entity_title(): lang_string {
  59          return new lang_string('file');
  60      }
  61  
  62      /**
  63       * Initialise the entity
  64       *
  65       * @return base
  66       */
  67      public function initialise(): base {
  68          $columns = $this->get_all_columns();
  69          foreach ($columns as $column) {
  70              $this->add_column($column);
  71          }
  72  
  73          // All the filters defined by the entity can also be used as conditions.
  74          $filters = $this->get_all_filters();
  75          foreach ($filters as $filter) {
  76              $this
  77                  ->add_filter($filter)
  78                  ->add_condition($filter);
  79          }
  80  
  81          return $this;
  82      }
  83  
  84      /**
  85       * Returns list of all available columns
  86       *
  87       * @return column[]
  88       */
  89      protected function get_all_columns(): array {
  90          $filesalias = $this->get_table_alias('files');
  91          $contextalias = $this->get_table_alias('context');
  92  
  93          // Name.
  94          $columns[] = (new column(
  95              'name',
  96              new lang_string('filename', 'core_repository'),
  97              $this->get_entity_name()
  98          ))
  99              ->add_joins($this->get_joins())
 100              ->set_type(column::TYPE_TEXT)
 101              ->add_field("{$filesalias}.filename")
 102              ->set_is_sortable(true);
 103  
 104          // Size.
 105          $columns[] = (new column(
 106              'size',
 107              new lang_string('size'),
 108              $this->get_entity_name()
 109          ))
 110              ->add_joins($this->get_joins())
 111              ->set_type(column::TYPE_INTEGER)
 112              ->add_field("{$filesalias}.filesize")
 113              ->add_field("CASE WHEN {$filesalias}.filename = '.' THEN 1 ELSE 0 END", 'directory')
 114              ->set_is_sortable(true)
 115              ->add_callback(static function($filesize, stdClass $fileinfo): string {
 116                  // Absent file size and/or directory should not return output.
 117                  if ($fileinfo->filesize === null || $fileinfo->directory) {
 118                      return '';
 119                  }
 120                  return display_size($fileinfo->filesize);
 121              });
 122  
 123          // Path.
 124          $columns[] = (new column(
 125              'path',
 126              new lang_string('path'),
 127              $this->get_entity_name()
 128          ))
 129              ->add_joins($this->get_joins())
 130              ->set_type(column::TYPE_TEXT)
 131              ->add_field("{$filesalias}.filepath")
 132              ->set_is_sortable(true);
 133  
 134          // Type.
 135          $columns[] = (new column(
 136              'type',
 137              new lang_string('type', 'core_repository'),
 138              $this->get_entity_name()
 139          ))
 140              ->add_joins($this->get_joins())
 141              ->set_type(column::TYPE_TEXT)
 142              ->add_field("{$filesalias}.mimetype")
 143              ->add_field("CASE WHEN {$filesalias}.filename = '.' THEN 1 ELSE 0 END", 'directory')
 144              ->set_is_sortable(true)
 145              ->add_callback(static function($mimetype, stdClass $fileinfo): string {
 146                  global $CFG;
 147                  require_once("{$CFG->libdir}/filelib.php");
 148  
 149                  // Absent mime type and/or directory has pre-determined output.
 150                  if ($fileinfo->mimetype === null && !$fileinfo->directory) {
 151                      return '';
 152                  } else if ($fileinfo->directory) {
 153                      return get_string('directory');
 154                  }
 155  
 156                  return get_mimetype_description($fileinfo->mimetype);
 157              });
 158  
 159          // Author.
 160          $columns[] = (new column(
 161              'author',
 162              new lang_string('author', 'core_repository'),
 163              $this->get_entity_name()
 164          ))
 165              ->add_joins($this->get_joins())
 166              ->set_type(column::TYPE_TEXT)
 167              ->add_field("{$filesalias}.author")
 168              ->set_is_sortable(true);
 169  
 170          // License.
 171          $columns[] = (new column(
 172              'license',
 173              new lang_string('license', 'core_repository'),
 174              $this->get_entity_name()
 175          ))
 176              ->add_joins($this->get_joins())
 177              ->set_type(column::TYPE_TEXT)
 178              ->add_field("{$filesalias}.license")
 179              ->set_is_sortable(true)
 180              ->add_callback(static function(?string $license): string {
 181                  global $CFG;
 182                  require_once("{$CFG->libdir}/licenselib.php");
 183  
 184                  $licenses = license_manager::get_licenses();
 185                  if ($license === null || !array_key_exists($license, $licenses)) {
 186                      return '';
 187                  }
 188                  return $licenses[$license]->fullname;
 189              });
 190  
 191          // Context.
 192          $columns[] = (new column(
 193              'context',
 194              new lang_string('context'),
 195              $this->get_entity_name()
 196          ))
 197              ->add_joins($this->get_joins())
 198              ->set_type(column::TYPE_TEXT)
 199              ->add_join("LEFT JOIN {context} {$contextalias} ON {$contextalias}.id = {$filesalias}.contextid")
 200              ->add_fields("{$filesalias}.contextid, " . context_helper::get_preload_record_columns_sql($contextalias))
 201              // Sorting may not order alphabetically, but will at least group contexts together.
 202              ->set_is_sortable(true)
 203              ->set_is_deprecated('See \'context:name\' for replacement')
 204              ->add_callback(static function($contextid, stdClass $context): string {
 205                  if ($contextid === null) {
 206                      return '';
 207                  }
 208  
 209                  context_helper::preload_from_record($context);
 210                  return context::instance_by_id($contextid)->get_context_name();
 211              });
 212  
 213          // Context link.
 214          $columns[] = (new column(
 215              'contexturl',
 216              new lang_string('contexturl'),
 217              $this->get_entity_name()
 218          ))
 219              ->add_joins($this->get_joins())
 220              ->set_type(column::TYPE_TEXT)
 221              ->add_join("LEFT JOIN {context} {$contextalias} ON {$contextalias}.id = {$filesalias}.contextid")
 222              ->add_fields("{$filesalias}.contextid, " . context_helper::get_preload_record_columns_sql($contextalias))
 223              // Sorting may not order alphabetically, but will at least group contexts together.
 224              ->set_is_sortable(true)
 225              ->set_is_deprecated('See \'context:link\' for replacement')
 226              ->add_callback(static function($contextid, stdClass $context): string {
 227                  if ($contextid === null) {
 228                      return '';
 229                  }
 230  
 231                  context_helper::preload_from_record($context);
 232                  $context = context::instance_by_id($contextid);
 233  
 234                  return html_writer::link($context->get_url(), $context->get_context_name());
 235              });
 236  
 237          // Content hash.
 238          $columns[] = (new column(
 239               'contenthash',
 240              new lang_string('contenthash', 'core_files'),
 241              $this->get_entity_name()
 242          ))
 243              ->add_joins($this->get_joins())
 244              ->set_type(column::TYPE_TEXT)
 245              ->add_field("{$filesalias}.contenthash")
 246              ->set_is_sortable(true);
 247  
 248          // Component.
 249          $columns[] = (new column(
 250              'component',
 251              new lang_string('plugin'),
 252              $this->get_entity_name()
 253          ))
 254              ->add_joins($this->get_joins())
 255              ->set_type(column::TYPE_TEXT)
 256              ->add_fields("{$filesalias}.component")
 257              ->set_is_sortable(true);
 258  
 259          // Area.
 260          $columns[] = (new column(
 261              'area',
 262              new lang_string('pluginarea'),
 263              $this->get_entity_name()
 264          ))
 265              ->add_joins($this->get_joins())
 266              ->set_type(column::TYPE_TEXT)
 267              ->add_fields("{$filesalias}.filearea")
 268              ->set_is_sortable(true);
 269  
 270          // Item ID.
 271          $columns[] = (new column(
 272              'itemid',
 273              new lang_string('pluginitemid'),
 274              $this->get_entity_name()
 275          ))
 276              ->add_joins($this->get_joins())
 277              ->set_type(column::TYPE_INTEGER)
 278              ->add_fields("{$filesalias}.itemid")
 279              ->set_is_sortable(true)
 280              ->set_disabled_aggregation_all();
 281  
 282          // Time created.
 283          $columns[] = (new column(
 284              'timecreated',
 285              new lang_string('timecreated', 'core_reportbuilder'),
 286              $this->get_entity_name()
 287          ))
 288              ->add_joins($this->get_joins())
 289              ->set_type(column::TYPE_TIMESTAMP)
 290              ->add_field("{$filesalias}.timecreated")
 291              ->add_callback([format::class, 'userdate'])
 292              ->set_is_sortable(true);
 293  
 294          return $columns;
 295      }
 296  
 297      /**
 298       * Return list of all available filters
 299       *
 300       * @return filter[]
 301       */
 302      protected function get_all_filters(): array {
 303          $filesalias = $this->get_table_alias('files');
 304  
 305          // Directory.
 306          $filters[] = (new filter(
 307              boolean_select::class,
 308              'directory',
 309              new lang_string('directory'),
 310              $this->get_entity_name(),
 311              "CASE WHEN {$filesalias}.filename = '.' THEN 1 ELSE 0 END"
 312          ))
 313              ->add_joins($this->get_joins());
 314  
 315          // Draft.
 316          $filters[] = (new filter(
 317              boolean_select::class,
 318              'draft',
 319              new lang_string('areauserdraft', 'core_repository'),
 320              $this->get_entity_name(),
 321              "CASE WHEN {$filesalias}.component = 'user' AND {$filesalias}.filearea = 'draft' THEN 1 ELSE 0 END"
 322          ))
 323              ->add_joins($this->get_joins());
 324  
 325          // Name.
 326          $filters[] = (new filter(
 327              text::class,
 328              'name',
 329              new lang_string('filename', 'core_repository'),
 330              $this->get_entity_name(),
 331              "{$filesalias}.filename"
 332          ))
 333              ->add_joins($this->get_joins());
 334  
 335          // Size.
 336          $filters[] = (new filter(
 337              number::class,
 338              'size',
 339              new lang_string('size'),
 340              $this->get_entity_name(),
 341              "{$filesalias}.filesize"
 342          ))
 343              ->add_joins($this->get_joins())
 344              ->set_limited_operators([
 345                  number::ANY_VALUE,
 346                  number::LESS_THAN,
 347                  number::GREATER_THAN,
 348                  number::RANGE,
 349              ]);
 350  
 351          // License (consider null = 'unknown/license not specified' for filtering purposes).
 352          $filters[] = (new filter(
 353              select::class,
 354              'license',
 355              new lang_string('license', 'core_repository'),
 356              $this->get_entity_name(),
 357              "COALESCE({$filesalias}.license, 'unknown')"
 358          ))
 359              ->add_joins($this->get_joins())
 360              ->set_options_callback(static function(): array {
 361                  global $CFG;
 362                  require_once("{$CFG->libdir}/licenselib.php");
 363  
 364                  $licenses = license_manager::get_licenses();
 365  
 366                  return array_map(static function(stdClass $license): string {
 367                      return $license->fullname;
 368                  }, $licenses);
 369              });
 370  
 371          // Content hash.
 372          $filters[] = (new filter(
 373              text::class,
 374              'contenthash',
 375              new lang_string('contenthash', 'core_files'),
 376              $this->get_entity_name(),
 377              "{$filesalias}.contenthash"
 378          ))
 379              ->add_joins($this->get_joins());
 380  
 381          // Time created.
 382          $filters[] = (new filter(
 383              date::class,
 384              'timecreated',
 385              new lang_string('timecreated', 'core_reportbuilder'),
 386              $this->get_entity_name(),
 387              "{$filesalias}.timecreated"
 388          ))
 389              ->add_joins($this->get_joins())
 390              ->set_limited_operators([
 391                  date::DATE_ANY,
 392                  date::DATE_RANGE,
 393                  date::DATE_LAST,
 394                  date::DATE_CURRENT,
 395              ]);
 396  
 397          return $filters;
 398      }
 399  }