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