]> scripts.mit.edu Git - autoinstallsdev/mediawiki.git/blob - includes/FormOptions.php
MediaWiki 1.30.2
[autoinstallsdev/mediawiki.git] / includes / FormOptions.php
1 <?php
2 /**
3  * Helper class to keep track of options when mixing links and form elements.
4  *
5  * Copyright © 2008, Niklas Laxström
6  * Copyright © 2011, Antoine Musso
7  * Copyright © 2013, Bartosz Dziewoński
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License along
20  * with this program; if not, write to the Free Software Foundation, Inc.,
21  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22  * http://www.gnu.org/copyleft/gpl.html
23  *
24  * @file
25  * @author Niklas Laxström
26  * @author Antoine Musso
27  */
28
29 /**
30  * Helper class to keep track of options when mixing links and form elements.
31  *
32  * @todo This badly needs some examples and tests :) The usage in SpecialRecentchanges class is a
33  *     good ersatz in the meantime.
34  */
35 class FormOptions implements ArrayAccess {
36         /** @name Type constants
37          * Used internally to map an option value to a WebRequest accessor
38          */
39         /* @{ */
40         /** Mark value for automatic detection (for simple data types only) */
41         const AUTO = -1;
42         /** String type, maps guessType() to WebRequest::getText() */
43         const STRING = 0;
44         /** Integer type, maps guessType() to WebRequest::getInt() */
45         const INT = 1;
46         /** Float type, maps guessType() to WebRequest::getFloat()
47          * @since 1.23 */
48         const FLOAT = 4;
49         /** Boolean type, maps guessType() to WebRequest::getBool() */
50         const BOOL = 2;
51         /** Integer type or null, maps to WebRequest::getIntOrNull()
52          * This is useful for the namespace selector.
53          */
54         const INTNULL = 3;
55         /** Array type, maps guessType() to WebRequest::getArray()
56          * @since 1.29 */
57         const ARR = 5;
58         /* @} */
59
60         /**
61          * Map of known option names to information about them.
62          *
63          * Each value is an array with the following keys:
64          * - 'default' - the default value as passed to add()
65          * - 'value' - current value, start with null, can be set by various functions
66          * - 'consumed' - true/false, whether the option was consumed using
67          *   consumeValue() or consumeValues()
68          * - 'type' - one of the type constants (but never AUTO)
69          */
70         protected $options = [];
71
72         # Setting up
73
74         /**
75          * Add an option to be handled by this FormOptions instance.
76          *
77          * @param string $name Request parameter name
78          * @param mixed $default Default value when the request parameter is not present
79          * @param int $type One of the type constants (optional, defaults to AUTO)
80          */
81         public function add( $name, $default, $type = self::AUTO ) {
82                 $option = [];
83                 $option['default'] = $default;
84                 $option['value'] = null;
85                 $option['consumed'] = false;
86
87                 if ( $type !== self::AUTO ) {
88                         $option['type'] = $type;
89                 } else {
90                         $option['type'] = self::guessType( $default );
91                 }
92
93                 $this->options[$name] = $option;
94         }
95
96         /**
97          * Remove an option being handled by this FormOptions instance. This is the inverse of add().
98          *
99          * @param string $name Request parameter name
100          */
101         public function delete( $name ) {
102                 $this->validateName( $name, true );
103                 unset( $this->options[$name] );
104         }
105
106         /**
107          * Used to find out which type the data is. All types are defined in the 'Type constants' section
108          * of this class.
109          *
110          * Detection of the INTNULL type is not supported; INT will be assumed if the data is an integer,
111          * MWException will be thrown if it's null.
112          *
113          * @param mixed $data Value to guess the type for
114          * @throws MWException If unable to guess the type
115          * @return int Type constant
116          */
117         public static function guessType( $data ) {
118                 if ( is_bool( $data ) ) {
119                         return self::BOOL;
120                 } elseif ( is_int( $data ) ) {
121                         return self::INT;
122                 } elseif ( is_float( $data ) ) {
123                         return self::FLOAT;
124                 } elseif ( is_string( $data ) ) {
125                         return self::STRING;
126                 } elseif ( is_array( $data ) ) {
127                         return self::ARR;
128                 } else {
129                         throw new MWException( 'Unsupported datatype' );
130                 }
131         }
132
133         # Handling values
134
135         /**
136          * Verify that the given option name exists.
137          *
138          * @param string $name Option name
139          * @param bool $strict Throw an exception when the option doesn't exist instead of returning false
140          * @throws MWException
141          * @return bool True if the option exists, false otherwise
142          */
143         public function validateName( $name, $strict = false ) {
144                 if ( !isset( $this->options[$name] ) ) {
145                         if ( $strict ) {
146                                 throw new MWException( "Invalid option $name" );
147                         } else {
148                                 return false;
149                         }
150                 }
151
152                 return true;
153         }
154
155         /**
156          * Use to set the value of an option.
157          *
158          * @param string $name Option name
159          * @param mixed $value Value for the option
160          * @param bool $force Whether to set the value when it is equivalent to the default value for this
161          *     option (default false).
162          */
163         public function setValue( $name, $value, $force = false ) {
164                 $this->validateName( $name, true );
165
166                 if ( !$force && $value === $this->options[$name]['default'] ) {
167                         // null default values as unchanged
168                         $this->options[$name]['value'] = null;
169                 } else {
170                         $this->options[$name]['value'] = $value;
171                 }
172         }
173
174         /**
175          * Get the value for the given option name. Uses getValueReal() internally.
176          *
177          * @param string $name Option name
178          * @return mixed
179          */
180         public function getValue( $name ) {
181                 $this->validateName( $name, true );
182
183                 return $this->getValueReal( $this->options[$name] );
184         }
185
186         /**
187          * Return current option value, based on a structure taken from $options.
188          *
189          * @param array $option Array structure describing the option
190          * @return mixed Value, or the default value if it is null
191          */
192         protected function getValueReal( $option ) {
193                 if ( $option['value'] !== null ) {
194                         return $option['value'];
195                 } else {
196                         return $option['default'];
197                 }
198         }
199
200         /**
201          * Delete the option value.
202          * This will make future calls to getValue() return the default value.
203          * @param string $name Option name
204          */
205         public function reset( $name ) {
206                 $this->validateName( $name, true );
207                 $this->options[$name]['value'] = null;
208         }
209
210         /**
211          * Get the value of given option and mark it as 'consumed'. Consumed options are not returned
212          * by getUnconsumedValues().
213          *
214          * @see consumeValues()
215          * @throws MWException If the option does not exist
216          * @param string $name Option name
217          * @return mixed Value, or the default value if it is null
218          */
219         public function consumeValue( $name ) {
220                 $this->validateName( $name, true );
221                 $this->options[$name]['consumed'] = true;
222
223                 return $this->getValueReal( $this->options[$name] );
224         }
225
226         /**
227          * Get the values of given options and mark them as 'consumed'. Consumed options are not returned
228          * by getUnconsumedValues().
229          *
230          * @see consumeValue()
231          * @throws MWException If any option does not exist
232          * @param array $names Array of option names as strings
233          * @return array Array of option values, or the default values if they are null
234          */
235         public function consumeValues( $names ) {
236                 $out = [];
237
238                 foreach ( $names as $name ) {
239                         $this->validateName( $name, true );
240                         $this->options[$name]['consumed'] = true;
241                         $out[] = $this->getValueReal( $this->options[$name] );
242                 }
243
244                 return $out;
245         }
246
247         /**
248          * @see validateBounds()
249          * @param string $name
250          * @param int $min
251          * @param int $max
252          */
253         public function validateIntBounds( $name, $min, $max ) {
254                 $this->validateBounds( $name, $min, $max );
255         }
256
257         /**
258          * Constrain a numeric value for a given option to a given range. The value will be altered to fit
259          * in the range.
260          *
261          * @since 1.23
262          *
263          * @param string $name Option name
264          * @param int|float $min Minimum value
265          * @param int|float $max Maximum value
266          * @throws MWException If option is not of type INT
267          */
268         public function validateBounds( $name, $min, $max ) {
269                 $this->validateName( $name, true );
270                 $type = $this->options[$name]['type'];
271
272                 if ( $type !== self::INT && $type !== self::FLOAT ) {
273                         throw new MWException( "Option $name is not of type INT or FLOAT" );
274                 }
275
276                 $value = $this->getValueReal( $this->options[$name] );
277                 $value = max( $min, min( $max, $value ) );
278
279                 $this->setValue( $name, $value );
280         }
281
282         /**
283          * Get all remaining values which have not been consumed by consumeValue() or consumeValues().
284          *
285          * @param bool $all Whether to include unchanged options (default: false)
286          * @return array
287          */
288         public function getUnconsumedValues( $all = false ) {
289                 $values = [];
290
291                 foreach ( $this->options as $name => $data ) {
292                         if ( !$data['consumed'] ) {
293                                 if ( $all || $data['value'] !== null ) {
294                                         $values[$name] = $this->getValueReal( $data );
295                                 }
296                         }
297                 }
298
299                 return $values;
300         }
301
302         /**
303          * Return options modified as an array ( name => value )
304          * @return array
305          */
306         public function getChangedValues() {
307                 $values = [];
308
309                 foreach ( $this->options as $name => $data ) {
310                         if ( $data['value'] !== null ) {
311                                 $values[$name] = $data['value'];
312                         }
313                 }
314
315                 return $values;
316         }
317
318         /**
319          * Format options to an array ( name => value )
320          * @return array
321          */
322         public function getAllValues() {
323                 $values = [];
324
325                 foreach ( $this->options as $name => $data ) {
326                         $values[$name] = $this->getValueReal( $data );
327                 }
328
329                 return $values;
330         }
331
332         # Reading values
333
334         /**
335          * Fetch values for all options (or selected options) from the given WebRequest, making them
336          * available for accessing with getValue() or consumeValue() etc.
337          *
338          * @param WebRequest $r The request to fetch values from
339          * @param array $optionKeys Which options to fetch the values for (default:
340          *     all of them). Note that passing an empty array will also result in
341          *     values for all keys being fetched.
342          * @throws MWException If the type of any option is invalid
343          */
344         public function fetchValuesFromRequest( WebRequest $r, $optionKeys = null ) {
345                 if ( !$optionKeys ) {
346                         $optionKeys = array_keys( $this->options );
347                 }
348
349                 foreach ( $optionKeys as $name ) {
350                         $default = $this->options[$name]['default'];
351                         $type = $this->options[$name]['type'];
352
353                         switch ( $type ) {
354                                 case self::BOOL:
355                                         $value = $r->getBool( $name, $default );
356                                         break;
357                                 case self::INT:
358                                         $value = $r->getInt( $name, $default );
359                                         break;
360                                 case self::FLOAT:
361                                         $value = $r->getFloat( $name, $default );
362                                         break;
363                                 case self::STRING:
364                                         $value = $r->getText( $name, $default );
365                                         break;
366                                 case self::INTNULL:
367                                         $value = $r->getIntOrNull( $name );
368                                         break;
369                                 case self::ARR:
370                                         $value = $r->getArray( $name );
371                                         break;
372                                 default:
373                                         throw new MWException( 'Unsupported datatype' );
374                         }
375
376                         if ( $value !== null ) {
377                                 $this->options[$name]['value'] = $value === $default ? null : $value;
378                         }
379                 }
380         }
381
382         /** @name ArrayAccess functions
383          * These functions implement the ArrayAccess PHP interface.
384          * @see https://secure.php.net/manual/en/class.arrayaccess.php
385          */
386         /* @{ */
387         /**
388          * Whether the option exists.
389          * @param string $name
390          * @return bool
391          */
392         public function offsetExists( $name ) {
393                 return isset( $this->options[$name] );
394         }
395
396         /**
397          * Retrieve an option value.
398          * @param string $name
399          * @return mixed
400          */
401         public function offsetGet( $name ) {
402                 return $this->getValue( $name );
403         }
404
405         /**
406          * Set an option to given value.
407          * @param string $name
408          * @param mixed $value
409          */
410         public function offsetSet( $name, $value ) {
411                 $this->setValue( $name, $value );
412         }
413
414         /**
415          * Delete the option.
416          * @param string $name
417          */
418         public function offsetUnset( $name ) {
419                 $this->delete( $name );
420         }
421         /* @} */
422 }