PluginProbe
VikBooking Hotel Booking Engine & PMS / 1.7.2
VikBooking Hotel Booking Engine & PMS v1.7.2
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 / widgets / bulk_messaging.php

bulk_messaging.php in VikBooking Hotel Booking Engine & PMS 1.7.2, at admin/helpers/widgets/bulk_messaging.php

1,259 lines 45.5 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 * Class handler for admin widget "bulk messaging".
15 *
16 * @since 1.16.7 (J) - 1.6.7 (WP)
17 */
18 class VikBookingAdminWidgetBulkMessaging extends VikBookingAdminWidget
19 {
20 /**
21 * The instance counter of this widget. Since we do not load individual parameters
22 * for each widget's instance, we use a static counter to determine its settings.
23 *
24 * @var int
25 */
26 protected static $instance_counter = -1;
27
28 /**
29 * Class constructor will define the widget name and identifier.
30 */
31 public function __construct()
32 {
33 // call parent constructor
34 parent::__construct();
35
36 $this->widgetName = JText::translate('VBO_W_BULKMESSAGING_TITLE');
37 $this->widgetDescr = JText::translate('VBO_W_BULKMESSAGING_DESCR');
38 $this->widgetId = basename(__FILE__, '.php');
39
40 // define widget and icon and style name
41 $this->widgetIcon = '<i class="' . VikBookingIcons::i('bullhorn') . '"></i>';
42 $this->widgetStyleName = 'yellow';
43
44 // load widget's settings
45 $this->widgetSettings = $this->loadSettings();
46 if (!is_object($this->widgetSettings)) {
47 $this->widgetSettings = new stdClass;
48 }
49 }
50
51 /**
52 * Custom method for this widget only to load the reservations.
53 * The method is called by the admin controller through an AJAX request.
54 * The visibility should be public, it should not exit the process, and
55 * any content sent to output will be returned to the AJAX response.
56 * In this case we return an array because this method requires "return":1.
57 */
58 public function loadBookings()
59 {
60 // get today's date
61 $today_ymd = date('Y-m-d');
62
63 $wrapper = VikRequest::getString('wrapper', '', 'request');
64 $type = VikRequest::getString('type', 'stayover', 'request');
65 $from_dt = VikRequest::getString('from_dt', $today_ymd, 'request');
66 $to_dt = VikRequest::getString('to_dt', '', 'request') ?: $from_dt;
67
68 if (empty($from_dt)) {
69 VBOHttpDocument::getInstance()->close(500, JText::translate('VBO_PLEASE_FILL_FIELDS'));
70 }
71
72 // get date timestamps
73 $from_ts = VikBooking::getDateTimestamp($from_dt, 0, 0);
74 $to_ts = VikBooking::getDateTimestamp($to_dt, 23, 59, 59);
75
76 // query the db
77 $dbo = JFactory::getDbo();
78
79 $q = $dbo->getQuery(true)
80 ->select($dbo->qn('o') . '.*')
81 ->select($dbo->qn('co.idcustomer'))
82 ->select('CONCAT_WS(" ", ' . $dbo->qn('c.first_name') . ', ' . $dbo->qn('c.last_name') . ') AS ' . $dbo->qn('customer_fullname'))
83 ->select($dbo->qn('c.country', 'customer_country'))
84 ->select($dbo->qn('c.pic'))
85 ->from($dbo->qn('#__vikbooking_orders', 'o'))
86 ->leftJoin($dbo->qn('#__vikbooking_customers_orders', 'co') . ' ON ' . $dbo->qn('co.idorder') . ' = ' . $dbo->qn('o.id'))
87 ->leftJoin($dbo->qn('#__vikbooking_customers', 'c') . ' ON ' . $dbo->qn('c.id') . ' = ' . $dbo->qn('co.idcustomer'))
88 ->where($dbo->qn('o.closure') . ' = 0');
89
90 if ($type == 'arrival') {
91 $q->where($dbo->qn('o.checkin') . ' >= ' . $from_ts);
92 $q->where($dbo->qn('o.checkin') . ' <= ' . $to_ts);
93 } elseif ($type == 'departure') {
94 $q->where($dbo->qn('o.checkout') . ' >= ' . $from_ts);
95 $q->where($dbo->qn('o.checkout') . ' <= ' . $to_ts);
96 } elseif ($type == 'bookdate') {
97 $q->where($dbo->qn('o.ts') . ' >= ' . $from_ts);
98 $q->where($dbo->qn('o.ts') . ' <= ' . $to_ts);
99 } else {
100 // stayover
101 $q->where('(
102 (' . $dbo->qn('o.checkin') . ' >= ' . $from_ts . ' AND ' . $dbo->qn('o.checkin') . ' <= ' . $to_ts . ')
103 OR
104 (' . $dbo->qn('o.checkout') . ' >= ' . $from_ts . ' AND ' . $dbo->qn('o.checkout') . ' <= ' . $to_ts . ')
105 )');
106 }
107
108 $dbo->setQuery($q);
109 $bookings = $dbo->loadAssocList();
110
111 // total checkboxes checked
112 $tot_checked = 0;
113
114 // first booking ID "checked"
115 $first_checked_bid = 0;
116 $widget_id = $this->widgetId;
117
118 // start output buffering
119 ob_start();
120
121 if (!$bookings) {
122 ?>
123 <p class="info"><?php echo JText::translate('VBNOORDERSFOUND'); ?></p>
124 <?php
125 } else {
126 // display all bookings of this day
127 foreach ($bookings as $ind => $booking) {
128 // get channel logo and other details
129 $ch_logo_obj = VikBooking::getVcmChannelsLogo($booking['channel'], true);
130 $channel_logo = is_object($ch_logo_obj) ? $ch_logo_obj->getSmallLogoURL() : '';
131 $nights_lbl = $booking['days'] > 1 ? JText::translate('VBDAYS') : JText::translate('VBDAY');
132 $rooms_lbl = !empty($booking['roomsnum']) && $booking['roomsnum'] > 1 ? ', ' . $booking['roomsnum'] . ' ' . JText::translate('VBPVIEWORDERSTHREE') : '';
133
134 // compose customer name
135 $customer_name = !empty($booking['customer_fullname']) ? $booking['customer_fullname'] : '';
136 if ($booking['closure'] > 0 || !strcasecmp($booking['custdata'], JText::translate('VBDBTEXTROOMCLOSED'))) {
137 $customer_name = '<span class="vbordersroomclosed"><i class="' . VikBookingIcons::i('ban') . '"></i> ' . JText::translate('VBDBTEXTROOMCLOSED') . '</span>';
138 }
139 if (empty($customer_name)) {
140 $customer_name = VikBooking::getFirstCustDataField($booking['custdata']);
141 }
142
143 // customer country flag
144 $customer_country = '';
145 $customer_cflag = '';
146 if (!empty($booking['customer_country'])) {
147 $customer_country = $booking['customer_country'];
148 } elseif (!empty($booking['country'])) {
149 $customer_country = $booking['country'];
150 }
151 if ($customer_country && is_file(VBO_ADMIN_PATH . DIRECTORY_SEPARATOR . 'resources' . DIRECTORY_SEPARATOR . 'countries' . DIRECTORY_SEPARATOR . $customer_country . '.png')) {
152 $customer_cflag = '<img src="'.VBO_ADMIN_URI.'resources/countries/' . $customer_country . '.png'.'" title="' . htmlspecialchars($customer_country) . '" class="vbo-country-flag vbo-country-flag-left"/>';
153 }
154
155 // check for any previous event triggered by this widget for the current reservation
156 $last_notified = null;
157 $history_obj = VikBooking::getBookingHistoryInstance($booking['id']);
158 $prev_ev_data = $history_obj->getEventsWithData('CE', function($data) use ($widget_id) {
159 return (is_object($data) && !empty($data->widget) && $data->widget == $widget_id);
160 }, $onlydata = false);
161 if (is_array($prev_ev_data) && $prev_ev_data) {
162 $last_notified = $prev_ev_data[0]['dt'];
163 }
164
165 // default checked status
166 $booking_is_checked = ($booking['status'] == 'confirmed' && !$last_notified);
167 if ($booking_is_checked) {
168 $tot_checked++;
169 if (!$first_checked_bid) {
170 $first_checked_bid = $booking['id'];
171 }
172 }
173
174 ?>
175 <div class="vbo-dashboard-guest-activity vbo-widget-bulkmess-reservation" data-type="<?php echo $booking['status']; ?>" data-resid="<?php echo $booking['id']; ?>">
176 <div class="vbo-widget-bulkmess-ckbox">
177 <input type="checkbox" value="<?php echo $booking['id']; ?>" <?php echo $booking_is_checked ? 'checked ' : ''; ?>/>
178 </div>
179 <div class="vbo-dashboard-guest-activity-avatar">
180 <?php
181 if (!empty($channel_logo)) {
182 // channel logo has got the highest priority
183 ?>
184 <img class="vbo-dashboard-guest-activity-avatar-profile" src="<?php echo $channel_logo; ?>" />
185 <?php
186 } elseif (!empty($booking['pic'])) {
187 // customer profile picture
188 ?>
189 <img class="vbo-dashboard-guest-activity-avatar-profile" src="<?php echo strpos($booking['pic'], 'http') === 0 ? $booking['pic'] : VBO_SITE_URI . 'resources/uploads/' . $booking['pic']; ?>" />
190 <?php
191 } else {
192 // we use an icon as fallback
193 VikBookingIcons::e('hotel', 'vbo-dashboard-guest-activity-avatar-icon');
194 }
195 ?>
196 </div>
197 <div class="vbo-dashboard-guest-activity-content">
198 <div class="vbo-dashboard-guest-activity-content-head">
199 <div class="vbo-dashboard-guest-activity-content-info-details">
200 <h4><?php echo $customer_name . $customer_cflag; ?></h4>
201 <div class="vbo-dashboard-guest-activity-content-info-icon">
202 <?php
203 if ($booking['status'] == 'cancelled') {
204 ?>
205 <span class="badge badge-danger"><?php echo JText::translate('VBCANCELLED'); ?></span>
206 <?php
207 } elseif ($booking['status'] == 'standby') {
208 ?>
209 <span class="badge badge-warning"><?php echo JText::translate('VBSTANDBY'); ?></span>
210 <?php
211 }
212 ?>
213 <span><?php VikBookingIcons::e('plane-arrival'); ?> <?php echo date(str_replace("/", $this->datesep, $this->df), $booking['checkin']); ?> - <?php echo $booking['days'] . ' ' . $nights_lbl . $rooms_lbl; ?></span>
214 <?php
215 if ($last_notified) {
216 ?>
217 <span class="vbo-widget-bulkmess-notified vbo-tooltip vbo-tooltip-top" data-tooltiptext="<?php echo JHtml::fetch('esc_attr', JText::translate('VBOBOOKHISTORYTCE')); ?>"><?php VikBookingIcons::e('envelope'); ?> <?php echo JHtml::fetch('date', $hist['dt'], 'Y-m-d H:i:s'); ?></span>
218 <?php
219 }
220 ?>
221 </div>
222 </div>
223 <div class="vbo-dashboard-guest-activity-content-info-date">
224 <span class="vbo-widget-bulkmess-openbook" onclick="vboWidgetBulkMessOpenBooking('<?php echo $booking['id']; ?>');">
225 <span class="label label-info"><?php VikBookingIcons::e('eye'); ?> <?php echo $booking['id']; ?></span>
226 </span>
227 <span><?php echo date(str_replace("/", $this->datesep, $this->df) . ' H:i', $booking['ts']); ?></span>
228 </div>
229 </div>
230 </div>
231 </div>
232 <?php
233 }
234 }
235
236 ?>
237 <script type="text/javascript">
238
239 jQuery(function() {
240
241 /**
242 * Register the first checked booking ID to let the mail preview work.
243 */
244 if (typeof window['vbo_current_bid'] === 'undefined') {
245 window['vbo_current_bid'] = '<?php echo $first_checked_bid; ?>';
246 }
247
248 /**
249 * Prepare the elements to toggle the checkbox on click.
250 */
251 jQuery('#<?php echo $wrapper; ?>').find('.vbo-widget-bulkmess-reservation').on('click', function(e) {
252 if (jQuery(e.target).hasClass('.vbo-widget-bulkmess-openbook') || jQuery(e.target).closest('.vbo-widget-bulkmess-openbook').length) {
253 return;
254 }
255
256 if (!jQuery(e.target).is('input[type="checkbox"]')) {
257 var ckbox = jQuery(this).find('input[type="checkbox"]');
258 if (ckbox.prop('checked')) {
259 ckbox.prop('checked', false);
260 } else {
261 ckbox.prop('checked', true);
262 }
263 }
264
265 // update total checked count
266 var checked_stats = vboWidgetBulkMessCountChecked('<?php echo $wrapper; ?>');
267
268 // update status on bookings step
269 jQuery('#<?php echo $wrapper; ?>').find('.vbo-widget-bulkmess-step-bookings').find('.vbo-widget-bulkmess-step-status').text(checked_stats['tot_checked'] + ' / ' + checked_stats['tot_bookings']);
270 });
271
272 });
273
274 </script>
275 <?php
276
277 // get the HTML buffer
278 $html_content = ob_get_contents();
279 ob_end_clean();
280
281 // return an associative array of values
282 return [
283 'html' => $html_content,
284 'tot_bookings' => count($bookings),
285 'tot_checked' => $tot_checked,
286 ];
287 }
288
289 /**
290 * Custom method for this widget only to send the communication message.
291 * The method is called by the admin controller through an AJAX request.
292 * The visibility should be public, it should not exit the process, and
293 * any content sent to output will be returned to the AJAX response.
294 * In this case we return an array because this method requires "return":1.
295 */
296 public function sendOneMessage()
297 {
298 $wrapper = VikRequest::getString('wrapper', '', 'request');
299 $bid = VikRequest::getInt('bid', 0, 'request');
300 $index = VikRequest::getInt('index', 0, 'request');
301 $message = VikRequest::getString('message', '', 'request', VIKREQUEST_ALLOWHTML);
302 $subject = VikRequest::getString('subject', '', 'request');
303
304 $booking = VikBooking::getBookingInfoFromID($bid);
305
306 if (!$booking) {
307 VBOHttpDocument::getInstance()->close(500, JText::translate('VBPEDITBUSYONE'));
308 }
309
310 if (empty($message)) {
311 VBOHttpDocument::getInstance()->close(500, JText::translate('VBO_PLEASE_FILL_FIELDS'));
312 }
313
314 if ($index === 0) {
315 // update widget's settings with last message and subject
316 $this->widgetSettings->last_subject = $subject;
317 $this->widgetSettings->last_message = $message;
318 $this->updateSettings(json_encode($this->widgetSettings));
319 }
320
321 // determine the message dispatching method for this booking
322 $sending_method = 'eMail';
323
324 // check if support for OTA messaging is available
325 $ota_messaging_supported = class_exists('VCMChatMessaging');
326
327 if ($ota_messaging_supported && VCMChatMessaging::getInstance($booking)->supportsOtaMessaging($mandatory = true)) {
328 // set flag to identify an OTA messaging notification over the regular email
329 $sending_method = 'Message';
330 } elseif (preg_match("/^no-email-[^@]+@[a-z0-9\.]+\.com$/i", $booking['custmail'])) {
331 // false email address, typical of Airbnb, ignore reservation record
332 // this statement should be entered only if VCM is outdated, or the Guest Messaging API will be used
333 VBOHttpDocument::getInstance()->close(500, 'The booking ID ' . $booking['id'] . ' does not have a valid email address that can be notified.');
334 }
335
336 if ($sending_method === 'eMail' && empty($booking['custmail'])) {
337 VBOHttpDocument::getInstance()->close(500, 'The booking ID ' . $booking['id'] . ' is missing the guest email address.');
338 }
339
340 // language translation
341 $lang = JFactory::getLanguage();
342 $vbo_tn = VikBooking::getTranslator();
343 $vbo_tn::$force_tolang = null;
344 $website_def_lang = $vbo_tn->getDefaultLang();
345 if (!empty($booking['lang'])) {
346 if ($lang->getTag() != $booking['lang']) {
347 if (VBOPlatformDetection::isWordPress()) {
348 // wp
349 $lang->load('com_vikbooking', VIKBOOKING_SITE_LANG, $booking['lang'], true);
350 $lang->load('com_vikbooking', VIKBOOKING_ADMIN_LANG, $booking['lang'], true);
351 } else {
352 // J
353 $lang->load('com_vikbooking', JPATH_SITE, $booking['lang'], true);
354 $lang->load('com_vikbooking', JPATH_ADMINISTRATOR, $booking['lang'], true);
355 $lang->load('joomla', JPATH_SITE, $booking['lang'], true);
356 $lang->load('joomla', JPATH_ADMINISTRATOR, $booking['lang'], true);
357 }
358 }
359 if ($website_def_lang != $booking['lang']) {
360 // force the translation to start because contents should be translated
361 $vbo_tn::$force_tolang = $booking['lang'];
362 }
363 }
364
365 // dispatch the message to the guest reservation
366 if ($sending_method === 'eMail') {
367 // regular email notification
368 $message = $this->notifyThroughEmail($booking, $message, $subject);
369 } else {
370 // notification through guest messaging API
371 $message = $this->notifyThroughMessage($booking, $message);
372 }
373
374 // update history for this booking with the information about the communication sent
375 VikBooking::getBookingHistoryInstance($booking['id'])
376 ->setExtraData(['widget' => $this->widgetId])
377 ->store('CE', $message);
378
379 // return an associative array of values
380 return [
381 'result' => 1,
382 ];
383 }
384
385 /**
386 * Preload the necessary CSS/JS assets.
387 *
388 * @return void
389 */
390 public function preload()
391 {
392 // load assets
393 $this->vbo_app->loadDatePicker();
394
395 if (VBOPlatformDetection::isJoomla()) {
396 // load assets
397 $this->vbo_app->loadVisualEditorAssets();
398 }
399
400 // JS lang defs
401 JText::script('VBO_PLEASE_SELECT');
402 JText::script('VBPVIEWORDERSPEOPLE');
403 JText::script('VBO_WANT_PROCEED');
404 JText::script('VBO_CONT_WRAPPER');
405 JText::script('VBO_CONT_WRAPPER_HELP');
406 }
407
408 /**
409 * Main method to invoke the widget. Contents will be loaded
410 * through AJAX requests, not via PHP when the page loads.
411 *
412 * @param VBOMultitaskData $data
413 *
414 * @return void
415 */
416 public function render(VBOMultitaskData $data = null)
417 {
418 // increase widget's instance counter
419 static::$instance_counter++;
420
421 // check whether the widget is being rendered via AJAX when adding it through the customizer
422 $is_ajax = $this->isAjaxRendering();
423
424 // generate a unique ID for the sticky notes wrapper instance
425 $wrapper_instance = !$is_ajax ? static::$instance_counter : rand();
426 $wrapper_id = 'vbo-widget-bulkmess-' . $wrapper_instance;
427
428 // get permissions
429 $vbo_auth_bookings = JFactory::getUser()->authorise('core.vbo.bookings', 'com_vikbooking');
430 if (!$vbo_auth_bookings) {
431 // display nothing
432 return;
433 }
434
435 // date format
436 $dtpicker_df = $this->getDateFormat('jui');
437
438 // build the default message subject
439 $def_subject = !empty($this->widgetSettings->last_subject) ? $this->widgetSettings->last_subject : JText::sprintf('VBOMAILSUBJECT', VikBooking::getFrontTitle());
440
441 ?>
442 <div id="<?php echo $wrapper_id; ?>" class="vbo-admin-widget-wrapper" data-instance="<?php echo $wrapper_instance; ?>">
443 <div class="vbo-admin-widget-head">
444 <div class="vbo-admin-widget-head-inline">
445 <h4><?php echo $this->widgetIcon; ?> <span><?php echo $this->widgetName; ?></span></h4>
446 </div>
447 </div>
448 <div class="vbo-widget-bulkmess-wrap">
449 <div class="vbo-widget-bulkmess-steps">
450
451 <div class="vbo-widget-bulkmess-step vbo-widget-bulkmess-step-search">
452 <div class="vbo-widget-bulkmess-step-title" onclick="vboWidgetBulkMessToggleStep('<?php echo $wrapper_id; ?>', 'search');">
453 <h4><?php VikBookingIcons::e('calendar'); ?> <?php echo JText::translate('VBODASHSEARCHKEYS'); ?></h4>
454 <span class="vbo-widget-bulkmess-step-status"></span>
455 </div>
456 <div class="vbo-widget-bulkmess-step-content">
457 <div class="vbo-admin-container vbo-admin-container-full vbo-admin-container-compact">
458 <div class="vbo-params-wrap">
459 <div class="vbo-params-container">
460
461 <div class="vbo-param-container">
462 <div class="vbo-param-label"><?php echo JText::translate('VBNEWRESTRICTIONDFROMRANGE'); ?></div>
463 <div class="vbo-param-setting">
464 <div class="vbo-field-calendar">
465 <div class="input-append">
466 <input type="text" class="vbo-widget-bulkmess-fromdt" value="" autocomplete="off" />
467 <button type="button" class="btn btn-secondary vbo-widget-bulkmess-fromdt-trigger"><?php VikBookingIcons::e('calendar'); ?></button>
468 </div>
469 </div>
470 </div>
471 </div>
472
473 <div class="vbo-param-container">
474 <div class="vbo-param-label"><?php echo JText::translate('VBNEWRESTRICTIONDTORANGE'); ?></div>
475 <div class="vbo-param-setting">
476 <div class="vbo-field-calendar">
477 <div class="input-append">
478 <input type="text" class="vbo-widget-bulkmess-todt" value="" autocomplete="off" />
479 <button type="button" class="btn btn-secondary vbo-widget-bulkmess-todt-trigger"><?php VikBookingIcons::e('calendar'); ?></button>
480 </div>
481 </div>
482 </div>
483 </div>
484
485 <div class="vbo-param-container">
486 <div class="vbo-param-label"><?php echo JText::translate('VBPSHOWSEASONSTHREE'); ?></div>
487 <div class="vbo-param-setting">
488 <select class="vbo-widget-bulkmess-type">
489 <option value="stayover"><?php echo JText::translate('VBOTYPESTAYOVER'); ?></option>
490 <option value="arrival"><?php echo JText::translate('VBOTYPEARRIVAL'); ?></option>
491 <option value="departure"><?php echo JText::translate('VBOTYPEDEPARTURE'); ?></option>
492 <option value="bookdate"><?php echo JText::translate('VBPEDITBUSYTWO'); ?></option>
493 </select>
494 </div>
495 </div>
496
497 <div class="vbo-param-container vbo-param-confirm-btn">
498 <div class="vbo-param-label"></div>
499 <div class="vbo-param-setting">
500 <button type="button" class="btn btn-primary vbo-btn-wide vbo-widget-bulkmess-loadbtn" onclick="vboWidgetBulkMessLoadBookings('<?php echo $wrapper_id; ?>');"><span><?php echo JText::translate('VBJQCALNEXT'); ?></span> <?php VikBookingIcons::e('chevron-right'); ?></button>
501 </div>
502 </div>
503
504 </div>
505 </div>
506 </div>
507 </div>
508 </div>
509
510 <div class="vbo-widget-bulkmess-step vbo-widget-bulkmess-step-hidden vbo-widget-bulkmess-step-bookings">
511 <div class="vbo-widget-bulkmess-step-title" onclick="vboWidgetBulkMessToggleStep('<?php echo $wrapper_id; ?>', 'bookings');">
512 <h4><?php VikBookingIcons::e('users'); ?> <?php echo JText::translate('VBMENUCUSTOMERS'); ?></h4>
513 <span class="vbo-widget-bulkmess-step-status"></span>
514 </div>
515 <div class="vbo-widget-bulkmess-step-content">
516 <div class="vbo-widget-bulkmess-bookings-actions">
517 <button type="button" class="btn btn-small" onclick="vboWidgetBulkMessSelectAll('<?php echo $wrapper_id; ?>');"><?php echo JText::translate('VBINVSELECTALL'); ?></button>
518 </div>
519 <div class="vbo-dashboard-guests-latest vbo-widget-bulkmess-bookings-list">
520 <p class="info"><?php echo JText::translate('VBNOORDERSFOUND'); ?></p>
521 </div>
522 <div class="vbo-widget-bulkmess-gonext" style="display: none;">
523 <button type="button" class="btn btn-primary vbo-btn-wide" onclick="vboWidgetBulkMessGoNext('<?php echo $wrapper_id; ?>', 'message');"><span><?php echo JText::translate('VBJQCALNEXT'); ?></span> <?php VikBookingIcons::e('chevron-right'); ?></button>
524 </div>
525 </div>
526 </div>
527
528 <div class="vbo-widget-bulkmess-step vbo-widget-bulkmess-step-hidden vbo-widget-bulkmess-step-message">
529 <div class="vbo-widget-bulkmess-step-title" onclick="vboWidgetBulkMessToggleStep('<?php echo $wrapper_id; ?>', 'message');">
530 <h4><?php VikBookingIcons::e('envelope'); ?> <?php echo JText::translate('VBSENDEMAILCUSTCONT'); ?></h4>
531 <span class="vbo-widget-bulkmess-step-status"></span>
532 </div>
533 <div class="vbo-widget-bulkmess-step-content">
534 <div class="vbo-admin-container vbo-admin-container-full vbo-admin-container-compact">
535 <div class="vbo-params-wrap">
536 <div class="vbo-params-container">
537
538 <div class="vbo-param-container">
539 <div class="vbo-param-label"><?php echo JText::translate('VBSENDEMAILCUSTSUBJ'); ?></div>
540 <div class="vbo-param-setting">
541 <input type="text" class="vbo-widget-bulkmess-subject" value="<?php echo JHtml::fetch('esc_attr', $def_subject); ?>" autocomplete="off" />
542 </div>
543 </div>
544
545 </div>
546 </div>
547 </div>
548
549 <div class="vbo-widget-bulkmess-message">
550 <?php echo $this->renderEditor(); ?>
551 </div>
552 <div class="vbo-widget-bulkmess-gonext">
553 <button type="button" class="btn btn-primary vbo-btn-wide" onclick="vboWidgetBulkMessGoNext('<?php echo $wrapper_id; ?>', 'send');"><span><?php echo JText::translate('VBO_SEND'); ?></span> <?php VikBookingIcons::e('chevron-right'); ?></button>
554 </div>
555 </div>
556 </div>
557
558 <div class="vbo-widget-bulkmess-step vbo-widget-bulkmess-step-hidden vbo-widget-bulkmess-step-send">
559 <div class="vbo-widget-bulkmess-step-title" onclick="vboWidgetBulkMessToggleStep('<?php echo $wrapper_id; ?>', 'send');">
560 <h4><?php VikBookingIcons::e('rocket'); ?> <?php echo JText::translate('VBO_SEND_MESSAGES'); ?></h4>
561 <span class="vbo-widget-bulkmess-step-status"></span>
562 </div>
563 <div class="vbo-widget-bulkmess-step-content">
564 <div class="vbo-widget-bulkmess-progress">
565 <div class="vbo-widget-bulkmess-progress-inner">
566 <progress value="0" max="100">0 / 0</progress>
567 </div>
568 </div>
569 </div>
570 </div>
571
572 </div>
573 </div>
574 </div>
575 <?php
576
577 if (static::$instance_counter === 0 || $is_ajax) {
578 /**
579 * Print the JS code only once for all instances of this widget.
580 * The real rendering is made through AJAX, not when the page loads.
581 */
582 ?>
583
584 <script type="text/javascript">
585
586 var vbo_widget_bulkmess_icn_load = '<?php echo VikBookingIcons::i('spinner', 'fa-spin fa-fw'); ?>';
587 var vbo_widget_bulkmess_icn_next = '<?php echo VikBookingIcons::i('chevron-right'); ?>';
588
589 /**
590 * Perform the request to load the bookings.
591 */
592 function vboWidgetBulkMessLoadBookings(wrapper) {
593 var widget_instance = jQuery('#' + wrapper);
594 if (!widget_instance.length) {
595 return false;
596 }
597
598 // get vars for making the request
599 var from_dt = widget_instance.find('.vbo-widget-bulkmess-fromdt').val();
600 var to_dt = widget_instance.find('.vbo-widget-bulkmess-todt').val();
601 var type = widget_instance.find('.vbo-widget-bulkmess-type').val();
602 var typelbl = widget_instance.find('.vbo-widget-bulkmess-type').find('option:selected').text();
603 typelbl = typelbl ? typelbl : 'Stayover';
604
605 // set loading icon
606 widget_instance.find('.vbo-widget-bulkmess-step-search')
607 .find('.vbo-widget-bulkmess-loadbtn')
608 .prop('disabled', true)
609 .find('i')
610 .attr('class', vbo_widget_bulkmess_icn_load);
611
612 // the widget method to call
613 var call_method = 'loadBookings';
614
615 // make a request to load the bookings
616 VBOCore.doAjax(
617 "<?php echo $this->getExecWidgetAjaxUri(); ?>",
618 {
619 widget_id: "<?php echo $this->getIdentifier(); ?>",
620 call: call_method,
621 return: 1,
622 from_dt: from_dt,
623 to_dt: to_dt,
624 type: type,
625 wrapper: wrapper,
626 tmpl: "component"
627 },
628 (response) => {
629 try {
630 var obj_res = typeof response === 'string' ? JSON.parse(response) : response;
631 if (!obj_res.hasOwnProperty(call_method)) {
632 console.error('Unexpected JSON response', obj_res);
633 return false;
634 }
635
636 // replace HTML with new bookings calendar
637 widget_instance.find('.vbo-widget-bulkmess-bookings-list').html(obj_res[call_method]['html']);
638
639 // update search step status
640 widget_instance.find('.vbo-widget-bulkmess-step-search').find('.vbo-widget-bulkmess-step-status').text(typelbl);
641
642 // check if we got results
643 if (obj_res[call_method]['tot_bookings'] > 0) {
644 // hide all steps except the bookings list
645 widget_instance.find('.vbo-widget-bulkmess-step').not('.vbo-widget-bulkmess-step-bookings').addClass('vbo-widget-bulkmess-step-hidden');
646 // show the button to go next
647 widget_instance.find('.vbo-widget-bulkmess-step-bookings').find('.vbo-widget-bulkmess-gonext').show();
648 } else {
649 // hide the button to go next
650 widget_instance.find('.vbo-widget-bulkmess-step-bookings').find('.vbo-widget-bulkmess-gonext').hide();
651 }
652
653 // display the bookings list step
654 widget_instance.find('.vbo-widget-bulkmess-step-bookings').removeClass('vbo-widget-bulkmess-step-hidden');
655
656 // update bookings step status
657 widget_instance.find('.vbo-widget-bulkmess-step-bookings').find('.vbo-widget-bulkmess-step-status').text(obj_res[call_method]['tot_checked'] + ' / ' + obj_res[call_method]['tot_bookings']);
658
659 // restore next icon
660 widget_instance.find('.vbo-widget-bulkmess-step-search')
661 .find('.vbo-widget-bulkmess-loadbtn')
662 .prop('disabled', false)
663 .find('i')
664 .attr('class', vbo_widget_bulkmess_icn_next);
665 } catch(err) {
666 console.error('could not parse JSON response', err, response);
667 widget_instance.find('.vbo-widget-bulkmess-bookings-list').html('');
668 }
669 },
670 (error) => {
671 widget_instance.find('.vbo-widget-bulkmess-bookings-list').html('');
672 console.error(error);
673 alert(error.responseText);
674 }
675 );
676 }
677
678 /**
679 * Renders the booking details widget.
680 */
681 function vboWidgetBulkMessOpenBooking(bid) {
682 VBOCore.handleDisplayWidgetNotification({widget_id: 'booking_details'}, {
683 booking_id: bid,
684 modal_options: {
685 /**
686 * Overwrite modal options for rendering the admin widget.
687 * We need to use a different suffix in case this current widget was
688 * also rendered within a modal, or it would get dismissed in favour
689 * of the newly opened admin widget.
690 */
691 suffix: 'widget_modal_inner_booking_details',
692 },
693 });
694 }
695
696 /**
697 * Completes a step.
698 */
699 function vboWidgetBulkMessGoNext(wrapper, stepname) {
700 var widget_instance = jQuery('#' + wrapper);
701 if (!widget_instance.length) {
702 return false;
703 }
704
705 if (stepname == 'message' || stepname == 'send') {
706 // count the number of selected bookings
707 var checked_stats = vboWidgetBulkMessCountChecked(wrapper);
708 if (!checked_stats['tot_checked']) {
709 alert(Joomla.JText._('VBO_PLEASE_SELECT'));
710 return false;
711 }
712
713 // update step status if about to build the message
714 if (stepname == 'message') {
715 widget_instance.find('.vbo-widget-bulkmess-step-message').find('.vbo-widget-bulkmess-step-status').text(Joomla.JText._('VBPVIEWORDERSPEOPLE') + ': ' + checked_stats['tot_checked']);
716 }
717
718 // ask for confirmation before sending
719 if (stepname == 'send') {
720 if (!confirm(Joomla.JText._('VBO_WANT_PROCEED'))) {
721 return false;
722 } else {
723 // start sending
724 vboWidgetBulkMessDoSend(wrapper);
725 }
726 }
727 }
728
729 // hide all steps except the next one
730 widget_instance.find('.vbo-widget-bulkmess-step').not('.vbo-widget-bulkmess-step-' + stepname).addClass('vbo-widget-bulkmess-step-hidden');
731
732 // display the next step
733 widget_instance.find('.vbo-widget-bulkmess-step-' + stepname).removeClass('vbo-widget-bulkmess-step-hidden');
734 }
735
736 /**
737 * Starts the process to send the communication messages.
738 */
739 function vboWidgetBulkMessDoSend(wrapper) {
740 var widget_instance = jQuery('#' + wrapper);
741 if (!widget_instance.length) {
742 return false;
743 }
744
745 // gather the list of booking IDs to notify
746 var bids_pool = [];
747 widget_instance.find('.vbo-widget-bulkmess-step-bookings')
748 .find('input[type="checkbox"]:checked')
749 .each(function() {
750 bids_pool.push(jQuery(this).val());
751 });
752
753 if (!bids_pool.length) {
754 alert(Joomla.JText._('VBO_PLEASE_SELECT'));
755 return false;
756 }
757
758 // hide all buttons to go next
759 widget_instance.find('.vbo-widget-bulkmess-gonext').hide();
760
761 // update step status
762 widget_instance.find('.vbo-widget-bulkmess-step-send').find('.vbo-widget-bulkmess-step-status').html('<?php VikBookingIcons::e('spinner', 'fa-spin fa-fw'); ?>');
763
764 // trigger the recursive async sending process
765 vboWodgetBulkMessRecursiveAsyncSend(0, bids_pool, wrapper);
766 }
767
768 /**
769 * Recursive function to asynchronously dispatch the messages.
770 */
771 function vboWodgetBulkMessRecursiveAsyncSend(i, pool, wrapper) {
772 var widget_instance = jQuery('#' + wrapper);
773 if (!widget_instance.length) {
774 throw new Error('Widget instance not found');
775 }
776
777 if (i >= pool.length) {
778 // process completed, update status
779 widget_instance.find('.vbo-widget-bulkmess-step-send')
780 .find('.vbo-widget-bulkmess-step-status')
781 .html('<?php VikBookingIcons::e('check-circle'); ?>');
782
783 // do not continue
784 return;
785 }
786
787 if (!pool.hasOwnProperty(i) || !pool[i]) {
788 throw new Error('Invalid index argument');
789 }
790
791 if (i === 0) {
792 // start the progress bar
793 widget_instance.find('.vbo-widget-bulkmess-step-send')
794 .find('progress')
795 .attr('value', 0)
796 .attr('max', pool.length)
797 .text('0 / ' + pool.length);
798 }
799
800 // gather message subject and content
801 var subject = widget_instance.find('.vbo-widget-bulkmess-subject').val();
802 var message = widget_instance.find('.vbo-widget-bulkmess-messagecont').val();
803
804 // the widget method to call
805 var call_method = 'sendOneMessage';
806
807 // make a request to send the message
808 VBOCore.doAjax(
809 "<?php echo $this->getExecWidgetAjaxUri(); ?>",
810 {
811 widget_id: "<?php echo $this->getIdentifier(); ?>",
812 call: call_method,
813 return: 1,
814 bid: pool[i],
815 index: i,
816 message: message,
817 subject: subject,
818 wrapper: wrapper,
819 tmpl: "component"
820 },
821 (response) => {
822 try {
823 var obj_res = typeof response === 'string' ? JSON.parse(response) : response;
824 if (!obj_res.hasOwnProperty(call_method)) {
825 console.error('Unexpected JSON response', obj_res);
826 return false;
827 }
828
829 // update progress bar
830 widget_instance.find('.vbo-widget-bulkmess-step-send')
831 .find('progress')
832 .attr('value', (i + 1))
833 .text((i + 1) + ' / ' + pool.length);
834
835 // go next
836 vboWodgetBulkMessRecursiveAsyncSend(i + 1, pool, wrapper);
837
838 } catch(err) {
839 // log and display error
840 console.error('could not parse JSON response', err, response);
841 alert('could not parse JSON response');
842
843 // go next either way
844 vboWodgetBulkMessRecursiveAsyncSend(i + 1, pool, wrapper);
845 }
846 },
847 (error) => {
848 // log and display error
849 console.error(error);
850 alert(error.responseText);
851
852 // update progress bar
853 widget_instance.find('.vbo-widget-bulkmess-step-send')
854 .find('progress')
855 .attr('value', (i + 1))
856 .text((i + 1) + ' / ' + pool.length);
857
858 // go next either way
859 vboWodgetBulkMessRecursiveAsyncSend(i + 1, pool, wrapper);
860 }
861 );
862 }
863
864 /**
865 * Toggles the visibility of a step.
866 */
867 function vboWidgetBulkMessToggleStep(wrapper, stepname) {
868 var widget_instance = jQuery('#' + wrapper);
869 if (!widget_instance.length) {
870 return false;
871 }
872
873 // display the current step
874 widget_instance.find('.vbo-widget-bulkmess-step-' + stepname).toggleClass('vbo-widget-bulkmess-step-hidden');
875 }
876
877 /**
878 * Counts the number of bookings checked.
879 */
880 function vboWidgetBulkMessCountChecked(wrapper) {
881 var widget_instance = jQuery('#' + wrapper);
882 if (!widget_instance.length) {
883 return false;
884 }
885
886 var container = widget_instance.find('.vbo-widget-bulkmess-step-bookings');
887
888 var tot_bookings = container.find('input[type="checkbox"]').length;
889 var tot_checked = container.find('input[type="checkbox"]:checked').length;
890
891 if (tot_checked) {
892 // overwrite the first checked booking ID to let the mail preview work
893 window['vbo_current_bid'] = container.find('input[type="checkbox"]:checked').first().attr('value');
894 }
895
896 return {
897 tot_checked: tot_checked,
898 tot_bookings: tot_bookings
899 };
900 }
901
902 /**
903 * Toggles the checked status for all bookings.
904 */
905 function vboWidgetBulkMessSelectAll(wrapper) {
906 var widget_instance = jQuery('#' + wrapper);
907 if (!widget_instance.length) {
908 return false;
909 }
910
911 var checked_stats = vboWidgetBulkMessCountChecked(wrapper);
912 if (!checked_stats['tot_bookings']) {
913 return false;
914 }
915
916 var container = widget_instance.find('.vbo-widget-bulkmess-step-bookings');
917 var tot_elements = checked_stats['tot_bookings'];
918
919 if (checked_stats['tot_checked'] < checked_stats['tot_bookings']) {
920 // select all
921 container.find('input[type="checkbox"]').prop('checked', true);
922 } else {
923 // select none
924 container.find('input[type="checkbox"]').prop('checked', false);
925 tot_elements = 0;
926 }
927
928 // update status on bookings step
929 container.find('.vbo-widget-bulkmess-step-status').text(tot_elements + ' / ' + checked_stats['tot_bookings']);
930 }
931
932 </script>
933 <?php
934 }
935 ?>
936
937 <script type="text/javascript">
938
939 jQuery(function() {
940
941 // render datepicker calendar for dates navigation
942 jQuery('#<?php echo $wrapper_id; ?>').find('.vbo-widget-bulkmess-fromdt, .vbo-widget-bulkmess-todt').datepicker({
943 minDate: "-1y",
944 maxDate: "+3y",
945 yearRange: "<?php echo (date('Y') - 2); ?>:<?php echo (date('Y') + 3); ?>",
946 changeMonth: true,
947 changeYear: true,
948 dateFormat: "<?php echo $dtpicker_df; ?>",
949 onSelect: function(selectedDate) {
950 if (!selectedDate) {
951 return;
952 }
953 if (jQuery(this).hasClass('vbo-widget-bulkmess-fromdt')) {
954 let nowstart = jQuery(this).datepicker('getDate');
955 let nowstartdate = new Date(nowstart.getTime());
956 jQuery('.vbo-widget-bulkmess-todt').datepicker('option', {minDate: nowstartdate});
957 }
958 }
959 });
960
961 // triggering for datepicker calendar icon
962 jQuery('#<?php echo $wrapper_id; ?>').find('.vbo-widget-bulkmess-fromdt-trigger, .vbo-widget-bulkmess-todt-trigger').click(function() {
963 var jdp = jQuery(this).parent().find('input.hasDatepicker');
964 if (jdp.length) {
965 jdp.focus();
966 }
967 });
968
969 });
970
971 </script>
972
973 <?php
974 }
975
976 /**
977 * Renders the editor to compose the message.
978 *
979 * @return string
980 */
981 private function renderEditor()
982 {
983 // load assets
984 $this->vbo_app->loadVisualEditorAssets();
985
986 // build a list of all special tags for the visual editor
987 $special_tags_base = [
988 '{customer_name}',
989 '{customer_pin}',
990 '{booking_id}',
991 '{checkin_date}',
992 '{checkout_date}',
993 '{num_nights}',
994 '{rooms_booked}',
995 '{tot_adults}',
996 '{tot_children}',
997 '{tot_guests}',
998 '{total}',
999 '{total_paid}',
1000 '{remaining_balance}',
1001 '{booking_link}',
1002 ];
1003
1004 // load all conditional text special tags
1005 $condtext_tags = array_keys(VikBooking::getConditionalRulesInstance()->getSpecialTags());
1006
1007 // join special tags with conditional texts to construct a list of editor buttons,
1008 // displayed within the toolbar of Quill editor
1009 $editor_btns = array_merge($special_tags_base, $condtext_tags);
1010
1011 // convert special tags into HTML buttons, displayed under the text editor
1012 $special_tags_base = array_map(function($tag)
1013 {
1014 return '<button type="button" class="btn" onclick="setCronTplTag(\'tpl_text\', \'' . $tag . '\');">' . $tag . '</button>';
1015 }, $special_tags_base);
1016
1017 // convert conditional texts into HTML buttons, displayed under the text editor
1018 $condtext_tags = array_map(function($tag)
1019 {
1020 return '<button type="button" class="btn vbo-condtext-specialtag-btn" onclick="setCronTplTag(\'tpl_text\', \'' . $tag . '\');">' . $tag . '</button>';
1021 }, $condtext_tags);
1022
1023 // build the default message value
1024 $def_message = !empty($this->widgetSettings->last_message) ? $this->widgetSettings->last_message : $this->getDefaultMessage();
1025
1026 return $this->vbo_app->renderVisualEditor(
1027 'vbo_widget_bulkmess_mess',
1028 $def_message,
1029 [
1030 'class' => 'vbo-widget-bulkmess-messagecont',
1031 'style' => 'width: 96%; height: 150px;',
1032 ],
1033 [
1034 'modes' => [
1035 'text',
1036 'visual',
1037 ],
1038 ],
1039 $editor_btns
1040 );
1041 }
1042
1043 /**
1044 * Returns the default message.
1045 *
1046 * @return string
1047 */
1048 private function getDefaultMessage()
1049 {
1050 $message = '';
1051 $logo_html = '';
1052 $sitelogo = VBOFactory::getConfig()->get('sitelogo');
1053 $company_name = VikBooking::getFrontTitle();
1054
1055 if ($sitelogo && is_file(VBO_ADMIN_PATH . DIRECTORY_SEPARATOR . 'resources'. DIRECTORY_SEPARATOR . $sitelogo)) {
1056 $logo_html = '<p style="text-align: center;">'
1057 . '<img src="' . VBO_ADMIN_URI . 'resources/' . $sitelogo . '" alt="' . htmlspecialchars($company_name) . '" /></p>'
1058 . "\n";
1059 }
1060
1061 $message =
1062 <<<HTML
1063 $logo_html
1064 <h1 style="text-align: center;">
1065 <span style="font-family: verdana;">$company_name</span>
1066 </h1>
1067 <hr class="vbo-editor-hl-mailwrapper">
1068 <h4>Dear {customer_name},</h4>
1069 <p><br></p>
1070 <p>This is a message for your stay from {checkin_date} to {checkout_date}.</p>
1071 <p><br></p>
1072 <p><br></p>
1073 <p>Thank you.</p>
1074 <p>$company_name</p>
1075 <hr class="vbo-editor-hl-mailwrapper">
1076 <p><br></p>
1077 HTML
1078 ;
1079
1080 return $message;
1081 }
1082
1083 /**
1084 * Sends a message to the guest through a regular email.
1085 *
1086 * @param array $booking the reservation record to notify.
1087 * @param string $message the message to send.
1088 * @param string $subject the email subject.
1089 *
1090 * @return string the message built and sent.
1091 */
1092 private function notifyThroughEmail(array $booking, $message, $subject)
1093 {
1094 // fetch booked room details
1095 $booking_rooms = VikBooking::loadOrdersRoomsData($booking['id']);
1096
1097 // translate contents
1098 $vbo_tn = VikBooking::getTranslator();
1099 $vbo_tn->translateContents($booking_rooms, '#__vikbooking_rooms', ['id' => 'idroom', 'name' => 'room_name']);
1100
1101 $message = $this->parseCustomerEmailTemplate($message, $booking, $booking_rooms, $vbo_tn);
1102
1103 $is_html = (strpos($message, '<') !== false || strpos($message, '</') !== false);
1104 if ($is_html && !preg_match("/(<\/?br\/?>)+/", $message)) {
1105 // when no br tags found, apply nl2br
1106 $message = nl2br($message);
1107 }
1108
1109 // get sender email address
1110 $admin_sendermail = VikBooking::getSenderMail();
1111
1112 if (!$this->vbo_app->sendMail($admin_sendermail, $admin_sendermail, $booking['custmail'], $admin_sendermail, $subject, $message, $is_html)) {
1113 VBOHttpDocument::getInstance()->close(500, 'Sending the email message to the booking ID ' . $booking['id'] . ' failed.');
1114 }
1115
1116 return $message;
1117 }
1118
1119 /**
1120 * Sends a message to the guest through a regular email.
1121 *
1122 * @param array $booking the reservation record to notify.
1123 * @param string $message the message to send.
1124 *
1125 * @return string the message built and sent.
1126 */
1127 private function notifyThroughMessage(array $booking, $message)
1128 {
1129 // fetch booked room details
1130 $booking_rooms = VikBooking::loadOrdersRoomsData($booking['id']);
1131
1132 // translate contents
1133 $vbo_tn = VikBooking::getTranslator();
1134 $vbo_tn->translateContents($booking_rooms, '#__vikbooking_rooms', ['id' => 'idroom', 'name' => 'room_name']);
1135
1136 $message = $this->parseCustomerEmailTemplate($message, $booking, $booking_rooms, $vbo_tn);
1137 if (empty($message)) {
1138 VBOHttpDocument::getInstance()->close(500, 'Message for the booking ID ' . $booking['id'] . ' is empty.');
1139 }
1140
1141 $messaging = VCMChatMessaging::getInstance($booking);
1142 $result = $messaging->setMessage($message)
1143 ->sendGuestMessage();
1144
1145 if (!$result && $error = $messaging->getError()) {
1146 // terminate with the error description
1147 VBOHttpDocument::getInstance()->close(500, "Message could not be sent to guest - Booking ID {$booking['id']} ({$booking['customer_name']}): {$error}");
1148 }
1149
1150 return $message;
1151 }
1152
1153 /**
1154 * Composes the actual email message by parsing special tokens.
1155 *
1156 * @param string $message the message content for the email.
1157 * @param array $booking booking array record.
1158 * @param array $booking_rooms list of rooms booked.
1159 * @param array $vbo_tn translator object.
1160 *
1161 * @return string the email message content ready to be sent.
1162 */
1163 private function parseCustomerEmailTemplate($message, $booking, $booking_rooms, $vbo_tn = null)
1164 {
1165 $tpl = $message;
1166
1167 /**
1168 * Parse all conditional text rules.
1169 */
1170 VikBooking::getConditionalRulesInstance()
1171 ->set(array('booking', 'rooms'), array($booking, $booking_rooms))
1172 ->parseTokens($tpl);
1173 //
1174
1175 $vbo_df = VikBooking::getDateFormat();
1176 $df = $vbo_df == "%d/%m/%Y" ? 'd/m/Y' : ($vbo_df == "%m/%d/%Y" ? 'm/d/Y' : 'Y-m-d');
1177 $tpl = str_replace('{customer_name}', $booking['customer_name'], $tpl);
1178 $tpl = str_replace('{booking_id}', $booking['id'], $tpl);
1179 $tpl = str_replace('{checkin_date}', date($df, $booking['checkin']), $tpl);
1180 $tpl = str_replace('{checkout_date}', date($df, $booking['checkout']), $tpl);
1181 $tpl = str_replace('{num_nights}', $booking['days'], $tpl);
1182 $rooms_booked = [];
1183 $tot_adults = 0;
1184 $tot_children = 0;
1185 $tot_guests = 0;
1186 foreach ($booking_rooms as $broom) {
1187 if (array_key_exists($broom['room_name'], $rooms_booked)) {
1188 $rooms_booked[$broom['room_name']] += 1;
1189 } else {
1190 $rooms_booked[$broom['room_name']] = 1;
1191 }
1192 $tot_adults += (int)$broom['adults'];
1193 $tot_children += (int)$broom['children'];
1194 $tot_guests += ((int)$broom['adults'] + (int)$broom['children']);
1195 }
1196 $tpl = str_replace('{tot_adults}', $tot_adults, $tpl);
1197 $tpl = str_replace('{tot_children}', $tot_children, $tpl);
1198 $tpl = str_replace('{tot_guests}', $tot_guests, $tpl);
1199 $rooms_booked_quant = [];
1200 foreach ($rooms_booked as $rname => $quant) {
1201 $rooms_booked_quant[] = ($quant > 1 ? $quant.' ' : '').$rname;
1202 }
1203 $tpl = str_replace('{rooms_booked}', implode(', ', $rooms_booked_quant), $tpl);
1204 $tpl = str_replace('{total}', VikBooking::numberFormat($booking['total']), $tpl);
1205 $tpl = str_replace('{total_paid}', VikBooking::numberFormat($booking['totpaid']), $tpl);
1206 $remaining_bal = $booking['total'] - $booking['totpaid'];
1207 $tpl = str_replace('{remaining_balance}', VikBooking::numberFormat($remaining_bal), $tpl);
1208 $tpl = str_replace('{customer_pin}', $booking['customer_pin'], $tpl);
1209
1210 $use_sid = empty($booking['sid']) && !empty($booking['idorderota']) ? $booking['idorderota'] : $booking['sid'];
1211
1212 $bestitemid = VikBooking::findProperItemIdType(['booking'], (!empty($booking['lang']) ? $booking['lang'] : null));
1213 $lang_suffix = $bestitemid && !empty($booking['lang']) ? '&lang=' . $booking['lang'] : '';
1214 $book_link = VikBooking::externalroute("index.php?option=com_vikbooking&view=booking&sid=" . $use_sid . "&ts=" . $booking['ts'] . $lang_suffix, false, (!empty($bestitemid) ? $bestitemid : null));
1215
1216 $tpl = str_replace('{booking_link}', $book_link, $tpl);
1217
1218 /**
1219 * Rooms Distinctive Features parsing
1220 */
1221 preg_match_all('/\{roomfeature ([a-zA-Z0-9 ]+)\}/U', $tpl, $matches);
1222 if (isset($matches[1]) && $matches[1]) {
1223 foreach ($matches[1] as $reqf) {
1224 $rooms_features = [];
1225 foreach ($booking_rooms as $broom) {
1226 $distinctive_features = [];
1227 $rparams = json_decode($broom['params'], true);
1228 if (array_key_exists('features', $rparams) && count($rparams['features']) > 0 && array_key_exists('roomindex', $broom) && !empty($broom['roomindex']) && array_key_exists($broom['roomindex'], $rparams['features'])) {
1229 $distinctive_features = $rparams['features'][$broom['roomindex']];
1230 }
1231 if (!count($distinctive_features)) {
1232 continue;
1233 }
1234 $feature_found = false;
1235 foreach ($distinctive_features as $dfk => $dfv) {
1236 if (stripos($dfk, $reqf) !== false) {
1237 $feature_found = $dfk;
1238 if (strlen(trim($dfk)) == strlen(trim($reqf))) {
1239 break;
1240 }
1241 }
1242 }
1243 if ($feature_found !== false && strlen((string)$distinctive_features[$feature_found])) {
1244 $rooms_features[] = $distinctive_features[$feature_found];
1245 }
1246 }
1247 if ($rooms_features) {
1248 $rpval = implode(', ', $rooms_features);
1249 } else {
1250 $rpval = '';
1251 }
1252 $tpl = str_replace("{roomfeature ".$reqf."}", $rpval, $tpl);
1253 }
1254 }
1255
1256 return $tpl;
1257 }
1258 }
1259