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\systemreports;
  20  
  21  use context_system;
  22  use core_reportbuilder\local\helpers\audience;
  23  use core_reportbuilder\local\helpers\database;
  24  use html_writer;
  25  use lang_string;
  26  use moodle_url;
  27  use pix_icon;
  28  use stdClass;
  29  use core_reportbuilder\datasource;
  30  use core_reportbuilder\manager;
  31  use core_reportbuilder\system_report;
  32  use core_reportbuilder\local\entities\user;
  33  use core_reportbuilder\local\filters\date;
  34  use core_reportbuilder\local\filters\text;
  35  use core_reportbuilder\local\filters\select;
  36  use core_reportbuilder\local\helpers\format;
  37  use core_reportbuilder\local\report\action;
  38  use core_reportbuilder\local\report\column;
  39  use core_reportbuilder\local\report\filter;
  40  use core_reportbuilder\output\report_name_editable;
  41  use core_reportbuilder\local\models\report;
  42  use core_reportbuilder\permission;
  43  
  44  /**
  45   * Reports list
  46   *
  47   * @package     core_reportbuilder
  48   * @copyright   2021 David Matamoros <davidmc@moodle.com>
  49   * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  50   */
  51  class reports_list extends system_report {
  52  
  53      /**
  54       * The name of our internal report entity
  55       *
  56       * @return string
  57       */
  58      private function get_report_entity_name(): string {
  59          return 'report';
  60      }
  61  
  62      /**
  63       * Initialise the report
  64       */
  65      protected function initialise(): void {
  66          $this->set_main_table('reportbuilder_report', 'rb');
  67          $this->add_base_condition_simple('rb.type', self::TYPE_CUSTOM_REPORT);
  68  
  69          // Select fields required for actions, permission checks, and row class callbacks.
  70          $this->add_base_fields('rb.id, rb.name, rb.source, rb.type, rb.usercreated, rb.contextid');
  71  
  72          // If user can't view all reports, limit the returned list to those reports they can see.
  73          [$where, $params] = $this->filter_by_allowed_reports_sql();
  74          if (!empty($where)) {
  75              $this->add_base_condition_sql($where, $params);
  76          }
  77  
  78          // Join user entity for "User modified" column.
  79          $entityuser = new user();
  80          $entityuseralias = $entityuser->get_table_alias('user');
  81  
  82          $this->add_entity($entityuser
  83              ->add_join("JOIN {user} {$entityuseralias} ON {$entityuseralias}.id = rb.usermodified")
  84          );
  85  
  86          // Define our internal entity for report elements.
  87          $this->annotate_entity($this->get_report_entity_name(),
  88              new lang_string('customreports', 'core_reportbuilder'));
  89  
  90          $this->add_columns();
  91          $this->add_filters();
  92          $this->add_actions();
  93  
  94          $this->set_downloadable(false);
  95      }
  96  
  97      /**
  98       * Ensure we can view the report
  99       *
 100       * @return bool
 101       */
 102      protected function can_view(): bool {
 103          return permission::can_view_reports_list();
 104      }
 105  
 106      /**
 107       * Dim the table row for invalid datasource
 108       *
 109       * @param stdClass $row
 110       * @return string
 111       */
 112      public function get_row_class(stdClass $row): string {
 113          return $this->report_source_valid($row->source) ? '' : 'text-muted';
 114      }
 115  
 116      /**
 117       * Add columns to report
 118       */
 119      protected function add_columns(): void {
 120          $tablealias = $this->get_main_table_alias();
 121  
 122          // Report name column.
 123          $this->add_column((new column(
 124              'name',
 125              new lang_string('name'),
 126              $this->get_report_entity_name()
 127          ))
 128              ->set_type(column::TYPE_TEXT)
 129              ->add_fields("{$tablealias}.name, {$tablealias}.id")
 130              ->set_is_sortable(true)
 131              ->add_callback(function(string $value, stdClass $row) {
 132                  global $PAGE;
 133  
 134                  $reportid = (int) $row->id;
 135  
 136                  $editable = new report_name_editable($reportid);
 137                  return $editable->render($PAGE->get_renderer('core'));
 138              })
 139          );
 140  
 141          // Report source column.
 142          $this->add_column((new column(
 143              'source',
 144              new lang_string('reportsource', 'core_reportbuilder'),
 145              $this->get_report_entity_name()
 146          ))
 147              ->set_type(column::TYPE_TEXT)
 148              ->add_fields("{$tablealias}.source")
 149              ->set_is_sortable(true)
 150              ->add_callback(function(string $value, stdClass $row) {
 151                  if (!$this->report_source_valid($value)) {
 152                      // Add danger badge if report source is not valid (either it's missing, or has errors).
 153                      return html_writer::span(get_string('errorsourceinvalid', 'core_reportbuilder'), 'badge badge-danger');
 154                  }
 155  
 156                  return call_user_func([$value, 'get_name']);
 157              })
 158          );
 159  
 160          // Time created column.
 161          $this->add_column((new column(
 162              'timecreated',
 163              new lang_string('timecreated', 'core_reportbuilder'),
 164              $this->get_report_entity_name()
 165          ))
 166              ->set_type(column::TYPE_TIMESTAMP)
 167              ->add_fields("{$tablealias}.timecreated")
 168              ->set_is_sortable(true)
 169              ->add_callback([format::class, 'userdate'])
 170          );
 171  
 172          // Time modified column.
 173          $this->add_column((new column(
 174              'timemodified',
 175              new lang_string('timemodified', 'core_reportbuilder'),
 176              $this->get_report_entity_name()
 177          ))
 178              ->set_type(column::TYPE_TIMESTAMP)
 179              ->add_fields("{$tablealias}.timemodified")
 180              ->set_is_sortable(true)
 181              ->add_callback([format::class, 'userdate'])
 182          );
 183  
 184          // The user who modified the report.
 185          $this->add_column_from_entity('user:fullname')
 186              ->set_title(new lang_string('usermodified', 'reportbuilder'));
 187  
 188          // Initial sorting.
 189          $this->set_initial_sort_column('report:timecreated', SORT_DESC);
 190      }
 191  
 192      /**
 193       * Add filters to report
 194       */
 195      protected function add_filters(): void {
 196          $tablealias = $this->get_main_table_alias();
 197  
 198          // Name filter.
 199          $this->add_filter((new filter(
 200              text::class,
 201              'name',
 202              new lang_string('name'),
 203              $this->get_report_entity_name(),
 204              "{$tablealias}.name"
 205          )));
 206  
 207          // Source filter.
 208          $this->add_filter((new filter(
 209              select::class,
 210              'source',
 211              new lang_string('reportsource', 'core_reportbuilder'),
 212              $this->get_report_entity_name(),
 213              "{$tablealias}.source"
 214          ))
 215              ->set_options_callback(static function(): array {
 216                  return manager::get_report_datasources();
 217              })
 218          );
 219  
 220          // Time created filter.
 221          $this->add_filter((new filter(
 222              date::class,
 223              'timecreated',
 224              new lang_string('timecreated', 'core_reportbuilder'),
 225              $this->get_report_entity_name(),
 226              "{$tablealias}.timecreated"
 227          ))
 228              ->set_limited_operators([
 229                  date::DATE_ANY,
 230                  date::DATE_RANGE,
 231              ])
 232          );
 233      }
 234  
 235      /**
 236       * Add actions to report
 237       */
 238      protected function add_actions(): void {
 239          // Edit content action.
 240          $this->add_action((new action(
 241              new moodle_url('/reportbuilder/edit.php', ['id' => ':id']),
 242              new pix_icon('t/right', ''),
 243              [],
 244              false,
 245              new lang_string('editreportcontent', 'core_reportbuilder')
 246          ))
 247              ->add_callback(function(stdClass $row): bool {
 248                  return $this->report_source_valid($row->source) && permission::can_edit_report(new report(0, $row));
 249              })
 250          );
 251  
 252          // Edit details action.
 253          $this->add_action((new action(
 254              new moodle_url('#'),
 255              new pix_icon('t/edit', ''),
 256              ['data-action' => 'report-edit', 'data-report-id' => ':id'],
 257              false,
 258              new lang_string('editreportdetails', 'core_reportbuilder')
 259          ))
 260              ->add_callback(function(stdClass $row): bool {
 261                  return $this->report_source_valid($row->source) && permission::can_edit_report(new report(0, $row));
 262              })
 263          );
 264  
 265          // Preview action.
 266          $this->add_action((new action(
 267              new moodle_url('/reportbuilder/view.php', ['id' => ':id']),
 268              new pix_icon('i/search', ''),
 269              [],
 270              false,
 271              new lang_string('viewreport', 'core_reportbuilder')
 272          ))
 273              ->add_callback(function(stdClass $row): bool {
 274                  // We check this only to give the action to editors, because normal users can just click on the report name.
 275                  return $this->report_source_valid($row->source) && permission::can_edit_report(new report(0, $row));
 276              })
 277          );
 278  
 279          // Delete action.
 280          $this->add_action((new action(
 281              new moodle_url('#'),
 282              new pix_icon('t/delete', ''),
 283              ['data-action' => 'report-delete', 'data-report-id' => ':id', 'data-report-name' => ':name'],
 284              false,
 285              new lang_string('deletereport', 'core_reportbuilder')
 286          ))
 287              ->add_callback(function(stdClass $row): bool {
 288  
 289                  // Ensure data name attribute is properly formatted.
 290                  $report = new report(0, $row);
 291                  $row->name = $report->get_formatted_name();
 292  
 293                  // We don't check whether report is valid to ensure editor can always delete them.
 294                  return permission::can_edit_report($report);
 295              })
 296          );
 297      }
 298  
 299      /**
 300       * Helper to determine whether given report source is valid (it both exists, and is available)
 301       *
 302       * @param string $source
 303       * @return bool
 304       */
 305      private function report_source_valid(string $source): bool {
 306          return manager::report_source_exists($source, datasource::class) && manager::report_source_available($source);
 307      }
 308  
 309      /**
 310       * Filters the list of reports to return only the ones the user has access to
 311       *
 312       * - A user with 'editall' capability will have access to all reports.
 313       * - A user with 'edit' capability will have access to:
 314       *      - Those reports this user has created.
 315       *      - Those reports this user is in audience of.
 316       * - A user with 'view' capability will have access to:
 317       *      - Those reports this user is in audience of.
 318       *
 319       * @return array
 320       */
 321      private function filter_by_allowed_reports_sql(): array {
 322          global $DB, $USER;
 323  
 324          // If user can't view all reports, limit the returned list to those reports they can see.
 325          if (!has_capability('moodle/reportbuilder:editall', context_system::instance())) {
 326              $reports = audience::user_reports_list();
 327  
 328              if (has_capability('moodle/reportbuilder:edit', context_system::instance())) {
 329                  // User can always see own reports and also those reports user is in audience of.
 330                  $paramuserid = database::generate_param_name();
 331  
 332                  if (empty($reports)) {
 333                      return ["rb.usercreated = :{$paramuserid}", [$paramuserid => $USER->id]];
 334                  }
 335  
 336                  $prefix = database::generate_param_name() . '_';
 337                  [$where, $params] = $DB->get_in_or_equal($reports, SQL_PARAMS_NAMED, $prefix);
 338  
 339                  $params = array_merge($params, [$paramuserid => $USER->id]);
 340  
 341                  return ["(rb.usercreated = :{$paramuserid} OR rb.id {$where})", $params];
 342  
 343              }
 344  
 345              // User has view capability. User can only see those reports user is in audience of.
 346              if (empty($reports)) {
 347                  return ['1=2', []];
 348              }
 349  
 350              $prefix = database::generate_param_name() . '_';
 351              [$where, $params] = $DB->get_in_or_equal($reports, SQL_PARAMS_NAMED, $prefix);
 352              return ["rb.id {$where}", $params];
 353          }
 354  
 355          return ['', []];
 356      }
 357  }