MDL-69145 core: Set the default filterset join type to ALL
[moodle.git] / lib / table / classes / local / filter / filter.php
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/>.
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  */
26 declare(strict_types=1);
28 namespace core_table\local\filter;
30 use Countable;
31 use JsonSerializable;
32 use InvalidArgumentException;
33 use Iterator;
35 /**
36  * Class representing a generic filter of any type.
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 class filter implements Countable, Iterator, JsonSerializable {
44     /** @var int The default filter type (ANY) */
45     const JOINTYPE_DEFAULT = 1;
47     /** @var int None of the following match */
48     const JOINTYPE_NONE = 0;
50     /** @var int Any of the following match */
51     const JOINTYPE_ANY = 1;
53     /** @var int All of the following match */
54     const JOINTYPE_ALL = 2;
56     /** @var string The name of this filter */
57     protected $name = null;
59     /** @var int The join type currently in use */
60     protected $jointype = self::JOINTYPE_DEFAULT;
62     /** @var array The list of active filter values */
63     protected $filtervalues = [];
65     /** @var int[] valid join types */
66     protected $jointypes = [
67         self::JOINTYPE_NONE,
68         self::JOINTYPE_ANY,
69         self::JOINTYPE_ALL,
70     ];
72     /** @var int The current iterator position */
73     protected $iteratorposition = null;
75     /**
76      * Constructor for the generic filter class.
77      *
78      * @param string $name The name of the current filter.
79      * @param int $jointype The join to use when combining the filters.
80      *                      See the JOINTYPE_ constants for further information on the field.
81      * @param mixed[] $values An array of filter objects to be applied.
82      */
83     public function __construct(string $name, ?int $jointype = null, ?array $values = null) {
84         $this->name = $name;
86         if ($jointype !== null) {
87             $this->set_join_type($jointype);
88         }
90         if (!empty($values)) {
91             foreach ($values as $value) {
92                 $this->add_filter_value($value);
93             }
94         }
95     }
97     /**
98      * Reset the iterator position.
99      */
100     public function reset_iterator(): void {
101         $this->iteratorposition = null;
102     }
104     /**
105      * Return the current filter value.
106      */
107     public function current() {
108         if ($this->iteratorposition === null) {
109             $this->rewind();
110         }
112         if ($this->iteratorposition === null) {
113             return null;
114         }
116         return $this->filtervalues[$this->iteratorposition];
117     }
119     /**
120      * Returns the current position of the iterator.
121      *
122      * @return int
123      */
124     public function key() {
125         if ($this->iteratorposition === null) {
126             $this->rewind();
127         }
129         return $this->iteratorposition;
130     }
132     /**
133      * Rewind the Iterator position to the start.
134      */
135     public function rewind(): void {
136         if ($this->iteratorposition === null) {
137             $this->sort_filter_values();
138         }
140         if (count($this->filtervalues)) {
141             $this->iteratorposition = 0;
142         }
143     }
145     /**
146      * Move to the next value in the list.
147      */
148     public function next(): void {
149         ++$this->iteratorposition;
150     }
152     /**
153      * Check if the current position is valid.
154      *
155      * @return bool
156      */
157     public function valid(): bool {
158         return isset($this->filtervalues[$this->iteratorposition]);
159     }
161     /**
162      * Return the number of contexts.
163      *
164      * @return int
165      */
166     public function count(): int {
167         return count($this->filtervalues);
168     }
170     /**
171      * Return the name of the filter.
172      *
173      * @return string
174      */
175     public function get_name(): string {
176         return $this->name;
177     }
179     /**
180      * Specify the type of join to employ for the filter.
181      *
182      * @param int $jointype The join type to use using one of the supplied constants
183      * @return self
184      */
185     public function set_join_type(int $jointype): self {
186         if (array_search($jointype, $this->jointypes) === false) {
187             throw new InvalidArgumentException('Invalid join type specified');
188         }
190         $this->jointype = $jointype;
192         return $this;
193     }
195     /**
196      * Return the currently specified join type.
197      *
198      * @return int
199      */
200     public function get_join_type(): int {
201         return $this->jointype;
202     }
204     /**
205      * Add a value to the filter.
206      *
207      * @param mixed $value
208      * @return self
209      */
210     public function add_filter_value($value): self {
211         if ($value === null) {
212             // Null values are usually invalid.
213             return $this;
214         }
216         if ($value === '') {
217             // Empty strings are invalid.
218             return $this;
219         }
221         if (array_search($value, $this->filtervalues) !== false) {
222             // Remove duplicates.
223             return $this;
224         }
226         $this->filtervalues[] = $value;
228         // Reset the iterator position.
229         $this->reset_iterator();
231         return $this;
232     }
234     /**
235      * Sort the filter values to ensure reliable, and consistent output.
236      */
237     protected function sort_filter_values(): void {
238         // Sort the filter values to ensure consistent output.
239         // Note: This is not a locale-aware sort, but we don't need this.
240         // It's primarily for consistency, not for actual sorting.
241         sort($this->filtervalues);
243         $this->reset_iterator();
244     }
246     /**
247      * Return the current filter values.
248      *
249      * @return mixed[]
250      */
251     public function get_filter_values(): array {
252         $this->sort_filter_values();
253         return $this->filtervalues;
254     }
256     /**
257      * Serialize filter.
258      *
259      * @return mixed|object
260      */
261     public function jsonSerialize() {
262         return (object) [
263             'name' => $this->get_name(),
264             'jointype' => $this->get_join_type(),
265             'values' => $this->get_filter_values(),
266         ];
267     }