1 <?php
2
3 /*
4 * This file is part of the Patron package.
5 *
6 * (c) Olivier Laviale <olivier.laviale@gmail.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12 namespace Patron;
13
14 use Brickrouge\Pager;
15
16 class Hooks
17 {
18 /**
19 * Renders a page element.
20 *
21 * <pre>
22 * <p:pager
23 * count = int
24 * page = int
25 * limit = int
26 * with = string
27 * range = expression
28 * noarrows = boolean>
29 * <!-- Content: p:with-param*, template? -->
30 * </p:pager>
31 * </pre>
32 *
33 * @param array $args
34 * @param Engine $patron
35 * @param mixed $template
36 *
37 * @return string
38 */
39 static public function markup_pager(array $args, Engine $patron, $template)
40 {
41 extract($args);
42
43 if (!$range)
44 {
45 if (isset($patron->context['range']))
46 {
47 $range = $patron->context['range'];
48 }
49 }
50
51 if ($range)
52 {
53 $count = $range['count'];
54 $limit = $range['limit'];
55 $page = isset($range['page']) ? $range['page'] : 0;
56
57 if (isset($range['with']))
58 {
59 $with = $range['with'];
60 }
61 }
62
63 $pager = new Pager
64 (
65 'div', array
66 (
67 Pager::T_COUNT => $count,
68 Pager::T_LIMIT => $limit,
69 Pager::T_POSITION => $page,
70 Pager::T_NO_ARROWS => $noarrows,
71 Pager::T_WITH => $with
72 )
73 );
74
75 return $template ? $patron($template, $pager) : (string) $pager;
76 }
77
78 /**
79 * Adds a template.
80 *
81 * <pre>
82 * <p:template
83 * name = qname>
84 * <!-- Content: p:with-param*, template -->
85 * </p:template>
86 * </pre>
87 *
88 * The `name` attribute defines the name of the template. The content of the markup defines
89 * the template.
90 *
91 * @param array $args
92 * @param Engine $patron
93 * @param mixed $template
94 */
95 static public function markup_template(array $args, Engine $patron, $template)
96 {
97 $patron->addTemplate($args['name'], $template);
98 }
99
100 /**
101 * Calls a template.
102 *
103 * <pre>
104 * <p:call-template
105 * name = qname>
106 * <!-- Content: p:with-param* -->
107 * </p:call-template>
108 * </pre>
109 *
110 * @param array $args
111 * @param Engine $patron
112 * @param mixed $template
113 *
114 * @return string
115 */
116 static public function markup_call_template(array $args, Engine $patron, $template)
117 {
118 return $patron->callTemplate($args['name'], $args);
119 }
120
121 /**
122 * Applies a template to each entries of the provided array.
123 *
124 * <pre>
125 * <p:foreach
126 * in = expression | this
127 * as = qname | this>
128 * <!-- Content: p:with-param*, template -->
129 * </p:foreach>
130 * </pre>
131 *
132 * At each turn the following variables are updated in `self`:
133 *
134 * - `count`: The number of entries.
135 * - `position`: The position of the current entry.
136 * - `left`: The number of entries left.
137 * - `even`: "even" if the position is even, an empty string otherwise.
138 * - `key`: The key of the entry.
139 *
140 * @param array $args
141 * @param Engine $patron
142 * @param mixed $template
143 *
144 * @return string
145 */
146 static public function markup_foreach(array $args, Engine $patron, $template)
147 {
148 #
149 # get entries array from context
150 #
151
152 $entries = $args['in'];
153
154 if (!$entries)
155 {
156 return;
157 }
158
159 if (!is_array($entries) && !is_object($entries))
160 {
161 $patron->error
162 (
163 'Invalid source for %param. Source must either be an array or a traversable object. Given: !entries', array
164 (
165 '%param' => 'in', '!entries' => $entries
166 )
167 );
168
169 return;
170 }
171
172 #
173 # create body from iterations
174 #
175
176 $count = count($entries);
177 $position = 0;
178 $left = $count;
179 $even = 'even';
180 $key = null;
181
182 $context = array
183 (
184 'count' => &$count,
185 'position' => &$position,
186 'left' => &$left,
187 'even' => &$even,
188 'key' => &$key
189 );
190
191 $as = $args['as'];
192
193 $patron->context['self'] = array_merge($patron->context['self'], $context);
194
195 $rc = '';
196
197 foreach ($entries as $key => $entry)
198 {
199 $position++;
200 $left--;
201 $even = ($position % 2) ? '' : 'even';
202
203 if ($as)
204 {
205 $patron->context[$as] = $entry;
206 }
207
208 $rc .= $patron($template, $entry);
209 }
210
211 return $rc;
212 }
213
214 /**
215 * Provides a simple if-then conditionality.
216 *
217 * <pre>
218 * <p:if
219 * test = expression
220 * select = expression
221 * equals = value>
222 * <!-- Content: p:with-param*, template -->
223 * </p:if>
224 * </pre>
225 *
226 * Either `test` or `select` and an operator (e.g. `equals`) should be defined.
227 *
228 * Example:
229 *
230 * <pre>
231 * <p:if test="@has_title">This article has a title</p:if>
232 * <p:if test="@has_title.not()">This article has no title</p:if>
233 * <p:if select="@comments_count" equals="10">This article has 10 comments</p:if>
234 * </pre>
235 *
236 * @param array $args
237 * @param Engine $patron
238 * @param mixed $template
239 *
240 * @throws Exception when both `test` and `select` are defined.
241 */
242 static public function markup_if(array $args, Engine $patron, $template)
243 {
244 if (isset($args['test']) && isset($args['select']))
245 {
246 throw new Exception("Ambiguous test. Both <q>test</q> and <q>select</q> are defined.");
247 }
248
249 $true = false;
250
251 if ($args['equals'] !== null)
252 {
253 $true = $args['select'] == $args['equals'];
254 }
255 else
256 {
257 $true = !empty($args['test']);
258 }
259
260 #
261 # if the evaluation is not empty (0 or ''), we publish the template
262 #
263
264 if ($true)
265 {
266 return $patron($template);
267 }
268 }
269
270 /**
271 * Selects one among a number of possible alternatives.
272 *
273 * <pre>
274 * <!-- Category: instruction -->
275 * <p:choose>
276 * <!-- Content: (p:when+, p:otherwise?) -->
277 * </p:choose>
278 *
279 * <p:when
280 * test = boolean-expression>
281 * <!-- Content: template -->
282 * </p:when>
283 *
284 * <p:otherwise>
285 * <!-- Content: template -->
286 * </p:otherwise>
287 * </pre>
288 *
289 * It consists of a sequence of `p:when` elements followed by an optional `p:otherwise`
290 * element. Each `p:when` element has a single attribute, test, which specifies an expression.
291 * The content of the `p:when` and `p:otherwise` elements is a template. When an `p:choose`
292 * element is processed, each of the `p:when` elements is tested in turn, by evaluating the
293 * expression and converting the resulting object to a boolean as if by a call to the boolean
294 * function. The content of the first, and only the first, `p:when` element whose test is true
295 * is instantiated. If no `p:when` is true, the content of the `p:otherwise` element is
296 * instantiated. If no `p:when` element is true, and no `p:otherwise` element is present,
297 * nothing is created.
298 *
299 * @param array $args
300 * @param Engine $patron
301 * @param mixed $template
302 */
303 static public function markup_choose(array $args, Engine $patron, $template)
304 {
305 $otherwise = null;
306
307 #
308 # handle 'when' children as they are defined.
309 # if we find an 'otherwise' we keep it for later
310 #
311
312 foreach ($template as $node)
313 {
314 $name = $node->name;
315
316 if ($name == 'otherwise')
317 {
318 $otherwise = $node;
319
320 continue;
321 }
322
323 if ($name != 'when')
324 {
325 return $patron->error('Unexpected child: :node', array(':node' => $node));
326 }
327
328 $value = $patron->evaluate($node->args['test'], true);
329
330 if ($value)
331 {
332 return $patron($node->nodes);
333 }
334 }
335
336 #
337 # otherwise
338 #
339
340 if (!$otherwise)
341 {
342 return;
343 }
344
345 return $patron($otherwise->nodes);
346 }
347
348 /**
349 * Binds a name to a value.
350 *
351 * <pre>
352 * <!-- Category: top-level-element -->
353 * <!-- Category: instruction -->
354 * <p:variable
355 * name = qname
356 * select = expression>
357 * <!-- Content: p:with-param*, template? -->
358 * </p:variable>
359 *
360 * <!-- Category: top-level-element -->
361 * <p:param
362 * name = qname
363 * select = expression>
364 * <!-- Content: template? -->
365 * </p:param>
366 * </pre>
367 *
368 * The value to which a variable is bound (the value of the variable) can be an object of any
369 * of the types that can be returned by expressions. There are two elements that can be used
370 * to bind variables: `p:variable` and `p:with-param`. The difference is that the value
371 * specified on the `p:with-param` variable is only a default value for the binding; when
372 * the template within which the `p:with-param` element occurs is invoked, parameters may
373 * be passed that are used in place of the default values.
374 *
375 * Both `p:variable` and `p:with-param` have a required name attribute, which specifies the
376 * name of the variable. The value of the name attribute is a qualified name.
377 *
378 * Example:
379 *
380 * <pre>
381 * <p:variable name="count" select="@comments_count" />
382 * <p:variable name="count">There are #{@comments_count} comments</p:variable>
383 * </pre>
384 *
385 * @param array $args
386 * @param Engine $patron
387 * @param mixed $template
388 */
389 static public function markup_variable(array $args, Engine $patron, $template)
390 {
391 $select = $args['select'];
392
393 if ($select && $template)
394 {
395 return $patron->error('Ambiguous selection');
396 }
397 else if ($select)
398 {
399 $value = $select;
400 }
401 else
402 {
403 $value = $patron($template);
404 }
405
406 $name = $args['name'];
407
408 $patron->context[$name] = $value;
409 }
410
411 /**
412 * Parses a template with a bounded value.
413 *
414 * <pre>
415 * <p:with
416 * select = expression>
417 * <!-- Content: p:with-param*, template -->
418 * </p:with>
419 * </pre>
420 *
421 * Example:
422 *
423 * <pre>
424 * <p:with select="articles.first().comments.last()">
425 * Last comment: <a href="#{@url}">#{@title}</a>
426 * </p:with>
427 * </pre>
428 *
429 * @param array $args
430 * @param Engine $patron
431 * @param mixed $template
432 */
433 static public function markup_with(array $args, Engine $patron, $template)
434 {
435 if ($template === null)
436 {
437 return $patron->error('A template is required.');
438 }
439
440 $select = $args['select'];
441
442 return $patron($template, $select);
443 }
444
445 /**
446 * Translates and interpolates a string.
447 *
448 * <pre>
449 * <p:translate
450 * native = string>
451 * <!-- Content: p:with-param* -->
452 * </p:translate>
453 * </pre>
454 *
455 * The arguments for the interpolation are provided using the attributes of the markup, or the
456 * `with-param` construction.
457 *
458 * Example:
459 *
460 * <pre>
461 * <p:translate native="Posted on :date by !name">
462 * <p:with-param name="date"><time datetime="#{@date}" pubdate="pubdate">#{@date.format_date()}</time></p:with-param>
463 * <p:with-param name="name" select="@user.name" />
464 * </p:translate>
465 * </pre>
466 *
467 * @param array $args
468 * @param Engine $patron
469 * @param mixed $template
470 */
471 static public function markup_translate(array $args, Engine $patron, $template)
472 {
473 $native = $args['native'];
474
475 return call_user_func('ICanBoogie\I18n\t', $native, $args);
476 }
477
478 /**
479 * Decorates a content with a template.
480 *
481 * <pre>
482 * <p:decorate
483 * with = string>
484 * <!-- Content: p:with-param*, template -->
485 * </p:decorate>
486 * </pre>
487 *
488 * The content of the markup is rendered to create the component to decorate, it is then passed
489 * to the decorating template as the `component` variable.
490 *
491 * The name of the decorating template is specified with the `with` attribute, and is
492 * interpolated e.g. if "page" is specified the templates "@page.html" or "partials/@page.html"
493 * are used, which ever comes first.
494 *
495 * The parameters specified using `with-param` are all turned into variables.
496 *
497 * Example:
498 *
499 * <pre>
500 * <p:decorate with="page">
501 * <p:page:content id="body" />
502 * </p:decorate>
503 * </pre>
504 *
505 * @param array $args
506 * @param Engine $engine
507 * @param mixed $template
508 */
509 static public function markup_decorate(array $args, Engine $engine, $template)
510 {
511 $template_name = $args['with'];
512
513 $rendered_template = $engine($template);
514
515 unset($args['with']);
516
517 $engine->context['component'] = $rendered_template;
518
519 foreach ($args as $name => $value)
520 {
521 $engine->context[$name] = $value;
522 }
523
524 return $engine->callTemplate('@' . $template_name, $args);
525 }
526 }