1 <?php 2 /* vim: set expandtab tabstop=4 shiftwidth=4: */ 3 // +----------------------------------------------------------------------+ 4 // | PHP version 4.0 | 5 // +----------------------------------------------------------------------+ 6 // | Copyright (c) 1997-2004 The PHP Group | 7 // +----------------------------------------------------------------------+ 8 // | This source file is subject to version 2.0 of the PHP license, | 9 // | that is bundled with this package in the file LICENSE, and is | 10 // | available at through the world-wide-web at | 11 // | http://www.php.net/license/2_02.txt. | 12 // | If you did not receive a copy of the PHP license and are unable to | 13 // | obtain it through the world-wide-web, please send a note to | 14 // | license@php.net so we can mail you a copy immediately. | 15 // +----------------------------------------------------------------------+ 16 // | Authors: Herim Vasquez <vasquezh@iro.umontreal.ca> | 17 // | Bertrand Mansion <bmansion@mamasam.com> | 18 // | Alexey Borzov <avb@php.net> 19 // +----------------------------------------------------------------------+ 20 // 21 // $Id$ 22 23 require_once('HTML/QuickForm/group.php'); 24 require_once('HTML/QuickForm/select.php'); 25 /** 26 * Static utility methods. 27 */ 28 require_once 'HTML/QuickForm/utils.php'; 29 30 /** 31 * Class to dynamically create two or more HTML Select elements 32 * The first select changes the content of the second select and so on. 33 * This element is considered as a group. Selects will be named 34 * groupName[0], groupName[1], groupName[2]... 35 * 36 * @author Herim Vasquez <vasquezh@iro.umontreal.ca> 37 * @author Bertrand Mansion <bmansion@mamasam.com> 38 * @version 1.0 39 * @since PHP4.04pl1 40 * @access public 41 */ 42 class HTML_QuickForm_hierselect extends HTML_QuickForm_group 43 { 44 // {{{ properties 45 46 /** 47 * Options for all the select elements 48 * 49 * Format is a bit more complex as we need to know which options 50 * are related to the ones in the previous select: 51 * 52 * Ex: 53 * // first select 54 * $select1[0] = 'Pop'; 55 * $select1[1] = 'Classical'; 56 * $select1[2] = 'Funeral doom'; 57 * 58 * // second select 59 * $select2[0][0] = 'Red Hot Chil Peppers'; 60 * $select2[0][1] = 'The Pixies'; 61 * $select2[1][0] = 'Wagner'; 62 * $select2[1][1] = 'Strauss'; 63 * $select2[2][0] = 'Pantheist'; 64 * $select2[2][1] = 'Skepticism'; 65 * 66 * // If only need two selects 67 * // - and using the depracated functions 68 * $sel =& $form->addElement('hierselect', 'cds', 'Choose CD:'); 69 * $sel->setMainOptions($select1); 70 * $sel->setSecOptions($select2); 71 * 72 * // - and using the new setOptions function 73 * $sel =& $form->addElement('hierselect', 'cds', 'Choose CD:'); 74 * $sel->setOptions(array($select1, $select2)); 75 * 76 * // If you have a third select with prices for the cds 77 * $select3[0][0][0] = '15.00$'; 78 * $select3[0][0][1] = '17.00$'; 79 * etc 80 * 81 * // You can now use 82 * $sel =& $form->addElement('hierselect', 'cds', 'Choose CD:'); 83 * $sel->setOptions(array($select1, $select2, $select3)); 84 * 85 * @var array 86 * @access private 87 */ 88 var $_options = array(); 89 90 /** 91 * Number of select elements on this group 92 * 93 * @var int 94 * @access private 95 */ 96 var $_nbElements = 0; 97 98 /** 99 * The javascript used to set and change the options 100 * 101 * @var string 102 * @access private 103 */ 104 var $_js = ''; 105 106 // }}} 107 // {{{ constructor 108 109 /** 110 * Class constructor 111 * 112 * @param string $elementName (optional)Input field name attribute 113 * @param string $elementLabel (optional)Input field label in form 114 * @param mixed $attributes (optional)Either a typical HTML attribute string 115 * or an associative array. Date format is passed along the attributes. 116 * @param mixed $separator (optional)Use a string for one separator, 117 * use an array to alternate the separators. 118 * @access public 119 * @return void 120 */ 121 public function __construct($elementName=null, $elementLabel=null, $attributes=null, $separator=null) { 122 // TODO MDL-52313 Replace with the call to parent::__construct(). 123 HTML_QuickForm_element::__construct($elementName, $elementLabel, $attributes); 124 $this->_persistantFreeze = true; 125 if (isset($separator)) { 126 $this->_separator = $separator; 127 } 128 $this->_type = 'hierselect'; 129 $this->_appendName = true; 130 } //end constructor 131 132 /** 133 * Old syntax of class constructor. Deprecated in PHP7. 134 * 135 * @deprecated since Moodle 3.1 136 */ 137 public function HTML_QuickForm_hierselect($elementName=null, $elementLabel=null, $attributes=null, $separator=null) { 138 debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER); 139 self::__construct($elementName, $elementLabel, $attributes, $separator); 140 } 141 142 // }}} 143 // {{{ setOptions() 144 145 /** 146 * Initialize the array structure containing the options for each select element. 147 * Call the functions that actually do the magic. 148 * 149 * @param array $options Array of options defining each element 150 * 151 * @access public 152 * @return void 153 */ 154 function setOptions($options) 155 { 156 $this->_options = $options; 157 158 if (empty($this->_elements)) { 159 $this->_nbElements = count($this->_options); 160 $this->_createElements(); 161 } else { 162 // setDefaults has probably been called before this function 163 // check if all elements have been created 164 $totalNbElements = count($this->_options); 165 for ($i = $this->_nbElements; $i < $totalNbElements; $i ++) { 166 $this->_elements[] = new HTML_QuickForm_select($i, null, array(), $this->getAttributes()); 167 $this->_nbElements++; 168 } 169 } 170 171 $this->_setOptions(); 172 } // end func setMainOptions 173 174 // }}} 175 // {{{ setMainOptions() 176 177 /** 178 * Sets the options for the first select element. Deprecated. setOptions() should be used. 179 * 180 * @param array $array Options for the first select element 181 * 182 * @access public 183 * @deprecated Deprecated since release 3.2.2 184 * @return void 185 */ 186 function setMainOptions($array) 187 { 188 $this->_options[0] = $array; 189 190 if (empty($this->_elements)) { 191 $this->_nbElements = 2; 192 $this->_createElements(); 193 } 194 } // end func setMainOptions 195 196 // }}} 197 // {{{ setSecOptions() 198 199 /** 200 * Sets the options for the second select element. Deprecated. setOptions() should be used. 201 * The main _options array is initialized and the _setOptions function is called. 202 * 203 * @param array $array Options for the second select element 204 * 205 * @access public 206 * @deprecated Deprecated since release 3.2.2 207 * @return void 208 */ 209 function setSecOptions($array) 210 { 211 $this->_options[1] = $array; 212 213 if (empty($this->_elements)) { 214 $this->_nbElements = 2; 215 $this->_createElements(); 216 } else { 217 // setDefaults has probably been called before this function 218 // check if all elements have been created 219 $totalNbElements = 2; 220 for ($i = $this->_nbElements; $i < $totalNbElements; $i ++) { 221 $this->_elements[] = new HTML_QuickForm_select($i, null, array(), $this->getAttributes()); 222 $this->_nbElements++; 223 } 224 } 225 226 $this->_setOptions(); 227 } // end func setSecOptions 228 229 // }}} 230 // {{{ _setOptions() 231 232 /** 233 * Sets the options for each select element 234 * 235 * @access private 236 * @return void 237 */ 238 function _setOptions() 239 { 240 $arrayKeys = []; 241 foreach (array_keys($this->_elements) AS $key) { 242 if (isset($this->_options[$key])) { 243 if ((empty($arrayKeys)) || HTML_QuickForm_utils::recursiveIsset($this->_options[$key], $arrayKeys)) { 244 $array = empty($arrayKeys) ? $this->_options[$key] : HTML_QuickForm_utils::recursiveValue($this->_options[$key], $arrayKeys); 245 if (is_array($array)) { 246 $select =& $this->_elements[$key]; 247 $select->_options = array(); 248 $select->loadArray($array); 249 $value = is_array($v = $select->getValue()) ? $v[0] : key($array); 250 $arrayKeys[] = $value; 251 } 252 } 253 } 254 } 255 } // end func _setOptions 256 257 // }}} 258 // {{{ setValue() 259 260 /** 261 * Sets values for group's elements 262 * 263 * @param array $value An array of 2 or more values, for the first, 264 * the second, the third etc. select 265 * 266 * @access public 267 * @return void 268 */ 269 function setValue($value) 270 { 271 // fix for bug #6766. Hope this doesn't break anything more 272 // after bug #7961. Forgot that _nbElements was used in 273 // _createElements() called in several places... 274 $this->_nbElements = max($this->_nbElements, count($value)); 275 parent::setValue($value); 276 $this->_setOptions(); 277 } // end func setValue 278 279 // }}} 280 // {{{ _createElements() 281 282 /** 283 * Creates all the elements for the group 284 * 285 * @access private 286 * @return void 287 */ 288 function _createElements() 289 { 290 for ($i = 0; $i < $this->_nbElements; $i++) { 291 $this->_elements[] = new HTML_QuickForm_select($i, null, array(), $this->getAttributes()); 292 } 293 } // end func _createElements 294 295 // }}} 296 // {{{ toHtml() 297 298 function toHtml() 299 { 300 $this->_js = ''; 301 if (!$this->_flagFrozen) { 302 // set the onchange attribute for each element except last 303 $keys = array_keys($this->_elements); 304 $onChange = array(); 305 for ($i = 0; $i < count($keys) - 1; $i++) { 306 $select =& $this->_elements[$keys[$i]]; 307 $onChange[$i] = $select->getAttribute('onchange'); 308 $select->updateAttributes( 309 array('onchange' => '_hs_swapOptions(this.form, \'' . $this->_escapeString($this->getName()) . '\', ' . $keys[$i] . ');' . $onChange[$i]) 310 ); 311 } 312 313 // create the js function to call 314 if (!defined('HTML_QUICKFORM_HIERSELECT_EXISTS')) { 315 $this->_js .= <<<JAVASCRIPT 316 function _hs_findOptions(ary, keys) 317 { 318 var key = keys.shift(); 319 if (!key in ary) { 320 return {}; 321 } else if (0 == keys.length) { 322 return ary[key]; 323 } else { 324 return _hs_findOptions(ary[key], keys); 325 } 326 } 327 328 function _hs_findSelect(form, groupName, selectIndex) 329 { 330 if (groupName+'['+ selectIndex +']' in form) { 331 return form[groupName+'['+ selectIndex +']']; 332 } else { 333 return form[groupName+'['+ selectIndex +'][]']; 334 } 335 } 336 337 function _hs_unescapeEntities(str) 338 { 339 var div = document.createElement('div'); 340 div.innerHTML = str; 341 return div.childNodes[0] ? div.childNodes[0].nodeValue : ''; 342 } 343 344 function _hs_replaceOptions(ctl, optionList) 345 { 346 var j = 0; 347 ctl.options.length = 0; 348 for (i in optionList) { 349 var optionText = (-1 == optionList[i].indexOf('&'))? optionList[i]: _hs_unescapeEntities(optionList[i]); 350 ctl.options[j++] = new Option(optionText, i, false, false); 351 } 352 } 353 354 function _hs_setValue(ctl, value) 355 { 356 var testValue = {}; 357 if (value instanceof Array) { 358 for (var i = 0; i < value.length; i++) { 359 testValue[value[i]] = true; 360 } 361 } else { 362 testValue[value] = true; 363 } 364 for (var i = 0; i < ctl.options.length; i++) { 365 if (ctl.options[i].value in testValue) { 366 ctl.options[i].selected = true; 367 } 368 } 369 } 370 371 function _hs_swapOptions(form, groupName, selectIndex) 372 { 373 var hsValue = []; 374 for (var i = 0; i <= selectIndex; i++) { 375 hsValue[i] = _hs_findSelect(form, groupName, i).value; 376 } 377 378 _hs_replaceOptions(_hs_findSelect(form, groupName, selectIndex + 1), 379 _hs_findOptions(_hs_options[groupName][selectIndex], hsValue)); 380 if (selectIndex + 1 < _hs_options[groupName].length) { 381 _hs_swapOptions(form, groupName, selectIndex + 1); 382 } 383 } 384 385 function _hs_onReset(form, groupNames) 386 { 387 for (var i = 0; i < groupNames.length; i++) { 388 try { 389 for (var j = 0; j <= _hs_options[groupNames[i]].length; j++) { 390 _hs_setValue(_hs_findSelect(form, groupNames[i], j), _hs_defaults[groupNames[i]][j]); 391 if (j < _hs_options[groupNames[i]].length) { 392 _hs_replaceOptions(_hs_findSelect(form, groupNames[i], j + 1), 393 _hs_findOptions(_hs_options[groupNames[i]][j], _hs_defaults[groupNames[i]].slice(0, j + 1))); 394 } 395 } 396 } catch (e) { 397 if (!(e instanceof TypeError)) { 398 throw e; 399 } 400 } 401 } 402 } 403 404 function _hs_setupOnReset(form, groupNames) 405 { 406 setTimeout(function() { _hs_onReset(form, groupNames); }, 25); 407 } 408 409 function _hs_onReload() 410 { 411 var ctl; 412 for (var i = 0; i < document.forms.length; i++) { 413 for (var j in _hs_defaults) { 414 if (ctl = _hs_findSelect(document.forms[i], j, 0)) { 415 for (var k = 0; k < _hs_defaults[j].length; k++) { 416 _hs_setValue(_hs_findSelect(document.forms[i], j, k), _hs_defaults[j][k]); 417 } 418 } 419 } 420 } 421 422 if (_hs_prevOnload) { 423 _hs_prevOnload(); 424 } 425 } 426 427 var _hs_prevOnload = null; 428 if (window.onload) { 429 _hs_prevOnload = window.onload; 430 } 431 window.onload = _hs_onReload; 432 433 var _hs_options = {}; 434 var _hs_defaults = {}; 435 436 JAVASCRIPT; 437 define('HTML_QUICKFORM_HIERSELECT_EXISTS', true); 438 } 439 // option lists 440 $jsParts = array(); 441 for ($i = 1; $i < $this->_nbElements; $i++) { 442 $jsParts[] = $this->_convertArrayToJavascript($this->_options[$i]); 443 } 444 $this->_js .= "\n_hs_options['" . $this->_escapeString($this->getName()) . "'] = [\n" . 445 implode(",\n", $jsParts) . 446 "\n];\n"; 447 // default value; if we don't actually have any values yet just use 448 // the first option (for single selects) or empty array (for multiple) 449 $values = array(); 450 foreach (array_keys($this->_elements) as $key) { 451 if (is_array($v = $this->_elements[$key]->getValue())) { 452 $values[] = count($v) > 1? $v: $v[0]; 453 } else { 454 // XXX: accessing the supposedly private _options array 455 $values[] = $this->_elements[$key]->getMultiple() || empty($this->_elements[$key]->_options[0])? 456 array(): 457 $this->_elements[$key]->_options[0]['attr']['value']; 458 } 459 } 460 $this->_js .= "_hs_defaults['" . $this->_escapeString($this->getName()) . "'] = " . 461 $this->_convertArrayToJavascript($values, false) . ";\n"; 462 } 463 include_once('HTML/QuickForm/Renderer/Default.php'); 464 $renderer = new HTML_QuickForm_Renderer_Default(); 465 $renderer->setElementTemplate('{element}'); 466 parent::accept($renderer); 467 468 if (!empty($onChange)) { 469 $keys = array_keys($this->_elements); 470 for ($i = 0; $i < count($keys) - 1; $i++) { 471 $this->_elements[$keys[$i]]->updateAttributes(array('onchange' => $onChange[$i])); 472 } 473 } 474 return (empty($this->_js)? '': "<script type=\"text/javascript\">\n//<![CDATA[\n" . $this->_js . "//]]>\n</script>") . 475 $renderer->toHtml(); 476 } // end func toHtml 477 478 // }}} 479 // {{{ accept() 480 481 function accept(&$renderer, $required = false, $error = null) 482 { 483 $renderer->renderElement($this, $required, $error); 484 } // end func accept 485 486 // }}} 487 // {{{ onQuickFormEvent() 488 489 function onQuickFormEvent($event, $arg, &$caller) 490 { 491 if ('updateValue' == $event) { 492 // we need to call setValue() so that the secondary option 493 // matches the main option 494 return HTML_QuickForm_element::onQuickFormEvent($event, $arg, $caller); 495 } else { 496 $ret = parent::onQuickFormEvent($event, $arg, $caller); 497 // add onreset handler to form to properly reset hierselect (see bug #2970) 498 if ('addElement' == $event) { 499 $onReset = $caller->getAttribute('onreset'); 500 if (strlen($onReset)) { 501 if (strpos($onReset, '_hs_setupOnReset')) { 502 $caller->updateAttributes(array('onreset' => str_replace('_hs_setupOnReset(this, [', "_hs_setupOnReset(this, ['" . $this->_escapeString($this->getName()) . "', ", $onReset))); 503 } else { 504 $caller->updateAttributes(array('onreset' => "var temp = function() { {$onReset} } ; if (!temp()) { return false; } ; if (typeof _hs_setupOnReset != 'undefined') { return _hs_setupOnReset(this, ['" . $this->_escapeString($this->getName()) . "']); } ")); 505 } 506 } else { 507 $caller->updateAttributes(array('onreset' => "if (typeof _hs_setupOnReset != 'undefined') { return _hs_setupOnReset(this, ['" . $this->_escapeString($this->getName()) . "']); } ")); 508 } 509 } 510 return $ret; 511 } 512 } // end func onQuickFormEvent 513 514 // }}} 515 // {{{ _convertArrayToJavascript() 516 517 /** 518 * Converts PHP array to its Javascript analog 519 * 520 * @access private 521 * @param array PHP array to convert 522 * @param bool Generate Javascript object literal (default, works like PHP's associative array) or array literal 523 * @return string Javascript representation of the value 524 */ 525 function _convertArrayToJavascript($array, $assoc = true) 526 { 527 if (!is_array($array)) { 528 return $this->_convertScalarToJavascript($array); 529 } else { 530 $items = array(); 531 foreach ($array as $key => $val) { 532 $item = $assoc? "'" . $this->_escapeString($key) . "': ": ''; 533 if (is_array($val)) { 534 $item .= $this->_convertArrayToJavascript($val, $assoc); 535 } else { 536 $item .= $this->_convertScalarToJavascript($val); 537 } 538 $items[] = $item; 539 } 540 } 541 $js = implode(', ', $items); 542 return $assoc? '{ ' . $js . ' }': '[' . $js . ']'; 543 } 544 545 // }}} 546 // {{{ _convertScalarToJavascript() 547 548 /** 549 * Converts PHP's scalar value to its Javascript analog 550 * 551 * @access private 552 * @param mixed PHP value to convert 553 * @return string Javascript representation of the value 554 */ 555 function _convertScalarToJavascript($val) 556 { 557 if (is_bool($val)) { 558 return $val ? 'true' : 'false'; 559 } elseif (is_int($val) || is_double($val)) { 560 return $val; 561 } elseif (is_string($val)) { 562 return "'" . $this->_escapeString($val) . "'"; 563 } elseif (is_null($val)) { 564 return 'null'; 565 } else { 566 // don't bother 567 return '{}'; 568 } 569 } 570 571 // }}} 572 // {{{ _escapeString() 573 574 /** 575 * Quotes the string so that it can be used in Javascript string constants 576 * 577 * @access private 578 * @param string 579 * @return string 580 */ 581 function _escapeString($str) 582 { 583 return strtr($str,array( 584 "\r" => '\r', 585 "\n" => '\n', 586 "\t" => '\t', 587 "'" => "\\'", 588 '"' => '\"', 589 '\\' => '\\\\' 590 )); 591 } 592 593 // }}} 594 } // end class HTML_QuickForm_hierselect 595 ?>
title
Description
Body
title
Description
Body
title
Description
Body
title
Body