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 / adapter / platform / wordpress.php
vikappointments / site / helpers / libraries / adapter / platform Last commit date
index.html 1 month ago wordpress.php 1 month ago
wordpress.php
1464 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 // this should be already loaded from autoload.php
15 VAPLoader::import('libraries.adapter.version.listener');
16
17 /**
18 * Helper class used to adapt the application to the requirements
19 * of the installed WordPress version.
20 *
21 * @see VersionListener Used to evaluate the current WordPress version.
22 *
23 * @since 1.6.3
24 * @since 1.7 Renamed from UIApplicationWordpress
25 */
26 class VAPApplicationWordpress extends VAPApplication
27 {
28 /**
29 * Backward compatibility for WordPress admin list <table> class.
30 *
31 * @return string The class selector to use.
32 */
33 public function getAdminTableClass()
34 {
35 return 'wp-list-table widefat striped';
36 }
37
38 /**
39 * Backward compatibility for WordPress admin list <table> head opening.
40 *
41 * @return string The <thead> tag to use.
42 */
43 public function openTableHead()
44 {
45 return '<thead>';
46 }
47
48 /**
49 * Backward compatibility for WordPress admin list <table> head closing.
50 *
51 * @return string The </thead> tag to use.
52 */
53 public function closeTableHead()
54 {
55 return '</thead>';
56 }
57
58 /**
59 * Backward compatibility for WordPress admin list <th> class.
60 *
61 * @param string $align The additional class to use for horizontal alignment.
62 * Accepted rules should be: left, center or right.
63 *
64 * @return string The class selector to use.
65 */
66 public function getAdminThClass($align = 'center')
67 {
68 return 'manage-column ' . $align;
69 }
70
71 /**
72 * Backward compatibility for WordPress admin list checkAll JS event.
73 *
74 * @param integer The total count of rows in the table.
75 *
76 * @return string The check all checkbox input to use.
77 */
78 public function getAdminToggle($count)
79 {
80 return '<input type="checkbox" onclick="Joomla.checkAll(this)" value="" name="checkall-toggle" />';
81 }
82
83 /**
84 * Backward compatibility for WordPress admin list isChecked JS event.
85 *
86 * @return string The JS function to use.
87 */
88 public function checkboxOnClick()
89 {
90 return 'Joomla.isChecked(this.checked);';
91 }
92
93 /**
94 * Helper method to send e-mails.
95 *
96 * @param string $from_address The e-mail address of the sender.
97 * @param string $from_name The name of the sender.
98 * @param string $to The e-mail address of the receiver.
99 * @param string $reply_address The reply to e-mail address.
100 * @param string $subject The subject of the e-mail.
101 * @param string $hmess The body of the e-mail (HTML is supported).
102 * @param array $attachments The list of the attachments to include.
103 * @param boolean $is_html True to support HTML body, otherwise false for plain text.
104 * @param string $encoding The encoding to use.
105 *
106 * @return boolean True if the e-mail was sent successfully, otherwise false.
107 */
108 public function sendMail($from_address, $from_name, $to, $reply_address, $subject, $hmess, $attachments = null, $is_html = true, $encoding = 'base64')
109 {
110 $mailer = JFactory::getMailer();
111
112 if ($is_html)
113 {
114 $hmess = "<html>\n<head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"></head>\n<body>$hmess</body>\n</html>";
115 }
116
117 $sender = array($from_address, $from_name);
118 $mailer->setSender($sender);
119 $mailer->addRecipient($to);
120
121 if ($reply_address)
122 {
123 $mailer->addReplyTo($reply_address);
124 }
125
126 $mailer->setSubject($subject);
127 $mailer->setBody($hmess);
128 $mailer->isHTML($is_html);
129
130 $mailer->Encoding = $encoding;
131
132 if ($attachments !== null && is_array($attachments))
133 {
134 foreach ($attachments as $attach)
135 {
136 if (!empty($attach) && file_exists($attach))
137 {
138 $mailer->addAttachment($attach);
139 }
140 }
141 }
142
143 return $mailer->Send();
144 }
145
146 /**
147 * Backward compatibility for WordPress framework loading.
148 *
149 * @param string $fw The framework to load.
150 */
151 public function loadFramework($fw = '')
152 {
153 JHtml::fetch($fw);
154 }
155
156 /**
157 * Backward compatibility for Joomla add stylesheet.
158 *
159 * @param string $url URL to the linked style sheet.
160 * @param array $options Array of options. Example: array('version' => 'auto', 'conditional' => 'lt IE 9').
161 * @param array $attribs Array of attributes. Example: array('id' => 'scriptid', 'async' => 'async', 'data-test' => 1).
162 *
163 * @return void
164 *
165 * @since 1.7
166 */
167 public function addStyleSheet($url = '', $options = array(), $attribs = array())
168 {
169 if (empty($url))
170 {
171 return;
172 }
173
174 /**
175 * Add versioning to options array in order
176 * to reset the cache every time a new update is released.
177 * Versioning is used only in case it wasn't specified.
178 */
179 if (!isset($options['version']))
180 {
181 // make sure the constant is defined
182 if (defined('VIKAPPOINTMENTS_SOFTWARE_VERSION'))
183 {
184 $options['version'] = VIKAPPOINTMENTS_SOFTWARE_VERSION;
185 }
186 }
187 else if (empty($options['version']) || $options['version'] == 'auto')
188 {
189 // unset versioning
190 unset($options['version']);
191 }
192
193 // use JHtml to load native dependencies when needed
194 JHtml::fetch('stylesheet', $url, $options, $attribs);
195 }
196
197 /**
198 * Backward compatibility for WordPress add script.
199 *
200 * @param string $file Path to file.
201 * @param array $options Array of options. Example: array('version' => 'auto', 'conditional' => 'lt IE 9').
202 * @param array $attribs Array of attributes. Example: array('id' => 'scriptid', 'async' => 'async', 'data-test' => 1).
203 *
204 * @return void
205 */
206 public function addScript($file = '', $options = array(), $attribs = array())
207 {
208 if (empty($file))
209 {
210 return;
211 }
212
213 /**
214 * Add versioning to options array in order
215 * to reset the cache every time a new update is released.
216 * Versioning is used only in case it wasn't specified.
217 *
218 * @since 1.7
219 */
220 if (!isset($options['version']))
221 {
222 // make sure the constant is defined
223 if (defined('VIKAPPOINTMENTS_SOFTWARE_VERSION'))
224 {
225 $options['version'] = VIKAPPOINTMENTS_SOFTWARE_VERSION;
226 }
227 }
228 else if (empty($options['version']) || $options['version'] == 'auto')
229 {
230 // unset versioning
231 unset($options['version']);
232 }
233
234 // use JHtml to load native dependencies when needed
235 JHtml::fetch('script', $file, $options, $attribs);
236 }
237
238 /**
239 * Backward compatibility for card/row opening.
240 *
241 * @param string $class The class attribute for the fieldset.
242 * @param string $id The ID attribute for the fieldset.
243 *
244 * @return string The html to display.
245 *
246 * @since 1.7
247 */
248 public function openCard($class = '', $id = '')
249 {
250 return '<div class="row-fluid' . ($class ? ' ' . $class : '') . '"' . ($id ? ' id="' . $id . '' : '') . '>';
251 }
252
253 /**
254 * Backward compatibility for card/row closing.
255 *
256 * @return string The html to display.
257 *
258 * @since 1.7
259 */
260 public function closeCard()
261 {
262 return '</div>';
263 }
264
265 /**
266 * Backward compatibility for WordPress fieldset opening.
267 *
268 * @param string $legend The title of the fieldset.
269 * @param string $class The class attribute for the fieldset.
270 * @param string $id The ID attribute for the fieldset.
271 *
272 * @return string The html to display.
273 *
274 * @since 1.3
275 */
276 public function openFieldset($legend, $class = '', $id = '')
277 {
278 static $incrementalId = 0;
279 static $useScript = true;
280
281 if (!$id)
282 {
283 // generate a unique ID if not supplied
284 $id = 'vap-postbox-' . (++$incrementalId);
285 }
286
287 $input = JFactory::getApplication()->input;
288
289 $data = array();
290 $data['name'] = $legend;
291 $data['class'] = $class;
292 $data['id'] = $id;
293 $data['tag'] = preg_replace("/[^a-z0-9_]+/i", '_', implode('.', [$input->get('option'), $input->get('view'), $id]));
294 $data['visible'] = $input->cookie->getBool($data['tag'], true);
295
296 if ($useScript)
297 {
298 // use script only once
299 $useScript = false;
300
301 // include script from layout
302 $script = JLayoutHelper::render('html.form.fieldset.script');
303 }
304 else
305 {
306 $script = '';
307 }
308
309 return JLayoutHelper::render('html.form.fieldset.open', $data) . $script;
310 }
311
312 /**
313 * Backward compatibility for WordPress fieldset closing.
314 *
315 * @return string The html to display.
316 *
317 * @since 1.3
318 */
319 public function closeFieldset()
320 {
321 return JLayoutHelper::render('html.form.fieldset.close');
322 }
323
324 /**
325 * Backward compatibility for WordPress empty fieldset opening.
326 *
327 * @param string $class An additional class to use for the fieldset.
328 * @param string $id The ID attribute for the fieldset.
329 *
330 * @return string The html to display.
331 *
332 * @since 1.3
333 */
334 public function openEmptyFieldset($class = '', $id = '')
335 {
336 return $this->openFieldset('', $class, $id);
337 }
338
339 /**
340 * Backward compatibility for WordPress empty fieldset opening.
341 *
342 * @return string The html to display.
343 *
344 * @since 1.3
345 */
346 public function closeEmptyFieldset()
347 {
348 return $this->closeFieldset();
349 }
350
351 /**
352 * Backward compatibility for WordPress control opening.
353 *
354 * @param string $label The label of the control field.
355 * @param string $class The class of the control field.
356 * @param mixed $attr The additional attributes to add (string or array).
357 *
358 * @return string The html to display.
359 *
360 * @since 1.3
361 */
362 public function openControl($label, $class = '', $attr = array())
363 {
364 $data = array();
365
366 if (is_string($attr))
367 {
368 // string is not supported on WordPress platform
369 trigger_error(sprintf('%s() expects parameter 3 to be array, %s given', __METHOD__, gettype($attr)), E_USER_NOTICE);
370
371 // use empty attributes
372 $attr = array();
373 }
374
375 foreach ($attr as $k => $v)
376 {
377 $data[$k] = $v;
378 }
379
380 $data['label'] = $label;
381 $data['class'] = $class;
382
383 return JLayoutHelper::render('html.form.control.open', $data);
384 }
385
386 /**
387 * Backward compatibility for WordPress control closing.
388 *
389 * @return string The html to display.
390 *
391 * @since 1.3
392 */
393 public function closeControl()
394 {
395 return JLayoutHelper::render('html.form.control.close');
396 }
397
398 /**
399 * Prepares the editor scripts for being used.
400 * Useful in case the editor is initialized via JavaScript/AJAX.
401 *
402 * @param string $name The name of the editor.
403 *
404 * @return void
405 *
406 * @since 1.6.3
407 */
408 public function prepareEditor($name)
409 {
410 switch (strtolower($name))
411 {
412 case 'tinymce':
413 JHtml::fetch('behavior.tinyMCE');
414 break;
415
416 case 'codemirror':
417 JHtml::fetch('behavior.codeMirror');
418 break;
419 }
420 }
421
422 /**
423 * Returns the specified editor.
424 *
425 * @param mixed $editor The editor to load.
426 * The default one if not specified.
427 *
428 * @return JEditor The editor instance.
429 *
430 * @since 1.7
431 */
432 public function getEditor($editor = null)
433 {
434 return JFactory::getEditor($editor);
435 }
436
437 /**
438 * Returns the codemirror editor in WordPress 4.9+, otherwise a simple textarea.
439 *
440 * @param string $name The name of the textarea.
441 * @param string $value The value of the textarea.
442 * @param array $params An array of options (@since 1.7).
443 *
444 * @return string The html to display.
445 *
446 * @since 1.4
447 */
448 public function getCodeMirror($name, $value, array $params = array())
449 {
450 if (VersionListener::isLowerThan('4.9'))
451 {
452 // CodeMirror is not supported on WP lower than 4.9, use a plain textarea
453 $editorName = 'none';
454 }
455 else
456 {
457 // use CoreMirror editor
458 $editorName = 'codemirror';
459 }
460
461 // display editor
462 return JEditor::getInstance($editorName)->display($name, $value, '100%', 600, 30, 30, $buttons = false, $id = null, $params);
463 }
464
465 /**
466 * Backward compatibility for WordPress Bootstrap tabset opening.
467 *
468 * @param string $group The group of the tabset.
469 * @param string $attr The attributes to use.
470 *
471 * @return string The html to display.
472 *
473 * @since 1.4
474 */
475 public function bootStartTabSet($group, $attr = array())
476 {
477 /**
478 * In case the cookie attribute was included within the,
479 * array, we can register the script that will be used to
480 * handle the tab changes. The last selected tab will
481 * be stored in a cookie in order to be pre-selected
482 * when refreshing the page.
483 *
484 * @since 1.7
485 */
486 if (isset($attr['cookie']))
487 {
488 $this->bootstrapTabSetCookie = '<script>' . JHtml::fetch('vaphtml.scripts.tabhandler', $group, $attr['cookie']) . '</script>';
489 }
490 else
491 {
492 $this->bootstrapTabSetCookie = '';
493 }
494
495 return JHtml::fetch('bootstrap.startTabSet', $group, $attr);
496 }
497
498 /**
499 * Backward compatibility for WordPress Bootstrap tabset closing.
500 *
501 * @return string The html to display.
502 *
503 * @since 1.4
504 */
505 public function bootEndTabSet()
506 {
507 /**
508 * Append the 'cookie' script after creating the tabset.
509 *
510 * @since 1.7
511 */
512
513 return JHtml::fetch('bootstrap.endTabSet') . $this->bootstrapTabSetCookie;
514 }
515
516 /**
517 * Backward compatibility for WordPress Bootstrap add tab.
518 *
519 * @param string $group The tabset parent group.
520 * @param string $id The id of the tab.
521 * @param string $label The title of the tab.
522 * @param array $options A list of options.
523 *
524 * @return string The html to display.
525 *
526 * @since 1.4
527 */
528 public function bootAddTab($group, $id, $label, array $options = array())
529 {
530 /**
531 * In case the badge option is specified, append a badge
532 * to the tab label, displaying the count of records.
533 *
534 * @since 1.7
535 */
536 if (isset($options['badge']))
537 {
538 $badge_class = isset($options['badge']['class']) ? $options['badge']['class'] : 'badge-info';
539 $badge_id = isset($options['badge']['id']) ? $options['badge']['id'] : $id . '_tab_badge';
540 $badge_count = isset($options['badge']['count']) ? $options['badge']['count'] : (int) $options['badge'];
541
542 $label .= "<span class=\"badge {$badge_class} tab-badge-count\" id=\"{$badge_id}\" data-count=\"{$badge_count}\"> </span>";
543 }
544
545 return JHtml::fetch('bootstrap.addTab', $group, $id, $label);
546 }
547
548 /**
549 * Backward compatibility for WordPress Bootstrap end tab.
550 *
551 * @return string The html to display.
552 *
553 * @since 1.4
554 */
555 public function bootEndTab()
556 {
557 return JHtml::fetch('bootstrap.endTab');
558 }
559
560 /**
561 * Backward compatibility for WordPress Bootstrap open modal JS event.
562 *
563 * @param string $onclose The javascript function to call on close event.
564 *
565 * @return string The javascript function.
566 *
567 * @since 1.5
568 */
569 public function bootOpenModalJS($onclose = '')
570 {
571 if ($onclose)
572 {
573 $onclose .= '();';
574 }
575
576 return
577 <<<JS
578 var on_hide = null;
579
580 if ("$onclose") {
581 on_hide = function() {
582 $onclose
583 }
584 }
585
586 wpOpenJModal(id, url, null, on_hide);
587
588 return false;
589 JS
590 ;
591 }
592
593 /**
594 * Backward compatibility for WordPress Bootstrap dismiss modal JS event.
595 *
596 * @param string $selector The selector to identify the modal box.
597 *
598 * @return string The javascript function.
599 *
600 * @since 1.5
601 */
602 public function bootDismissModalJS($selector = null)
603 {
604 if (!is_null($selector))
605 {
606 /**
607 * The static selector is no more supported due
608 * to the possibility of conflicts between 2 or more
609 * modals loaded within the same page.
610 *
611 * @deprecated 1.8
612 */
613 JFactory::getDocument()->addScriptDeclaration('console.warn("The selector is deprecated in ' . __METHOD__ . '()");');
614 }
615
616 return
617 <<<JS
618 if ('$selector') {
619 id = '$selector';
620 }
621
622 wpCloseJModal(id);
623 JS
624 ;
625
626 return
627 <<<JS
628 wpCloseJModal('$selector');
629 JS
630 ;
631 }
632
633 /**
634 * Adds javascript support for Bootstrap popovers.
635 *
636 * @param string $selector Selector for the popover.
637 * @param array $options An array of options for the popover.
638 * Options for the popover can be:
639 * animation boolean apply a css fade transition to the popover
640 * html boolean Insert HTML into the popover. If false, jQuery's text method will be used to insert
641 * content into the dom.
642 * placement string|function how to position the popover - top | bottom | left | right
643 * selector string If a selector is provided, popover objects will be delegated to the specified targets.
644 * trigger string how popover is triggered - hover | focus | manual
645 * title string|function default title value if `title` tag isn't present
646 * content string|function default content value if `data-content` attribute isn't present
647 * delay number|object delay showing and hiding the popover (ms) - does not apply to manual trigger type
648 * If a number is supplied, delay is applied to both hide/show
649 * Object structure is: delay: { show: 500, hide: 100 }
650 * container string|boolean Appends the popover to a specific element: { container: 'body' }
651 */
652 public function attachPopover($selector = '.wpPopover', array $options = array())
653 {
654 static $loaded = array();
655
656 $sign = serialize(array($selector, $options));
657
658 if (!isset($loaded[$sign]))
659 {
660 // do not sanitize HTML contents
661 $options['sanitize'] = false;
662
663 /**
664 * In case the "container" attribute is not set,
665 * always place the popover within the body.
666 *
667 * @since 1.6.5
668 */
669 if (!isset($options['container']))
670 {
671 $options['container'] = 'body';
672 }
673
674 $data = $options ? json_encode($options) : '{}';
675 JFactory::getDocument()->addScriptDeclaration(
676 <<<JS
677 jQuery(function() {
678 jQuery('$selector').popover($data);
679 });
680 JS
681 );
682
683 $loaded[$sign] = 1;
684 }
685 }
686
687 /**
688 * Create a standard tag and attach a popover event.
689 * NOTE. FontAwesome framework MUST be loaded in order to work.
690 *
691 * @param array $options An array of options for the popover.
692 *
693 * @see VAPApplication::attachPopover() for further details about options keys.
694 *
695 * @since 1.6
696 */
697 public function createPopover(array $options = array())
698 {
699 $icon = isset($options['icon_class']) ? $options['icon_class'] : 'fas fa-question-circle';
700
701 $icon = isset($options['icon']) ? 'fas fa-' . $options['icon'] : $icon;
702
703 $template = "<i class=\"{$icon} vap-quest-popover\" {popover}></i>";
704
705 return $this->_popover($template, $options);
706 }
707
708 /**
709 * Create a text span and attach a popover event.
710 *
711 * @param array $options An array of options for the popover.
712 *
713 * @see VAPApplication::attachPopover() for further details about options keys.
714 *
715 * @since 1.6
716 */
717 public function textPopover(array $options = array())
718 {
719 $title = isset($options['title']) ? $options['title'] : '[MISSING TITLE]';
720 $template = "<span class=\"inline-popover vap-quest-popover\" {popover}>{$title}</span>";
721
722 return $this->_popover($template, $options);
723 }
724
725 /**
726 * Creates a popover using the provided template.
727 *
728 * @param string $template The popover template.
729 * @param array $options An array of options for the popover.
730 *
731 * @return string The popover HTML.
732 *
733 * @see VAPApplication::attachPopover() for further details about options keys.
734 */
735 protected function _popover($template, array $options)
736 {
737 $layout = new JLayoutFile('html.plugins.popover', null, array('component' => 'com_vikappointments'));
738
739 $options['html'] = true;
740 $options['title'] = isset($options['title']) ? $options['title'] : '';
741 $options['content'] = isset($options['content']) ? $options['content'] : '';
742 $options['trigger'] = isset($options['trigger']) ? $options['trigger'] : 'hover focus';
743 $options['placement'] = isset($options['placement']) ? $options['placement'] : 'right';
744 $options['template'] = isset($options['template']) ? $options['template'] : $layout->render();
745
746 // attach an empty array option so that the data will be recovered
747 // directly from the tag during the runtime
748 $this->attachPopover(".vap-quest-popover", array());
749
750 $attr = '';
751 foreach ($options as $k => $v)
752 {
753 $attr .= "data-{$k}=\"" . esc_attr($v) . "\" ";
754 }
755
756 return str_replace('{popover}', $attr, $template);
757 }
758
759 /**
760 * Return the WordPress date format specs.
761 *
762 * @param string $format The format to use.
763 * @param array &$attributes Some attributes to use.
764 *
765 * @return string The adapted date format.
766 *
767 * @since 1.6
768 */
769 public function jdateFormat($format = null, array &$attributes = array())
770 {
771 if ($format === null)
772 {
773 $format = VAPFactory::getConfig()->getString('dateformat');
774
775 if (!empty($attributes['showTime']))
776 {
777 // concat the time format (24 hours format only)
778 $format .= ' H:i';
779 }
780 }
781
782 // strip % from date format string, which was required in Joomla
783 return str_replace('%', '', $format);
784 }
785
786 /**
787 * Provides support to handle the WordPress calendar across different frameworks.
788 *
789 * @param mixed $value The date or the timestamp to fill.
790 * @param string $name The input name.
791 * @param string $id The input id attribute.
792 * @param string $format The date format.
793 * @param array $attributes Some attributes to use.
794 *
795 * @return string The calendar field.
796 *
797 * @since 1.6
798 */
799 public function calendar($value, $name, $id = null, $format = null, array $attributes = array())
800 {
801 $format = $this->jdateFormat($format, $attributes);
802
803 JHtml::fetch('behavior.calendar');
804
805 return JHtml::fetch('calendar', $value, $name, $id, $format, $attributes);
806 }
807
808 /**
809 * Method used to obtain a WordPress media form field.
810 *
811 * @return string The media in HTML.
812 *
813 * @since 1.6
814 */
815 public function getMediaField($name, $value = null, array $data = array())
816 {
817 // import form field class
818 JLoader::import('adapter.form.field');
819
820 // create XML field manifest
821 $xml = "<field name=\"$name\" type=\"media\" />";
822
823 // instantiate field
824 $field = JFormField::getInstance(simplexml_load_string($xml));
825
826 // overwrite name and value within data
827 $data['name'] = $name;
828 $data['value'] = $value;
829
830 // inject display data within field instance
831 foreach ($data as $k => $v)
832 {
833 $field->bind($v, $k);
834 }
835
836 // render field
837 return $field->render();
838 }
839
840 /**
841 * Method used to handle the reCAPTCHA events.
842 *
843 * @param string $event The reCAPTCHA event to trigger.
844 * Here's the list of the accepted events:
845 * - display Returns the HTML used to display the ReCAPTCHA input;
846 * - check Validates the POST data to make sure the ReCAPTCHA input was checked.
847 * @param array $options A configuration array.
848 *
849 * @return mixed The event response.
850 *
851 * @since 1.6
852 */
853 public function reCaptcha($event = 'display', array $options = array())
854 {
855 $response = null;
856
857 /**
858 * Trigger action to perform the specified ReCAPTCHA event.
859 *
860 * @param mixed $response The response to return.
861 * @param array $options A configuration array.
862 *
863 * @since 1.0
864 */
865 do_action_ref_array('vik_recaptcha_' . strtolower($event), array(&$response, $options));
866
867 return $response;
868 }
869
870 /**
871 * Checks if the com_user captcha is configured.
872 * In case the parameter is set to global, the default one
873 * will be retrieved.
874 *
875 * @param string $plugin The plugin name to check. Leave empty
876 * to use any type of captcha.
877 *
878 * @return boolean True if configured, otherwise false.
879 *
880 * @since 1.6
881 */
882 public function isCaptcha($plugin = null)
883 {
884 /**
885 * Trigger action to check whether the ReCAPTCHA plugin is supported.
886 *
887 * @param boolean $active True if active, false otherwise.
888 * @param mixed $plugin The requested plugin, if specified.
889 *
890 * @since 1.0
891 */
892 return apply_filters('vik_recaptcha_on', false, $plugin);
893 }
894
895 /**
896 * Checks if the global captcha is configured.
897 *
898 * @param string $plugin The plugin name to check. Leave empty
899 * to use any type of captcha.
900 *
901 * @return boolean True if configured, otherwise false.
902 *
903 * @since 1.6
904 */
905 public function isGlobalCaptcha($plugin = null)
906 {
907 return $this->isCaptcha($plugin);
908 }
909
910 /**
911 * Rewrites an internal URI that needs to be used outside of the website.
912 * This means that the routed URI MUST start with the base path of the site.
913 *
914 * @param mixed $query The query string or an associative array of data.
915 * @param boolean $xhtml Replace & by &amp; for XML compliance.
916 * @param mixed $itemid The itemid to use. If null, the current one will be used.
917 *
918 * @return string The complete routed URI.
919 *
920 * @since 1.6
921 */
922 public function routeForExternalUse($query = '', $xhtml = true, $itemid = null)
923 {
924 // check if the query already specifies the Itemid
925 if (!preg_match("/&Itemid=[\d]*/", $query))
926 {
927 // try to extract view from query
928 if (preg_match("/&view=([a-z0-9_]+)(?:&|$)/", $query, $match))
929 {
930 $view = end($match);
931 }
932 else
933 {
934 $view = null;
935 }
936
937 // import shortcodes model
938 $model = JModel::getInstance('vikappointments', 'shortcodes', 'admin');
939 $itemid = $model->best($view);
940
941 if ($itemid)
942 {
943 // update query with Itemid found
944 $query .= (strpos($query, '?') === false ? '?' : '&') . 'Itemid=' . $itemid;
945 }
946 }
947
948 // route URL
949 return JRoute::rewrite($query, false);
950 }
951
952 /**
953 * Routes an admin URL for being used outside from the website (complete URI).
954 *
955 * @param mixed $query The query string or an associative array of data.
956 * @param boolean $xhtml Replace & by &amp; for XML compliance.
957 *
958 * @return string The complete routed URI.
959 *
960 * @since 1.6.3
961 */
962 public function adminUrl($query = '', $xhtml = true)
963 {
964 $app = JFactory::getApplication();
965
966 if (is_array($query))
967 {
968 // make sure the array is not empty
969 if ($query)
970 {
971 $query = '?' . http_build_query($query);
972 }
973 else
974 {
975 $query = '';
976 }
977
978 // the query is an array, build the query string
979 $query = 'index.php' . $query;
980 }
981
982 // replace initial index.php with admin.php
983 $query = preg_replace("/^index\.php/", 'admin.php', $query);
984 // replace option=com_vikappointments with page=vikappointments
985 $query = preg_replace("/(&|\?)option=com_vikappointments/", '$1page=vikappointments', $query);
986
987 // finalise admin URI
988 $uri = JUri::root() . 'wp-admin/' . $query;
989
990 if ($xhtml)
991 {
992 $uri = str_replace('&', '&amp;', $uri);
993 }
994
995 return $uri;
996 }
997
998 /**
999 * Prepares a plain/routed URL to be used for an AJAX request.
1000 *
1001 * @param mixed $query The query string or a routed URL.
1002 * @param boolean $xhtml Replace & by &amp; for XML compliance.
1003 *
1004 * @return string The AJAX end-point URI.
1005 *
1006 * @since 1.7
1007 */
1008 public function ajaxUrl($query = '', $xhtml = false)
1009 {
1010 // instantiate path based on specified query
1011 $path = new JUri($query);
1012
1013 // delete option var from query
1014 $path->delVar('option');
1015
1016 // force action in query
1017 $path->setVar('action', 'vikappointments');
1018
1019 // force application client in case of front-end
1020 if (JFactory::getApplication()->isClient('site'))
1021 {
1022 $path->setVar('vik_ajax_client', 'site');
1023 }
1024
1025 // create AJAX URI
1026 $uri = admin_url('admin-ajax.php') . '?' . $path->getQuery();
1027
1028 if ($xhtml)
1029 {
1030 // try to make "&" XML safe
1031 $uri = preg_replace("/&(?!amp;)/", '&amp;', $uri);
1032 }
1033
1034 return $uri;
1035 }
1036
1037 /**
1038 * Includes the CSRF-proof token within the specified query string/URL.
1039 *
1040 * @param mixed $query The query string or a routed URL.
1041 * @param boolean $xhtml Replace & by &amp; for XML compliance.
1042 *
1043 * @return string The resulting path.
1044 *
1045 * @since 1.7
1046 */
1047 public function addUrlCSRF($query = '', $xhtml = false)
1048 {
1049 JLoader::import('adapter.session.session');
1050
1051 // safely append the CSRF token within the query string
1052 $uri = JUri::getInstance($query);
1053 $uri->setVar(JSession::getFormTokenName(), JSession::getFormToken());
1054
1055 if ($xhtml)
1056 {
1057 // try to make "&" XML safe
1058 $uri = preg_replace("/&(?!amp;)/", '&amp;', (string) $uri);
1059 }
1060
1061 return (string) $uri;
1062 }
1063
1064 /**
1065 * Returns the platform base path.
1066 *
1067 * @return string
1068 *
1069 * @since 1.7.1
1070 */
1071 public function getAbsolutePath()
1072 {
1073 return ABSPATH;
1074 }
1075
1076 /**
1077 * Helper method to pre-load the assets needed for the Employee Area.
1078 *
1079 * @return void
1080 *
1081 * @since 1.6
1082 */
1083 public function loadEmployeeAreaAssets()
1084 {
1085 parent::loadEmployeeAreaAssets();
1086
1087 // load bootstrap too
1088 $doc = JFactory::getDocument();
1089
1090 $internalFilesOptions = array('version' => VIKAPPOINTMENTS_SOFTWARE_VERSION);
1091
1092 $doc->addStyleSheet(VIKAPPOINTMENTS_CORE_MEDIA_URI . 'css/bootstrap.lite.css', $internalFilesOptions, array('id' => 'bootstrap-lite-style'));
1093 $doc->addScript(VIKAPPOINTMENTS_CORE_MEDIA_URI . 'js/bootstrap.min.js', $internalFilesOptions, array('id' => 'bootstrap-script'));
1094 }
1095
1096 /**
1097 * Returns a list of users that are currently logged-in.
1098 *
1099 * @param mixed $limit The query limit, if specified.
1100 * @param integer $offset The query offset, if specified.
1101 *
1102 * @return array A list of users.
1103 *
1104 * @since 1.6.3
1105 */
1106 public function getLoggedUsers($limit = null, $offset = 0)
1107 {
1108 // get record of currently logged-in users (get_users() returns always an array)
1109 $users = get_users([
1110 'meta_key' => 'session_tokens',
1111 'meta_compare' => 'EXISTS',
1112 ]);
1113
1114 // splice the users list following the specified limits
1115 $users = array_splice($users, $offset, $limit);
1116
1117 if (!$users)
1118 {
1119 // do not proceed in case of no users
1120 return array();
1121 }
1122
1123 $dbo = JFactory::getDbo();
1124
1125 $query = $dbo->getQuery(true)
1126 ->select($dbo->qn(array('id', 'country_code')))
1127 ->from($dbo->qn('#__vikappointments_users', 'c'));
1128
1129 /**
1130 * Here's a list of columns that have to be supported:
1131 *
1132 * @property string session_time The session last time.
1133 * @property integer jid The user ID.
1134 * @property integer id The customer ID.
1135 * @property string billing_name The user nominative.
1136 * @property string billing_mail The user e-mail address.
1137 * @property string country_code The user 2-letters country code.
1138 */
1139
1140 // map array to extract a valid response
1141 return array_map(function($user) use ($query, $dbo)
1142 {
1143 $sessions = get_user_meta($user->ID, 'session_tokens', true);
1144
1145 if (is_array($sessions) && $sessions)
1146 {
1147 // extract last session token
1148 $session = end($sessions);
1149 $last_login = $session['login'];
1150 }
1151 else
1152 {
1153 // impossible to fetch last login
1154 $last_login = null;
1155 }
1156
1157 $tmp = array();
1158 $tmp['jid'] = $user->ID;
1159 $tmp['session_time'] = $last_login;
1160 $tmp['billing_name'] = $user->display_name;
1161 $tmp['billing_mail'] = $user->user_email;
1162 $tmp['country_code'] = null;
1163 $tmp['id'] = null;
1164
1165 // complete query
1166 $query->clear('where')->where(array(
1167 $dbo->qn('c.jid') . ' = ' . (int) $user->ID,
1168 $dbo->qn('c.billing_mail') . ' = ' . $dbo->q($user->user_email),
1169 ), 'OR');
1170
1171 $dbo->setQuery($query, 0, 1);
1172
1173 // merge columns found
1174 $tmp = array_merge($tmp, (array) $dbo->loadAssoc());
1175
1176 return $tmp;
1177 }, $users);
1178 }
1179
1180 /**
1181 * Prepares the specified content before being displayed.
1182 *
1183 * @param mixed &$content The table content instance or a string to fetch.
1184 * @param mixed $params True to apply the full description, false to apply
1185 * the short description, if any. Any other non scalar
1186 * value to pass a configuration for plugins.
1187 *
1188 * @return void
1189 *
1190 * @since 1.6.3
1191 */
1192 public function onContentPrepare(&$content, $params = array())
1193 {
1194 $pattern = "/(?:<p>)?<!--\s*more\s*-->(?:<\/p>)?/i";
1195
1196 if (is_null($content))
1197 {
1198 $content = '';
1199 }
1200
1201 // create Content table in case a string was passed
1202 if (is_string($content))
1203 {
1204 $text = $content;
1205
1206 $content = JTable::getInstance('content');
1207 $content->text = $text;
1208 }
1209
1210 if (is_bool($params))
1211 {
1212 // BC: take the specified type of text (full or short)
1213 $full = $params;
1214
1215 // use an empty array
1216 $params = array();
1217 }
1218 else
1219 {
1220 /**
1221 * Extract type of text from parameters.
1222 * If not specified, the full text will be used.
1223 *
1224 * @since 1.7
1225 */
1226 $full = isset($params['fulltext']) ? (bool) $params['fulltext'] : true;
1227 }
1228
1229 // always replaces double line-breaks with paragraph elements
1230 $content->text = wpautop($content->text);
1231
1232 /**
1233 * Interprets shortcodes contained within the full text.
1234 *
1235 * @since 1.7
1236 */
1237 $content->text = do_shortcode($content->text);
1238
1239 // check if the description owns a readmore separator
1240 if (preg_match($pattern, $content->text))
1241 {
1242 // split the description in 2 chunks
1243 $chunks = preg_split($pattern, $content->text, 2);
1244
1245 // overwrite text with short (0) or full (1) description
1246 $content->text = $chunks[$full ? 1 : 0];
1247
1248 /**
1249 * Register intro and full texts too.
1250 *
1251 * @since 1.7
1252 */
1253 $content->introtext = $chunks[0];
1254 $content->fulltext = $chunks[1];
1255 }
1256 }
1257
1258 /**
1259 * Returns a list of supported payment gateways.
1260 *
1261 * @return array A list of paths.
1262 *
1263 * @since 1.6.3
1264 */
1265 public function getPaymentDrivers()
1266 {
1267 // import payment dispatcher
1268 JLoader::import('adapter.payment.dispatcher');
1269
1270 // get paths list of supported drivers
1271 return JPaymentDispatcher::getSupportedDrivers('vikappointments');
1272 }
1273
1274 /**
1275 * Returns the configuration form of a payment.
1276 *
1277 * @param string $payment The name of the payment.
1278 *
1279 * @return mixed The configuration array/object.
1280 *
1281 * @since 1.6.3
1282 *
1283 * @uses getPaymentInstance()
1284 */
1285 public function getPaymentConfig($payment)
1286 {
1287 // instantiate payment and access its configuration form
1288 return $this->getPaymentInstance($payment)->getAdminParameters();
1289 }
1290
1291 /**
1292 * Provides a new payment instance for the specified arguments.
1293 *
1294 * @param string $payment The name of the payment that should be instantiated.
1295 * @param mixed $order The details of the order that has to be paid.
1296 * @param mixed $config The payment configuration array or a JSON string.
1297 *
1298 * @return mixed The payment instance.
1299 *
1300 * @throws RuntimeException
1301 *
1302 * @since 1.6.3
1303 */
1304 public function getPaymentInstance($payment, $order = array(), $config = array())
1305 {
1306 // import payment dispatcher
1307 JLoader::import('adapter.payment.dispatcher');
1308
1309 // instantiate payment
1310 return JPaymentDispatcher::getInstance('vikappointments', $payment, $order, $config);
1311 }
1312
1313 /**
1314 * Returns the component manufacturer name or link.
1315 *
1316 * @param array $options An array of options:
1317 * - link (boolean) True to return a link, false to return the
1318 * name only (false by default);
1319 * - short (boolean) True to display the short manufacturer name,
1320 * false otherwise (false by default);
1321 * - long (boolean) True to display the long manufacturer name,
1322 * false otherwise (true by default);
1323 * - separator (string) A separator string to insert between the
1324 * names fetched ('-' by default).
1325 *
1326 * @return string The manufacturer name or link.
1327 *
1328 * @since 1.6.3
1329 */
1330 public function getManufacturer(array $options = array())
1331 {
1332 // add support for manufacturer default options
1333 $options['manufacturer'] = array(
1334 // specify a default URI
1335 'link' => 'https://vikwp.com',
1336 // specify the manufacturer short name
1337 'short' => 'VikWP',
1338 // specify the manufacturer long name
1339 'long' => 'vikwp.com',
1340 );
1341
1342 // invoke parent to complete name building
1343 return parent::getManufacturer($options);
1344 }
1345
1346 /**
1347 * Checks whether the reservation can be completed.
1348 *
1349 * @return boolean
1350 *
1351 * @since 1.6.3
1352 */
1353 public function checkAvailability()
1354 {
1355 // always return true in WordPress
1356 return true;
1357 }
1358
1359 /**
1360 * Displays the platform alert.
1361 *
1362 * @param array $options The alert display data.
1363 *
1364 * @return string The HTML of the alert.
1365 *
1366 * @see alert()
1367 *
1368 * @since 1.7
1369 */
1370 protected function displayAlert(array $data)
1371 {
1372 // register script to handle cookie alert
1373 JHtml::fetch('vaphtml.scripts.cookiealert');
1374
1375 // include INLINE class to keep the notice stuck on its position
1376 if (empty($data['attrs']['class']))
1377 {
1378 $data['attrs']['class'] = 'inline';
1379 }
1380 else
1381 {
1382 $data['attrs']['class'] .= ' inline';
1383 }
1384
1385 // instantiate layout file
1386 $layout = new JLayoutFile('html.system.notice');
1387
1388 // display layout
1389 return $layout->render($data);
1390 }
1391
1392 /**
1393 * Returns a list of supported SMS providers.
1394 *
1395 * @return array A list of paths.
1396 *
1397 * @since 1.7
1398 */
1399 public function getSmsDrivers()
1400 {
1401 // import sms dispatcher
1402 JLoader::import('adapter.sms.dispatcher');
1403
1404 // get paths list of supported drivers
1405 return JSmsDispatcher::getSupportedDrivers('vikappointments');
1406 }
1407
1408 /**
1409 * Returns the configuration form of a SMS provider.
1410 *
1411 * @param string $driver The name of the driver.
1412 *
1413 * @return mixed The configuration array/object.
1414 *
1415 * @since 1.7
1416 */
1417 public function getSmsConfig($driver)
1418 {
1419 // instantiate driver and access its configuration form
1420 return $this->getSmsInstance($driver)->getAdminParameters();
1421 }
1422
1423 /**
1424 * Provides a new SMS driver instance for the specified arguments.
1425 *
1426 * @param string $driver The name of the provider that should be instantiated.
1427 * If not specified, the default one will be used.
1428 * @param mixed $config The SMS configuration array or a JSON string.
1429 * @param mixed $order The details of the order that has to be notified.
1430 *
1431 * @return mixed The driver instance.
1432 *
1433 * @throws RuntimeException
1434 *
1435 * @since 1.7
1436 */
1437 public function getSmsInstance($driver = null, $config = null, $order = array())
1438 {
1439 if (is_null($driver))
1440 {
1441 // get default driver if not specified
1442 $driver = VAPFactory::getConfig()->get('smsapi');
1443 }
1444
1445 if (empty($driver))
1446 {
1447 // SMS API not configured
1448 throw new RuntimeException('SMS API framework not configured', 500);
1449 }
1450
1451 if (is_null($config))
1452 {
1453 // get default configuration if not specified
1454 $config = VAPFactory::getConfig()->get('smsapifields');
1455 }
1456
1457 // import sms dispatcher
1458 JLoader::import('adapter.sms.dispatcher');
1459
1460 // instantiate sms
1461 return JSmsDispatcher::getInstance('vikappointments', $driver, $order, $config);
1462 }
1463 }
1464