1 <?php
  2   3   4   5   6   7 
  8 
  9 namespace Hawk;
 10 
 11  12  13  14  15 
 16 class View{
 17      18  19 
 20     private $file,
 21 
 22      23  24  25  26 
 27     $content,
 28 
 29      30  31  32  33 
 34     $data,
 35 
 36      37  38  39  40 
 41     $cached = false;
 42 
 43      44  45 
 46     private static $instances = array();
 47 
 48      49  50 
 51     const PLUGINS_VIEW = 'view-plugins/';
 52 
 53      54  55 
 56     const BLOCK_START_REGEX = '#\{(if|elseif|else|for|foreach|while)\s*(\(.+?\))?\s*\}#i';
 57 
 58      59  60 
 61     const BLOCK_END_REGEX = '#\{\/(if|for|foreach|while)\s*\}#is';
 62 
 63      64  65 
 66     const ECHO_REGEX = '#\{{2}\s*(.+?)\}{2}#is';
 67 
 68      69  70 
 71     const ASSIGN_REGEX = '#\{assign\s+name=(["\'])(\w+)\\1\s*\}(.*?)\{\/assign\}#ims';
 72 
 73      74  75 
 76     const PLUGIN_REGEX = '#\{(\w+)((\s+[\w\-]+\=([\'"])((?:[^\4\\\\]|\\\\.)*?)\4)*?)\s*\}#sm';
 77 
 78      79  80 
 81     const PLUGIN_ARGUMENTS_REGEX = '#([\w\-]+)\=([\'"])(\{?)((?:[^\2\\\\]|\\\\.)*?)(\}?)\\2#sm';
 82 
 83      84  85 
 86     const TRANSLATION_REGEX = '#{(?!if)([a-zA-Z]{2})}(.*?){/\\1}#ism';
 87 
 88 
 89      90  91  92  93 
 94     public function __construct($file){
 95         if(!is_file($file)) {
 96             
 97             throw new ViewException(ViewException::TYPE_FILE_NOT_FOUND, $file);
 98         }
 99 
100         $this->file = $file;
101 
102         
103         $this->cacheFile = 'views/' . str_replace(array(ROOT_DIR, '/'), array('', '-'), realpath($this->file)) . '.php';
104 
105         if(! App::cache()->isCached($this->file, $this->cacheFile)) {
106             $this->content = file_get_contents($this->file);
107             $this->parse();
108             App::cache()->save($this->cacheFile, $this->parsed);
109         }
110 
111         self::$instances[realpath($this->file)] = $this;
112     }
113 
114 
115     116 117 118 119 120 121 
122     public function setData($data = array()){
123         $this->data = $data;
124         return $this;
125     }
126 
127 
128     129 130 131 132 133 134 
135     public function addData($data = array()){
136         $this->data = array_merge($this->data, $data);
137         return $this;
138     }
139 
140 
141     142 143 144 145 
146     public function getData(){
147         return $this->data;
148     }
149 
150 
151     152 153 154 155 
156     private function parse(){
157         
158         $replaces = array(
159             
160             self::BLOCK_START_REGEX => "<?php $1 $2 : ?>",
161 
162             
163             self::BLOCK_END_REGEX   => "<?php end$1; ?>",
164 
165             
166             self::ECHO_REGEX        => "<?= $1 ?>",
167 
168             
169             self::TRANSLATION_REGEX => "<?php if(LANGUAGE == '$1'): ?>$2<?php endif; ?>",
170 
171             
172             self::ASSIGN_REGEX      => "<?php ob_start(); ?>$3<?php \$$2 = ob_get_clean(); ?>"
173         );
174 
175         $this->parsed = preg_replace(array_keys($replaces), $replaces, $this->content);
176 
177         
178         $this->parsed = $this->parsePlugin($this->parsed);
179 
180         $this->parsed = '<?php namespace ' . __NAMESPACE__ . '; ?>' . $this->parsed;
181 
182         return $this;
183     }
184 
185 
186     187 188 189 190 191 192 193 
194     private function parsePlugin($content, $inPlugin = false){
195         return preg_replace_callback(
196             self::PLUGIN_REGEX, function ($matches) use ($inPlugin) {
197                 list($l, $component, $arguments) = $matches;
198                 $componentClass = '\\Hawk\\View\\Plugins\\' . ucfirst($component);
199 
200                 if(!class_exists($componentClass)) {
201                     return $matches[0];
202                 }
203                 try{
204 
205                     $parameters = array();
206 
207                     while(preg_match(self::PLUGIN_ARGUMENTS_REGEX, $arguments, $m)){
208                         list($whole, $name, $quote, $lbrace, $value, $rbrace) = $m;
209                         if($lbrace && $rbrace) {
210                             $subPlugin = $lbrace . stripslashes($value) . $rbrace;
211                             if(preg_match(self::PLUGIN_REGEX, $subPlugin)) {
212                                 
213                                 $value = $this->parsePlugin($subPlugin, true);
214                             }
215 
216                             
217                         }
218                         else{
219                             
220                             $value = '\'' .addcslashes($lbrace . $value . $rbrace, '\\\'') . '\'';
221                         }
222 
223                         $parameters[$name] = '\'' . $name . '\' => ' . $value;
224 
225                         
226                         $arguments = str_replace($m[0], '', $arguments);
227                     }
228 
229                     if(! $inPlugin) {
230                         return '<?= new ' . $componentClass . '("' . $this->file . '", $_viewData, array(' . implode(',', $parameters) . ') ) ?>';
231                     }
232                     else{
233                         return '(new ' . $componentClass . '("' . $this->file . '", $_viewData, array(' . implode(',', $parameters) . ')))->display()';
234                     }
235                 }
236                 catch(\Exception $e){
237                     return $matches[0];
238                 }
239             }, $content
240         );
241     }
242 
243 
244     245 246 247 248 
249     public function display(){
250         extract($this->data);
251         $_viewData = $this->data;
252         ob_start();
253 
254         include App::cache()->getCacheFilePath($this->cacheFile);
255 
256         return ob_get_clean();
257     }
258 
259 
260     261 262 263 264 265 266 267 
268     public static function make($file, $data = array()){
269         $view = new self($file);
270         $view->setData($data);
271         return $view->display();
272     }
273 
274 
275     276 277 278 279 280 281 282 
283     public static function makeFromString($content, $data = array()){
284         $file = tempnam(TMP_DIR, '');
285         file_put_contents($file, $content);
286 
287         
288         $view = new self($file);
289         $view->setData($data);
290         $result = $view->display();
291 
292         
293         unlink($file);
294         App::cache()->clear($view->cacheFile);
295 
296         return $result;
297     }
298 }
299 
300 
301 
302 
303 304 305 306 307 
308 class ViewException extends \Exception{
309     310 311 
312     const TYPE_FILE_NOT_FOUND = 1;
313 
314     315 316 
317     const TYPE_EVAL = 2;
318 
319     320 321 322 323 324 325 
326     public function __construct($type, $file, $previous = null){
327         $code = $type;
328         switch($type){
329             case self::TYPE_FILE_NOT_FOUND:
330                 $message = "Error creating a view from template file $file : No such file or directory";
331                 break;
332 
333             case self::TYPE_EVAL:
334                 $trace = array_map(
335                     function ($t) {
336                         return $t['file'] . ':' . $t['line'];
337                     }, $previous->getTrace()
338                 );
339 
340                 $message = "An error occured while building the view from file $file : " . $previous->getMessage() . PHP_EOL . implode(PHP_EOL, $trace);
341                 break;
342         }
343 
344         parent::__construct($message, $code, $previous);
345     }
346 }
347