Project

General

Profile

1
<?php
2
//error_reporting(E_ALL ^ E_NOTICE);
3
/*~ class.quickskin.php
4
.---------------------------------------------------------------------------.
5
|  Software: QuickSkin Class                                                |
6
|   Version: 5.0                                                            |
7
|   Contact: andy.prevost@worxteam.com,andy@codeworx.ca                     |
8
|      Info: http://quickskin.sourceforge.net                               |
9
|   Support: http://sourceforge.net/projects/quickskin/                     |
10
| ------------------------------------------------------------------------- |
11
|    Author: Andy Prevost andy.prevost@worxteam.com (admin)                 |
12
|    Author: Manuel 'EndelWar' Dalla Lana endelwar@aregar.it (former admin) |
13
|    Author: Philipp v. Criegern philipp@criegern.com (original founder)    |
14
| Copyright (c) 2002-2009, Andy Prevost. All Rights Reserved.               |
15
|    * NOTE: QuickSkin is the SmartTemplate project renamed. SmartTemplate  |
16
|            information and downloads can still be accessed at the         |
17
|            smarttemplate.sourceforge.net site                             |
18
| ------------------------------------------------------------------------- |
19
|   License: Distributed under the Lesser General Public License (LGPL)     |
20
|            http://www.gnu.org/copyleft/lesser.html                        |
21
| This program is distributed in the hope that it will be useful - WITHOUT  |
22
| ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or     |
23
| FITNESS FOR A PARTICULAR PURPOSE.                                         |
24
| ------------------------------------------------------------------------- |
25
| We offer a number of paid services:                                       |
26
| - Web Hosting on highly optimized fast and secure servers                 |
27
| - Technology Consulting                                                   |
28
| - Oursourcing (highly qualified programmers and graphic designers)        |
29
'---------------------------------------------------------------------------'
30
Last modified: January 01 2009 ~*/
31

    
32
/* designed to work with PHP5 - will NOT work with PHP4 */
33

    
34
/* PURPOSE: 'Compiles' HTML-Templates to PHP Code
35
 *
36
 * Usage Example I:
37
 *
38
 * $page = new QuickSkin( "template.html" );
39
 * $page->assign( 'TITLE',  'TemplateDemo - Userlist' );
40
 * $page->assign( 'user',   DB_read_all( 'select * from ris_user' ) );
41
 * $page->output();
42
 *
43
 * Usage Example II:
44
 *
45
 * $data = array(
46
 *             'TITLE' => 'TemplateDemo - Userlist',
47
 *             'user'  => DB_read_all( 'select * from ris_user' )
48
 *         );
49
 * $page = new QuickSkin( "template.html" );
50
 * $page->output( $data );
51
 *
52
 */
53

    
54
class QuickSkin {
55

    
56
  /////////////////////////////////////////////////
57
  // PROPERTIES, PUBLIC
58
  /////////////////////////////////////////////////
59

    
60
  /**
61
   * Reuse Code
62
   * Whether to use stored compiled php code or not (for debug purpose)
63
   * @var bool
64
   */
65
  public $reuse_code       = false;
66

    
67
  /**
68
   * Template Directory
69
   * Directory where all templates are stored, can be overwritten by
70
   * global configuration array $_CONFIG['template_dir']
71
   * @var string
72
   */
73
  public $template_dir     = '_skins/';
74

    
75
  /**
76
   * Temp Directory
77
   * Where to store compiled templates, can be overwritten by
78
   * global configuration array $_CONFIG['quickskin_compiled']
79
   * @var string
80
   */
81
  public $temp_dir         = '_skins_tmp/';
82

    
83
  /**
84
   * Cache Directory
85
   * Temporary folder for output cache storage, can be overwritten by
86
   * global configuration array $_CONFIG['quickskin_cache']
87
   * Note: typically set the same as the Temp Directory, but can be unique
88
   * @var string
89
   */
90
  public $cache_dir         = '_skins_tmp/';
91

    
92
  /**
93
   * Cache Lifetime
94
   * Default Output Cache Lifetime in Seconds, can be overwritten by
95
   * global configuration array $_CONFIG['cache_lifetime']
96
   * @var int
97
   */
98
  public $cache_lifetime    = 600; // seconds
99

    
100
  /**
101
   * Extensions Directory
102
   * Directory where all extensions are stored
103
   * @var string
104
   */
105
  public $extensions_dir    = '_lib/qx';      /* Directory where all extensions are stored */
106

    
107
  /**
108
   * Extension Prefix
109
   * Filename prefix on all the extensions files
110
   * @var string
111
   */
112
  public $extension_prefix  = 'qx_';
113

    
114
  /**
115
   * Left Delimiter
116
   * Default Left delimiter, can be overwritten by
117
   * global configuration array $_CONFIG['left_delimiter']
118
   * @var string
119
   */
120
  public $left_delimiter    = '{';
121

    
122
  /**
123
   * Right Delimiter
124
   * Default Right delimiter, can be overwritten by
125
   * global configuration array $_CONFIG['right_delimiter']
126
   * @var string
127
   */
128
  public $right_delimiter   = '}';
129

    
130
  /**
131
   * Extension Tagged
132
   * List of used QuickSkin Extensions
133
   * @var array
134
   */
135
  public $extension_tagged  = array();
136

    
137
  /**
138
   * QuickSkin Version Number
139
   * List of used QuickSkin Extensions
140
   * @var string
141
   */
142
  public $version           = '5.0';
143

    
144
  /////////////////////////////////////////////////
145
  // PROPERTIES, PRIVATE
146
  /////////////////////////////////////////////////
147

    
148
  private $cache_filename; /* Temporary file for output cache storage */
149
  private $tpl_file;       /* The template filename */
150
  private $cpl_file;       /* The compiled template filename */
151
  private $data = array(); /* Template content array */
152
  private $parser;         /* Parser Class */
153
  private $debugger;       /* Debugger Class */
154
  private $skins_sub_dir;  /* temporary variable to hold the subdirectory of the main template */
155
  private $supp_templates = '';   /* supplementary templates */
156
  private $supptemplate   = '';   /* supplementary template */
157

    
158
  /* QuickSkin Constructor
159
   * @access public
160
   * @param string $template_filename Template Filename
161
   */
162
  function __construct( $template_filename = '' ) {
163
    global $_CONFIG;
164

    
165
    if (!empty($_CONFIG['quickskin_compiled'])) {
166
      $this->temp_dir  =  $_CONFIG['quickskin_compiled'];
167
    }
168
    if (!empty($_CONFIG['quickskin_cache'])) {
169
      $this->cache_dir  =  $_CONFIG['quickskin_cache'];
170
    }
171
    if (is_numeric($_CONFIG['cache_lifetime'])) {
172
      $this->cache_lifetime  =  $_CONFIG['cache_lifetime'];
173
    }
174
    if (!empty($_CONFIG['template_dir'])  &&  is_file($_CONFIG['template_dir'] . '/' . $template_filename)) {
175
      $this->template_dir  =  $_CONFIG['template_dir'];
176
    }
177
    $this->tpl_file  =  $template_filename;
178
    if ( dirname($this->tpl_file) != "") {
179
      $this->skins_sub_dir = dirname($this->tpl_file);
180
    }
181
  }
182

    
183
  /* DEPRECATED METHODS */
184
  /* Methods used in older parser versions, soon will be removed */
185
  function set_templatefile ($template_filename)  { $this->tpl_file  =  $template_filename; }
186
  function add_value ($name, $value )       { $this->assign($name, $value); }
187
  function add_array ($name, $value )       { $this->append($name, $value); }
188

    
189
  /* Process file or contents to strip out the <body tag (inclusive)
190
   * and the </body tag to the end
191
   *
192
   * Usage Example:
193
   * $page->getContents( '', '/contents.htm' );
194
   * or
195
   * $page->getContents( 'start of data .... end of data' );
196
   *
197
   * @access public
198
   * @param string $contents Parameter contents
199
   * @param string $filename Parameter filename (fully qualified)
200
   * @desc strip out body tags and return only page data
201
   */
202
  function getContents($contents, $filename="") {
203
    if ( $contents == '' && $filename != '' && file_exists($filename) ) {
204
      $contents = file_get_contents($filename);
205
    }
206

    
207
    // START process any PHP code
208
    ob_start();
209
    eval("?>".$contents."<?php ");
210
    $contents = ob_get_contents();
211
    ob_end_clean();
212
    // END process any PHP code
213
    $lower_contents = strtolower($contents);
214
    // determine if a <body tag exists and process if necessary
215
    $bodytag_start = strpos($lower_contents, "<body");
216
    if ( $bodytag_start !== false ) {
217
      $bodytag_end    = strpos($lower_contents, ">", $bodytag_start) + 1;
218
      // get contents with <body tag removed
219
      $contents       = substr($contents, $bodytag_end);
220
      $lower_contents = strtolower($contents);
221
      // work on </body closing tag
222
      $end_start      = strpos($lower_contents, "</body");
223
      $end_end        = strpos($lower_contents, ">", $bodytag_start) + 1;
224
      // return stripped out <body and </body tags
225
      return $this->getExtensions( substr($contents, 0, $end_start) );
226
    } else {
227
      // body tags not found, so return data
228
      return $this->getExtensions( $contents );
229
    }
230
  }
231

    
232
  /* Determine Contents Command from Variable Name
233
   * {variable}             :  array( "echo",              "variable" )  ->  echo $_obj['variable']
234
   * {variable > new_name}  :  array( "_obj['new_name']=", "variable" )  ->  $_obj['new_name']= $_obj['variable']
235
   * @param string $tag Variale Name used in Template
236
   * @return array  Array Command, Variable
237
   * @access private
238
   * @desc Determine Contents Command from Variable Name
239
   */
240
  function processCmd($tag) {
241
    if (preg_match('/^(.+) > ([a-zA-Z0-9_.]+)$/', $tag, $tagvar)) {
242
      $tag  =  $tagvar[1];
243
      list($newblock, $newskalar)  =  $this->var_name($tagvar[2]);
244
      $cmd  =  "\$$newblock"."['$newskalar']=";
245
    } else {
246
      $cmd  =  'echo';
247
    }
248
    $ret = array($cmd, $tag);
249
    return $ret;
250
  }
251

    
252
  /* Load and process the Extensions that may be used in the Contents
253
   *
254
   * Usage Example:
255
   * $tcnt = $this->getExtensions( $param );
256
   *
257
   * @access public
258
   * @param string $param (content to process)
259
   * @return string
260
   * @desc Load and process the Extensions that may be used in the Contents
261
   */
262
  function getExtensions($contents) {
263
    $header = '';
264
    /* Include Extensions */
265
    if (preg_match_all('/'.$this->left_delimiter.'([a-zA-Z0-9_]+):([^}]*)'.$this->right_delimiter.'/', $contents, $var)) {
266
      foreach ($var[2] as $cnt => $tag) {
267
        /* Determine Command (echo / $obj[n]=) */
268
        list($cmd, $tag)  =  $this->processCmd($tag);
269

    
270
        $extension  =  $var[1][$cnt];
271
        //if (!isset($this->extension_tagged[$extension])) {
272
          $header .= 'include_once "'.$this->extensions_dir."/".$this->extension_prefix."$extension.php\";\n";
273
        //  $this->extension_tagged[$extension]  =  true;
274
        //}
275
        if (!strlen($tag)) {
276
          $code  =  "<?php\n$cmd ".$this->extension_prefix."$extension();\n?>\n";
277
        } elseif (substr($tag, 0, 1) == '"') {
278
          $code  =  "<?php\n$cmd ".$this->extension_prefix."$extension($tag);\n?>\n";
279
        } elseif (strpos($tag, ',')) {
280
          list($tag, $addparam)  =  explode(',', $tag, 2);
281
          list($block, $skalar)  =  $this->var_name($tag);
282
          if (preg_match('/^([a-zA-Z_]+)/', $addparam, $match)) {
283
            $nexttag   =  $match[1];
284
            list($nextblock, $nextskalar)  =  $this->var_name($nexttag);
285
            $addparam  =  substr($addparam, strlen($nexttag));
286
            $code  =  "<?php\n$cmd ".$this->extension_prefix."$extension(\$$block"."['$skalar'],\$$nextblock"."['$nextskalar']"."$addparam);\n?>\n";
287
          } else {
288
            $code  =  "<?php\n$cmd ".$this->extension_prefix."$extension(\$$block"."['$skalar'],$addparam);\n?>\n";
289
          }
290
        } else {
291
          list($block, $skalar) = $this->var_name($tag);
292
          $code  =  "<?php\n$cmd ".$this->extension_prefix."$extension(\$$block"."['$skalar']);\n?>\n";
293
        }
294
        $contents  =  str_replace($var[0][$cnt],  $code,  $contents);
295
      }
296
    }
297
    // START process any PHP code
298
    ob_start();
299
    eval($header);
300
    eval("?>".$contents."<?php ");
301
    $contents = ob_get_contents();
302
    ob_end_clean();
303
    // END process any PHP code
304
    return $contents;
305
  }
306

    
307
  /* Assign Supplementary Template
308
   *
309
   * Usage Example:
310
   * $page->addtpl( 'sponsors', 'default/poweredby.htm' );
311
   *
312
   * @access public
313
   * @param string $name Parameter Name
314
   * @param string $value Parameter Value
315
   * @desc Assign Supplementary Template
316
   */
317
  function addtpl ( $name, $value = '' ) {
318
    if (is_array($name)) {
319
      foreach ($name as $k => $v) {
320
        $this->supptemplate[$k]  =  $v;
321
      }
322
    } else {
323
      $this->supptemplate[$name]  =  $value;
324
    }
325
  }
326

    
327
  /* Assign Template Content
328
   *
329
   * Usage Example:
330
   * $page->assign( 'TITLE',     'My Document Title' );
331
   * $page->assign( 'userlist',  array(
332
   *                               array( 'ID' => 123,  'NAME' => 'John Doe' ),
333
   *                               array( 'ID' => 124,  'NAME' => 'Jack Doe' ),
334
   *                             );
335
   *
336
   * @access public
337
   * @param string $name Parameter Name
338
   * @param mixed $value Parameter Value
339
   * @desc Assign Template Content
340
   */
341
  function assign ( $name, $value = '' ) {
342
    if (is_array($name)) {
343
      foreach ($name as $k => $v) {
344
        $this->data[$k]  =  $v;
345
      }
346
    } else {
347
      $this->data[$name]  =  $value;
348
    }
349
  }
350

    
351
  /* Assign Template Content
352
   *
353
   * Usage Example:
354
   * $page->append( 'userlist',  array( 'ID' => 123,  'NAME' => 'John Doe' ) );
355
   * $page->append( 'userlist',  array( 'ID' => 124,  'NAME' => 'Jack Doe' ) );
356
   *
357
   * @access public
358
   * @param string $name Parameter Name
359
   * @param mixed $value Parameter Value
360
   * @desc Assign Template Content
361
   */
362
  function append ( $name, $value ) {
363
    if (is_array($value)) {
364
      $this->data[$name][]  =  $value;
365
    } elseif (!is_array($this->data[$name])) {
366
      $this->data[$name]  .=  $value;
367
    }
368
  }
369

    
370
  /* Parser Wrapper
371
   * Returns Template Output as a String
372
   *
373
   * @access public
374
   * @param array $_top Content Array
375
   * @return string  Parsed Template
376
   * @desc Output Buffer Parser Wrapper
377
   */
378
  function result ( $_top = '' ) {
379
    ob_start();
380
    $this->output( $_top );
381
    $result  =  ob_get_contents();
382
    ob_end_clean();
383
    return $result;
384
  }
385

    
386
  /* Execute parsed Template
387
   * Prints Parsing Results to Standard Output
388
   *
389
   * @access public
390
   * @param array $_top Content Array
391
   * @desc Execute parsed Template
392
   */
393
  function output ( $_top = '' ) {
394
    global $_top;
395

    
396
    $data   = $this->data;
397
    /* Process supplementary templates */
398
    if ( is_array($this->supptemplate) && !empty($this->supptemplate) ) { // passed by addtpl functionality
399
      foreach ($this->supptemplate as $key => $value) {
400
        $supp_templates[$key] = file_get_contents($value);
401
      }
402
    }
403

    
404
    /* Make sure that folder names have a trailing '/' */
405
    if (strlen($this->template_dir)  &&  substr($this->template_dir, -1) != '/') {
406
      $this->template_dir  .=  '/';
407
    }
408
    if (strlen($this->temp_dir)  &&  substr($this->temp_dir, -1) != '/') {
409
      $this->temp_dir  .=  '/';
410
    }
411

    
412
    /* Prepare Template Content*/
413
    if (!is_array($_top)) {
414
      if (strlen($_top)) {
415
        $this->tpl_file  =  $_top;
416
      }
417
      $_top  =  $this->data;
418
    }
419
    $_obj  =  &$_top;
420
    $_stack_cnt  =  0;
421
    $_stack[$_stack_cnt++]  =  $_obj;
422

    
423
    /* Check if template is already compiled */
424
    $queryString = $_SERVER['QUERY_STRING'];
425
    $cpl_file_name = preg_replace('/[:\/.\\\\]/', '_', $this->tpl_file . '?' . $queryString);
426
    if (strlen($cpl_file_name) > 0) {
427
      $cpl_file_name = 'qs_' . md5($cpl_file_name);
428
      $this->cpl_file  =  $this->temp_dir . $cpl_file_name . '.php';
429
      $compile_template  =  true;
430
      if ($this->reuse_code) {
431
        if (is_file($this->cpl_file)) {
432
          if ($this->mtime($this->cpl_file) > $this->mtime($this->template_dir . $this->tpl_file)) {
433
            $compile_template  =  false;
434
          }
435
        }
436
      }
437
      if ($compile_template) {
438
        $this->parser = new QuickSkinParser();
439
        $this->parser->template_dir     = $this->template_dir;
440
        $this->parser->skins_sub_dir    = $this->skins_sub_dir;
441
        $this->parser->tpl_file         = $this->tpl_file;
442
        $this->parser->left_delimiter   = $this->left_delimiter;
443
        $this->parser->right_delimiter  = $this->right_delimiter;
444
        $this->parser->extensions_dir   = $this->extensions_dir;
445
        $this->parser->extension_prefix = $this->extension_prefix;
446
        $this->parser->supp_templates   = $this->supp_templates;
447

    
448
        if (!$this->parser->compile($this->cpl_file,$data,$this->supp_templates,$this->extensions_dir)) {
449
          exit('QuickSkin Parser Error: ' . $this->parser->error);
450
        }
451
      }
452
      /* Execute Compiled Template */
453
      include($this->cpl_file);
454
    } else {
455
      exit('QuickSkin Error: You must set a template file name');
456
    }
457
    /* Delete Global Content Array in order to allow multiple use of QuickSkin class in one script */
458
    unset ($GLOBALS['_top']);
459
  }
460

    
461
  /* Debug Template
462
   *
463
   * @access public
464
   * @param array $_top Content Array
465
   * @desc Debug Template
466
   */
467
  function debug ( $_top = '' ) {
468
    /* Prepare Template Content */
469
    if (!$_top) {
470
      $_top  =  $this->data;
471
    }
472
    if (@include_once('class.quickskindebugger.php')) {
473
      $this->debugger = new QuickSkinDebugger($this->template_dir . $this->tpl_file, $this->right_delimiter, $this->left_delimiter);
474
      $this->debugger->start($_top);
475
    } else {
476
      exit( 'QuickSkin Error: Cannot find class.quickskindebugger.php; check QuickSkin installation');
477
    }
478
  }
479

    
480
  /* Start Ouput Content Buffering
481
   *
482
   * Usage Example:
483
   * $page = new QuickSkin('template.html');
484
   * $page->use_cache();
485
   * ...
486
   *
487
   * @access public
488
   * @desc Output Cache
489
   */
490
  function use_cache ( $key = '' ) {
491
    if (empty($_POST)) {
492
      $this->cache_filename  =  $this->cache_dir . 'cache_' . md5($_SERVER['REQUEST_URI'] . serialize($key)) . '.ser';
493
      if (($_SERVER['HTTP_CACHE_CONTROL'] != 'no-cache')  &&  ($_SERVER['HTTP_PRAGMA'] != 'no-cache')  &&  @is_file($this->cache_filename)) {
494
        if ((time() - filemtime($this->cache_filename)) < $this->cache_lifetime) {
495
          readfile($this->cache_filename);
496
          exit;
497
        }
498
      }
499
      ob_start( array( &$this, 'cache_callback' ) );
500
    }
501
  }
502

    
503
  /* Output Buffer Callback Function
504
   *
505
   * @access private
506
   * @param string $output
507
   * @return string $output
508
   */
509
  function cache_callback ( $output ) {
510
    if ($hd = @fopen($this->cache_filename, 'w')) {
511
      fwrite($hd,  $output);
512
      fclose($hd);
513
    } else {
514
      $output = 'QuickSkin Error: failed to open cache file "' . $this->cache_filename . '"';
515
    }
516
    return $output;
517
  }
518

    
519
  /* Determine Last Filechange Date (if File exists)
520
   *
521
   * @access private
522
   * @param string $filename
523
   * @return mixed
524
   * @desc Determine Last Filechange Date
525
   */
526
  function mtime ( $filename ) {
527
    if (@is_file($filename)) {
528
      $ret = filemtime($filename);
529
      return $ret;
530
    }
531
  }
532

    
533
  /* Set (or reset) Properties (variables)
534
   *
535
   * Usage Example:
536
   * $page->set('reuse_code', true);
537
   *
538
   * @access public
539
   * @param string $name Parameter Name
540
   * @param mixed $value Parameter Value
541
   * NOTE: will not work with arrays, there are no arrays to set/reset */
542
  function set ( $name, $value = '' ) {
543
    if ( isset($this->$name) ) {
544
      $this->$name = $value;
545
    } else {
546
      exit( 'QuickSkin Error: Attempt to set a non-existant class property: ' . $name);
547
    }
548
  }
549

    
550
}
551

    
552
/*~
553
.---------------------------------------------------------------------------.
554
|  Software: QuickSkinParser Class * Used by QuickSkin Class                |
555
|   Version: 5.0                                                            |
556
|   Contact: andy.prevost@worxteam.com,andy@codeworx.ca                     |
557
|      Info: http://quickskin.sourceforge.net                               |
558
|   Support: http://sourceforge.net/projects/quickskin/                     |
559
| ------------------------------------------------------------------------- |
560
|    Author: Andy Prevost andy.prevost@worxteam.com (admin)                 |
561
|    Author: Manuel 'EndelWar' Dalla Lana endelwar@aregar.it (former admin) |
562
|    Author: Philipp v. Criegern philipp@criegern.com (original founder)    |
563
| Copyright (c) 2002-2009, Andy Prevost. All Rights Reserved.               |
564
|    * NOTE: QuickSkin is the SmartTemplate project renamed. SmartTemplate  |
565
|            information and downloads can still be accessed at the         |
566
|            smarttemplate.sourceforge.net site                             |
567
| ------------------------------------------------------------------------- |
568
|   License: Distributed under the Lesser General Public License (LGPL)     |
569
|            http://www.gnu.org/copyleft/lesser.html                        |
570
| This program is distributed in the hope that it will be useful - WITHOUT  |
571
| ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or     |
572
| FITNESS FOR A PARTICULAR PURPOSE.                                         |
573
| ------------------------------------------------------------------------- |
574
| We offer a number of paid services:                                       |
575
| - Web Hosting on highly optimized fast and secure servers                 |
576
| - Technology Consulting                                                   |
577
| - Oursourcing (highly qualified programmers and graphic designers)        |
578
'---------------------------------------------------------------------------'
579
Last modified: January 01 2009 ~*/
580

    
581
class QuickSkinParser {
582

    
583
  /////////////////////////////////////////////////
584
  // PROPERTIES, PUBLIC
585
  /////////////////////////////////////////////////
586

    
587
  public $error;               /* Error messages */
588
  public $template;            /* The template itself */
589
  public $tpl_file;            /* The template filename */
590
  public $template_dir;        /* The template filename used to extract the dirname for subtemplates */
591
  public $skins_sub_dir;       /* The template subdirectory, passed by main class */
592
  public $extensions_dir;      /* The extension directory */
593
  public $extension_tagged = array(); /* List of used QuickSkin Extensions */
594
  public $left_delimiter;      /* Default Left delimiter */
595
  public $right_delimiter;     /* Default Right delimiter */
596
  public $supp_templates = ''; /* Contains array or single supplementary template(s) */
597
  public $extension_prefix;    /* filename prefix on all the extensions files */
598

    
599
  /* QuickSkinParser Constructor */
600
  /*
601
  function __construct() {
602
  }
603
  */
604

    
605
  /* Replace template logical expression in IF..ENDIF (|| or &&) with php logical expression
606
   * @access private
607
   * @desc replace template logical expression (|| or &&) with php logical expression
608
   * @author Bruce Huang (msn: huang_x_c@163.com)
609
   * @param string $src_page source page intended to be replaced
610
   */
611
  function replace_logic_expression( &$src_page ) {
612
    /* cannot find '||' or '&&' */
613
    if(!strpos($src_page, '||') && !strpos($src_page, '&&')) {
614
      return;
615
    }
616
    /* match 'ELSE' and the last sub expression */
617
    if (preg_match_all('/<!-- (ELSE)?IF \s*(\(*).*[|&]{2}\s*\(*\s*([a-zA-Z0-9_.]+)\s*([!=<>]+)\s*(["]?[^"]*?["]?)\s*(\)*)\s* -->/', $src_page, $var)) {
618
      foreach ($var[3] as $cnt => $tag) {
619
        list($parent, $block)  =  $this->var_name($tag);
620
        $cmp   =  $var[4][$cnt];
621
        $val   =  $var[5][$cnt];
622
        $else  =  ($var[1][$cnt] == 'ELSE') ? '} else' : '';
623
        if ($cmp == '=') {
624
          $cmp  =  '==';
625
        }
626
        if (preg_match('/"([^"]*)"/',$val,$matches)) {
627
          $code_suffix  =  "\$$parent"."['$block'] $cmp \"".$matches[1].$var[6][$cnt]."\"){\n?>";
628
        } elseif (preg_match('/([^"]*)/',$val,$matches)) {
629
          list($parent_right, $block_right)  =  $this->var_name($matches[1]);
630
          $code_suffix  =  "\$$parent"."['$block'] $cmp \$$parent_right"."['$block_right']".$var[6][$cnt]."){\?>";
631
        }
632

    
633
        /* match other sub expressions */
634
        if (preg_match_all('/([a-zA-Z0-9_.]+)\s*([!=<>]+)\s*(["]?[^"]*?["]?)\s*(\)*\s*[|&]{2}\s*\(*)\s*/', $var[0][$cnt], $sub_var)) {
635
          $code_mid = '';
636
          foreach($sub_var[1] as $sub_cnt => $sub_tag) {
637
            list($sub_parent, $sub_block) = $this->var_name($sub_tag);
638
            $cmp = $sub_var[2][$sub_cnt];
639
            $val = $sub_var[3][$sub_cnt];
640
            $logic_exp  =  $sub_var[4][$sub_cnt];
641
            if ($cmp == '=') {
642
              $cmp  =  '==';
643
            }
644
            if (preg_match('/"([^"]*)"/',$val,$matches)) {
645
              $code_mid  =  $code_mid."\$$sub_parent"."['$sub_block'] $cmp \"".$matches[1]."\"".$logic_exp;
646
            } elseif (preg_match('/([^"]*)/',$val,$matches)) {
647
              list($sub_parent_right, $sub_block_right)  =  $this->var_name($matches[1]);
648
              $code_mid  =  $code_mid."\$$sub_parent"."['$sub_block'] $cmp \$$sub_parent_right"."['$sub_block_right']".$logic_exp;
649
            }
650
          }
651
        }
652
        $code = "<?php\n".$else.'if ('.$var[2][$cnt].$code_mid.$code_suffix;
653
        $src_page = str_replace($var[0][$cnt],  $code,  $src_page);
654
      }
655
    }
656
  }
657

    
658
  /* Main Template Parser
659
   * @param string $compiled_template_filename Compiled Template Filename
660
   * @desc Creates Compiled PHP Template
661
   */
662
  function compile( $compiled_template_filename = '', $data='', $supp_templates='', $extensions_dir='' ) {
663

    
664
    $this->extension_prefix = preg_quote($this->extension_prefix);
665

    
666
    /* Load Template */
667
    $template_filename = $this->template_dir . $this->tpl_file;
668
    if ($hd = @fopen($template_filename, 'r')) {
669
      if (filesize($template_filename)) {
670
        $this->template = fread($hd, filesize($template_filename));
671
        $this->left_delimiter = preg_quote($this->left_delimiter);
672
        $this->right_delimiter = preg_quote($this->right_delimiter);
673
      } else {
674
        $this->template = 'QuickSkin Parser Error: File size is zero byte: ' .$template_filename;
675
      }
676
      fclose($hd);
677
    } else {
678
      $this->template = 'QuickSkin Parser Error: File not found: ' .$template_filename;
679
    }
680

    
681
    if (empty($this->template)) {
682
      return;
683
    }
684

    
685
    /* Do the variable substitution for paths, urls, subtemplates */
686
    $this->template = $this->worx_var_swap($this->template, $data, $supp_templates);
687

    
688
    $header = '';
689

    
690
    /* Code to allow subtemplates */
691
    if(eregi("<!-- INCLUDE", $this->template)) {
692
      while ($this->count_subtemplates() > 0) {
693
        preg_match_all('/<!-- INCLUDE ([a-zA-Z0-9\-_.]+) -->/', $this->template, $tvar);
694
        foreach($tvar[1] as $subfile) {
695
          if(file_exists($this->template_dir . '/' . $this->skins_sub_dir . '/' .$subfile)) {
696
            $subst = implode('',file($this->template_dir . '/' . $this->skins_sub_dir . '/' .$subfile));
697
          } else {
698
            $subst = 'QuickSkin Parser Error: Subtemplate not found: \''.$subfile.'\'';
699
          }
700
          $this->template = str_replace("<!-- INCLUDE $subfile -->", $subst, $this->template);
701
        }
702
      }
703
    }
704
    /* END, ELSE Blocks */
705
    $page  =  preg_replace("/<!-- ENDIF.+?-->/", "<?php\n}\n?>", $this->template);
706
    $page  =  preg_replace("/<!-- END[ a-zA-Z0-9_.]* -->/",  "<?php\n}\n\$_obj=\$_stack[--\$_stack_cnt];}\n?>", $page);
707
    $page  =  str_replace("<!-- ELSE -->", "<?php\n} else {\n?>", $page);
708

    
709
    /* 'BEGIN - END' Blocks */
710
    if (preg_match_all('/<!-- BEGIN ([a-zA-Z0-9_.]+) -->/', $page, $var)) {
711
      foreach ($var[1] as $tag) {
712
        list($parent, $block)  =  $this->var_name($tag);
713
        $code  =  "<?php\n"
714
            . "if (!empty(\$$parent"."['$block'])){\n"
715
            . "if (!is_array(\$$parent"."['$block']))\n"
716
            . "\$$parent"."['$block']=array(array('$block'=>\$$parent"."['$block']));\n"
717
            . "\$_stack[\$_stack_cnt++]=\$_obj;\n"
718
            . "\$rowcounter = 0;\n"
719
            . "foreach (\$$parent"."['$block'] as \$rowcnt=>\$$block) {\n"
720
              . "\$$block"."['ROWCNT']=(\$rowcounter);\n"
721
              . "\$$block"."['ALTROW']=\$rowcounter%2;\n"
722
              . "\$$block"."['ROWBIT']=\$rowcounter%2;\n"
723
              . "\$rowcounter++;"
724
              . "\$_obj=&\$$block;\n?>";
725
        $page  =  str_replace("<!-- BEGIN $tag -->",  $code,  $page);
726
      }
727
    }
728

    
729
    /* replace logical operator in [ELSE]IF */
730
    $this->replace_logic_expression($page);
731

    
732
    /* 'IF nnn=mmm' Blocks */
733
    if (preg_match_all('/<!-- (ELSE)?IF ([a-zA-Z0-9_.]+)\s*([!=<>]+)\s*(["]?[^"]*["]?) -->/', $page, $var)) {
734
      foreach ($var[2] as $cnt => $tag) {
735
        list($parent, $block)  =  $this->var_name($tag);
736
        $cmp   =  $var[3][$cnt];
737
        $val   =  $var[4][$cnt];
738
        $else  =  ($var[1][$cnt] == 'ELSE') ? '} else' : '';
739
        if ($cmp == '=') {
740
          $cmp  =  '==';
741
        }
742

    
743
        if (preg_match('/"([^"]*)"/',$val,$matches)) {
744
          $code  =  "<?php\n$else"."if (\$$parent"."['$block'] $cmp \"".$matches[1]."\"){\n?>";
745
        } elseif (preg_match('/([^"]*)/',$val,$matches)) {
746
          list($parent_right, $block_right)  =  $this->var_name($matches[1]);
747
          $code  =  "<?php\n$else"."if (\$$parent"."['$block'] $cmp \$$parent_right"."['$block_right']){\n?>";
748
        }
749

    
750
        $page  =  str_replace($var[0][$cnt],  $code,  $page);
751
      }
752
    }
753

    
754
    /* 'IF nnn' Blocks */
755
    if (preg_match_all('/<!-- (ELSE)?IF ([a-zA-Z0-9_.]+) -->/', $page, $var)) {
756
      foreach ($var[2] as $cnt => $tag) {
757
        $else  =  ($var[1][$cnt] == 'ELSE') ? '} else' : '';
758
        list($parent, $block)  =  $this->var_name($tag);
759
        $code  =  "<?php\n$else"."if (!empty(\$$parent"."['$block'])){\n?>";
760
        $page  =  str_replace($var[0][$cnt],  $code,  $page);
761
      }
762
    }
763

    
764
    /* 'IF {extension:variable}'=mmm Blocks
765
     * e.g.
766
     * <!-- IF {count:list} > 0 -->
767
     * List populated
768
     * <!-- ELSE -->
769
     * List is empty
770
     * <!-- ENDIF -->
771
     * thanks to Khary Sharpe (ksharpe [at] kharysharpe [dot] com) for the initial code
772
     */
773
    if (preg_match_all('/<!-- (ELSE)?IF {([a-zA-Z0-9_]+):([^}]*)}\s*([!=<>]+)\s*(["]?[^"]*["]?) -->/', $page, $var)) {
774
      foreach ($var[2] as $cnt => $tag) {
775
        list($parent, $block)  =  $this->var_name($tag);
776
        $cmp   =  $var[4][$cnt];
777
        $val   =  $var[5][$cnt];
778
        $else  =  ($var[1][$cnt] == 'ELSE') ? '} else' : '';
779
        if ($cmp == '=') {
780
          $cmp  =  '==';
781
        }
782

    
783
        $extension = $var[2][$cnt];
784
        $extension_var = $var[3][$cnt];
785
        if (!isset($this->extension_tagged[$extension])) {
786
          $header .= 'include_once  "'.$this->extensions_dir."/".$this->extension_prefix."$extension.php\";\n";
787
          $this->extension_tagged[$extension] = true;
788
        }
789
        if (!strlen($extension_var)) {
790
          $code = "<?php\n$else"."if (".$this->extension_prefix."$extension() $cmp $val) {\n?>\n";
791
        } elseif (substr($extension_var, 0, 1) == '"') {
792
          $code = "<?php\n$else"."if (".$this->extension_prefix."$extension($extension_var) $cmp $val) {\n?>\n";
793
        } elseif (strpos($extension_var, ',')) {
794
          list($tag, $addparam) = explode(',', $extension_var, 2);
795
          list($block, $skalar) = $this->var_name($extension_var);
796
          if (preg_match('/^([a-zA-Z_]+)/', $addparam, $match)) {
797
            $nexttag = $match[1];
798
            list($nextblock, $nextskalar) = $this->var_name($nexttag);
799
            $addparam = substr($addparam, strlen($nexttag));
800
            $code = "<?php\n$else"."if (".$this->extension_prefix."$extension(\$$block"."['$skalar'],\$$nextblock"."['$nextskalar']"."$addparam) $cmp $val) {\n?>\n";
801
          } else {
802
            $code = "<?php\n$else"."if (".$this->extension_prefix."$extension(\$$block"."['$skalar'],$addparam) $cmp $val) {\n?>\n";
803
          }
804
        } else {
805
          list($block, $skalar) = $this->var_name($extension_var);
806
          $code = "<?php\n$else"."if (".$this->extension_prefix."$extension(\$$block"."['$skalar']) $cmp $val) {\n?>\n";
807
        }
808
        $page = str_replace($var[0][$cnt], $code, $page);
809
      }
810
    }
811

    
812
    /* Replace Scalars */
813
    if (preg_match_all('/'.$this->left_delimiter.'([a-zA-Z0-9_. >]+)'.$this->right_delimiter.'/', $page, $var)) {
814
      foreach ($var[1] as $fulltag) {
815
        /* Determine Command (echo / $obj[n]=) */
816
        list($cmd, $tag)  =  $this->cmd_name($fulltag);
817
        list($block, $skalar)  =  $this->var_name($tag);
818
        $code  =  "<?php\n$cmd \$$block"."['$skalar'];\n?>\n";
819
        $page  =  str_replace(stripslashes($this->left_delimiter).$fulltag.stripslashes($this->right_delimiter), $code, $page);
820
      }
821
    }
822

    
823
    /* Replace Translations */
824
    if (preg_match_all('/<"([a-zA-Z0-9_.]+)">/', $page, $var)) {
825
      foreach ($var[1] as $tag) {
826
        list($block, $skalar)  =  $this->var_name($tag);
827
        $code  =  "<?php\necho gettext('$skalar');\n?>\n";
828
        $page  =  str_replace('<"'.$tag.'">',  $code,  $page);
829
      }
830
    }
831

    
832
    /* Include Extensions */
833
    if (preg_match_all('/'.$this->left_delimiter.'([a-zA-Z0-9_]+):([^}]*)'.$this->right_delimiter.'/', $page, $var)) {
834
      foreach ($var[2] as $cnt => $tag) {
835
        /* Determine Command (echo / $obj[n]=) */
836
        list($cmd, $tag)  =  $this->cmd_name($tag);
837

    
838
        $extension  =  $var[1][$cnt];
839
        if (!isset($this->extension_tagged[$extension])) {
840
          $header .= 'include_once "'.$this->extensions_dir."/".$this->extension_prefix."$extension.php\";\n";
841
          $this->extension_tagged[$extension]  =  true;
842
        }
843
        if (!strlen($tag)) {
844
          $code  =  "<?php\n$cmd ".$this->extension_prefix."$extension();\n?>\n";
845
        } elseif (substr($tag, 0, 1) == '"') {
846
          $code  =  "<?php\n$cmd ".$this->extension_prefix."$extension($tag);\n?>\n";
847
        } elseif (strpos($tag, ',')) {
848
          list($tag, $addparam)  =  explode(',', $tag, 2);
849
          list($block, $skalar)  =  $this->var_name($tag);
850
          if (preg_match('/^([a-zA-Z_]+)/', $addparam, $match)) {
851
            $nexttag   =  $match[1];
852
            list($nextblock, $nextskalar)  =  $this->var_name($nexttag);
853
            $addparam  =  substr($addparam, strlen($nexttag));
854
            $code  =  "<?php\n$cmd ".$this->extension_prefix."$extension(\$$block"."['$skalar'],\$$nextblock"."['$nextskalar']"."$addparam);\n?>\n";
855
          } else {
856
            $code  =  "<?php\n$cmd ".$this->extension_prefix."$extension(\$$block"."['$skalar'],$addparam);\n?>\n";
857
          }
858
        } else {
859
          list($block, $skalar) = $this->var_name($tag);
860
          $code  =  "<?php\n$cmd ".$this->extension_prefix."$extension(\$$block"."['$skalar']);\n?>\n";
861
        }
862
        $page  =  str_replace($var[0][$cnt],  $code,  $page);
863
      }
864
    }
865

    
866
    /* Add Include Header */
867
    if (isset($header) && !empty($header)) {
868
      $page  =  "<?php\n$header\n?>$page";
869
    }
870

    
871
    /* do substitutions on included supplementary templates */
872
    $page = $this->worx_tpl_swap($page, $data, $supp_templates);
873

    
874
    /* Store Code to Temp Dir */
875
    if (strlen($compiled_template_filename)) {
876
      if ($hd  =  fopen($compiled_template_filename,  'w')) {
877
        fwrite($hd,  $page);
878
        fclose($hd);
879
        return true;
880
      } else {
881
        $this->error  =  'Could not write compiled file.';
882
        return false;
883
      }
884
    } else {
885
      return $page;
886
    }
887
  }
888

    
889
  /* Splits Template-Style Variable Names into an Array-Name/Key-Name Components
890
   * {example}               :  array( "_obj",                   "example" )  ->  $_obj['example']
891
   * {example.value}         :  array( "_obj['example']",        "value" )    ->  $_obj['example']['value']
892
   * {example.0.value}       :  array( "_obj['example'][0]",     "value" )    ->  $_obj['example'][0]['value']
893
   * {top.example}           :  array( "_stack[0]",              "example" )  ->  $_stack[0]['example']
894
   * {parent.example}        :  array( "_stack[$_stack_cnt-1]",  "example" )  ->  $_stack[$_stack_cnt-1]['example']
895
   * {parent.parent.example} :  array( "_stack[$_stack_cnt-2]",  "example" )  ->  $_stack[$_stack_cnt-2]['example']
896
   * @param string $tag Variale Name used in Template
897
   * @return array  Array Name, Key Name
898
   * @access private
899
   * @desc Splits Template-Style Variable Names into an Array-Name/Key-Name Components
900
   */
901
  function var_name($tag) {
902
    $parent_level  =  0;
903
    while (substr($tag, 0, 7) == 'parent.') {
904
      $tag  =  substr($tag, 7);
905
      $parent_level++;
906
    }
907
    if (substr($tag, 0, 4) == 'top.') {
908
      $obj  =  '_stack[0]';
909
      $tag  =  substr($tag,4);
910
    } elseif ($parent_level) {
911
      $obj  =  '_stack[$_stack_cnt-'.$parent_level.']';
912
    } else {
913
      $obj  =  '_obj';
914
    }
915
    while (is_int(strpos($tag, '.'))) {
916
      list($parent, $tag)  =  explode('.', $tag, 2);
917
      if (is_numeric($parent)) {
918
        $obj  .=  "[" . $parent . "]";
919
      } else {
920
        $obj  .=  "['" . $parent . "']";
921
      }
922
    }
923
    $ret = array($obj, $tag);
924
    return $ret;
925
  }
926

    
927
  /* Determine Template Command from Variable Name
928
   * {variable}             :  array( "echo",              "variable" )  ->  echo $_obj['variable']
929
   * {variable > new_name}  :  array( "_obj['new_name']=", "variable" )  ->  $_obj['new_name']= $_obj['variable']
930
   * @param string $tag Variale Name used in Template
931
   * @return array  Array Command, Variable
932
   * @access private
933
   * @desc Determine Template Command from Variable Name
934
   */
935
  function cmd_name($tag) {
936
    if (preg_match('/^(.+) > ([a-zA-Z0-9_.]+)$/', $tag, $tagvar)) {
937
      $tag  =  $tagvar[1];
938
      list($newblock, $newskalar)  =  $this->var_name($tagvar[2]);
939
      $cmd  =  "\$$newblock"."['$newskalar']=";
940
    } else {
941
      $cmd  =  'echo';
942
    }
943
    $ret = array($cmd, $tag);
944
    return $ret;
945
  }
946

    
947
  /* @return int Number of subtemplate included
948
   * @access private
949
   * @desc Count number of subtemplates included in current template
950
   */
951
  function count_subtemplates() {
952
    $ret = preg_match_all('/<!-- INCLUDE ([a-zA-Z0-9_.]+) -->/', $this->template, $tvar);
953
    unset($tvar);
954
    return $ret;
955
  }
956

    
957
  function worx_var_swap($tpldata, $data, $supp_templates) { /* do the substitution of the variables here */
958

    
959
    /* replace all the template elements (sub templates) */
960
    if ( is_array($supp_templates) && !empty($supp_templates) ) {
961
      foreach ($supp_templates as $key => $val) {
962
        $tpldata = str_replace("\{$key}", $val, $tpldata);
963
      }
964
    }
965
    /* do the substitution of the directory names here */
966

    
967
    return $tpldata;
968

    
969
  }
970

    
971
  function worx_tpl_swap($tpldata, $data, $supp_templates) { /* do the substitution of the sub templates here */
972

    
973
    /* do the substitution of the directory names here */
974

    
975
    /* do image link substitution */
976
    if ( $data['tpl_img'] != '' && $data['url_img'] != '' ) {
977
      $tpldata = str_replace($data['tpl_img'],$data['url_img'],$tpldata);
978
      unset($data['tpl_img']);
979
      unset($data['url_img']);
980
    } elseif (defined(_URL_USRIMG)) {
981
      $tpldata = str_replace('tplimgs/',_URL_USRIMG,$tpldata);
982
    }
983

    
984
    /* do javascript link substitution */
985
    if ( $data['tpl_js'] != '' && $data['url_js'] != '' ) {
986
      $tpldata = str_replace($data['tpl_js'],$data['url_js'],$tpldata);
987
      unset($data['img_tpl']);
988
      unset($data['url_js']);
989
    } elseif (defined(_URL_USRJS)) {
990
      $tpldata = str_replace('tpljs/',_URL_USRJS,$tpldata);
991
    }
992

    
993
    /* do css link substitution */
994
    if ( $data['tpl_css'] != '' && $data['url_css'] != '' ) {
995
      $tpldata = str_replace($data['tpl_css'],$data['url_css'],$tpldata);
996
      unset($data['tpl_css']);
997
      unset($data['url_css']);
998
    } elseif (defined(_URL_USRCSS)) {
999
      $tpldata = str_replace('url_css/',_URL_USRCSS,$tpldata);
1000
    }
1001

    
1002
    return $tpldata;
1003

    
1004
  }
1005

    
1006
}
1007

    
1008
?>
(1-1/2)