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