PageRenderTime 49ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/libs/HTML/QuickForm2/Element/Select.php

https://github.com/CodeYellowBV/piwik
PHP | 575 lines | 264 code | 41 blank | 270 comment | 39 complexity | a301198923f2a48397c028d7e0d6b016 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. * Classes for <select> 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: Select.php 300722 2010-06-24 10:15:52Z mansion $
  43. * @link http://pear.php.net/package/HTML_QuickForm2
  44. */
  45. /**
  46. * Base class for simple HTML_QuickForm2 elements
  47. */
  48. // require_once 'HTML/QuickForm2/Element.php';
  49. /**
  50. * Collection of <option>s and <optgroup>s
  51. *
  52. * This class handles the output of <option> tags. The class is not intended to
  53. * be used directly.
  54. *
  55. * @category HTML
  56. * @package HTML_QuickForm2
  57. * @author Alexey Borzov <avb@php.net>
  58. * @author Bertrand Mansion <golgote@mamasam.com>
  59. * @version Release: @package_version@
  60. */
  61. class HTML_QuickForm2_Element_Select_OptionContainer extends HTML_Common2
  62. implements IteratorAggregate, Countable
  63. {
  64. /**
  65. * List of options and optgroups in this container
  66. *
  67. * Options are stored as arrays (for performance reasons), optgroups as
  68. * instances of Optgroup class.
  69. *
  70. * @var array
  71. */
  72. protected $options = array();
  73. /**
  74. * Reference to parent <select>'s values
  75. * @var array
  76. */
  77. protected $values;
  78. /**
  79. * Reference to parent <select>'s possible values
  80. * @var array
  81. */
  82. protected $possibleValues;
  83. /**
  84. * Class constructor
  85. *
  86. * @param array Reference to values of parent <select> element
  87. * @param array Reference to possible values of parent <select> element
  88. */
  89. public function __construct(&$values, &$possibleValues)
  90. {
  91. $this->values =& $values;
  92. $this->possibleValues =& $possibleValues;
  93. }
  94. /**
  95. * Adds a new option
  96. *
  97. * Please note that if you pass 'selected' attribute in the $attributes
  98. * parameter then this option's value will be added to <select>'s values.
  99. *
  100. * @param string Option text
  101. * @param string 'value' attribute for <option> tag
  102. * @param mixed Additional attributes for <option> tag (either as a
  103. * string or as an associative array)
  104. */
  105. public function addOption($text, $value, $attributes = null)
  106. {
  107. if (null === $attributes) {
  108. $attributes = array('value' => (string)$value);
  109. } else {
  110. $attributes = self::prepareAttributes($attributes);
  111. if (isset($attributes['selected'])) {
  112. // the 'selected' attribute will be set in __toString()
  113. unset($attributes['selected']);
  114. if (!in_array($value, $this->values)) {
  115. $this->values[] = $value;
  116. }
  117. }
  118. $attributes['value'] = (string)$value;
  119. }
  120. if (!isset($attributes['disabled'])) {
  121. $this->possibleValues[(string)$value] = true;
  122. }
  123. $this->options[] = array('text' => $text, 'attr' => $attributes);
  124. }
  125. /**
  126. * Adds a new optgroup
  127. *
  128. * @param string 'label' attribute for optgroup tag
  129. * @param mixed Additional attributes for <optgroup> tag (either as a
  130. * string or as an associative array)
  131. * @return HTML_QuickForm2_Element_Select_Optgroup
  132. */
  133. public function addOptgroup($label, $attributes = null)
  134. {
  135. $optgroup = new HTML_QuickForm2_Element_Select_Optgroup(
  136. $this->values, $this->possibleValues,
  137. $label, $attributes
  138. );
  139. $this->options[] = $optgroup;
  140. return $optgroup;
  141. }
  142. /**
  143. * Returns an array of contained options
  144. *
  145. * @return array
  146. */
  147. public function getOptions()
  148. {
  149. return $this->options;
  150. }
  151. public function __toString()
  152. {
  153. $indentLvl = $this->getIndentLevel();
  154. $indent = $this->getIndent() . self::getOption('indent');
  155. $linebreak = self::getOption('linebreak');
  156. $html = '';
  157. $strValues = array_map('strval', $this->values);
  158. foreach ($this->options as $option) {
  159. if (is_array($option)) {
  160. if (in_array($option['attr']['value'], $strValues, true)) {
  161. $option['attr']['selected'] = 'selected';
  162. }
  163. $html .= $indent . '<option' .
  164. self::getAttributesString($option['attr']) .
  165. '>' . $option['text'] . '</option>' . $linebreak;
  166. } elseif ($option instanceof HTML_QuickForm2_Element_Select_OptionContainer) {
  167. $option->setIndentLevel($indentLvl + 1);
  168. $html .= $option->__toString();
  169. }
  170. }
  171. return $html;
  172. }
  173. /**
  174. * Returns an iterator over contained elements
  175. *
  176. * @return HTML_QuickForm2_Element_Select_OptionIterator
  177. */
  178. public function getIterator()
  179. {
  180. return new HTML_QuickForm2_Element_Select_OptionIterator($this->options);
  181. }
  182. /**
  183. * Returns a recursive iterator over contained elements
  184. *
  185. * @return RecursiveIteratorIterator
  186. */
  187. public function getRecursiveIterator()
  188. {
  189. return new RecursiveIteratorIterator(
  190. new HTML_QuickForm2_Element_Select_OptionIterator($this->options),
  191. RecursiveIteratorIterator::SELF_FIRST
  192. );
  193. }
  194. /**
  195. * Returns the number of options in the container
  196. *
  197. * @return int
  198. */
  199. public function count()
  200. {
  201. return count($this->options);
  202. }
  203. }
  204. /**
  205. * Class representing an <optgroup> tag
  206. *
  207. * Do not instantiate this class yourself, use
  208. * {@link HTML_QuickForm2_Element_Select::addOptgroup()} method
  209. *
  210. * @category HTML
  211. * @package HTML_QuickForm2
  212. * @author Alexey Borzov <avb@php.net>
  213. * @author Bertrand Mansion <golgote@mamasam.com>
  214. * @version Release: @package_version@
  215. */
  216. class HTML_QuickForm2_Element_Select_Optgroup
  217. extends HTML_QuickForm2_Element_Select_OptionContainer
  218. {
  219. /**
  220. * Class constructor
  221. *
  222. * @param array Reference to values of parent <select> element
  223. * @param array Reference to possible values of parent <select> element
  224. * @param string 'label' attribute for optgroup tag
  225. * @param mixed Additional attributes for <optgroup> tag (either as a
  226. * string or as an associative array)
  227. */
  228. public function __construct(&$values, &$possibleValues, $label, $attributes = null)
  229. {
  230. parent::__construct($values, $possibleValues);
  231. $this->setAttributes($attributes);
  232. $this->attributes['label'] = (string)$label;
  233. }
  234. public function __toString()
  235. {
  236. $indent = $this->getIndent();
  237. $linebreak = self::getOption('linebreak');
  238. return $indent . '<optgroup' . $this->getAttributes(true) . '>' .
  239. $linebreak . parent::__toString() . $indent . '</optgroup>' . $linebreak;
  240. }
  241. }
  242. /**
  243. * Implements a recursive iterator for options arrays
  244. *
  245. * @category HTML
  246. * @package HTML_QuickForm2
  247. * @author Alexey Borzov <avb@php.net>
  248. * @author Bertrand Mansion <golgote@mamasam.com>
  249. * @version Release: @package_version@
  250. */
  251. class HTML_QuickForm2_Element_Select_OptionIterator extends RecursiveArrayIterator
  252. implements RecursiveIterator
  253. {
  254. public function hasChildren()
  255. {
  256. return $this->current() instanceof HTML_QuickForm2_Element_Select_OptionContainer;
  257. }
  258. public function getChildren()
  259. {
  260. return new HTML_QuickForm2_Element_Select_OptionIterator(
  261. $this->current()->getOptions()
  262. );
  263. }
  264. }
  265. /**
  266. * Class representing a <select> element
  267. *
  268. * @category HTML
  269. * @package HTML_QuickForm2
  270. * @author Alexey Borzov <avb@php.net>
  271. * @author Bertrand Mansion <golgote@mamasam.com>
  272. * @version Release: @package_version@
  273. */
  274. class HTML_QuickForm2_Element_Select extends HTML_QuickForm2_Element
  275. {
  276. protected $persistent = true;
  277. /**
  278. * Values for the select element (i.e. values of the selected options)
  279. * @var array
  280. */
  281. protected $values = array();
  282. /**
  283. * Possible values for select elements
  284. *
  285. * A value is considered possible if it is present as a value attribute of
  286. * some option and that option is not disabled.
  287. * @var array
  288. */
  289. protected $possibleValues = array();
  290. /**
  291. * Object containing options for the <select> element
  292. * @var HTML_QuickForm2_Element_Select_OptionContainer
  293. */
  294. protected $optionContainer;
  295. /**
  296. * Enable intrinsic validation by default
  297. * @var array
  298. */
  299. protected $data = array('intrinsic_validation' => true);
  300. /**
  301. * Class constructor
  302. *
  303. * Select element can understand the following keys in $data parameter:
  304. * - 'options': data to populate element's options with. Passed to
  305. * {@link loadOptions()} method.
  306. * - 'intrinsic_validation': setting this to false will disable
  307. * that validation, {@link getValue()} will then return all submit
  308. * values, not just those corresponding to options present in the
  309. * element. May be useful in AJAX scenarios.
  310. *
  311. * @param string Element name
  312. * @param mixed Attributes (either a string or an array)
  313. * @param array Additional element data
  314. * @throws HTML_QuickForm2_InvalidArgumentException if junk is given in $options
  315. */
  316. public function __construct($name = null, $attributes = null, array $data = array())
  317. {
  318. $options = isset($data['options'])? $data['options']: array();
  319. unset($data['options']);
  320. parent::__construct($name, $attributes, $data);
  321. $this->loadOptions($options);
  322. }
  323. public function getType()
  324. {
  325. return 'select';
  326. }
  327. public function __toString()
  328. {
  329. if ($this->frozen) {
  330. return $this->getFrozenHtml();
  331. } else {
  332. if (empty($this->attributes['multiple'])) {
  333. $attrString = $this->getAttributes(true);
  334. } else {
  335. $this->attributes['name'] .= '[]';
  336. $attrString = $this->getAttributes(true);
  337. $this->attributes['name'] = substr($this->attributes['name'], 0, -2);
  338. }
  339. $indent = $this->getIndent();
  340. return $indent . '<select' . $attrString . '>' .
  341. self::getOption('linebreak') .
  342. $this->optionContainer->__toString() .
  343. $indent . '</select>';
  344. }
  345. }
  346. protected function getFrozenHtml()
  347. {
  348. if (null === ($value = $this->getValue())) {
  349. return '&nbsp;';
  350. }
  351. $valueHash = is_array($value)? array_flip($value): array($value => true);
  352. $options = array();
  353. foreach ($this->optionContainer->getRecursiveIterator() as $child) {
  354. if (is_array($child) && isset($valueHash[$child['attr']['value']]) &&
  355. empty($child['attr']['disabled']))
  356. {
  357. $options[] = $child['text'];
  358. }
  359. }
  360. $html = implode('<br />', $options);
  361. if ($this->persistent) {
  362. $name = $this->attributes['name'] .
  363. (empty($this->attributes['multiple'])? '': '[]');
  364. // Only use id attribute if doing single hidden input
  365. $idAttr = (1 == count($valueHash))? array('id' => $this->getId()): array();
  366. foreach ($valueHash as $key => $item) {
  367. $html .= '<input type="hidden"' . self::getAttributesString(array(
  368. 'name' => $name,
  369. 'value' => $key
  370. ) + $idAttr) . ' />';
  371. }
  372. }
  373. return $html;
  374. }
  375. /**
  376. * Returns the value of the <select> element
  377. *
  378. * Please note that the returned value may not necessarily be equal to that
  379. * passed to {@link setValue()}. It passes "intrinsic validation" confirming
  380. * that such value could possibly be submitted by this <select> element.
  381. * Specifically, this method will return null if the elements "disabled"
  382. * attribute is set, it will not return values if there are no options having
  383. * such a "value" attribute or if such options' "disabled" attribute is set.
  384. * It will also only return a scalar value for single selects, mimicking
  385. * the common browsers' behaviour.
  386. *
  387. * @return mixed "value" attribute of selected option in case of single
  388. * select, array of selected options' "value" attributes in
  389. * case of multiple selects, null if no options selected
  390. */
  391. public function getValue()
  392. {
  393. if (!empty($this->attributes['disabled']) || 0 == count($this->values)
  394. || ($this->data['intrinsic_validation']
  395. && (0 == count($this->optionContainer) || 0 == count($this->possibleValues)))
  396. ) {
  397. return null;
  398. }
  399. $values = array();
  400. foreach ($this->values as $value) {
  401. if (!$this->data['intrinsic_validation'] || !empty($this->possibleValues[$value])) {
  402. $values[] = $value;
  403. }
  404. }
  405. if (0 == count($values)) {
  406. return null;
  407. } elseif (!empty($this->attributes['multiple'])) {
  408. return $this->applyFilters($values);
  409. } elseif (1 == count($values)) {
  410. return $this->applyFilters($values[0]);
  411. } else {
  412. // The <select> is not multiple, but several options are to be
  413. // selected. At least IE and Mozilla select the last selected
  414. // option in this case, we should do the same
  415. foreach ($this->optionContainer->getRecursiveIterator() as $child) {
  416. if (is_array($child) && in_array($child['attr']['value'], $values)) {
  417. $lastValue = $child['attr']['value'];
  418. }
  419. }
  420. return $this->applyFilters($lastValue);
  421. }
  422. }
  423. public function setValue($value)
  424. {
  425. if (is_array($value)) {
  426. $this->values = array_values($value);
  427. } else {
  428. $this->values = array($value);
  429. }
  430. return $this;
  431. }
  432. /**
  433. * Loads <option>s (and <optgroup>s) for select element
  434. *
  435. * The method expects a array of options and optgroups:
  436. * <pre>
  437. * array(
  438. * 'option value 1' => 'option text 1',
  439. * ...
  440. * 'option value N' => 'option text N',
  441. * 'optgroup label 1' => array(
  442. * 'option value' => 'option text',
  443. * ...
  444. * ),
  445. * ...
  446. * )
  447. * </pre>
  448. * If value is a scalar, then array key is treated as "value" attribute of
  449. * <option> and value as this <option>'s text. If value is an array, then
  450. * key is treated as a "label" attribute of <optgroup> and value as an
  451. * array of <option>s for this <optgroup>.
  452. *
  453. * If you need to specify additional attributes for <option> and <optgroup>
  454. * tags, then you need to use {@link addOption()} and {@link addOptgroup()}
  455. * methods instead of this one.
  456. *
  457. * @param array
  458. * @throws HTML_QuickForm2_InvalidArgumentException if junk is given in $options
  459. * @return HTML_QuickForm2_Element_Select
  460. */
  461. public function loadOptions(array $options)
  462. {
  463. $this->possibleValues = array();
  464. $this->optionContainer = new HTML_QuickForm2_Element_Select_OptionContainer(
  465. $this->values, $this->possibleValues
  466. );
  467. $this->loadOptionsFromArray($this->optionContainer, $options);
  468. return $this;
  469. }
  470. /**
  471. * Adds options from given array into given container
  472. *
  473. * @param HTML_QuickForm2_Element_Select_OptionContainer options will be
  474. * added to this container
  475. * @param array options array
  476. */
  477. protected function loadOptionsFromArray(
  478. HTML_QuickForm2_Element_Select_OptionContainer $container, $options
  479. )
  480. {
  481. foreach ($options as $key => $value) {
  482. if (is_array($value)) {
  483. $optgroup = $container->addOptgroup($key);
  484. $this->loadOptionsFromArray($optgroup, $value);
  485. } else {
  486. $container->addOption($value, $key);
  487. }
  488. }
  489. }
  490. /**
  491. * Adds a new option
  492. *
  493. * Please note that if you pass 'selected' attribute in the $attributes
  494. * parameter then this option's value will be added to <select>'s values.
  495. *
  496. * @param string Option text
  497. * @param string 'value' attribute for <option> tag
  498. * @param mixed Additional attributes for <option> tag (either as a
  499. * string or as an associative array)
  500. */
  501. public function addOption($text, $value, $attributes = null)
  502. {
  503. return $this->optionContainer->addOption($text, $value, $attributes);
  504. }
  505. /**
  506. * Adds a new optgroup
  507. *
  508. * @param string 'label' attribute for optgroup tag
  509. * @param mixed Additional attributes for <optgroup> tag (either as a
  510. * string or as an associative array)
  511. * @return HTML_QuickForm2_Element_Select_Optgroup
  512. */
  513. public function addOptgroup($label, $attributes = null)
  514. {
  515. return $this->optionContainer->addOptgroup($label, $attributes);
  516. }
  517. public function updateValue()
  518. {
  519. if (!$this->getAttribute('multiple')) {
  520. parent::updateValue();
  521. } else {
  522. $name = $this->getName();
  523. foreach ($this->getDataSources() as $ds) {
  524. if (null !== ($value = $ds->getValue($name)) ||
  525. $ds instanceof HTML_QuickForm2_DataSource_Submit)
  526. {
  527. $this->setValue(null === $value? array(): $value);
  528. return;
  529. }
  530. }
  531. }
  532. }
  533. }
  534. ?>