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.com>                            |
17// +----------------------------------------------------------------------+
18//
19// $Id: Flexy.php 334846 2014-09-12 04:50:56Z alan_k $
20//
21//  Base Compiler Class
22//  Standard 'Original Flavour' Flexy compiler
23
24// this does the main conversion, (eg. for {vars and methods})
25// it relays into Compiler/Tag & Compiler/Flexy for tags and namespace handling.
26
27
28
29
30require_once 'HTML/Template/Flexy/Tokenizer.php';
31require_once 'HTML/Template/Flexy/Token.php';
32
33class HTML_Template_Flexy_Compiler_Flexy extends HTML_Template_Flexy_Compiler {
34
35    /**
36     * reference to calling controller
37     *
38     * @var $flexy HTML_Template_Flexy
39     * @access public
40     */
41    var $flexy;
42
43
44
45    /**
46    * The current template (Full path)
47    *
48    * @var string
49    * @access public
50    */
51    var $currentTemplate;
52
53    /**
54     * when using flexy::contents - this contains the map of
55     * <... flexy:conntents="KEY">.....VALUE ...<...>
56     *
57     */
58    var $contentStrings = array();
59
60    /**
61    * The compile method.
62    *
63    * @params   object HTML_Template_Flexy
64    * @params   string|false string to compile of false to use a file.
65    * @return   string   filename of template
66    * @access   public
67    */
68    function compile($flexy, $string=false)
69    {
70        // read the entire file into one variable
71
72        // note this should be moved to new HTML_Template_Flexy_Token
73        // and that can then manage all the tokens in one place..
74        global $_HTML_TEMPLATE_FLEXY_COMPILER;
75
76        $this->flexy = $flexy;
77
78        $this->currentTemplate  = $flexy->currentTemplate;
79
80        $gettextStrings = &$_HTML_TEMPLATE_FLEXY_COMPILER['gettextStrings'];
81        $gettextStrings = array(); // reset it.
82
83        if (@$this->options['debug']) {
84            echo "compiling template $flexy->currentTemplate<BR>";
85
86        }
87
88        // reset the elements.
89        $flexy->_elements = array();
90
91        // replace this with a singleton??
92
93        $GLOBALS['_HTML_TEMPLATE_FLEXY']['currentOptions']  = $this->options;
94        $GLOBALS['_HTML_TEMPLATE_FLEXY']['elements']        = array();
95        $GLOBALS['_HTML_TEMPLATE_FLEXY']['filename']        = $flexy->currentTemplate;
96        $GLOBALS['_HTML_TEMPLATE_FLEXY']['prefixOutput']    = '';
97        $GLOBALS['_HTML_TEMPLATE_FLEXY']['compiledTemplate']= $flexy->compiledTemplate;
98
99
100        // initialize Translation 2, and
101        $this->flexy->initializeTranslator();
102
103
104        // load the template!
105        $data = $string;
106        $res = false;
107        if ($string === false) {
108            $data = file_get_contents($flexy->currentTemplate);
109        }
110
111        // PRE PROCESS {_(.....)} translation markers.
112        if (strpos($data, '{_(') !== false) {
113            $data = $this->preProcessTranslation($data);
114        }
115
116        // Tree generation!!!
117
118
119
120        if (!$this->options['forceCompile'] && isset($_HTML_TEMPLATE_FLEXY_COMPILER['cache'][md5($data)])) {
121            $res = $_HTML_TEMPLATE_FLEXY_COMPILER['cache'][md5($data)];
122        } else {
123
124
125            $tokenizer = new HTML_Template_Flexy_Tokenizer($data);
126            $tokenizer->fileName = $flexy->currentTemplate;
127
128
129
130            //$tokenizer->debug=1;
131            $tokenizer->options['ignore_html'] = $this->options['nonHTML'];
132
133
134            require_once 'HTML/Template/Flexy/Token.php';
135            $res = HTML_Template_Flexy_Token::buildTokens($tokenizer);
136            if ($this->is_a($res, 'PEAR_Error')) {
137                return $res;
138            }
139            $_HTML_TEMPLATE_FLEXY_COMPILER['cache'][md5($data)] = $res;
140
141        }
142
143
144        // technically we shouldnt get here as we dont cache errors..
145        if ($this->is_a($res, 'PEAR_Error')) {
146            return $res;
147        }
148
149        // turn tokens into Template..
150
151        $data = $res->compile($this);
152
153        if ($this->is_a($data, 'PEAR_Error')) {
154            return $data;
155        }
156
157        $data = $GLOBALS['_HTML_TEMPLATE_FLEXY']['prefixOutput'] . $data;
158
159        if (   $flexy->options['debug'] > 1) {
160            echo "<B>Result: </B><PRE>".htmlspecialchars($data)."</PRE><BR>\n";
161        }
162
163        if ($this->options['nonHTML']) {
164           $data =  str_replace("?>\n", "?>\n\n", $data);
165        }
166
167
168
169
170        // at this point we are into writing stuff...
171        if ($flexy->options['compileToString']) {
172            if (   $flexy->options['debug']) {
173                echo "<B>Returning string:<BR>\n";
174            }
175
176            $flexy->elements =  $GLOBALS['_HTML_TEMPLATE_FLEXY']['elements'];
177            return $data;
178        }
179
180
181
182
183        // error checking?
184        $file  = $flexy->compiledTemplate;
185        if (isset($flexy->options['output.block'])) {
186            list($file, $part) = explode('#', $file);
187        }
188
189        if( ($cfp = fopen($file, 'w')) ) {
190            if ($flexy->options['debug']) {
191                echo "<B>Writing: </B>$file<BR>\n";
192            }
193            fwrite($cfp, $data);
194            fclose($cfp);
195
196            chmod($file, 0775);
197            // make the timestamp of the two items match.
198            clearstatcache();
199            touch($file, filemtime($flexy->currentTemplate));
200            if ($file != $flexy->compiledTemplate) {
201                chmod($flexy->compiledTemplate, 0775);
202                // make the timestamp of the two items match.
203                clearstatcache();
204                touch($flexy->compiledTemplate, filemtime($flexy->currentTemplate));
205            }
206
207
208        } else {
209            return HTML_Template_Flexy::staticRaiseError('HTML_Template_Flexy::failed to write to '.$flexy->compiledTemplate,
210                HTML_TEMPLATE_FLEXY_ERROR_FILE, HTML_TEMPLATE_FLEXY_ERROR_RETURN);
211        }
212        // gettext strings
213
214        if (file_exists($flexy->getTextStringsFile)) {
215            unlink($flexy->getTextStringsFile);
216        }
217
218        if($gettextStrings && ($cfp = fopen( $flexy->getTextStringsFile, 'w') ) ) {
219
220            fwrite($cfp, serialize(array_unique($gettextStrings)));
221            fclose($cfp);
222            chmod($flexy->getTextStringsFile, 0664);
223        }
224
225        // elements
226        if (file_exists($flexy->elementsFile)) {
227            unlink($flexy->elementsFile);
228        }
229
230        if( $GLOBALS['_HTML_TEMPLATE_FLEXY']['elements'] &&
231            ($cfp = fopen( $flexy->elementsFile, 'w') ) ) {
232            fwrite($cfp, serialize( $GLOBALS['_HTML_TEMPLATE_FLEXY']['elements']));
233            fclose($cfp);
234            chmod($flexy->elementsFile, 0664);
235            // now clear it.
236
237        }
238
239        return true;
240    }
241
242
243
244
245
246
247    /**
248    * do the early tranlsation of {_(......)_} text
249    *
250    *
251    * @param    input string
252    * @return   output string
253    * @access   public
254    */
255    function preProcessTranslation($data) {
256        global $_HTML_TEMPLATE_FLEXY_COMPILER;
257        $matches = array();
258        $lmatches = explode ('{_(', $data);
259        array_shift($lmatches);
260        // shift the first..
261        foreach ($lmatches as $k) {
262            if (false === strpos($k, ')_}')) {
263                continue;
264            }
265            $x = explode(')_}', $k);
266            $matches[] = $x[0];
267        }
268
269
270       //echo '<PRE>';print_r($matches);
271        // we may need to do some house cleaning here...
272        $_HTML_TEMPLATE_FLEXY_COMPILER['gettextStrings'] = $matches;
273
274
275        // replace them now..
276        // ** leaving in the tag (which should be ignored by the parser..
277        // we then get rid of the tags during the toString method in this class.
278        foreach($matches as $v) {
279            $data = str_replace('{_('.$v.')_}', '{_('.$this->flexy->translateString($v).')_}', $data);
280        }
281        return $data;
282    }
283
284
285
286
287
288    /**
289    * Flag indicating compiler is inside {_( .... )_} block, and should not
290    * add to the gettextstrings array.
291    *
292    * @var boolean
293    * @access public
294    */
295    var $inGetTextBlock = false;
296
297    /**
298    * This is the base toString Method, it relays into toString{TokenName}
299    *
300    * @param    object    HTML_Template_Flexy_Token_*
301    *
302    * @return   string     string to build a template
303    * @access   public
304    * @see      toString*
305    */
306
307
308    function toString($element)
309    {
310        static $len = 26; // strlen('HTML_Template_Flexy_Token_');
311        if ($this->options['debug'] > 1) {
312            $x = $element;
313            unset($x->children);
314            //echo htmlspecialchars(print_r($x,true))."<BR>\n";
315        }
316        if ($element->token == 'GetTextStart') {
317            $this->inGetTextBlock = true;
318            return '';
319        }
320        if ($element->token == 'GetTextEnd') {
321            $this->inGetTextBlock = false;
322            return '';
323        }
324
325
326        $class = get_class($element);
327        if (strlen($class) >= $len) {
328            $type = substr($class, $len);
329            return $this->{'toString'.$type}($element);
330        }
331
332        $ret = $element->value;
333        $add = $element->compileChildren($this);
334        if ($this->is_a($add, 'PEAR_Error')) {
335            return $add;
336        }
337        $ret .= $add;
338
339        if ($element->close) {
340            $add = $element->close->compile($this);
341            if ($this->is_a($add, 'PEAR_Error')) {
342                return $add;
343            }
344            $ret .= $add;
345        }
346
347        return $ret;
348    }
349
350
351    /**
352    *   HTML_Template_Flexy_Token_Else toString
353    *
354    * @param    object    HTML_Template_Flexy_Token_Else
355    *
356    * @return   string     string to build a template
357    * @access   public
358    * @see      toString*
359    */
360
361
362    function toStringElse($element)
363     {
364        // pushpull states to make sure we are in an area.. - should really check to see
365        // if the state it is pulling is a if...
366        if ($element->pullState() === false) {
367            return $this->appendHTML(
368                "<font color=\"red\">Unmatched {else:} on line: {$element->line}</font>"
369                );
370        }
371        $element->pushState();
372        return $this->appendPhp("} else {");
373    }
374
375    /**
376    *   HTML_Template_Flexy_Token_End toString
377    *
378    * @param    object    HTML_Template_Flexy_Token_Else
379    *
380    * @return   string     string to build a template
381    * @access   public
382    * @see      toString*
383    */
384
385    function toStringEnd($element)
386    {
387        // pushpull states to make sure we are in an area.. - should really check to see
388        // if the state it is pulling is a if...
389        if ($element->pullState() === false) {
390            return $this->appendHTML(
391                "<font color=\"red\">Unmatched {end:} on line: {$element->line}</font>"
392                );
393        }
394
395        return $this->appendPhp("}");
396    }
397
398    /**
399    *   HTML_Template_Flexy_Token_EndTag toString
400    *
401    * @param    object    HTML_Template_Flexy_Token_EndTag
402    *
403    * @return   string     string to build a template
404    * @access   public
405    * @see      toString*
406    */
407
408
409
410    function toStringEndTag($element)
411    {
412        return $this->toStringTag($element);
413    }
414
415
416
417    /**
418    *   HTML_Template_Flexy_Token_Foreach toString
419    *
420    * @param    object    HTML_Template_Flexy_Token_Foreach
421    *
422    * @return   string     string to build a template
423    * @access   public
424    * @see      toString*
425    */
426
427
428    function toStringForeach($element)
429    {
430
431        $loopon = $element->toVar($element->loopOn);
432        if ($this->is_a($loopon, 'PEAR_Error')) {
433            return $loopon;
434        }
435
436        $ret = 'if ($this->options[\'strict\'] || ('.
437            'is_array('. $loopon. ')  || ' .
438            'is_object(' . $loopon  . '))) ' .
439            'foreach(' . $loopon  . " ";
440
441        $ret .= "as \${$element->key}";
442
443        if ($element->value) {
444            $ret .=  " => \${$element->value}";
445        }
446        $ret .= ") {";
447
448        $element->pushState();
449        $element->pushVar($element->key);
450        $element->pushVar($element->value);
451        return $this->appendPhp($ret);
452    }
453    /**
454    *   HTML_Template_Flexy_Token_If toString
455    *
456    * @param    object    HTML_Template_Flexy_Token_If
457    *
458    * @return   string     string to build a template
459    * @access   public
460    * @see      toString*
461    */
462
463    function toStringIf($element)
464    {
465
466        $var = $element->toVar($element->condition);
467        if ($this->is_a($var, 'PEAR_Error')) {
468            return $var;
469        }
470
471        $ret = "if (".$element->isNegative . $var .")  {";
472        $element->pushState();
473        return $this->appendPhp($ret);
474    }
475
476   /**
477    *  get Modifier Wrapper
478    *
479    * converts :h, :u, :r , .....
480    * @param    object    HTML_Template_Flexy_Token_Method|Var
481    *
482    * @return   array prefix,suffix
483    * @access   public
484    * @see      toString*
485    */
486
487    function getModifierWrapper($element)
488    {
489        $prefix = 'echo ';
490
491        $suffix = '';
492        $modifier = strlen(trim($element->modifier)) ? $element->modifier : ' ';
493
494        switch ($modifier) {
495            case 'h':
496                break;
497            case 'u':
498                $prefix = 'echo urlencode(';
499                $suffix = ')';
500                break;
501            case 'r':
502                $prefix = 'echo \'<pre>\'; echo htmlspecialchars(print_r(';
503                $suffix = ',true)); echo \'</pre>\';';
504                break;
505            case 'n':
506                // blank or value..
507                $numberformat = @$GLOBALS['_HTML_TEMPLATE_FLEXY']['currentOptions']['numberFormat'];
508                $prefix = 'echo number_format(';
509                $suffix = $GLOBALS['_HTML_TEMPLATE_FLEXY']['currentOptions']['numberFormat'] . ')';
510                break;
511            case 'b': // nl2br + htmlspecialchars
512                $prefix = 'echo nl2br(htmlspecialchars(';
513
514                // add language ?
515                $suffix = '))';
516                break;
517            case 'e':
518                $prefix = 'echo htmlentities(';
519                // add language ?
520                $suffix = ')';
521                break;
522
523            case ' ':
524                $prefix = 'echo htmlspecialchars(';
525                // add language ?
526                $suffix = ')';
527                break;
528            default:
529               $prefix = 'echo $this->plugin("'.trim($element->modifier) .'",';
530               $suffix = ')';
531
532
533        }
534
535        return array($prefix, $suffix);
536    }
537
538
539
540  /**
541    *   HTML_Template_Flexy_Token_Var toString
542    *
543    * @param    object    HTML_Template_Flexy_Token_Method
544    *
545    * @return   string     string to build a template
546    * @access   public
547    * @see      toString*
548    */
549
550    function toStringVar($element)
551    {
552        // ignore modifier at present!!
553
554        $var = $element->toVar($element->value);
555        if ($this->is_a($var, 'PEAR_Error')) {
556            return $var;
557        }
558        list($prefix, $suffix) = $this->getModifierWrapper($element);
559        return $this->appendPhp( $prefix . $var . $suffix .';');
560    }
561   /**
562    *   HTML_Template_Flexy_Token_Method toString
563    *
564    * @param    object    HTML_Template_Flexy_Token_Method
565    *
566    * @return   string     string to build a template
567    * @access   public
568    * @see      toString*
569    */
570
571    function toStringMethod($element)
572    {
573
574
575        // set up the modifier at present!!
576
577        list($prefix, $suffix) = $this->getModifierWrapper($element);
578
579        // add the '!' to if
580
581        if ($element->isConditional) {
582            $prefix = 'if ('.$element->isNegative;
583            $element->pushState();
584            $suffix = ')';
585        }
586
587
588        // check that method exists..
589        // if (method_exists($object,'method');
590        $bits = explode('.', $element->method);
591        $method = array_pop($bits);
592
593        $object = implode('.', $bits);
594
595        $var = $element->toVar($object);
596        if ($this->is_a($var, 'PEAR_Error')) {
597            return $var;
598        }
599
600        if (($object == 'GLOBALS') &&
601            $GLOBALS['_HTML_TEMPLATE_FLEXY']['currentOptions']['globalfunctions']) {
602            // we should check if they something weird like: GLOBALS.xxxx[sdf](....)
603            $var = $method;
604        } else {
605            $prefix = 'if ($this->options[\'strict\'] || (isset('.$var.
606                ') && method_exists('.$var .", '{$method}'))) " . $prefix;
607            $var = $element->toVar($element->method);
608        }
609
610
611        if ($this->is_a($var, 'PEAR_Error')) {
612            return $var;
613        }
614
615        $ret  =  $prefix;
616        $ret .=  $var . "(";
617        $s =0;
618
619
620
621        foreach($element->args as $a) {
622
623            if ($s) {
624                $ret .= ",";
625            }
626            $s =1;
627            if ($a{0} == '#') {
628                if (is_numeric(substr($a, 1, -1))) {
629                    $ret .= substr($a, 1, -1);
630                } else {
631                    $ret .= '"'. addslashes(substr($a, 1, -1)) . '"';
632                }
633                continue;
634            }
635
636            $var = $element->toVar($a);
637            if ($this->is_a($var, 'PEAR_Error')) {
638                return $var;
639            }
640            $ret .= $var;
641
642        }
643        $ret .= ")" . $suffix;
644
645        if ($element->isConditional) {
646            $ret .= ' { ';
647        } else {
648            $ret .= ";";
649        }
650
651
652
653        return $this->appendPhp($ret);
654
655
656
657   }
658   /**
659    *   HTML_Template_Flexy_Token_Processing toString
660    *
661    * @param    object    HTML_Template_Flexy_Token_Processing
662    *
663    * @return   string     string to build a template
664    * @access   public
665    * @see      toString*
666    */
667
668
669    function toStringProcessing($element)
670    {
671        // if it's XML then quote it..
672        if (strtoupper(substr($element->value, 2, 3)) == 'XML') {
673            return $this->appendPhp("echo '" . str_replace("'", "\\"."'", $element->value) . "';");
674        }
675        // otherwise it's PHP code - so echo it..
676        return $element->value;
677    }
678
679    /**
680    *   HTML_Template_Flexy_Token_Text toString
681    *
682    * @param    object    HTML_Template_Flexy_Token_Text
683    *
684    * @return   string     string to build a template
685    * @access   public
686    * @see      toString*
687    */
688
689
690
691    function toStringText($element)
692    {
693
694        // first get rid of stuff thats not translated etc.
695        // empty strings => output.
696        // comments -> just output
697        // our special tags -> output..
698
699        if (!strlen(trim($element->value) )) {
700            return $this->appendHtml($element->value);
701        }
702        // dont add comments to translation lists.
703
704        if (substr($element->value, 0, 4) == '<!--') {
705            return $this->appendHtml($element->value);
706        }
707        // ignore anything wrapped with {_( .... )_}
708        if ($this->inGetTextBlock) {
709            return $this->appendHtml($element->value);
710        }
711
712
713        if (!$element->isWord()) {
714            return $this->appendHtml($element->value);
715        }
716
717        // grab the white space at start and end (and keep it!
718
719        $value = ltrim($element->value);
720        $front = substr($element->value, 0, -strlen($value));
721        $value = rtrim($element->value);
722        $rear  = substr($element->value, strlen($value));
723        $value = trim($element->value);
724
725
726        // convert to escaped chars.. (limited..)
727        //$value = strtr($value,$cleanArray);
728
729        // this only applies to html templates
730        if (empty($this->flexy->options['nonHTML'])) {
731            $this->addStringToGettext($value);
732            $value = $this->flexy->translateString($value);
733        }
734        // its a simple word!
735        return $this->appendHtml($front . $value . $rear);
736
737    }
738
739
740
741      /**
742    *   HTML_Template_Flexy_Token_Cdata toString
743    *
744    * @param    object    HTML_Template_Flexy_Token_Cdata ?
745    *
746    * @return   string     string to build a template
747    * @access   public
748    * @see      toString*
749    */
750
751
752
753    function toStringCdata($element)
754    {
755        return $this->appendHtml($element->value);
756    }
757
758
759
760
761
762
763
764
765
766
767    /**
768    * addStringToGettext
769    *
770    * Adds a string to the gettext array.
771    *
772    * @param   mixed        preferably.. string to store
773    *
774    * @return   none
775    * @access   public
776    */
777
778    function addStringToGettext($string)
779    {
780        if (!empty($this->options['disableTranslate'])) {
781            return;
782        }
783        if (!is_string($string)) {
784            return;
785        }
786
787        if (!preg_match('/\w+/i', $string)) {
788            return;
789        }
790        $string = trim($string);
791
792        if (substr($string, 0, 4) == '<!--') {
793            return;
794        }
795
796        $GLOBALS['_HTML_TEMPLATE_FLEXY_COMPILER']['gettextStrings'][] = $string;
797    }
798
799
800
801     /**
802    *   HTML_Template_Flexy_Token_Tag toString
803    *
804    * @param    object    HTML_Template_Flexy_Token_Tag
805    *
806    * @return   string     string to build a template
807    * @access   public
808    * @see      toString*
809    */
810
811    function toStringTag($element) {
812
813        $original = $element->getAttribute('ALT');
814        // techncially only input type=(submit|button|input) alt=.. applies, but we may
815        // as well translate any occurance...
816        if ( (($element->tag == 'IMG') || ($element->tag == 'INPUT'))
817                && is_string($original) && strlen($original)) {
818            $this->addStringToGettext($original);
819            $quote = $element->ucAttributes['ALT']{0};
820            $element->ucAttributes['ALT'] = $quote  . $this->flexy->translateString($original). $quote;
821        }
822        $original = $element->getAttribute('TITLE');
823        if (is_string($original) && strlen($original)) {
824            $this->addStringToGettext($original);
825            $quote = $element->ucAttributes['TITLE']{0};
826            $element->ucAttributes['TITLE'] = $quote  . $this->flexy->translateString($original). $quote;
827        }
828
829
830        if (strpos($element->tag, ':') === false) {
831            $namespace = 'Tag';
832        } else {
833            $bits =  explode(':', $element->tag);
834            $namespace = $bits[0];
835        }
836        if ($namespace{0} == '/') {
837            $namespace = substr($namespace, 1);
838        }
839        if (empty($this->tagHandlers[$namespace])) {
840
841            require_once 'HTML/Template/Flexy/Compiler/Flexy/Tag.php';
842            $this->tagHandlers[$namespace] = &HTML_Template_Flexy_Compiler_Flexy_Tag::factory($namespace, $this);
843            if (!$this->tagHandlers[$namespace] ) {
844                return HTML_Template_Flexy::staticRaiseError('HTML_Template_Flexy::failed to create Namespace Handler '.$namespace .
845                    ' in file ' . $GLOBALS['_HTML_TEMPLATE_FLEXY']['filename'],
846                    HTML_TEMPLATE_FLEXY_ERROR_SYNTAX, HTML_TEMPLATE_FLEXY_ERROR_RETURN);
847            }
848
849        }
850        return $this->tagHandlers[$namespace]->toString($element);
851
852
853    }
854     /**
855     * PHP5 compat - arg...
856     * - where else does this affect
857     */
858    function classExists($class)
859    {
860        return (substr(phpversion(),0,1) < 5) ? class_exists($class) :  class_exists($class,false);
861    }
862
863
864}
865