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