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\report;
20
 
21
use lang_string;
22
use moodle_exception;
23
use core_reportbuilder\local\filters\base;
24
use core_reportbuilder\local\helpers\database;
25
use core_reportbuilder\local\models\filter as filter_model;
26
 
27
/**
28
 * Class to represent a report filter
29
 *
30
 * @package     core_reportbuilder
31
 * @copyright   2021 Paul Holden <paulh@moodle.com>
32
 * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
33
 */
34
final class filter {
35
 
36
    /** @var string $filterclass */
37
    private $filterclass;
38
 
39
    /** @var string $name */
40
    private $name;
41
 
42
    /** @var lang_string $header */
43
    private $header;
44
 
45
    /** @var string $entity */
46
    private $entityname;
47
 
48
    /** @var string $fieldsql */
49
    private $fieldsql = '';
50
 
51
    /** @var array $fieldparams */
52
    private $fieldparams = [];
53
 
54
    /** @var string[] $joins */
55
    protected $joins = [];
56
 
57
    /** @var bool $available */
58
    protected $available = true;
59
 
60
    /** @var bool $deprecated */
61
    protected $deprecated = false;
62
 
63
    /** @var string $deprecatedmessage */
64
    protected $deprecatedmessage;
65
 
66
    /** @var mixed $options */
67
    protected $options;
68
 
69
    /** @var array $limitoperators */
70
    protected $limitoperators = [];
71
 
72
    /** @var filter_model $persistent */
73
    protected $persistent;
74
 
75
    /**
76
     * Filter constructor
77
     *
78
     * @param string $filterclass Filter type class to use, must extend {@see base} filter class
79
     * @param string $name Internal name of the filter
80
     * @param lang_string $header Title of the filter used in reports
81
     * @param string $entityname Name of the entity this filter belongs to. Typically when creating filters within entities
82
     *      this value should be the result of calling {@see get_entity_name}, however if creating filters inside reports directly
83
     *      it should be the name of the entity as passed to {@see \core_reportbuilder\local\report\base::annotate_entity}
84
     * @param string $fieldsql SQL clause to use for filtering, {@see set_field_sql}
85
     * @param array $fieldparams
86
     * @throws moodle_exception For invalid filter class
87
     */
88
    public function __construct(
89
        string $filterclass,
90
        string $name,
91
        lang_string $header,
92
        string $entityname,
93
        string $fieldsql = '',
94
        array $fieldparams = []
95
    ) {
96
        if (!class_exists($filterclass) || !is_subclass_of($filterclass, base::class)) {
97
            throw new moodle_exception('filterinvalid', 'reportbuilder', '', null, $filterclass);
98
        }
99
 
100
        $this->filterclass = $filterclass;
101
        $this->name = $name;
102
        $this->header = $header;
103
        $this->entityname = $entityname;
104
 
105
        if ($fieldsql !== '') {
106
            $this->set_field_sql($fieldsql, $fieldparams);
107
        }
108
    }
109
 
110
    /**
111
     * Get filter class path
112
     *
113
     * @return string
114
     */
115
    public function get_filter_class(): string {
116
        return $this->filterclass;
117
    }
118
 
119
    /**
120
     * Get filter name
121
     *
122
     * @return string
123
     */
124
    public function get_name(): string {
125
        return $this->name;
126
    }
127
 
128
    /**
129
     * Return header
130
     *
131
     * @return string
132
     */
133
    public function get_header(): string {
134
        return $this->header->out();
135
    }
136
 
137
    /**
138
     * Set header
139
     *
140
     * @param lang_string $header
141
     * @return self
142
     */
143
    public function set_header(lang_string $header): self {
144
        $this->header = $header;
145
        return $this;
146
    }
147
 
148
    /**
149
     * Return filter entity name
150
     *
151
     * @return string
152
     */
153
    public function get_entity_name(): string {
154
        return $this->entityname;
155
    }
156
 
157
    /**
158
     * Return unique identifier for this filter
159
     *
160
     * @return string
161
     */
162
    public function get_unique_identifier(): string {
163
        return $this->get_entity_name() . ':' . $this->get_name();
164
    }
165
 
166
    /**
167
     * Return joins
168
     *
169
     * @return string[]
170
     */
171
    public function get_joins(): array {
172
        return array_values($this->joins);
173
    }
174
 
175
    /**
176
     * Add join clause required for this filter to join to existing tables/entities
177
     *
178
     * This is necessary in the case where {@see set_field_sql} is selecting data from a table that isn't otherwise queried
179
     *
180
     * @param string $join
181
     * @return self
182
     */
183
    public function add_join(string $join): self {
184
        $this->joins[trim($join)] = trim($join);
185
        return $this;
186
    }
187
 
188
    /**
189
     * Add multiple join clauses required for this filter, passing each to {@see add_join}
190
     *
191
     * Typically when defining filters in entities, you should pass {@see \core_reportbuilder\local\report\base::get_joins} to
192
     * this method, so that all entity joins are included in the report when your filter is used in it
193
     *
194
     * @param string[] $joins
195
     * @return self
196
     */
197
    public function add_joins(array $joins): self {
198
        foreach ($joins as $join) {
199
            $this->add_join($join);
200
        }
201
        return $this;
202
    }
203
 
204
    /**
205
     * Get SQL expression for the field
206
     *
207
     * @return string
208
     */
209
    public function get_field_sql(): string {
210
        return $this->fieldsql;
211
    }
212
 
213
    /**
214
     * Get the SQL params for the field being filtered
215
     *
216
     * @return array
217
     */
218
    public function get_field_params(): array {
219
        return $this->fieldparams;
220
    }
221
 
222
    /**
223
     * Retrieve SQL expression and parameters for the field
224
     *
225
     * @param int $index
226
     * @return array [$sql, [...$params]]
227
     */
228
    public function get_field_sql_and_params(int $index = 0): array {
229
        $fieldsql = $this->get_field_sql();
230
        $fieldparams = $this->get_field_params();
231
 
232
        // Shortcut if there aren't any parameters.
233
        if (empty($fieldparams)) {
234
            return [$fieldsql, $fieldparams];
235
        }
236
 
237
        // Simple callback for replacement of parameter names within filter SQL.
238
        $transform = function(string $param) use ($index): string {
239
            return "{$param}_{$index}";
240
        };
241
 
242
        $paramnames = array_keys($fieldparams);
243
        $sql = database::sql_replace_parameter_names($fieldsql, $paramnames, $transform);
244
 
245
        $params = [];
246
        foreach ($paramnames as $paramname) {
247
            $paramnametransform = $transform($paramname);
248
            $params[$paramnametransform] = $fieldparams[$paramname];
249
        }
250
 
251
        return [$sql, $params];
252
    }
253
 
254
    /**
255
     * Set the SQL expression for the field that is being filtered. It will be passed to the filter class
256
     *
257
     * @param string $sql
258
     * @param array $params
259
     * @return self
260
     */
261
    public function set_field_sql(string $sql, array $params = []): self {
262
        $this->fieldsql = $sql;
263
        $this->fieldparams = $params;
264
        return $this;
265
    }
266
 
267
    /**
268
     * Return available state of the filter for the current user
269
     *
270
     * @return bool
271
     */
272
    public function get_is_available(): bool {
273
        return $this->available;
274
    }
275
 
276
    /**
277
     * Conditionally set whether the filter is available. For instance the filter may be added to a report with the
278
     * expectation that only some users are able to see it
279
     *
280
     * @param bool $available
281
     * @return self
282
     */
283
    public function set_is_available(bool $available): self {
284
        $this->available = $available;
285
        return $this;
286
    }
287
 
288
    /**
289
     * Set deprecated state of the filter, in which case it will still be shown when already present in existing reports but
290
     * won't be available for selection in the report editor
291
     *
292
     * @param string $deprecatedmessage
293
     * @return self
294
     */
295
    public function set_is_deprecated(string $deprecatedmessage = ''): self {
296
        $this->deprecated = true;
297
        $this->deprecatedmessage = $deprecatedmessage;
298
        return $this;
299
    }
300
 
301
    /**
302
     * Return deprecated state of the filter
303
     *
304
     * @return bool
305
     */
306
    public function get_is_deprecated(): bool {
307
        return $this->deprecated;
308
    }
309
 
310
    /**
311
     * Return deprecated message of the filter
312
     *
313
     * @return string
314
     */
315
    public function get_is_deprecated_message(): string {
316
        return $this->deprecatedmessage;
317
    }
318
 
319
    /**
320
     * Set the options for the filter in the format that the filter class expected (e.g. the "select" filter expects an array)
321
     *
322
     * This method should only be used if the options do not require any calculations/queries, in which
323
     * case {@see set_options_callback} should be used. For performance, {@see get_string} shouldn't be used either, use of
324
     * {@see lang_string} is instead encouraged
325
     *
326
     * @param mixed $options
327
     * @return self
328
     */
329
    public function set_options($options): self {
330
        $this->options = $options;
331
        return $this;
332
    }
333
 
334
    /**
335
     * Set the options for the filter to be returned by a callback (that receives no arguments) in the format that the filter
336
     * class expects
337
     *
338
     * @param callable $callback
339
     * @return self
340
     */
341
    public function set_options_callback(callable $callback): self {
342
        $this->options = $callback;
343
        return $this;
344
    }
345
 
346
    /**
347
     * Get the options for the filter, returning via the the previously set options or generated via defined options callback
348
     *
349
     * @return mixed
350
     */
351
    public function get_options() {
352
        if (is_callable($this->options)) {
353
            $callable = $this->options;
354
            $this->options = ($callable)();
355
        }
356
        return $this->options;
357
    }
358
 
359
    /**
360
     * Set a limited subset of operators that should be used for the filter, refer to each filter class to find defined
361
     * operator constants
362
     *
363
     * @param array $limitoperators Simple array of operator values
364
     * @return self
365
     */
366
    public function set_limited_operators(array $limitoperators): self {
367
        $this->limitoperators = $limitoperators;
368
        return $this;
369
    }
370
 
371
    /**
372
     * Filter given operators to include only those previously defined by {@see set_limited_operators}
373
     *
374
     * @param array $operators All operators as defined by the filter class
375
     * @return array
376
     */
377
    public function restrict_limited_operators(array $operators): array {
378
        if (empty($this->limitoperators)) {
379
            return $operators;
380
        }
381
 
382
        return array_intersect_key($operators, array_flip($this->limitoperators));
383
    }
384
 
385
    /**
386
     * Set filter persistent
387
     *
388
     * @param filter_model $persistent
389
     * @return self
390
     */
391
    public function set_persistent(filter_model $persistent): self {
392
        $this->persistent = $persistent;
393
        return $this;
394
    }
395
 
396
    /**
397
     * Return filter persistent
398
     *
399
     * @return filter_model|null
400
     */
401
    public function get_persistent(): ?filter_model {
402
        return $this->persistent ?? null;
403
    }
404
}