1<?php 2/* vim: set expandtab tabstop=4 shiftwidth=4: */ 3// +----------------------------------------------------------------------+ 4// | PHP Version 4 | 5// +----------------------------------------------------------------------+ 6// | Copyright (c) 1997, 1998, 1999, 2000, 2001 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// | Author: Alan Knowles <alan@akbkhome.com> | 17// | Based on HTML_Common by: Adam Daniel <adaniel1@eesus.jnj.com> | 18// +----------------------------------------------------------------------+ 19// 20// $Id: Element.php 299896 2010-05-28 06:03:39Z alan_k $ 21 22/** 23 * Lightweight HTML Element builder and render 24 * 25 * This differs from HTML_Common in the following ways: 26 * 27 * $element->attributes is Public 28 * $element->override if set to anything other than false, renders the value rather than 29 * the defined element 30 * 31 * $element->children is a recursvable child array which is rendered by toHTML 32 * $element->toHtml() is implemented 33 * $element->toHtmlNoClose() renders only the first tag and children (designed for <form 34 * No support for tab offsets, comments ... 35 * 36 * Full support for Select, and common Form elements using 37 * setValue() 38 * setOptions() 39 * 40 * overlay support with SetFrom - base + inherited.. 41 * 42 * attributes array values: 43 * key="value" // standard key="value" in output 44 * key = true // outputs just key. 45 * 46 * children can be 47 * another HTML_Element 48 * or string (raw text) 49 * 50 * 51 * @author Adam Daniel <adaniel1@eesus.jnj.com> 52 * @version 1.6 53 * @since PHP 4.0.3pl1 54 * @abstract 55 */ 56class HTML_Template_Flexy_Element { 57 58 59 60 /** 61 * Tag that this Element represents. 62 * @var array 63 * @access public 64 */ 65 var $tag = ''; 66 /** 67 * Associative array of table attributes 68 * Note Special values: 69 * true == only display the key 70 * false == remove 71 * 72 * @var array 73 * @access public 74 */ 75 var $attributes = array(); 76 77 /** 78 * Sequence array of children 79 * children that are strings are assumed to be text 80 * @var array 81 * @access public 82 */ 83 var $children = array(); 84 85 /** 86 * override the tag. 87 * if this is set to anything other than false, it will be output 88 * rather than the tags+children 89 * @var array 90 * @access public 91 */ 92 var $override = false; 93 /** 94 * prefix the tag. 95 * this is output by toHtml as a prefix to the tag (can be used for require tags) 96 * @var array 97 * @access private 98 */ 99 var $prefix = ''; 100 /** 101 * suffix the tag. 102 * this is output by toHtml as a suffix to the tag (can be used for error messages) 103 * @var array 104 * @access private 105 */ 106 var $suffix = ''; 107 108 /** 109 * a value for delayed merging into live objects 110 * if you set this on an element, it is merged by setValue, at merge time. 111 * @var array 112 * @access public 113 */ 114 var $value = null; 115 /** 116 * If an input element has a label element associated to it 117 * *and* the 'useElementLabels' option is true, then you can 118 * optionally set the text of this label. This permits 119 * to set custom strings for doing translations. 120 * @var string | null 121 * @access public 122 */ 123 var $label = null; 124 /** 125 * Class constructor 126 * @param mixed $attributes Associative array of table tag attributes 127 * or HTML attributes name="value" pairs 128 * @access public 129 */ 130 function HTML_Template_Flexy_Element($tag='', $attributes=null) 131 { 132 133 $this->tag = strtolower($tag); 134 if (false !== strpos($tag, ':')) { 135 $bits = explode(':',$this->tag); 136 $this->tag = $bits[0] . ':'.strtolower($bits[1]); 137 } 138 139 $this->setAttributes($attributes); 140 } // end constructor 141 142 143 /** 144 * Returns an HTML formatted attribute string 145 * @param array $attributes 146 * @return string 147 * @access private 148 */ 149 function attributesToHTML() 150 { 151 $strAttr = ''; 152 $xhtmlclose = ''; 153 154 $activeEngine = HTML_Template_Flexy::$activeEngine; 155 $charset = empty($activeEngine->options['charset']) ? 156 'ISO-8859-1' : 157 $activeEngine->options['charset']; 158 159 foreach ($this->attributes as $key => $value) { 160 161 // you shouldn't do this, but It shouldnt barf when you do.. 162 if (is_array($value) || is_object($value)) { 163 continue; 164 } 165 166 if ($key == 'flexy:xhtml') { 167 continue; 168 } 169 if ($value === false) { 170 continue; 171 } 172 if ($value === true) { 173 // this is not xhtml compatible.. 174 if ($key == '/') { 175 $xhtmlclose = ' /'; 176 continue; 177 } 178 if (isset($this->attributes['flexy:xhtml'])) { 179 $strAttr .= " {$key}=\"{$key}\""; 180 } else { 181 $strAttr .= ' ' . $key; 182 } 183 continue; 184 } 185 // dont replace & with & 186 if ($this->tag == 'textbox') { // XUL linefeed fix. 187 $value = str_replace("\n", ' ', htmlspecialchars($value,ENT_COMPAT,$charset)); 188 } else { 189 $value = str_replace('&nbsp;',' ',htmlspecialchars($value,ENT_COMPAT,$charset)); 190 } 191 // translation.. 192 193 194 if (($this->tag == 'input') && 195 $this->attributes['type'] == 'submit' && 196 $key == 'value') { 197 198 $value = htmlspecialchars($activeEngine->translateString($value)); 199 } 200 if (in_array($this->tag ,array('input','textarea')) && $key == 'placeholder') { 201 202 $value = htmlspecialchars($activeEngine->translateString($value)); 203 } 204 205 $strAttr .= ' ' . $key . '="' . $value . '"'; 206 207 208 } 209 $strAttr .= $xhtmlclose; 210 return $strAttr; 211 } // end func _getAttrString 212 213 /** 214 * Static Method to get key/value array from attributes. 215 * Returns a valid atrributes array from either a string or array 216 * @param mixed $attributes Either a typical HTML attribute string or an associative array 217 * @access private 218 */ 219 function parseAttributes($attributes) 220 { 221 if (is_array($attributes)) { 222 $ret = array(); 223 foreach ($attributes as $key => $value) { 224 if (is_int($key)) { 225 $ret[strtolower($value)] = true; 226 } else { 227 $ret[strtolower($key)] = $value; 228 } 229 } 230 return $ret; 231 232 } elseif (is_string($attributes)) { 233 $preg = "/(([A-Za-z_:]|[^\\x00-\\x7F])([A-Za-z0-9_:.-]|[^\\x00-\\x7F])*)" . 234 "([ \\n\\t\\r]+)?(=([ \\n\\t\\r]+)?(\"[^\"]*\"|'[^']*'|[^ \\n\\t\\r]*))?/"; 235 if (preg_match_all($preg, $attributes, $regs)) { 236 for ($counter=0; $counter<count($regs[1]); $counter++) { 237 $name = $regs[1][$counter]; 238 $check = $regs[0][$counter]; 239 $value = $regs[7][$counter]; 240 if (trim($name) == trim($check)) { 241 $arrAttr[strtolower(trim($name))] = strtolower(trim($name)); 242 } else { 243 if (substr($value, 0, 1) == "\"" || substr($value, 0, 1) == "'") { 244 $value = substr($value, 1, -1); 245 } 246 $arrAttr[strtolower(trim($name))] = trim($value); 247 } 248 } 249 return $arrAttr; 250 } 251 } 252 } // end func _parseAttributes 253 254 255 256 257 /** 258 * Utility function to set values from common tag types. 259 * @param HTML_Element $from override settings from another element. 260 * @access public 261 */ 262 263 function setValue($value) { 264 // store the value in all situations 265 $this->value = $value; 266 $tag = strtolower($this->tag); 267 if (strpos($tag,':') !== false) { 268 $bits = explode(':',$tag); 269 $tag = $bits[1]; 270 } 271 switch ($tag) { 272 case 'input': 273 switch (isset($this->attributes['type']) ? strtolower($this->attributes['type']) : '') { 274 case 'checkbox': 275 if (isset($this->attributes['checked'])) { 276 unset($this->attributes['checked']); 277 } 278 // if value is nto set, it doesnt make any difference what you set ? 279 if (!isset($this->attributes['value'])) { 280 return; 281 } 282 //print_r($this); echo "SET TO "; serialize($value); 283 if (isset($this->attributes['name']) && (substr($this->attributes['name'],-2) == '[]')) { 284 if (is_array($value) && 285 in_array((string) $this->attributes['value'],$value) 286 ) { 287 $this->attributes['checked'] = true; 288 } 289 // removed - see bug 15279 - not sure if there is any knock on effects from this. 290 ///return; 291 } 292 if ($this->attributes['value'] == $value) { 293 $this->attributes['checked'] = true; 294 } 295 296 297 return; 298 case 'radio': 299 if (isset($this->attributes['checked'])) { 300 unset($this->attributes['checked']); 301 } 302 // if we dont have values associated yet, store it.. 303 if (!isset($this->attributes['value'])) { 304 $this->value = $value; 305 return; 306 } 307 if ($this->attributes['value'] == $value) { 308 $this->attributes['checked'] = true; 309 } 310 return; 311 312 default: 313 // no other input accepts array as a value. 314 if (is_array($value)) { 315 return; 316 } 317 318 $this->attributes['value'] = $value; 319 return; 320 } 321 322 case 'select': 323 324 if (!is_array($value)) { 325 $value = array($value); 326 } 327 328 // its setting the default value.. 329 330 foreach($this->children as $i=>$child) { 331 332 if (is_string($child)) { 333 continue; 334 } 335 if ($child->tag == 'optgroup') { 336 foreach($this->children[$i]->children as $ii=>$child) { 337 338 // does the value exist and match.. 339 if (isset($child->attributes['value']) 340 && in_array((string) $child->attributes['value'], $value)) 341 { 342 $this->children[$i]->children[$ii]->attributes['selected'] = 343 isset($this->attributes['flexy:xhtml']) ? 'selected' : true; 344 continue; 345 } 346 if (isset($child->attributes['value']) && 347 isset($this->children[$i]->children[$ii]->attributes['selected'])) 348 { 349 unset($this->children[$i]->children[$ii]->attributes['selected']); 350 continue; 351 } 352 // value doesnt exst.. 353 354 if (isset($this->children[$i]->children[$ii]->attributes['selected'])) { 355 unset($this->children[$i]->children[$ii]->attributes['selected']); 356 continue; 357 } 358 } 359 continue; 360 } 361 362 // standard option value... 363 //echo "testing {$child->attributes['value']} against ". print_r($value,true)."\n"; 364 // does the value exist and match.. 365 366 if (isset($child->attributes['value']) 367 && in_array((string) $child->attributes['value'], $value)) 368 { 369 370 371 $this->children[$i]->attributes['selected'] = 372 isset($this->attributes['flexy:xhtml']) ? 'selected' : true;; 373 continue; 374 } 375 // no value attribute try and use the contents. 376 if (!isset($child->attributes['value']) 377 && is_string($child->children[0]) 378 && in_array((string) $child->children[0], $value)) 379 { 380 381 $this->children[$i]->attributes['selected'] = 382 isset($this->attributes['flexy:xhtml']) ? 'selected' : true; 383 continue; 384 } 385 386 if (isset($child->attributes['value']) && 387 isset($this->children[$i]->attributes['selected'])) 388 { 389 //echo "clearing selected\n"; 390 unset($this->children[$i]->attributes['selected']); 391 continue; 392 } 393 // value doesnt exst.. 394 395 if (isset($this->children[$i]->attributes['selected'])) { 396 //echo "clearing selected\n"; 397 unset($this->children[$i]->attributes['selected']); 398 continue; 399 } 400 401 402 } 403 return; 404 case 'textarea': 405 case 'label': 406 $activeEngine = HTML_Template_Flexy::$activeEngine; 407 $charset = empty($activeEngine->options['charset']) ? 408 'ISO-8859-1' : 409 $activeEngine->options['charset']; 410 411 $this->children = array(htmlspecialchars($value,ENT_COMPAT,$charset)); 412 return; 413 case '': // dummy objects. 414 $this->value = $value; 415 return; 416 417 // XUL elements 418 case 'menulist': 419 case 'textbox': 420 case 'checkbox': 421 require_once 'HTML/Template/Flexy/Element/Xul.php'; 422 HTML_Template_Flexy_Element_Xul::setValue($this,$value); 423 return ; 424 425 default: 426 if (is_array($value)) { 427 return; 428 } 429 $this->value = $value; 430 } 431 432 433 434 435 } 436 /** 437 * Utility function equivilant to HTML_Select - loadArray ** 438 * but using 439 * key=>value maps 440 * <option value="key">Value</option> 441 * Key=key (eg. both the same) maps to 442 * <option>key</option> 443 * and label = array(key=>value) maps to 444 * <optgroup label="label"> <option value="key">value</option></optgroup> 445 * 446 * $element->setOptions(array('a'=>'xxx','b'=>'yyy')); 447 * or 448 * $element->setOptions(array('a','b','c','d'),true); 449 * 450 * 451 *. 452 * @param HTML_Element $from override settings from another element. 453 * @param HTML_Element $noValue ignore the key part of the array 454 * @access public 455 */ 456 457 function setOptions($array,$noValue=false) 458 { 459 if (!is_array($array)) { 460 $this->children = array(); 461 return; 462 } 463 $activeEngine = HTML_Template_Flexy::$activeEngine; 464 $charset = empty($activeEngine->options['charset']) ? 465 'ISO-8859-1' : 466 $activeEngine->options['charset']; 467 468 $tag = strtolower($this->tag); 469 $namespace = ''; 470 if (false !== strpos($this->tag, ':')) { 471 472 $bits = explode(':',$this->tag); 473 $namespace = $bits[0] . ':'; 474 $tag = strtolower($bits[1]); 475 476 } 477 // if we have specified a xultag!!? 478 if (strlen($tag) && ($tag != 'select')) { 479 require_once 'HTML/Template/Flexy/Element/Xul.php'; 480 return HTML_Template_Flexy_Element_Xul::setOptions($this,$array,$noValue); 481 } 482 483 foreach($array as $k=>$v) { 484 if (is_array($v)) { // optgroup 485 $child = new HTML_Template_Flexy_Element($namespace . 'optgroup',array('label'=>$k)); 486 foreach($v as $kk=>$vv) { 487 $atts=array(); 488 if (($kk !== $vv) && !$noValue) { 489 $atts = array('value'=>$kk); 490 } else { 491 $atts = array('value'=>$vv); 492 } 493 $add = new HTML_Template_Flexy_Element($namespace . 'option',$atts); 494 $add->children = array(htmlspecialchars($vv,ENT_COMPAT,$charset)); 495 $child->children[] = $add; 496 } 497 $this->children[] = $child; 498 continue; 499 } 500 $atts=array(); 501 if (($k !== $v) && !$noValue) { 502 $atts = array('value'=>$k); 503 } else { 504 $atts = array('value'=>$v); 505 } 506 $add = new HTML_Template_Flexy_Element($namespace . 'option',$atts); 507 $add->children = array(htmlspecialchars($v,ENT_COMPAT,$charset)); 508 $this->children[] = $add; 509 } 510 511 } 512 513 514 515 /** 516 * Returns THIS select element's options as an associative array 517 * Validates that $this element is "select" 518 * @return array $options 519 * @access public 520 */ 521 function getOptions() 522 { 523 524 $tag = strtolower($this->tag); 525 $namespace = ''; 526 if (false !== strpos($this->tag, ':')) { 527 $bits = explode(':',$this->tag); 528 $namespace = $bits[0] . ':'; 529 $tag = strtolower($bits[1]); 530 } 531 532 // this is not a select element 533 if (strlen($tag) && ($tag != 'select')) { 534 return false; 535 } 536 537 // creates an associative array that can be used by setOptions() 538 // null does work for no value ( a "Please Choose" option, for example) 539 foreach ($this->children as $child) { 540 if (is_object($child)) { 541 $child->attributes['value'] = isset($child->attributes['value']) ? $child->attributes['value'] : ''; 542 $children[$child->attributes['value']] = $child->children[0]; 543 } 544 } 545 return $children; 546 } 547 548 /** 549 * Removes all of this element's options 550 * Validates that $this element is "select" 551 * @return bool result 552 * @access public 553 */ 554 function clearOptions($children = array()) 555 { 556 $tag = strtolower($this->tag); 557 $namespace = ''; 558 if (false !== strpos($this->tag, ':')) { 559 $bits = explode(':',$this->tag); 560 $namespace = $bits[0] . ':'; 561 $tag = strtolower($bits[1]); 562 } 563 564 // this is not a select element 565 if (strlen($tag) && ($tag != 'select')) { 566 return false; 567 } 568 569 // clear this select's options 570 $this->children = array(null); 571 $this->values = array(null); 572 573 // If called with an array of new options go ahead and set them 574 $this->setOptions($children); 575 576 return true; 577 } 578 579 /** 580 * Sets the HTML attributes 581 * @param mixed $attributes Either a typical HTML attribute string or an associative array 582 * @access public 583 */ 584 585 function setAttributes($attributes) 586 { 587 $attrs= $this->parseAttributes($attributes); 588 if (!is_array($attrs)) { 589 return false; 590 } 591 foreach ($attrs as $key => $value) { 592 $this->attributes[$key] = $value; 593 } 594 } // end func updateAttributes 595 596 /** 597 * Removes an attributes 598 * 599 * @param string $attr Attribute name 600 * @since 1.4 601 * @access public 602 * @return void 603 * @throws 604 */ 605 function removeAttributes($attrs) 606 { 607 if (is_string($attrs)) { 608 $attrs = array($attrs); 609 } 610 foreach ($attrs as $attr) { 611 if (isset($this->attributes[strtolower($attr)])) { 612 $this->attributes[strtolower($attr)] = false; 613 } 614 } 615 } //end func removeAttribute 616 617 618 /** 619 * Output HTML and children 620 * 621 * @access public 622 * @param object $overlay = merge data from object. 623 * @return string 624 * @abstract 625 */ 626 function toHtml($overlay=false) 627 { 628 629 //echo "BEFORE<PRE>";print_R($this); 630 $ret = $this; 631 if ($overlay !== false) { 632 $ret = HTML_Template_Flexy::mergeElement($this,$overlay); 633 } 634 635 if ($ret->override !== false) { 636 return $ret->override; 637 } 638 $prefix = $ret->prefix; 639 if (is_object($prefix)) { 640 $prefix = $prefix->toHtml(); 641 } 642 $suffix = $ret->suffix; 643 if (is_object($suffix)) { 644 $suffix = $suffix->toHtml(); 645 } 646 //echo "AFTER<PRE>";print_R($ret); 647 648 $tag = $this->tag; 649 if (strpos($tag,':') !== false) { 650 $bits = explode(':',$tag); 651 $tag = $bits[1]; 652 } 653 // tags that never should have closers 654 $close = "</{$ret->tag}>"; 655 656 if (in_array(strtoupper($tag),array("INPUT","IMG", "LINK", "META", "HR", "BR"))) { 657 $close = ''; 658 if (isset($ret->attributes['flexy:xhtml'])) { 659 $this->attributes['/'] = true; 660 } 661 662 } 663 if (isset($this->attributes['/'])) { 664 $close = ''; 665 } 666 667 $close .= $suffix ; 668 669 return "{$prefix}<{$ret->tag}" . $ret->attributesToHTML() . '>' . $ret->childrenToHTML() . $close; 670 671 672 } // end func toHtml 673 674 675 /** 676 * Output Open Tag and any children and not Child tag (designed for use with <form + hidden elements> 677 * 678 * @access public 679 * @param object $overlay = merge data from object. 680 * @return string 681 * @abstract 682 */ 683 function toHtmlnoClose($overlay=false) 684 { 685 $ret = $this; 686 if ($ret->override !== false) { 687 return $ret->override; 688 } 689 if ($overlay !== false) { 690 $ret = HTML_Template_Flexy::mergeElement($this,$overlay); 691 } 692 693 694 return "<{$ret->tag}".$ret->attributesToHTML() . '>' . $ret->childrenToHTML(); 695 696 697 } // end func toHtml 698 699 700 /** 701 * Output HTML and children 702 * 703 * @access public 704 * @return string 705 * @abstract 706 */ 707 function childrenToHtml() 708 { 709 $ret = ''; 710 foreach($this->children as $child) { 711 if (!is_object($child)) { 712 $ret .= $child; 713 continue; 714 } 715 716 $ret .= $child->toHtml(); 717 } 718 return $ret; 719 } // end func toHtml 720 721 722 /** 723 * merge this element with another 724 * originally was in main engine 725 * 726 * @param HTML_Template_Flexy_Element $new (with data to replace/merge) 727 * @return HTML_Template_Flexy_Element the combined/merged data. 728 * @static 729 * @access public 730 */ 731 function merge($new) 732 { 733 // Clone objects is possible to avoid creating references between elements 734 $original = clone($this); 735 // no new - return original 736 if (!$new) { 737 return $original; 738 } 739 740 $new = clone($new); 741 742 // If the properties of $original differ from those of $new and 743 // they are set on $new, set them to $new's. Otherwise leave them 744 // as they are. 745 746 if ($new->tag && ($new->tag != $original->tag)) { 747 $original->tag = $new->tag; 748 } 749 750 if ($new->override !== false) { 751 $original->override = $new->override; 752 } 753 754 if (count($new->children)) { 755 //echo "<PRE> COPY CHILDREN"; print_r($from->children); 756 $original->children = $new->children; 757 } 758 759 if (is_array($new->attributes)) { 760 761 foreach ($new->attributes as $key => $value) { 762 $original->attributes[$key] = $value; 763 } 764 } 765 // originals never have prefixes or suffixes.. 766 $original->prefix = $new->prefix; 767 $original->suffix = $new->suffix; 768 769 if ($new->value !== null) { 770 $original->setValue($new->value); 771 } 772 if ($new->label !== null) { 773 $original->label = $new->label; 774 } 775 return $original; 776 777 } 778 779 780 781} // end class HTML_Template_Flexy_Element 782