PluginProbe ʕ •ᴥ•ʔ
VikAppointments Services Booking Calendar / 1.2.20
VikAppointments Services Booking Calendar v1.2.20
1.2.21 1.2.20 trunk 1.2.17 1.2.18 1.2.19
vikappointments / libraries / adapter / layout / file.php
vikappointments / libraries / adapter / layout Last commit date
base.php 1 month ago file.php 1 month ago helper.php 1 month ago
file.php
572 lines
1 <?php
2 /**
3 * @package VikWP - Libraries
4 * @subpackage adapter.layout
5 * @author E4J s.r.l.
6 * @copyright Copyright (C) 2023 E4J s.r.l. All Rights Reserved.
7 * @license http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
8 * @link https://vikwp.com
9 */
10
11 // No direct access
12 defined('ABSPATH') or die('No script kiddies please!');
13
14 JLoader::import('adapter.layout.base');
15 JLoader::import('adapter.filesystem.path');
16
17 /**
18 * Base class for rendering a display layout loaded from from a layout file.
19 *
20 * It is possible to create theme overrides for the layouts by adding the specific
21 * file into a path built as follows:
22 * /wp-content/uploads/[PLUGIN_NAME]/layouts/[CLIENT]/[LAYOUT_PATH].php
23 *
24 * For example, in case we need to override the site 'html.user.login' layout
25 * that belong to the 'vik' plugin, the path will look like:
26 * /wp-content/uploads/vik/layouts/site/html/user/login.php
27 *
28 * The client can assume only 2 values: site or admin.
29 *
30 * @since 10.0
31 */
32 class JLayoutFile extends JLayoutBase
33 {
34 /**
35 * A list containing the cached layout paths.
36 *
37 * @var array
38 */
39 protected static $cache = array();
40
41 /**
42 * The identifier of the layout file to render.
43 *
44 * @var string
45 */
46 protected $layoutId = '';
47
48 /**
49 * The base path of the files to include.
50 *
51 * @var string
52 */
53 protected $basePath = null;
54
55 /**
56 * Full path to actual layout files, after possible template override check.
57 *
58 * @var string
59 * @since 10.1.18
60 */
61 protected $fullPath = null;
62
63 /**
64 * Paths to search for layouts.
65 *
66 * @var array
67 * @since 10.1.18
68 */
69 protected $includePaths = array();
70
71 /**
72 * Method to instantiate the file-based layout.
73 *
74 * @param string $layoutId Dot separated path to the layout file, relative to base path.
75 * @param string $basePath Base path to use when loading layout files.
76 * @param mixed $options Optional custom options to load. Registry or array format.
77 */
78 public function __construct($layoutId, $basePath = null, $options = null)
79 {
80 // initialise / load options
81 $this->setOptions($options);
82
83 // main properties
84 $this->setLayoutId($layoutId);
85 $this->basePath = $basePath;
86
87 // init enviroment
88 $this->setComponent($this->options->get('component', 'auto'));
89 $this->setClient($this->options->get('client', 'auto'));
90 }
91
92 /**
93 * Method to render the layout.
94 *
95 * @param array $displayData Array of properties available for use inside
96 * the layout file to build the displayed output.
97 *
98 * @return string The necessary HTML to display the layout.
99 *
100 * @uses clearDebugMessages()
101 * @uses getPath()
102 * @uses isDebugEnabled()
103 * @uses renderDebugMessages()
104 */
105 public function render($displayData = array())
106 {
107 $this->clearDebugMessages();
108
109 // inherit base output from parent class
110 $layoutOutput = '';
111
112 // automatically merge any previously data set if $displayData is an array
113 if (is_array($displayData))
114 {
115 $displayData = array_merge($this->data, $displayData);
116 }
117
118 // check possible overrides, and build the full path to layout file
119 $path = $this->getPath();
120
121 if ($this->isDebugEnabled())
122 {
123 echo '<pre>' . $this->renderDebugMessages() . '</pre>';
124 }
125
126 // nothing to show
127 if (empty($path))
128 {
129 return $layoutOutput;
130 }
131
132 // start buffer
133 ob_start();
134 // include the file
135 include $path;
136 // push the buffer data in a variable
137 $layoutOutput = ob_get_contents();
138 // end buffer end clean
139 ob_end_clean();
140
141 return $layoutOutput;
142 }
143
144 /**
145 * Method to find the full real file path, checking possible overrides.
146 *
147 * @return string The full path to the layout file.
148 *
149 * @uses getLayoutId()
150 * @uses getIncludePaths()
151 * @uses addDebugMessage()
152 */
153 protected function getPath()
154 {
155 $layoutId = $this->getLayoutId();
156 $includePaths = $this->getIncludePaths();
157
158 $this->addDebugMessage('<strong>Layout:</strong> ' . $this->layoutId);
159
160 // make sure we have something to render
161 if (!$layoutId)
162 {
163 $this->addDebugMessage('<strong>There is no active layout</strong>');
164
165 return;
166 }
167
168 // make sure some paths have been specified
169 if (!$includePaths)
170 {
171 $this->addDebugMessage('<strong>There are no folders to search for layouts:</strong> ' . $layoutId);
172
173 return;
174 }
175
176 // create signature key
177 $hash = md5(json_encode($includePaths));
178
179 // check if the same layout has been already cached
180 if (!empty(static::$cache[$layoutId][$hash]))
181 {
182 $this->addDebugMessage('<strong>Cached path:</strong> ' . static::$cache[$layoutId][$hash]);
183
184 return static::$cache[$layoutId][$hash];
185 }
186
187 $this->addDebugMessage('<strong>Include Paths:</strong> ' . print_r($includePaths, true));
188
189 // standard version
190 $rawPath = str_replace('.', '/', $this->layoutId) . '.php';
191 $this->addDebugMessage('<strong>Searching layout for:</strong> ' . $rawPath);
192
193 // search for a layout file
194 $foundLayout = JPath::find($this->includePaths, $rawPath);
195
196 if (!$foundLayout)
197 {
198 // impossible to find any layouts
199 $this->addDebugMessage('<strong>Unable to find layout: </strong> ' . $layoutId);
200
201 return;
202 }
203
204 $this->addDebugMessage('<strong>Found layout:</strong> ' . $foundLayout);
205
206 // cache the layout found for later uses
207 static::$cache[$layoutId][$hash] = $foundLayout;
208
209 return static::$cache[$layoutId][$hash];
210 }
211
212 /**
213 * Adds one path to include in layout search.
214 * Proxy of addIncludePaths().
215 *
216 * @param string $path The path to search for layouts.
217 *
218 * @return self This object to support chaining.
219 *
220 * @since 10.1.18
221 *
222 * @uses addIncludePaths()
223 */
224 public function addIncludePath($path)
225 {
226 $this->addIncludePaths($path);
227
228 return $this;
229 }
230
231 /**
232 * Adds one or more paths to include in layout search.
233 *
234 * @param mixed $paths The path or array of paths to search for layouts.
235 *
236 * @return self This object to support chaining.
237 *
238 * @since 10.1.18
239 *
240 * @uses getIncludePaths()
241 * @uses setIncludePaths()
242 */
243 public function addIncludePaths($paths)
244 {
245 if (empty($paths))
246 {
247 return $this;
248 }
249
250 $includePaths = $this->getIncludePaths();
251
252 // in case the path is an array, merge all the paths and make sure we have no duplicated
253 if (is_array($paths))
254 {
255 $includePaths = array_unique(array_merge($paths, $includePaths));
256 }
257 // otherwise add the path as first element
258 else
259 {
260 array_unshift($includePaths, $paths);
261 }
262
263 // update include paths
264 $this->setIncludePaths($includePaths);
265
266 return $this;
267 }
268
269 /**
270 * Clears the include paths.
271 *
272 * @return self This object to support chaining.
273 *
274 * @since 10.1.18
275 */
276 public function clearIncludePaths()
277 {
278 $this->includePaths = array();
279
280 return $this;
281 }
282
283 /**
284 * Gets the active include paths.
285 *
286 * @return array
287 *
288 * @since 10.1.18
289 *
290 * @uses getDefaultIncludePaths()
291 */
292 public function getIncludePaths()
293 {
294 if (empty($this->includePaths))
295 {
296 $this->includePaths = $this->getDefaultIncludePaths();
297 }
298
299 return $this->includePaths;
300 }
301
302 /**
303 * Gets the active layout id.
304 *
305 * @return string
306 *
307 * @since 10.1.18
308 */
309 public function getLayoutId()
310 {
311 return $this->layoutId;
312 }
313
314 /**
315 * Removes one path from the layout search.
316 * Proxy of removeIncludePaths().
317 *
318 * @param string $path The path to remove from the layout search.
319 *
320 * @return self This object to support chaining.
321 *
322 * @since 10.1.18
323 *
324 * @uses removeIncludePaths()
325 */
326 public function removeIncludePath($path)
327 {
328 $this->removeIncludePaths($path);
329
330 return $this;
331 }
332
333 /**
334 * Removes one or more paths to exclude in layout search.
335 *
336 * @param mixed $paths The path or array of paths to remove for the layout search.
337 *
338 * @return self This object to support chaining.
339 *
340 * @since 10.1.18
341 */
342 public function removeIncludePaths($paths)
343 {
344 if (!empty($paths))
345 {
346 // always use an array of paths
347 $paths = (array) $paths;
348
349 // obtain a list of paths by excluding the specified ones
350 $this->includePaths = array_diff($this->includePaths, $paths);
351 }
352
353 return $this;
354 }
355
356 /**
357 * Validates that the active component is valid.
358 *
359 * @param string $option URL Option of the component (e.g. com_xxx).
360 *
361 * @return boolean True if valid, false otherwise.
362 *
363 * @since 10.1.18
364 */
365 protected function validComponent($option = null)
366 {
367 // by default we will validate the active component
368 $component = ($option !== null) ? $option : $this->options->get('component', null);
369
370 // validate option format
371 return !empty($component) && preg_match("/^com_/", $component);
372 }
373
374 /**
375 * Method to change the component where search for layouts.
376 *
377 * @param string $option URL Option of the component (e.g. com_xxx).
378 *
379 * @return void
380 *
381 * @since 10.1.18
382 *
383 * @uses validComponent()
384 * @uses clearIncludePaths()
385 */
386 public function setComponent($option)
387 {
388 $component = null;
389
390 switch ((string) $option)
391 {
392 case 'none':
393 $component = null;
394 break;
395
396 case 'auto':
397 // recover component name from request
398 $component = JFactory::getApplication()->input->get('option', null);
399 break;
400
401 default:
402 $component = $option;
403 break;
404 }
405
406 // extra checks
407 if (!$this->validComponent($component))
408 {
409 $component = null;
410 }
411 else
412 {
413 $component = preg_replace("/^com_/", '', $component);
414 }
415
416 // update component option
417 $this->options->set('component', $component);
418
419 // refresh include paths
420 $this->clearIncludePaths();
421 }
422
423 /**
424 * Function to initialise the application client.
425 *
426 * @param mixed $client The application client:
427 * - 0 or 'site': front-end;
428 * - 1 or 'admin': back-end.
429 *
430 * @return void
431 *
432 * @since 10.1.18
433 *
434 * @uses clearIncludePaths()
435 */
436 public function setClient($client)
437 {
438 // force string conversion to avoid unexpected states
439 switch ((string) $client)
440 {
441 case 'site':
442 case '0':
443 $client = 0;
444 break;
445
446 case 'admin':
447 case '1':
448 $client = 1;
449 break;
450
451 default:
452 $client = (int) JFactory::getApplication()->isAdmin();
453 break;
454 }
455
456 // update client option
457 $this->options->set('client', $client);
458
459 // refresh include paths
460 $this->clearIncludePaths();
461 }
462
463 /**
464 * Sets the active layout id.
465 *
466 * @param string $layoutId Layout identifier.
467 *
468 * @return self This object to support chaining.
469 *
470 * @since 10.1.18
471 */
472 public function setLayoutId($layoutId)
473 {
474 $this->layoutId = $layoutId;
475 $this->fullPath = null;
476
477 return $this;
478 }
479
480 /**
481 * Gets the default array of include paths.
482 *
483 * @return array
484 *
485 * @since 10.1.18
486 */
487 public function getDefaultIncludePaths()
488 {
489 $paths = array();
490
491 // (1 - highest priority) Received a custom high priority path
492 if ($this->basePath !== null)
493 {
494 $paths[] = rtrim($this->basePath, DIRECTORY_SEPARATOR);
495 }
496
497 // component layouts & overrides if exist
498 $component = $this->options->get('component', null);
499
500 if (!empty($component))
501 {
502 // get upload dir
503 $uploads = wp_upload_dir();
504
505 if ($this->options->get('client') == 0)
506 {
507 // (2) build component override site layouts path
508 $paths[] = rtrim($uploads['basedir'], DIRECTORY_SEPARATOR) . '/' . $component . '/layouts/site';
509
510 // (3) build component site layouts path
511 $paths[] = WP_PLUGIN_DIR . '/' . $component . '/site/layouts';
512 }
513 else
514 {
515 // (2) build component override admin layouts path
516 $paths[] = rtrim($uploads['basedir'], DIRECTORY_SEPARATOR) . '/' . $component . '/layouts/admin';
517
518 // (3) build component admin layouts path
519 $paths[] = WP_PLUGIN_DIR . '/' . $component . '/admin/layouts';
520 }
521
522 // (4) build libraries layouts path
523 $paths[] = WP_PLUGIN_DIR . '/' . $component . '/libraries';
524 }
525
526 return $paths;
527 }
528
529 /**
530 * Sets the include paths to search for layouts.
531 *
532 * @param array $paths Array with paths to search in.
533 *
534 * @return self This object to support chaining.
535 *
536 * @since 10.1.18
537 */
538 public function setIncludePaths($paths)
539 {
540 $this->includePaths = (array) $paths;
541
542 return $this;
543 }
544
545 /**
546 * Render a layout with the same include paths & options.
547 *
548 * @param string $layoutId The identifier for the sublayout to be searched in a
549 * subfolder with the name of the current layout.
550 * @param mixed $displayData Data to be rendered.
551 *
552 * @return string The necessary HTML to display the layout.
553 *
554 * @since 10.1.18
555 */
556 public function sublayout($layoutId, $displayData = array())
557 {
558 // sublayouts are searched in a subfolder with the name of the current layout
559 if (!empty($this->layoutId))
560 {
561 $layoutId = $this->layoutId . '.' . $layoutId;
562 }
563
564 // instantiate new layout file
565 $sublayout = new static($layoutId, $this->basePath, $this->options);
566 $sublayout->includePaths = $this->includePaths;
567
568 // render sublayout
569 return $sublayout->render($displayData);
570 }
571 }
572