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.
   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\entities;
  20  
  21  use core\context_helper;
  22  use core_reportbuilder\local\entities\base;
  23  use core_reportbuilder\local\filters\{select, text};
  24  use core_reportbuilder\local\report\{column, filter};
  25  use html_writer;
  26  use lang_string;
  27  use stdClass;
  28  
  29  /**
  30   * Context entity
  31   *
  32   * @package     core
  33   * @copyright   2023 Paul Holden <paulh@moodle.com>
  34   * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  35   */
  36  class context extends base {
  37  
  38      /**
  39       * Database tables that this entity uses and their default aliases
  40       *
  41       * @return array
  42       */
  43      protected function get_default_table_aliases(): array {
  44          return ['context' => 'ctx'];
  45      }
  46  
  47      /**
  48       * The default title for this entity in the list of columns/conditions/filters in the report builder
  49       *
  50       * @return lang_string
  51       */
  52      protected function get_default_entity_title(): lang_string {
  53          return new lang_string('context');
  54      }
  55  
  56      /**
  57       * Initialise the entity
  58       *
  59       * @return base
  60       */
  61      public function initialise(): base {
  62          $columns = $this->get_all_columns();
  63          foreach ($columns as $column) {
  64              $this->add_column($column);
  65          }
  66  
  67          // All the filters defined by the entity can also be used as conditions.
  68          $filters = $this->get_all_filters();
  69          foreach ($filters as $filter) {
  70              $this
  71                  ->add_filter($filter)
  72                  ->add_condition($filter);
  73          }
  74  
  75          return $this;
  76      }
  77  
  78      /**
  79       * Returns list of all available columns
  80       *
  81       * @return column[]
  82       */
  83      protected function get_all_columns(): array {
  84          global $DB;
  85  
  86          $contextalias = $this->get_table_alias('context');
  87  
  88          // Name.
  89          $columns[] = (new column(
  90              'name',
  91              new lang_string('contextname'),
  92              $this->get_entity_name()
  93          ))
  94              ->add_joins($this->get_joins())
  95              ->set_type(column::TYPE_TEXT)
  96              ->add_fields(context_helper::get_preload_record_columns_sql($contextalias))
  97              // Sorting may not order alphabetically, but will at least group contexts together.
  98              ->set_is_sortable(true)
  99              ->add_callback(static function($contextid, stdClass $context): string {
 100                  if ($contextid === null) {
 101                      return '';
 102                  }
 103  
 104                  context_helper::preload_from_record($context);
 105                  return context_helper::instance_by_id($contextid)->get_context_name();
 106              });
 107  
 108          // Link.
 109          $columns[] = (new column(
 110              'link',
 111              new lang_string('contexturl'),
 112              $this->get_entity_name()
 113          ))
 114              ->add_joins($this->get_joins())
 115              ->set_type(column::TYPE_TEXT)
 116              ->add_fields(context_helper::get_preload_record_columns_sql($contextalias))
 117              // Sorting may not order alphabetically, but will at least group contexts together.
 118              ->set_is_sortable(true)
 119              ->add_callback(static function($contextid, stdClass $context): string {
 120                  if ($contextid === null) {
 121                      return '';
 122                  }
 123  
 124                  context_helper::preload_from_record($context);
 125                  $context = context_helper::instance_by_id($contextid);
 126  
 127                  return html_writer::link($context->get_url(), $context->get_context_name());
 128              });
 129  
 130          // Level.
 131          $columns[] = (new column(
 132              'level',
 133              new lang_string('contextlevel'),
 134              $this->get_entity_name()
 135          ))
 136              ->add_joins($this->get_joins())
 137              ->set_type(column::TYPE_INTEGER)
 138              ->add_fields("{$contextalias}.contextlevel")
 139              ->set_is_sortable(true)
 140              // It doesn't make sense to offer integer aggregation methods for this column.
 141              ->set_disabled_aggregation(['avg', 'max', 'min', 'sum'])
 142              ->add_callback(static function(?int $level): string {
 143                  if ($level === null) {
 144                      return '';
 145                  }
 146  
 147                  return context_helper::get_level_name($level);
 148              });
 149  
 150          // Path.
 151          $columns[] = (new column(
 152              'path',
 153              new lang_string('path'),
 154              $this->get_entity_name()
 155          ))
 156              ->add_joins($this->get_joins())
 157              ->set_type(column::TYPE_TEXT)
 158              ->add_field("{$contextalias}.path")
 159              ->set_is_sortable(true);
 160  
 161          // Parent (note we select the parent path in SQL, so that aggregation/grouping is on the parent data itself).
 162          $columns[] = (new column(
 163              'parent',
 164              new lang_string('contextparent'),
 165              $this->get_entity_name()
 166          ))
 167              ->add_joins($this->get_joins())
 168              ->set_type(column::TYPE_TEXT)
 169              // The "path" column looks like "/1/2/3", for context ID 3. In order to select/group by the parent context, we
 170              // concatenate a trailing slash (to prevent partial matches, e.g. "/1/2/31"), then replace "/3/" with empty string.
 171              ->add_field("
 172                  REPLACE(
 173                      " . $DB->sql_concat("{$contextalias}.path", "'/'") . ",
 174                      " . $DB->sql_concat("'/'", $DB->sql_cast_to_char("{$contextalias}.id"), "'/'") . ",
 175                      ''
 176                  )", 'parent'
 177              )
 178              // Sorting may not order alphabetically, but will at least group contexts together.
 179              ->set_is_sortable(true)
 180              ->add_callback(static function (?string $parent): string {
 181  
 182                  // System level (no parent) or null.
 183                  if ($parent === '' || $parent === null) {
 184                      return '';
 185                  }
 186  
 187                  $contextids = explode('/', $parent);
 188                  $contextid = (int) array_pop($contextids);
 189  
 190                  return context_helper::instance_by_id($contextid)->get_context_name();
 191              });
 192  
 193          return $columns;
 194      }
 195  
 196      /**
 197       * Return list of all available filters
 198       *
 199       * @return filter[]
 200       */
 201      protected function get_all_filters(): array {
 202          $contextalias = $this->get_table_alias('context');
 203  
 204          // Level.
 205          $filters[] = (new filter(
 206              select::class,
 207              'level',
 208              new lang_string('contextlevel'),
 209              $this->get_entity_name(),
 210              "{$contextalias}.contextlevel"
 211          ))
 212              ->add_joins($this->get_joins())
 213              ->set_options_callback(static function(): array {
 214                  $levels = context_helper::get_all_levels();
 215  
 216                  return array_map(static function(string $levelclass): string {
 217                      return $levelclass::get_level_name();
 218                  }, $levels);
 219              });
 220  
 221          // Path.
 222          $filters[] = (new filter(
 223              text::class,
 224              'path',
 225              new lang_string('path'),
 226              $this->get_entity_name(),
 227              "{$contextalias}.path"
 228          ))
 229              ->add_joins($this->get_joins());
 230  
 231          return $filters;
 232      }
 233  }