PageRenderTime 37ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/system/library/PEAR/HTML/QuickForm2/Element/Hierselect.php

https://bitbucket.org/spekkionu/passworddb
PHP | 419 lines | 163 code | 29 blank | 227 comment | 17 complexity | 10506716b58f06b48fc8bbc7eb44ce6e MD5 | raw file
Possible License(s): BSD-2-Clause
  1. <?php
  2. /**
  3. * Hierarchical select element
  4. *
  5. * PHP version 5
  6. *
  7. * LICENSE:
  8. *
  9. * Copyright (c) 2006-2012, 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: Hierselect.php 325158 2012-04-13 21:04:26Z avb $
  43. * @link http://pear.php.net/package/HTML_QuickForm2
  44. */
  45. /**
  46. * Base class for HTML_QuickForm2 groups
  47. */
  48. require_once 'HTML/QuickForm2/Container/Group.php';
  49. /**
  50. * Classes for <select> elements
  51. */
  52. require_once 'HTML/QuickForm2/Element/Select.php';
  53. /**
  54. * Class for adding inline javascript to the form
  55. */
  56. require_once 'HTML/QuickForm2/Element/Script.php';
  57. /**
  58. * Hierarchical select element
  59. *
  60. * Class to dynamically create two or more HTML Select elements
  61. * The first select changes the content of the second select and so on.
  62. * This element is considered as a group. Selects will be named
  63. * groupName[0], groupName[1], groupName[2]...
  64. *
  65. * @category HTML
  66. * @package HTML_QuickForm2
  67. * @author Herim Vasquez <vasquezh@iro.umontreal.ca>
  68. * @author Bertrand Mansion <bmansion@mamasam.com>
  69. * @author Alexey Borzov <avb@php.net>
  70. * @license http://opensource.org/licenses/bsd-license.php New BSD License
  71. * @version Release: 2.0.0
  72. * @link http://pear.php.net/package/HTML_QuickForm2
  73. */
  74. class HTML_QuickForm2_Element_Hierselect extends HTML_QuickForm2_Container_Group
  75. {
  76. /**
  77. * Options for all the select elements
  78. *
  79. * @see loadOptions()
  80. * @var array
  81. */
  82. protected $options = array();
  83. /**
  84. * PHP callback function for getting additional options
  85. *
  86. * @see loadOptions()
  87. * @var callback
  88. */
  89. protected $callback = null;
  90. /**
  91. * Javascript callback function for getting additional options
  92. *
  93. * @see loadOptions()
  94. * @var string
  95. */
  96. protected $jsCallback = null;
  97. /**
  98. * Number of select elements in hierselect
  99. * @var int
  100. */
  101. protected $size = 0;
  102. /**
  103. * Values for child selects, needed for form reset handling in JS
  104. * @var array
  105. * @see _loadChildOptions()
  106. * @see _generateInlineScript()
  107. */
  108. private $_values = array();
  109. public function getType()
  110. {
  111. return 'hierselect';
  112. }
  113. /**
  114. * Class constructor
  115. *
  116. * Hierselect element can understand the following keys in $data parameter:
  117. * - 'options': data to populate child elements' options with. Passed to
  118. * {@link loadOptions()} method.
  119. * - 'size': number of selects in hierselect. If not given will be set
  120. * from size of options array or size of array passed to setValue()
  121. * $data is propagated to created Select elements with these keys removed.
  122. *
  123. * @param string $name Element name
  124. * @param string|array $attributes Attributes (either a string or an array)
  125. * @param array $data Additional element data
  126. */
  127. public function __construct($name = null, $attributes = null, array $data = array())
  128. {
  129. if (!empty($data['size'])) {
  130. $this->size = $data['size'];
  131. }
  132. $options = isset($data['options'])? $data['options']: array();
  133. unset($data['options'], $data['size']);
  134. parent::__construct($name, $attributes, $data);
  135. $this->loadOptions($options);
  136. }
  137. /**
  138. * Initializes the the options for each select element.
  139. *
  140. * Format is a bit more complex than for a simple select as we need to know
  141. * which options are related to the ones in the previous select:
  142. *
  143. * Ex:
  144. * <code>
  145. * // first select
  146. * $select1[0] = 'Pop';
  147. * $select1[1] = 'Classical';
  148. * $select1[2] = 'Funeral doom';
  149. *
  150. * // second select
  151. * $select2[0][0] = 'Red Hot Chilly Peppers';
  152. * $select2[0][1] = 'The Pixies';
  153. * $select2[1][0] = 'Wagner';
  154. * $select2[1][1] = 'Strauss';
  155. * $select2[2][0] = 'Pantheist';
  156. * $select2[2][1] = 'Skepticism';
  157. *
  158. * // Two selects
  159. * $sel = $form->addElement('hierselect', 'cds')->setLabel('Choose CD:');
  160. * $sel->loadOptions(array($select1, $select2));
  161. *
  162. * // If you have a third select with prices for the cds
  163. * $select3[0][0][0] = '15.00$';
  164. * $select3[0][0][1] = '17.00$';
  165. * // etc
  166. *
  167. * // You can now use
  168. * $sel = $form->addElement('hierselect', 'cds')->setLabel('Choose CD:');
  169. * $sel->loadOptions(array($select1, $select2, $select3));
  170. * </code>
  171. *
  172. * @param array $options Array of options defining each element
  173. * @param callback $callback Callback function to load additional options.
  174. * It will receive an array of keys and should return associative
  175. * array ('option value' => 'option text')
  176. * @param string $jsCallback Javascript function to load additional options
  177. * (presumably via some sort of AJAX request). It will receive an
  178. * array of keys and should return {'values': [...], 'texts': [...]}
  179. *
  180. * @return HTML_QuickForm2_Element_Hierselect
  181. * @throws HTML_QuickForm2_InvalidArgumentException
  182. */
  183. public function loadOptions(array $options, $callback = null, $jsCallback = null)
  184. {
  185. if (null !== $callback && !is_callable($callback, false, $callbackName)) {
  186. throw new HTML_QuickForm2_InvalidArgumentException(
  187. 'Hierselect expects a valid callback for loading options, \'' .
  188. $callbackName . '\' was given'
  189. );
  190. }
  191. $this->options = $options;
  192. $this->callback = $callback;
  193. $this->jsCallback = $jsCallback;
  194. $this->size = max($this->size, count($options));
  195. $this->_createSelects();
  196. $this->_loadChildOptions();
  197. return $this;
  198. }
  199. /**
  200. * Populates hierselect with Select elements
  201. */
  202. private function _createSelects()
  203. {
  204. for ($i = count($this); $i < $this->size; $i++) {
  205. $data = $this->getData();
  206. unset($data['label']);
  207. $this->appendChild(new HTML_QuickForm2_Element_Select(
  208. $i, array('id' => self::generateId($this->getName() . "[{$i}]")) + $this->getAttributes(), $data
  209. ));
  210. }
  211. }
  212. /**
  213. * Loads options for child Select elements
  214. */
  215. private function _loadChildOptions()
  216. {
  217. $idx = 0;
  218. $this->_values = array();
  219. foreach ($this as $select) {
  220. if (empty($this->options[$idx])) {
  221. $this->options[$idx] = array();
  222. }
  223. $keys = $this->_values;
  224. $array =& $this->options[$idx++];
  225. while (!empty($keys)) {
  226. $key = array_shift($keys);
  227. if (!isset($array[$key])) {
  228. if (!empty($keys)) {
  229. $array[$key] = array();
  230. } elseif (!empty($this->callback)) {
  231. $array[$key] = call_user_func($this->callback, $this->_values);
  232. } else {
  233. // Most probably called from constructor with neither
  234. // options nor callback provided
  235. return;
  236. }
  237. }
  238. $array =& $array[$key];
  239. }
  240. $select->loadOptions($array);
  241. $this->_values[] = null !== ($v = $select->getValue())? $v: key($array);
  242. }
  243. }
  244. /**
  245. * Sets the element's value
  246. *
  247. * This also creates missing selects and loads their options, in addition
  248. * to {@link HTML_QuickForm2_Container_Group::setValue()} behaviour
  249. *
  250. * @param array $value
  251. *
  252. * @return HTML_QuickForm2_Element_Hierselect
  253. */
  254. public function setValue($value)
  255. {
  256. $this->size = max($this->size, count($value));
  257. $this->_createSelects();
  258. parent::setValue($value);
  259. $this->_loadChildOptions();
  260. return $this;
  261. }
  262. /**
  263. * Sets the element's name
  264. *
  265. * Need to override group's implementation due to overridden updateValue()
  266. *
  267. * @param string $name
  268. *
  269. * @return HTML_QuickForm2_Element_Hierselect
  270. */
  271. public function setName($name)
  272. {
  273. parent::setName($name);
  274. $this->updateValue();
  275. return $this;
  276. }
  277. /**
  278. * Called when the element needs to update its value from form's data sources
  279. *
  280. * Hierselect uses the Element's implementation of updateValue() since its
  281. * values need to be passed through setValue() to properly update options of
  282. * its child selects.
  283. */
  284. protected function updateValue()
  285. {
  286. $name = $this->getName();
  287. foreach ($this->getDataSources() as $ds) {
  288. if (null !== ($value = $ds->getValue($name))) {
  289. $this->setValue($value);
  290. return;
  291. }
  292. }
  293. }
  294. /**
  295. * Prepares options for JS encoding
  296. *
  297. * We need to preserve order of options when adding them via javascript, so
  298. * cannot use object literal and for/in loop (see bug #16603). Therefore we
  299. * convert an associative array of options to two arrays of their values
  300. * and texts.
  301. *
  302. * @param array $ary Options array
  303. * @param int $depth Depth within options array
  304. *
  305. * @link http://pear.php.net/bugs/bug.php?id=16603
  306. * @return array Array with separate options and texts
  307. */
  308. private function _prepareOptions($ary, $depth)
  309. {
  310. if (!is_array($ary)) {
  311. $ret = $ary;
  312. } elseif (0 == $depth) {
  313. $ret = array('values' => array_keys($ary), 'texts' => array_values($ary));
  314. } else {
  315. $ret = array();
  316. foreach ($ary as $k => $v) {
  317. $ret[$k] = $this->_prepareOptions($v, $depth - 1);
  318. }
  319. }
  320. return $ret;
  321. }
  322. /**
  323. * Generates inline javascript containing element's defaults and (available) options
  324. *
  325. * @return string
  326. */
  327. private function _generateInlineScript()
  328. {
  329. // we store values and options with id of first select rather than with
  330. // the element's name since the former has more chances to be unique
  331. $selectId = reset($this->elements)->getId();
  332. $cr = HTML_Common2::getOption('linebreak');
  333. $js = "qf.elements.hierselect.defaults['{$selectId}'] = " .
  334. HTML_QuickForm2_JavascriptBuilder::encode($this->_values) . ";{$cr}";
  335. $jsParts = array();
  336. for ($i = 1; $i < count($this->options); $i++) {
  337. $jsParts[] = empty($this->options[$i])
  338. ? '{}' : HTML_QuickForm2_JavascriptBuilder::encode($this->_prepareOptions(
  339. $this->options[$i], $i
  340. ));
  341. }
  342. $js .= "qf.elements.hierselect.options['{$selectId}'] = [{$cr}" . implode(",{$cr}", $jsParts) . "{$cr}];";
  343. return $js;
  344. }
  345. /**
  346. * Generates a javascript function call to initialize hierselect behaviour
  347. *
  348. * @return string
  349. */
  350. private function _generateInitScript()
  351. {
  352. HTML_QuickForm2_Loader::loadClass('HTML_QuickForm2_JavascriptBuilder');
  353. $ids = array();
  354. foreach ($this as $element) {
  355. $ids[] = $element->getId();
  356. }
  357. return 'qf.elements.hierselect.init(' . HTML_QuickForm2_JavascriptBuilder::encode($ids)
  358. . (empty($this->jsCallback)? '': ", {$this->jsCallback}") . ');';
  359. }
  360. /**
  361. * Renders the hierselect using the given renderer
  362. *
  363. * @param HTML_QuickForm2_Renderer $renderer
  364. *
  365. * @return HTML_QuickForm2_Renderer
  366. * @throws HTML_QuickForm2_Exception if number of selects in hierselect cannot
  367. * be determined
  368. */
  369. public function render(HTML_QuickForm2_Renderer $renderer)
  370. {
  371. if (0 == $this->size) {
  372. throw new HTML_QuickForm2_Exception(
  373. 'Unable to determine number of selects in hierselect'
  374. );
  375. }
  376. if ($this->toggleFrozen()) {
  377. // frozen hierselect does not need any javascript
  378. return parent::render($renderer);
  379. }
  380. $jsBuilder = $renderer->getJavascriptBuilder();
  381. $jsBuilder->addLibrary('hierselect', 'quickform-hierselect.js');
  382. $jsBuilder->addElementJavascript($this->_generateInitScript());
  383. $script = $this->appendChild(new HTML_QuickForm2_Element_Script('script'))
  384. ->setContent($this->_generateInlineScript());
  385. parent::render($renderer);
  386. $this->removeChild($script);
  387. return $renderer;
  388. }
  389. }
  390. ?>