PageRenderTime 41ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/libs/HTML/QuickForm2/Node.php

https://github.com/CodeYellowBV/piwik
PHP | 693 lines | 282 code | 70 blank | 341 comment | 47 complexity | 21ea89366d3f68617bf77cc407bf22b1 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. * Base class for all HTML_QuickForm2 elements
  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: Node.php 300747 2010-06-25 16:16:50Z mansion $
  43. * @link http://pear.php.net/package/HTML_QuickForm2
  44. */
  45. /**
  46. * HTML_Common2 - base class for HTML elements
  47. */
  48. // require_once 'HTML/Common2.php';
  49. // By default, we generate element IDs with numeric indexes appended even for
  50. // elements with unique names. If you want IDs to be equal to the element
  51. // names by default, set this configuration option to false.
  52. if (null === HTML_Common2::getOption('id_force_append_index')) {
  53. HTML_Common2::setOption('id_force_append_index', true);
  54. }
  55. /**
  56. * Exception classes for HTML_QuickForm2
  57. */
  58. // require_once 'HTML/QuickForm2/Exception.php';
  59. require_once dirname(__FILE__) . '/Exception.php';
  60. /**
  61. * Static factory class for QuickForm2 elements
  62. */
  63. // require_once 'HTML/QuickForm2/Factory.php';
  64. /**
  65. * Base class for HTML_QuickForm2 rules
  66. */
  67. // require_once 'HTML/QuickForm2/Rule.php';
  68. /**
  69. * Abstract base class for all QuickForm2 Elements and Containers
  70. *
  71. * This class is mostly here to define the interface that should be implemented
  72. * by the subclasses. It also contains static methods handling generation
  73. * of unique ids for elements which do not have ids explicitly set.
  74. *
  75. * @category HTML
  76. * @package HTML_QuickForm2
  77. * @author Alexey Borzov <avb@php.net>
  78. * @author Bertrand Mansion <golgote@mamasam.com>
  79. * @version Release: @package_version@
  80. */
  81. abstract class HTML_QuickForm2_Node extends HTML_Common2
  82. {
  83. /**
  84. * Array containing the parts of element ids
  85. * @var array
  86. */
  87. protected static $ids = array();
  88. /**
  89. * Element's "frozen" status
  90. * @var boolean
  91. */
  92. protected $frozen = false;
  93. /**
  94. * Whether element's value should persist when element is frozen
  95. * @var boolean
  96. */
  97. protected $persistent = false;
  98. /**
  99. * Element containing current
  100. * @var HTML_QuickForm2_Container
  101. */
  102. protected $container = null;
  103. /**
  104. * Contains options and data used for the element creation
  105. * @var array
  106. */
  107. protected $data = array();
  108. /**
  109. * Validation rules for element
  110. * @var array
  111. */
  112. protected $rules = array();
  113. /**
  114. * An array of callback filters for element
  115. * @var array
  116. */
  117. protected $filters = array();
  118. /**
  119. * Error message (usually set via Rule if validation fails)
  120. * @var string
  121. */
  122. protected $error = null;
  123. /**
  124. * Changing 'name' and 'id' attributes requires some special handling
  125. * @var array
  126. */
  127. protected $watchedAttributes = array('id', 'name');
  128. /**
  129. * Intercepts setting 'name' and 'id' attributes
  130. *
  131. * These attributes should always be present and thus trying to remove them
  132. * will result in an exception. Changing their values is delegated to
  133. * setName() and setId() methods, respectively
  134. *
  135. * @param string Attribute name
  136. * @param string Attribute value, null if attribute is being removed
  137. * @throws HTML_QuickForm2_InvalidArgumentException if trying to
  138. * remove a required attribute
  139. */
  140. protected function onAttributeChange($name, $value = null)
  141. {
  142. if ('name' == $name) {
  143. if (null === $value) {
  144. throw new HTML_QuickForm2_InvalidArgumentException(
  145. "Required attribute 'name' can not be removed"
  146. );
  147. } else {
  148. $this->setName($value);
  149. }
  150. } elseif ('id' == $name) {
  151. if (null === $value) {
  152. throw new HTML_QuickForm2_InvalidArgumentException(
  153. "Required attribute 'id' can not be removed"
  154. );
  155. } else {
  156. $this->setId($value);
  157. }
  158. }
  159. }
  160. /**
  161. * Class constructor
  162. *
  163. * @param string Element name
  164. * @param mixed Attributes (either a string or an array)
  165. * @param array Element data (label, options and data used for element creation)
  166. */
  167. public function __construct($name = null, $attributes = null, $data = null)
  168. {
  169. parent::__construct($attributes);
  170. $this->setName($name);
  171. // Autogenerating the id if not set on previous steps
  172. if ('' == $this->getId()) {
  173. $this->setId();
  174. }
  175. if (!empty($data)) {
  176. $this->data = array_merge($this->data, $data);
  177. }
  178. }
  179. /**
  180. * Generates an id for the element
  181. *
  182. * Called when an element is created without explicitly given id
  183. *
  184. * @param string Element name
  185. * @return string The generated element id
  186. */
  187. protected static function generateId($elementName)
  188. {
  189. $stop = !self::getOption('id_force_append_index');
  190. $tokens = strlen($elementName)
  191. ? explode('[', str_replace(']', '', $elementName))
  192. : ($stop? array('qfauto', ''): array('qfauto'));
  193. $container =& self::$ids;
  194. $id = '';
  195. do {
  196. $token = array_shift($tokens);
  197. // Handle the 'array[]' names
  198. if ('' === $token) {
  199. if (empty($container)) {
  200. $token = 0;
  201. } else {
  202. $keys = array_keys($container);
  203. $token = end($keys);
  204. while (isset($container[$token])) {
  205. $token++;
  206. }
  207. }
  208. }
  209. $id .= '-' . $token;
  210. if (!isset($container[$token])) {
  211. $container[$token] = array();
  212. // Handle duplicate names when not having mandatory indexes
  213. } elseif (empty($tokens) && $stop) {
  214. $tokens[] = '';
  215. }
  216. // Handle mandatory indexes
  217. if (empty($tokens) && !$stop) {
  218. $tokens[] = '';
  219. $stop = true;
  220. }
  221. $container =& $container[$token];
  222. } while (!empty($tokens));
  223. return substr($id, 1);
  224. }
  225. /**
  226. * Stores the explicitly given id to prevent duplicate id generation
  227. *
  228. * @param string Element id
  229. */
  230. protected static function storeId($id)
  231. {
  232. $tokens = explode('-', $id);
  233. $container =& self::$ids;
  234. do {
  235. $token = array_shift($tokens);
  236. if (!isset($container[$token])) {
  237. $container[$token] = array();
  238. }
  239. $container =& $container[$token];
  240. } while (!empty($tokens));
  241. }
  242. /**
  243. * Returns the element options
  244. *
  245. * @return array
  246. */
  247. public function getData()
  248. {
  249. return $this->data;
  250. }
  251. /**
  252. * Returns the element's type
  253. *
  254. * @return string
  255. */
  256. abstract public function getType();
  257. /**
  258. * Returns the element's name
  259. *
  260. * @return string
  261. */
  262. public function getName()
  263. {
  264. return isset($this->attributes['name'])? $this->attributes['name']: null;
  265. }
  266. /**
  267. * Sets the element's name
  268. *
  269. * @param string
  270. * @return HTML_QuickForm2_Node
  271. */
  272. abstract public function setName($name);
  273. /**
  274. * Returns the element's id
  275. *
  276. * @return string
  277. */
  278. public function getId()
  279. {
  280. return isset($this->attributes['id'])? $this->attributes['id']: null;
  281. }
  282. /**
  283. * Sets the elements id
  284. *
  285. * Please note that elements should always have an id in QuickForm2 and
  286. * therefore it will not be possible to remove the element's id or set it to
  287. * an empty value. If id is not explicitly given, it will be autogenerated.
  288. *
  289. * @param string Element's id, will be autogenerated if not given
  290. * @return HTML_QuickForm2_Node
  291. */
  292. public function setId($id = null)
  293. {
  294. if (is_null($id)) {
  295. $id = self::generateId($this->getName());
  296. } else {
  297. self::storeId($id);
  298. }
  299. $this->attributes['id'] = (string)$id;
  300. return $this;
  301. }
  302. /**
  303. * Returns the element's value
  304. *
  305. * @return mixed
  306. */
  307. abstract public function getValue();
  308. /**
  309. * Sets the element's value
  310. *
  311. * @param mixed
  312. * @return HTML_QuickForm2_Node
  313. */
  314. abstract public function setValue($value);
  315. /**
  316. * Returns the element's label(s)
  317. *
  318. * @return string|array
  319. */
  320. public function getLabel()
  321. {
  322. if (isset($this->data['label'])) {
  323. return $this->data['label'];
  324. }
  325. return null;
  326. }
  327. /**
  328. * Sets the element's label(s)
  329. *
  330. * @param string|array Label for the element (may be an array of labels)
  331. * @return HTML_QuickForm2_Node
  332. */
  333. public function setLabel($label)
  334. {
  335. $this->data['label'] = $label;
  336. return $this;
  337. }
  338. /**
  339. * Changes the element's frozen status
  340. *
  341. * @param bool Whether the element should be frozen or editable. If
  342. * omitted, the method will not change the frozen status,
  343. * just return its current value
  344. * @return bool Old value of element's frozen status
  345. */
  346. public function toggleFrozen($freeze = null)
  347. {
  348. $old = $this->frozen;
  349. if (null !== $freeze) {
  350. $this->frozen = (bool)$freeze;
  351. }
  352. return $old;
  353. }
  354. /**
  355. * Changes the element's persistent freeze behaviour
  356. *
  357. * If persistent freeze is on, the element's value will be kept (and
  358. * submitted) in a hidden field when the element is frozen.
  359. *
  360. * @param bool New value for "persistent freeze". If omitted, the
  361. * method will not set anything, just return the current
  362. * value of the flag.
  363. * @return bool Old value of "persistent freeze" flag
  364. */
  365. public function persistentFreeze($persistent = null)
  366. {
  367. $old = $this->persistent;
  368. if (null !== $persistent) {
  369. $this->persistent = (bool)$persistent;
  370. }
  371. return $old;
  372. }
  373. /**
  374. * Adds the link to the element containing current
  375. *
  376. * @param HTML_QuickForm2_Container Element containing the current one,
  377. * null if the link should really be
  378. * removed (if removing from container)
  379. * @throws HTML_QuickForm2_InvalidArgumentException If trying to set a
  380. * child of an element as its container
  381. */
  382. protected function setContainer(HTML_QuickForm2_Container $container = null)
  383. {
  384. if (null !== $container) {
  385. $check = $container;
  386. do {
  387. if ($this === $check) {
  388. throw new HTML_QuickForm2_InvalidArgumentException(
  389. 'Cannot set an element or its child as its own container'
  390. );
  391. }
  392. } while ($check = $check->getContainer());
  393. if (null !== $this->container && $container !== $this->container) {
  394. $this->container->removeChild($this);
  395. }
  396. }
  397. $this->container = $container;
  398. if (null !== $container) {
  399. $this->updateValue();
  400. }
  401. }
  402. /**
  403. * Returns the element containing current
  404. *
  405. * @return HTML_QuickForm2_Container|null
  406. */
  407. public function getContainer()
  408. {
  409. return $this->container;
  410. }
  411. /**
  412. * Returns the data sources for this element
  413. *
  414. * @return array
  415. */
  416. protected function getDataSources()
  417. {
  418. if (empty($this->container)) {
  419. return array();
  420. } else {
  421. return $this->container->getDataSources();
  422. }
  423. }
  424. /**
  425. * Called when the element needs to update its value from form's data sources
  426. */
  427. abstract public function updateValue();
  428. /**
  429. * Adds a validation rule
  430. *
  431. * @param HTML_QuickForm2_Rule|string Validation rule or rule type
  432. * @param string|int If first parameter is rule type, then
  433. * message to display if validation fails, otherwise constant showing
  434. * whether to perfom validation client-side and/or server-side
  435. * @param mixed Additional data for the rule
  436. * @param int Whether to perfom validation server-side
  437. * and/or client side. Combination of HTML_QuickForm2_Rule::RUNAT_* constants
  438. * @return HTML_QuickForm2_Rule The added rule
  439. * @throws HTML_QuickForm2_InvalidArgumentException if $rule is of a
  440. * wrong type or rule name isn't registered with Factory
  441. * @throws HTML_QuickForm2_NotFoundException if class for a given rule
  442. * name cannot be found
  443. * @todo Need some means to mark the Rules for running client-side
  444. */
  445. public function addRule($rule, $messageOrRunAt = '', $options = null,
  446. $runAt = HTML_QuickForm2_Rule::RUNAT_SERVER)
  447. {
  448. if ($rule instanceof HTML_QuickForm2_Rule) {
  449. $rule->setOwner($this);
  450. $runAt = '' == $messageOrRunAt? HTML_QuickForm2_Rule::RUNAT_SERVER: $messageOrRunAt;
  451. } elseif (is_string($rule)) {
  452. $rule = HTML_QuickForm2_Factory::createRule($rule, $this, $messageOrRunAt, $options);
  453. } else {
  454. throw new HTML_QuickForm2_InvalidArgumentException(
  455. 'addRule() expects either a rule type or ' .
  456. 'a HTML_QuickForm2_Rule instance'
  457. );
  458. }
  459. $this->rules[] = array($rule, $runAt);
  460. return $rule;
  461. }
  462. /**
  463. * Removes a validation rule
  464. *
  465. * The method will *not* throw an Exception if the rule wasn't added to the
  466. * element.
  467. *
  468. * @param HTML_QuickForm2_Rule Validation rule to remove
  469. * @return HTML_QuickForm2_Rule Removed rule
  470. */
  471. public function removeRule(HTML_QuickForm2_Rule $rule)
  472. {
  473. foreach ($this->rules as $i => $r) {
  474. if ($r[0] === $rule) {
  475. unset($this->rules[$i]);
  476. break;
  477. }
  478. }
  479. return $rule;
  480. }
  481. /**
  482. * Creates a validation rule
  483. *
  484. * This method is mostly useful when when chaining several rules together
  485. * via {@link HTML_QuickForm2_Rule::and_()} and {@link HTML_QuickForm2_Rule::or_()}
  486. * methods:
  487. * <code>
  488. * $first->addRule('nonempty', 'Fill in either first or second field')
  489. * ->or_($second->createRule('nonempty'));
  490. * </code>
  491. *
  492. * @param string Rule type
  493. * @param string Message to display if validation fails
  494. * @param mixed Additional data for the rule
  495. * @return HTML_QuickForm2_Rule The created rule
  496. * @throws HTML_QuickForm2_InvalidArgumentException If rule type is unknown
  497. * @throws HTML_QuickForm2_NotFoundException If class for the rule
  498. * can't be found and/or loaded from file
  499. */
  500. public function createRule($type, $message = '', $options = null)
  501. {
  502. return HTML_QuickForm2_Factory::createRule($type, $this, $message, $options);
  503. }
  504. /**
  505. * Checks whether an element is required
  506. *
  507. * @return boolean
  508. */
  509. public function isRequired()
  510. {
  511. foreach ($this->rules as $rule) {
  512. if ($rule[0] instanceof HTML_QuickForm2_Rule_Required) {
  513. return true;
  514. }
  515. }
  516. return false;
  517. }
  518. /**
  519. * Performs the server-side validation
  520. *
  521. * @return boolean Whether the element is valid
  522. */
  523. protected function validate()
  524. {
  525. foreach ($this->rules as $rule) {
  526. if (strlen($this->error)) {
  527. break;
  528. }
  529. if ($rule[1] & HTML_QuickForm2_Rule::RUNAT_SERVER) {
  530. $rule[0]->validate();
  531. }
  532. }
  533. return !strlen($this->error);
  534. }
  535. /**
  536. * Sets the error message to the element
  537. *
  538. * @param string
  539. * @return HTML_QuickForm2_Node
  540. */
  541. public function setError($error = null)
  542. {
  543. $this->error = (string)$error;
  544. return $this;
  545. }
  546. /**
  547. * Returns the error message for the element
  548. *
  549. * @return string
  550. */
  551. public function getError()
  552. {
  553. return $this->error;
  554. }
  555. /**
  556. * Returns Javascript code for getting the element's value
  557. *
  558. * @return string
  559. */
  560. abstract public function getJavascriptValue();
  561. /**
  562. * Adds a filter
  563. *
  564. * A filter is simply a PHP callback which will be applied to the element value
  565. * when getValue() is called. A filter is by default applied recursively :
  566. * if the value is an array, each elements it contains will
  567. * also be filtered, unless the recursive flag is set to false.
  568. *
  569. * @param callback The PHP callback used for filter
  570. * @param array Optional arguments for the callback. The first parameter
  571. * will always be the element value, then these options will
  572. * be used as parameters for the callback.
  573. * @param bool Whether to apply the filter recursively to contained elements
  574. * @return HTML_QuickForm2_Node The element
  575. * @throws HTML_QuickForm2_InvalidArgumentException If callback is incorrect
  576. */
  577. public function addFilter($callback, array $options = null, $recursive = true)
  578. {
  579. if (!is_callable($callback, false, $callbackName)) {
  580. throw new HTML_QuickForm2_InvalidArgumentException(
  581. 'Callback Filter requires a valid callback, \'' . $callbackName .
  582. '\' was given'
  583. );
  584. }
  585. $this->filters[] = array($callback, $options, 'recursive' => $recursive);
  586. return $this;
  587. }
  588. /**
  589. * Removes all element filters
  590. */
  591. public function removeFilters()
  592. {
  593. $this->filters = array();
  594. }
  595. /**
  596. * Applies element filters on element value
  597. * @param mixed Element value
  598. * @return mixed Filtered value
  599. */
  600. protected function applyFilters($value)
  601. {
  602. foreach ($this->filters as $filter) {
  603. if (is_array($value) && !empty($filter['recursive'])) {
  604. array_walk_recursive($value,
  605. array('HTML_QuickForm2_Node', 'applyFilter'), $filter);
  606. } else {
  607. self::applyFilter($value, null, $filter);
  608. }
  609. }
  610. return $value;
  611. }
  612. protected static function applyFilter(&$value, $key = null, $filter)
  613. {
  614. $callback = $filter[0];
  615. $options = $filter[1];
  616. if (!is_array($options)) {
  617. $options = array();
  618. }
  619. array_unshift($options, $value);
  620. $value = call_user_func_array($callback, $options);
  621. }
  622. }
  623. ?>