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 / site / helpers / libraries / html / assets.php
vikappointments / site / helpers / libraries / html Last commit date
assets.php 1 month ago color.php 1 month ago countries.php 1 month ago index.html 1 month ago media.php 1 month ago site.php 1 month ago sitescripts.php 1 month ago status.php 1 month ago vikappointments.php 1 month ago
assets.php
682 lines
1 <?php
2 /**
3 * @package VikAppointments
4 * @subpackage core
5 * @author E4J s.r.l.
6 * @copyright Copyright (C) 2021 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 * VikAppointments HTML assets loader.
16 *
17 * @since 1.7
18 */
19 abstract class VAPHtmlAssets
20 {
21 /**
22 * A cache pool to keep track of the loaded resources.
23 *
24 * @var array
25 */
26 protected static $cache = [];
27
28 /**
29 * Includes the resources to use Fancybox jQuery add-on.
30 *
31 * @param mixed $script True to include the helper script too.
32 * If not specified, it will be included
33 * only for the back-end.
34 *
35 * @return void
36 *
37 * @link https://fancyapps.com/fancybox/3/
38 */
39 public static function fancybox($script = null)
40 {
41 if (is_null($script))
42 {
43 $script = JFactory::getApplication()->isClient('administrator');
44 }
45
46 $vik = VAPApplication::getInstance();
47
48 // check if already loaded
49 if (!static::isLoaded(__METHOD__))
50 {
51 // include resources
52 $vik->addScript(VAPASSETS_URI . 'js/jquery.fancybox.js', ['version' => '3.5.7']);
53 $vik->addStylesheet(VAPASSETS_URI . 'css/jquery.fancybox.css', ['version' => '3.5.7']);
54 }
55
56 // check if script is already loaded
57 if ($script && !static::isLoaded(__METHOD__, $script))
58 {
59 // include script too
60 $vik->addScript(VAPASSETS_URI . 'js/vikappointments.js');
61 }
62 }
63
64 /**
65 * Includes the resources to use Select2 jQuery add-on.
66 *
67 * @return void
68 *
69 * @link http://select2.github.io/select2/
70 */
71 public static function select2()
72 {
73 // check if already loaded
74 if (static::isLoaded(__METHOD__))
75 {
76 return;
77 }
78
79 $vik = VAPApplication::getInstance();
80
81 // include resources
82 $vik->addScript(VAPASSETS_URI . 'js/select2/select2.min.js', ['version' => '3.5.1']);
83 $vik->addStylesheet(VAPASSETS_URI . 'js/select2/select2.css', ['version' => '3.5.1']);
84
85 /**
86 * Try to load a specific language to translate native texts.
87 * DO NOT load all the translations as Select2 simply overrides
88 * the methods used to return the texts.
89 *
90 * @since 1.6.5
91 */
92 $tag = explode('-', JFactory::getLanguage()->getTag());
93
94 // fetch locale based on current Joomla! language
95 $locale = 'select2_locale_' . strtolower($tag[0]) . '.js';
96
97 // make sure the locale exists
98 $path = implode(DIRECTORY_SEPARATOR, [VAPBASE, 'assets', 'js', 'select2', 'i18n', $locale]);
99
100 if (is_file($path))
101 {
102 // load locale script after Select2 Core file
103 $vik->addScript(VAPASSETS_URI . 'js/select2/i18n/' . $locale, ['version' => '3.5.1']);
104 }
105 }
106
107 /**
108 * Includes the resources to use Google Maps APIs.
109 *
110 * @param string $key The Google API Key. Use an empty string
111 * to load the script without API Key.
112 *
113 * @return void
114 *
115 * @link https://developers.google.com/maps/documentation
116 */
117 public static function googlemaps($key = null, $lib = null)
118 {
119 // check if already loaded
120 if (static::isLoaded(__METHOD__))
121 {
122 return;
123 }
124
125 $vik = VAPApplication::getInstance();
126
127 if (is_null($key))
128 {
129 // API Key not specified, recover it from config
130 $key = VAPFactory::getConfig()->get('googleapikey');
131
132 if (!$key)
133 {
134 // The administrator didn't specify a Google API Key.
135 // Since the "Missing API Key" error doesn't trigger
136 // the gm_authFailure() function, we should use a random
137 // string just to be able to catch the error, which
138 // would let us to unset the events registered by Google,
139 // such as the "Auto-complete" field (Places API).
140 $key = md5($_SERVER['REMOTE_ADDR']);
141 }
142 }
143
144 $args = [];
145
146 if ($key)
147 {
148 // specify key only if not empty
149 $args['key'] = $key;
150 }
151
152 if ($lib)
153 {
154 // loads any additional library
155 $args['libraries'] = $lib;
156 }
157
158 $uri = 'https://maps.googleapis.com/maps/api/js' . ($args ? '?' . http_build_query($args) : '');
159
160 // include script
161 $vik->addScript($uri, ['version' => 'auto']);
162 }
163
164 /**
165 * Includes the resources to use FontAwesome.
166 *
167 * @return void
168 *
169 * @link http://fontawesome.com/
170 */
171 public static function fontawesome()
172 {
173 // check if already loaded
174 if (static::isLoaded(__METHOD__))
175 {
176 return;
177 }
178
179 if (JFactory::getApplication()->isClient('administrator') && VersionListener::isJoomla50())
180 {
181 // do not load FontAwesome if we are in the administrator of a Joomla! 5.0 instance
182 return;
183 }
184
185 $vik = VAPApplication::getInstance();
186
187 // include resources
188 $vik->addStylesheet(VAPASSETS_URI . 'css/fontawesome/css/font-awesome.min.css', ['version' => '5.15.3']);
189 $vik->addStylesheet(VAPASSETS_URI . 'css/fontawesome/css/solid.min.css', ['version' => '5.15.3']);
190 $vik->addStylesheet(VAPASSETS_URI . 'css/fontawesome/css/regular.min.css', ['version' => '5.15.3']);
191 $vik->addStylesheet(VAPASSETS_URI . 'css/fontawesome/css/brands.min.css', ['version' => '5.15.3']);
192 }
193
194 /**
195 * Includes the resources to use Chart JS.
196 *
197 * @return void
198 *
199 * @link https://www.chartjs.org
200 */
201 public static function chartjs()
202 {
203 // check if already loaded
204 if (static::isLoaded(__METHOD__))
205 {
206 return;
207 }
208
209 $vik = VAPApplication::getInstance();
210
211 // include resources
212 $vik->addScript(VAPASSETS_URI . 'js/charts-framework/Chart.min.js', ['version' => '2.9.4']);
213 }
214
215 /**
216 * Loads the scripts needed to use Colorpicker jQuery plugin.
217 *
218 * @return void
219 *
220 * @link https://www.eyecon.ro/colorpicker/
221 */
222 public static function colorpicker()
223 {
224 // check if already loaded
225 if (static::isLoaded(__METHOD__))
226 {
227 return;
228 }
229
230 $vik = VAPApplication::getInstance();
231
232 $vik->addScript(VAPASSETS_URI . 'js/colorpicker/colorpicker.js');
233 $vik->addScript(VAPASSETS_URI . 'js/colorpicker/eye.js');
234 $vik->addScript(VAPASSETS_URI . 'js/colorpicker/utils.js');
235
236 $vik->addStyleSheet(VAPASSETS_URI . 'css/colorpicker/colorpicker.css');
237 }
238
239 /**
240 * Includes utils resources of the framework.
241 *
242 * @return void
243 */
244 public static function utils()
245 {
246 // check if already loaded
247 if (static::isLoaded(__METHOD__))
248 {
249 return;
250 }
251
252 $vik = VAPApplication::getInstance();
253
254 // include resources
255 $vik->addScript(VAPASSETS_URI . 'js/utils.js');
256 }
257
258 /**
259 * Includes the resources to support a jQuery context menu.
260 *
261 * @return void
262 */
263 public static function contextmenu()
264 {
265 // check if already loaded
266 if (static::isLoaded(__METHOD__))
267 {
268 return;
269 }
270
271 $vik = VAPApplication::getInstance();
272
273 // include resources
274 $vik->addScript(VAPASSETS_URI . 'js/contextmenu.js');
275 $vik->addStylesheet(VAPASSETS_URI . 'css/contextmenu.css');
276 }
277
278 /**
279 * Includes the context menu that allows the
280 * possibility of selecting a status code.
281 *
282 * @return void
283 */
284 public static function statuscodes()
285 {
286 // check if already loaded
287 if (static::isLoaded(__METHOD__))
288 {
289 return;
290 }
291
292 // load jQuery context menu plugin
293 static::contextmenu();
294
295 $vik = VAPApplication::getInstance();
296
297 // include resources
298 $vik->addScript(VAPASSETS_URI . 'js/statuscodes.js');
299 }
300
301 /**
302 * Configures the Currency JS object.
303 *
304 * @return void
305 */
306 public static function currency()
307 {
308 // check if already loaded
309 if (static::isLoaded(__METHOD__))
310 {
311 return;
312 }
313
314 /**
315 * Manually load the utils to properly support the onInstanceReady function.
316 *
317 * @since 1.7.7
318 */
319 static::utils();
320
321 // load dependencies first
322 VAPApplication::getInstance()->addScript(VAPASSETS_URI . 'js/currency.js');
323
324 // get currency instance
325 $currency = VAPFactory::getCurrency();
326
327 $options = new stdClass;
328 $options->position = $currency->getPosition();
329 $options->separator = $currency->getDecimalMark();
330 $options->thousands = $currency->getThousandsMark();
331 $options->digits = $currency->getDecimalDigits();
332
333 /**
334 * The JS object now supports a conversion rate.
335 *
336 * @since 1.7.6
337 */
338 $options->conversionRatio = $currency->getConversionRate();
339
340 $symbol = $currency->getSymbol();
341 $options = json_encode($options);
342
343 JFactory::getDocument()->addScriptDeclaration(
344 <<<JS
345 (function($) {
346 'use strict';
347
348 $(function() {
349 onInstanceReady(() => {
350 return typeof VAPCurrency !== 'undefined';
351 }).then(() => {
352 VAPCurrency.getInstance('{$symbol}', {$options});
353 });
354 });
355 })(jQuery);
356 JS
357 );
358 }
359
360 /**
361 * Includes the resources needed to handle a TOAST message.
362 *
363 * @param string $position The position in which the toast will be displayed.
364 * @param string $container The container to which append the toast.
365 *
366 * @return void
367 */
368 public static function toast($position = null, $container = 'body')
369 {
370 $document = JFactory::getDocument();
371
372 // a string was passed
373 $config = '\'' . addslashes($position) . '\'';
374
375 if (empty($position))
376 {
377 // no specified position
378 $config = 'null';
379 }
380 else if (preg_match("/^[a-zA-Z0-9._]+$/i", $position))
381 {
382 // a variable was passed
383 $config = "typeof {$position} !== 'undefined' ? {$position} : $config";
384 }
385
386 $container = '\'' . addslashes($container) . '\'';
387
388 // check if already loaded
389 if (static::isLoaded(__METHOD__))
390 {
391 // always overwrite position
392 $document->addScriptDeclaration('jQuery(function() { VAPToast.changePosition(' . $config . '); });');
393 return;
394 }
395
396 $vik = VAPApplication::getInstance();
397
398 // include resources
399 $vik->addScript(VAPASSETS_URI . 'js/toast.js');
400 $vik->addStylesheet(VAPASSETS_URI . 'css/toast.css');
401
402 // create toast
403 $document->addScriptDeclaration('jQuery(function() { VAPToast.create(' . $config . ', ' . $container . '); });');
404 }
405
406 /**
407 * Includes the resources to use Intl-Tel jQuery add-on.
408 *
409 * @param string $selector All the elements matching the specified
410 * selector will be initialized using this
411 * jQuery add-on. Leave empty just to include
412 * the resources.
413 * @param array $config A configuration array.
414 *
415 * @return void
416 *
417 * @link https://github.com/jackocnr/intl-tel-input
418 */
419 public static function intltel($selector = null, array $config = [])
420 {
421 $document = JFactory::getDocument();
422
423 // check if already loaded
424 if (!static::isLoaded(__METHOD__))
425 {
426 $vik = VAPApplication::getInstance();
427
428 // include resources
429 $vik->addScript(VAPASSETS_URI . 'js/tel/jquery.intlTelInput.min.js');
430 $vik->addScript(VAPASSETS_URI . 'js/tel/utils.js');
431
432 $vik->addStylesheet(VAPASSETS_URI . 'css/tel/css/intlTelInput.min.css');
433
434 // decide whether to save the phone as plain number
435 // or human-readable
436 $save_plain = !empty($config['savePlain']) ? 1 : 0;
437
438 // fetch validator instance (use 'validator' variable if not specified)
439 $validator = !empty($config['validator']) ? $config['validator'] : 'validator';
440
441 // include global script
442 $document->addScriptDeclaration(
443 <<<JS
444 var vapIntlTelFormSubmit;
445 var vapIntlTelValidatorCallback;
446
447 jQuery(function() {
448 if (!vapIntlTelFormSubmit) {
449 // before submit the form, include the dial code within the input
450 vapIntlTelFormSubmit = function() {
451 // get all form input
452 jQuery(this).find('.iti input').filter('[type="text"],[type="tel"]').each(function() {
453 var input = jQuery(this);
454
455 if (input.val().length) {
456 if ({$save_plain}) {
457 input.val(input.intlTelInput('getNumber'));
458 } else if (!input.val().match(/^\+/)){
459 // prepend dial code only once
460 var country = input.intlTelInput('getSelectedCountryData');
461 input.val('+' + country.dialCode + ' ' + input.val());
462 }
463 }
464 });
465 };
466 }
467
468 // deregister event if already attached
469 jQuery('form').off('submit', vapIntlTelFormSubmit);
470 jQuery('form').on('submit', vapIntlTelFormSubmit);
471
472 // check if the specified validator variable exists and it is an instance of VikFormValidator
473 if (typeof {$validator} === 'object' && {$validator}.constructor.name === 'VikFormValidator') {
474 if (!vapIntlTelValidatorCallback) {
475 vapIntlTelValidatorCallback = function() {
476 var ok = true;
477
478 // get all form input
479 jQuery({$validator}.form).find('.iti input.required').filter('[type="text"],[type="tel"]').each(function() {
480 var input = jQuery(this);
481
482 // always return false in case the input is required and empty
483 if (input.val().length == 0 && input.hasClass('required')) {
484 {$validator}.setInvalid(input);
485 ok = false;
486
487 // return true to continue
488 return true;
489 }
490
491 // in case the input is not empty, check if the specified number is valid
492 if (input.val().length && !input.intlTelInput('isValidNumber')) {
493 // invalid number
494 {$validator}.setInvalid(input);
495 ok = false;
496
497 // return true to continue
498 return true;
499 }
500
501 // number is valid (or empty and optional)
502 {$validator}.unsetInvalid(input);
503 });
504
505 return ok;
506 };
507 }
508
509 // register validation callback
510 {$validator}.removeCallback(vapIntlTelValidatorCallback);
511 {$validator}.addCallback(vapIntlTelValidatorCallback);
512 }
513 });
514 JS
515 );
516 }
517
518 // check if already initialized
519 if (!$selector || static::isLoaded(__METHOD__, $selector))
520 {
521 return;
522 }
523
524 $data = isset($config['data']) ? (array) $config['data'] : [];
525
526 if (!isset($data['preferredCountries']))
527 {
528 // fetch preferred countries
529 $data['preferredCountries'] = [];
530 // inject default country code
531 $data['preferredCountries'][] = strtolower(VAPCustomFields::getDefaultCountryCode());
532 // iterate all supported languages
533 foreach (VikAppointments::getKnownLanguages() as $lang)
534 {
535 if (preg_match("/-/", $lang))
536 {
537 // split language tag
538 list($reg, $country) = explode('-', $lang);
539 }
540 else
541 {
542 // we have a language tag without country code, such as "el"
543 $country = $lang;
544 }
545
546 // use as preferred country
547 $data['preferredCountries'][] = strtolower($country);
548 }
549
550 // make list unique
551 $data['preferredCountries'] = array_values(array_unique($data['preferredCountries']));
552 }
553
554 if (!isset($data['autoPlaceholder']))
555 {
556 // turn off default placeholder
557 $data['autoPlaceholder'] = 'off';
558 }
559
560 // encode JSON data to initialize the input
561 $json = json_encode(array_merge($data, $config));
562
563 // initialize selectors
564 $document->addScriptDeclaration(
565 <<<JS
566 jQuery(function() {
567 // create private function to handle auto-format
568 function updatePhoneInput(event) {
569 // Unbind this method from the input to avoid infinite loops.
570 // When typing a prefix the plugin changes the country and trigger
571 // the "countrychange" event, which re-call this method recursively.
572 jQuery(this).off('change countrychange', updatePhoneInput);
573
574 // format the phone number when the value or the country change
575 var number = jQuery(this).intlTelInput('getNumber');
576 jQuery(this).intlTelInput('setNumber', number);
577
578 // re-activate event once the "countrychange" event has been already triggered
579 jQuery(this).on('change countrychange', updatePhoneInput);
580 }
581
582 // always format phone when the number or the country change
583 jQuery('{$selector}').intlTelInput({$json}).on('change countrychange', updatePhoneInput);
584
585 // set maximum width of country dropdown
586 jQuery('{$selector}').on('open:countrydropdown', function() {
587 var dialog = jQuery(this).closest('.iti').find('.iti__country-list');
588
589 // reset max width
590 dialog.css('max-width', 'initial');
591
592 // make sure the dialog doesn't exceed the screen bounds
593 if (dialog.offset().left + dialog.outerWidth() > jQuery(window).width()) {
594 dialog.css('max-width', jQuery(this).outerWidth());
595 }
596 });
597 });
598 JS
599 );
600 }
601
602 /**
603 * Loads the environment CSS.
604 *
605 * @return void
606 *
607 * @since 1.7.2
608 */
609 public static function environment()
610 {
611 // check if already loaded
612 if (static::isLoaded(__METHOD__))
613 {
614 return;
615 }
616
617 $vik = VAPApplication::getInstance();
618
619 // create customizer model
620 $customizer = JModelVAP::getInstance('customizer');
621
622 // include the CSS file containing the environment variables
623 $vik->addStyleSheet($customizer->getEnvironmentUrl(), [
624 'version' => filemtime($customizer->getEnvironmentFile()),
625 ]);
626 }
627
628 /**
629 * Loads the custom CSS file.
630 *
631 * @return void
632 *
633 * @since 1.7.2
634 */
635 public static function customcss()
636 {
637 // check if already loaded
638 if (static::isLoaded(__METHOD__))
639 {
640 return;
641 }
642
643 $vik = VAPApplication::getInstance();
644
645 // build path of custom CSS file
646 $custom_css_path = VAPBASE . DIRECTORY_SEPARATOR . 'assets' . DIRECTORY_SEPARATOR . 'css' . DIRECTORY_SEPARATOR . 'vap-custom.css';
647
648 if (is_file($custom_css_path))
649 {
650 // include the CSS file containing the custom rules
651 $vik->addStyleSheet(VAPASSETS_URI . 'css/vap-custom.css', [
652 'version' => filemtime($custom_css_path),
653 ]);
654 }
655 }
656
657 /**
658 * Helper method used to check whether a resource was loaded or not.
659 * This method accepts any number of arguments, which will be used
660 * as signature for the cache.
661 *
662 * @return boolean True if already loaded, false otherwise.
663 */
664 protected static function isLoaded()
665 {
666 // create signature
667 $sign = md5(serialize(func_get_args()));
668
669 if (isset(static::$cache[$sign]))
670 {
671 // already loaded
672 return true;
673 }
674
675 // cache because it will be used after returning to the caller
676 static::$cache[$sign] = 1;
677
678 // not loaded yet
679 return false;
680 }
681 }
682