PluginProbe
VikBooking Hotel Booking Engine & PMS / 1.6.6
VikBooking Hotel Booking Engine & PMS v1.6.6
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.6, at admin/helpers/jv_helper.php

1,351 lines 45.2 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 // access the document
816 $doc = JFactory::getDocument();
817
818 // build the list of font families
819 $font_families = $this->getVisualEditorFonts();
820 $font_shortfam = $this->getVisualEditorFonts(true);
821 $js_font_names = json_encode($font_shortfam);
822 $css_font_decl = '';
823 foreach ($font_families as $k => $font_name) {
824 $font_val = $font_shortfam[$k];
825 $css_font_decl .= '.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="' . $font_val . '"]::before,';
826 $css_font_decl .= '.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="' . $font_val . '"]::before {' . "\n";
827 $css_font_decl .= 'content: "' . $font_name . '";' . "\n";
828 $css_font_decl .= 'font-family: "' . $font_name . '";' . "\n";
829 $css_font_decl .= '}' . "\n";
830 }
831
832 // append inline CSS styles to document
833 $doc->addStyleDeclaration('
834 .ql-picker.ql-specialtags .ql-picker-label {
835 padding-right: 18px;
836 }
837 .ql-picker.ql-specialtags .ql-picker-label:before {
838 content: "' . htmlspecialchars(JText::translate('VBO_CONDTEXT_TKN')) . '";
839 }
840 ' . $css_font_decl);
841
842 // append theme CSS to document
843 $doc->addStyleSheet(VBO_ADMIN_URI . 'resources/quill/quill.snow.css');
844
845 // append JS assets to document
846 $this->addScript(VBO_ADMIN_URI . 'resources/quill/quill.js');
847 $this->addScript(VBO_ADMIN_URI . 'resources/quill/quill-image-resize.min.js');
848 $this->addScript(VBO_ADMIN_URI . 'resources/quill/vik-content-builder.js');
849
850 // icon for mail wrapper
851 $mail_wrapper_icn = '<i class="' . VikBookingIcons::i('minus-square') . '" title="' . htmlspecialchars(JText::translate('VBO_INSERT_CONT_WRAPPER')) . '"></i>';
852
853 // icon for mail preview
854 $mail_preview_icn = '<i class="' . VikBookingIcons::i('eye') . '" title="' . htmlspecialchars(JText::translate('VBOPREVIEW')) . '"></i>';
855
856 // icon for property logo (home icon)
857 $mail_homelogo_icn = '<i class="' . VikBookingIcons::i('hotel') . '" title="' . htmlspecialchars(JText::translate('VBCONFIGFOURLOGO'), ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401) . '"></i>';
858
859 // append JS script declaration to document
860 $doc->addScriptDeclaration(
861 <<<JS
862 // Quill pre-configuration
863 (function() {
864 // configure Quill to use inline styles rather than classes
865
866 var AlignClass = Quill.import('attributors/class/align');
867 Quill.register(AlignClass, true);
868
869 var BackgroundClass = Quill.import('attributors/class/background');
870 Quill.register(BackgroundClass, true);
871
872 var ColorClass = Quill.import('attributors/class/color');
873 Quill.register(ColorClass, true);
874
875 var FontClass = Quill.import('attributors/class/font');
876 Quill.register(FontClass, true);
877
878 var SizeClass = Quill.import('attributors/class/size');
879 Quill.register(SizeClass, true);
880
881 var AlignStyle = Quill.import('attributors/style/align');
882 Quill.register(AlignStyle, true);
883
884 var BackgroundStyle = Quill.import('attributors/style/background');
885 Quill.register(BackgroundStyle, true);
886
887 var ColorStyle = Quill.import('attributors/style/color');
888 Quill.register(ColorStyle, true);
889
890 var SizeStyle = Quill.import('attributors/style/size');
891 Quill.register(SizeStyle, true);
892
893 var FontStyle = Quill.import('attributors/style/font');
894 Quill.register(FontStyle, true);
895
896 // set additional fonts
897 var Font = Quill.import('formats/font');
898 Font.whitelist = $js_font_names;
899 Quill.register(Font, true);
900
901 // register custom Blot for special tags
902 var Inline = Quill.import('blots/inline');
903 class Specialtag extends Inline {
904 static create(value) {
905 let node = super.create(value);
906 if (value) {
907 node.setAttribute('class', value);
908 }
909 return node;
910 }
911
912 static formats(domNode) {
913 return domNode.getAttribute("class");
914 }
915
916 format(name, value) {
917 if (name !== this.statics.blotName || !value) {
918 return super.format(name, value);
919 }
920 if (value) {
921 this.domNode.setAttribute('class', value);
922 }
923 }
924 }
925 Specialtag.blotName = 'specialtag';
926 Specialtag.tagName = 'strong';
927 Quill.register(Specialtag);
928
929 // register custom Blot for mail-wrapper
930 var BlockEmbed = Quill.import('blots/block/embed');
931 class MailWrapper extends BlockEmbed { }
932 MailWrapper.blotName = 'mailwrapper';
933 MailWrapper.className = 'vbo-editor-hl-mailwrapper';
934 MailWrapper.tagName = 'hr';
935 Quill.register(MailWrapper);
936
937 // register custom Blot for preview
938 class Preview extends Inline { }
939 Preview.blotName = 'preview';
940 Preview.tagName = 'span';
941 Quill.register(Preview);
942
943 // register custom icons for mail-wrapper and preview
944 var icons = Quill.import('ui/icons');
945 icons['mailwrapper'] = '$mail_wrapper_icn';
946 icons['preview'] = '$mail_preview_icn';
947 icons['homelogo'] = '$mail_homelogo_icn';
948 })();
949 JS
950 );
951 }
952
953 /**
954 * Renders a third-party visual editor (Quill).
955 *
956 * @param string $name the input name of the textarea field.
957 * @param string $value the current value of the textarea field/editor.
958 * @param array $attrs list of associative array attributes for the textarea.
959 * @param array $opts associative array of options for the editor.
960 * @param array $btns associative array of custom buttons for the editor (special tags).
961 *
962 * @return string the necessary HTML to render the visual editor.
963 *
964 * @since 1.15.0 (J) - 1.5.0 (WP)
965 */
966 public function renderVisualEditor($name, $value, $attrs = array(), $opts = array(), $btns = array())
967 {
968 if (empty($name)) {
969 return null;
970 }
971
972 // build the AJAX endpoint for uploading files and to preview the message
973 $upload_endpoint = VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=upload_media_file');
974 $ajax_preview_mess = VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=mail.preview_visual_editor');
975 $ajax_logo_url = VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=mail.get_default_logo');
976
977 // the HTML to build
978 $editor = "\n";
979
980 // static editor counter
981 static $editor_counter = 0;
982
983 // increase counter for this instance
984 $editor_counter++;
985
986 // build id attributes for text and visual editors
987 $editor_id = 'vik-contentbuilder-editor-' . $editor_counter;
988 if (!isset($attrs['id'])) {
989 $attrs['id'] = 'vik-contentbuilder-tarea-' . $editor_counter;
990 }
991
992 // textarea attributes
993 $ta_attributes = array();
994 foreach ($attrs as $aname => $aval) {
995 if ($aname == 'name') {
996 // skip reserved attribute name
997 continue;
998 }
999 $ta_attributes[] = $aname . '="' . JHtml::fetch('esc_attr', $aval) . '"';
1000 }
1001
1002 // labels for modes and editor
1003 $text_html_lbl = JText::translate('VBO_MODE_TEXTHTML');
1004 $visual_mode_lbl = JText::translate('VBO_MODE_VISUAL');
1005 JText::script('VBO_CONT_WRAPPER');
1006 JText::script('VBO_CONT_WRAPPER_HELP');
1007
1008 // allowed modes to display the visual editor
1009 $allowed_modes = array(
1010 'text' => $text_html_lbl,
1011 'visual' => $visual_mode_lbl,
1012 'modal-visual' => $visual_mode_lbl,
1013 );
1014
1015 // define the default buttons to display for mode switching
1016 $modes = array(
1017 'text' => $text_html_lbl,
1018 'modal-visual' => $visual_mode_lbl,
1019 );
1020
1021 // overwrite modes to display
1022 if (isset($opts['modes']) && is_array($opts['modes']) && count($opts['modes'])) {
1023 // check if the given array is associative, hence inclusive of texts
1024 if (array_keys($opts['modes']) != range(0, (count($opts['modes']) - 1))) {
1025 // replace modes
1026 $modes = $opts['modes'];
1027 } else {
1028 // only the allowed keys must have been passed
1029 $new_modes = array();
1030 foreach ($opts['modes'] as $mode_type) {
1031 if (!isset($allowed_modes[$mode_type])) {
1032 continue;
1033 }
1034 $new_modes[$mode_type] = $allowed_modes[$mode_type];
1035 }
1036 $modes = count($new_modes) ? $new_modes : $modes;
1037 }
1038 }
1039
1040 // overwrite default mode
1041 if (isset($opts['def_mode']) && isset($modes[$opts['def_mode']]) && count($modes) > 1) {
1042 $def_mode_val = $modes[$opts['def_mode']];
1043 unset($modes[$opts['def_mode']]);
1044 // sort modes accordingly
1045 $modes = array_merge(array($opts['def_mode'] => $def_mode_val), $modes);
1046 // reset the array pointer
1047 reset($modes);
1048 }
1049
1050 // set default mode
1051 $default_mode = key($modes);
1052
1053 // build visual editor JS options
1054 $js_editor_opts = [
1055 'snippetsyntax' => true,
1056 'modules' => [
1057 'toolbar' => [
1058 'container' => [
1059 [
1060 [
1061 'font' => $this->getVisualEditorFonts(true)
1062 ],
1063 ],
1064 [
1065 [
1066 'header' => [1, 2, 3, 4, 5, 6, false]
1067 ]
1068 ],
1069 [
1070 'bold',
1071 'italic',
1072 'underline',
1073 'strike',
1074 'blockquote',
1075 ],
1076 [
1077 ['align' => []],
1078 ['indent' => '-1'],
1079 ['indent' => '+1'],
1080 ],
1081 [
1082 [
1083 'color' => []
1084 ],
1085 [
1086 'background' => []
1087 ],
1088 ],
1089 [
1090 ['list' => 'ordered'],
1091 ['list' => 'bullet'],
1092 ],
1093 [
1094 'link',
1095 'image',
1096 'homelogo',
1097 ],
1098 [
1099 'mailwrapper',
1100 'preview',
1101 ],
1102 ],
1103 ],
1104 'imageResize' => [
1105 'displaySize' => true,
1106 ],
1107 ],
1108 'theme' => 'snow',
1109 ];
1110
1111 // build the list of special tags to be added to the editor
1112 $special_tags_btns = array();
1113 foreach ($btns as $tag_val) {
1114 $special_tags_btns[] = $tag_val;
1115 }
1116
1117 if (count($special_tags_btns)) {
1118 // add custom buttons to the editor to manage special tags
1119 $js_editor_opts['modules']['toolbar']['container'][] = array(
1120 array('specialtags' => $special_tags_btns)
1121 );
1122 // append CSS inline styles
1123 $editor .= '<style type="text/css">' . "\n";
1124 foreach ($special_tags_btns as $tag_val) {
1125 $editor .= '.ql-picker.ql-specialtags .ql-picker-item[data-value="' . $tag_val . '"]:before {
1126 content: "' . $tag_val . '";
1127 }' . "\n";
1128 }
1129 $editor .= '</style>' . "\n";
1130 }
1131
1132 // attept to pretty print a JSON encoded string for the editor options
1133 $editor_opts_str = defined('JSON_PRETTY_PRINT') ? json_encode($js_editor_opts, JSON_PRETTY_PRINT) : json_encode($js_editor_opts);
1134
1135 // safe default value for editor (HTML tags should not be converted to entities)
1136 $safe_value = preg_replace("/(<\/ ?textarea>)+/i", '', $value);
1137
1138 // the HTML to render the visual editor
1139 $html_visual_editor = '<div class="vik-contentbuilder-editor-container" id="' . $editor_id . '"></div>';
1140
1141 // build the actual HTML content
1142 $editor .= '<div class="vik-contentbuilder-wrapper">' . "\n";
1143 if (count($modes) > 1) {
1144 // display buttons to switch mode only if more than one mode available
1145 $editor .= "\t" . '<div class="vik-contentbuilder-switcher">' . "\n";
1146 foreach ($modes as $key => $val) {
1147 if (!isset($allowed_modes[$key])) {
1148 continue;
1149 }
1150 $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>';
1151 }
1152 $editor .= "\t" . '</div>' . "\n";
1153 }
1154 $editor .= "\t" . '<div class="vik-contentbuilder-inner">' . "\n";
1155 $editor .= "\t\t" . '<textarea name="' . $name . '" data-switch="text" ' . implode(' ', $ta_attributes) . ' style="' . ($default_mode != 'text' ? 'display: none;' : '') . '">' . $safe_value . '</textarea>' . "\n";
1156 if (isset($modes['visual'])) {
1157 $editor .= "\t\t" . '<div class="vik-contentbuilder-container" data-switch="visual" style="' . ($default_mode != 'visual' ? 'display: none;' : '') . '">' . "\n";
1158 $editor .= "\t\t\t" . $html_visual_editor . "\n";
1159 $editor .= "\t\t" . '</div>' . "\n";
1160 }
1161 if (isset($modes['modal-visual'])) {
1162 $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";
1163 $editor .= "\t\t\t" . '<a class="vbo-modal-overlay-close" href="javascript: void(0);" onclick="VikContentBuilder.switchMode(this, \'text\');"></a>' . "\n";
1164 $editor .= "\t\t\t" . '<div class="vbo-modal-overlay-content vik-contentbuilder-modal-content">' . "\n";
1165 $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";
1166 $editor .= "\t\t\t\t" . '<div class="vbo-modal-overlay-content-body vik-contentbuilder-modal-body">' . "\n";
1167 $editor .= "\t\t\t\t\t" . (!isset($modes['visual']) ? $html_visual_editor : '') . "\n";
1168 $editor .= "\t\t\t\t" . '</div>' . "\n";
1169 $editor .= "\t\t\t" . '</div>' . "\n";
1170 $editor .= "\t\t" . '</div>' . "\n";
1171 }
1172 $editor .= "\t" . '</div>' . "\n";
1173 $editor .= '</div>' . "\n";
1174 $editor .= "\n";
1175
1176 // add JS script to HTML content
1177 $editor .= '<script>' . "\n";
1178 $editor .= 'jQuery(function() {
1179 var vbo_toast_mailwrapper = null;
1180 var visual_editor_handlers = {
1181 "specialtags": function(tag) {
1182 if (tag) {
1183 var cursorPosition = this.quill.getSelection().index;
1184 this.quill.insertText(cursorPosition, tag, "specialtag", "vbo-editor-hl-specialtag");
1185 cursorPosition += tag.length + 1;
1186 this.quill.setSelection(cursorPosition, "silent");
1187 this.quill.insertText(cursorPosition, " ");
1188 this.quill.setSelection(cursorPosition + 1, "silent");
1189 this.quill.deleteText(cursorPosition - 1, 1);
1190 }
1191 },
1192 "image": function(clicked) {
1193 var img_handler = new VikContentBuilderImageHandler(this.quill);
1194 img_handler.setEndpoint("' . $upload_endpoint . '").present();
1195 },
1196 "mailwrapper": function(clicked) {
1197 var range = this.quill.getSelection(true);
1198 this.quill.insertText(range.index, "\n", "user");
1199 this.quill.insertEmbed(range.index + 1, "mailwrapper", true, "user");
1200 this.quill.setSelection(range.index + 2, "silent");
1201 if (!vbo_toast_mailwrapper) {
1202 vbo_toast_mailwrapper = 1;
1203 VBOToast.enqueue(new VBOToastMessage({
1204 title: Joomla.JText._("VBO_CONT_WRAPPER"),
1205 body: Joomla.JText._("VBO_CONT_WRAPPER_HELP"),
1206 icon: "' . VikBookingIcons::i('minus-square') . '",
1207 delay: {
1208 min: 6000,
1209 max: 20000,
1210 tolerance: 4000,
1211 },
1212 action: () => {
1213 VBOToast.dispose(true);
1214 }
1215 }));
1216 }
1217 },
1218 "preview": function(clicked) {
1219 VBOCore.doAjax("' . $ajax_preview_mess . '", {
1220 content: this.quill.root.innerHTML,
1221 bid: (typeof window["vbo_current_bid"] !== "undefined" ? window["vbo_current_bid"] : null)
1222 }, (resp) => {
1223 var pop_win = window.open("", "", "width=800, height=600, scrollbars=yes");
1224 pop_win.document.body.innerHTML = resp[0];
1225 }, (err) => {
1226 console.log(err);
1227 alert(err.responseText);
1228 });
1229 },
1230 "homelogo": function(clicked) {
1231 VBOCore.doAjax("' . $ajax_logo_url . '", {}, (resp) => {
1232 try {
1233 this.quill.insertEmbed(this.quill.getSelection().index, "image", resp.url);
1234 } catch(e) {
1235 alert("Generic logo image error");
1236 }
1237 }, (err) => {
1238 console.log(err);
1239 alert(err.responseText);
1240 });
1241 }
1242 };
1243 var visual_editor_ext_opts = ' . $editor_opts_str . ';
1244 visual_editor_ext_opts["modules"]["toolbar"]["handlers"] = visual_editor_handlers;
1245 var visual_editor = new Quill("#' . $editor_id . '", visual_editor_ext_opts);
1246 var editor_content = jQuery("textarea#' . $attrs['id'] . '").val();
1247 if (editor_content && editor_content.length) {
1248 if (editor_content.indexOf("<") >= 0) {
1249 // replace special tags
1250 editor_content = editor_content.replace(/([^"\']|^)({(?:condition: ?)?[a-z0-9_]{5,64}})([^"\']|$)/g, function(match, before, tag, after) {
1251 return before + "<strong class=\"vbo-editor-hl-specialtag\">" + tag + "</strong>" + after;
1252 });
1253 var editor_delta = visual_editor.clipboard.convert(editor_content);
1254 // set editor HTML content
1255 visual_editor.setContents(editor_delta, "silent");
1256 } else {
1257 // set text content
1258 visual_editor.setText(editor_content, "silent");
1259 }
1260 }
1261 visual_editor.on("text-change", function(delta, source) {
1262 jQuery("textarea#' . $attrs['id'] . '").val(visual_editor.root.innerHTML);
1263 });
1264 jQuery("textarea#' . $attrs['id'] . '").on("change", function() {
1265 var editor_content = jQuery(this).val();
1266 var editor_delta = visual_editor.clipboard.convert(editor_content);
1267 visual_editor.setContents(editor_delta, "silent");
1268 });
1269 try {
1270 // push editor instance to the pool
1271 VikContentBuilder.pushEditor(visual_editor);
1272 } catch(e) {
1273 console.error("Could not push new visual editor instance", e);
1274 }
1275 });' . "\n";
1276 $editor .= '</script>' . "\n";
1277
1278 // return the necessary HTML string to be displayed
1279 return $editor;
1280 }
1281
1282 /**
1283 * Loads the necessary assets to render context menus.
1284 *
1285 * @return void
1286 *
1287 * @since 1.16.0 (J) - 1.6.0 (WP)
1288 */
1289 public function loadContextMenuAssets()
1290 {
1291 static $loaded = null;
1292
1293 if ($loaded) {
1294 return;
1295 }
1296
1297 // get appearance preference
1298 $app_pref = VikBooking::getAppearancePref();
1299
1300 $dark_mode = 'null';
1301 if ($app_pref == 'light') {
1302 $dark_mode = 'false';
1303 } elseif ($app_pref == 'dark') {
1304 $dark_mode = 'true';
1305 }
1306
1307 $this->addScript(VBO_ADMIN_URI . 'resources/contextmenu.js');
1308
1309 $doc = JFactory::getDocument();
1310
1311 $doc->addStyleSheet(VBO_ADMIN_URI . 'resources/contextmenu.css');
1312 $doc->addScriptDeclaration(
1313 <<<JS
1314 (function($) {
1315 'use strict';
1316
1317 $(function() {
1318 $.vboContextMenu.defaults.darkMode = {$dark_mode};
1319 $.vboContextMenu.defaults.class = 'vbo-dropdown-cxmenu';
1320 });
1321 })(jQuery);
1322 JS
1323 );
1324
1325 $loaded = 1;
1326 }
1327
1328 /**
1329 * Loads the assets necessary to render a phone input field.
1330 *
1331 * @return void
1332 *
1333 * @since 1.16.0 (J) - 1.6.0 (WP)
1334 */
1335 public function loadPhoneInputFieldAssets()
1336 {
1337 static $loaded = null;
1338
1339 if ($loaded) {
1340 return;
1341 }
1342
1343 $loaded = 1;
1344
1345 $document = JFactory::getDocument();
1346
1347 $document->addStyleSheet(VBO_SITE_URI . 'resources/intlTelInput.css');
1348 $document->addScript(VBO_SITE_URI . 'resources/intlTelInput.js');
1349 }
1350 }
1351