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
/**
18
 * Table filterset.
19
 *
20
 * @package    core
21
 * @category   table
22
 * @copyright  2020 Andrew Nicols <andrew@nicols.co.uk>
23
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24
 */
25
 
26
declare(strict_types=1);
27
 
28
namespace core_table\local\filter;
29
 
30
use InvalidArgumentException;
31
use JsonSerializable;
32
use UnexpectedValueException;
33
use moodle_exception;
34
 
35
/**
36
 * Class representing a set of filters.
37
 *
38
 * @package    core
39
 * @copyright  2020 Andrew Nicols <andrew@nicols.co.uk>
40
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
41
 */
42
abstract class filterset implements JsonSerializable {
43
    /** @var int The default filter type (ALL) */
44
    const JOINTYPE_DEFAULT = 2;
45
 
46
    /** @var int None of the following match */
47
    const JOINTYPE_NONE = 0;
48
 
49
    /** @var int Any of the following match */
50
    const JOINTYPE_ANY = 1;
51
 
52
    /** @var int All of the following match */
53
    const JOINTYPE_ALL = 2;
54
 
55
    /** @var int The join type currently in use */
56
    protected $jointype = null;
57
 
58
    /** @var array The list of combined filter types */
59
    protected $filtertypes = null;
60
 
61
    /** @var array The list of active filters */
62
    protected $filters = [];
63
 
64
    /** @var int[] valid join types */
65
    protected $jointypes = [
66
        self::JOINTYPE_NONE,
67
        self::JOINTYPE_ANY,
68
        self::JOINTYPE_ALL,
69
    ];
70
 
71
    /**
72
     * Specify the type of join to employ for the filter.
73
     *
74
     * @param int $jointype The join type to use using one of the supplied constants
75
     * @return self
76
     */
77
    public function set_join_type(int $jointype): self {
78
        if (array_search($jointype, $this->jointypes) === false) {
79
            throw new InvalidArgumentException('Invalid join type specified');
80
        }
81
 
82
        $this->jointype = $jointype;
83
 
84
        return $this;
85
    }
86
 
87
    /**
88
     * Return the currently specified join type.
89
     *
90
     * @return int
91
     */
92
    public function get_join_type(): int {
93
        if ($this->jointype === null) {
94
            $this->jointype = self::JOINTYPE_DEFAULT;
95
        }
96
        return $this->jointype;
97
    }
98
 
99
    /**
100
     * Add the specified filter.
101
     *
102
     * @param filter $filter
103
     * @return self
104
     */
105
    public function add_filter(filter $filter): self {
106
        $filtername = $filter->get_name();
107
 
108
        if (array_key_exists($filtername, $this->filters)) {
109
            // This filter already exists.
110
            if ($this->filters[$filtername] === $filter) {
111
                // This is the same value as already added.
112
                // Just ignore it.
113
                return $this;
114
            }
115
 
116
            // This is a different value to last time. Fail as this is not supported.
117
            throw new UnexpectedValueException(
118
                "A filter of type '{$filtername}' has already been added. Check that you have the correct filter."
119
            );
120
        }
121
 
122
        // Ensure that the filter is both known, and is of the correct type.
123
        $validtypes = $this->get_all_filtertypes();
124
 
125
        if (!array_key_exists($filtername, $validtypes)) {
126
            // Unknown filter.
127
            throw new InvalidArgumentException(
128
                "The filter '{$filtername}' was not recognised."
129
            );
130
        }
131
 
132
        // Check that the filter is of the correct type.
133
        if (!is_a($filter, $validtypes[$filtername])) {
134
            $actualtype = get_class($filter);
135
            $requiredtype = $validtypes[$filtername];
136
 
137
            throw new InvalidArgumentException(
138
                "The filter '{$filtername}' was incorrectly specified as a {$actualtype}. It must be a {$requiredtype}."
139
            );
140
        }
141
 
142
        // All good. Add the filter.
143
        $this->filters[$filtername] = $filter;
144
 
145
        return $this;
146
    }
147
 
148
    /**
149
     * Add the specified filter from the supplied params.
150
     *
151
     * @param string $filtername The name of the filter to create
152
     * @param mixed[] ...$args Additional arguments used to create this filter type
153
     * @return self
154
     */
155
    public function add_filter_from_params(string $filtername, ...$args): self {
156
        // Fetch the list of valid filters by name.
157
        $validtypes = $this->get_all_filtertypes();
158
 
159
        if (!array_key_exists($filtername, $validtypes)) {
160
            // Unknown filter.
161
            throw new InvalidArgumentException(
162
                "The filter '{$filtername}' was not recognised."
163
            );
164
        }
165
 
166
        $filterclass = $validtypes[$filtername];
167
 
168
        if (!class_exists($filterclass)) {
169
            // Filter class cannot be class autoloaded.
170
            throw new InvalidArgumentException(
171
                "The filter class '{$filterclass}' for filter '{$filtername}' could not be found."
172
            );
173
        }
174
 
175
        // Pass all supplied arguments to the constructor when adding a new filter.
176
        // This allows for a wider definition of the the filter in child classes.
177
        $this->add_filter(new $filterclass($filtername, ...$args));
178
 
179
        return $this;
180
    }
181
 
182
    /**
183
     * Return the current set of filters.
184
     *
185
     * @return filter[]
186
     */
187
    public function get_filters(): array {
188
        // Sort the filters by their name to ensure consistent output.
189
        // Note: This is not a locale-aware sort, but we don't need this.
190
        // It's primarily for consistency, not for actual sorting.
191
        asort($this->filters);
192
 
193
        return $this->filters;
194
    }
195
 
196
    /**
197
     * Check whether the filter has been added or not.
198
     *
199
     * @param string $filtername
200
     * @return bool
201
     */
202
    public function has_filter(string $filtername): bool {
203
        // We do not check if the filtername is valid, only that it exists.
204
        // This is an existence check and there is no benefit to doing any more.
205
        return array_key_exists($filtername, $this->filters);
206
    }
207
 
208
    /**
209
     * Get the named filter.
210
     *
211
     * @param string $filtername
212
     * @return filter
213
     */
214
    public function get_filter(string $filtername): filter {
215
        if (!array_key_exists($filtername, $this->get_all_filtertypes())) {
216
            throw new UnexpectedValueException("The filter specified ({$filtername}) is invalid.");
217
        }
218
 
219
        if (!array_key_exists($filtername, $this->filters)) {
220
            throw new UnexpectedValueException("The filter specified ({$filtername}) has not been created.");
221
        }
222
 
223
        return $this->filters[$filtername];
224
    }
225
 
226
    /**
227
     * Confirm whether the filter has been correctly specified.
228
     *
229
     * @throws moodle_exception
230
     */
231
    public function check_validity(): void {
232
        // Ensure that all required filters are present.
233
        $missing = [];
234
        foreach (array_keys($this->get_required_filters()) as $filtername) {
235
            if (!array_key_exists($filtername, $this->filters)) {
236
                $missing[] = $filtername;
237
            }
238
        }
239
 
240
        if (!empty($missing)) {
241
            throw new moodle_exception(
242
                'missingrequiredfields',
243
                'core_table',
244
                '',
245
                implode(get_string('listsep', 'langconfig') . ' ', $missing)
246
            );
247
        }
248
    }
249
 
250
    /**
251
     * Get the list of required filters in an array of filtername => filter class type.
252
     *
253
     * @return array
254
     */
255
    protected function get_required_filters(): array {
256
        return [];
257
    }
258
 
259
    /**
260
     * Get the list of optional filters in an array of filtername => filter class type.
261
     *
262
     * @return array
263
     */
264
    protected function get_optional_filters(): array {
265
        return [];
266
    }
267
 
268
    /**
269
     * Get all filter valid types in an array of filtername => filter class type.
270
     *
271
     * @return array
272
     */
273
    public function get_all_filtertypes(): array {
274
        if ($this->filtertypes === null) {
275
            $required = $this->get_required_filters();
276
            $optional = $this->get_optional_filters();
277
 
278
            $conflicts = array_keys(array_intersect_key($required, $optional));
279
 
280
            if (!empty($conflicts)) {
281
                throw new InvalidArgumentException(
282
                    "Some filter types are both required, and optional: " . implode(', ', $conflicts)
283
                );
284
            }
285
 
286
            $this->filtertypes = array_merge($required, $optional);
287
            asort($this->filtertypes);
288
        }
289
 
290
        return $this->filtertypes;
291
    }
292
 
293
    /**
294
     * Serialize filterset.
295
     *
296
     * @return mixed|object
297
     */
298
    #[\ReturnTypeWillChange]
299
    public function jsonSerialize() {
300
        return (object) [
301
            'jointype' => $this->get_join_type(),
302
            'filters' => $this->get_filters(),
303
        ];
304
    }
305
}