PageRenderTime 47ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/libs/HTML/QuickForm2/Controller.php

https://github.com/CodeYellowBV/piwik
PHP | 508 lines | 229 code | 33 blank | 246 comment | 35 complexity | 9a233f661695b278e42656e088c455a2 MD5 | raw file
Possible License(s): LGPL-3.0, JSON, MIT, GPL-3.0, LGPL-2.1, GPL-2.0, AGPL-1.0, BSD-2-Clause, BSD-3-Clause
  1. <?php
  2. /**
  3. * Class implementing the Page Controller pattern for multipage forms
  4. *
  5. * PHP version 5
  6. *
  7. * LICENSE:
  8. *
  9. * Copyright (c) 2006-2010, Alexey Borzov <avb@php.net>,
  10. * Bertrand Mansion <golgote@mamasam.com>
  11. * All rights reserved.
  12. *
  13. * Redistribution and use in source and binary forms, with or without
  14. * modification, are permitted provided that the following conditions
  15. * are met:
  16. *
  17. * * Redistributions of source code must retain the above copyright
  18. * notice, this list of conditions and the following disclaimer.
  19. * * Redistributions in binary form must reproduce the above copyright
  20. * notice, this list of conditions and the following disclaimer in the
  21. * documentation and/or other materials provided with the distribution.
  22. * * The names of the authors may not be used to endorse or promote products
  23. * derived from this software without specific prior written permission.
  24. *
  25. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
  26. * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
  27. * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  28. * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  29. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  30. * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  31. * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  32. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
  33. * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  34. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  35. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  36. *
  37. * @category HTML
  38. * @package HTML_QuickForm2
  39. * @author Alexey Borzov <avb@php.net>
  40. * @author Bertrand Mansion <golgote@mamasam.com>
  41. * @license http://opensource.org/licenses/bsd-license.php New BSD License
  42. * @version SVN: $Id: Controller.php 295963 2010-03-08 14:33:43Z avb $
  43. * @link http://pear.php.net/package/HTML_QuickForm2
  44. */
  45. /** The class representing a page of a multipage form */
  46. // require_once 'HTML/QuickForm2/Controller/Page.php';
  47. /** Object wrapping around session variable used to store controller data */
  48. // require_once 'HTML/QuickForm2/Controller/SessionContainer.php';
  49. /** Class presenting the values stored in session by Controller as submitted ones */
  50. // require_once 'HTML/QuickForm2/DataSource/Session.php';
  51. /**
  52. * Class implementing the Page Controller pattern for multipage forms
  53. *
  54. * This class keeps track of pages and (default) action handlers for the form,
  55. * it manages $_SESSION container for the form values, allows setting
  56. * DataSources for the form as a whole and getting its value.
  57. *
  58. * @category HTML
  59. * @package HTML_QuickForm2
  60. * @author Alexey Borzov <avb@php.net>
  61. * @author Bertrand Mansion <golgote@mamasam.com>
  62. * @version Release: @package_version@
  63. */
  64. class HTML_QuickForm2_Controller implements IteratorAggregate
  65. {
  66. /**
  67. * Key in $_REQUEST array that contains the ID of the Controller
  68. */
  69. const KEY_ID = '_qfc_id';
  70. /**
  71. * Key in $_SESSION array that contains the Controller data (needs ID substituted via sprintf())
  72. */
  73. const KEY_CONTAINER = '_%s_container';
  74. /**
  75. * Whether the form is a wizard
  76. * @var boolean
  77. */
  78. protected $wizard = true;
  79. /**
  80. * Whether Controller ID should be sent in GET and POST parameters
  81. * @var boolean
  82. */
  83. protected $propagate = true;
  84. /**
  85. * Controller ID
  86. * @var string
  87. */
  88. protected $id = null;
  89. /**
  90. * Contains the pages (instances of HTML_QuickForm2_Controller_Page) of the multipage form
  91. * @var array
  92. */
  93. protected $pages = array();
  94. /**
  95. * Contains the mapping of action names to handlers (objects implementing HTML_QuickForm2_Controller_Action)
  96. * @var array
  97. */
  98. protected $handlers = array();
  99. /**
  100. * The action extracted from HTTP request: array('page', 'action')
  101. * @var array
  102. */
  103. protected $actionName = null;
  104. /**
  105. * A wrapper around session variable used to store form data
  106. * @var HTML_QuickForm2_Controller_SessionContainer
  107. */
  108. protected $sessionContainer = null;
  109. /**
  110. * Finds a controller name in $_REQUEST
  111. *
  112. * @return string|null Returns nulle if either a KEY_ID is not present
  113. * in $_REQUEST or KEY_CONTAINER is not present in
  114. * $_SESSION
  115. */
  116. public static function findControllerID()
  117. {
  118. if (empty($_REQUEST[self::KEY_ID])
  119. || empty($_SESSION[sprintf(self::KEY_CONTAINER, $_REQUEST[self::KEY_ID])])
  120. ) {
  121. return null;
  122. } else {
  123. return $_REQUEST[self::KEY_ID];
  124. }
  125. }
  126. /**
  127. * Class constructor
  128. *
  129. * Sets the form ID, whether to send this ID in POST and GET parameters,
  130. * wizard / non-wizard behaviour.
  131. *
  132. * Different forms should be given different IDs, as they are used to store
  133. * values in session. If $id is empty, the controller will try to find it
  134. * in $_REQUEST, throwing the exception if this fails.
  135. *
  136. * Wizard forms only allow going to the next page if all the previous ones
  137. * are valid.
  138. *
  139. * @param string Form ID
  140. * @param boolean Whether the form is a wizard
  141. * @param boolean Whether form's ID should be sent with GET and POST parameters
  142. * @throws HTML_QuickForm2_NotFoundException if ID is not given and cannot
  143. * be found in $_REQUEST, or session container is empty
  144. */
  145. public function __construct($id = null, $wizard = true, $propagateId = false)
  146. {
  147. if (empty($id)) {
  148. $propagateId = true;
  149. $id = self::findControllerID();
  150. }
  151. if (empty($id)) {
  152. throw new HTML_QuickForm2_NotFoundException(
  153. 'Controller ID not available in $_REQUEST or session ' .
  154. 'container is empty, please provide ID to constructor'
  155. );
  156. }
  157. $this->id = $id;
  158. $this->wizard = (bool)$wizard;
  159. $this->propagate = (bool)$propagateId;
  160. }
  161. /**
  162. * Returns whether the form is a wizard
  163. *
  164. * @return boolean
  165. */
  166. public function isWizard()
  167. {
  168. return $this->wizard;
  169. }
  170. /**
  171. * Returns the form ID
  172. *
  173. * @return string
  174. */
  175. public function getId()
  176. {
  177. return $this->id;
  178. }
  179. /**
  180. * Returns whether to send form id with GET and POST parameters
  181. *
  182. * @return boolean
  183. */
  184. public function propagateId()
  185. {
  186. return $this->propagate;
  187. }
  188. /**
  189. * Returns the session container with the controller data
  190. *
  191. * @return HTML_QuickForm2_Controller_SessionContainer
  192. */
  193. public function getSessionContainer()
  194. {
  195. if (empty($this->sessionContainer)) {
  196. $this->sessionContainer = new HTML_QuickForm2_Controller_SessionContainer($this);
  197. }
  198. return $this->sessionContainer;
  199. }
  200. /**
  201. * Removes the session variable containing the controller data
  202. */
  203. public function destroySessionContainer()
  204. {
  205. unset($_SESSION[sprintf(self::KEY_CONTAINER, $this->id)]);
  206. $this->sessionContainer = null;
  207. }
  208. /**
  209. * Extracts the name of the page and the action to perform with it from HTTP request data
  210. *
  211. * @return array first element is page name, second is action name
  212. */
  213. public function getActionName()
  214. {
  215. if (is_array($this->actionName)) {
  216. return $this->actionName;
  217. }
  218. if (empty($this->pages)) {
  219. throw new HTML_QuickForm2_NotFoundException('No pages added to the form');
  220. }
  221. $names = array_map('preg_quote', array_keys($this->pages));
  222. $regex = '/^_qf_(' . implode('|', $names) . ')_(.+?)(_x)?$/';
  223. foreach (array_keys($_REQUEST) as $key) {
  224. if (preg_match($regex, $key, $matches)) {
  225. $this->actionName = array($matches[1], $matches[2]);
  226. break;
  227. }
  228. }
  229. if (!is_array($this->actionName)) {
  230. reset($this->pages);
  231. $this->actionName = array(key($this->pages), 'display');
  232. }
  233. return $this->actionName;
  234. }
  235. /**
  236. * Processes the request
  237. *
  238. * This finds the page, the action to perform with it and passes the action
  239. * to the page's handle() method.
  240. *
  241. * @throws HTML_QuickForm2_Exception
  242. */
  243. public function run()
  244. {
  245. list($page, $action) = $this->getActionName();
  246. return $this->pages[$page]->handle($action);
  247. }
  248. /**
  249. * Adds a handler for a specific action
  250. *
  251. * @param string action name
  252. * @param HTML_QuickForm2_Controller_Action the handler for the action
  253. */
  254. public function addHandler($actionName, HTML_QuickForm2_Controller_Action $action)
  255. {
  256. $this->handlers[$actionName] = $action;
  257. }
  258. /**
  259. * Handles an action
  260. *
  261. * This will be called if the page itself does not have a handler for a
  262. * specific action. The method also loads and uses default handlers for
  263. * common actions, if specific ones were not added.
  264. *
  265. * @param HTML_QuickForm2_Controller_Page form page
  266. * @param string action name
  267. * @throws HTML_QuickForm2_NotFoundException if handler for an action is missing
  268. */
  269. public function handle(HTML_QuickForm2_Controller_Page $page, $actionName)
  270. {
  271. if (!isset($this->handlers[$actionName])
  272. && in_array($actionName, array('next', 'back', 'submit', 'display', 'jump'))
  273. ) {
  274. $className = 'HTML_QuickForm2_Controller_Action_' . ucfirst($actionName);
  275. if (!class_exists($className)) {
  276. HTML_QuickForm2_Loader::loadClass($className);
  277. }
  278. $this->addHandler($actionName, new $className());
  279. }
  280. if (isset($this->handlers[$actionName])) {
  281. return $this->handlers[$actionName]->perform($page, $actionName);
  282. } else {
  283. throw new HTML_QuickForm2_NotFoundException(
  284. "Unhandled action '{$actionName}' for page '{$page->getForm()->getId()}'"
  285. );
  286. }
  287. }
  288. /**
  289. * Adds a new page to the form
  290. *
  291. * @param HTML_QuickForm2_Controller_Page
  292. */
  293. public function addPage(HTML_QuickForm2_Controller_Page $page)
  294. {
  295. $pageId = $page->getForm()->getId();
  296. if (!empty($this->pages[$pageId])) {
  297. throw new HTML_QuickForm2_InvalidArgumentException(
  298. "Duplicate page ID '{$pageId}'"
  299. );
  300. }
  301. $page->setController($this);
  302. $this->pages[$pageId] = $page;
  303. }
  304. /**
  305. * Returns a page
  306. *
  307. * @param string Page ID
  308. * @return HTML_QuickForm2_Controller_Page
  309. * @throws HTML_QuickForm2_NotFoundException if there is no page with
  310. * the given ID
  311. */
  312. public function getPage($pageId)
  313. {
  314. if (!empty($this->pages[$pageId])) {
  315. return $this->pages[$pageId];
  316. } else {
  317. throw new HTML_QuickForm2_NotFoundException(
  318. "Unknown page '{$pageId}'"
  319. );
  320. }
  321. }
  322. /**
  323. * Returns the page preceding the given one
  324. *
  325. * @param HTML_QuickForm2_Controller_Page
  326. * @return HTML_QuickForm2_Controller_Page|null
  327. */
  328. public function previousPage(HTML_QuickForm2_Controller_Page $reference)
  329. {
  330. $previous = null;
  331. foreach ($this->pages as $page) {
  332. if ($page === $reference) {
  333. return $previous;
  334. }
  335. $previous = $page;
  336. }
  337. return null;
  338. }
  339. /**
  340. * Returns the page following the given one
  341. *
  342. * @param HTML_QuickForm2_Controller_Page
  343. * @return HTML_QuickForm2_Controller_Page|null
  344. */
  345. public function nextPage(HTML_QuickForm2_Controller_Page $reference)
  346. {
  347. $previous = null;
  348. foreach ($this->pages as $page) {
  349. if ($previous === $reference) {
  350. return $page;
  351. }
  352. $previous = $page;
  353. }
  354. return null;
  355. }
  356. /**
  357. * Checks whether the pages of the controller are valid
  358. *
  359. * @param HTML_QuickForm2_Controller_Page If given, check only the pages
  360. * before (not including) that page
  361. * @return bool
  362. */
  363. public function isValid(HTML_QuickForm2_Controller_Page $reference = null)
  364. {
  365. $container = $this->getSessionContainer();
  366. foreach ($this->pages as $id => $page) {
  367. if ($reference === $page) {
  368. return true;
  369. }
  370. if (!$container->getValidationStatus($id)) {
  371. // We should handle the possible situation when the user has never
  372. // seen a page of a non-modal multipage form
  373. if (!$this->isWizard()
  374. && null === $container->getValidationStatus($id)
  375. ) {
  376. // Empty Session datasource makes the form look submitted
  377. $page->getForm()->setDatasources(array_merge(
  378. $container->getDatasources(),
  379. array(new HTML_QuickForm2_DataSource_Session(array()))
  380. ));
  381. // This will store the "submitted" values in session and
  382. // return validation status
  383. if ($page->storeValues()) {
  384. continue;
  385. }
  386. }
  387. return false;
  388. }
  389. }
  390. return true;
  391. }
  392. /**
  393. * Returns the first page that failed validation
  394. *
  395. * @return HTML_QuickForm2_Controller_Page|null
  396. */
  397. public function getFirstInvalidPage()
  398. {
  399. foreach ($this->pages as $id => $page) {
  400. if (!$this->getSessionContainer()->getValidationStatus($id)) {
  401. return $page;
  402. }
  403. }
  404. return null;
  405. }
  406. /**
  407. * Adds a new data source to the Controller
  408. *
  409. * Note that Controller data sources are stored in session, so your data source
  410. * implementation should properly handle its (un)serialization.
  411. *
  412. * @param HTML_QuickForm2_DataSource Data source
  413. */
  414. public function addDataSource(HTML_QuickForm2_DataSource $datasource)
  415. {
  416. $this->getSessionContainer()->storeDatasources(
  417. array_merge($this->getSessionContainer()->getDatasources(),
  418. array($datasource))
  419. );
  420. }
  421. /**
  422. * Returns the form's values
  423. *
  424. * @return array
  425. */
  426. public function getValue()
  427. {
  428. $values = array();
  429. foreach (array_keys($this->pages) as $id) {
  430. $pageValues = $this->getSessionContainer()->getValues($id);
  431. // skip elements representing actions
  432. foreach ($pageValues as $key => $value) {
  433. if (0 !== strpos($key, '_qf')) {
  434. if (isset($values[$key]) && is_array($value)) {
  435. $values[$key] = self::arrayMerge($values[$key], $value);
  436. } else {
  437. $values[$key] = $value;
  438. }
  439. }
  440. }
  441. }
  442. return $values;
  443. }
  444. /**
  445. * Merges two arrays
  446. *
  447. * Merges two arrays like the PHP function array_merge_recursive does,
  448. * the difference being that existing integer keys will not be renumbered.
  449. *
  450. * @param array
  451. * @param array
  452. * @return array resulting array
  453. */
  454. protected static function arrayMerge($a, $b)
  455. {
  456. foreach ($b as $k => $v) {
  457. if (!is_array($v) || isset($a[$k]) && !is_array($a[$k])) {
  458. $a[$k] = $v;
  459. } else {
  460. $a[$k] = self::arrayMerge(isset($a[$k])? $a[$k]: array(), $v);
  461. }
  462. }
  463. return $a;
  464. }
  465. /**
  466. * Returns an Iterator for the form's pages
  467. *
  468. * @return ArrayIterator
  469. */
  470. public function getIterator()
  471. {
  472. return new ArrayIterator($this->pages);
  473. }
  474. }
  475. ?>