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 402] [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\local\helpers;
  20  
  21  use cache;
  22  use context;
  23  use context_system;
  24  use core_collator;
  25  use core_component;
  26  use core_plugin_manager;
  27  use core_reportbuilder\local\audiences\base;
  28  use core_reportbuilder\local\models\audience as audience_model;
  29  
  30  /**
  31   * Class containing report audience helper methods
  32   *
  33   * @package     core_reportbuilder
  34   * @copyright   2021 David Matamoros <davidmc@moodle.com>
  35   * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  36   */
  37  class audience {
  38  
  39      /**
  40       * Return audience instances for a given report. Note that any records pointing to invalid audience types will be excluded
  41       *
  42       * @param int $reportid
  43       * @return base[]
  44       */
  45      public static function get_base_records(int $reportid): array {
  46          $records = audience_model::get_records(['reportid' => $reportid], 'id');
  47  
  48          $instances = array_map(static function(audience_model $audience): ?base {
  49              return base::instance(0, $audience->to_record());
  50          }, $records);
  51  
  52          // Filter and remove null elements (invalid audience types).
  53          return array_filter($instances);
  54      }
  55  
  56      /**
  57       * Returns list of report IDs that the specified user can access, based on audience configuration. This can be expensive if the
  58       * site has lots of reports, with lots of audiences, so we cache the result for the duration of the users session
  59       *
  60       * @param int|null $userid User ID to check, or the current user if omitted
  61       * @return int[]
  62       */
  63      public static function get_allowed_reports(?int $userid = null): array {
  64          global $USER, $DB;
  65  
  66          $userid = $userid ?: (int) $USER->id;
  67  
  68          // Prepare cache, if we previously stored the users allowed reports then return that.
  69          $cache = cache::make('core', 'reportbuilder_allowed_reports');
  70          $cachedreports = $cache->get($userid);
  71          if ($cachedreports !== false) {
  72              return $cachedreports;
  73          }
  74  
  75          $allowedreports = [];
  76          $reportaudiences = [];
  77  
  78          // Retrieve all audiences and group them by report for convenience.
  79          $audiences = audience_model::get_records();
  80          foreach ($audiences as $audience) {
  81              $reportaudiences[$audience->get('reportid')][] = $audience;
  82          }
  83  
  84          foreach ($reportaudiences as $reportid => $audiences) {
  85  
  86              // Generate audience SQL based on those for the current report.
  87              [$wheres, $params] = self::user_audience_sql($audiences);
  88              if (count($wheres) === 0) {
  89                  continue;
  90              }
  91  
  92              $paramuserid = database::generate_param_name();
  93              $params[$paramuserid] = $userid;
  94  
  95              $sql = "SELECT DISTINCT(u.id)
  96                        FROM {user} u
  97                       WHERE (" . implode(' OR ', $wheres) . ")
  98                         AND u.deleted = 0
  99                         AND u.id = :{$paramuserid}";
 100  
 101              // If we have a matching record, user can view the report.
 102              if ($DB->record_exists_sql($sql, $params)) {
 103                  $allowedreports[] = $reportid;
 104              }
 105          }
 106  
 107          // Store users allowed reports in cache.
 108          $cache->set($userid, $allowedreports);
 109  
 110          return $allowedreports;
 111      }
 112  
 113      /**
 114       * Purge the audience cache of allowed reports
 115       */
 116      public static function purge_caches(): void {
 117          cache::make('core', 'reportbuilder_allowed_reports')->purge();
 118      }
 119  
 120      /**
 121       * Generate SQL select clause and params for selecting reports specified user can access, based on audience configuration
 122       *
 123       * @param string $reporttablealias
 124       * @param int|null $userid User ID to check, or the current user if omitted
 125       * @return array
 126       */
 127      public static function user_reports_list_sql(string $reporttablealias, ?int $userid = null): array {
 128          global $DB;
 129  
 130          $allowedreports = self::get_allowed_reports($userid);
 131  
 132          if (empty($allowedreports)) {
 133              return ['1=0', []];
 134          }
 135  
 136          // Get all sql audiences.
 137          $prefix = database::generate_param_name() . '_';
 138          [$select, $params] = $DB->get_in_or_equal($allowedreports, SQL_PARAMS_NAMED, $prefix);
 139          $sql = "{$reporttablealias}.id {$select}";
 140  
 141          return [$sql, $params];
 142      }
 143  
 144      /**
 145       * Return list of report ID's specified user can access, based on audience configuration
 146       *
 147       * @param int|null $userid User ID to check, or the current user if omitted
 148       * @return int[]
 149       */
 150      public static function user_reports_list(?int $userid = null): array {
 151          global $DB;
 152  
 153          [$select, $params] = self::user_reports_list_sql('rb', $userid);
 154          $sql = "SELECT rb.id
 155                    FROM {reportbuilder_report} rb
 156                   WHERE {$select}";
 157  
 158          return $DB->get_fieldset_sql($sql, $params);
 159      }
 160  
 161      /**
 162       * Returns SQL to limit the list of reports to those that the given user has access to
 163       *
 164       * - A user with 'editall' capability will have access to all reports
 165       * - A user with 'edit' capability will have access to:
 166       *      - Those reports this user has created
 167       *      - Those reports this user is in audience of
 168       * - A user with 'view' capability will have access to:
 169       *      - Those reports this user is in audience of
 170       *
 171       * @param string $reporttablealias
 172       * @param int|null $userid User ID to check, or the current user if omitted
 173       * @param context|null $context
 174       * @return array
 175       */
 176      public static function user_reports_list_access_sql(
 177          string $reporttablealias,
 178          ?int $userid = null,
 179          ?context $context = null
 180      ): array {
 181          global $DB, $USER;
 182  
 183          if ($context === null) {
 184              $context = context_system::instance();
 185          }
 186  
 187          // If user can't view all reports, limit the returned list to those reports they can see.
 188          if (!has_capability('moodle/reportbuilder:editall', $context, $userid)) {
 189              $reports = self::user_reports_list($userid);
 190  
 191              [$paramprefix, $paramuserid] = database::generate_param_names(2);
 192              [$reportselect, $params] = $DB->get_in_or_equal($reports, SQL_PARAMS_NAMED, "{$paramprefix}_", true, null);
 193  
 194              $where = "{$reporttablealias}.id {$reportselect}";
 195  
 196              // User can also see any reports that they can edit.
 197              if (has_capability('moodle/reportbuilder:edit', $context, $userid)) {
 198                  $where = "({$reporttablealias}.usercreated = :{$paramuserid} OR {$where})";
 199                  $params[$paramuserid] = $userid ?? $USER->id;
 200              }
 201  
 202              return [$where, $params];
 203          }
 204  
 205          return ['1=1', []];
 206      }
 207  
 208      /**
 209       * Return appropriate list of where clauses and params for given audiences
 210       *
 211       * @param audience_model[] $audiences
 212       * @param string $usertablealias
 213       * @return array[] [$wheres, $params]
 214       */
 215      public static function user_audience_sql(array $audiences, string $usertablealias = 'u'): array {
 216          $wheres = $params = [];
 217  
 218          foreach ($audiences as $audience) {
 219              if ($instance = base::instance(0, $audience->to_record())) {
 220                  $instancetablealias = database::generate_alias();
 221                  [$instancejoin, $instancewhere, $instanceparams] = $instance->get_sql($instancetablealias);
 222  
 223                  $wheres[] = "{$usertablealias}.id IN (
 224                      SELECT {$instancetablealias}.id
 225                        FROM {user} {$instancetablealias}
 226                             {$instancejoin}
 227                       WHERE {$instancewhere}
 228                       )";
 229                  $params += $instanceparams;
 230              }
 231          }
 232  
 233          return [$wheres, $params];
 234      }
 235  
 236      /**
 237       * Returns the list of audiences types in the system.
 238       *
 239       * @return array
 240       */
 241      private static function get_audience_types(): array {
 242          $sources = [];
 243  
 244          $audiences = core_component::get_component_classes_in_namespace(null, 'reportbuilder\\audience');
 245          foreach ($audiences as $class => $path) {
 246              $audienceclass = $class::instance();
 247              if (is_subclass_of($class, base::class) && $audienceclass->user_can_add()) {
 248                  [$component] = explode('\\', $class);
 249  
 250                  if ($plugininfo = core_plugin_manager::instance()->get_plugin_info($component)) {
 251                      $componentname = $plugininfo->displayname;
 252                  } else {
 253                      $componentname = get_string('site');
 254                  }
 255  
 256                  $sources[$componentname][$class] = $audienceclass->get_name();
 257              }
 258          }
 259  
 260          return $sources;
 261      }
 262  
 263      /**
 264       * Get all the audiences types the current user can add to, organised by categories.
 265       *
 266       * @return array
 267       *
 268       * @deprecated since Moodle 4.1 - please do not use this function any more, {@see custom_report_audience_cards_exporter}
 269       */
 270      public static function get_all_audiences_menu_types(): array {
 271          debugging('The function ' . __FUNCTION__ . '() is deprecated, please do not use it any more. ' .
 272              'See \'custom_report_audience_cards_exporter\' class for replacement', DEBUG_DEVELOPER);
 273  
 274          $menucardsarray = [];
 275          $notavailablestr = get_string('notavailable', 'moodle');
 276  
 277          $audiencetypes = self::get_audience_types();
 278          $audiencetypeindex = 0;
 279          foreach ($audiencetypes as $categoryname => $audience) {
 280              $menucards = [
 281                  'name' => $categoryname,
 282                  'key' => 'index' . ++$audiencetypeindex,
 283              ];
 284  
 285              foreach ($audience as $classname => $name) {
 286                  $class = $classname::instance();
 287                  $title = $class->is_available() ? get_string('addaudience', 'core_reportbuilder', $class->get_name()) :
 288                      $notavailablestr;
 289                  $menucard['title'] = $title;
 290                  $menucard['name'] = $class->get_name();
 291                  $menucard['disabled'] = !$class->is_available();
 292                  $menucard['identifier'] = get_class($class);
 293                  $menucard['action'] = 'add-audience';
 294                  $menucards['items'][] = $menucard;
 295              }
 296  
 297              // Order audience types on each category alphabetically.
 298              core_collator::asort_array_of_arrays_by_key($menucards['items'], 'name');
 299              $menucards['items'] = array_values($menucards['items']);
 300  
 301              $menucardsarray[] = $menucards;
 302          }
 303  
 304          return $menucardsarray;
 305      }
 306  }