PageRenderTime 55ms CodeModel.GetById 28ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/ext/anewt.new/form/form.lib.php

https://github.com/ssscrape/ssscrape
PHP | 573 lines | 197 code | 66 blank | 310 comment | 21 complexity | e64728030bbd6cf98fbbb27906663fce MD5 | raw file
  1. <?php
  2. /*
  3. * Anewt, Almost No Effort Web Toolkit, form module
  4. *
  5. * This code is copyrighted and distributed under the terms of the GNU LGPL.
  6. * See the README file for more information.
  7. */
  8. anewt_include('validator');
  9. mkenum(
  10. 'ANEWT_FORM_METHOD_POST',
  11. 'ANEWT_FORM_METHOD_GET'
  12. );
  13. /**
  14. * Basic form class.
  15. *
  16. * This class can be used to create an XHTML form. An AnewtForm instance holds
  17. * a number of controls, fieldset or custom XHTML elements. Additionally, it has
  18. * a few properties that influence its behaviour:
  19. *
  20. * - \c id property is the form id
  21. * - \c method is either \c ANEWT_FORM_METHOD_GET or \c ANEWT_FORM_METHOD_POST
  22. * - \c action is the URL the form will be posted to
  23. *
  24. * Usually an AnewtForm subclass is created. The constructor of this subclass
  25. * adds controls to the form, and optionally the handle_valid() and
  26. * handle_invalid() methods are overridden. Calling code then instantiates this
  27. * subclass, fills it with values using fill() or autofill(), then processes the
  28. * form using process().
  29. *
  30. * \todo Reference module documentation once it's written.
  31. */
  32. class AnewtForm extends AnewtContainer
  33. {
  34. /**
  35. * Numeric array to hold all children of this form.
  36. */
  37. private $_children = array();
  38. /**
  39. * Associative array to hold all form controls by name.
  40. */
  41. private $_controls_by_name = array();
  42. /**
  43. * Associative array to hold all form fieldsets by name.
  44. */
  45. private $_fieldsets_by_name = array();
  46. /**
  47. * List of references to hidden controls.
  48. */
  49. private $_hidden_controls = array();
  50. /**
  51. * Initialize a new AnewtForm instance.
  52. *
  53. * Do not forget to call this method when you override the constructor in
  54. * a subclass, i.e. call <code>parent::__construct()</code>.
  55. */
  56. function __construct()
  57. {
  58. $this->_seed(array(
  59. 'method' => ANEWT_FORM_METHOD_POST,
  60. 'action' => AnewtRequest::relative_url(),
  61. 'description' => null,
  62. 'error' => null,
  63. 'autocomplete' => null,
  64. ));
  65. /* Automatic id for classes extending AnewtForm */
  66. $class_name = get_class($this);
  67. if ($class_name != 'AnewtForm')
  68. {
  69. $this->id = sprintf('form-%s', str_strip_suffix(strtolower($class_name), 'form'));
  70. }
  71. }
  72. /**
  73. * Setup the form in one step. This is just a convenience method, any of the
  74. * arguments are optional.
  75. *
  76. * \param $id
  77. * Id of the form
  78. * \param $method
  79. * Form method; must be one of the ANEWT_FORM_METHOD_GET or
  80. * ANEWT_FORM_METHOD_POST constants
  81. * \param $action
  82. * The url to post this form to
  83. */
  84. function setup($id=null, $method=null, $action=null)
  85. {
  86. if (!is_null($id))
  87. {
  88. assert('is_string($id)');
  89. $this->set('id', $id);
  90. }
  91. if (!is_null($method))
  92. {
  93. assert('($method === ANEWT_FORM_METHOD_GET) || ($method === ANEWT_FORM_METHOD_POST)');
  94. $this->set('method', $method);
  95. }
  96. if (!is_null($action))
  97. {
  98. assert('is_string($action)');
  99. $this->set('action', $action);
  100. }
  101. }
  102. /**
  103. * \{
  104. * \name Getter and setter methods
  105. */
  106. /**
  107. * Return the form method as a string.
  108. *
  109. * \return
  110. * Form method as string, either GET or POST.
  111. */
  112. function get_method_as_string()
  113. {
  114. $method = $this->_get('method');
  115. assert('($method === ANEWT_FORM_METHOD_GET) || ($method === ANEWT_FORM_METHOD_POST)');
  116. if ($method == ANEWT_FORM_METHOD_GET)
  117. {
  118. return 'get';
  119. } else {
  120. return 'post';
  121. }
  122. }
  123. /** \} */
  124. /**
  125. * \{
  126. * \name Methods for getting and setting form values
  127. */
  128. /**
  129. * Fill form using the supplied values. This will iterate over all controls
  130. * of the form and set their value accordingly (if any).
  131. *
  132. * The return value can be used to determine whether a form was completely
  133. * filled using the provided data, or that some values were missing
  134. * from the \c $values array. See autofill() for more information about
  135. * this.
  136. *
  137. * \param $values
  138. * Associative array used as a name to value mapping.
  139. *
  140. * \return
  141. * \c true if the form was succesfully and completely using the provided
  142. * \c $values, \c false otherwise.
  143. *
  144. * \see AnewtFormControl::fill()
  145. */
  146. function fill($values)
  147. {
  148. assert('is_assoc_array($values);');
  149. $out = true;
  150. foreach (array_keys($this->_controls_by_name) as $name)
  151. {
  152. $out = $this->_controls_by_name[$name]->fill($values) && $out;
  153. }
  154. return $out;
  155. }
  156. /**
  157. * Fill form automatically from query data. If the form uses the GET method
  158. * the data comes from <code>$_GET</code>. If the form uses the POST method
  159. * the data comes from <code>$_POST</code>.
  160. *
  161. * The return value (see also fill() for an explanation) can be used to
  162. * detect incomplete \c $_GET or \c $_POST values. This may happen when
  163. * handling different form flavours with different controls using a single
  164. * form, e.g. a simple and advanced search form pointing to the same page in
  165. * which an instance of the advanced flavour of the form handles the
  166. * submitted data. It may also happen when users are messing with the
  167. * submitted data. You may then decide to process() the form only if all
  168. * values were provided. Example:
  169. * <code>if ($form->autofill() && $form->process()) { ...} else { ...}</code>
  170. *
  171. * \return
  172. * True if this fill could be automatically filled from \c $_GET or \c
  173. * $_POST, false if this was not the case.
  174. *
  175. * \see AnewtForm::fill()
  176. */
  177. function autofill()
  178. {
  179. $form_method = $this->_get('method');
  180. if (AnewtRequest::is_get() && $form_method == ANEWT_FORM_METHOD_GET) {
  181. return $this->fill($_GET);
  182. }
  183. elseif (AnewtRequest::is_post() && $form_method == ANEWT_FORM_METHOD_POST) {
  184. return $this->fill($_POST);
  185. }
  186. return false;
  187. }
  188. /**
  189. * Get the value of a control.
  190. *
  191. * The specified control must exist.
  192. *
  193. * This method takes into account whether a control is empty and optional,
  194. * in which case \c NULL is returned. For this reason, the value as returned
  195. * by this method may differ from the value obtained using
  196. * <code>get('value')</code> on the control instance itself, which always
  197. * returns the ‘real’ control value. (This is because you can change the \c
  198. * required property afterwards, so trying to be smart is not what we want
  199. * there.)
  200. *
  201. * \param $name
  202. * The name of the control
  203. *
  204. * \return
  205. * The value of the control
  206. *
  207. * \see AnewtForm::get_control_values
  208. * \see AnewtForm::set_control_value
  209. */
  210. function get_control_value($name)
  211. {
  212. assert('is_string($name)');
  213. assert('array_has_key($this->_controls_by_name, $name); // control must exist');
  214. $control = $this->_controls_by_name[$name];
  215. if ($control->is_empty() && !$control->get('required'))
  216. return null;
  217. return $control->get('value');
  218. }
  219. /**
  220. * Set the value of a control.
  221. *
  222. * The specified control must exist.
  223. *
  224. * \param $name
  225. * The name of the control
  226. *
  227. * \param $value
  228. * The value to set
  229. *
  230. * \see AnewtForm::get_control_value
  231. */
  232. function set_control_value($name, $value)
  233. {
  234. assert('is_string($name)');
  235. assert('array_has_key($this->_controls_by_name, $name); // control must exist');
  236. $this->_controls_by_name[$name]->set('value', $value);
  237. }
  238. /**
  239. * Retrieve all form values.
  240. *
  241. * See AnewtForm::get_control_value for a description of the values returned
  242. * by this method, which may contain \c NULL in some cases.
  243. *
  244. * \return
  245. * Associative array with all form control values by control name.
  246. *
  247. * \see AnewtForm::get_control_value
  248. */
  249. function get_control_values()
  250. {
  251. $out = array();
  252. foreach (array_keys($this->_controls_by_name) as $name)
  253. $out[$name] = $this->get_control_value($name);
  254. return $out;
  255. }
  256. /** \} */
  257. /**
  258. * \{
  259. * \name Form processing methods
  260. */
  261. /**
  262. * Process this form.
  263. *
  264. * This will validate the form and all controls, and call either
  265. * handle_valid() or handle_invalid() depending on the validation results.
  266. * The return value of the handle_valid() or handle_invalid() function is
  267. * returned to the caller of this method.
  268. *
  269. * If the handle_valid() and handle_invalid() methods are not overridden
  270. * \c true and \c false are returned, respectively. This allows calling code
  271. * to use something like this:
  272. * <code>if ($form->process()) { ... } else { ... }</code>
  273. *
  274. * \return
  275. * Return value from handle_valid() or handle_invalid();
  276. *
  277. * \see AnewtForm::is_valid
  278. * \see AnewtForm::handle_valid
  279. * \see AnewtForm::handle_invalid
  280. */
  281. function process()
  282. {
  283. if ($this->is_valid())
  284. return $this->handle_valid();
  285. else
  286. return $this->handle_invalid();
  287. }
  288. /**
  289. * Callback when form validation was succesful.
  290. *
  291. * This method returns true. You may override this method in subclasses.
  292. */
  293. function handle_valid()
  294. {
  295. return true;
  296. }
  297. /**
  298. * Callback when form validation did not succeed.
  299. *
  300. * This method returns \c false. You may override this method in subclasses.
  301. */
  302. function handle_invalid()
  303. {
  304. return false;
  305. }
  306. /** \} */
  307. /**
  308. * \{
  309. * \name Validation methods
  310. */
  311. /**
  312. * Checks whether the form is completely valid. This iterates over all
  313. * controls and checks their validity as well.
  314. *
  315. * \return True if valid, false otherwise
  316. */
  317. function is_valid()
  318. {
  319. /* The default return value is true. This does not expose security or
  320. * validation vulnerabilities, because forms without controls are always
  321. * considered valid. */
  322. $result = true;
  323. /* Now validate all controls */
  324. foreach (array_keys($this->_controls_by_name) as $name)
  325. {
  326. $result = $this->_controls_by_name[$name]->is_valid() && $result;
  327. }
  328. return $result;
  329. }
  330. /** \} */
  331. /**
  332. * \{
  333. * \name Methods for controls and other form elements
  334. */
  335. /**
  336. * Add a control to this form.
  337. *
  338. * \param $control
  339. * The form control instance to add
  340. */
  341. function add_control($control)
  342. {
  343. assert('$control instanceof AnewtFormControl');
  344. $name = $control->get('name');
  345. assert('!array_has_key($this->_controls_by_name, $name); // form control names must be unique');
  346. $control->_set_form($this);
  347. $this->_controls_by_name[$name] = $control;
  348. /* Keep an additional list of references to hidden controls */
  349. if ($control instanceof AnewtFormControlHidden)
  350. $this->_hidden_controls[] = $control;
  351. $this->_children[] = $control;
  352. }
  353. /**
  354. * Easily add a hidden control to this form.
  355. *
  356. * This is a convenience method to easily add a hidden form control to this
  357. * form. It is exactly the same as creating an AnewtFormControlHidden
  358. * instance manually and adding to the form using AnewtForm::add_control
  359. *
  360. * \param $name
  361. * The name of the control
  362. *
  363. * \param $value
  364. * The hidden value
  365. *
  366. * \see AnewtForm::add_control
  367. * \see AnewtFormControlHidden
  368. */
  369. function add_hidden_control($name, $value)
  370. {
  371. $control = new AnewtFormControlHidden($name);
  372. $control->set('value', $value);
  373. $this->add_control($control);
  374. }
  375. /**
  376. * Add a fieldset to this form.
  377. *
  378. * \param $fieldset
  379. * The fieldset instance to add
  380. */
  381. function add_fieldset($fieldset)
  382. {
  383. assert('$fieldset instanceof AnewtFormFieldset');
  384. $this->_children[] = $fieldset;
  385. $fieldset_name = $fieldset->_get('name');
  386. assert('!array_key_exists($fieldset_name, $this->_fieldsets_by_name); // fieldset names must be unique');
  387. $this->_fieldsets_by_name[$fieldset_name] = $fieldset;
  388. foreach (array_keys($fieldset->_children) as $key)
  389. {
  390. if ($fieldset->_children[$key] instanceof AnewtFormControl)
  391. {
  392. $control_name = $fieldset->_children[$key]->get('name');
  393. assert('!array_key_exists($control_name, $this->_controls_by_name); // control names must be unique');
  394. $this->_controls_by_name[$control_name] = $fieldset->_children[$key];
  395. }
  396. }
  397. }
  398. /**
  399. * Add a custom node to this form.
  400. *
  401. * This node is directly embedded in the rendered output by form renderers.
  402. *
  403. * \param $node
  404. * An AnewtXMLDomNode to embed in the form.
  405. */
  406. function add_node($node)
  407. {
  408. assert('$node instanceof AnewtXMLDomNode;');
  409. $this->_children[] = $node;
  410. }
  411. /**
  412. * Check whether a control with this name already exists.
  413. *
  414. * \param $name
  415. * The name of the control
  416. *
  417. * \return
  418. * Boolean indicating Whether the control exists
  419. */
  420. function has_control($name)
  421. {
  422. assert('is_string($name)');
  423. return array_key_exists($name, $this->_controls_by_name);
  424. }
  425. /**
  426. * Get a reference to a control.
  427. *
  428. * The control must exist for this function to work.
  429. *
  430. * \param $name
  431. * The name of the control
  432. *
  433. * \return
  434. * A reference to the control instance
  435. */
  436. function get_control($name)
  437. {
  438. assert('is_string($name)');
  439. assert('array_has_key($this->_controls_by_name, $name); // control must exist');
  440. return $this->_controls_by_name[$name];
  441. }
  442. /**
  443. * Get a reference to a fieldset.
  444. *
  445. * The fieldset must exist for this function to work.
  446. *
  447. * \param $name
  448. * The name of the fieldset
  449. *
  450. * \return
  451. * A reference to the AnewtFormFieldset instance
  452. */
  453. function get_fieldset($name)
  454. {
  455. assert('is_string($name)');
  456. assert('array_has_key($this->_fieldsets_by_name, $name); // fieldset must exist');
  457. return $this->_fieldsets_by_name[$name];
  458. }
  459. /**
  460. * \protected
  461. *
  462. * Return all children on this form as a list.
  463. *
  464. * This method is intended for form renderer implementations only.
  465. *
  466. * \return
  467. * List of children, e.g. form control instances and fieldsets.
  468. */
  469. function _children()
  470. {
  471. return $this->_children;
  472. }
  473. /**
  474. * Return hidden form controls.
  475. *
  476. * This method is intended for form renderer implementations only.
  477. *
  478. * \return
  479. * Array of hidden form controls (may be empty)
  480. */
  481. function _hidden_controls()
  482. {
  483. return $this->_hidden_controls;
  484. }
  485. /**
  486. * Check whether this form contains a file upload control (or a derived
  487. * control). This is useful to find out which enctype the form should have.
  488. *
  489. * This method is intended for form renderer implementations only.
  490. *
  491. * \return
  492. * True if the form contains a file upload control, false otherwise.
  493. *
  494. * \todo Implement AnewtFormControlFileUpload
  495. * \todo Set a special flag in add_control instead of using this method
  496. */
  497. function _contains_file_upload_control()
  498. {
  499. foreach (array_keys($this->_controls_by_name) as $key) {
  500. if ($this->_controls_by_name[$key] instanceof AnewtFormControlFileUpload)
  501. return true;
  502. }
  503. return false;
  504. }
  505. /** \} */
  506. }
  507. ?>