PluginProbe
VikBooking Hotel Booking Engine & PMS / 1.6.7
VikBooking Hotel Booking Engine & PMS v1.6.7
1.8.16 1.8.15 1.8.14 1.8.13 1.8.12 1.8.11 1.8.10 1.8.9 1.8.6 1.8.7 1.8.8 trunk 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.7 1.6.8 1.6.9 1.7.0 1.7.1 1.7.2 All 37 releases
vikbooking / admin / helpers / jv_helper.php

jv_helper.php in VikBooking Hotel Booking Engine & PMS 1.6.7, at admin/helpers/jv_helper.php

1,408 lines 47.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * @package VikBooking
4 * @subpackage com_vikbooking
5 * @author Alessio Gaggii - e4j - Extensionsforjoomla.com
6 * @copyright Copyright (C) 2018 e4j - Extensionsforjoomla.com. All rights reserved.
7 * @license GNU General Public License version 2 or later; see LICENSE
8 * @link https://vikwp.com
9 */
10
11 defined('ABSPATH') or die('No script kiddies please!');
12
13 /**
14 * Extends native application functions.
15 *
16 * @wponly the class extends VikApplication and uses different vars.
17 * @since 1.0
18 * @see VikApplication
19 */
20 class VboApplication extends VikApplication
21 {
22 /**
23 * Additional commands container for any methods.
24 *
25 * @var array
26 */
27 private $commands;
28
29 /**
30 * This method loads an additional CSS file (if available)
31 * for the current CMS, and CMS version.
32 *
33 * @return void
34 **/
35 public function normalizeBackendStyles()
36 {
37 $document = JFactory::getDocument();
38
39 if (is_file(VBO_ADMIN_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'wp.css')) {
40 $document->addStyleSheet(VBO_ADMIN_URI . 'helpers/' . 'wp.css', ['version' => VIKBOOKING_SOFTWARE_VERSION], ['id' => 'vbo-wp-style']);
41 }
42 }
43
44 /**
45 * Includes a script URI.
46 *
47 * @param string $uri The script URI.
48 *
49 * @return void
50 */
51 public function addScript($uri)
52 {
53 JHtml::fetch('script', $uri);
54 }
55
56 /**
57 * Sets additional commands for any methods. Like raise an error if the recipient email address is empty.
58 * Returns this object for chainability.
59 */
60 public function setCommand($key, $value)
61 {
62 if (!empty($key)) {
63 $this->commands[$key] = $value;
64 }
65 return $this;
66 }
67
68 public function sendMail($from_address, $from_name, $to, $reply_address, $subject, $hmess, $is_html = true, $encoding = 'base64', $attachment = null)
69 {
70 if (!is_array($to) && strpos($to, ',') !== false) {
71 $all_recipients = explode(',', $to);
72 foreach ($all_recipients as $k => $v) {
73 if (empty($v)) {
74 unset($all_recipients[$k]);
75 }
76 }
77 if (count($all_recipients) > 0) {
78 $to = $all_recipients;
79 }
80 }
81
82 if (empty($to)) {
83 //Prevent Joomla Exceptions that would stop the script execution
84 if (isset($this->commands['print_errors'])) {
85 VikError::raiseWarning('', 'The recipient email address is empty. Email message could not be sent. Please check your configuration.');
86 }
87 return false;
88 }
89
90 if ($from_name == $from_address) {
91 $mainframe = JFactory::getApplication();
92 $attempt_fromn = $mainframe->get('fromname', '');
93 if (!empty($attempt_fromn)) {
94 $from_name = $attempt_fromn;
95 }
96 }
97
98 /**
99 * Conditional text rules may set extra recipients or attachments.
100 *
101 * @since 1.16.0 (J) - 1.6.0 (WP)
102 */
103 $extra_admin_recipients = VikBooking::addAdminEmailRecipient(null);
104 $bcc_addresses = VikBooking::addAdminEmailRecipient(null, $bcc = true);
105 $extra_attachments = VikBooking::addEmailAttachment(null);
106 if ($extra_admin_recipients) {
107 // cast a possible string to array
108 $to = (array) $to;
109 // merge additional recipients
110 $to = array_merge($to, $extra_admin_recipients);
111 }
112 if ($extra_attachments) {
113 $attachment = $attachment ? (array) $attachment : [];
114 $attachment = array_merge($attachment, $extra_attachments);
115 }
116
117 /**
118 * We let the internal library process the email sending depending on the platform.
119 * This will allow us to perform the required manipulation of the content, if needed.
120 *
121 * @since 1.15.2 (J) - 1.5.5 (WP)
122 */
123 $mail_data = new VBOMailWrapper([
124 'sender' => [$from_address, $from_name],
125 'recipient' => $to,
126 'bcc' => $bcc_addresses,
127 'reply' => $reply_address,
128 'subject' => $subject,
129 'content' => $hmess,
130 'attachments' => $attachment,
131 ]);
132
133 // unset queues for the next email sending operation
134 VikBooking::addAdminEmailRecipient(null, false, $reset = true);
135 VikBooking::addEmailAttachment(null, $reset = true);
136
137 // dispatch the email sending command
138 return VBOFactory::getPlatform()->getMailer()->send($mail_data);
139 }
140
141 /**
142 * @param $arr_values array
143 * @param $current_key string
144 * @param $empty_value string (J3.x only)
145 * @param $default
146 * @param $input_name string
147 * @param $record_id = '' string
148 */
149 public function getDropDown($arr_values, $current_key, $empty_value, $default, $input_name, $record_id = '')
150 {
151 $dropdown = '';
152 $dropdown .= '<select name="'.$input_name.'" onchange="document.adminForm.submit();">'."\n";
153 $dropdown .= '<option value="">'.$default.'</option>'."\n";
154 $list = "\n";
155 foreach ($arr_values as $k => $v) {
156 $dropdown .= '<option value="'.$k.'"'.($k == $current_key ? ' selected="selected"' : '').'>'.$v.'</option>'."\n";
157 }
158 $dropdown .= '</select>'."\n";
159
160 return $dropdown;
161 }
162
163 public function loadSelect2()
164 {
165 //load JS + CSS
166 $document = JFactory::getDocument();
167 $document->addStyleSheet(VBO_ADMIN_URI.'resources/select2.min.css');
168 $this->addScript(VBO_ADMIN_URI.'resources/select2.min.js');
169 }
170
171 /**
172 * Returns the HTML code to render a regular dropdown
173 * menu styled through the jQuery plugin Select2.
174 *
175 * @param $arr_values array
176 * @param $current_key string
177 * @param $input_name string
178 * @param $placeholder string used when the select has no selected option (it's empty)
179 * @param $empty_name [string] the name of the option to set an empty value to the field (<option>$empty_name</option>)
180 * @param $empty_val [string] the value of the option to set an empty value to the field (<option>$empty_val</option>)
181 * @param $onchange [string] javascript code for the onchange attribute
182 * @param $idattr [string] the identifier attribute of the select
183 *
184 * @return string
185 */
186 public function getNiceSelect($arr_values, $current_key, $input_name, $placeholder, $empty_name = '', $empty_val = '', $onchange = 'document.adminForm.submit();', $idattr = '')
187 {
188 //load JS + CSS
189 $this->loadSelect2();
190
191 //attribute
192 $idattr = empty($idattr) ? rand(1, 999) : $idattr;
193
194 //select
195 $dropdown = '<select id="'.$idattr.'" name="'.$input_name.'"'.(!empty($onchange) ? ' onchange="'.$onchange.'"' : '').'>'."\n";
196 if (!empty($placeholder) && empty($current_key)) {
197 //in order for the placeholder value to appear, there must be a blank <option> as the first option in the select
198 $dropdown .= '<option></option>'."\n";
199 } else {
200 //unset the placeholder to not pass it to the select2 object, or the empty value will not be displayed
201 $placeholder = '';
202 }
203 if (strlen($empty_name) || strlen($empty_val)) {
204 $dropdown .= '<option value="'.$empty_val.'">'.$empty_name.'</option>'."\n";
205 }
206 foreach ($arr_values as $k => $v) {
207 $dropdown .= '<option value="'.$k.'"'.($k == $current_key ? ' selected="selected"' : '').'>'.$v.'</option>'."\n";
208 }
209 $dropdown .= '</select>'."\n";
210
211 //js code
212 $dropdown .= '<script type="text/javascript">'."\n";
213 $dropdown .= 'jQuery(document).ready(function() {'."\n";
214 $dropdown .= ' jQuery("#'.$idattr.'").select2('.(!empty($placeholder) ? '{placeholder: "'.addslashes($placeholder).'"}' : '').');'."\n";
215 $dropdown .= '});'."\n";
216 $dropdown .= '</script>'."\n";
217
218 return $dropdown;
219 }
220
221 /**
222 * Adds the script declaration to render the Bootstrap JModal window.
223 * The suffix can be passed to generate other JS functions.
224 * Optionally pass JavaScript code for the 'show' and 'hide' events.
225 * For compatibility with the Joomla framework, this method should be
226 * echoed although it does not return anything on WordPress.
227 *
228 * @param $suffix string
229 * @param $hide_js string
230 * @param $show_js string
231 *
232 * @return void should still be echoed for compatibility with J.
233 */
234 public function getJmodalScript($suffix = '', $hide_js = '', $show_js = '')
235 {
236 static $loaded = [];
237
238 $doc = JFactory::getDocument();
239
240 if (!isset($loaded[$suffix]))
241 {
242 $doc->addScriptDeclaration(
243 <<<JS
244 function vboOpenJModal$suffix(id, modal_url, new_title) {
245
246 var on_hide = null;
247
248 if ("$hide_js") {
249 on_hide = function() {
250 $hide_js
251 }
252 }
253
254 var on_show = null;
255
256 if ("$show_js") {
257 on_show = function() {
258 $show_js
259 }
260 }
261
262 wpOpenJModal(id, modal_url, on_show, on_hide);
263
264 if (new_title) {
265 jQuery('#jmodal-' + id + ' .modal-header h3').text(new_title);
266 }
267
268 return false;
269 }
270 JS
271 );
272
273 $loaded[$suffix] = 1;
274 }
275 }
276
277 /**
278 * Returns a safe sub-string string with the requested length, by
279 * avoiding errors for those errors not supporting multi-byte strings.
280 *
281 * @param string $text the text to apply the substr onto.
282 * @param string $len the length of the sub-string to take.
283 *
284 * @return string the portion of the string.
285 *
286 * @since 1.15.0 (J) - 1.5.0 (WP)
287 */
288 public function safeSubstr($text, $len = 3)
289 {
290 $mb_supported = function_exists('mb_substr');
291
292 if ($len < 1) {
293 return $text;
294 }
295
296 return $mb_supported ? mb_substr($text, 0, $len, 'UTF-8') : substr($text, 0, $len);
297 }
298
299 /**
300 * Loads the necessary JS and CSS assets to render the jQuery UI Datepicker calendar.
301 *
302 * @since 1.1.0
303 * @since 1.15.0 (J) - 1.5.0 (WP) the lang definitions work for both front and back -ends.
304 */
305 public function loadDatePicker()
306 {
307 static $datepicker_loaded = null;
308
309 if ($datepicker_loaded) {
310 // loaded flag
311 return;
312 }
313
314 $document = JFactory::getDocument();
315 $document->addStyleSheet(VBO_SITE_URI.'resources/jquery-ui.min.css');
316
317 JHtml::fetch('jquery.framework', true, true);
318 $this->addScript(VBO_SITE_URI.'resources/jquery-ui.min.js');
319
320 $vbo_df = VikBooking::getDateFormat();
321 $juidf = $vbo_df == "%d/%m/%Y" ? 'dd/mm/yy' : ($vbo_df == "%m/%d/%Y" ? 'mm/dd/yy' : 'yy/mm/dd');
322
323 $is_rtl_lan = false;
324 $is_rtl_str = 'false';
325 $day_names_min_len = 2;
326 $now_lang = JFactory::getLanguage();
327 if (method_exists($now_lang, 'isRtl')) {
328 $is_rtl_lan = $now_lang->isRtl();
329 $is_rtl_str = $is_rtl_lan ? 'true' : $is_rtl_str;
330 if ($is_rtl_lan) {
331 // for most RTL languages, 2 chars for the week-days would not make sense
332 $day_names_min_len = 3;
333 }
334 }
335
336 $ldecl = '
337 jQuery(function($){'."\n".'
338 $.datepicker.regional["vikbooking"] = {'."\n".'
339 closeText: "'.JText::translate('VBJQCALDONE').'",'."\n".'
340 prevText: "'.JText::translate('VBJQCALPREV').'",'."\n".'
341 nextText: "'.JText::translate('VBJQCALNEXT').'",'."\n".'
342 currentText: "'.JText::translate('VBJQCALTODAY').'",'."\n".'
343 monthNames: ["'.JText::translate('VBMONTHONE').'","'.JText::translate('VBMONTHTWO').'","'.JText::translate('VBMONTHTHREE').'","'.JText::translate('VBMONTHFOUR').'","'.JText::translate('VBMONTHFIVE').'","'.JText::translate('VBMONTHSIX').'","'.JText::translate('VBMONTHSEVEN').'","'.JText::translate('VBMONTHEIGHT').'","'.JText::translate('VBMONTHNINE').'","'.JText::translate('VBMONTHTEN').'","'.JText::translate('VBMONTHELEVEN').'","'.JText::translate('VBMONTHTWELVE').'"],'."\n".'
344 monthNamesShort: ["'.$this->safeSubstr(JText::translate('VBMONTHONE')).'","'.$this->safeSubstr(JText::translate('VBMONTHTWO')).'","'.$this->safeSubstr(JText::translate('VBMONTHTHREE')).'","'.$this->safeSubstr(JText::translate('VBMONTHFOUR')).'","'.$this->safeSubstr(JText::translate('VBMONTHFIVE')).'","'.$this->safeSubstr(JText::translate('VBMONTHSIX')).'","'.$this->safeSubstr(JText::translate('VBMONTHSEVEN')).'","'.$this->safeSubstr(JText::translate('VBMONTHEIGHT')).'","'.$this->safeSubstr(JText::translate('VBMONTHNINE')).'","'.$this->safeSubstr(JText::translate('VBMONTHTEN')).'","'.$this->safeSubstr(JText::translate('VBMONTHELEVEN')).'","'.$this->safeSubstr(JText::translate('VBMONTHTWELVE')).'"],'."\n".'
345 dayNames: ["'.JText::translate('VBWEEKDAYZERO').'", "'.JText::translate('VBWEEKDAYONE').'", "'.JText::translate('VBWEEKDAYTWO').'", "'.JText::translate('VBWEEKDAYTHREE').'", "'.JText::translate('VBWEEKDAYFOUR').'", "'.JText::translate('VBWEEKDAYFIVE').'", "'.JText::translate('VBWEEKDAYSIX').'"],'."\n".'
346 dayNamesShort: ["'.$this->safeSubstr(JText::translate('VBWEEKDAYZERO')).'", "'.$this->safeSubstr(JText::translate('VBWEEKDAYONE')).'", "'.$this->safeSubstr(JText::translate('VBWEEKDAYTWO')).'", "'.$this->safeSubstr(JText::translate('VBWEEKDAYTHREE')).'", "'.$this->safeSubstr(JText::translate('VBWEEKDAYFOUR')).'", "'.$this->safeSubstr(JText::translate('VBWEEKDAYFIVE')).'", "'.$this->safeSubstr(JText::translate('VBWEEKDAYSIX')).'"],'."\n".'
347 dayNamesMin: ["'.$this->safeSubstr(JText::translate('VBWEEKDAYZERO'), $day_names_min_len).'", "'.$this->safeSubstr(JText::translate('VBWEEKDAYONE'), $day_names_min_len).'", "'.$this->safeSubstr(JText::translate('VBWEEKDAYTWO'), $day_names_min_len).'", "'.$this->safeSubstr(JText::translate('VBWEEKDAYTHREE'), $day_names_min_len).'", "'.$this->safeSubstr(JText::translate('VBWEEKDAYFOUR'), $day_names_min_len).'", "'.$this->safeSubstr(JText::translate('VBWEEKDAYFIVE'), $day_names_min_len).'", "'.$this->safeSubstr(JText::translate('VBWEEKDAYSIX'), $day_names_min_len).'"],'."\n".'
348 weekHeader: "'.JText::translate('VBJQCALWKHEADER').'",'."\n".'
349 dateFormat: "'.$juidf.'",'."\n".'
350 firstDay: '.VikBooking::getFirstWeekDay().','."\n".'
351 isRTL: ' . $is_rtl_str . ','."\n".'
352 showMonthAfterYear: false,'."\n".'
353 yearSuffix: ""'."\n".'
354 };'."\n".'
355 $.datepicker.setDefaults($.datepicker.regional["vikbooking"]);'."\n".'
356 });';
357
358 /**
359 * Trigger event to allow third party plugins to overwrite the JS declaration for the datepicker.
360 *
361 * @since 1.16.0 (J) - 1.6.0 (WP)
362 */
363 VBOFactory::getPlatform()->getDispatcher()->trigger('onBeforeDeclareDatepickerRegionalVikBooking', [$is_rtl_lan, $now_lang->getTag(), &$ldecl]);
364
365 // add script declaration
366 $document->addScriptDeclaration($ldecl);
367
368 // cache loaded flag
369 $datepicker_loaded = 1;
370 }
371
372 /**
373 * Loads the CMS's native datepicker calendar.
374 *
375 * @since 1.10
376 */
377 public function getCalendar($val, $name, $id = null, $df = null, array $attributes = array())
378 {
379 if ($df === null)
380 {
381 $df = VikBooking::getDateFormat();
382 }
383
384 return parent::calendar($val, $name, $id, $df, $attributes);
385 }
386
387 /**
388 * Returns a masked e-mail address. The e-mail are masked using
389 * a technique to encode the bytes in hexadecimal representation.
390 * The chunk of the masked e-mail will be also encoded to be HTML readable.
391 *
392 * @param string $email The e-mail to mask.
393 * @param boolean $reverse True to reverse the e-mail address.
394 * Only if the e-mail is not contained into an attribute.
395 *
396 * @return string The masked e-mail address.
397 */
398 public function maskMail($email, $reverse = false)
399 {
400 if ($reverse)
401 {
402 // reverse the e-mail address
403 $email = strrev($email);
404 }
405
406 // converts the e-mail address from bin to hex
407 $email = bin2hex($email);
408 // append ;&#x sequence after every chunk of the masked e-mail
409 $email = chunk_split($email, 2, ";&#x");
410 // prepend &#x sequence before the address and trim the ending sequence
411 $email = "&#x" . substr($email, 0, -3);
412
413 return $email;
414 }
415
416 /**
417 * Returns a safemail tag to avoid the bots spoof a plain address.
418 *
419 * @param string $email The e-mail address to mask.
420 * @param boolean $mail_to True if the address should be wrapped
421 * within a "mailto" link.
422 *
423 * @return string The HTML tag containing the masked address.
424 *
425 * @uses maskMail()
426 */
427 public function safeMailTag($email, $mail_to = false)
428 {
429 // include the CSS declaration to reverse the text contained in the <safemail> tags
430 JFactory::getDocument()->addStyleDeclaration('safemail {direction: rtl;unicode-bidi: bidi-override;}');
431
432 // mask the reversed e-mail address
433 $masked = $this->maskMail($email, true);
434
435 // include the address into a custom <safemail> tag
436 $tag = "<safemail>$masked</safemail>";
437
438 if ($mail_to)
439 {
440 // mask the address for mailto command (do not use reverse)
441 $mailto = $this->maskMail($email);
442
443 // wrap the safemail tag within a mailto link
444 $tag = "<a href=\"mailto:$mailto\" class=\"mailto\">$tag</a>";
445 }
446
447 return $tag;
448 }
449
450 /**
451 * Loads and echoes the script necessary to render the Fancybox
452 * plugin for jQuery to open images or iframes within a modal box.
453 * This resolves conflicts with some Bootstrap or Joomla (4) versions
454 * that do not support the old-native CSS class .modal with "behavior.modal".
455 * Mainly made to open pictures in a modal box, so the default "type" is set to "image".
456 * By passing a custom $opts string, the "type" property could be set to "iframe", but
457 * in this case it's better to use the other method of this class (Jmodal).
458 * The base jQuery library should be already loaded when using this method.
459 *
460 * @param string $selector The jQuery selector to trigger Fancybox.
461 * @param string $opts The options object for the Fancybox setup.
462 * @param boolean $reloadfunc If true, an additional function is included in the script
463 * to apply again Fancybox to newly added images to the DOM (via Ajax).
464 *
465 * @return void
466 *
467 * @uses addScript()
468 */
469 public function prepareModalBox($selector = '.vbomodal', $opts = '', $reloadfunc = false)
470 {
471 if (empty($opts)) {
472 $opts = '{
473 "helpers": {
474 "overlay": {
475 "locked": false
476 }
477 },
478 "width": "70%",
479 "height": "75%",
480 "autoScale": true,
481 "transitionIn": "none",
482 "transitionOut": "none",
483 "padding": 0,
484 "type": "image"
485 }';
486 }
487 $document = JFactory::getDocument();
488 $document->addStyleSheet(VBO_SITE_URI.'resources/jquery.fancybox.css');
489 $this->addScript(VBO_SITE_URI.'resources/jquery.fancybox.js');
490
491 $reloadjs = '
492 function reloadFancybox() {
493 jQuery("'.$selector.'").fancybox('.$opts.');
494 }
495 ';
496 $js = '
497 <script type="text/javascript">
498 jQuery(document).ready(function() {
499 jQuery("'.$selector.'").fancybox('.$opts.');
500 });'.($reloadfunc ? $reloadjs : '').'
501 </script>';
502
503 echo $js;
504 }
505
506 /**
507 * Method used to handle the reCAPTCHA events.
508 *
509 * @param string $event The reCAPTCHA event to trigger.
510 * Here's the list of the accepted events:
511 * - display Returns the HTML used to
512 * display the reCAPTCHA input.
513 * - check Validates the POST data to make sure
514 * the reCAPTCHA input was checked.
515 * @param array $options A configuration array.
516 *
517 * @return mixed The event response.
518 *
519 * @since 1.2.3
520 * @wponly the Joomla integration differs
521 */
522 public function reCaptcha($event = 'display', array $options = array())
523 {
524 $response = null;
525 // an optional configuration array (just leave empty)
526 $options = array();
527 // trigger reCAPTCHA display event to fill $response var
528 do_action_ref_array('vik_recaptcha_' . $event, array(&$response, $options));
529 // display reCAPTCHA by echoing it (empty in case reCAPTCHA is not available)
530 return $response;
531 }
532
533 /**
534 * Checks if the com_user captcha is configured.
535 * In case the parameter is set to global, the default one
536 * will be retrieved.
537 *
538 * @param string $plugin The plugin name to check ('recaptcha' by default).
539 *
540 * @return boolean True if configured, otherwise false.
541 *
542 * @since 1.2.3
543 * @wponly the Joomla integration differs
544 */
545 public function isCaptcha($plugin = 'recaptcha')
546 {
547 return apply_filters('vik_' . $plugin . '_on', false);
548 }
549
550 /**
551 * Checks if the global captcha is configured.
552 *
553 * @param string $plugin The plugin name to check ('recaptcha' by default).
554 *
555 * @return boolean True if configured, otherwise false.
556 *
557 * @since 1.2.3
558 */
559 public function isGlobalCaptcha($plugin = 'recaptcha')
560 {
561 return $this->isCaptcha($plugin);
562 }
563
564 /**
565 * Method used to obtain a WordPress media form field.
566 *
567 * @return string The media in HTML.
568 *
569 * @since 1.3.0
570 */
571 public function getMediaField($name, $value = null, array $data = array())
572 {
573 // check if WordPress is installed
574 if (VBOPlatformDetection::isWordPress())
575 {
576 add_action('admin_enqueue_scripts', function() {
577 wp_enqueue_media();
578 });
579
580 // import form field class
581 JLoader::import('adapter.form.field');
582
583 // create XML field manifest
584 $xml = "<field name=\"$name\" type=\"media\" modowner=\"vikbooking\" />";
585
586 // instantiate field
587 $field = JFormField::getInstance(simplexml_load_string($xml));
588
589 // overwrite name and value within data
590 $data['name'] = $name;
591 $data['value'] = $value;
592
593 // inject display data within field instance
594 foreach ($data as $k => $v)
595 {
596 $field->bind($v, $k);
597 }
598
599 // render field
600 return $field->render();
601 }
602
603 // fallback to Joomla
604
605 // init media field
606 $field = new JFormFieldMedia(null, $value);
607 // setup an empty form as placeholder
608 $field->setForm(new JForm('vikbooking.media'));
609
610 // force field attributes
611 $data['name'] = $name;
612 $data['value'] = $value;
613
614 if (empty($data['previewWidth']))
615 {
616 // there is no preview width, set a defualt value
617 // to make the image visible within the popover
618 $data['previewWidth'] = 480;
619 }
620
621 // render the field
622 return $field->render('joomla.form.field.media', $data);
623 }
624
625 /**
626 * Displays a multi-state toggle switch element with unlimited buttons.
627 * Custom values, contents, labels, attributes and JS events can be attached
628 * to each button. VCM will use this same method.
629 *
630 * @param string $name the input name equal for all radio buttons.
631 * @param string $value the current input field value to be pre-selected.
632 * @param array $values list of radio buttons with each value.
633 * @param array $labels list of contents for each button trigger.
634 * @param array $attrs list of associative array attributes for each button.
635 * @param array $wrap list of associative array attributes for the wrapper.
636 *
637 * @return string the necessary HTML to render the multi-state toggle switch.
638 *
639 * @since 1.15.0 (J) - 1.5.0 (WP)
640 */
641 public function multiStateToggleSwitchField($name, $value, $values = array(), $labels = array(), $attrs = array(), $wrap = array())
642 {
643 static $tooltip_js_declared = null;
644
645 // whether tooltip for titles is needed
646 $needs_tooltip = false;
647
648 // HTML container
649 $multi_state_switch = '';
650
651 if (!is_array($values) || !count($values)) {
652 // values must be set or we don't know what buttons to display
653 return $multi_state_switch;
654 }
655
656 // build default classes for the tri-state toggle switch (with 3 buttons)
657 $def_tristate_cls = array(
658 'vik-multiswitch-radiobtn-on',
659 'vik-multiswitch-radiobtn-def',
660 'vik-multiswitch-radiobtn-off',
661 );
662
663 // start wrapper
664 $multi_state_switch .= "\n" . '<div class="vik-multiswitch-wrap' . (isset($wrap['class']) ? (' ' . $wrap['class']) : '') . '">' . "\n";
665
666 foreach ($values as $btn_k => $btn_val) {
667 // build default classes for button label
668 $btn_classes = array('vik-multiswitch-radiobtn');
669 if (isset($def_tristate_cls[$btn_k])) {
670 // push default class for a 3-state toggle switch
671 array_push($btn_classes, $def_tristate_cls[$btn_k]);
672 }
673 // check if additional custom classes have been defined for this button
674 if (isset($attrs[$btn_k]) && isset($attrs[$btn_k]['label_class']) && !empty($attrs[$btn_k]['label_class'])) {
675 if (is_array($attrs[$btn_k]['label_class'])) {
676 // list of additional classes for this button
677 $btn_classes = array_merge($btn_classes, $attrs[$btn_k]['label_class']);
678 } elseif (is_string($attrs[$btn_k]['label_class'])) {
679 // multiple classes should be space-separated
680 array_push($btn_classes, $attrs[$btn_k]['label_class']);
681 }
682 }
683
684 // check title as first thing, even though this is passed along with the labels
685 $label_title = '';
686 if (isset($labels[$btn_k]) && !is_scalar($labels[$btn_k]) && isset($labels[$btn_k]['title'])) {
687 $needs_tooltip = true;
688 $label_title = ' title="' . addslashes(htmlentities($labels[$btn_k]['title'])) . '"';
689 }
690
691 // start button label
692 $multi_state_switch .= "\t" . '<label class="' . implode(' ', $btn_classes) . '"' . $label_title . '>' . "\n";
693
694 // check button input radio
695 $radio_attributes = array();
696 if (($value !== null && $value == $btn_val) || ($value === null && $btn_k === 0)) {
697 // this radio button must be checked (pre-selected)
698 $radio_attributes['checked'] = true;
699 }
700 // check if custom attributes were specified for this input
701 if (isset($attrs[$btn_k]) && isset($attrs[$btn_k]['input'])) {
702 // must be an associative array with key = attribute name, value = attribute value
703 foreach ($attrs[$btn_k]['input'] as $attr_name => $attr_val) {
704 // javascript events could be attached like 'onchange'=>'myCallback(this.value)'
705 $radio_attributes[$attr_name] = $attr_val;
706 }
707 }
708 $radio_attr_string = '';
709 foreach ($radio_attributes as $attr_name => $attr_val) {
710 if ($attr_val === true) {
711 // short-attribute name, like "checked"
712 $radio_attr_string .= $attr_name . ' ';
713 continue;
714 }
715 $radio_attr_string .= $attr_name . '="' . $attr_val . '" ';
716 }
717 $multi_state_switch .= "\t\t" . '<input type="radio" name="' . $name . '" value="' . $btn_val . '" ' . $radio_attr_string . '/>' . "\n";
718
719 // add button trigger
720 $multi_state_switch .= "\t\t" . '<span class="vik-multiswitch-trigger"></span>' . "\n";
721
722 // check button label text
723 if (isset($labels[$btn_k])) {
724 /**
725 * By default, the buttons of the toggle switch use an animation,
726 * which requires an absolute positioning of the "label-text".
727 * For this reason, there cannot be a minimum width for these texts
728 * and so the content should fit the default width. Usually, using
729 * a font-awesome icon is the best content. For using literal texts,
730 * like "Dark", "Light" etc.. the class "vik-multiswitch-noanimation"
731 * should be passed to the button label text.
732 */
733 $label_txt = '';
734 $label_class = '';
735 if (!is_scalar($labels[$btn_k])) {
736 // with an associative array we accept value, title and custom classes
737 if (isset($labels[$btn_k]['value'])) {
738 $label_txt = $labels[$btn_k]['value'];
739 }
740 if (isset($labels[$btn_k]['class'])) {
741 $label_class = ' ' . ltrim($labels[$btn_k]['class']);
742 }
743 } else {
744 // just a string, maybe with text or HTML mixed content
745 $label_txt = $labels[$btn_k];
746 }
747 if (strlen($label_txt)) {
748 // append button label text only if some text has been defined
749 $multi_state_switch .= "\t\t" . '<span class="vik-multiswitch-txt' . $label_class . '">' . $label_txt . '</span>' . "\n";
750 }
751 }
752
753 // end button label
754 $multi_state_switch .= "\t" . '</label>' . "\n";
755 }
756
757 // end wrapper
758 $multi_state_switch .= '</div>' . "\n";
759
760 // check tooltip JS rendering
761 if (!$tooltip_js_declared && $needs_tooltip) {
762 // turn static flag on
763 $tooltip_js_declared = 1;
764
765 // add script declaration for JS rendering of tooltips
766 $doc = JFactory::getDocument();
767 $doc->addScriptDeclaration(
768 <<<JS
769 jQuery(function() {
770 if (jQuery.isFunction(jQuery.fn.tooltip)) {
771 jQuery('.vik-multiswitch-wrap label').tooltip();
772 }
773 });
774 JS
775 );
776 }
777
778 return $multi_state_switch;
779 }
780
781 /**
782 * Returns a list of supported fonts for the third-party visual editor (Quill).
783 *
784 * @param bool $short_names whether to return short font names.
785 *
786 * @return array list of font family names or short names.
787 *
788 * @since 1.15.0 (J) - 1.5.0 (WP)
789 */
790 public function getVisualEditorFonts($short_names = false)
791 {
792 // supported fonts
793 $font_families = ['Sans Serif', 'Arial', 'Courier', 'Garamond', 'Tahoma', 'Times New Roman', 'Verdana', 'Inconsolata', 'Sailec Light', 'Monospace'];
794
795 if (!$short_names) {
796 // return the regular names to be displayed
797 return $font_families;
798 }
799
800 // return the "short" names of the supported fonts
801 return array_map(function($font) {
802 return str_replace(' ', '-', strtolower($font));
803 }, $font_families);
804 }
805
806 /**
807 * Loads the necessary assets for the third-party visual editor (Quill).
808 *
809 * @return void
810 *
811 * @since 1.15.0 (J) - 1.5.0 (WP)
812 */
813 public function loadVisualEditorAssets()
814 {
815 static $loaded = null;
816
817 if ($loaded) {
818 return;
819 }
820
821 $loaded = 1;
822
823 // access the document
824 $doc = JFactory::getDocument();
825
826 // build the list of font families
827 $font_families = $this->getVisualEditorFonts();
828 $font_shortfam = $this->getVisualEditorFonts(true);
829 $js_font_names = json_encode($font_shortfam);
830
831 // build inline CSS styles
832 $css_font_decl = '';
833 foreach ($font_families as $k => $font_name) {
834 $font_val = $font_shortfam[$k];
835 $css_font_decl .= '.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="' . $font_val . '"]::before,';
836 $css_font_decl .= '.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="' . $font_val . '"]::before {' . "\n";
837 $css_font_decl .= 'content: "' . $font_name . '";' . "\n";
838 $css_font_decl .= 'font-family: "' . $font_name . '";' . "\n";
839 $css_font_decl .= '}' . "\n";
840 }
841
842 $css_font_decl .= '
843 .ql-picker.ql-specialtags .ql-picker-label {
844 padding-right: 18px;
845 }
846 .ql-picker.ql-specialtags .ql-picker-label:before {
847 content: "' . htmlspecialchars(JText::translate('VBO_CONDTEXT_TKN')) . '";
848 }
849 ';
850
851 /**
852 * Cache the pre-configuration CSS onto a file to allow AJAX requests
853 * to properly load the asset inline within the response. Before this
854 * change the styles used to be added as an inline style declaration.
855 *
856 * @since 1.16.7 (J) - 1.6.7 (WP)
857 */
858 $cached_preconfig_css_path = implode(DIRECTORY_SEPARATOR, [VBO_ADMIN_PATH, 'resources', 'quill', 'vik-quill-preconfig-cache.css']);
859 $cached_preconfig_css_uri = VBO_ADMIN_URI . 'resources/quill/vik-quill-preconfig-cache.css';
860 $cached_preconfig_css_ok = is_file($cached_preconfig_css_path);
861
862 if (!$cached_preconfig_css_ok) {
863 // attempt to create the file
864 $cached_preconfig_css_ok = JFile::write($cached_preconfig_css_path, $css_font_decl);
865 }
866
867 if ($cached_preconfig_css_ok) {
868 // load cached script file
869 $doc->addStyleSheet($cached_preconfig_css_uri);
870 } else {
871 // revert to append CSS style declaration to document
872 $doc->addStyleDeclaration($css_font_decl);
873 }
874
875 // append theme CSS to document
876 $doc->addStyleSheet(VBO_ADMIN_URI . 'resources/quill/quill.snow.css');
877
878 // append JS assets to document
879 $this->addScript(VBO_ADMIN_URI . 'resources/quill/quill.js');
880 $this->addScript(VBO_ADMIN_URI . 'resources/quill/quill-image-resize.min.js');
881 $this->addScript(VBO_ADMIN_URI . 'resources/quill/vik-content-builder.js');
882
883 // icon for mail wrapper
884 $mail_wrapper_icn = '<i class="' . VikBookingIcons::i('minus-square') . '" title="' . htmlspecialchars(JText::translate('VBO_INSERT_CONT_WRAPPER')) . '"></i>';
885
886 // icon for mail preview
887 $mail_preview_icn = '<i class="' . VikBookingIcons::i('eye') . '" title="' . htmlspecialchars(JText::translate('VBOPREVIEW')) . '"></i>';
888
889 // icon for property logo (home icon)
890 $mail_homelogo_icn = '<i class="' . VikBookingIcons::i('hotel') . '" title="' . htmlspecialchars(JText::translate('VBCONFIGFOURLOGO'), ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401) . '"></i>';
891
892 // build script pre-configuration string
893 $quill_preconfig_script =
894 <<<JS
895 // Quill pre-configuration
896 (function() {
897 // configure Quill to use inline styles rather than classes
898
899 var AlignClass = Quill.import('attributors/class/align');
900 Quill.register(AlignClass, true);
901
902 var BackgroundClass = Quill.import('attributors/class/background');
903 Quill.register(BackgroundClass, true);
904
905 var ColorClass = Quill.import('attributors/class/color');
906 Quill.register(ColorClass, true);
907
908 var FontClass = Quill.import('attributors/class/font');
909 Quill.register(FontClass, true);
910
911 var SizeClass = Quill.import('attributors/class/size');
912 Quill.register(SizeClass, true);
913
914 var AlignStyle = Quill.import('attributors/style/align');
915 Quill.register(AlignStyle, true);
916
917 var BackgroundStyle = Quill.import('attributors/style/background');
918 Quill.register(BackgroundStyle, true);
919
920 var ColorStyle = Quill.import('attributors/style/color');
921 Quill.register(ColorStyle, true);
922
923 var SizeStyle = Quill.import('attributors/style/size');
924 Quill.register(SizeStyle, true);
925
926 var FontStyle = Quill.import('attributors/style/font');
927 Quill.register(FontStyle, true);
928
929 // set additional fonts
930 var Font = Quill.import('formats/font');
931 Font.whitelist = $js_font_names;
932 Quill.register(Font, true);
933
934 // register custom Blot for special tags
935 var Inline = Quill.import('blots/inline');
936 class Specialtag extends Inline {
937 static create(value) {
938 var node = super.create(value);
939 if (value) {
940 node.setAttribute('class', value);
941 }
942 return node;
943 }
944
945 static formats(domNode) {
946 return domNode.getAttribute("class");
947 }
948
949 format(name, value) {
950 if (name !== this.statics.blotName || !value) {
951 return super.format(name, value);
952 }
953 if (value) {
954 this.domNode.setAttribute('class', value);
955 }
956 }
957 }
958 Specialtag.blotName = 'specialtag';
959 Specialtag.tagName = 'strong';
960 Quill.register(Specialtag);
961
962 // register custom Blot for mail-wrapper
963 var BlockEmbed = Quill.import('blots/block/embed');
964 class MailWrapper extends BlockEmbed { }
965 MailWrapper.blotName = 'mailwrapper';
966 MailWrapper.className = 'vbo-editor-hl-mailwrapper';
967 MailWrapper.tagName = 'hr';
968 Quill.register(MailWrapper);
969
970 // register custom Blot for preview
971 class Preview extends Inline { }
972 Preview.blotName = 'preview';
973 Preview.tagName = 'span';
974 Quill.register(Preview);
975
976 // register custom icons for mail-wrapper and preview
977 var icons = Quill.import('ui/icons');
978 icons['mailwrapper'] = '$mail_wrapper_icn';
979 icons['preview'] = '$mail_preview_icn';
980 icons['homelogo'] = '$mail_homelogo_icn';
981 })();
982 JS
983 ;
984
985 /**
986 * Cache the pre-configuration script onto a file to allow AJAX requests
987 * to properly load the script inline within the response. Before this
988 * change the script used to be added as an inline script declaration.
989 *
990 * @since 1.16.7 (J) - 1.6.7 (WP)
991 */
992 $cached_preconfig_script_path = implode(DIRECTORY_SEPARATOR, [VBO_ADMIN_PATH, 'resources', 'quill', 'vik-quill-preconfig-cache.js']);
993 $cached_preconfig_script_uri = VBO_ADMIN_URI . 'resources/quill/vik-quill-preconfig-cache.js';
994 $cached_preconfig_script_ok = is_file($cached_preconfig_script_path);
995
996 if (!$cached_preconfig_script_ok) {
997 // attempt to create the file
998 $cached_preconfig_script_ok = JFile::write($cached_preconfig_script_path, $quill_preconfig_script);
999 }
1000
1001 if ($cached_preconfig_script_ok) {
1002 // load cached script file
1003 $this->addScript($cached_preconfig_script_uri);
1004 } else {
1005 // revert to append JS script declaration to document
1006 $doc->addScriptDeclaration($quill_preconfig_script);
1007 }
1008 }
1009
1010 /**
1011 * Renders a third-party visual editor (Quill).
1012 *
1013 * @param string $name the input name of the textarea field.
1014 * @param string $value the current value of the textarea field/editor.
1015 * @param array $attrs list of associative array attributes for the textarea.
1016 * @param array $opts associative array of options for the editor.
1017 * @param array $btns associative array of custom buttons for the editor (special tags).
1018 *
1019 * @return string the necessary HTML to render the visual editor.
1020 *
1021 * @since 1.15.0 (J) - 1.5.0 (WP)
1022 */
1023 public function renderVisualEditor($name, $value, $attrs = array(), $opts = array(), $btns = array())
1024 {
1025 if (empty($name)) {
1026 return null;
1027 }
1028
1029 // build the AJAX endpoint for uploading files and to preview the message
1030 $upload_endpoint = VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=upload_media_file');
1031 $ajax_preview_mess = VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=mail.preview_visual_editor');
1032 $ajax_logo_url = VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=mail.get_default_logo');
1033
1034 // the HTML to build
1035 $editor = "\n";
1036
1037 // static editor counter
1038 static $editor_counter = 0;
1039
1040 // increase counter for this instance
1041 $editor_counter++;
1042
1043 // build id attributes for text and visual editors
1044 $editor_id = 'vik-contentbuilder-editor-' . $editor_counter;
1045 if (!isset($attrs['id'])) {
1046 $attrs['id'] = 'vik-contentbuilder-tarea-' . $editor_counter;
1047 }
1048
1049 // textarea attributes
1050 $ta_attributes = array();
1051 foreach ($attrs as $aname => $aval) {
1052 if ($aname == 'name') {
1053 // skip reserved attribute name
1054 continue;
1055 }
1056 $ta_attributes[] = $aname . '="' . JHtml::fetch('esc_attr', $aval) . '"';
1057 }
1058
1059 // labels for modes and editor
1060 $text_html_lbl = JText::translate('VBO_MODE_TEXTHTML');
1061 $visual_mode_lbl = JText::translate('VBO_MODE_VISUAL');
1062 JText::script('VBO_CONT_WRAPPER');
1063 JText::script('VBO_CONT_WRAPPER_HELP');
1064
1065 // allowed modes to display the visual editor
1066 $allowed_modes = array(
1067 'text' => $text_html_lbl,
1068 'visual' => $visual_mode_lbl,
1069 'modal-visual' => $visual_mode_lbl,
1070 );
1071
1072 // define the default buttons to display for mode switching
1073 $modes = array(
1074 'text' => $text_html_lbl,
1075 'modal-visual' => $visual_mode_lbl,
1076 );
1077
1078 // overwrite modes to display
1079 if (isset($opts['modes']) && is_array($opts['modes']) && count($opts['modes'])) {
1080 // check if the given array is associative, hence inclusive of texts
1081 if (array_keys($opts['modes']) != range(0, (count($opts['modes']) - 1))) {
1082 // replace modes
1083 $modes = $opts['modes'];
1084 } else {
1085 // only the allowed keys must have been passed
1086 $new_modes = array();
1087 foreach ($opts['modes'] as $mode_type) {
1088 if (!isset($allowed_modes[$mode_type])) {
1089 continue;
1090 }
1091 $new_modes[$mode_type] = $allowed_modes[$mode_type];
1092 }
1093 $modes = count($new_modes) ? $new_modes : $modes;
1094 }
1095 }
1096
1097 // overwrite default mode
1098 if (isset($opts['def_mode']) && isset($modes[$opts['def_mode']]) && count($modes) > 1) {
1099 $def_mode_val = $modes[$opts['def_mode']];
1100 unset($modes[$opts['def_mode']]);
1101 // sort modes accordingly
1102 $modes = array_merge(array($opts['def_mode'] => $def_mode_val), $modes);
1103 // reset the array pointer
1104 reset($modes);
1105 }
1106
1107 // set default mode
1108 $default_mode = key($modes);
1109
1110 // build visual editor JS options
1111 $js_editor_opts = [
1112 'snippetsyntax' => true,
1113 'modules' => [
1114 'toolbar' => [
1115 'container' => [
1116 [
1117 [
1118 'font' => $this->getVisualEditorFonts(true)
1119 ],
1120 ],
1121 [
1122 [
1123 'header' => [1, 2, 3, 4, 5, 6, false]
1124 ]
1125 ],
1126 [
1127 'bold',
1128 'italic',
1129 'underline',
1130 'strike',
1131 'blockquote',
1132 ],
1133 [
1134 ['align' => []],
1135 ['indent' => '-1'],
1136 ['indent' => '+1'],
1137 ],
1138 [
1139 [
1140 'color' => []
1141 ],
1142 [
1143 'background' => []
1144 ],
1145 ],
1146 [
1147 ['list' => 'ordered'],
1148 ['list' => 'bullet'],
1149 ],
1150 [
1151 'link',
1152 'image',
1153 'homelogo',
1154 ],
1155 [
1156 'mailwrapper',
1157 'preview',
1158 ],
1159 ],
1160 ],
1161 'imageResize' => [
1162 'displaySize' => true,
1163 ],
1164 ],
1165 'theme' => 'snow',
1166 ];
1167
1168 // build the list of special tags to be added to the editor
1169 $special_tags_btns = array();
1170 foreach ($btns as $tag_val) {
1171 $special_tags_btns[] = $tag_val;
1172 }
1173
1174 if (count($special_tags_btns)) {
1175 // add custom buttons to the editor to manage special tags
1176 $js_editor_opts['modules']['toolbar']['container'][] = array(
1177 array('specialtags' => $special_tags_btns)
1178 );
1179 // append CSS inline styles
1180 $editor .= '<style type="text/css">' . "\n";
1181 foreach ($special_tags_btns as $tag_val) {
1182 $editor .= '.ql-picker.ql-specialtags .ql-picker-item[data-value="' . $tag_val . '"]:before {
1183 content: "' . $tag_val . '";
1184 }' . "\n";
1185 }
1186 $editor .= '</style>' . "\n";
1187 }
1188
1189 // attept to pretty print a JSON encoded string for the editor options
1190 $editor_opts_str = defined('JSON_PRETTY_PRINT') ? json_encode($js_editor_opts, JSON_PRETTY_PRINT) : json_encode($js_editor_opts);
1191
1192 // safe default value for editor (HTML tags should not be converted to entities)
1193 $safe_value = preg_replace("/(<\/ ?textarea>)+/i", '', $value);
1194
1195 // the HTML to render the visual editor
1196 $html_visual_editor = '<div class="vik-contentbuilder-editor-container" id="' . $editor_id . '"></div>';
1197
1198 // build the actual HTML content
1199 $editor .= '<div class="vik-contentbuilder-wrapper">' . "\n";
1200 if (count($modes) > 1) {
1201 // display buttons to switch mode only if more than one mode available
1202 $editor .= "\t" . '<div class="vik-contentbuilder-switcher">' . "\n";
1203 foreach ($modes as $key => $val) {
1204 if (!isset($allowed_modes[$key])) {
1205 continue;
1206 }
1207 $editor .= '<button type="button" class="btn vik-contentbuilder-switcher-btn' . ($default_mode == $key ? ' vik-contentbuilder-switcher-btn-active' : '') . '" data-switch="' . $key . '" onclick="VikContentBuilder.switchMode(this);">' . $val . '</button>';
1208 }
1209 $editor .= "\t" . '</div>' . "\n";
1210 }
1211 $editor .= "\t" . '<div class="vik-contentbuilder-inner">' . "\n";
1212 $editor .= "\t\t" . '<textarea name="' . $name . '" data-switch="text" ' . implode(' ', $ta_attributes) . ' style="' . ($default_mode != 'text' ? 'display: none;' : '') . '">' . $safe_value . '</textarea>' . "\n";
1213 if (isset($modes['visual'])) {
1214 $editor .= "\t\t" . '<div class="vik-contentbuilder-container" data-switch="visual" style="' . ($default_mode != 'visual' ? 'display: none;' : '') . '">' . "\n";
1215 $editor .= "\t\t\t" . $html_visual_editor . "\n";
1216 $editor .= "\t\t" . '</div>' . "\n";
1217 }
1218 if (isset($modes['modal-visual'])) {
1219 $editor .= "\t\t" . '<div class="vbo-modal-overlay-block vik-contentbuilder-modal" data-switch="modal-visual" data-appendto=".vik-contentbuilder-modal-content" style="' . ($default_mode != 'modal-visual' ? 'display: none;' : '') . '">' . "\n";
1220 $editor .= "\t\t\t" . '<a class="vbo-modal-overlay-close" href="javascript: void(0);" onclick="VikContentBuilder.switchMode(this, \'text\');"></a>' . "\n";
1221 $editor .= "\t\t\t" . '<div class="vbo-modal-overlay-content vik-contentbuilder-modal-content">' . "\n";
1222 $editor .= "\t\t\t\t" . '<div class="vbo-modal-overlay-content-head vik-contentbuilder-modal-head"><span class="vbo-modal-overlay-close-times" onclick="VikContentBuilder.switchMode(this, \'text\');">&times;</span></div>' . "\n";
1223 $editor .= "\t\t\t\t" . '<div class="vbo-modal-overlay-content-body vik-contentbuilder-modal-body">' . "\n";
1224 $editor .= "\t\t\t\t\t" . (!isset($modes['visual']) ? $html_visual_editor : '') . "\n";
1225 $editor .= "\t\t\t\t" . '</div>' . "\n";
1226 $editor .= "\t\t\t" . '</div>' . "\n";
1227 $editor .= "\t\t" . '</div>' . "\n";
1228 }
1229 $editor .= "\t" . '</div>' . "\n";
1230 $editor .= '</div>' . "\n";
1231 $editor .= "\n";
1232
1233 // add JS script to HTML content
1234 $editor .= '<script>' . "\n";
1235 $editor .= 'jQuery(function() {
1236 var vbo_toast_mailwrapper = null;
1237 var visual_editor_handlers = {
1238 "specialtags": function(tag) {
1239 if (tag) {
1240 var cursorPosition = this.quill.getSelection().index;
1241 this.quill.insertText(cursorPosition, tag, "specialtag", "vbo-editor-hl-specialtag");
1242 cursorPosition += tag.length + 1;
1243 this.quill.setSelection(cursorPosition, "silent");
1244 this.quill.insertText(cursorPosition, " ");
1245 this.quill.setSelection(cursorPosition + 1, "silent");
1246 this.quill.deleteText(cursorPosition - 1, 1);
1247 }
1248 },
1249 "image": function(clicked) {
1250 var img_handler = new VikContentBuilderImageHandler(this.quill);
1251 img_handler.setEndpoint("' . $upload_endpoint . '").present();
1252 },
1253 "mailwrapper": function(clicked) {
1254 var range = this.quill.getSelection(true);
1255 this.quill.insertText(range.index, "\n", "user");
1256 this.quill.insertEmbed(range.index + 1, "mailwrapper", true, "user");
1257 this.quill.setSelection(range.index + 2, "silent");
1258 if (!vbo_toast_mailwrapper) {
1259 vbo_toast_mailwrapper = 1;
1260 VBOToast.enqueue(new VBOToastMessage({
1261 title: Joomla.JText._("VBO_CONT_WRAPPER"),
1262 body: Joomla.JText._("VBO_CONT_WRAPPER_HELP"),
1263 icon: "' . VikBookingIcons::i('minus-square') . '",
1264 delay: {
1265 min: 6000,
1266 max: 20000,
1267 tolerance: 4000,
1268 },
1269 action: () => {
1270 VBOToast.dispose(true);
1271 }
1272 }));
1273 }
1274 },
1275 "preview": function(clicked) {
1276 VBOCore.doAjax("' . $ajax_preview_mess . '", {
1277 content: this.quill.root.innerHTML,
1278 bid: (typeof window["vbo_current_bid"] !== "undefined" ? window["vbo_current_bid"] : null)
1279 }, (resp) => {
1280 var pop_win = window.open("", "", "width=800, height=600, scrollbars=yes");
1281 pop_win.document.body.innerHTML = resp[0];
1282 }, (err) => {
1283 console.log(err);
1284 alert(err.responseText);
1285 });
1286 },
1287 "homelogo": function(clicked) {
1288 VBOCore.doAjax("' . $ajax_logo_url . '", {}, (resp) => {
1289 try {
1290 this.quill.insertEmbed(this.quill.getSelection().index, "image", resp.url);
1291 } catch(e) {
1292 alert("Generic logo image error");
1293 }
1294 }, (err) => {
1295 console.log(err);
1296 alert(err.responseText);
1297 });
1298 }
1299 };
1300 var visual_editor_ext_opts = ' . $editor_opts_str . ';
1301 visual_editor_ext_opts["modules"]["toolbar"]["handlers"] = visual_editor_handlers;
1302 var visual_editor = new Quill("#' . $editor_id . '", visual_editor_ext_opts);
1303 var editor_content = jQuery("textarea#' . $attrs['id'] . '").val();
1304 if (editor_content && editor_content.length) {
1305 if (editor_content.indexOf("<") >= 0) {
1306 // replace special tags
1307 editor_content = editor_content.replace(/([^"\']|^)({(?:condition: ?)?[a-z0-9_]{5,64}})([^"\']|$)/g, function(match, before, tag, after) {
1308 return before + "<strong class=\"vbo-editor-hl-specialtag\">" + tag + "</strong>" + after;
1309 });
1310 var editor_delta = visual_editor.clipboard.convert(editor_content);
1311 // set editor HTML content
1312 visual_editor.setContents(editor_delta, "silent");
1313 } else {
1314 // set text content
1315 visual_editor.setText(editor_content, "silent");
1316 }
1317 }
1318 visual_editor.on("text-change", function(delta, source) {
1319 jQuery("textarea#' . $attrs['id'] . '").val(visual_editor.root.innerHTML);
1320 });
1321 jQuery("textarea#' . $attrs['id'] . '").on("change", function() {
1322 var editor_content = jQuery(this).val();
1323 var editor_delta = visual_editor.clipboard.convert(editor_content);
1324 visual_editor.setContents(editor_delta, "silent");
1325 });
1326 try {
1327 // push editor instance to the pool
1328 VikContentBuilder.pushEditor(visual_editor);
1329 } catch(e) {
1330 console.error("Could not push new visual editor instance", e);
1331 }
1332 });' . "\n";
1333 $editor .= '</script>' . "\n";
1334
1335 // return the necessary HTML string to be displayed
1336 return $editor;
1337 }
1338
1339 /**
1340 * Loads the necessary assets to render context menus.
1341 *
1342 * @return void
1343 *
1344 * @since 1.16.0 (J) - 1.6.0 (WP)
1345 */
1346 public function loadContextMenuAssets()
1347 {
1348 static $loaded = null;
1349
1350 if ($loaded) {
1351 return;
1352 }
1353
1354 // get appearance preference
1355 $app_pref = VikBooking::getAppearancePref();
1356
1357 $dark_mode = 'null';
1358 if ($app_pref == 'light') {
1359 $dark_mode = 'false';
1360 } elseif ($app_pref == 'dark') {
1361 $dark_mode = 'true';
1362 }
1363
1364 $this->addScript(VBO_ADMIN_URI . 'resources/contextmenu.js');
1365
1366 $doc = JFactory::getDocument();
1367
1368 $doc->addStyleSheet(VBO_ADMIN_URI . 'resources/contextmenu.css');
1369 $doc->addScriptDeclaration(
1370 <<<JS
1371 (function($) {
1372 'use strict';
1373
1374 $(function() {
1375 $.vboContextMenu.defaults.darkMode = {$dark_mode};
1376 $.vboContextMenu.defaults.class = 'vbo-dropdown-cxmenu';
1377 });
1378 })(jQuery);
1379 JS
1380 );
1381
1382 $loaded = 1;
1383 }
1384
1385 /**
1386 * Loads the assets necessary to render a phone input field.
1387 *
1388 * @return void
1389 *
1390 * @since 1.16.0 (J) - 1.6.0 (WP)
1391 */
1392 public function loadPhoneInputFieldAssets()
1393 {
1394 static $loaded = null;
1395
1396 if ($loaded) {
1397 return;
1398 }
1399
1400 $loaded = 1;
1401
1402 $document = JFactory::getDocument();
1403
1404 $document->addStyleSheet(VBO_SITE_URI . 'resources/intlTelInput.css');
1405 $document->addScript(VBO_SITE_URI . 'resources/intlTelInput.js');
1406 }
1407 }
1408