/MoxesTemplate.php

https://github.com/max4o/MoxesTemplate · PHP · 335 lines · 302 code · 22 blank · 11 comment · 71 complexity · ee30d30c80040113d412634105aba78d MD5 · raw file

  1. <?php
  2. /*
  3. * MoxesTemplate.php -- Template system for the Moxes Content Management System
  4. *
  5. * Developed by Milen Hristov <milen@moxes.net>
  6. * http://template.moxes.net
  7. *
  8. * Version 1.2
  9. * Revision date 03.11.2011
  10. *
  11. * TODO: Cache, Comment, NoParse, Loop modifications
  12. */
  13. class MoxesTemplate {
  14. private $filename;
  15. private $path;
  16. private $fh;
  17. private $row;
  18. private $openBlock = '{';
  19. private $closeBlock = '}';
  20. public $params = array();
  21. public $funcs = array();
  22. private $blocks = array();
  23. private $loopVars = array();
  24. private $print = 1;
  25. private $content;
  26. function __construct( $opt = null ) {
  27. if ( is_array( $opt ) ) {
  28. if ( ! empty( $opt['path'] ) ) {
  29. if ( is_dir( $opt['path'] ) ) {
  30. $this->path = $opt['path'];
  31. } else {
  32. $this->error("Invalid path {$opt['path']}");
  33. }
  34. }
  35. $filename = $opt['path'] .'/'.$opt['filename'];
  36. } else {
  37. $filename = $opt;
  38. }
  39. if ( file_exists( $filename ) && is_readable( $filename ) ) {
  40. $this->filename = $filename;
  41. } else {
  42. $this->error("Cannot open file {$filename}");
  43. }
  44. }
  45. private function parseStart() {
  46. $this->fh = fopen( $this->filename, 'r' );
  47. if ( ! $this->fh ) {
  48. $this->error("Cannot read from file {$this->filename}");
  49. }
  50. $this->row = 1;
  51. $this->content = '';
  52. return $this->parse();
  53. }
  54. public function param( $var = '', $val = '') {
  55. $this->params[$var] = $val;
  56. }
  57. public function func( $var = '', $val = '') {
  58. $this->funcs[$var] = $val;
  59. }
  60. private function getChar() {
  61. $char = fgetc( $this->fh );
  62. if ( $char == "\n" ) {
  63. $this->row++;
  64. }
  65. return $char;
  66. }
  67. private function parse() {
  68. $return = '';
  69. while ( false !== ( $char = $this->getChar() ) ) {
  70. if ( $char == $this->openBlock ) {
  71. $this->addContent( $this->parseBlock() );
  72. } else {
  73. if ( $this->print ) {
  74. $this->addContent( $char );
  75. }
  76. }
  77. }
  78. }
  79. private function parseBlock() {
  80. $block = '';
  81. while ( false !== ( $char = $this->getChar() ) ) {
  82. if ( $char == $this->closeBlock ) {
  83. return $this->block( $block );
  84. } else {
  85. $block .= $char;
  86. }
  87. }
  88. }
  89. private function parseVar( $var = '' ) {
  90. $el = explode( '.', $var );
  91. $t = count( $this->loopVars ) - 1;
  92. if ( ( $t >= 0 ) && isset( $this->loopVars[$t][$el[0]] ) ) {
  93. $p = $this->loopVars[ $t ];
  94. } else {
  95. $p = $this->params;
  96. }
  97. for ( $i = 0 ; $i < count( $el ) ; $i++ ) {
  98. if ( is_array( $p ) ) {
  99. if ( isset( $p[$el[$i]] ) ) {
  100. $p = $p[$el[$i]];
  101. } else {
  102. return null;
  103. }
  104. } elseif ( is_object( $p ) ) {
  105. if ( isset( $p->$el[$i] ) ) {
  106. $p = $p->$el[$i];
  107. } else {
  108. return null;
  109. }
  110. } else {
  111. return null;
  112. }
  113. }
  114. return $p;
  115. }
  116. private function block( $block = '' ) {
  117. if ( preg_match( '/^\s*\/\s*(\w+)\s*$/', $block, $matches) ) {
  118. $last = &$this->blocks[count( $this->blocks ) - 1];
  119. if ( isset( $last ) && ( $last['tag'] == $matches[1] ) ) {
  120. if ( $matches[1] == 'loop' ) {
  121. array_pop( $this->loopVars );
  122. if ( $last['rows'] > $last['row'] ) {
  123. $last['row']++;
  124. fseek( $this->fh, $last['marker'] );
  125. $this->blockLoopVars( $last );
  126. return;
  127. }
  128. }
  129. $this->print = $last['print'];
  130. array_pop( $this->blocks );
  131. } else {
  132. $this->error("Cannot close an unopened block \"$matches[1]\"", $this->row);
  133. }
  134. } elseif ( preg_match( '/^\s*\$([\w|\.]+)\s*$/', $block, $matches ) ) {
  135. return $this->blockVar( $matches[1] );
  136. } elseif ( preg_match( '/^\s*loop\s+\$([\w|\.]+)\s*$/', $block, $matches ) ) {
  137. return $this->blockLoop( $matches[1] );
  138. } elseif ( preg_match( '/^\s*if\s+?(.*)\s*$/i', $block, $matches ) ) {
  139. return $this->blockIf( $matches[1] );
  140. } elseif ( preg_match( '/^\s*elseif\s+?(.*)\s*$/i', $block, $matches ) ) {
  141. return $this->blockElseif( $matches[1] );
  142. } elseif ( preg_match( '/^\s*else\s*$/i', $block, $matches ) ) {
  143. return $this->blockElse();
  144. } elseif ( preg_match( '/^\s*include\s+"(.*?)"\s*$/i', $block, $matches ) ) {
  145. return $this->blockInclude( $matches[1] );
  146. } else {
  147. if ( preg_match( '/^\s*(\w+)\s*("(.*?)"){0,1}\s*$/i', $block, $matches ) ) {
  148. if ( $this->funcs[$matches[1]] ) {
  149. return $this->funcs[$matches[1]]($matches[3]);
  150. }
  151. }
  152. return $this->openBlock.$block.$this->closeBlock;
  153. }
  154. }
  155. private function blockVar( $var = '' ) {
  156. return $this->parseVar( $var );
  157. }
  158. private function blockInclude( $var = '' ) {
  159. if ( $this->path ) {
  160. $found = 0;
  161. $path = opendir( $this->path );
  162. while ( ( $file = readdir( $path ) ) !== false ) {
  163. if ( preg_match( '/^\./', $file ) ) {
  164. continue;
  165. }
  166. if ( ( $var == $file ) && is_file( $this->path.'/'.$file ) ) {
  167. $found = 1;
  168. break;
  169. }
  170. }
  171. if ( ! $found ) {
  172. $this->error("Invalid include file {$var}");
  173. }
  174. }
  175. if ( $this->path ) {
  176. $load = new MoxesTemplate( array( 'path' => $this->path, 'filename' => $var ) );
  177. } else {
  178. $load = new MoxesTemplate( $var );
  179. }
  180. if ( $load ) {
  181. $load->params = $this->params;
  182. $load->funcs = $this->funcs;
  183. return $load->output();
  184. }
  185. return '';
  186. }
  187. private function blockLoop( $var = '' ) {
  188. array_push(
  189. $this->blocks, array(
  190. 'tag' => 'loop',
  191. 'print' => $this->print
  192. )
  193. );
  194. $last = count( $this->blocks ) - 1;
  195. $pVar = $this->parseVar( $var );
  196. if ( isset( $pVar ) && is_array( $pVar ) && ( count( $pVar ) > 0 ) ) {
  197. $this->blocks[$last]['var'] = $pVar;
  198. $this->blocks[$last]['rows'] = count( $pVar );
  199. $this->blocks[$last]['row'] = 1;
  200. $this->blocks[$last]['marker'] = ftell( $this->fh );
  201. $this->blockLoopVars( $this->blocks[$last] );
  202. } else {
  203. $this->print = 0;
  204. }
  205. }
  206. private function blockLoopVars( &$last ) {
  207. $val = current($last['var']);
  208. $key = key( $last['var'] );
  209. array_push( $this->loopVars,
  210. array(
  211. 'key' => $key,
  212. 'value' => $val,
  213. 'count' => $last['row'],
  214. '__isLast' => $last['row'] == $last['rows'] ? 1 : 0,
  215. '__isFirst' => $last['row'] == 1 ? 1 : 0,
  216. '__isOdd' => $last['row'] % 2 ? 1 : 0,
  217. '__prevKey' => isset($last['prevKey']) ? $last['prevKey'] : '',
  218. '__prevValue' => isset($last['prevVal']) ? $last['prevVal'] : '',
  219. )
  220. );
  221. next( $last['var'] );
  222. $last['prevKey'] = $key;
  223. $last['prevVal'] = $val;
  224. }
  225. private function blockIf( $condition ) {
  226. $success = $this->trueCondition( $condition );
  227. array_push(
  228. $this->blocks, array(
  229. 'success' => $success,
  230. 'tag' => 'if',
  231. 'row' => $this->row,
  232. 'print' => $this->print
  233. )
  234. );
  235. $this->print += $success ? 0 : -1;
  236. }
  237. private function blockElseif( $condition ) {
  238. $last = &$this->blocks[count( $this->blocks) -1];
  239. if ( $last['success'] ) {
  240. $this->print = $last['print'] - 1;
  241. } else {
  242. $success = $this->trueCondition( $condition );
  243. if ( $success ) {
  244. $last['success'] = true;
  245. $this->print++;
  246. }
  247. }
  248. }
  249. private function blockElse() {
  250. $t = $this->blocks[count( $this->blocks ) - 1 ];
  251. if ( isset( $t ) && ( $t['tag'] == 'if' ) ) {
  252. $this->print += $t['success'] ? -1 : 1;
  253. } else {
  254. $this->error("Unopened block \"else\"", $t['row']);
  255. }
  256. }
  257. private function trueCondition( $condition = '' ) {
  258. if ( preg_match( '/^\s*\$([\w|\.]+)\s*$/' , $condition, $matches ) ) {
  259. return $this->parseVar( $matches[1] ) ? true : false;
  260. } elseif ( preg_match( '/^\s*\!\s*\$([\w|\.]+)\s*$/' , $condition, $matches ) ) {
  261. return $this->parseVar( $matches[1] ) ? false : true;
  262. } elseif ( preg_match( '/^\s*isset\(\s*\$([\w|\.]+)\s*\)\s*$/i' , $condition, $matches ) ) {
  263. return $this->parseVar( $matches[1] ) !== null ? true : false;
  264. } elseif ( preg_match( '/^\s*\!\s*isset\(\s*\$([\w|\.]+)\s*\)\s*$/i' , $condition, $matches ) ) {
  265. return $this->parseVar( $matches[1] ) === null ? true : false;
  266. } elseif ( preg_match( '/^\s*(\$([\w|\.]+)|"(.*?)")\s*([\=|\>|\<|\!]\={0,1}%{0,1})\s*(\$([\w|\.]+)|"(.*?)")\s*$/' , $condition, $matches ) ) {
  267. $t1 = $matches[2] ? $this->parseVar( $matches[2]) : $matches[3];
  268. $t2 = $matches[6] ? $this->parseVar( $matches[6]) : $matches[7];
  269. switch ( $matches[4] ) {
  270. case "==":
  271. return $t1 == $t2 ? true : false; break;
  272. case "!=":
  273. return $t1 != $t2 ? true : false; break;
  274. case ">=":
  275. return $t1 >= $t2 ? true : false; break;
  276. case "<=":
  277. return $t1 <= $t2 ? true : false; break;
  278. case ">":
  279. return $t1 > $t2 ? true : false; break;
  280. case "<":
  281. return $t1 < $t2 ? true : false; break;
  282. case "%":
  283. return $t1 % $t2 ? true : false; break;
  284. case "!%":
  285. return $t1 % $t2 ? false : true; break;
  286. default:
  287. $this->error("Invalid statement \"{$matches[4]}\"", $this->row); break;
  288. }
  289. } else {
  290. $this->error("Invalid statement", $this->row );
  291. }
  292. }
  293. private function addContent( $t ) {
  294. if ( $this->print > 0 ) {
  295. $this->content .= $t;
  296. }
  297. }
  298. public function output() {
  299. $this->parseStart();
  300. $countBlocks = count( $this->blocks );
  301. if ( $countBlocks > 0 ) {
  302. $this->error("There is unclosed block \"{$this->blocks[$countBlocks-1]['tag']}\"", $this->blocks[$countBlocks-1]['row']);
  303. }
  304. return $this->content;
  305. }
  306. private function error( $error = '', $row = '' ) {
  307. die("MoxesTemplate: {$error}".( $row ? " ({$this->filename}: {$this->row})":""));
  308. }
  309. public function filename() {
  310. return $this->filename;
  311. }
  312. }
  313. ?>