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

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

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