1 <?php
2
3 4 5 6 7 8 9 10
11
12 namespace Patron;
13
14 use ICanBoogie\Debug;
15 use ICanBoogie\Exception;
16
17 use Brickrouge\Alert;
18
19 define('WDPATRON_DELIMIT_MACROS', false);
20
21 class Engine extends TextHole
22 {
23 const PREFIX = 'p:';
24
25 protected $trace_templates = false;
26
27 public function __construct()
28 {
29
30
31
32
33 $this->contextInit();
34
35
36
37
38
39 $this->functions['to_s'] = function($a)
40 {
41 if (is_array($a) || (is_object($a) && !method_exists($a, '__toString')))
42 {
43 return \ICanBoogie\dump($a);
44 }
45
46 return (string) $a;
47 };
48
49 $this->functions['add'] = function($a,$b)
50 {
51 return ($a + $b);
52 };
53
54 $this->addFunction('try', array($this, '_get_try'));
55
56
57
58
59
60
61
62 $this->addFunction('if', function($a, $b, $c=null) { return $a ? $b : $c; });
63 $this->addFunction('or', function($a, $b) { return $a ? $a : $b; });
64 $this->addFunction('not', function($a) { return !$a; });
65 $this->addFunction('mod', function($a, $b) { return $a % $b; });
66 $this->addFunction('bit', function($a, $b) { return (int) $a & (1 << $b); });
67
68 $this->addFunction('greater', function($a, $b) { return ($a > $b); });
69 $this->addFunction('smaller', function($a, $b) { return ($a < $b); });
70 $this->addFunction('equals', function($a, $b) { return ($a == $b); });
71 $this->addFunction('different', function($a, $b) { return ($a != $b); });
72
73
74
75
76
77 $this->addFunction('minus', function($a, $b) { return $a - $b; });
78 $this->addFunction('plus', function($a, $b) { return $a + $b; });
79 $this->addFunction('times', function($a, $b) { return $a * $b; });
80 $this->addFunction('by', function($a, $b) { return $a / $b; });
81
82
83
84
85
86 $this->addFunction('split', function($a, $b=",") { return explode($b,$a); });
87 $this->addFunction('join', function($a, $b=",") { return implode($b,$a); });
88 $this->addFunction('index', function() { $a = func_get_args(); $i = array_shift($a); return $a[$i]; });
89 $this->addFunction('replace', function($a, $b, $c="") { return str_replace($b, $c, $a); });
90
91
92
93
94
95 96 97 98 99 100 101 102 103 104
105
106 $this->addFunction('first', function($a, $n=null) { $rc = array_slice($a, 0, $n ? $n : 1); return $n === null ? array_shift($rc) : $rc; });
107
108
109
110
111
112
113
114 $this->addFunction('markdown', function($txt) { require_once __DIR__ . '/../textmark.php'; return Markdown($txt); });
115 }
116
117 private static $singleton;
118
119 static public function get_singleton()
120 {
121 if (self::$singleton)
122 {
123 return self::$singleton;
124 }
125
126 $class = get_called_class();
127
128 self::$singleton = $singleton = new $class();
129
130 return $singleton;
131 }
132
133 public function _get_try($from, $which, $default=null)
134 {
135 $form = (array) $from;
136
137 return isset($from[$which]) ? $from[$which] : $default;
138 }
139
140 141 142 143 144 145 146
147
148 protected $trace = array();
149 protected $errors = array();
150
151 public function trace_enter($a)
152 {
153 array_unshift($this->trace, $a);
154 }
155
156 public function trace_exit()
157 {
158 array_shift($this->trace);
159 }
160
161 public function error($alert, array $args=array())
162 {
163 if ($alert instanceof \ICanBoogie\Exception\Config)
164 {
165 $this->errors[] = new Alert($alert->getMessage());
166
167 return;
168 }
169 else if ($alert instanceof \Exception)
170 {
171 $alert = Debug::format_alert($alert);
172 }
173 else
174 {
175 $alert = \ICanBoogie\format($alert, $args);
176 }
177
178
179
180
181
182 $trace_html = null;
183
184 if ($this->trace)
185 {
186 $i = count($this->trace);
187 $root = \ICanBoogie\DOCUMENT_ROOT;
188 $root_length = strlen($root);
189
190 foreach ($this->trace as $trace)
191 {
192 list($which, $message) = $trace;
193
194 if ($which == 'file')
195 {
196 if (strpos($message, $root_length) === 0)
197 {
198 $message = substr($message, $root_length);
199 }
200 }
201
202 $trace_html .= sprintf('#%02d: in %s "%s"', $i--, $which, $message) . '<br />';
203 }
204
205 if ($trace_html)
206 {
207 $trace = '<pre>' . $trace_html . '</pre>';
208 }
209 }
210
211
212
213
214
215 $this->errors[] = '<div class="alert alert-error">' . $alert . $trace_html . '</div>';
216 }
217
218 public function handle_exception(\Exception $e)
219 {
220 if ($e instanceof \ICanBoogie\HTTP\HTTP\Error)
221 {
222 throw $e;
223 }
224 else if ($e instanceof \ICanBoogie\ActiveRecord\ActiveRecordException)
225 {
226 throw $e;
227 }
228
229 $this->error($e);
230 }
231
232 public function fetchErrors()
233 {
234 $rc = implode(PHP_EOL, $this->errors);
235
236 $this->errors = array();
237
238 return $rc;
239 }
240
241 public function get_file()
242 {
243 foreach ($this->trace as $trace)
244 {
245 list($which, $data) = $trace;
246
247 if ($which == 'file')
248 {
249 return $data;
250 }
251 }
252 }
253
254 public function get_template_dir()
255 {
256 return dirname($this->get_file());
257 }
258
259 260 261 262 263 264 265
266
267 protected $templates = array();
268 protected $templates_searched = false;
269
270 protected function search_templates()
271 {
272 global $core;
273
274 if ($this->templates_searched)
275 {
276 return;
277 }
278
279 $templates = $core->site->partial_templates;
280
281 foreach ($templates as $id => $path)
282 {
283 $this->addTemplate($id, '!f:' . $path);
284 }
285
286 $this->templates_searched = true;
287 }
288
289 public function addTemplate($name, $template)
290 {
291 if (isset($this->templates[$name]))
292 {
293 $this->error
294 (
295 'The template %name is already defined ! !template', array
296 (
297 '%name' => $name, '!template' => $template
298 )
299 );
300
301 return;
302 }
303
304 $this->templates[$name] = $template;
305 }
306
307 protected function get_template($name)
308 {
309
310
311
312
313
314 if (empty($this->templates[$name]))
315 {
316 $this->search_templates();
317 }
318
319 if (isset($this->templates[$name]))
320 {
321 $template = $this->templates[$name];
322
323
324
325
326
327 if (is_string($template))
328 {
329 $file = null;
330
331 if ($template{0} == '!' && $template{1} == 'f' && $template{2} == ':')
332 {
333 $file = substr($template, 3);
334 $template = file_get_contents($file);
335 $file = substr($file, strlen(\ICanBoogie\DOCUMENT_ROOT));
336 }
337
338 339 340 341 342 343 344 345 346
347
348 $this->templates[$name] = $template;
349 }
350
351 return $template;
352 }
353 }
354
355 public function callTemplate($name, array $args=array())
356 {
357
358
359 $template = $this->get_template($name);
360
361 if (!$template)
362 {
363 $er = 'Unknown template %name';
364 $params = array('%name' => $name);
365
366 if ($this->templates)
367 {
368 $er .= ', available templates: :list';
369 $params[':list'] = implode(', ', array_keys($this->templates));
370 }
371
372 $this->error($er, $params);
373
374 return;
375 }
376
377 array_unshift($this->trace, array('template', $name));
378
379 if (version_compare(PHP_VERSION, '5.3.4', '>='))
380 {
381 $this->context['self']['arguments'] = $args;
382 }
383 else
384 {
385 $self = $this->context['self'];
386 $self['arguments'] = $args;
387 $this->context['self'] = $self;
388 }
389
390 $rc = $this($template);
391
392 array_shift($this->trace);
393
394 return $rc;
395 }
396
397 398 399 400 401 402 403
404
405 protected function contextInit()
406 {
407 $this->context = new \BlueTihi\Context(array('self' => null, 'this' => null));
408 }
409
410 411 412 413 414 415 416
417
418 protected function get_compiled($template)
419 {
420 static $compiler;
421
422 if ($compiler === null)
423 {
424 $compiler = new Compiler();
425 }
426
427 return $compiler($template);
428 }
429
430 public function publish($template, $bind=null, array $options=array())
431 {
432 trigger_error('The <q>publish</q> method is deprecated, please use invoke.');
433
434 return $this->__invoke($template, $bind, $options);
435 }
436
437 public function __invoke($template, $bind=null, array $options=array())
438 {
439 if (!$template)
440 {
441 return;
442 }
443
444 if ($bind !== null)
445 {
446 $this->context['this'] = $bind;
447 }
448
449 $file = null;
450
451 foreach ($options as $option => $value)
452 {
453 switch ((string) $option)
454 {
455 case 'variables':
456 {
457 $this->context = array_merge($this->context, $value);
458 }
459 break;
460
461 case 'file':
462 {
463 $file = $value;
464 }
465 break;
466
467 default:
468 {
469 trigger_error(\ICanBoogie\format('Suspicious option: %option :value', array('%option' => $option, ':value' => $value)));
470 }
471 break;
472 }
473 }
474
475 if (is_array($template) && isset($template['file']))
476 {
477 $file = $template['file'];
478
479 unset($template['file']);
480 }
481
482 if ($file)
483 {
484 array_unshift($this->trace, array('file', $file));
485 }
486
487
488
489
490
491 if (!is_array($template))
492 {
493 $template = $this->get_compiled($template);
494 }
495
496 $rc = '';
497
498 foreach ($template as $node)
499 {
500 if (!($node instanceof Node))
501 {
502 var_dump($node); continue;
503 }
504
505
506
507 try
508 {
509 $rc .= $node($this, $this->context);
510 }
511 catch (\Exception $e)
512 {
513 $rc .= Debug::format_alert($e);
514 }
515
516 $rc .= $this->fetchErrors();
517 }
518
519 $rc .= $this->fetchErrors();
520
521
522
523
524
525 if ($file)
526 {
527 array_shift($this->trace);
528 }
529
530 return $rc;
531 }
532
533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567
568
569 public $context_markup = array();
570 }