PageRenderTime 43ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/table/classes/local/filter/filterset.php

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