FormOptions.php 11 KB

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