Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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
        [$select, $params] = $DB->get_in_or_equal($allowedreports, SQL_PARAMS_NAMED, database::generate_param_name('_'));
137
        $sql = "{$reporttablealias}.id {$select}";
138
 
139
        return [$sql, $params];
140
    }
141
 
142
    /**
143
     * Return list of report ID's specified user can access, based on audience configuration
144
     *
145
     * @param int|null $userid User ID to check, or the current user if omitted
146
     * @return int[]
147
     */
148
    public static function user_reports_list(?int $userid = null): array {
149
        global $DB;
150
 
151
        [$select, $params] = self::user_reports_list_sql('rb', $userid);
152
        $sql = "SELECT rb.id
153
                  FROM {reportbuilder_report} rb
154
                 WHERE {$select}";
155
 
156
        return $DB->get_fieldset_sql($sql, $params);
157
    }
158
 
159
    /**
160
     * Returns SQL to limit the list of reports to those that the given user has access to
161
     *
162
     * - A user with 'viewall/editall' capability will have access to all reports
163
     * - A user with 'edit' capability will have access to:
164
     *      - Those reports this user has created
165
     *      - Those reports this user is in audience of
166
     * - Otherwise:
167
     *      - Those reports this user is in audience of
168
     *
169
     * @param string $reporttablealias
170
     * @param int|null $userid User ID to check, or the current user if omitted
171
     * @param context|null $context
172
     * @return array
173
     */
174
    public static function user_reports_list_access_sql(
175
        string $reporttablealias,
176
        ?int $userid = null,
177
        ?context $context = null
178
    ): array {
179
        global $DB, $USER;
180
 
181
        if ($context === null) {
182
            $context = context_system::instance();
183
        }
184
 
185
        if (has_any_capability(['moodle/reportbuilder:editall', 'moodle/reportbuilder:viewall'], $context, $userid)) {
186
            return ['1=1', []];
187
        }
188
 
189
        // Limit the returned list to those reports the user can see, by selecting based on report audience.
190
        [$reportselect, $params] = $DB->get_in_or_equal(
191
            self::user_reports_list($userid),
192
            SQL_PARAMS_NAMED,
193
            database::generate_param_name('_'),
194
            true,
195
            null,
196
        );
197
 
198
        $where = "{$reporttablealias}.id {$reportselect}";
199
 
200
        // User can also see any reports that they can edit.
201
        if (has_capability('moodle/reportbuilder:edit', $context, $userid)) {
202
            $paramuserid = database::generate_param_name();
203
            $where = "({$reporttablealias}.usercreated = :{$paramuserid} OR {$where})";
204
            $params[$paramuserid] = $userid ?? $USER->id;
205
        }
206
 
207
        return [$where, $params];
208
    }
209
 
210
    /**
211
     * Return appropriate list of where clauses and params for given audiences
212
     *
213
     * @param audience_model[] $audiences
214
     * @param string $usertablealias
215
     * @return array[] [$wheres, $params]
216
     */
217
    public static function user_audience_sql(array $audiences, string $usertablealias = 'u'): array {
218
        $wheres = $params = [];
219
 
220
        foreach ($audiences as $audience) {
221
            if ($instance = base::instance(0, $audience->to_record())) {
222
                $instancetablealias = database::generate_alias();
223
                [$instancejoin, $instancewhere, $instanceparams] = $instance->get_sql($instancetablealias);
224
 
225
                $wheres[] = "{$usertablealias}.id IN (
226
                    SELECT {$instancetablealias}.id
227
                      FROM {user} {$instancetablealias}
228
                           {$instancejoin}
229
                     WHERE {$instancewhere}
230
                     )";
231
                $params += $instanceparams;
232
            }
233
        }
234
 
235
        return [$wheres, $params];
236
    }
237
 
238
    /**
239
     * Return a list of audiences that are used by any schedule of the given report
240
     *
241
     * @param int $reportid
242
     * @return int[] Array of audience IDs
243
     */
244
    public static function get_audiences_for_report_schedules(int $reportid): array {
245
        global $DB;
246
 
247
        $audiences = $DB->get_fieldset_select(schedule::TABLE, 'audiences', 'reportid = ?', [$reportid]);
248
 
249
        // Reduce JSON encoded audience data of each schedule to an array of audience IDs.
250
        $audienceids = array_reduce($audiences, static function(array $carry, string $audience): array {
251
            return array_merge($carry, (array) json_decode($audience));
252
        }, []);
253
 
254
        return array_unique($audienceids, SORT_NUMERIC);
255
    }
256
 
257
    /**
258
     * Returns the list of audiences types in the system.
259
     *
260
     * @return array
261
     */
262
    private static function get_audience_types(): array {
263
        $sources = [];
264
 
265
        $audiences = core_component::get_component_classes_in_namespace(null, 'reportbuilder\\audience');
266
        foreach ($audiences as $class => $path) {
267
            $audienceclass = $class::instance();
268
            if (is_subclass_of($class, base::class) && $audienceclass->user_can_add()) {
269
                $componentname = $audienceclass->get_component_displayname();
270
                $sources[$componentname][$class] = $audienceclass->get_name();
271
            }
272
        }
273
 
274
        return $sources;
275
    }
276
 
277
    /**
278
     * Get all the audiences types the current user can add to, organised by categories.
279
     *
280
     * @return array
281
     *
282
     * @deprecated since Moodle 4.1 - please do not use this function any more, {@see custom_report_audience_cards_exporter}
283
     */
284
    public static function get_all_audiences_menu_types(): array {
285
        debugging('The function ' . __FUNCTION__ . '() is deprecated, please do not use it any more. ' .
286
            'See \'custom_report_audience_cards_exporter\' class for replacement', DEBUG_DEVELOPER);
287
 
288
        $menucardsarray = [];
289
        $notavailablestr = get_string('notavailable', 'moodle');
290
 
291
        $audiencetypes = self::get_audience_types();
292
        $audiencetypeindex = 0;
293
        foreach ($audiencetypes as $categoryname => $audience) {
294
            $menucards = [
295
                'name' => $categoryname,
296
                'key' => 'index' . ++$audiencetypeindex,
297
            ];
298
 
299
            foreach ($audience as $classname => $name) {
300
                $class = $classname::instance();
301
                $title = $class->is_available() ? get_string('addaudience', 'core_reportbuilder', $class->get_name()) :
302
                    $notavailablestr;
303
                $menucard['title'] = $title;
304
                $menucard['name'] = $class->get_name();
305
                $menucard['disabled'] = !$class->is_available();
306
                $menucard['identifier'] = get_class($class);
307
                $menucard['action'] = 'add-audience';
308
                $menucards['items'][] = $menucard;
309
            }
310
 
311
            // Order audience types on each category alphabetically.
312
            core_collator::asort_array_of_arrays_by_key($menucards['items'], 'name');
313
            $menucards['items'] = array_values($menucards['items']);
314
 
315
            $menucardsarray[] = $menucards;
316
        }
317
 
318
        return $menucardsarray;
319
    }
320
}