PluginProbe
VikBooking Hotel Booking Engine & PMS / 1.8.6
VikBooking Hotel Booking Engine & PMS v1.8.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 / site / helpers / cpin.php

cpin.php in VikBooking Hotel Booking Engine & PMS 1.8.6, at site/helpers/cpin.php

1,418 lines 41.7 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 * Handle customers within the reservation scope.
15 */
16 class VikBookingCustomersPin
17 {
18 public $all_pins = [];
19 public $is_admin = false;
20 public $fieldflags = [];
21 public $error = '';
22 private $dbo;
23 private $new_pin = '';
24 private $new_customer_id = 0;
25
26 public function __construct()
27 {
28 $this->dbo = JFactory::getDbo();
29 }
30
31 /**
32 * Generates a serial code of a fixed length from a chars map.
33 *
34 * @param int $length The length of the serial code to generate.
35 * @param ?array $map Optional map of allowed characters.
36 *
37 * @return string
38 *
39 * @since 1.18.3 (J) - 1.8.3 (WP)
40 */
41 public function generateSerialCode(int $length = 8, ?array $map = null)
42 {
43 $code = '';
44
45 if (!$map) {
46 // use default tokens unless specified
47 $map = [
48 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
49 '0123456789',
50 ];
51 }
52
53 // iterate until the specified length is reached
54 for ($i = strlen($code); $i < $length; $i++) {
55 // toss tokens block
56 $_row = rand(0, count($map) - 1);
57 // toss block character
58 $_col = rand(0, strlen($map[$_row]) - 1);
59
60 // append character to serial code
61 $code .= (string) $map[$_row][$_col];
62 }
63
64 return $code;
65 }
66
67 /**
68 * Generates a unique PIN number for the customer.
69 *
70 * @param bool $notpush True to avoid internal caching.
71 *
72 * @return string The unique pin-code string.
73 *
74 * @since 1.15.0 (J) - 1.5.0 (WP) pin length changed from 5 to 8 digits (integers).
75 * @since 1.18.3 (J) - 1.8.3 (WP) pin length changed to 8 alphanumeric characters.
76 */
77 public function generateUniquePin($notpush = false)
78 {
79 do {
80 // generate a random serial code
81 $rand_pin = $this->generateSerialCode(8);
82 } while ($this->pinExists($rand_pin));
83
84 if (!$notpush) {
85 $this->all_pins[] = $rand_pin;
86 }
87
88 return $rand_pin;
89 }
90
91 /**
92 * Checks if the pin already exists.
93 *
94 * @param string $pin
95 * @param string $ignorepin
96 *
97 * @return bool
98 */
99 public function pinExists($pin, $ignorepin = '')
100 {
101 $current_pins = $this->all_pins ?: $this->getAllPins($ignorepin);
102
103 return in_array($pin, $current_pins);
104 }
105
106 /**
107 * Fetches and sets all the pins currently stored in the database.
108 *
109 * @param string $ignorepin Optional pin to ignore.
110 *
111 * @return array List of pin codes found.
112 */
113 public function getAllPins($ignorepin = '')
114 {
115 $current_pins = [];
116
117 $q = "SELECT `pin` FROM `#__vikbooking_customers`".(!empty($ignorepin) ? " WHERE `pin`!=".$this->dbo->quote($ignorepin) : "").";";
118 $this->dbo->setQuery($q);
119 $pins = $this->dbo->loadAssocList();
120
121 foreach ($pins as $v) {
122 $current_pins[] = $v['pin'];
123 }
124
125 $this->all_pins = $current_pins;
126
127 return $this->all_pins;
128 }
129
130 /**
131 * Attempts to fetch the customer details record by Joomla/WordPress User ID.
132 *
133 * @param array $customer_details
134 *
135 * @return array can also be used with no return value as reference.
136 */
137 private function getDetailsByUjid(&$customer_details)
138 {
139 $user = JFactory::getUser();
140
141 if (!$user->guest && (int)$user->id > 0) {
142 $q = "SELECT * FROM `#__vikbooking_customers` WHERE `ujid`=" . (int)$user->id . " ORDER BY `#__vikbooking_customers`.`id` DESC";
143 $this->dbo->setQuery($q, 0, 1);
144 $customer = $this->dbo->loadAssoc();
145 if ($customer) {
146 $customer['cfields'] = empty($customer['cfields']) ? array() : json_decode($customer['cfields'], true);
147 $customer_details = $customer;
148 }
149 }
150
151 return $customer_details;
152 }
153
154 /**
155 * Attempts to fetch the customer details record by PIN Cookie.
156 *
157 * @param array $customer_details
158 *
159 * @return array can also be used with no return value as reference.
160 */
161 private function getDetailsByPinCookie(&$customer_details)
162 {
163 $pin_cookie = $this->getPinCookie();
164 $pin_cookie = empty($pin_cookie) ? $this->getNewPin() : $pin_cookie;
165 if ($pin_cookie) {
166 $q = "SELECT * FROM `#__vikbooking_customers` WHERE `pin`=" . $this->dbo->quote($pin_cookie) . " ORDER BY `#__vikbooking_customers`.`id` DESC";
167 $this->dbo->setQuery($q, 0, 1);
168 $customer = $this->dbo->loadAssoc();
169 if ($customer) {
170 $customer['cfields'] = empty($customer['cfields']) ? array() : json_decode($customer['cfields'], true);
171 $customer_details = $customer;
172 }
173 }
174
175 return $customer_details;
176 }
177
178 /**
179 * Gets "decoded" PIN from Cookie.
180 *
181 * @return string The unmasked pin cookie value.
182 *
183 * @since 1.18.4 (J) - 1.8.4 (WP) added support to alphanumeric cookie values.
184 */
185 private function getPinCookie()
186 {
187 $cookie = JFactory::getApplication()->input->cookie;
188 $cookie_val = $cookie->get('vboPinData', '', 'string');
189
190 return !empty($cookie_val) ? $this->unMaskPinValue($cookie_val) : '';
191 }
192
193 /**
194 * Sets "encoded" PIN to Cookie with a lifetime of 365 days.
195 *
196 * @param string $pin The plain alphanumeric pin value to store as a cookie.
197 *
198 * @return string The masked pin cookie value that was set.
199 *
200 * @since 1.18.4 (J) - 1.8.4 (WP) added support to alphanumeric cookie values.
201 */
202 private function setPinCookie($pin)
203 {
204 $pin_cookie = '';
205
206 if (!empty($pin)) {
207 $pin_cookie = $this->maskPinValue($pin);
208 VikRequest::setCookie('vboPinData', $pin_cookie, (time() + (86400 * 365)), '/', '', false, true);
209 }
210
211 return $pin_cookie;
212 }
213
214 /**
215 * Masks a raw PIN alphanumeric value by using a XOR based mapping.
216 *
217 * @param string $pin The plain alphanumeric pin value to mask.
218 * @param ?string $key Optional masking key.
219 *
220 * @return string The masked PIN string.
221 *
222 * @since 1.18.4 (J) - 1.8.4 (WP)
223 */
224 private function maskPinValue(string $pin, ?string $key = null)
225 {
226 $key = $key ?: VBOFactory::getConfig()->getString('cpin_mask_salt_key', 'vbo1987');
227
228 $masked = '';
229
230 for ($i = 0; $i < strlen($pin); $i++) {
231 $masked .= chr(ord($pin[$i]) ^ ord($key[$i % strlen($key)]));
232 }
233
234 return base64_encode($masked);
235 }
236
237 /**
238 * Un-masks a PIN alphanumeric value by using a XOR based mapping.
239 *
240 * @param string $pin The masked alphanumeric pin value to unmask.
241 * @param ?string $key Optional masking key.
242 *
243 * @return string The un-masked PIN string.
244 *
245 * @since 1.18.4 (J) - 1.8.4 (WP)
246 */
247 private function unMaskPinValue(string $pin, ?string $key = null)
248 {
249 $key = $key ?: VBOFactory::getConfig()->getString('cpin_mask_salt_key', 'vbo1987');
250
251 $unmasked = '';
252
253 for ($i = 0; $i < strlen($pin); $i++) {
254 $unmasked .= chr(ord($pin[$i]) ^ ord($key[$i % strlen($key)]));
255 }
256
257 return base64_decode($unmasked);
258 }
259
260 /**
261 * Unsets PIN Cookie
262 */
263 private function unsetPinCookie()
264 {
265 $cookie = JFactory::getApplication()->input->cookie;
266 VikRequest::setCookie('vboPinData', $pin_cookie, (time() - (86400 * 365)), '/', '', false, true);
267 $cookie_val = $cookie->get('vboPinData', '', 'string');
268
269 return $pin_cookie;
270 }
271
272 /**
273 * Counts the number of "short" PIN codes generated by older versions.
274 *
275 * @return int
276 *
277 * @since 1.18.6 (J) - 1.8.6 (WP)
278 */
279 public function countShortPins()
280 {
281 // count all records with PIN codes containing only numbers
282 $this->dbo->setQuery(
283 $this->dbo->getQuery(true)
284 ->select('COUNT(*)')
285 ->from($this->dbo->qn('#__vikbooking_customers'))
286 ->where($this->dbo->qn('pin') . ' REGEXP ' . $this->dbo->q('^[0-9]{4,8}$'))
287 );
288
289 return (int) $this->dbo->loadResult();
290 }
291
292 /**
293 * Normalizes the "short" PIN codes generated by older versions.
294 *
295 * @return int Number of normalized records.
296 *
297 * @since 1.18.6 (J) - 1.8.6 (WP)
298 */
299 public function normalizeShortPins()
300 {
301 // match all records with PIN codes containing only numbers
302 $this->dbo->setQuery(
303 $this->dbo->getQuery(true)
304 ->select($this->dbo->qn('id'))
305 ->from($this->dbo->qn('#__vikbooking_customers'))
306 ->where($this->dbo->qn('pin') . ' REGEXP ' . $this->dbo->q('^[0-9]{4,8}$'))
307 );
308
309 $records = $this->dbo->loadAssocList();
310
311 $normalized = 0;
312
313 foreach ($records as $record) {
314 // generate a unique PIN code
315 $longPin = $this->generateUniquePin();
316
317 // build customer record object
318 $customer_obj = new stdClass;
319 $customer_obj->id = $record['id'];
320 $customer_obj->pin = $longPin;
321
322 // update customer record
323 $this->dbo->updateObject('#__vikbooking_customers', $customer_obj, 'id');
324
325 // increase counter
326 $normalized++;
327 }
328
329 return $normalized;
330 }
331
332 /**
333 * Loads the customer details by Joomla/WordPress User ID or by PIN cookie.
334 * Returns an associative array with the record fetched from the DB.
335 *
336 * @return array empty array or customer record associative array.
337 */
338 public function loadCustomerDetails()
339 {
340 $customer_details = [];
341
342 // first attempt is through Joomla User ID
343 $this->getDetailsByUjid($customer_details);
344
345 if (!count($customer_details)) {
346 // second attempt is through PIN Cookie
347 $this->getDetailsByPinCookie($customer_details);
348 }
349
350 return $customer_details;
351 }
352
353 /**
354 * Checks whether the given customer has got automatic discounts reserved for the current booking.
355 *
356 * @param array $customer customer record associative array.
357 * @param array $booking the booking information array to validate the coupon.
358 *
359 * @return array empty array or proper customer coupon record.
360 *
361 * @since 1.16.0 (J) - 1.6.0 (WP)
362 */
363 public function getCustomerCoupon(array $customer = [], array $booking = [])
364 {
365 if (empty($customer['id'])) {
366 return [];
367 }
368
369 $q = "SELECT `ccp`.`idcoupon`, `cp`.* FROM `#__vikbooking_customers_coupons` AS `ccp`
370 LEFT JOIN `#__vikbooking_coupons` AS `cp` ON `ccp`.`idcoupon`=`cp`.`id`
371 WHERE `ccp`.`idcustomer`=" . (int)$customer['id'] . " AND `ccp`.`automatic`=1 ORDER BY `ccp`.`idcoupon` DESC";
372 $this->dbo->setQuery($q);
373 $customer_coupons = $this->dbo->loadAssocList();
374 if (!$customer_coupons) {
375 return [];
376 }
377
378 if (empty($booking)) {
379 // do not perform any validation on the coupon restrictions
380 return $customer_coupons[0];
381 }
382
383 // process all coupon codes for this customer to find the best one for this booking details
384 foreach ($customer_coupons as $coupon) {
385 if (!empty($coupon['datevalid'])) {
386 $dateparts = explode("-", $coupon['datevalid']);
387 $pickinfo = getdate($booking['checkin']);
388 $dropinfo = getdate($booking['checkout']);
389 $checkpick = mktime(0, 0, 0, $pickinfo['mon'], $pickinfo['mday'], $pickinfo['year']);
390 $checkdrop = mktime(0, 0, 0, $dropinfo['mon'], $dropinfo['mday'], $dropinfo['year']);
391 if (!($checkpick >= $dateparts[0] && $checkpick <= $dateparts[1] && $checkdrop >= $dateparts[0] && $checkdrop <= $dateparts[1])) {
392 // invalid dates
393 continue;
394 }
395 }
396 if (!empty($coupon['minlos']) && $coupon['minlos'] > $booking['days']) {
397 // invalid min LOS
398 continue;
399 }
400 if (!$coupon['allvehicles'] && !empty($coupon['idrooms'])) {
401 // validate rooms booked
402 foreach ((array)$booking['rooms'] as $room) {
403 if (!preg_match("/;" . $room['id'] . ";/i", $coupon['idrooms'])) {
404 // room not allowed
405 continue 2;
406 }
407 }
408 }
409 // return the first eligible coupon discount
410 return $coupon;
411 }
412
413 // no eligible discounts found
414 return [];
415 }
416
417 /**
418 * Attempts to fetch the customer details record by PIN code.
419 */
420 public function getCustomerByPin($pin)
421 {
422 $customer = [];
423 $this->setNewPin($pin);
424
425 if (!empty($pin)) {
426 $q = "SELECT * FROM `#__vikbooking_customers` WHERE `pin`=".$this->dbo->quote($pin)." ORDER BY `#__vikbooking_customers`.`id` DESC";
427 $this->dbo->setQuery($q, 0, 1);
428 $customer = $this->dbo->loadAssoc();
429 if ($customer) {
430 $customer['cfields'] = empty($customer['cfields']) ? array() : json_decode($customer['cfields'], true);
431 $this->setPinCookie($pin);
432 }
433 }
434
435 return $customer ?: [];
436 }
437
438 /**
439 * Get customer by ID.
440 *
441 * @param int $cust_id the ID of the customer.
442 *
443 * @return array the customer array or an empty array.
444 */
445 public function getCustomerByID($cust_id)
446 {
447 $customer = [];
448 if (!empty($cust_id)) {
449 $q = "SELECT `c`.*,`nat`.`country_name`,`nat`.`country_2_code` FROM `#__vikbooking_customers` AS `c` LEFT JOIN `#__vikbooking_countries` AS `nat` ON `c`.`country`=`nat`.`country_3_code` WHERE `c`.`id`=".$this->dbo->quote($cust_id);
450 $this->dbo->setQuery($q, 0, 1);
451 $customer = $this->dbo->loadAssoc();
452 $customer = !$customer ? [] : $customer;
453 }
454
455 if ($customer) {
456 $customer['cfields'] = empty($customer['cfields']) ? array() : json_decode($customer['cfields'], true);
457 $customer['chdata'] = !empty($customer['chdata']) ? json_decode($customer['chdata'], true) : array();
458 $customer['chdata'] = is_array($customer['chdata']) ? $customer['chdata'] : array();
459 }
460
461 return $customer;
462 }
463
464 /**
465 * Get customer array by booking ID.
466 * Also returns the pax_data information.
467 *
468 * @param int $orderid the VBO booking ID.
469 *
470 * @return array the customer array or an empty array.
471 *
472 * @uses getCustomerByID()
473 */
474 public function getCustomerFromBooking($orderid)
475 {
476 if (empty($orderid)) {
477 return [];
478 }
479 $q = "SELECT `idcustomer`, `signature`, `pax_data` FROM `#__vikbooking_customers_orders` WHERE `idorder`=".(int)$orderid.";";
480 $this->dbo->setQuery($q);
481 $data = $this->dbo->loadAssoc();
482 if (!$data) {
483 return [];
484 }
485
486 $customer = $this->getCustomerByID($data['idcustomer']);
487 if ($customer) {
488 /**
489 * Merge pax_data into the customer array to know whether
490 * the pre check-in or the registration was performed.
491 *
492 * @since 1.12 (J) - 1.1 (WP)
493 */
494 $customer['pax_data'] = $data['pax_data'];
495
496 /**
497 * Merge signature as well.
498 *
499 * @since 1.17.4 (J) - 1.7.4 (WP)
500 */
501 $customer['signature'] = $data['signature'];
502 }
503
504 return $customer;
505 }
506
507 /**
508 * Counts the number of bookings for the given customer ID.
509 *
510 * @param int $cust_id The VBO customer ID.
511 * @param array $options Associative list of query options.
512 *
513 * @return int Number of bookings found.
514 *
515 * @since 1.17.3 (J) - 1.7.3 (WP)
516 */
517 public function countCustomerBookings(int $cust_id, array $options = [])
518 {
519 $q = $this->dbo->getQuery(true)
520 ->select('COUNT(*)')
521 ->from($this->dbo->qn('#__vikbooking_customers_orders', 'co'))
522 ->where($this->dbo->qn('co.idcustomer') . ' = ' . $cust_id);
523
524 if ($options['status'] ?? null) {
525 $q->leftJoin($this->dbo->qn('#__vikbooking_orders', 'o') . ' ON ' . $this->dbo->qn('co.idorder') . ' = ' . $this->dbo->qn('o.id'));
526 if (is_array($options['status'])) {
527 // multiple statuses filter
528 $q->where($this->dbo->qn('o.status') . ' IN (' . implode(', ', array_map([$this->dbo, 'q'], $options['status'])) . ')');
529 } else {
530 // single status filter
531 $q->where($this->dbo->qn('o.status') . ' = ' . $this->dbo->q($options['status']));
532 }
533 }
534
535 $this->dbo->setQuery($q);
536
537 return (int) $this->dbo->loadResult();
538 }
539
540 /**
541 * Returns the customer verification data for the given booking ID.
542 * Verification data is used to store guest verification values.
543 *
544 * @param int $bid The booking record ID.
545 *
546 * @return array Either an empty array or a JSON-decoded array.
547 *
548 * @since 1.18.0 (J) - 1.8.0 (WP)
549 */
550 public function getBookingVerificationData(int $bid)
551 {
552 $this->dbo->setQuery(
553 $this->dbo->getQuery(true)
554 ->select($this->dbo->qn('verification_data'))
555 ->from($this->dbo->qn('#__vikbooking_customers_orders'))
556 ->where($this->dbo->qn('idorder') . ' = ' . $bid)
557 );
558
559 $data = $this->dbo->loadResult();
560
561 if (empty($data)) {
562 return [];
563 }
564
565 return (array) json_decode($data, true);
566 }
567
568 /**
569 * Updates the customer verification data for the given booking ID.
570 * Verification data is used to store guest verification values.
571 *
572 * @param int $bid The booking record ID.
573 * @param mixed $data The data to set, JSON-encoded string, array or object.
574 * @param bool $merge True for adding the data to the existing verification data.
575 *
576 * @return bool
577 *
578 * @throws InvalidArgumentException
579 *
580 * @since 1.18.0 (J) - 1.8.0 (WP)
581 */
582 public function setBookingVerificationData(int $bid, $data, bool $merge = false)
583 {
584 if ($merge && (is_array($data) || is_object($data))) {
585 $data = array_merge($this->getBookingVerificationData($bid), (array) $data);
586 }
587
588 if (!is_null($data) && !is_scalar($data)) {
589 $data = json_encode($data);
590 }
591
592 if (!is_string($data)) {
593 throw new InvalidArgumentException('Invalid verification data.', 400);
594 }
595
596 $this->dbo->setQuery(
597 $this->dbo->getQuery(true)
598 ->update($this->dbo->qn('#__vikbooking_customers_orders'))
599 ->set($this->dbo->qn('verification_data') . ' = ' . $this->dbo->q($data))
600 ->where($this->dbo->qn('idorder') . ' = ' . $bid)
601 );
602
603 $this->dbo->execute();
604
605 return (bool) $this->dbo->getAffectedRows();
606 }
607
608 /**
609 * Returns the customer identity data for the given booking ID.
610 * Identity data is used to store guest identity values.
611 *
612 * @param int $bid The booking record ID.
613 *
614 * @return array Either an empty array or a JSON-decoded array.
615 *
616 * @since 1.18.0 (J) - 1.8.0 (WP)
617 */
618 public function getBookingIdentityData(int $bid)
619 {
620 $this->dbo->setQuery(
621 $this->dbo->getQuery(true)
622 ->select($this->dbo->qn('identity'))
623 ->from($this->dbo->qn('#__vikbooking_customers_orders'))
624 ->where($this->dbo->qn('idorder') . ' = ' . $bid)
625 );
626
627 $data = $this->dbo->loadResult();
628
629 if (empty($data)) {
630 return [];
631 }
632
633 return (array) json_decode($data, true);
634 }
635
636 /**
637 * Updates the customer identity data for the given booking ID.
638 * Identity data is used to store guest identity values.
639 *
640 * @param int $bid The booking record ID.
641 * @param mixed $data The data to set: null, string URI, array or object.
642 * @param bool $merge True for adding the data to the existing identity data.
643 *
644 * @return bool
645 *
646 * @throws InvalidArgumentException
647 *
648 * @since 1.18.0 (J) - 1.8.0 (WP)
649 */
650 public function setBookingIdentityData(int $bid, $data, bool $merge = false)
651 {
652 if (is_string($data) && preg_match('/^http/', $data)) {
653 // always cast the string URI to an array
654 $data = (array) $data;
655 }
656
657 if ($merge && (is_array($data) || is_object($data))) {
658 $data = array_merge($this->getBookingIdentityData($bid), (array) $data);
659 }
660
661 if (!is_null($data) && !is_scalar($data)) {
662 $data = json_encode($data);
663 } elseif (!is_null($data)) {
664 throw new InvalidArgumentException('Invalid identity data.', 400);
665 }
666
667 $this->dbo->setQuery(
668 $this->dbo->getQuery(true)
669 ->update($this->dbo->qn('#__vikbooking_customers_orders'))
670 ->set($this->dbo->qn('identity') . ' = ' . (is_null($data) ? 'NULL' : $this->dbo->q($data)))
671 ->where($this->dbo->qn('idorder') . ' = ' . $bid)
672 );
673
674 $this->dbo->execute();
675
676 return (bool) $this->dbo->getAffectedRows();
677 }
678
679 /**
680 * Checks whether a customer with the same email address already exists
681 * Returns false or the record of the existing customer
682 *
683 * @param string $email the email address.
684 * @param string $first_name optional first name to compare existing emails.
685 * @param string $last_name optional last name to compare existing emails.
686 *
687 * @return mixed false if customer does not exist, array if customer found.
688 *
689 * @since 1.13 equal email addresses can be shared across multiple customers
690 * so long as the name or the last name are different.
691 */
692 public function customerExists($email, $first_name = '', $last_name = '')
693 {
694 if (empty($email)) {
695 return false;
696 }
697 $q = "SELECT * FROM `#__vikbooking_customers` WHERE `email`=".$this->dbo->quote(trim($email))." ORDER BY `#__vikbooking_customers`.`id` DESC;";
698 $this->dbo->setQuery($q);
699 $customers = $this->dbo->loadAssocList();
700 if (!$customers) {
701 return false;
702 }
703
704 if (empty($first_name) || empty($last_name)) {
705 // no info to compare an existing customer, so say it exists with this email address
706 return $customers[0];
707 }
708
709 // check if name and last name match with the current customers having this email address
710 foreach ($customers as $c) {
711 if (stripos($c['first_name'], $first_name) !== false && stripos($c['last_name'], $last_name) !== false) {
712 // customer with same email, first name and last name found
713 return $c;
714 }
715 }
716
717 /**
718 * We now check if a record with same email, first name and last name is found first, if not we return false.
719 *
720 * @since 1.14 (Joomla) - 1.4.0 (WordPress)
721 */
722 return false;
723 }
724
725 /**
726 * Sets some customer extra information like address, city, zip, company name, vat
727 */
728 public function setCustomerExtraInfo($fieldflags)
729 {
730 if (is_array($fieldflags) && count($fieldflags) > 0) {
731 $this->fieldflags = $fieldflags;
732 }
733 }
734
735 /**
736 * Converts the 2-char country code or country name into the ISO Alpha3 Char Code.
737 *
738 * @param string $country the 2-char country code or country name to convert.
739 *
740 * @return string either the 3-char version or the passed value.
741 *
742 * @since 1.13.5
743 */
744 public function get3CharCountry($country)
745 {
746 if (empty($country) || strlen((string)$country) < 2) {
747 return $country;
748 }
749
750 // trim white spaces
751 $country = trim($country);
752
753 // check what field to look for
754 $clause = [];
755 if (strlen($country) == 2) {
756 array_push($clause, "`country_2_code`=" . $this->dbo->q($country));
757 } else {
758 array_push($clause, "`country_name` LIKE " . $this->dbo->q("%{$country}%"));
759 }
760
761 // query the db
762 $q = "SELECT `country_3_code` FROM `#__vikbooking_countries` WHERE " . implode(' AND ', $clause);
763 $this->dbo->setQuery($q, 0, 1);
764 $three_country = $this->dbo->loadResult();
765 if ($three_country) {
766 // 3-char code found
767 return $three_country;
768 }
769
770 // nothing found, return the passed value
771 return $country;
772 }
773
774 /**
775 * Saves the customer in DB if it doesn't exist, generates the PIN and sets the cookie
776 */
777 public function saveCustomerDetails($first_name, $last_name, $email, $phone_number, $country, $cfields)
778 {
779 if (empty($first_name) || empty($last_name) || empty($email)) {
780 $this->setError('Missing fields for saving new customer');
781 return false;
782 }
783
784 // convert any 2-char country code or country name into a 3-char country code
785 if (!empty($country) && (strlen($country) == 2 || strlen($country) > 3)) {
786 $country = $this->get3CharCountry($country);
787 }
788
789 /**
790 * In case the phone number is missing the international prefix at the beginning of the string,
791 * mostly happens in case of OTA bookings, we pre-pend the country prefix.
792 *
793 * @since 1.12 (patch October 2019)
794 */
795 $phone_number = trim($phone_number);
796 if (!empty($phone_number) && !empty($country) && substr($phone_number, 0, 1) != '+' && substr($phone_number, 0, 2) != '00') {
797 // try to find the country phone prefix
798 $q = "SELECT `phone_prefix` FROM `#__vikbooking_countries` WHERE `country_" . (strlen($country) == 2 ? '2' : '3') . "_code`=" . $this->dbo->quote($country) . ";";
799 $this->dbo->setQuery($q);
800 $phone_prefix = $this->dbo->loadResult();
801 if ($phone_prefix) {
802 $country_prefix = str_replace(' ', '', $phone_prefix);
803 $num_prefix = str_replace('+', '', $country_prefix);
804 if (substr($phone_number, 0, strlen($num_prefix)) != $num_prefix) {
805 // country prefix is completely missing
806 $phone_number = $country_prefix . $phone_number;
807 } else {
808 // try to prepend the plus symbol because the phone number starts with the country prefix
809 $phone_number = '+' . $phone_number;
810 }
811 }
812 }
813
814 // check if the customer exists
815 $customer = $this->customerExists($email, $first_name, $last_name);
816
817 // access the current user
818 $user = JFactory::getUser();
819
820 if ($customer === false) {
821 $new_pin = $this->generateUniquePin();
822
823 // build customer record
824 $customer_obj = new stdClass;
825 $customer_obj->first_name = $first_name;
826 $customer_obj->last_name = $last_name;
827 $customer_obj->email = $email;
828 $customer_obj->phone = $phone_number;
829 $customer_obj->country = $country;
830 $customer_obj->cfields = is_array($cfields) && $cfields ? json_encode($cfields) : null;
831 $customer_obj->pin = $new_pin;
832 $customer_obj->ujid = $this->is_admin ? 0 : (int)$user->id;
833 // add the extra field flags
834 foreach ($this->fieldflags as $flagk => $flagv) {
835 if (is_array($flagv) || is_object($flagv)) {
836 $flagv = json_encode($flagv);
837 }
838 $customer_obj->{$flagk} = $flagv;
839 }
840
841 // trigger the customer before-insert event
842 $this->pluginCustomerSync(0, 'insert', (array)$customer_obj, $before = true);
843
844 // insert the new customer record
845 $this->dbo->insertObject('#__vikbooking_customers', $customer_obj, 'id');
846 $new_customer_id = isset($customer_obj->id) ? $customer_obj->id : null;
847
848 if (!empty($new_customer_id)) {
849 $this->setNewPin($new_pin);
850 $this->setNewCustomerId($new_customer_id);
851
852 // trigger the customer after-save event
853 $this->pluginCustomerSync($new_customer_id, 'insert', (array)$customer_obj, $before = false);
854 }
855 } elseif (is_array($customer) && $customer) {
856 $this->setNewPin($customer['pin']);
857 $this->setNewCustomerId($customer['id']);
858
859 // build customer record
860 $customer_obj = new stdClass;
861 $customer_obj->id = $customer['id'];
862 $customer_obj->first_name = $first_name;
863 $customer_obj->last_name = $last_name;
864 $customer_obj->email = $email;
865 $customer_obj->phone = $phone_number;
866 $customer_obj->country = $country;
867 if (!$this->is_admin) {
868 $customer_obj->cfields = is_array($cfields) && $cfields ? json_encode($cfields) : null;
869 }
870 $customer_obj->pin = $customer['pin'];
871 $customer_obj->ujid = $this->is_admin ? 0 : (int)$user->id;
872 // add the extra field flags
873 foreach ($this->fieldflags as $flagk => $flagv) {
874 if (is_array($flagv) || is_object($flagv)) {
875 $flagv = json_encode($flagv);
876 }
877 $customer_obj->{$flagk} = $flagv;
878 }
879
880 // trigger the customer before-update event
881 $this->pluginCustomerSync($customer['id'], 'update', (array)$customer_obj, $before = true);
882
883 // update customer record
884 $this->dbo->updateObject('#__vikbooking_customers', $customer_obj, 'id');
885
886 // trigger the customer after-save event
887 $this->pluginCustomerSync($customer['id'], 'update', (array)$customer_obj, $before = false);
888 }
889
890 //unset extra info
891 $this->fieldflags = [];
892
893 return !$this->is_admin ? $this->storeCustomerCookie() : true;
894 }
895
896 public function storeCustomerCookie()
897 {
898 $pin = $this->getNewPin();
899 $customer_id = $this->getNewCustomerId();
900 if (empty($pin) || empty($customer_id)) {
901 return false;
902 }
903 $this->setPinCookie($pin);
904 return true;
905 }
906
907 /**
908 * Stores a relation between the Customer ID and the Booking ID
909 * This method should be called after the saveCustomerDetails() because
910 * it requires the methods setNewPin and setNewCustomerId to be called before.
911 * Since VBO 1.9 this method also calculates and sets the commissions
912 * amount if the customer is a sales channel.
913 * Requires the records in _ordersrooms to be stored before being called.
914 *
915 * @param int orderid the ID of the VBO order
916 *
917 * @return bool
918 */
919 public function saveCustomerBooking($orderid)
920 {
921 $pin = $this->getNewPin();
922 $customer_id = $this->getNewCustomerId();
923 if (empty($orderid) || empty($pin) || empty($customer_id)) {
924 return false;
925 }
926
927 $q = "SELECT * FROM `#__vikbooking_ordersrooms` WHERE `idorder`=".(int)$orderid.";";
928 $this->dbo->setQuery($q);
929 $orders_rooms = $this->dbo->loadAssocList();
930 if (!$orders_rooms) {
931 return false;
932 }
933
934 /**
935 * Fetch any existing pre-check-in (or similar) information and keep it during a booking modification event.
936 *
937 * @since 1.18.6 (J) - 1.8.6 (WP)
938 */
939 $this->dbo->setQuery(
940 $this->dbo->getQuery(true)
941 ->select('*')
942 ->from($this->dbo->qn('#__vikbooking_customers_orders'))
943 ->where($this->dbo->qn('idorder') . ' = ' . (int) $orderid)
944 );
945 $prevCustomerBookingData = (array) ($this->dbo->loadAssoc() ?: []);
946
947 // prepare customer-booking data record
948 $customerBookingData = [
949 'idcustomer' => (int) $customer_id,
950 'idorder' => (int) $orderid,
951 ];
952 foreach ($prevCustomerBookingData as $col => $val) {
953 if ($col === 'id' || isset($customerBookingData[$col])) {
954 // skip field
955 continue;
956 }
957 if (!empty($val)) {
958 // preserve previous field
959 $customerBookingData[$col] = $val;
960 }
961 }
962
963 // cast customer-booking data record to object for insert
964 $customerBookingData = (object) $customerBookingData;
965
966 // safely delete any previous customer-booking relation
967 $q = "DELETE FROM `#__vikbooking_customers_orders` WHERE `idorder`=".$this->dbo->quote($orderid).";";
968 $this->dbo->setQuery($q);
969 $this->dbo->execute();
970
971 // (re-)insert customer-booking data
972 $this->dbo->insertObject('#__vikbooking_customers_orders', $customerBookingData, 'id');
973
974 // when assigning a booking to a customer, check that the traveler first and last name is not empty for the page Dashboard that reads it
975 if (empty($orders_rooms[0]['t_first_name']) && empty($orders_rooms[0]['t_last_name'])) {
976 $customer_info = $this->getCustomerByID($customer_id);
977 $q = "UPDATE `#__vikbooking_ordersrooms` SET `t_first_name`=".$this->dbo->quote($customer_info['first_name']).", `t_last_name`=".$this->dbo->quote($customer_info['last_name'])." WHERE `idorder`=".(int)$orderid." LIMIT 1;";
978 $this->dbo->setQuery($q);
979 $this->dbo->execute();
980
981 // update the country as well
982 if (!empty($customer_info['country'])) {
983 $q = "UPDATE `#__vikbooking_orders` SET `country`=".$this->dbo->quote($customer_info['country'])." WHERE `id`=".(int)$orderid.";";
984 $this->dbo->setQuery($q);
985 $this->dbo->execute();
986 }
987 }
988
989 // commissions for customers that are sales channels
990 $this->updateBookingCommissions($orderid, $customer_id);
991
992 return true;
993 }
994
995 /**
996 * Changes the customer assigned to the booking by
997 * re-calculating the amount of commissions (if any).
998 *
999 * @param int $orderid the booking id.
1000 * @param int $customer_id the id of the new customer.
1001 *
1002 * @return boolean
1003 *
1004 * @since 1.11
1005 */
1006 public function updateCustomerBooking($orderid, $customer_id)
1007 {
1008 if (empty($orderid) || empty($customer_id)) {
1009 return false;
1010 }
1011 $new_customer = $this->getCustomerByID($customer_id);
1012 if (!count($new_customer)) {
1013 // invalid customer ID given
1014 return false;
1015 }
1016 $old_customer = $this->getCustomerFromBooking($orderid);
1017 if (count($old_customer) && (int)$old_customer['ischannel'] > 0) {
1018 // unset first the old commissions and channel name
1019 $q = "UPDATE `#__vikbooking_orders` SET `channel`=NULL, `cmms`=NULL WHERE `id`=".(int)$orderid.";";
1020 $this->dbo->setQuery($q);
1021 $this->dbo->execute();
1022 }
1023 if (count($old_customer)) {
1024 // update reference
1025 $q = "UPDATE `#__vikbooking_customers_orders` SET `idcustomer`=".(int)$new_customer['id']." WHERE `idorder`=".(int)$orderid.";";
1026 $this->dbo->setQuery($q);
1027 $this->dbo->execute();
1028 if (!empty($new_customer['country']) && strlen($new_customer['country']) == 3) {
1029 // update booking country
1030 $q = "UPDATE `#__vikbooking_orders` SET `country`=" . $this->dbo->quote($new_customer['country']) . " WHERE `id`=" . (int)$orderid . ";";
1031 $this->dbo->setQuery($q);
1032 $this->dbo->execute();
1033 }
1034 } else {
1035 // insert relation
1036 $q = "INSERT INTO `#__vikbooking_customers_orders` (`idcustomer`,`idorder`) VALUES(".(int)$new_customer['id'].", ".(int)$orderid.");";
1037 $this->dbo->setQuery($q);
1038 $this->dbo->execute();
1039 if (!empty($new_customer['country']) && strlen($new_customer['country']) == 3) {
1040 // update booking country
1041 $q = "UPDATE `#__vikbooking_orders` SET `country`=" . $this->dbo->quote($new_customer['country']) . " WHERE `id`=" . (int)$orderid . ";";
1042 $this->dbo->setQuery($q);
1043 $this->dbo->execute();
1044 }
1045 }
1046
1047 // update commissions and sale channel information
1048 return $this->updateBookingCommissions($orderid, $new_customer['id']);
1049 }
1050
1051 /**
1052 * Calculates and sets the commissions amount if the customer
1053 * is a "sales channel" with a percentage greater than 0.
1054 *
1055 * @param int $orderid
1056 * @param int $customer_id
1057 *
1058 * @since 1.9
1059 */
1060 public function updateBookingCommissions($orderid, $customer_id = 0)
1061 {
1062 if (empty($customer_id)) {
1063 $customer_id = $this->getNewCustomerId();
1064 }
1065
1066 if (empty($orderid) || empty($customer_id)) {
1067 return false;
1068 }
1069
1070 $customer = $this->getCustomerByID($customer_id);
1071 if ((int)$customer['ischannel'] > 0 && !empty($customer['chdata']) && isset($customer['chdata']['commission']) && $customer['chdata']['commission'] > 0.00) {
1072 $q = "SELECT * FROM `#__vikbooking_orders` WHERE `id`=".$this->dbo->quote($orderid).";";
1073 $this->dbo->setQuery($q);
1074 $order_info = $this->dbo->loadAssoc();
1075 if ($order_info) {
1076 if ((float)$order_info['total'] > 0.00) {
1077 $cmms_calc_base = $this->calcCommissionsBaseAmount($order_info, $customer);
1078 $cmms_amount = $cmms_calc_base * $customer['chdata']['commission'] / 100;
1079 $source_name = 'customer'.$customer['id'].(array_key_exists('chname', $customer['chdata']) ? '_'.$customer['chdata']['chname'] : '');
1080
1081 $q = "UPDATE `#__vikbooking_orders` SET `channel`=".$this->dbo->quote($source_name).", `cmms`=".$this->dbo->quote($cmms_amount)." WHERE `id`=".$this->dbo->quote($order_info['id']).";";
1082 $this->dbo->setQuery($q);
1083 $this->dbo->execute();
1084
1085 return true;
1086 }
1087 }
1088 }
1089
1090 return false;
1091 }
1092
1093 /**
1094 * Calculates the base amount on which the commissions should be applied.
1095 * Considers the parameters for this customer sales channel for taxes and rooms rates.
1096 *
1097 * @param array $order_info
1098 * @param array $customer
1099 */
1100 private function calcCommissionsBaseAmount($order_info, $customer)
1101 {
1102 $cmms_calc_base = $order_info['total'];
1103
1104 if (!empty($customer['chdata']) && array_key_exists('calccmmon', $customer['chdata']) && (int)$customer['chdata']['calccmmon'] > 0) {
1105 // commissions based on room rates only
1106 $q = "SELECT `or`.* FROM `#__vikbooking_ordersrooms` AS `or` WHERE `or`.`idorder`='" . (int)$order_info['id'] . "';";
1107 $this->dbo->setQuery($q);
1108 $order_rooms = $this->dbo->loadAssocList();
1109 if ($order_rooms) {
1110 $map_cost_tax = [];
1111 foreach ($order_rooms as $or) {
1112 if (!((float)$or['room_cost'] > 0.00) && !((float)$or['cust_cost'] > 0.00)) {
1113 // missing information about the room cost - cannot proceed
1114 continue;
1115 }
1116 $map_cost_tax[] = array(
1117 'amount' => ((float)$or['room_cost'] > 0.00 ? $or['room_cost'] : $or['cust_cost']),
1118 'taxid' => (!empty($or['cust_cost']) ? intval($or['cust_idiva']) : $this->getTaxIdFromTar($or['idtar']))
1119 );
1120 }
1121 if (count($map_cost_tax) == count($order_rooms)) {
1122 // all rooms have a custom cost or a room cost set. We can proceed
1123 if (array_key_exists('applycmmon', $customer['chdata']) && (int)$customer['chdata']['applycmmon'] > 0) {
1124 // commissions based on amounts tax excluded
1125 if ($this->pricesTaxIncluded()) {
1126 // prices are tax included so update the amounts in $map_cost_tax
1127 foreach ($map_cost_tax as $ctk => $ctv) {
1128 if (!((float)$ctv['amount'] > 0.00) || empty($ctv['taxid'])) {
1129 continue;
1130 }
1131 list($aliq, $taxcap) = $this->getAliqFromTaxId($ctv['taxid']);
1132 if ((float)$aliq > 0.00) {
1133 $op_div = (100 + $aliq) / 100;
1134 $tmp_op = $ctv['amount'] / $op_div;
1135 /**
1136 * Tax Cap implementation
1137 *
1138 * @since 1.12
1139 */
1140 if ($taxcap > 0 && ($ctv['amount'] - $tmp_op) > $taxcap) {
1141 $tmp_op = $ctv['amount'] - $taxcap;
1142 }
1143 $map_cost_tax[$ctk]['amount'] = $tmp_op;
1144 }
1145 }
1146 }
1147 }
1148 // sum all the amounts to get the base amount where commissions will be applied
1149 $sum = 0;
1150 foreach ($map_cost_tax as $k => $map) {
1151 $sum += $map['amount'];
1152 }
1153 $cmms_calc_base = $sum;
1154 }
1155 }
1156 }
1157
1158 return $cmms_calc_base;
1159 }
1160
1161 private function pricesTaxIncluded()
1162 {
1163 return VikBooking::ivaInclusa();
1164 }
1165
1166 /**
1167 * Retrieves the aliquot and tax cap of the tax ID passed.
1168 * Returns 0 if nothing is found, the Tax Aliq otherwise.
1169 *
1170 * @param int $taxid the ID of the tax rate
1171 *
1172 * @return array the aliquote as 0th array value, the tax cap as 1st value.
1173 *
1174 * @since 1.12 this private method used to return just the aliquote.
1175 */
1176 private function getAliqFromTaxId($taxid)
1177 {
1178 $aliq = 0;
1179 $taxcap = 0;
1180 if (intval($taxid) > 0) {
1181 $q = "SELECT `i`.`aliq`,`i`.`taxcap` FROM `#__vikbooking_iva` AS `i` WHERE `i`.`id`=" . (int)$taxid;
1182 $this->dbo->setQuery($q, 0, 1);
1183 $tax_info = $this->dbo->loadAssoc();
1184 if ($tax_info) {
1185 $aliq = $tax_info['aliq'];
1186 $taxcap = $tax_info['taxcap'];
1187 }
1188 }
1189
1190 return [$aliq, $taxcap];
1191 }
1192
1193 /**
1194 * Retrieves the ID of the tax used for the tariff passed.
1195 * Returns 0 if nothing is found, the Tax ID otherwise.
1196 *
1197 * @param int $idtar
1198 */
1199 private function getTaxIdFromTar($idtar)
1200 {
1201 $taxid = 0;
1202 if (intval($idtar) > 0) {
1203 $q = "SELECT `d`.`id`,`d`.`idprice`,`p`.`idiva`,`i`.`aliq` FROM `#__vikbooking_dispcost` AS `d` LEFT JOIN `#__vikbooking_prices` `p` ON `p`.`id`=`d`.`idprice` LEFT JOIN `#__vikbooking_iva` `i` ON `i`.`id`=`p`.`idiva` WHERE `d`.`id`=" . (int)$idtar;
1204 $this->dbo->setQuery($q, 0, 1);
1205 $tax_info = $this->dbo->loadAssoc();
1206 if ($tax_info) {
1207 $taxid = (int)$tax_info['idiva'];
1208 }
1209 }
1210
1211 return $taxid;
1212 }
1213
1214 /**
1215 * Takes the Customer PIN from the Order ID
1216 *
1217 * @param int $orderid
1218 */
1219 public function getPinCodeByOrderId($orderid)
1220 {
1221 $pin = '';
1222 if (!empty($orderid)) {
1223 $q = "SELECT `o`.`id`,`oc`.`idcustomer`,`c`.`pin` FROM `#__vikbooking_orders` AS `o` LEFT JOIN `#__vikbooking_customers_orders` `oc` ON `oc`.`idorder`=`o`.`id` LEFT JOIN `#__vikbooking_customers` `c` ON `c`.`id`=`oc`.`idcustomer` WHERE `o`.`id`=".intval($orderid)." AND `oc`.`idcustomer` IS NOT NULL;";
1224 $this->dbo->setQuery($q);
1225 $custdata = $this->dbo->loadAssocList();
1226 if ($custdata) {
1227 if (!empty($custdata[0]['pin'])) {
1228 $pin = $custdata[0]['pin'];
1229 }
1230 }
1231 }
1232
1233 return $pin;
1234 }
1235
1236 /**
1237 * Triggers the events to sync customers accross multiple plugins.
1238 *
1239 * @param int $customer_id
1240 * @param string $mode
1241 * @param array $data optional customer data associative array.
1242 * @param bool $before whether it's before or after saving the customer.
1243 *
1244 * @since 1.16.7 (J) - 1.6.7 (WP) introduced args $data and $before.
1245 */
1246 public function pluginCustomerSync($customer_id, $mode, $data = [], $before = true)
1247 {
1248 if ($data) {
1249 $customer = $data;
1250 } else {
1251 $q = "SELECT * FROM `#__vikbooking_customers` WHERE `id`=" . (int)$customer_id;
1252 $this->dbo->setQuery($q, 0, 1);
1253 $customer = $this->dbo->loadAssoc();
1254 if (!$customer) {
1255 return false;
1256 }
1257 }
1258
1259 // make sure to import this type of plugins
1260 JPluginHelper::importPlugin('e4j');
1261
1262 // get the event name
1263 if ($mode == 'insert') {
1264 // trigger plugin -> customer creation
1265 $ev_name = 'onCustomerInsert';
1266 } elseif ($mode == 'update') {
1267 // trigger plugin -> customer update
1268 $ev_name = 'onCustomerUpdate';
1269 } elseif ($mode == 'delete') {
1270 // trigger plugin -> customer delete
1271 $ev_name = 'onCustomerDelete';
1272 } else {
1273 return false;
1274 }
1275
1276 // event options
1277 $options = [
1278 'alias' => 'com_vikbooking',
1279 'version' => (defined('VIKBOOKING_SOFTWARE_VERSION') ? VIKBOOKING_SOFTWARE_VERSION : E4J_SOFTWARE_VERSION),
1280 'admin' => VikBooking::isAdmin(),
1281 'call' => __FUNCTION__,
1282 ];
1283
1284 try {
1285 if ($before) {
1286 /**
1287 * Trigger event for the imminent creation, update or deletion of the customer.
1288 */
1289 VBOFactory::getPlatform()->getDispatcher()->trigger($ev_name, [$customer, $options]);
1290 } elseif (!$before && ($mode === 'insert' || $mode === 'update')) {
1291 /**
1292 * Trigger event after the creation or the update of the customer.
1293 */
1294 VBOFactory::getPlatform()->getDispatcher()->trigger('onAfterSaveCustomer', [$customer, ($mode === 'insert')]);
1295 }
1296 } catch (Throwable $e) {
1297 // do nothing
1298 }
1299
1300 return true;
1301 }
1302
1303 /**
1304 * Sets the current customer PIN
1305 *
1306 * @param string $pin
1307 */
1308 public function setNewPin($pin = '')
1309 {
1310 $this->new_pin = $pin;
1311 }
1312
1313 /**
1314 * Get the current customer PIN
1315 */
1316 public function getNewPin()
1317 {
1318 return $this->new_pin;
1319 }
1320
1321 /**
1322 * Sets the current customer ID
1323 *
1324 * @param int $cid
1325 */
1326 public function setNewCustomerId($cid = 0)
1327 {
1328 $this->new_customer_id = (int)$cid;
1329 }
1330
1331 /**
1332 * Get the current customer ID
1333 */
1334 public function getNewCustomerId()
1335 {
1336 return $this->new_customer_id;
1337 }
1338
1339 /**
1340 * Explanation of the XML error
1341 *
1342 * @param object $error
1343 */
1344 public function libxml_display_error($error)
1345 {
1346 $return = "\n";
1347 switch ($error->level) {
1348 case LIBXML_ERR_WARNING :
1349 $return .= "Warning ".$error->code.": ";
1350 break;
1351 case LIBXML_ERR_ERROR :
1352 $return .= "Error ".$error->code.": ";
1353 break;
1354 case LIBXML_ERR_FATAL :
1355 $return .= "Fatal Error ".$error->code.": ";
1356 break;
1357 }
1358 $return .= trim($error->message);
1359 if ($error->file) {
1360 $return .= " in ".$error->file;
1361 }
1362 $return .= " on line ".$error->line."\n";
1363 return $return;
1364 }
1365
1366 /**
1367 * Get the XML errors occurred
1368 */
1369 public function libxml_display_errors()
1370 {
1371 $errorstr = "";
1372 $errors = libxml_get_errors();
1373 foreach ($errors as $error) {
1374 $errorstr .= $this->libxml_display_error($error);
1375 }
1376 libxml_clear_errors();
1377 return $errorstr;
1378 }
1379
1380 private function setError($str)
1381 {
1382 $this->error .= $str."\n";
1383 }
1384
1385 public function getError()
1386 {
1387 return nl2br(rtrim($this->error, "\n"));
1388 }
1389
1390 /**
1391 * Tells whether Vik Booking is up to date to support the customer picture.
1392 * May be used by VCM to detect if this feature is available in Vik Booking.
1393 *
1394 * @return bool
1395 *
1396 * @since 1.15.3 (J) - 1.5.5 (WP)
1397 * @requires VCM >= 1.8.6
1398 */
1399 public function supportsProfileAvatar()
1400 {
1401 return true;
1402 }
1403
1404 /**
1405 * Tells whether Vik Booking is up to date to support the state/province.
1406 * May be used by VCM to detect if this feature is available in Vik Booking.
1407 *
1408 * @return bool
1409 *
1410 * @since 1.16.1 (J) - 1.6.1 (WP)
1411 * @requires VCM >= 1.8.12
1412 */
1413 public function supportsStateProvince()
1414 {
1415 return true;
1416 }
1417 }
1418