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 / application / document.php
vikappointments / libraries / adapter / application Last commit date
application.php 1 month ago document.php 1 month ago object.php 1 month ago registry.php 1 month ago route.php 1 month ago version.php 1 month ago
document.php
666 lines
1 <?php
2 /**
3 * @package VikWP - Libraries
4 * @subpackage adapter.application
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 /**
15 * Document class, provides an easy interface to parse and display a document
16 * using the Joomla standard functions.
17 *
18 * @since 10.0
19 */
20 class JDocument
21 {
22 /**
23 * An array to cache the meta data set using this class.
24 *
25 * @var array
26 */
27 protected $data = [];
28
29 /**
30 * A list containing all the style declarations loaded.
31 *
32 * @var array
33 */
34 protected $styleDeclarations = [];
35
36 /**
37 * Array of scripts options.
38 *
39 * @var array
40 * @since 10.1.14
41 */
42 protected $scriptOptions = [];
43
44 /**
45 * Array of scripts declarations to be
46 * appended after the body.
47 *
48 * @var array
49 * @since 10.1.29
50 */
51 protected $ajaxScripts = [];
52
53 /**
54 * Adds the possibility to override the callback used to register
55 * something within the head.
56 *
57 * @param callable
58 * @since 10.1.51
59 */
60 public $attachToHeadCustomCallback = null;
61
62 /**
63 * Sets a meta tag in the front-end only.
64 *
65 * @param string $name Name of the meta HTML tag.
66 * @param mixed $content Value of the meta HTML tag as array or string.
67 * @param string $attribute Attribute to use in the meta HTML tag.
68 *
69 * @return self This object to support chaining.
70 *
71 * @since 10.1.66 Robots are now treated differently.
72 *
73 * @uses attachToHead()
74 */
75 public function setMetaData($name, $content, $attribute = 'name')
76 {
77 if ($name === 'robots')
78 {
79 add_filter('wp_robots', function($robots) use ($content) {
80 if (!$content)
81 {
82 return $robots;
83 }
84
85 $robots = [];
86
87 // obtain all the directives from the content (separated by a comma)
88 $directives = preg_split("/\s*,\s*/", $content);
89
90 foreach ($directives as $directive)
91 {
92 // obtain key-val pairs (separated by a colon)
93 $directive = preg_split("/\s*:\s*/", $directive);
94
95 $key = strtolower(array_shift($directive));
96
97 if (count($directive) == 0)
98 {
99 // attribute only, use true as value
100 $val = true;
101 }
102 else
103 {
104 $val = implode(':', $directive);
105 }
106
107 $robots[$key] = $val;
108 }
109
110 return $robots;
111 });
112 }
113 else
114 {
115 $attribute = empty($attribute) || !is_string($attribute) ? 'name' : $attribute;
116
117 if (is_scalar($content))
118 {
119 $content = array($content);
120 }
121
122 $this->attachToHead(function() use ($name, $content, $attribute)
123 {
124 foreach ($content as $cont)
125 {
126 echo "<meta {$attribute}=\"{$name}\" content=\"" . esc_attr($cont) . "\" />\n";
127 }
128 }, true);
129 }
130
131 $this->data[$name] = $content;
132
133 return $this;
134 }
135
136 /**
137 * Gets a meta tag.
138 * Since Wordpress doesn't own a system to handle meta data,
139 * we can only return a cached version of the data set using this class.
140 *
141 * @param string $name Name of the meta HTML tag.
142 *
143 * @return string
144 */
145 public function getMetaData($name)
146 {
147 if (isset($this->data[$name]))
148 {
149 return $this->data[$name];
150 }
151
152 return '';
153 }
154
155 /**
156 * Sets the title of the document in the front-end only.
157 *
158 * @param string $title The title to be set.
159 *
160 * @return self This object to support chaining.
161 *
162 * @link https://developer.wordpress.org/reference/hooks/wp_title/ (Wordpress < 4.4)
163 * @link https://developer.wordpress.org/reference/functions/wp_get_document_title/ (Wordpress 4.4+)
164 */
165 public function setTitle($title)
166 {
167 /**
168 * We should use the hook 'pre_get_document_title' (available from WP >= v4.4),
169 * instead of 'wp_title', or the title won't actually be set.
170 *
171 * @since 10.1.23
172 */
173 add_filter('pre_get_document_title', function() use ($title)
174 {
175 return $title;
176 });
177
178 return $this;
179 }
180
181 /**
182 * Return the title of the document.
183 *
184 * @return string
185 *
186 * @link https://developer.wordpress.org/reference/hooks/wp_title/ (Wordpress < 4.4)
187 * @link https://developer.wordpress.org/reference/functions/wp_get_document_title/ (Wordpress 4.4+)
188 */
189 public function getTitle()
190 {
191 global $wp_version;
192
193 if (version_compare($wp_version, '4.4', '>='))
194 {
195 return wp_get_document_title();
196 }
197
198 return wp_title('&raquo;', false);
199 }
200
201 /**
202 * Sets the description of the document.
203 *
204 * @param string $desc The description to be set.
205 *
206 * @return self This object to support chaining.
207 *
208 * @uses setMetaData()
209 */
210 public function setDescription($desc)
211 {
212 return $this->setMetaData('description', (string) $desc);
213 }
214
215 /**
216 * Return the description of the document.
217 *
218 * @return string
219 *
220 * @uses getMetaData()
221 */
222 public function getDescription()
223 {
224 return $this->getMetaData('description');
225 }
226
227 /**
228 * Adds a linked script to the page.
229 *
230 * @param string $url URL to the linked script.
231 * @param array $options Array of options. Example: array('version' => 'auto', 'conditional' => 'lt IE 9')
232 * @param array $attribs Array of attributes. Example: array('id' => 'scriptid', 'async' => 'async', 'data-test' => 1)
233 *
234 * @return self This object to support chaining.
235 *
236 * @link https://developer.wordpress.org/reference/functions/wp_register_script/
237 */
238 public function addScript($url, $options = [], $attribs = [])
239 {
240 // check if the script should be loaded using WP native libs
241 $version = array_key_exists('version', $options) ? $options['version'] : null;
242 $footer = array_key_exists('footer', $options) ? (bool) $options['footer'] : false;
243 $id = empty($attribs['id']) ? md5($url) : $attribs['id'];
244
245 /**
246 * Use filter to approve/deny the loading of the given script.
247 *
248 * @param boolean $load The recursive filtered value (default 'true').
249 * @param string $url The resource URL.
250 * @param string $id The script ID attribute.
251 * @param string $version The script version, if specified.
252 * @param boolean $footer True whether the script is going to be loaded in the footer.
253 *
254 * @return boolean True to load the resource, false to ignore it.
255 *
256 * @since 10.1.25
257 */
258 $load = apply_filters('vik_before_include_script', true, $url, $id, $version, $footer);
259
260 if ($load)
261 {
262 // make sure this is not an AJAX call
263 if (!wp_doing_ajax())
264 {
265 // if the headers have been sent, the script must be registered in the footer.
266 if (headers_sent())
267 {
268 $footer = true;
269 }
270
271 // the default array of dependencies
272 $deps = [
273 'jquery-core',
274 'jquery-ui-core',
275 ];
276
277 /**
278 * Added support to custom dependencies.
279 *
280 * @since 10.1.38
281 */
282 if (!empty($options['dependencies']))
283 {
284 // join default dependencies with the given ones
285 $deps = array_merge($deps, (array) $options['dependencies']);
286 // get rid of duplicates
287 $deps = array_values(array_unique($deps));
288 }
289
290 // loads scripts always after jQuery Core (included by Wordpress)
291 wp_register_script($id, $url, $deps, $version, $footer);
292 wp_enqueue_script($id);
293 }
294 // since the footer is already printed, we need to each our script directly
295 else if ($url)
296 {
297 if ($version)
298 {
299 $url .= '?ver=' . $version;
300 }
301
302 echo '<script type="text/javascript" src="' . $url . '"></script>';
303 }
304 }
305
306 return $this;
307 }
308
309 /**
310 * Adds a script to the page.
311 *
312 * @param string $content Script snippet.
313 * @param string $type Scripting mime (defaults to 'text/javascript').
314 *
315 * @return self This object to support chaining.
316 *
317 * @uses attachToHead()
318 */
319 public function addScriptDeclaration($content, $type = 'text/javascript')
320 {
321 /**
322 * Always use the script declaration without checking
323 * if it has been already loaded.
324 *
325 * @since 10.1.17
326 */
327 $this->attachToHead(function() use ($content, $type)
328 {
329 /**
330 * Use "joomla-options new" class in case we are attaching a JSON string.
331 * The "new" word means that the options has to be loaded.
332 *
333 * @since 10.1.14
334 */
335 $class = $type == 'application/json' ? ' class="joomla-options new"' : '';
336
337 echo "<script type=\"{$type}\"{$class}>\n{$content}\n</script>\n";
338 });
339
340 return $this;
341 }
342
343 /**
344 * Adds a linked stylesheet to the page.
345 *
346 * @param string $url URL to the linked style sheet.
347 * @param array $options Array of options. Example: array('version' => 'auto', 'conditional' => 'lt IE 9')
348 * @param array $attribs Array of attributes. Example: array('id' => 'stylesheet', 'data-test' => 1)
349 *
350 * @return self This object to support chaining.
351 *
352 * @link https://codex.wordpress.org/Function_Reference/wp_register_style
353 */
354 public function addStyleSheet($url, $options = [], $attribs = [])
355 {
356 $version = array_key_exists('version', $options) ? $options['version'] : null;
357 $media = array_key_exists('media', $options) ? (string) $options['media'] : 'all';
358 $id = empty($attribs['id']) ? md5($url) : $attribs['id'];
359
360 /**
361 * Use filter to approve/deny the loading of the given stylesheet.
362 *
363 * @param boolean $load The recursive filtered value (default 'true').
364 * @param string $url The resource URL.
365 * @param string $id The stylesheet ID attribute.
366 * @param string $version The stylesheet version, if specified.
367 *
368 * @return boolean True to load the resource, false to ignore it.
369 *
370 * @since 10.1.25
371 */
372 $load = apply_filters('vik_before_include_style', true, $url, $id, $version);
373
374 if ($load)
375 {
376 // attach the style to the <head> only if the headers haven't been sent
377 // and if we are not doing an AJAX call
378 if (!headers_sent() && !wp_doing_ajax())
379 {
380 $deps = [];
381
382 /**
383 * Added support to custom dependencies.
384 *
385 * @since 10.1.38
386 */
387 if (!empty($options['dependencies']))
388 {
389 // join default dependencies with the given ones
390 $deps = array_merge($deps, (array) $options['dependencies']);
391 // get rid of duplicates
392 $deps = array_values(array_unique($deps));
393 }
394
395 wp_register_style($id, $url, $deps, $version, $media);
396 wp_enqueue_style($id);
397 }
398 // otherwise print the style in the document <body>
399 else
400 {
401 if ($version)
402 {
403 $url .= '?ver=' . $version;
404 }
405
406 echo '<link rel="stylesheet" id="' . $id . '" href="' . $url . '" type="text/css" media="' . $media . '">';
407 }
408 }
409
410 return $this;
411 }
412
413 /**
414 * Adds a stylesheet declaration to the page.
415 *
416 * @param string $content Style declaration.
417 * @param string $type Type of stylesheet (defaults to 'text/css').
418 *
419 * @return self This object to support chaining.
420 *
421 * @uses attachToHead()
422 */
423 public function addStyleDeclaration($content, $type = 'text/css')
424 {
425 if (!in_array($content, $this->styleDeclarations))
426 {
427 $this->attachToHead(function() use ($content, $type)
428 {
429 echo "<style type=\"{$type}\">\n{$content}\n</style>\n";
430 });
431
432 $this->styleDeclarations[] = $content;
433 }
434
435 return $this;
436 }
437
438 /**
439 * Internal method execute a callback within the <head> tags.
440 * The callback will be attached to the wp_head or admin_head hooks
441 * depending on the section we are currently using.
442 *
443 * @param mixed $callback The callback or the function name to attach.
444 * @param boolean $frontOnly True to attach the method only in the front-end.
445 *
446 * @return void
447 *
448 * @link https://codex.wordpress.org/Plugin_API/Action_Reference/wp_head
449 * @link https://codex.wordpress.org/Plugin_API/Action_Reference/admin_head
450 * @link https://developer.wordpress.org/reference/hooks/wp_print_footer_scripts/
451 * @link https://developer.wordpress.org/reference/hooks/admin_print_footer_scripts/
452 */
453 protected function attachToHead($callback, $frontOnly = false)
454 {
455 $app = JFactory::getApplication();
456
457 // make sure we are in the front-end or the back-end is allowed
458 if ($app->isSite() || !$frontOnly)
459 {
460 /**
461 * If provided, use the custom callback instead.
462 *
463 * @since 10.1.51
464 */
465 if (is_callable($this->attachToHeadCustomCallback))
466 {
467 // invoke callback and teminate
468 return call_user_func_array($this->attachToHeadCustomCallback, [$callback]);
469 }
470
471 // make sure we are not doing an AJAX call
472 if (!wp_doing_ajax())
473 {
474 $head_hook = $app->isAdmin() ? 'admin_head' : 'wp_head';
475
476 // make sure that the head hook that we are using haven't been called yet,
477 // otherwise our script will never be executed
478 if (!headers_sent() && !did_action($head_hook))
479 {
480 // admin_head hook for the back-end <head>
481 // wp_head hook for the front-end <head>
482 add_action($head_hook, $callback);
483 }
484 else
485 {
486 // admin_print_footer_scripts hook for the back-end <footer>
487 // wp_footer wp_print_footer_scripts for the front-end <footer>
488 add_action($app->isAdmin() ? 'admin_print_footer_scripts' : 'wp_print_footer_scripts', $callback);
489
490 /**
491 * NOTE: do not use 'wp_footer' hook because it prints the script declarations
492 * always before the <script> tags, causing errors for missing resources.
493 */
494 }
495 }
496 // we need to invoke our callback directly
497 else
498 {
499 // push scripts within the AJAX callbacks
500 $this->ajaxScripts[] = $callback;
501 }
502 }
503 }
504
505 /**
506 * Returns the AJAX scripts that should be included after
507 * the body in order to properly access all the needed elements.
508 *
509 * @return string
510 *
511 * @since 10.1.29
512 */
513 public function getAjaxScripts()
514 {
515 // start buffering
516 ob_start();
517
518 // iterate registered scripts
519 foreach ($this->ajaxScripts as $callback)
520 {
521 // invoke the callback to print the script
522 call_user_func($callback);
523 }
524
525 // catch buffer
526 $js = ob_get_contents();
527 // clear buffer
528 ob_end_clean();
529
530 // empty list
531 $this->ajaxScripts = [];
532
533 return $js;
534 }
535
536 /**
537 * Add options for script.
538 *
539 * @param string $key Name in Storage.
540 * @param mixed $options Scrip options as array or string.
541 * @param boolean $merge Whether merge with existing (true) or replace (false).
542 *
543 * @return self This object to support chaining.
544 *
545 * @since 10.1.14
546 */
547 public function addScriptOptions($key, $options, $merge = true)
548 {
549 if (empty($this->scriptOptions[$key]))
550 {
551 $this->scriptOptions[$key] = [];
552 }
553
554 if ($merge && is_array($options))
555 {
556 $this->scriptOptions[$key] = array_replace_recursive($this->scriptOptions[$key], $options);
557 }
558 else
559 {
560 $this->scriptOptions[$key] = $options;
561 }
562
563 return $this;
564 }
565
566 /**
567 * Get script(s) options.
568 *
569 * @param string $key Name in Storage.
570 *
571 * @return array Options for given $key, or all script options.
572 *
573 * @since 10.1.14
574 */
575 public function getScriptOptions($key = null)
576 {
577 if ($key)
578 {
579 return (empty($this->scriptOptions[$key])) ? [] : $this->scriptOptions[$key];
580 }
581 else
582 {
583 return $this->scriptOptions;
584 }
585 }
586
587 /**
588 * Returns the document charset encoding.
589 *
590 * @return string
591 *
592 * @since 10.1.20
593 */
594 public function getCharset()
595 {
596 $output = get_option('blog_charset');
597
598 if (!$output)
599 {
600 $output = 'UTF-8';
601 }
602
603 return $output;
604 }
605
606 /**
607 * Returns the document language.
608 *
609 * @return string
610 *
611 * @since 10.1.20
612 */
613 public function getLanguage()
614 {
615 return strtolower(JFactory::getLanguage()->getTag());
616 }
617
618 /**
619 * Returns the document direction declaration.
620 *
621 * @return string
622 *
623 * @since 10.1.20
624 */
625 public function getDirection()
626 {
627 if (function_exists('is_rtl'))
628 {
629 $output = is_rtl() ? 'rtl' : 'ltr';
630 }
631 else
632 {
633 $output = 'ltr';
634 }
635
636 return $output;
637 }
638
639 /**
640 * Adds `<link>` tags to the head of the document.
641 *
642 * $relType defaults to 'rel' as it is the most common relation type used
643 * ('rev' refers to reverse relation, 'rel' indicates normal, forward relation).
644 * Typical tag: `<link href="index.php" rel="Start">`.
645 *
646 * @param string $href The link that is being related.
647 * @param string $relation Relation of link.
648 * @param string $relType Relation type attribute. Either rel or rev (default: 'rel').
649 * @param array $attribs Associative array of remaining attributes.
650 *
651 * @return self This object to support chaining.
652 *
653 * @since 10.1.48
654 */
655 public function addHeadLink(string $href, string $relation, string $relType = 'rel', array $attribs = [])
656 {
657 $this->attachToHead(function() use ($href, $relation, $relType, $attribs)
658 {
659 JLoader::import('adapter.utilities.array');
660 echo "<link href=\"{$href}\" {$relType}=\"$relation\" " . ArrayHelper::toString($attribs) . " />\n";
661 });
662
663 return $this;
664 }
665 }
666