coding style tweaks + removed some PHP4-isms
[lhc/web/wiklou.git] / includes / FormOptions.php
1 <?php
2 /**
3 * Helper class to keep track of options when mixing links and form elements.
4 * @todo This badly need some examples and tests :-)
5 *
6 * Copyright © 2008, Niklas Laxstiröm
7 *
8 * Copyright © 2011, Ashar Voultoiz
9 *
10 * @author Niklas Laxström
11 * @author Ashar Voultoiz
12 */
13
14 class FormOptions implements ArrayAccess {
15 /** @name Type constants
16 * Used internally to map an option value to a WebRequest accessor
17 */
18 /* @{ */
19 /** Mark value for automatic detection (for simple data types only) */
20 const AUTO = -1;
21 /** String type, maps guessType() to WebRequest::getText() */
22 const STRING = 0;
23 /** Integer type, maps guessType() to WebRequest::getInt() */
24 const INT = 1;
25 /** Boolean type, maps guessType() to WebRequest::getBool() */
26 const BOOL = 2;
27 /** Integer type or null, maps to WebRequest::getIntOrNull()
28 * This is useful for the namespace selector.
29 */
30 const INTNULL = 3;
31 /* @} */
32
33 /**
34 * @todo Document!
35 */
36 protected $options = array();
37
38 # Setting up
39
40 public function add( $name, $default, $type = self::AUTO ) {
41 $option = array();
42 $option['default'] = $default;
43 $option['value'] = null;
44 $option['consumed'] = false;
45
46 if ( $type !== self::AUTO ) {
47 $option['type'] = $type;
48 } else {
49 $option['type'] = self::guessType( $default );
50 }
51
52 $this->options[$name] = $option;
53 }
54
55 public function delete( $name ) {
56 $this->validateName( $name, true );
57 unset( $this->options[$name] );
58 }
59
60 /**
61 * Used to find out which type the data is.
62 * All types are defined in the 'Type constants' section of this class
63 * Please note we do not support detection of INTNULL MediaWiki type
64 * which will be assumed as INT if the data is an integer.
65 *
66 * @param $data Mixed: value to guess type for
67 * @exception MWException Unsupported datatype
68 * @return Type constant
69 */
70 public static function guessType( $data ) {
71 if ( is_bool( $data ) ) {
72 return self::BOOL;
73 } elseif ( is_int( $data ) ) {
74 return self::INT;
75 } elseif ( is_string( $data ) ) {
76 return self::STRING;
77 } else {
78 throw new MWException( 'Unsupported datatype' );
79 }
80 }
81
82 # Handling values
83
84 /**
85 * Verify the given option name exist.
86 *
87 * @param $name String: option name
88 * @param $strict Boolean: throw an exception when the option does not exist (default false)
89 * @return Boolean: true if option exist, false otherwise
90 */
91 public function validateName( $name, $strict = false ) {
92 if ( !isset( $this->options[$name] ) ) {
93 if ( $strict ) {
94 throw new MWException( "Invalid option $name" );
95 } else {
96 return false;
97 }
98 }
99 return true;
100 }
101
102 /**
103 * Use to set the value of an option.
104 *
105 * @param $name String: option name
106 * @param $value Mixed: value for the option
107 * @param $force Boolean: whether to set the value when it is equivalent to the default value for this option (default false).
108 * @return null
109 */
110 public function setValue( $name, $value, $force = false ) {
111 $this->validateName( $name, true );
112
113 if ( !$force && $value === $this->options[$name]['default'] ) {
114 // null default values as unchanged
115 $this->options[$name]['value'] = null;
116 } else {
117 $this->options[$name]['value'] = $value;
118 }
119 }
120
121 /**
122 * Get the value for the given option name.
123 * Internally use getValueReal()
124 *
125 * @param $name String: option name
126 * @return Mixed
127 */
128 public function getValue( $name ) {
129 $this->validateName( $name, true );
130
131 return $this->getValueReal( $this->options[$name] );
132 }
133
134 /**
135 * @todo Document
136 * @param $option Array: array structure describing the option
137 * @return Mixed. Value or the default value if it is null
138 */
139 protected function getValueReal( $option ) {
140 if ( $option['value'] !== null ) {
141 return $option['value'];
142 } else {
143 return $option['default'];
144 }
145 }
146
147 /**
148 * Delete the option value.
149 * This will make future calls to getValue() return the default value.
150 * @param $name String: option name
151 * @return null
152 */
153 public function reset( $name ) {
154 $this->validateName( $name, true );
155 $this->options[$name]['value'] = null;
156 }
157
158 /**
159 * @todo Document
160 * @param $name String: option name
161 * @return null
162 */
163 public function consumeValue( $name ) {
164 $this->validateName( $name, true );
165 $this->options[$name]['consumed'] = true;
166
167 return $this->getValueReal( $this->options[$name] );
168 }
169
170 /**
171 * @todo Document
172 * @param $names Array: array of option names
173 * @return null
174 */
175 public function consumeValues( /*Array*/ $names ) {
176 $out = array();
177
178 foreach ( $names as $name ) {
179 $this->validateName( $name, true );
180 $this->options[$name]['consumed'] = true;
181 $out[] = $this->getValueReal( $this->options[$name] );
182 }
183
184 return $out;
185 }
186
187 /**
188 * Validate and set an option integer value
189 * The value will be altered to fit in the range.
190 *
191 * @param $name String: option name
192 * @param $min Int: minimum value
193 * @param $max Int: maximum value
194 * @exception MWException Option is not of type int
195 * @return null
196 */
197 public function validateIntBounds( $name, $min, $max ) {
198 $this->validateName( $name, true );
199
200 if ( $this->options[$name]['type'] !== self::INT ) {
201 throw new MWException( "Option $name is not of type int" );
202 }
203
204 $value = $this->getValueReal( $this->options[$name] );
205 $value = max( $min, min( $max, $value ) );
206
207 $this->setValue( $name, $value );
208 }
209
210 /**
211 * Getting the data out for use
212 * @param $all Boolean: whether to include unchanged options (default: false)
213 * @return Array
214 */
215 public function getUnconsumedValues( $all = false ) {
216 $values = array();
217
218 foreach ( $this->options as $name => $data ) {
219 if ( !$data['consumed'] ) {
220 if ( $all || $data['value'] !== null ) {
221 $values[$name] = $this->getValueReal( $data );
222 }
223 }
224 }
225
226 return $values;
227 }
228
229 /**
230 * Return options modified as an array ( name => value )
231 * @return Array
232 */
233 public function getChangedValues() {
234 $values = array();
235
236 foreach ( $this->options as $name => $data ) {
237 if ( $data['value'] !== null ) {
238 $values[$name] = $data['value'];
239 }
240 }
241
242 return $values;
243 }
244
245 /**
246 * Format options to an array ( name => value)
247 * @return Array
248 */
249 public function getAllValues() {
250 $values = array();
251
252 foreach ( $this->options as $name => $data ) {
253 $values[$name] = $this->getValueReal( $data );
254 }
255
256 return $values;
257 }
258
259 # Reading values
260
261 public function fetchValuesFromRequest( WebRequest $r, $values = false ) {
262 if ( !$values ) {
263 $values = array_keys( $this->options );
264 }
265
266 foreach ( $values as $name ) {
267 $default = $this->options[$name]['default'];
268 $type = $this->options[$name]['type'];
269
270 switch( $type ) {
271 case self::BOOL:
272 $value = $r->getBool( $name, $default ); break;
273 case self::INT:
274 $value = $r->getInt( $name, $default ); break;
275 case self::STRING:
276 $value = $r->getText( $name, $default ); break;
277 case self::INTNULL:
278 $value = $r->getIntOrNull( $name ); break;
279 default:
280 throw new MWException( 'Unsupported datatype' );
281 }
282
283 if ( $value !== null ) {
284 $this->options[$name]['value'] = $value === $default ? null : $value;
285 }
286 }
287 }
288
289 /** @name ArrayAccess functions
290 * Those function implements PHP ArrayAccess interface
291 * @see http://php.net/manual/en/class.arrayaccess.php
292 */
293 /* @{ */
294 /** Whether option exist*/
295 public function offsetExists( $name ) {
296 return isset( $this->options[$name] );
297 }
298 /** Retrieve an option value */
299 public function offsetGet( $name ) {
300 return $this->getValue( $name );
301 }
302 /** Set an option to given value */
303 public function offsetSet( $name, $value ) {
304 $this->setValue( $name, $value );
305 }
306 /** Delete the option */
307 public function offsetUnset( $name ) {
308 $this->delete( $name );
309 }
310 /* @} */
311 }