PluginProbe
FakerPress / 0.8.0
FakerPress v0.8.0
0.9.2 trunk 0.1.0 0.1.1 0.1.2 0.1.3 0.5.2 0.5.3 0.6.1 0.6.2 0.6.3 0.6.4 0.6.5 0.6.6 0.7.0 0.7.1 0.7.2 0.8.0 0.9.0 0.9.1
fakerpress / src / FakerPress / Template.php

Template.php in FakerPress 0.8.0, at src/FakerPress/Template.php

667 lines 17.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace FakerPress;
3
4 use function FakerPress\is_truthy;
5 use function FakerPress\set;
6 use function FakerPress\get;
7
8 class Template {
9 /**
10 * The folders into which we will look for the template.
11 *
12 * @since 0.5.1
13 *
14 * @var array
15 */
16 protected $folder = [];
17
18 /**
19 * The origin class for the plugin where the template lives
20 *
21 * @since 0.5.1
22 *
23 * @var object
24 */
25 public $origin;
26
27 /**
28 * The local context for templates, mutable on every self::render() call
29 *
30 * @since 0.5.1
31 *
32 * @var array
33 */
34 protected $context = [];
35
36 /**
37 * The global context for this instance of templates
38 *
39 * @since 0.5.1
40 *
41 * @var array
42 */
43 protected $global = [];
44
45 /**
46 * Allow chaing if class will extract data from the local context
47 *
48 * @since 0.5.1
49 *
50 * @var boolean
51 */
52 protected $template_context_extract = false;
53
54 /**
55 * Base template for where to look for template
56 *
57 * @since 0.5.1
58 *
59 * @var array
60 */
61 protected $template_base_path;
62
63 /**
64 * Should we use a lookup into the list of folders to try to find the file
65 *
66 * @since 0.5.1
67 *
68 * @var bool
69 */
70 protected $template_folder_lookup = false;
71
72 /**
73 * Configures the class origin plugin path
74 *
75 * @since 0.5.1
76 *
77 * @param object|string $origin The base origin for the templates
78 *
79 * @return self
80 */
81 public function set_template_origin( $origin = null ) {
82 if ( empty( $origin ) ) {
83 $origin = $this->origin;
84 }
85
86 if ( is_string( $origin ) ) {
87 // Origin needs to be a class with a `instance` method
88 if ( class_exists( $origin ) && method_exists( $origin, 'instance' ) ) {
89 $origin = call_user_func( [ $origin, 'instance' ] );
90 }
91 }
92
93 if ( ! is_string( $origin ) ) {
94 $this->origin = $origin;
95 $this->template_base_path = untrailingslashit( $this->origin->path() );
96 } else {
97 $this->template_base_path = untrailingslashit( (array) explode( '/', $origin ) );
98 }
99
100 return $this;
101 }
102
103 /**
104 * Configures the class with the base folder in relation to the Origin
105 *
106 * @since 0.5.1
107 *
108 * @param array|string $folder Which folder we are going to look for templates
109 *
110 * @return self
111 */
112 public function set_template_folder( $folder = null ) {
113 // Allows configuring a already set class
114 if ( ! isset( $folder ) ) {
115 $folder = $this->folder;
116 }
117
118 // If Folder is String make it an Array
119 if ( is_string( $folder ) ) {
120 $folder = (array) explode( '/', $folder );
121 }
122
123 // Cast as Array and save
124 $this->folder = (array) $folder;
125
126 return $this;
127 }
128
129 /**
130 * Configures the class with the base folder in relation to the Origin
131 *
132 * @since 0.5.1
133 *
134 * @param mixed $use Should we look for template files in the list of folders
135 *
136 * @return self
137 */
138 public function set_template_folder_lookup( $value = true ) {
139 $this->template_folder_lookup = is_truthy( $value );
140
141 return $this;
142 }
143
144 /**
145 * Configures the class global context
146 *
147 * @since 0.5.1
148 *
149 * @param array $context Default global Context
150 *
151 * @return self
152 */
153 public function add_template_globals( $context = [] ) {
154 // Cast as Array merge and save
155 $this->global = wp_parse_args( (array) $context, $this->global );
156
157 return $this;
158 }
159
160 /**
161 * Configures if the class will extract context for template
162 *
163 * @since 0.5.1
164 *
165 * @param bool $value Should we extract context for templates
166 *
167 * @return self
168 */
169 public function set_template_context_extract( $value = false ) {
170 // Cast as bool and save
171 $this->template_context_extract = is_truthy( $value );
172
173 return $this;
174 }
175
176 /**
177 * Sets a Index inside of the global or local context
178 * Final to prevent extending the class when the `get` already exists on the child class
179 *
180 * @since 0.5.1
181 *
182 * @see \FakerPress\get
183 *
184 * @param array $index Specify each nested index in order.
185 * Example: array( 'lvl1', 'lvl2' );
186 * @param mixed $default Default value if the search finds nothing.
187 * @param boolean $is_local Use the Local or Global context
188 *
189 * @return mixed The value of the specified index or the default if not found.
190 */
191 final public function get( $index, $default = null, $is_local = true ) {
192 $context = $this->get_global_values();
193
194 if ( true === $is_local ) {
195 $context = $this->get_local_values();
196 }
197
198 /**
199 * Allows filtering the the getting of Context variables, also short circuiting
200 * Following the same strucuture as WP Core
201 *
202 * @since 4.6.2
203 *
204 * @param mixed $value The value that will be filtered
205 * @param array $index Specify each nested index in order.
206 * Example: array( 'lvl1', 'lvl2' );
207 * @param mixed $default Default value if the search finds nothing.
208 * @param boolean $is_local Use the Local or Global context
209 * @param self $template Current instance of the Template
210 */
211 $value = apply_filters( 'fakerpress_template_context_get', null, $index, $default, $is_local, $this );
212 if ( null !== $value ) {
213 return $value;
214 }
215
216 return get( $context, $index, $default );
217 }
218
219 /**
220 * Sets a Index inside of the global or local context
221 * Final to prevent extending the class when the `set` already exists on the child class
222 *
223 * @since 0.5.1
224 *
225 * @see fp_array_set
226 *
227 * @param string|array $index To set a key nested multiple levels deep pass an array
228 * specifying each key in order as a value.
229 * Example: array( 'lvl1', 'lvl2', 'lvl3' );
230 * @param mixed $value The value.
231 * @param boolean $is_local Use the Local or Global context
232 *
233 * @return array Full array with the key set to the specified value.
234 */
235 final public function set( $index, $value = null, $is_local = true ) {
236 if ( true === $is_local ) {
237 $this->context = set( $this->context, $index, $value );
238
239 return $this->context;
240 }
241
242 $this->global = set( $this->global, $index, $value );
243
244 return $this->global;
245 }
246
247 /**
248 * Merges local and global context, and saves it locally
249 *
250 * @since 0.5.1
251 *
252 * @param array $context Local Context array of data
253 * @param string $file Complete path to include the PHP File
254 * @param array $name Template name
255 *
256 * @return array
257 */
258 public function merge_context( $context = [], $file = null, $name = null ) {
259 // Allow for simple null usage as well as array() for nothing
260 if ( is_null( $context ) ) {
261 $context = [];
262 }
263
264 // Applies local context on top of Global one
265 $context = wp_parse_args( (array) $context, $this->global );
266
267 /**
268 * Allows filtering the Local context
269 *
270 * @since 0.5.1
271 *
272 * @param array $context Local Context array of data
273 * @param string $file Complete path to include the PHP File
274 * @param array $name Template name
275 * @param self $template Current instance of the Template
276 */
277 $this->context = apply_filters( 'fakerpress_template_context', $context, $file, $name, $this );
278
279 return $this->context;
280 }
281
282 /**
283 * Fetches the path for locating files in the Plugin Folder
284 *
285 * @since 0.5.1
286 *
287 * @return string
288 */
289 protected function get_template_plugin_path() {
290 // Craft the plugin Path
291 $path = array_merge( (array) $this->template_base_path, $this->folder );
292
293 // Implode to avoid Window Problems
294 $path = implode( DIRECTORY_SEPARATOR, $path );
295
296 /**
297 * Allows filtering of the base path for templates
298 *
299 * @since 0.5.1
300 *
301 * @param string $path Complete path to include the base plugin folder
302 * @param self $template Current instance of the Template
303 */
304 return apply_filters( 'fakerpress_template_plugin_path', $path, $this );
305 }
306
307 /**
308 * Fetches the Namespace for the public paths, normally folders to look for
309 * in the theme's directory.
310 *
311 * @since 0.5.1
312 *
313 * @return array
314 */
315 protected function get_template_public_namespace() {
316 $namespace = [
317 'fakerpress',
318 ];
319
320 if ( ! empty( $this->origin->template_namespace ) ) {
321 $namespace[] = $this->origin->template_namespace;
322 }
323
324 /**
325 * Allows filtering of the base path for templates
326 *
327 * @since 4.7.20
328 *
329 * @param array $namespace Which is the namespace we will look for files in the theme
330 * @param self $template Current instance of the Template
331 */
332 return apply_filters( 'fakerpress_template_public_namespace', $namespace, $this );
333 }
334
335 /**
336 * Fetches the path for locating files given a base folder normally theme related
337 *
338 * @since 0.5.1
339 *
340 * @param mixed $base Base path to look into
341 *
342 * @return string
343 */
344 protected function get_template_public_path( $base ) {
345 // Craft the plugin Path
346 $path = array_merge( (array) $base, (array) $this->get_template_public_namespace() );
347
348 // Implode to avoid Window Problems
349 $path = implode( DIRECTORY_SEPARATOR, $path );
350
351 /**
352 * Allows filtering of the base path for templates
353 *
354 * @since 0.5.1
355 *
356 * @param string $path Complete path to include the base public folder
357 * @param self $template Current instance of the Template
358 */
359 return apply_filters( 'fakerpress_template_public_path', $path, $this );
360 }
361
362 /**
363 * Fetches the folders in which we will look for a given file
364 *
365 * @since 0.5.1
366 *
367 * @return array
368 */
369 protected function get_template_path_list() {
370 $folders = [];
371
372 // Only look into public folders if we tell to use folders
373 if ( $this->template_folder_lookup ) {
374 $folders[] = [
375 'id' => 'child-theme',
376 'priority' => 10,
377 'path' => $this->get_template_public_path( STYLESHEETPATH ),
378 ];
379 $folders[] = [
380 'id' => 'parent-theme',
381 'priority' => 15,
382 'path' => $this->get_template_public_path( TEMPLATEPATH ),
383 ];
384 }
385
386 $folders[] = [
387 'id' => 'plugin',
388 'priority' => 20,
389 'path' => $this->get_template_plugin_path(),
390 ];
391
392 /**
393 * Allows filtering of the list of folders in which we will look for the
394 * template given.
395 *
396 * @since 0.5.1
397 *
398 * @param array $folders Complete path to include the base public folder
399 * @param self $template Current instance of the Template
400 */
401 $folders = apply_filters( 'fakerpress_template_path_list', $folders, $this );
402
403 uasort( $folders, 'FakerPress\sort_by_priority' );
404
405 return $folders;
406 }
407
408 /**
409 * Tries to locate the correct file we want to load based on the Template class
410 * configuration and it's list of folders
411 *
412 * @since 0.5.1
413 *
414 * @param mixed $name File name we are looking for
415 *
416 * @return string
417 */
418 public function get_template_file( $name ) {
419 // If name is String make it an Array
420 if ( is_string( $name ) ) {
421 $name = (array) explode( '/', $name );
422 }
423
424 $folders = $this->get_template_path_list();
425
426 foreach ( $folders as $folder ) {
427 $folder['path'] = trim( $folder['path'] );
428 if ( ! $folder['path'] ) {
429 continue;
430 }
431
432 // Build the File Path
433 $file = implode( DIRECTORY_SEPARATOR, array_merge( (array) $folder['path'], $name ) );
434
435 // Append the Extension to the file path
436 $file .= '.php';
437
438 // Skip non-existent files
439 if ( file_exists( $file ) ) {
440 /**
441 * A more Specific Filter that will include the template name
442 *
443 * @since 4.6.2
444 * @since 4.7.20 The $name param no longers contains the extension
445 *
446 * @param string $file Complete path to include the PHP File
447 * @param array $name Template name
448 * @param self $template Current instance of the Template
449 */
450 return apply_filters( 'fakerpress_template_file', $file, $name, $this );
451 }
452 }
453
454 // Couldn't find a template on the Stack
455 return false;
456 }
457
458 /**
459 * A very simple method to include a Template, allowing filtering and additions using hooks.
460 *
461 * @since 0.5.1
462 *
463 * @param string $name Which file we are talking about including
464 * @param array $context Any context data you need to expose to this file
465 * @param boolean $echo If we should also print the Template
466 *
467 * @return string|false Either the final content HTML or `false` if no template could be found.
468 */
469 public function render( $name, $context = [], $echo = true ) {
470 // If name is String make it an Array
471 if ( is_string( $name ) ) {
472 $name = (array) explode( '/', $name );
473 }
474
475 // Clean this Variable
476 $name = array_map( 'sanitize_key', $name );
477
478 if ( ! empty( $this->origin->template_namespace ) ) {
479 $namespace = array_merge( (array) $this->origin->template_namespace, $name );
480 } else {
481 $namespace = $name;
482 }
483
484 // Setup the Hook name
485 $hook_name = implode( '/', $namespace );
486
487 // Check if the file exists
488 $file = $this->get_template_file( $name );
489
490 // Check if it's a valid variable
491 if ( ! $file ) {
492 return false;
493 }
494
495 // Before we load the file we check if it exists
496 if ( ! file_exists( $file ) ) {
497 return false;
498 }
499
500 ob_start();
501
502 // Merges the local data passed to template to the global scope
503 $this->merge_context( $context, $file, $name );
504
505 /**
506 * Fires an Action before including the template file
507 *
508 * @since 0.5.1
509 *
510 * @param string $file Complete path to include the PHP File
511 * @param array $name Template name
512 * @param self $template Current instance of the Template
513 */
514 do_action( 'fakerpress_template_before_include', $file, $name, $this );
515
516 /**
517 * Fires an Action for a given template name before including the template file
518 *
519 * E.g.:
520 * `fakerpress_template_before_include:events/blocks/parts/details`
521 * `fakerpress_template_before_include:events/embed`
522 * `fakerpress_template_before_include:tickets/login-to-purchase`
523 *
524 * @since 0.5.1
525 *
526 * @param string $file Complete path to include the PHP File
527 * @param array $name Template name
528 * @param self $template Current instance of the Template
529 */
530 do_action( "fakerpress_template_before_include:$hook_name", $file, $name, $this );
531
532 // Only do this if really needed (by default it wont).
533 if ( true === $this->template_context_extract && ! empty( $this->context ) ) {
534 // We don't allow Extrating of a variable called $name
535 if ( isset( $this->context['name'] ) ) {
536 unset( $this->context['name'] );
537 }
538
539 // We don't allow the extraction of a variable called `$file`.
540 if ( isset( $this->context['file'] ) ) {
541 unset( $this->context['file'] );
542 }
543
544 // Make any provided variables available in the template variable scope.
545 extract( $this->context ); // @codingStandardsIgnoreLine
546 }
547
548 include $file;
549
550 /**
551 * Fires an Action after including the template file
552 *
553 * @since 0.5.1
554 *
555 * @param string $file Complete path to include the PHP File
556 * @param array $name Template name
557 * @param self $template Current instance of the Template
558 */
559 do_action( 'fakerpress_template_after_include', $file, $name, $this );
560
561 /**
562 * Fires an Action for a given template name after including the template file
563 *
564 * E.g.:
565 * `fakerpress_template_after_include:events/blocks/parts/details`
566 * `fakerpress_template_after_include:events/embed`
567 * `fakerpress_template_after_include:tickets/login-to-purchase`
568 *
569 * @since 0.5.1
570 *
571 * @param string $file Complete path to include the PHP File
572 * @param array $name Template name
573 * @param self $template Current instance of the Template
574 */
575 do_action( "fakerpress_template_after_include:$hook_name", $file, $name, $this );
576
577 // Only fetch the contents after the action
578 $html = ob_get_clean();
579
580 /**
581 * Allow users to filter the final HTML
582 *
583 * @since 0.5.1
584 *
585 * @param string $html The final HTML
586 * @param string $file Complete path to include the PHP File
587 * @param array $name Template name
588 * @param self $template Current instance of the Template
589 */
590 $html = apply_filters( 'fakerpress_template_html', $html, $file, $name, $this );
591
592 /**
593 * Allow users to filter the final HTML by the name
594 *
595 * E.g.:
596 * `fakerpress_template_html:events/blocks/parts/details`
597 * `fakerpress_template_html:events/embed`
598 * `fakerpress_template_html:tickets/login-to-purchase`
599 *
600 * @since 0.5.1
601 *
602 * @param string $html The final HTML
603 * @param string $file Complete path to include the PHP File
604 * @param array $name Template name
605 * @param self $template Current instance of the Template
606 */
607 $html = apply_filters( "fakerpress_template_html:$hook_name", $html, $file, $name, $this );
608
609 if ( $echo ) {
610 echo $html;
611 }
612
613 return $html;
614 }
615
616 /**
617 * Sets a number of values at the same time.
618 *
619 * @since 0.5.1
620 *
621 * @param array $values An associative key/value array of the values to set.
622 * @param bool $is_local Whether to set the values as global or local; defaults to local as the `set` method does.
623 *
624 * @see Template::set()
625 */
626 public function set_values( array $values = [], $is_local = true ) {
627 foreach ( $values as $key => $value ) {
628 $this->set( $key, $value, $is_local );
629 }
630 }
631
632 /**
633 * Returns the Template global context.
634 *
635 * @since 0.5.1
636 *
637 * @return array An associative key/value array of the Template global context.
638 */
639 public function get_global_values() {
640 return $this->global;
641 }
642
643 /**
644 * Returns the Template local context.
645 *
646 * @since 0.5.1
647 *
648 * @return array An associative key/value array of the Template local context.
649 */
650 public function get_local_values() {
651 return $this->context;
652 }
653
654 /**
655 * Returns the Template global and local context values.
656 *
657 * Local values will override the template global context values.
658 *
659 * @since 0.5.1
660 *
661 * @return array An associative key/value array of the Template global and local context.
662 */
663 public function get_values() {
664 return array_merge( $this->get_global_values(), $this->get_local_values() );
665 }
666 }
667