1<?php
2/* vim: set expandtab tabstop=4 shiftwidth=4: */
3// +----------------------------------------------------------------------+
4// | PHP Version 4                                                        |
5// +----------------------------------------------------------------------+
6// | Copyright (c) 1997-2002 The PHP Group                                |
7// +----------------------------------------------------------------------+
8// | This source file is subject to version 2.02 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:  Alan Knowles <alan@akbkhome>                               |
17// +----------------------------------------------------------------------+
18//
19// $Id: Tag.php 334846 2014-09-12 04:50:56Z alan_k $
20/* FC/BC compatibility with php5 */
21if ( (substr(phpversion(),0,1) < 5) && !function_exists('clone')) {
22    eval('function clone($t) { return $t; }');
23}
24
25/**
26* Compiler That deals with standard HTML Tag output.
27* Since it's pretty complex it has it's own class.
28* I guess this class should deal with the main namespace
29* and the parent (standard compiler can redirect other namespaces to other classes.
30*
31* one instance of these exists for each namespace.
32*
33*
34* @version    $Id: Tag.php 334846 2014-09-12 04:50:56Z alan_k $
35*/
36
37class HTML_Template_Flexy_Compiler_Flexy_Tag
38{
39
40
41    /**
42    * Parent Compiler for
43    *
44    * @var  object  HTML_Template_Flexy_Compiler
45    *
46    * @access public
47    */
48    var $compiler;
49
50    /**
51    *
52    * Factory method to create Tag Handlers
53    *
54    * $type = namespace eg. <flexy:toJavascript loads Flexy.php
55    * the default is this... (eg. Tag)
56    *
57    *
58    * @param   string    Namespace handler for element.
59    * @param   object   HTML_Template_Flexy_Compiler
60    *
61    *
62    * @return    object    tag compiler
63    * @access   public
64    */
65
66    static function &factory($type,&$compiler) {
67        if (!$type) {
68            $type = 'Tag';
69        }
70
71        $class = 'HTML_Template_Flexy_Compiler_Flexy_' . $type;
72        if ($compiler->classExists($class)) {
73            $ret = new $class;
74            $ret->compiler = &$compiler;
75            return $ret;
76        }
77
78        $filename = 'HTML/Template/Flexy/Compiler/Flexy/' . ucfirst(strtolower($type)) . '.php';
79        if (!HTML_Template_Flexy_Compiler_Flexy_Tag::fileExistsInPath($filename)) {
80            $ret = HTML_Template_Flexy_Compiler_Flexy_Tag::factory('Tag',$compiler);
81            return $ret;
82        }
83        // if we dont have a handler - just use the basic handler.
84        if (!file_exists(dirname(__FILE__) . '/'. ucfirst(strtolower($type)) . '.php')) {
85            $type = 'Tag';
86        }
87
88        include_once 'HTML/Template/Flexy/Compiler/Flexy/' . ucfirst(strtolower($type)) . '.php';
89
90        $class = 'HTML_Template_Flexy_Compiler_Flexy_' . $type;
91        if (!$compiler->classExists($class)) {
92            $ret = false;
93            return $ret;
94        }
95        $ret = HTML_Template_Flexy_Compiler_Flexy_Tag::factory($type,$compiler);
96        return $ret;
97    }
98    /**
99    *
100    * Check that a file exists in the "include_path"
101    *
102    * @param   string    Filename
103    *
104    * @return    boolean  true if it is in there.
105    * @access   public
106    */
107    static function fileExistsInPath($filename) {
108        if (isset($GLOBALS['_'.__CLASS__]['cache'][$filename])) {
109            return $GLOBALS['_'.__CLASS__]['cache'][$filename];
110        }
111        $bits = explode(PATH_SEPARATOR,ini_get('include_path'));
112        foreach($bits as $b) {
113            if (file_exists("$b/$filename")) {
114                return $GLOBALS['_'.__CLASS__]['cache'][$filename] = true;
115            }
116        }
117        return $GLOBALS['_'.__CLASS__]['cache'][$filename] = false;
118    }
119
120
121
122    /**
123    * The current element to parse..
124    *
125    * @var object
126    * @access public
127    */
128    var $element;
129
130    /**
131    * Flag to indicate has attribute flexy:foreach (so you cant mix it with flexy:if!)
132    *
133    * @var boolean
134    * @access public
135    */
136    var $hasForeach = false;
137
138    /**
139    * toString - display tag, attributes, postfix and any code in attributes.
140    * Note first thing it does is call any parseTag Method that exists..
141    *
142    *
143    * @see parent::toString()
144    */
145    function toString($element)
146    {
147
148        global $_HTML_TEMPLATE_FLEXY_TOKEN;
149        global $_HTML_TEMPLATE_FLEXY;
150
151        // store the element in a variable
152        $this->element = $element;
153       // echo "toString: Line {$this->element->line} &lt;{$this->element->tag}&gt;\n";
154
155        // if the FLEXYSTARTCHILDREN flag was set, only do children
156        // normally set in BODY tag.
157        // this will probably be superseeded by the Class compiler.
158
159        if (isset($element->ucAttributes['FLEXY:STARTCHILDREN'])) {
160
161            return $element->compileChildren($this->compiler);
162        }
163        // look for flexy:ignore..
164        $flexyignore = $this->parseAttributeIgnore();
165
166        // rewriting should be done with a tag.../flag.
167
168        $this->reWriteURL("HREF");
169        $this->reWriteURL("SRC");
170        $this->reWriteURL("BACKGROUND");
171
172        // handle elements
173        if (($ret =$this->_parseTags()) !== false) {
174            return $ret;
175        }
176        // these add to the close tag..
177
178        $ret  = $this->parseAttributeForeach();
179        $ret .= $this->parseAttributeIf();
180
181        // support Custom Attributes...
182        require_once 'HTML/Template/Flexy/Compiler/Flexy/CustomFlexyAttributes.php';
183		$customFlexyAttributes = new HTML_Template_Flexy_Compiler_Flexy_CustomFlexyAttributes($this->compiler);
184		$customFlexyAttributes->doCustomAttributes($element);
185
186
187        $add = $this->toStringOpenTag($element,$ret);
188
189        if (is_object($add) && is_a($add,'PEAR_Error')) {
190            return $add;
191        }
192
193
194
195
196
197        // post stuff this is probably in the wrong place...
198
199        if ($element->postfix) {
200            foreach ($element->postfix as $e) {
201                $add = $e->compile($this->compiler);
202                if (is_object($add) && is_a($add,'PEAR_Error')) {
203                    return $add;
204                }
205                $ret .= $add;
206            }
207        } else if ($this->element->postfix) { // if postfixed by self..
208            foreach ($this->element->postfix as $e) {
209                $add = $e->compile($this->compiler);
210                if (is_object($add) && is_a($add,'PEAR_Error')) {
211                    return $add;
212                }
213
214                $ret .= $add;
215            }
216        }
217
218
219        $tmp = $this->toStringChildren($element,$ret);
220        if (is_object($tmp) && is_a($tmp,'PEAR_Error')) {
221            return  $tmp;
222        }
223        $tmp = $this->toStringCloseTag($element,$ret);
224        if (is_object($tmp) && is_a($tmp,'PEAR_Error')) {
225            return  $tmp;
226        }
227
228
229        // reset flexyignore
230
231        $_HTML_TEMPLATE_FLEXY_TOKEN['flexyIgnore'] = $flexyignore;
232
233        if (isset($_HTML_TEMPLATE_FLEXY['currentOptions']['output.block']) &&
234            ($_HTML_TEMPLATE_FLEXY['currentOptions']['output.block'] == $element->getAttribute('ID'))) {
235
236           // echo $_HTML_TEMPLATE_FLEXY['compiledTemplate'];
237
238            $fh = fopen($_HTML_TEMPLATE_FLEXY['compiledTemplate'],'w');
239            fwrite($fh,$ret);
240            fclose($fh);
241
242        }
243
244
245
246        return $ret;
247    }
248
249    /**
250     * convert a tag into compiled version
251     * @arg object Element
252     * @arg inout output string to template
253     * @return none? or pear error.
254     *
255     */
256
257    function toStringOpenTag(&$element,&$ret)
258    {
259        // START ADDITION...
260        if ((empty($element->tag)) || (empty($element->oTag))) {
261                return;
262        }
263        // ...END ADDITION
264
265
266        // spit ou the tag and attributes.
267
268        if ($element->oTag{0} == '?') {
269            $ret .= '<?php echo "<"; ?>';
270        } else {
271            $ret .= "<";
272        }
273        $ret .= $element->oTag;
274        //echo '<PRE>'.print_r($element->attributes,true);
275        foreach ($element->attributes as $k=>$v) {
276            // if it's a flexy tag ignore it.
277
278
279            if (strtoupper($k) == 'FLEXY:RAW') {
280                if (!is_array($v) || !isset($v[1]) || !is_object($v[1])) {
281                    return $this->_raiseErrorWithPositionAndTag(
282                        'flexy:raw only accepts a variable or method call as an argument, eg.'.
283                        ' flexy:raw="{somevalue}" you provided something else.' .
284
285                         null,   HTML_TEMPLATE_FLEXY_ERROR_DIE);
286                }
287                $add = $v[1]->compile($this->compiler);
288                if (is_object($add) && is_a($add,'PEAR_Error')) {
289                    return $add;
290                }
291                $ret .= ' ' . $add;
292                continue;
293
294            }
295
296            if (strtoupper(substr($k,0,6)) == 'FLEXY:') {
297                continue;
298            }
299            // true == an attribute without a ="xxx"
300            if ($v === true) {
301                $ret .= " $k";
302                continue;
303            }
304
305            // if it's an input with value or placeholder...
306            // the output the translated value..
307            if (is_string($v) && (in_array($element->tag , array('INPUT','TEXTAREA'))) &&
308                    (strtoupper($k) == 'VALUE' || strtoupper($k) == 'PLACEHOLDER'  )
309                ) {
310                // look up the translation..
311                $v = '"'. htmlspecialchars(
312                    $this->compiler->flexy->translateString(substr($v,1,-1))
313                    ) .'"';
314
315            }
316
317            // if it's a string just dump it.
318            if (is_string($v)) {
319                $v = str_replace(array('{_(',')_}'),array('',''),$v);
320                $ret .=  " {$k}={$v}";
321                continue;
322            }
323
324            // normally the value is an array of string, however
325            // if it is an object - then it's a conditional key.
326            // eg.  if (something) echo ' SELECTED';
327            // the object is responsible for adding it's space..
328
329            if (is_object($v)) {
330                $add = $v->compile($this->compiler);
331                if (is_object($add) && is_a($add,'PEAR_Error')) {
332                    return $add;
333                }
334
335                $ret .= $add;
336                continue;
337            }
338
339            // otherwise its a key="sometext{andsomevars}"
340
341            $ret .=  " {$k}=";
342
343            foreach($v as $item) {
344
345                if (is_string($item)) {
346                    // skip translation strings in tags.
347                    $item = str_replace(array('{_(',')_}'),array('',''),$item);
348                    $ret .= $item;
349                    continue;
350                }
351                $add = $item->compile($this->compiler);
352                if (is_object($add) && is_a($add,'PEAR_Error')) {
353                    return $add;
354                }
355                $ret .= $add;
356            }
357        }
358        $ret .= ">";
359	}
360    /**
361     * compile children to string.
362     * @arg object Element
363     * @arg inout output string to template
364     * @return none? or pear error.
365     */
366
367	function toStringChildren(&$element,&$ret)
368	{
369		 // dump contents of script raw - to prevent gettext additions..
370        //  print_r($element);
371		//  make sure tag isn't empty because it wouldn't make sense to output script without script tags
372        if (((! empty($element->tag)) && ($element->tag == 'SCRIPT'))
373			|| ((! empty($element->oTag)) && ($element->oTag == 'SCRIPT'))) {
374            foreach($element->children as $c) {
375                //print_R($c);
376                if (!$c) {
377                    continue;
378                }
379                if ($c->token == 'Text') {
380                    $ret .= $c->value;
381                    continue;
382                }
383                // techically we shouldnt have anything else inside of script tags.
384                // as the tokeinzer is supposted to ignore it..
385            }
386            return;
387        }
388        $add = $element->compileChildren($this->compiler);
389        if (is_object($add) && is_a($add,'PEAR_Error')) {
390            return $add;
391        }
392        $ret .= $add;
393
394	}
395  /**
396     * compile closing tag to string.
397     * @arg object Element
398     * @arg inout output string to template
399     * @return none? or pear error.
400     */
401
402	function toStringCloseTag(&$element,&$ret)
403	{
404		// output the closing tag.
405		//  If the tag is empty don't output closing tags, just output postfixes if any exist...
406        if ( !$element->close) {
407            return;
408        }
409
410        if ((! empty($element->tag)) && (! empty($element->oTag)))
411        {
412            $add = $element->close->compile($this->compiler);
413            if (is_object($add) && is_a($add,'PEAR_Error')) {
414                return $add;
415            }
416            $ret .= $add;
417            return;
418        }
419        // RICK - added by me
420        // element has a seperate closing tag (eg. </something>) and opening and closing tags should be removed
421        // because FLEXY:OMITTAG element attribute is set, but still need postfix stuff like for ending ifs and foreach
422        // so this is NOT OPTIONAL if foreach and if are not optional.
423        if ($element->close->postfix)  {
424            foreach ($element->close->postfix as $e)  {
425                $add = $e->compile($this->compiler);
426                if (is_object($add) && is_a($add,'PEAR_Error'))  {
427                    return $add;
428                }
429                $ret .= $add;
430            }
431            return;
432        }
433        if ($this->element->close->postfix)  { // if postfixed by self..
434            foreach ($this->element->close->postfix as $e)  {
435                $add = $e->compile($this->compiler);
436                if (is_object($add) && is_a($add,'PEAR_Error'))  {
437                    return $add;
438                }
439
440                $ret .= $add;
441            }
442            return;
443        }
444
445	}
446
447
448    /**
449    * Reads an flexy:foreach attribute -
450    *
451    *
452    * @return   string to add to output.
453    * @access   public
454    */
455
456    function parseAttributeIgnore()
457    {
458
459        global $_HTML_TEMPLATE_FLEXY_TOKEN;
460
461        $flexyignore = $_HTML_TEMPLATE_FLEXY_TOKEN['flexyIgnore'];
462
463        if ($this->element->getAttribute('FLEXY:IGNORE') !== false) {
464            $_HTML_TEMPLATE_FLEXY_TOKEN['flexyIgnore'] = true;
465            $this->element->clearAttribute('FLEXY:IGNORE');
466        }
467        return $flexyignore;
468
469    }
470
471    /**
472    * Reads an flexy:foreach attribute -
473    *
474    *
475    * @return   string to add to output.
476    * @access   public
477    */
478
479    function parseAttributeForeach()
480    {
481        global  $_HTML_TEMPLATE_FLEXY;
482        $foreach = $this->element->getAttribute('FLEXY:FOREACH');
483        if ($foreach === false) {
484            return '';
485        }
486        //var_dump($foreach);
487
488        $this->element->hasForeach = true;
489        // create a foreach element to wrap this with.
490        $foreachTokens = explode( ",", $foreach ); //usual
491        $first = array_shift($foreachTokens);
492        // we will accept first argument as a method call.. with arguments.
493        // this however does not  deal with  '#' with commas and braces insed very weill..
494        if (strpos($first, '(') !== false) {
495            while (strpos($first, ')') === false) {
496                if (!count($foreachTokens)) {
497                    return $this->_raiseErrorWithPositionAndTag(
498                        "Missing Closer on functin call: An flexy:foreach attribute was found. flexy:foreach=&quot;$foreach&quot;<BR>
499                        the syntax is  &lt;sometag flexy:foreach=&quot;onarray,withvariable[,withanothervar] &gt;<BR>",
500                        null,  HTML_TEMPLATE_FLEXY_ERROR_DIE);
501                }
502                $first .= ',' . array_shift($foreachTokens);
503            }
504        }
505        array_unshift($foreachTokens, $first);
506
507        $foreachObj =  $this->element->factory('Foreach',
508                $foreachTokens,
509                $this->element->line);
510        // failed = probably not enough variables..
511
512
513        if ($foreachObj === false) {
514
515            return $this->_raiseErrorWithPositionAndTag(
516                "Missing Arguments: An flexy:foreach attribute was found. flexy:foreach=&quot;$foreach&quot;<BR>
517                 the syntax is  &lt;sometag flexy:foreach=&quot;onarray,withvariable[,withanothervar] &gt;<BR>",
518                 null,  HTML_TEMPLATE_FLEXY_ERROR_DIE);
519        }
520
521
522
523        // does it have a closetag?
524        if (!$this->element->close) {
525
526            if ($this->element->getAttribute('/') === false) {
527
528
529                return $this->_raiseErrorWithPositionAndTag(
530                    "A flexy:foreach attribute was found without a corresponding &lt;/{$this->element->tag} tag",
531                    null, HTML_TEMPLATE_FLEXY_ERROR_DIE);
532            }
533            // it's an xhtml tag!
534            $this->element->postfix = array($this->element->factory("End", '', $this->element->line));
535        } else {
536            $this->element->close->postfix = array($this->element->factory("End", '', $this->element->line));
537        }
538
539        $this->element->clearAttribute('FLEXY:FOREACH');
540        return $foreachObj->compile($this->compiler);
541    }
542    /**
543    * Reads an flexy:if attribute -
544    *
545    *
546    * @return   string to add to output.
547    * @access   public
548    */
549
550    function parseAttributeIf()
551    {
552        // dont use the together, if is depreciated..
553        $if = $this->element->getAttribute('FLEXY:IF');
554
555        if ($if === false) {
556            return '';
557        }
558
559        if (isset($this->element->hasForeach)) {
560            return $this->_raiseErrorWithPositionAndTag("You may not use FOREACH and IF tags in the same tag",
561                  null, HTML_TEMPLATE_FLEXY_ERROR_DIE);
562        }
563
564        // allow if="!somevar"
565        $ifnegative = '';
566
567        if ($if{0} == '!') {
568            $ifnegative = '!';
569            $if = substr($if,1);
570        }
571        // if="xxxxx"
572        // if="xxxx.xxxx()" - should create a method prefixed with 'if:'
573        // these checks should really be in the if/method class..!!!
574
575
576
577        if (!preg_match('/^[_A-Z][A-Z0-9_]*(\[[0-9]+\])?((\[|%5B)[A-Z0-9_]+(\]|%5D))*'.
578                '(\.[_A-Z][A-Z0-9_]*((\[|%5B)[A-Z0-9_]+(\]|%5D))*)*(\\([^)]*\))?$/i',$if)) {
579            return $this->_raiseErrorWithPositionAndTag(
580                "IF tags only accept simple object.variable or object.method() values. {$if}",
581                null, HTML_TEMPLATE_FLEXY_ERROR_DIE);
582
583        }
584
585        if (substr($if,-1) == ')') {
586            // grab args..
587            $args = substr($if,strpos($if,'(')+1,-1);
588            // simple explode ...
589
590            $args = strlen(trim($args)) ? explode(',',$args) : array();
591            //print_R($args);
592
593            // this is nasty... - we need to check for quotes = eg. # at beg. & end..
594            $args_clean = array();
595            for ($i=0; $i<count($args); $i++) {
596                if ($args[$i]{0} != '#') {
597                    $args_clean[] = $args[$i];
598                    continue;
599                }
600                // single # - so , must be inside..
601                if ((strlen($args[$i]) > 1) && ($args[$i]{strlen($args[$i])-1}=='#')) {
602                    $args_clean[] = $args[$i];
603                    continue;
604                }
605
606                $args[$i] .=',' . $args[$i+1];
607                // remove args+1..
608                array_splice($args,$i+1,1);
609                $i--;
610                // reparse..
611            }
612
613
614
615            $ifObj =  $this->element->factory('Method',
616                    array('if:'.$ifnegative.substr($if,0,strpos($if,'(')), $args_clean),
617                    $this->element->line);
618        } else {
619            $ifObj =  $this->element->factory('If', $ifnegative.$if, $this->element->line);
620        }
621
622        // does it have a closetag? - you must have one - so you will have to hack in <span flexy:if=..><img></span> on tags
623        // that do not have close tags - it's done this way to try and avoid mistakes.
624
625
626        if (!$this->element->close) {
627            //echo "<PRE>";print_R($this->element);
628
629            if ($this->element->getAttribute('/') !== false) {
630                $this->element->postfix = array($this->element->factory("End",'', $this->element->line));
631            } else {
632
633                 return $this->_raiseErrorWithPositionAndTag(
634                    "An flexy:if attribute was found in &lt;{$this->element->name} tag without a corresponding &lt;/{$this->element->name} tag",
635                    null, HTML_TEMPLATE_FLEXY_ERROR_DIE);
636
637                }
638        } else {
639
640            $this->element->close->postfix = array($this->element->factory("End",'', $this->element->line));
641        }
642        $this->element->clearAttribute('FLEXY:IF');
643        return $ifObj->compile($this->compiler);
644    }
645
646     /**
647    * Reads Tags - and relays to parseTagXXXXXXX
648    *
649    *
650    * @return   string | false = html output or ignore (just output the tag)
651    * @access   private
652    */
653
654
655    function _parseTags()
656    {
657        global $_HTML_TEMPLATE_FLEXY_TOKEN;
658        // doesnt really need strtolower etc. as php functions are not case sensitive!
659
660
661        /* always render script correctly */
662        if (0 == strcasecmp($this->element->tag,"script")) {
663            return $this->parseTagScript();
664        }
665
666        if ($this->element->getAttribute('FLEXY:DYNAMIC')) {
667            return $this->compiler->appendPhp(
668                $this->getElementPhp( $this->element->getAttribute('ID') )
669            );
670
671        }
672
673        if ($this->element->getAttribute('FLEXY:IGNOREONLY') !== false) {
674            return false;
675        }
676        if ($_HTML_TEMPLATE_FLEXY_TOKEN['flexyIgnore']) {
677            return false;
678        }
679        $tag = $this->element->tag;
680        if (strpos($tag,':') !== false) {
681            $bits = explode(':',$tag);
682            $tag = $bits[1];
683        }
684
685        if (in_array(strtolower($tag), array('menulist','textbox','checkbox'))) {
686            $method = 'parseXulTag';
687        } else {
688            $method = 'parseTag'.$tag;
689            if (!method_exists($this,$method)) {
690                return false;
691            }
692        }
693
694        if (($this->element->getAttribute('NAME') === false) &&
695            ($this->element->getAttribute('ID') === false) ) {
696            return false;
697        }
698        // do any of the attributes use flexy data...
699        //foreach ($this->element->attributes as $k=>$v) {
700        //    if (is_array($v)) {
701        //        return false;
702        //   }
703        //}
704
705        //echo "call $method" . serialize($this->element->attributes). "\n";
706
707        return $this->$method();
708            // allow the parse methods to return output.
709
710    }
711
712
713
714
715    /**
716    * produces the code for dynamic elements
717    *
718    * @return   string | false = html output or ignore (just output the tag)
719    * @access   public
720    */
721
722    function getElementPhp($id,$mergeWithName=false,$varsOnly = false) {
723
724        global $_HTML_TEMPLATE_FLEXY;
725        static $tmpId=0;
726
727
728
729        if (!$id) {
730
731              return $this->_raiseErrorWithPositionAndTag("Dynamic tags require an ID value",
732                 null, HTML_TEMPLATE_FLEXY_ERROR_DIE);
733
734        }
735
736        // dont mix and match..
737        if (($this->element->getAttribute('FLEXY:IF') !== false) ||
738            ($this->element->getAttribute('FLEXY:FOREACH') !== false) )
739        {
740            return $this->_raiseErrorWithPositionAndTag(
741                " You can not mix flexy:if= or flexy:foreach= with dynamic form elements  " .
742                " (turn off tag to element code with flexyIgnore=0, use flexy:ignore=&quot;yes&quot; in the tag" .
743                " or put the conditional outside in a span tag",
744                null, HTML_TEMPLATE_FLEXY_ERROR_DIE);
745        }
746
747        if ((strtolower($this->element->getAttribute('TYPE')) == 'checkbox' ) &&
748                (substr($this->element->getAttribute('NAME'),-2) == '[]')) {
749            if ($this->element->getAttribute('ID') === false) {
750                $id = 'tmpId'. (++$tmpId);
751                $this->element->attributes['id'] = $id;
752                $this->element->ucAttributes['ID'] = $id;
753            } else {
754                $id = $this->element->getAttribute('ID');
755            }
756            $mergeWithName =  true;
757        }
758
759
760
761
762
763        if (isset($_HTML_TEMPLATE_FLEXY['elements'][$id])) {
764           // echo "<PRE>";print_r($this);print_r($_HTML_TEMPLATE_FLEXY['elements']);echo "</PRE>";
765            return $this->_raiseErrorWithPositionAndTag(
766                "The Dynamic tag Name '$id' has already been used previously by  tag &lt;{$_HTML_TEMPLATE_FLEXY['elements'][$id]->tag}&gt;",
767                null,HTML_TEMPLATE_FLEXY_ERROR_DIE);
768        }
769
770        $ret = '';
771        $unset = '';
772
773        //echo '<PRE>';print_r($this->element);echo '</PRE>';
774        if (isset($this->element->ucAttributes['FLEXY:USE'])) {
775            $ar = $this->element->ucAttributes['FLEXY:USE'];
776            $str = '';
777
778            for($i =1; $i < count($ar) -1; $i++) {
779                switch(true) {
780                    case (is_object($ar[$i]) && is_a($ar[$i], 'HTML_Template_Flexy_Token_Var')):
781                        $str .= '. ' . $ar[$i]->toVar($ar[$i]->value). ' ';
782                        break;
783                    case is_string($ar[$i]):
784                        $str .= '. ' . $ar[0] . $ar[$i] . $ar[0];
785                        break;
786                    default:
787                        return $this->_raiseErrorWithPositionAndTag(
788                            "unsupported type found in attribute, use flexy:ignore to prevent parsing or remove it. " .
789                                print_r($this->element,true),
790                            null,HTML_TEMPLATE_FLEXY_ERROR_DIE);
791                }
792            }
793            $str = trim(ltrim($str,'.'));
794            $_HTML_TEMPLATE_FLEXY['elements'][$id] = $this->toElement($this->element);
795
796            return  $ret .
797                '
798                if (!isset($this->elements['.$str.'])) {
799                    echo "ELEMENT MISSING $str";
800                }
801                echo $this->elements['.$str.']->toHtml();' .$unset;
802        }
803
804
805
806        if ($this->elementUsesDynamic($this->element)) {
807            $used = array();
808            foreach ($this->element->attributes as $attribute => $attributeValue) {
809                if (!is_array($attributeValue)) {
810                    continue;
811                }
812                if (strtoupper(substr($attribute,0,6)) == 'FLEXY:') {
813                    continue;
814                }
815                unset($this->element->attributes[$attribute]);
816                // generate code to put data into value..
817                $output_avar = '$this->elements[\''.$id.'\']->attributes[\''.$attribute.'\']';
818                $used[] = "'{$attribute}'";
819                $ret .= "\nif (!isset({$output_avar})) {\n";
820                // get the " or ' that encapsulates the element.
821                $wrapper = array_shift($attributeValue);
822                array_pop($attributeValue);
823                $ret .= "    {$output_avar} = '';\n";
824                //echo '<PRE>';print_r($attributeValue);echo '</PRE>';
825                foreach($attributeValue as $item) {
826
827                    if (is_string($item)) {
828                        $ret .= "    {$output_avar} .= {$wrapper}{$item}{$wrapper};\n";
829                        continue;
830                    }
831                    if (!is_object($item) || !is_a($item, 'HTML_Template_Flexy_Token_Var')) {
832                        return $this->_raiseErrorWithPositionAndTag(
833                            "unsupported type found in attribute, use flexy:ignore to prevent parsing or remove it. " .
834                                print_r($this->element,true),
835                            null,HTML_TEMPLATE_FLEXY_ERROR_DIE);
836                    }
837
838                    $var = $item->toVar($item->value);
839                    if (is_object($var) && is_a($var, 'PEAR_Error')) {
840                        return $var;
841                    }
842                    list($prefix,$suffix) = $this->compiler->getModifierWrapper($item);
843                    $prefix =  substr($prefix,4);
844
845                    $ret .= "    {$output_avar} .= {$prefix}{$var}{$suffix};\n";
846                }
847
848                $ret .= "}\n";
849            }
850            $ret .= "\$_attributes_used = array(".implode(',',$used).");\n";
851            $unset = "\n".'if (isset($_attributes_used)) {  foreach($_attributes_used as $_a) {'."\n".
852                     '    unset($this->elements[\''. $id .'\']->attributes[$_a]);'."\n" .
853                     "}}\n";
854
855
856        }
857
858
859
860
861        // this is for a case where you can use a sprintf as the name, and overlay it with a variable element..
862        $_HTML_TEMPLATE_FLEXY['elements'][$id] = $this->toElement($this->element);
863
864
865
866        if ($varsOnly) { // used by form tag.
867            return array($ret,$unset);
868        }
869
870        if ($var = $this->element->getAttribute('FLEXY:NAMEUSES')) {
871            // force var to use name (as radio buttons pick up id.)
872
873            $ename = $this->element->getAttribute('NAME');
874            $printfnamevar = $printfvar = 'sprintf(\''.$ename .'\','.$this->element->toVar($var) .')';
875            // support id replacement as well ...
876            $idreplace = '';
877
878
879            if (strtolower($this->element->getAttribute('TYPE')) == 'radio') {
880                $ename = $this->element->getAttribute('ID');
881                $printfvar = 'sprintf(\''.$ename .'\','.$this->element->toVar($var) .')';
882            }
883
884
885            if ($this->element->getAttribute('ID')) {
886                $idvar     = 'sprintf(\''.$this->element->getAttribute('ID') .'\','.$this->element->toVar($var) .')';
887                $idreplace = '$_element->attributes[\'id\'] = '.$idvar.';';
888            }
889            return  $ret . '
890                $_element = $this->mergeElement(
891                    $this->elements[\''.$id.'\'],
892                    isset('.$this->element->toVar($var).') && isset($this->elements['.$printfnamevar .']) ? $this->elements['.$printfnamevar .'] : false
893                );
894                $_element->attributes[\'name\'] = '.$printfnamevar. ';
895                ' . $idreplace . '
896                echo $_element->toHtml();' .$unset;
897
898        }
899
900
901        if ($mergeWithName) {
902            $name = $this->element->getAttribute('NAME');
903            //if ((strtolower($this->element->getAttribute('TYPE')) == 'checkbox') && (substr($name,-2) == '[]')) {
904            //    $name = substr($name,0,-2);
905            //}
906            if (!$name) {
907                return $ret .'
908                    $_element = $this->elements[\''.$id.'\'];
909                    echo  $_element->toHtml();' . $unset;
910            } else {
911                return  $ret .
912                    '
913                    $_element = $this->elements[\''.$id.'\'];
914                    if (isset($this->elements[\''.$name.'\'])) {
915                        $_element = $this->mergeElement($_element,$this->elements[\''.$name.'\']);
916                    }
917                    echo  $_element->toHtml();' . $unset;
918            }
919
920        }
921        return $ret . 'echo $this->elements[\''.$id.'\']->toHtml();'. $unset;
922
923    }
924
925    /**
926    * Reads an Script tag - check if PHP is allowed.
927    *
928    * @return   false|PEAR_Error
929    * @access   public
930    */
931    function parseTagScript()
932    {
933
934
935        $lang = $this->element->getAttribute('LANGUAGE');
936        if (!$lang) {
937            return false;
938        }
939        $lang = strtoupper($lang);
940        $allow = $GLOBALS['_HTML_TEMPLATE_FLEXY']['currentOptions']['allowPHP'];
941
942        if ($allow === true) {
943
944            return false;
945        }
946
947        if ($lang == "PHP") {
948            if ($allow == 'delete') {
949                return '';
950            }
951           return $this->_raiseErrorWithPositionAndTag('PHP code found in script (script)',
952                HTML_TEMPLATE_FLEXY_ERROR_SYNTAX,HTML_TEMPLATE_FLEXY_ERROR_RETURN
953            );
954        }
955        return false;
956
957    }
958    /**
959    * Reads an Input tag - build a element object for it
960    *
961    *
962    * @return   string | false = html output or ignore (just output the tag)
963    * @access   public
964    */
965
966
967    function parseTagInput()
968    {
969        global $_HTML_TEMPLATE_FLEXY;
970
971        $utype = strtoupper($this->element->getAttribute('TYPE'));
972        if (in_array($utype, array('SUBMIT','BUTTON','INPUT','')))  {
973            $this->compiler->addStringToGettext(
974                    $this->element->getAttribute('VALUE')
975            );
976
977        }
978        if (in_array($utype, array('TEXT','PASSWORD'))) {
979            $this->compiler->addStringToGettext(
980                    $this->element->getAttribute('PLACEHOLDER')
981            );
982        }
983        // form elements : format:
984        //value - fill out as PHP CODE
985
986        // as a general rule, this uses name, rather than ID except on
987        // radio
988        $mergeWithName = false;
989        $id = $this->element->getAttribute('NAME');
990
991
992        if (isset($this->element->ucAttributes['FLEXY:RAW'])) {
993            return $this->_raiseErrorWithPositionAndTag(
994                    "Flexy:raw can only be used with flexy:ignore, to prevent conversion of html ".
995                    "elements to flexy elements",
996                    null, HTML_TEMPLATE_FLEXY_ERROR_DIE
997            );
998        }
999        // checkboxes need more work.. - at the momemnt assume one with the same value...
1000        if (!in_array(strtoupper($this->element->getAttribute('TYPE')), array('RADIO'))) {
1001            if (!$id) {
1002                return false;
1003            }
1004            return $this->compiler->appendPhp($this->getElementPhp( $id,$mergeWithName));
1005
1006        }
1007        // now we are only dealing with radio buttons.. which are a bit odd...
1008
1009        // we need to create a generic holder for this based on the name..
1010        // this is only really available for use with setting stuff...
1011
1012        if (!isset($_HTML_TEMPLATE_FLEXY['elements'][$id])) {
1013            $_HTML_TEMPLATE_FLEXY['elements'][$id] = new HTML_Template_Flexy_Element("input",
1014                array('type'=>'radio'));
1015
1016        }
1017        // we dont really care if it is getting reused loads of times.. (expected as each radio button will use it.
1018        $name = $id;
1019        $id = $this->element->getAttribute('ID');
1020        if (!$id) {
1021            $id = $name . '_' . $this->element->getAttribute('VALUE');
1022        }
1023        // this get's tricky as we could end up with elements with the same name... (if value was not set..,
1024        // or two elements have the same name..
1025
1026        $mergeWithName = true;
1027
1028        return $this->compiler->appendPhp($this->getElementPhp( $id,$mergeWithName));
1029
1030    }
1031
1032    /**
1033    * Deal with a TextArea tag - build a element object for it
1034    *
1035    * @return   string | false = html output or ignore (just output the tag)
1036    * @access   public
1037    */
1038
1039    function parseTagTextArea()
1040    {
1041        // create a  translatable string for placeholder addString should handle if it does not exist..
1042        $this->compiler->addStringToGettext(
1043            $this->element->getAttribute('PLACEHOLDER')
1044        );
1045
1046        return $this->compiler->appendPhp(
1047            $this->getElementPhp( $this->element->getAttribute('NAME')));
1048
1049
1050
1051    }
1052    /**
1053    * Deal with Selects - build a element object for it (unless flexyignore is set)
1054    *
1055    *
1056    * @return   string | false = html output or ignore (just output the tag)
1057    * @access   public
1058    */
1059
1060    function parseTagSelect()
1061    {
1062        return $this->compiler->appendPhp(
1063            $this->getElementPhp( $this->element->getAttribute('NAME')));
1064    }
1065
1066
1067
1068
1069     /**
1070    * Reads an Form tag - and set up the element object header etc.
1071    *
1072    * @return   string | false = html output or ignore (just output the tag)
1073    * @access   public
1074    */
1075
1076    function parseTagForm()
1077    {
1078        global $_HTML_TEMPLATE_FLEXY;
1079        $copy = clone($this->element);
1080        $copy->children = array();
1081        $id = $this->element->getAttribute('NAME');
1082        // dont make forms dynamic if they dont have a name..
1083        if (!$id) {
1084            return false;
1085        }
1086
1087        // this adds the element to the elements array.
1088        $old = clone($this->element);
1089        $this->element = $copy;
1090        list($prefix,$suffix) = $this->getElementPhp($id,false,true);
1091        $this->element= $old;
1092
1093
1094        return
1095            $this->compiler->appendPhp($prefix .'echo $this->elements[\''.$id.'\']->toHtmlnoClose();'.$suffix) .
1096            $this->element->compileChildren($this->compiler) .
1097            $this->compiler->appendHtml( "</{$copy->oTag}>");
1098
1099    }
1100
1101    /**
1102    * Deal with Label - build a element object for it (unless flexyignore is set)
1103    *
1104    *
1105    * @return   string | false = html output or ignore (just output the tag)
1106    * @access   public
1107    */
1108
1109    function parseTagLabel()
1110    {
1111
1112        if (empty($GLOBALS['_HTML_TEMPLATE_FLEXY']['currentOptions']['useElementLabels'])) {
1113            return false;
1114        }
1115        // this may need some protection for general usage.....
1116
1117        $for = $this->element->getAttribute('FOR');
1118        $ret = '';
1119        $tmp = $this->toStringChildren($this->element, $ret);
1120        if (is_object($tmp) && is_a($tmp,'PEAR_Error')) {
1121            return $tmp;
1122        }
1123
1124        return $this->compiler->appendPhp(
1125                'echo "<label for=\"' . $for . '\">";' .
1126                'if (!empty($this->elements[\'' . $for . '\']->label)) ' .
1127                ' { echo htmlspecialchars($this->elements[\'' . $for . '\']->label); } else { ?>' .
1128                htmlspecialchars($ret) . '<? } ' .
1129                'echo "</label>";'
1130            );
1131    }
1132
1133
1134
1135    /**
1136    * reWriteURL - can using the config option 'url_rewrite'
1137    *  format "from:to,from:to"
1138    * only handle left rewrite.
1139    * so
1140    *  "/images:/myroot/images"
1141    * would change
1142    *   /images/xyz.gif to /myroot/images/xyz.gif
1143    *   /images/stylesheet/imagestyles.css to  /myroot/images/stylesheet/imagestyles.css
1144    *   note /imagestyles did not get altered.
1145    * will only work on strings (forget about doing /images/{someimage}
1146    *
1147    *
1148    * @param    string attribute to rewrite
1149    * @return   none
1150    * @access   public
1151    */
1152    function reWriteURL($which)
1153    {
1154        global  $_HTML_TEMPLATE_FLEXY;
1155
1156
1157        if (!is_string($original = $this->element->getAttribute($which))) {
1158            return;
1159        }
1160
1161        if ($original == '') {
1162            return;
1163        }
1164
1165        if (empty($_HTML_TEMPLATE_FLEXY['currentOptions']['url_rewrite'])) {
1166            return;
1167        }
1168
1169        $bits = explode(",",$_HTML_TEMPLATE_FLEXY['currentOptions']['url_rewrite']);
1170        $new = $original;
1171
1172        foreach ($bits as $bit) {
1173            if (!strlen(trim($bit))) {
1174                continue;
1175            }
1176            $parts = explode (':', $bit);
1177            if (!isset($parts[1])) {
1178                return $this->_raiseErrorWithPositionAndTag('HTML_Template_Flexy: url_rewrite syntax incorrect'.
1179                    print_r(array($bits,$bits),true),null,HTML_TEMPLATE_FLEXY_ERROR_DIE);
1180            }
1181            $new = preg_replace('#^'.$parts[0].'#',$parts[1], $new);
1182        }
1183
1184
1185        if ($original == $new) {
1186            return;
1187        }
1188        $this->element->ucAttributes[$which] = '"'. $new . '"';
1189    }
1190
1191    /**
1192    * Convert flexy tokens to HTML_Template_Flexy_Elements.
1193    *
1194    * @param    object token to convert into a element.
1195    * @return   object HTML_Template_Flexy_Element
1196    * @access   public
1197    */
1198    function toElement($element,$stripspaces  = false)
1199    {
1200        require_once 'HTML/Template/Flexy/Element.php';
1201        $ret = new HTML_Template_Flexy_Element;
1202
1203
1204        if (strtolower(get_class($element)) != 'html_template_flexy_token_tag') {
1205            $this->compiler->addStringToGettext($element->value);
1206            return $element->value;
1207        }
1208
1209
1210        $ret->tag = strtolower($element->tag);
1211
1212        if ($ret->tag == 'menulist') {  // for XUL menulist, remove the white space between tags..
1213            $stripspaces = true;
1214        }
1215
1216        $ats = $element->getAttributes();
1217
1218        if (isset($element->attributes['flexy:xhtml'])) {
1219            $ats['flexy:xhtml'] = true;
1220        }
1221
1222        foreach(array_keys($ats)  as $a) {
1223            $ret->attributes[$a] = $this->unHtmlEntities($ats[$a]);
1224        }
1225        //print_r($ats);
1226        if (!$element->children) {
1227            return $ret;
1228        }
1229
1230        // children - normally to deal with <element>
1231
1232        //print_r($this->children);
1233        foreach(array_keys($element->children) as $i) {
1234            // not quite sure why this happens - but it does.
1235            if (!is_object($element->children[$i])) {
1236                continue;
1237            }
1238            if ($stripspaces && (strtolower(get_class($element->children[$i])) != 'html_template_flexy_token_tag')) {
1239                continue;
1240            }
1241            $ret->children[] = $this->toElement($element->children[$i],$stripspaces);
1242        }
1243        return $ret;
1244    }
1245
1246      /**
1247    * do the reverse of htmlspecialchars on an attribute..
1248    *
1249    * copied from get-html-translation-table man page
1250    *
1251    * @param   mixed       from attribute values
1252    *
1253    * @return   string          return
1254    * @access   public
1255    * @see      see also methods.....
1256    */
1257
1258    function unHtmlEntities ($in)
1259    {
1260        if (!is_string($in)) {
1261            return $in;
1262        }
1263        $trans_tbl = get_html_translation_table (HTML_ENTITIES);
1264        $trans_tbl = array_flip ($trans_tbl);
1265        $ret = strtr ($in, $trans_tbl);
1266        return preg_replace_callback('/&#(\d+);/m', array($this,'unHtmlEntitiesChar'),$ret);
1267    }
1268
1269    function unHtmlEntitiesChar($ar) {
1270
1271        return chr($ar[1]);
1272    }
1273
1274     /**
1275    * Deal with XUL tags
1276    *
1277    * @return   string | false = html output or ignore (just output the tag)
1278    * @access   public
1279    */
1280
1281    function parseXulTag()
1282    {
1283
1284        // does it contain any flexy tags??
1285        if ($this->elementUsesDynamic($this->element)) {
1286            return false;
1287        }
1288
1289        return $this->compiler->appendPhp(
1290            $this->getElementPhp( $this->element->getAttribute('ID')));
1291    }
1292
1293     /**
1294    * Recursively search for any flexy:if flexy:foreach or {xxxx} tags inside tags..
1295    *
1296    * @param    HTML_Template_Flexy_Token   element to check.
1297    * @return   boolean true if it finds a  dynamic tag.
1298    * @access   public
1299    */
1300
1301
1302    function elementUsesDynamic($e)
1303    {
1304        if (!is_object($e)) {
1305            return false;
1306        }
1307        if (is_a($e,'HTML_Template_Flexy_Token_Var')) {
1308            return true;
1309        }
1310        if (is_a($e,'HTML_Template_Flexy_Token_Foreach')) {
1311            return true;
1312        }
1313        if (is_a($e,'HTML_Template_Flexy_Token_If')) {
1314            return true;
1315        }
1316        if (is_a($e,'HTML_Template_Flexy_Token_Method')) {
1317            return true;
1318        }
1319        if (!is_a($e,'HTML_Template_Flexy_Token_Tag')) {
1320            return false;
1321        }
1322        if  ($e->getAttribute('FLEXY:IF')  !== false) {
1323            return true;
1324        }
1325        if  ($e->getAttribute('FLEXY:FOREACH')  !== false) {
1326            return true;
1327        }
1328        foreach($e->attributes as $k=>$v) {
1329            if (is_array($v) || is_object($v)) {
1330                return true;
1331            }
1332        }
1333        foreach($e->children as $c) {
1334            if ($this->elementUsesDynamic($c)) {
1335                return true;
1336            }
1337        }
1338        return false;
1339
1340
1341
1342    }
1343
1344
1345    /**
1346    * calls HTML_Template_Flexy::raiseError() with the current file, line and tag
1347    * @param    string  Message to display
1348    * @param    type   (see HTML_Template_Flexy::raiseError())
1349    * @param    boolean  isFatal.
1350    *
1351    * @access   private
1352    */
1353    function _raiseErrorWithPositionAndTag($message, $type = null, $fatal = HTML_TEMPLATE_FLEXY_ERROR_RETURN ) {
1354        global $_HTML_TEMPLATE_FLEXY;
1355        $message = "Error:{$_HTML_TEMPLATE_FLEXY['filename']} on Line {$this->element->line}" .
1356                   " in Tag &lt;{$this->element->tag}&gt;:<BR>\n" . $message;
1357        return HTML_Template_Flexy::staticRaiseError($message, $type, $fatal);
1358    }
1359
1360
1361}
1362