Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401]

   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 Countable;
  31  use JsonSerializable;
  32  use InvalidArgumentException;
  33  use Iterator;
  34  
  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 {
  43  
  44      /** @var int The default filter type (ANY) */
  45      const JOINTYPE_DEFAULT = 1;
  46  
  47      /** @var int None of the following match */
  48      const JOINTYPE_NONE = 0;
  49  
  50      /** @var int Any of the following match */
  51      const JOINTYPE_ANY = 1;
  52  
  53      /** @var int All of the following match */
  54      const JOINTYPE_ALL = 2;
  55  
  56      /** @var string The name of this filter */
  57      protected $name = null;
  58  
  59      /** @var int The join type currently in use */
  60      protected $jointype = self::JOINTYPE_DEFAULT;
  61  
  62      /** @var array The list of active filter values */
  63      protected $filtervalues = [];
  64  
  65      /** @var int[] valid join types */
  66      protected $jointypes = [
  67          self::JOINTYPE_NONE,
  68          self::JOINTYPE_ANY,
  69          self::JOINTYPE_ALL,
  70      ];
  71  
  72      /** @var int The current iterator position */
  73      protected $iteratorposition = null;
  74  
  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;
  85  
  86          if ($jointype !== null) {
  87              $this->set_join_type($jointype);
  88          }
  89  
  90          if (!empty($values)) {
  91              foreach ($values as $value) {
  92                  $this->add_filter_value($value);
  93              }
  94          }
  95      }
  96  
  97      /**
  98       * Reset the iterator position.
  99       */
 100      public function reset_iterator(): void {
 101          $this->iteratorposition = null;
 102      }
 103  
 104      /**
 105       * Return the current filter value.
 106       */
 107      #[\ReturnTypeWillChange]
 108      public function current() {
 109          if ($this->iteratorposition === null) {
 110              $this->rewind();
 111          }
 112  
 113          if ($this->iteratorposition === null) {
 114              return null;
 115          }
 116  
 117          return $this->filtervalues[$this->iteratorposition];
 118      }
 119  
 120      /**
 121       * Returns the current position of the iterator.
 122       *
 123       * @return int
 124       */
 125      #[\ReturnTypeWillChange]
 126      public function key() {
 127          if ($this->iteratorposition === null) {
 128              $this->rewind();
 129          }
 130  
 131          return $this->iteratorposition;
 132      }
 133  
 134      /**
 135       * Rewind the Iterator position to the start.
 136       */
 137      public function rewind(): void {
 138          if ($this->iteratorposition === null) {
 139              $this->sort_filter_values();
 140          }
 141  
 142          if (count($this->filtervalues)) {
 143              $this->iteratorposition = 0;
 144          }
 145      }
 146  
 147      /**
 148       * Move to the next value in the list.
 149       */
 150      public function next(): void {
 151          ++$this->iteratorposition;
 152      }
 153  
 154      /**
 155       * Check if the current position is valid.
 156       *
 157       * @return bool
 158       */
 159      public function valid(): bool {
 160          return isset($this->filtervalues[$this->iteratorposition]);
 161      }
 162  
 163      /**
 164       * Return the number of contexts.
 165       *
 166       * @return int
 167       */
 168      public function count(): int {
 169          return count($this->filtervalues);
 170      }
 171  
 172      /**
 173       * Return the name of the filter.
 174       *
 175       * @return string
 176       */
 177      public function get_name(): string {
 178          return $this->name;
 179      }
 180  
 181      /**
 182       * Specify the type of join to employ for the filter.
 183       *
 184       * @param int $jointype The join type to use using one of the supplied constants
 185       * @return self
 186       */
 187      public function set_join_type(int $jointype): self {
 188          if (array_search($jointype, $this->jointypes) === false) {
 189              throw new InvalidArgumentException('Invalid join type specified');
 190          }
 191  
 192          $this->jointype = $jointype;
 193  
 194          return $this;
 195      }
 196  
 197      /**
 198       * Return the currently specified join type.
 199       *
 200       * @return int
 201       */
 202      public function get_join_type(): int {
 203          return $this->jointype;
 204      }
 205  
 206      /**
 207       * Add a value to the filter.
 208       *
 209       * @param mixed $value
 210       * @return self
 211       */
 212      public function add_filter_value($value): self {
 213          if ($value === null) {
 214              // Null values are usually invalid.
 215              return $this;
 216          }
 217  
 218          if ($value === '') {
 219              // Empty strings are invalid.
 220              return $this;
 221          }
 222  
 223          if (array_search($value, $this->filtervalues) !== false) {
 224              // Remove duplicates.
 225              return $this;
 226          }
 227  
 228          $this->filtervalues[] = $value;
 229  
 230          // Reset the iterator position.
 231          $this->reset_iterator();
 232  
 233          return $this;
 234      }
 235  
 236      /**
 237       * Sort the filter values to ensure reliable, and consistent output.
 238       */
 239      protected function sort_filter_values(): void {
 240          // Sort the filter values to ensure consistent output.
 241          // Note: This is not a locale-aware sort, but we don't need this.
 242          // It's primarily for consistency, not for actual sorting.
 243          sort($this->filtervalues);
 244  
 245          $this->reset_iterator();
 246      }
 247  
 248      /**
 249       * Return the current filter values.
 250       *
 251       * @return mixed[]
 252       */
 253      public function get_filter_values(): array {
 254          $this->sort_filter_values();
 255          return $this->filtervalues;
 256      }
 257  
 258      /**
 259       * Serialize filter.
 260       *
 261       * @return mixed|object
 262       */
 263      #[\ReturnTypeWillChange]
 264      public function jsonSerialize() {
 265          return (object) [
 266              'name' => $this->get_name(),
 267              'jointype' => $this->get_join_type(),
 268              'values' => $this->get_filter_values(),
 269          ];
 270      }
 271  }