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 &amp;
186            if ($this->tag == 'textbox') {  // XUL linefeed fix.
187                $value = str_replace("\n", '&#13;', htmlspecialchars($value,ENT_COMPAT,$charset));
188            } else {
189                $value = str_replace('&amp;nbsp;','&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