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

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

1,074 lines 32.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * @package VikBooking
4 * @subpackage com_vikbooking
5 * @author Alessio Gaggii - e4j - Extensionsforjoomla.com
6 * @copyright Copyright (C) 2018 e4j - Extensionsforjoomla.com. All rights reserved.
7 * @license GNU General Public License version 2 or later; see LICENSE
8 * @link https://vikwp.com
9 */
10
11 defined('ABSPATH') or die('No script kiddies please!');
12
13 /**
14 * Handle customers within the reservation scope.
15 */
16 class VikBookingCustomersPin
17 {
18 public $all_pins;
19 public $is_admin;
20 public $fieldflags;
21 public $error;
22 private $dbo;
23 private $new_pin;
24 private $new_customer_id;
25
26 public function __construct()
27 {
28 $this->all_pins = false;
29 $this->is_admin = false;
30 $this->fieldflags = [];
31 $this->error = '';
32 $this->dbo = JFactory::getDbo();
33 $this->new_pin = '';
34 $this->new_customer_id = 0;
35 }
36
37 /**
38 * Generates a unique PIN number for the customer.
39 *
40 * @param boolean $notpush
41 *
42 * @return int 8-digit pin
43 *
44 * @since 1.15.0 (J) - 1.5.0 (WP) pin length changed from 5 to 8 digits.
45 */
46 public function generateUniquePin($notpush = false)
47 {
48 // minimum 5 digits, maximum 8 digits
49 $rand_pin = rand(10000, 99999999);
50 if ($this->pinExists($rand_pin)) {
51 while ($this->pinExists($rand_pin)) {
52 $rand_pin += 1;
53 }
54 }
55
56 if (!$notpush) {
57 $this->all_pins[] = $rand_pin;
58 }
59
60 return $rand_pin;
61 }
62
63 /**
64 * Checks if the pin already exists.
65 *
66 * @param string $pin
67 * @param string $ignorepin
68 *
69 * @return boolean
70 */
71 public function pinExists($pin, $ignorepin = '')
72 {
73 $current_pins = $this->all_pins === false ? $this->getAllPins($ignorepin) : $this->all_pins;
74 return in_array($pin, $current_pins);
75 }
76
77 /**
78 * Fetches and sets all the pins currently stored in the database.
79 *
80 * @param string $ignorepin
81 */
82 public function getAllPins($ignorepin = '')
83 {
84 $current_pins = [];
85
86 $q = "SELECT `pin` FROM `#__vikbooking_customers`".(!empty($ignorepin) ? " WHERE `pin`!=".$this->dbo->quote($ignorepin) : "").";";
87 $this->dbo->setQuery($q);
88 $pins = $this->dbo->loadAssocList();
89 if ($pins) {
90 foreach ($pins as $v) {
91 $current_pins[] = $v['pin'];
92 }
93 }
94
95 $this->all_pins = $current_pins;
96
97 return $this->all_pins;
98 }
99
100 /**
101 * Attempts to fetch the customer details record by Joomla/WordPress User ID.
102 *
103 * @param array $customer_details
104 *
105 * @return array can also be used with no return value as reference.
106 */
107 private function getDetailsByUjid(&$customer_details)
108 {
109 $user = JFactory::getUser();
110
111 if (!$user->guest && (int)$user->id > 0) {
112 $q = "SELECT * FROM `#__vikbooking_customers` WHERE `ujid`=" . (int)$user->id . " ORDER BY `#__vikbooking_customers`.`id` DESC";
113 $this->dbo->setQuery($q, 0, 1);
114 $customer = $this->dbo->loadAssoc();
115 if ($customer) {
116 $customer['cfields'] = empty($customer['cfields']) ? array() : json_decode($customer['cfields'], true);
117 $customer_details = $customer;
118 }
119 }
120
121 return $customer_details;
122 }
123
124 /**
125 * Attempts to fetch the customer details record by PIN Cookie.
126 *
127 * @param array $customer_details
128 *
129 * @return array can also be used with no return value as reference.
130 */
131 private function getDetailsByPinCookie(&$customer_details)
132 {
133 $pin_cookie = $this->getPinCookie();
134 $pin_cookie = empty($pin_cookie) ? (int)$this->getNewPin() : $pin_cookie;
135 if ($pin_cookie) {
136 $q = "SELECT * FROM `#__vikbooking_customers` WHERE `pin`=" . $this->dbo->quote($pin_cookie) . " ORDER BY `#__vikbooking_customers`.`id` DESC";
137 $this->dbo->setQuery($q, 0, 1);
138 $customer = $this->dbo->loadAssoc();
139 if ($customer) {
140 $customer['cfields'] = empty($customer['cfields']) ? array() : json_decode($customer['cfields'], true);
141 $customer_details = $customer;
142 }
143 }
144
145 return $customer_details;
146 }
147
148 /**
149 * Gets "decoded" PIN from Cookie.
150 *
151 * @return int the pin cookie.
152 */
153 private function getPinCookie()
154 {
155 $pin_cookie = 0;
156 $cookie = JFactory::getApplication()->input->cookie;
157 $cookie_val = $cookie->get('vboPinData', '', 'string');
158 if (!empty($cookie_val) && intval($cookie_val) > 0) {
159 $cookie_val = intval(strrev( (string)$cookie_val )) / 1987;
160 $pin_cookie = (int)$cookie_val > 0 ? $cookie_val : $pin_cookie;
161 }
162 return $pin_cookie;
163 }
164
165 /**
166 * Sets "encoded" PIN to Cookie with a lifetime of 365 days.
167 *
168 * @param string $pin
169 */
170 private function setPinCookie($pin)
171 {
172 $pin_cookie = 0;
173 if (!empty($pin)) {
174 $pin_cookie = (int)$pin * 1987;
175 $pin_cookie = strrev( (string)$pin_cookie );
176 VikRequest::setCookie('vboPinData', $pin_cookie, (time() + (86400 * 365)), '/', '', false, true);
177 }
178
179 return $pin_cookie;
180 }
181
182 /**
183 * Unsets PIN Cookie
184 */
185 private function unsetPinCookie()
186 {
187 $cookie = JFactory::getApplication()->input->cookie;
188 VikRequest::setCookie('vboPinData', $pin_cookie, (time() - (86400 * 365)), '/', '', false, true);
189 $cookie_val = $cookie->get('vboPinData', '', 'string');
190
191 return $pin_cookie;
192 }
193
194 /**
195 * Loads the customer details by Joomla/WordPress User ID or by PIN cookie.
196 * Returns an associative array with the record fetched from the DB.
197 *
198 * @return array empty array or customer record associative array.
199 */
200 public function loadCustomerDetails()
201 {
202 $customer_details = [];
203
204 // first attempt is through Joomla User ID
205 $this->getDetailsByUjid($customer_details);
206
207 if (!count($customer_details)) {
208 // second attempt is through PIN Cookie
209 $this->getDetailsByPinCookie($customer_details);
210 }
211
212 return $customer_details;
213 }
214
215 /**
216 * Checks whether the given customer has got automatic discounts reserved for the current booking.
217 *
218 * @param array $customer customer record associative array.
219 * @param array $booking the booking information array to validate the coupon.
220 *
221 * @return array empty array or proper customer coupon record.
222 *
223 * @since 1.16.0 (J) - 1.6.0 (WP)
224 */
225 public function getCustomerCoupon(array $customer = [], array $booking = [])
226 {
227 if (empty($customer['id'])) {
228 return [];
229 }
230
231 $q = "SELECT `ccp`.`idcoupon`, `cp`.* FROM `#__vikbooking_customers_coupons` AS `ccp`
232 LEFT JOIN `#__vikbooking_coupons` AS `cp` ON `ccp`.`idcoupon`=`cp`.`id`
233 WHERE `ccp`.`idcustomer`=" . (int)$customer['id'] . " AND `ccp`.`automatic`=1 ORDER BY `ccp`.`idcoupon` DESC";
234 $this->dbo->setQuery($q);
235 $customer_coupons = $this->dbo->loadAssocList();
236 if (!$customer_coupons) {
237 return [];
238 }
239
240 if (empty($booking)) {
241 // do not perform any validation on the coupon restrictions
242 return $customer_coupons[0];
243 }
244
245 // process all coupon codes for this customer to find the best one for this booking details
246 foreach ($customer_coupons as $coupon) {
247 if (!empty($coupon['datevalid'])) {
248 $dateparts = explode("-", $coupon['datevalid']);
249 $pickinfo = getdate($booking['checkin']);
250 $dropinfo = getdate($booking['checkout']);
251 $checkpick = mktime(0, 0, 0, $pickinfo['mon'], $pickinfo['mday'], $pickinfo['year']);
252 $checkdrop = mktime(0, 0, 0, $dropinfo['mon'], $dropinfo['mday'], $dropinfo['year']);
253 if (!($checkpick >= $dateparts[0] && $checkpick <= $dateparts[1] && $checkdrop >= $dateparts[0] && $checkdrop <= $dateparts[1])) {
254 // invalid dates
255 continue;
256 }
257 }
258 if (!empty($coupon['minlos']) && $coupon['minlos'] > $booking['days']) {
259 // invalid min LOS
260 continue;
261 }
262 if (!$coupon['allvehicles'] && !empty($coupon['idrooms'])) {
263 // validate rooms booked
264 foreach ((array)$booking['rooms'] as $room) {
265 if (!preg_match("/;" . $room['id'] . ";/i", $coupon['idrooms'])) {
266 // room not allowed
267 continue 2;
268 }
269 }
270 }
271 // return the first eligible coupon discount
272 return $coupon;
273 }
274
275 // no eligible discounts found
276 return [];
277 }
278
279 /**
280 * Attempts to fetch the customer details record by PIN code.
281 */
282 public function getCustomerByPin($pin)
283 {
284 $customer = [];
285 $this->setNewPin($pin);
286
287 if (!empty($pin)) {
288 $q = "SELECT * FROM `#__vikbooking_customers` WHERE `pin`=".$this->dbo->quote($pin)." ORDER BY `#__vikbooking_customers`.`id` DESC";
289 $this->dbo->setQuery($q, 0, 1);
290 $customer = $this->dbo->loadAssoc();
291 if ($customer) {
292 $customer['cfields'] = empty($customer['cfields']) ? array() : json_decode($customer['cfields'], true);
293 $this->setPinCookie($pin);
294 }
295 }
296
297 return $customer;
298 }
299
300 /**
301 * Get customer by ID.
302 *
303 * @param int $cust_id the ID of the customer.
304 *
305 * @return array the customer array or an empty array.
306 */
307 public function getCustomerByID($cust_id)
308 {
309 $customer = [];
310 if (!empty($cust_id)) {
311 $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);
312 $this->dbo->setQuery($q, 0, 1);
313 $customer = $this->dbo->loadAssoc();
314 $customer = !$customer ? [] : $customer;
315 }
316
317 if ($customer) {
318 $customer['cfields'] = empty($customer['cfields']) ? array() : json_decode($customer['cfields'], true);
319 $customer['chdata'] = !empty($customer['chdata']) ? json_decode($customer['chdata'], true) : array();
320 $customer['chdata'] = is_array($customer['chdata']) ? $customer['chdata'] : array();
321 }
322
323 return $customer;
324 }
325
326 /**
327 * Get customer array by booking ID.
328 * Also returns the pax_data information.
329 *
330 * @param int $orderid the VBO booking ID.
331 *
332 * @return array the customer array or an empty array.
333 *
334 * @uses getCustomerByID()
335 */
336 public function getCustomerFromBooking($orderid)
337 {
338 if (empty($orderid)) {
339 return [];
340 }
341 $q = "SELECT `idcustomer`, `pax_data` FROM `#__vikbooking_customers_orders` WHERE `idorder`=".(int)$orderid.";";
342 $this->dbo->setQuery($q);
343 $data = $this->dbo->loadAssoc();
344 if (!$data) {
345 return [];
346 }
347
348 $customer = $this->getCustomerByID($data['idcustomer']);
349 if ($customer) {
350 /**
351 * Merge pax_data into the customer array to know whether
352 * the pre check-in or the registration was performed.
353 *
354 * @since 1.12
355 */
356 $customer['pax_data'] = $data['pax_data'];
357 }
358
359 return $customer;
360 }
361
362 /**
363 * Checks whether a customer with the same email address already exists
364 * Returns false or the record of the existing customer
365 *
366 * @param string $email the email address.
367 * @param string $first_name optional first name to compare existing emails.
368 * @param string $last_name optional last name to compare existing emails.
369 *
370 * @return mixed false if customer does not exist, array if customer found.
371 *
372 * @since 1.13 equal email addresses can be shared across multiple customers
373 * so long as the name or the last name are different.
374 */
375 public function customerExists($email, $first_name = '', $last_name = '')
376 {
377 if (empty($email)) {
378 return false;
379 }
380 $q = "SELECT * FROM `#__vikbooking_customers` WHERE `email`=".$this->dbo->quote(trim($email))." ORDER BY `#__vikbooking_customers`.`id` DESC;";
381 $this->dbo->setQuery($q);
382 $customers = $this->dbo->loadAssocList();
383 if (!$customers) {
384 return false;
385 }
386
387 if (empty($first_name) || empty($last_name)) {
388 // no info to compare an existing customer, so say it exists with this email address
389 return $customers[0];
390 }
391
392 // check if name and last name match with the current customers having this email address
393 foreach ($customers as $c) {
394 if (stripos($c['first_name'], $first_name) !== false && stripos($c['last_name'], $last_name) !== false) {
395 // customer with same email, first name and last name found
396 return $c;
397 }
398 }
399
400 /**
401 * We now check if a record with same email, first name and last name is found first, if not we return false.
402 *
403 * @since 1.14 (Joomla) - 1.4.0 (WordPress)
404 */
405 return false;
406 }
407
408 /**
409 * Sets some customer extra information like address, city, zip, company name, vat
410 */
411 public function setCustomerExtraInfo($fieldflags)
412 {
413 if (is_array($fieldflags) && count($fieldflags) > 0) {
414 $this->fieldflags = $fieldflags;
415 }
416 }
417
418 /**
419 * Converts the 2-char country code or country name into the ISO Alpha3 Char Code.
420 *
421 * @param string $country the 2-char country code or country name to convert.
422 *
423 * @return string either the 3-char version or the passed value.
424 *
425 * @since 1.13.5
426 */
427 public function get3CharCountry($country)
428 {
429 if (empty($country) || strlen((string)$country) < 2) {
430 return $country;
431 }
432
433 // trim white spaces
434 $country = trim($country);
435
436 // check what field to look for
437 $clause = [];
438 if (strlen($country) == 2) {
439 array_push($clause, "`country_2_code`=" . $this->dbo->quote($country));
440 } else {
441 array_push($clause, "`country_name` LIKE " . $this->dbo->quote("%{$country}%"));
442 }
443
444 // query the db
445 $q = "SELECT `country_3_code` FROM `#__vikbooking_countries` WHERE " . implode(' AND ', $clause);
446 $this->dbo->setQuery($q, 0, 1);
447 $three_country = $this->dbo->loadResult();
448 if ($three_country) {
449 // 3-char code found
450 return $three_country;
451 }
452
453 // nothing found, return the passed value
454 return $country;
455 }
456
457 /**
458 * Saves the customer in DB if it doesn't exist, generates the PIN and sets the cookie
459 */
460 public function saveCustomerDetails($first_name, $last_name, $email, $phone_number, $country, $cfields)
461 {
462 if (empty($first_name) || empty($last_name) || empty($email)) {
463 $this->setError('Missing fields for saving new customer');
464 return false;
465 }
466
467 /**
468 * We convert any 2-char country code into a 3-char country code
469 *
470 * @since 1.13.5
471 */
472 if (!empty($country) && strlen($country) == 2) {
473 $country = $this->get3CharCountry($country);
474 }
475
476 /**
477 * In case the phone number is missing the international prefix at the beginning of the string,
478 * mostly happens in case of OTA bookings, we pre-pend the country prefix.
479 *
480 * @since 1.12 (patch October 2019)
481 */
482 $phone_number = trim($phone_number);
483 if (!empty($phone_number) && !empty($country) && substr($phone_number, 0, 1) != '+' && substr($phone_number, 0, 2) != '00') {
484 // try to find the country phone prefix
485 $q = "SELECT `phone_prefix` FROM `#__vikbooking_countries` WHERE `country_" . (strlen($country) == 2 ? '2' : '3') . "_code`=" . $this->dbo->quote($country) . ";";
486 $this->dbo->setQuery($q);
487 $phone_prefix = $this->dbo->loadResult();
488 if ($phone_prefix) {
489 $country_prefix = str_replace(' ', '', $phone_prefix);
490 $num_prefix = str_replace('+', '', $country_prefix);
491 if (substr($phone_number, 0, strlen($num_prefix)) != $num_prefix) {
492 // country prefix is completely missing
493 $phone_number = $country_prefix . $phone_number;
494 } else {
495 // try to prepend the plus symbol because the phone number starts with the country prefix
496 $phone_number = '+' . $phone_number;
497 }
498 }
499 }
500
501 // check if the customer exists
502 $customer = $this->customerExists($email, $first_name, $last_name);
503
504 if ($customer === false) {
505 $new_pin = $this->generateUniquePin();
506 $user = JFactory::getUser();
507
508 // build customer record
509 $customer_obj = new stdClass;
510 $customer_obj->first_name = $first_name;
511 $customer_obj->last_name = $last_name;
512 $customer_obj->email = $email;
513 $customer_obj->phone = $phone_number;
514 $customer_obj->country = $country;
515 $customer_obj->cfields = is_array($cfields) && $cfields ? json_encode($cfields) : null;
516 $customer_obj->pin = $new_pin;
517 $customer_obj->ujid = $this->is_admin ? 0 : (int)$user->id;
518 // add the extra field flags
519 foreach ($this->fieldflags as $flagk => $flagv) {
520 if (is_array($flagv) || is_object($flagv)) {
521 $flagv = json_encode($flagv);
522 }
523 $customer_obj->{$flagk} = $flagv;
524 }
525
526 // trigger the customer before-insert event
527 $this->pluginCustomerSync(0, 'insert', (array)$customer_obj, $before = true);
528
529 // insert the new customer record
530 $this->dbo->insertObject('#__vikbooking_customers', $customer_obj, 'id');
531 $new_customer_id = isset($customer_obj->id) ? $customer_obj->id : null;
532
533 if (!empty($new_customer_id)) {
534 $this->setNewPin($new_pin);
535 $this->setNewCustomerId($new_customer_id);
536
537 // trigger the customer after-save event
538 $this->pluginCustomerSync($new_customer_id, 'insert', (array)$customer_obj, $before = false);
539 }
540 } elseif (is_array($customer) && $customer) {
541 $this->setNewPin($customer['pin']);
542 $this->setNewCustomerId($customer['id']);
543
544 // build customer record
545 $customer_obj = new stdClass;
546 $customer_obj->id = $customer['id'];
547 $customer_obj->first_name = $first_name;
548 $customer_obj->last_name = $last_name;
549 $customer_obj->email = $email;
550 $customer_obj->phone = $phone_number;
551 $customer_obj->country = $country;
552 if (!$this->is_admin) {
553 $customer_obj->cfields = is_array($cfields) && $cfields ? json_encode($cfields) : null;
554 }
555 $customer_obj->pin = $new_pin;
556 $customer_obj->ujid = $this->is_admin ? 0 : (int)$user->id;
557 // add the extra field flags
558 foreach ($this->fieldflags as $flagk => $flagv) {
559 if (is_array($flagv) || is_object($flagv)) {
560 $flagv = json_encode($flagv);
561 }
562 $customer_obj->{$flagk} = $flagv;
563 }
564
565 // trigger the customer before-update event
566 $this->pluginCustomerSync($customer['id'], 'update', (array)$customer_obj, $before = true);
567
568 // update customer record
569 $this->dbo->updateObject('#__vikbooking_customers', $customer_obj, 'id');
570
571 // trigger the customer after-save event
572 $this->pluginCustomerSync($customer['id'], 'update', (array)$customer_obj, $before = false);
573 }
574
575 //unset extra info
576 $this->fieldflags = [];
577
578 return !$this->is_admin ? $this->storeCustomerCookie() : true;
579 }
580
581 public function storeCustomerCookie()
582 {
583 $pin = $this->getNewPin();
584 $customer_id = $this->getNewCustomerId();
585 if (empty($pin) || empty($customer_id)) {
586 return false;
587 }
588 $this->setPinCookie($pin);
589 return true;
590 }
591
592 /**
593 * Stores a relation between the Customer ID and the Booking ID
594 * This method should be called after the saveCustomerDetails() because
595 * it requires the methods setNewPin and setNewCustomerId to be called before.
596 * Since VBO 1.9 this method also calculates and sets the commissions
597 * amount if the customer is a sales channel.
598 * Requires the records in _ordersrooms to be stored before being called.
599 *
600 * @param int orderid the ID of the VBO order
601 *
602 * @return boolean
603 */
604 public function saveCustomerBooking($orderid)
605 {
606 $pin = $this->getNewPin();
607 $customer_id = $this->getNewCustomerId();
608 if (empty($orderid) || empty($pin) || empty($customer_id)) {
609 return false;
610 }
611
612 $q = "SELECT * FROM `#__vikbooking_ordersrooms` WHERE `idorder`=".(int)$orderid.";";
613 $this->dbo->setQuery($q);
614 $orders_rooms = $this->dbo->loadAssocList();
615 if (!$orders_rooms) {
616 return false;
617 }
618
619 $q = "DELETE FROM `#__vikbooking_customers_orders` WHERE `idorder`=".$this->dbo->quote($orderid).";";
620 $this->dbo->setQuery($q);
621 $this->dbo->execute();
622
623 $q = "INSERT INTO `#__vikbooking_customers_orders` (`idcustomer`,`idorder`) VALUES(".$this->dbo->quote($customer_id).", ".$this->dbo->quote($orderid).");";
624 $this->dbo->setQuery($q);
625 $this->dbo->execute();
626
627 // 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
628 if (empty($orders_rooms[0]['t_first_name']) && empty($orders_rooms[0]['t_last_name'])) {
629 $customer_info = $this->getCustomerByID($customer_id);
630 $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;";
631 $this->dbo->setQuery($q);
632 $this->dbo->execute();
633
634 // update the country as well
635 if (!empty($customer_info['country'])) {
636 $q = "UPDATE `#__vikbooking_orders` SET `country`=".$this->dbo->quote($customer_info['country'])." WHERE `id`=".(int)$orderid.";";
637 $this->dbo->setQuery($q);
638 $this->dbo->execute();
639 }
640 }
641
642 // commissions for customers that are sales channels
643 $this->updateBookingCommissions($orderid, $customer_id);
644
645 return true;
646 }
647
648 /**
649 * Changes the customer assigned to the booking by
650 * re-calculating the amount of commissions (if any).
651 *
652 * @param int $orderid the booking id.
653 * @param int $customer_id the id of the new customer.
654 *
655 * @return boolean
656 *
657 * @since 1.11
658 */
659 public function updateCustomerBooking($orderid, $customer_id)
660 {
661 if (empty($orderid) || empty($customer_id)) {
662 return false;
663 }
664 $new_customer = $this->getCustomerByID($customer_id);
665 if (!count($new_customer)) {
666 // invalid customer ID given
667 return false;
668 }
669 $old_customer = $this->getCustomerFromBooking($orderid);
670 if (count($old_customer) && (int)$old_customer['ischannel'] > 0) {
671 // unset first the old commissions and channel name
672 $q = "UPDATE `#__vikbooking_orders` SET `channel`=NULL, `cmms`=NULL WHERE `id`=".(int)$orderid.";";
673 $this->dbo->setQuery($q);
674 $this->dbo->execute();
675 }
676 if (count($old_customer)) {
677 // update reference
678 $q = "UPDATE `#__vikbooking_customers_orders` SET `idcustomer`=".(int)$new_customer['id']." WHERE `idorder`=".(int)$orderid.";";
679 $this->dbo->setQuery($q);
680 $this->dbo->execute();
681 if (!empty($new_customer['country']) && strlen($new_customer['country']) == 3) {
682 // update booking country
683 $q = "UPDATE `#__vikbooking_orders` SET `country`=" . $this->dbo->quote($new_customer['country']) . " WHERE `id`=" . (int)$orderid . ";";
684 $this->dbo->setQuery($q);
685 $this->dbo->execute();
686 }
687 } else {
688 // insert relation
689 $q = "INSERT INTO `#__vikbooking_customers_orders` (`idcustomer`,`idorder`) VALUES(".(int)$new_customer['id'].", ".(int)$orderid.");";
690 $this->dbo->setQuery($q);
691 $this->dbo->execute();
692 if (!empty($new_customer['country']) && strlen($new_customer['country']) == 3) {
693 // update booking country
694 $q = "UPDATE `#__vikbooking_orders` SET `country`=" . $this->dbo->quote($new_customer['country']) . " WHERE `id`=" . (int)$orderid . ";";
695 $this->dbo->setQuery($q);
696 $this->dbo->execute();
697 }
698 }
699
700 // update commissions and sale channel information
701 return $this->updateBookingCommissions($orderid, $new_customer['id']);
702 }
703
704 /**
705 * Calculates and sets the commissions amount if the customer
706 * is a "sales channel" with a percentage greater than 0.
707 *
708 * @param int $orderid
709 * @param int $customer_id
710 *
711 * @since 1.9
712 */
713 public function updateBookingCommissions($orderid, $customer_id = 0)
714 {
715 if (empty($customer_id)) {
716 $customer_id = $this->getNewCustomerId();
717 }
718
719 if (empty($orderid) || empty($customer_id)) {
720 return false;
721 }
722
723 $customer = $this->getCustomerByID($customer_id);
724 if ((int)$customer['ischannel'] > 0 && !empty($customer['chdata']) && isset($customer['chdata']['commission']) && $customer['chdata']['commission'] > 0.00) {
725 $q = "SELECT * FROM `#__vikbooking_orders` WHERE `id`=".$this->dbo->quote($orderid).";";
726 $this->dbo->setQuery($q);
727 $order_info = $this->dbo->loadAssoc();
728 if ($order_info) {
729 if ((float)$order_info['total'] > 0.00) {
730 $cmms_calc_base = $this->calcCommissionsBaseAmount($order_info, $customer);
731 $cmms_amount = $cmms_calc_base * $customer['chdata']['commission'] / 100;
732 $source_name = 'customer'.$customer['id'].(array_key_exists('chname', $customer['chdata']) ? '_'.$customer['chdata']['chname'] : '');
733
734 $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']).";";
735 $this->dbo->setQuery($q);
736 $this->dbo->execute();
737
738 return true;
739 }
740 }
741 }
742
743 return false;
744 }
745
746 /**
747 * Calculates the base amount on which the commissions should be applied.
748 * Considers the parameters for this customer sales channel for taxes and rooms rates.
749 *
750 * @param array $order_info
751 * @param array $customer
752 */
753 private function calcCommissionsBaseAmount($order_info, $customer)
754 {
755 $cmms_calc_base = $order_info['total'];
756
757 if (!empty($customer['chdata']) && array_key_exists('calccmmon', $customer['chdata']) && (int)$customer['chdata']['calccmmon'] > 0) {
758 // commissions based on room rates only
759 $q = "SELECT `or`.* FROM `#__vikbooking_ordersrooms` AS `or` WHERE `or`.`idorder`='" . (int)$order_info['id'] . "';";
760 $this->dbo->setQuery($q);
761 $order_rooms = $this->dbo->loadAssocList();
762 if ($order_rooms) {
763 $map_cost_tax = [];
764 foreach ($order_rooms as $or) {
765 if (!((float)$or['room_cost'] > 0.00) && !((float)$or['cust_cost'] > 0.00)) {
766 // missing information about the room cost - cannot proceed
767 continue;
768 }
769 $map_cost_tax[] = array(
770 'amount' => ((float)$or['room_cost'] > 0.00 ? $or['room_cost'] : $or['cust_cost']),
771 'taxid' => (!empty($or['cust_cost']) ? intval($or['cust_idiva']) : $this->getTaxIdFromTar($or['idtar']))
772 );
773 }
774 if (count($map_cost_tax) == count($order_rooms)) {
775 // all rooms have a custom cost or a room cost set. We can proceed
776 if (array_key_exists('applycmmon', $customer['chdata']) && (int)$customer['chdata']['applycmmon'] > 0) {
777 // commissions based on amounts tax excluded
778 if ($this->pricesTaxIncluded()) {
779 // prices are tax included so update the amounts in $map_cost_tax
780 foreach ($map_cost_tax as $ctk => $ctv) {
781 if (!((float)$ctv['amount'] > 0.00) || empty($ctv['taxid'])) {
782 continue;
783 }
784 list($aliq, $taxcap) = $this->getAliqFromTaxId($ctv['taxid']);
785 if ((float)$aliq > 0.00) {
786 $op_div = (100 + $aliq) / 100;
787 $tmp_op = $ctv['amount'] / $op_div;
788 /**
789 * Tax Cap implementation
790 *
791 * @since 1.12
792 */
793 if ($taxcap > 0 && ($ctv['amount'] - $tmp_op) > $taxcap) {
794 $tmp_op = $ctv['amount'] - $taxcap;
795 }
796 $map_cost_tax[$ctk]['amount'] = $tmp_op;
797 }
798 }
799 }
800 }
801 // sum all the amounts to get the base amount where commissions will be applied
802 $sum = 0;
803 foreach ($map_cost_tax as $k => $map) {
804 $sum += $map['amount'];
805 }
806 $cmms_calc_base = $sum;
807 }
808 }
809 }
810
811 return $cmms_calc_base;
812 }
813
814 private function pricesTaxIncluded()
815 {
816 if (!class_exists('VikBooking')) {
817 require_once(VBO_SITE_PATH.DIRECTORY_SEPARATOR.'helpers'.DIRECTORY_SEPARATOR.'lib.vikbooking.php');
818 }
819 return VikBooking::ivaInclusa();
820 }
821
822 /**
823 * Retrieves the aliquot and tax cap of the tax ID passed.
824 * Returns 0 if nothing is found, the Tax Aliq otherwise.
825 *
826 * @param int $taxid the ID of the tax rate
827 *
828 * @return array the aliquote as 0th array value, the tax cap as 1st value.
829 *
830 * @since 1.12 this private method used to return just the aliquote.
831 */
832 private function getAliqFromTaxId($taxid)
833 {
834 $aliq = 0;
835 $taxcap = 0;
836 if (intval($taxid) > 0) {
837 $q = "SELECT `i`.`aliq`,`i`.`taxcap` FROM `#__vikbooking_iva` AS `i` WHERE `i`.`id`=" . (int)$taxid;
838 $this->dbo->setQuery($q, 0, 1);
839 $tax_info = $this->dbo->loadAssoc();
840 if ($tax_info) {
841 $aliq = $tax_info['aliq'];
842 $taxcap = $tax_info['taxcap'];
843 }
844 }
845
846 return [$aliq, $taxcap];
847 }
848
849 /**
850 * Retrieves the ID of the tax used for the tariff passed.
851 * Returns 0 if nothing is found, the Tax ID otherwise.
852 *
853 * @param int $idtar
854 */
855 private function getTaxIdFromTar($idtar)
856 {
857 $taxid = 0;
858 if (intval($idtar) > 0) {
859 $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;
860 $this->dbo->setQuery($q, 0, 1);
861 $tax_info = $this->dbo->loadAssoc();
862 if ($tax_info) {
863 $taxid = (int)$tax_info['idiva'];
864 }
865 }
866
867 return $taxid;
868 }
869
870 /**
871 * Takes the Customer PIN from the Order ID
872 *
873 * @param int $orderid
874 */
875 public function getPinCodeByOrderId($orderid)
876 {
877 $pin = '';
878 if (!empty($orderid)) {
879 $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;";
880 $this->dbo->setQuery($q);
881 $custdata = $this->dbo->loadAssocList();
882 if ($custdata) {
883 if (!empty($custdata[0]['pin'])) {
884 $pin = $custdata[0]['pin'];
885 }
886 }
887 }
888
889 return $pin;
890 }
891
892 /**
893 * Triggers the events to sync customers accross multiple plugins.
894 *
895 * @param int $customer_id
896 * @param string $mode
897 * @param array $data optional customer data associative array.
898 * @param bool $before whether it's before or after saving the customer.
899 *
900 * @since 1.16.7 (J) - 1.6.7 (WP) introduced args $data and $before.
901 */
902 public function pluginCustomerSync($customer_id, $mode, $data = [], $before = true)
903 {
904 if ($data) {
905 $customer = $data;
906 } else {
907 $q = "SELECT * FROM `#__vikbooking_customers` WHERE `id`=" . (int)$customer_id;
908 $this->dbo->setQuery($q, 0, 1);
909 $customer = $this->dbo->loadAssoc();
910 if (!$customer) {
911 return false;
912 }
913 }
914
915 // make sure to import this type of plugins
916 JPluginHelper::importPlugin('e4j');
917
918 // get the event name
919 if ($mode == 'insert') {
920 // trigger plugin -> customer creation
921 $ev_name = 'onCustomerInsert';
922 } elseif ($mode == 'update') {
923 // trigger plugin -> customer update
924 $ev_name = 'onCustomerUpdate';
925 } elseif ($mode == 'delete') {
926 // trigger plugin -> customer delete
927 $ev_name = 'onCustomerDelete';
928 } else {
929 return false;
930 }
931
932 // event options
933 $options = [
934 'alias' => 'com_vikbooking',
935 'version' => (defined('VIKBOOKING_SOFTWARE_VERSION') ? VIKBOOKING_SOFTWARE_VERSION : E4J_SOFTWARE_VERSION),
936 'admin' => VikBooking::isAdmin(),
937 'call' => __FUNCTION__,
938 ];
939
940 try {
941 if ($before) {
942 /**
943 * Trigger event for the imminent creation, update or deletion of the customer.
944 */
945 VBOFactory::getPlatform()->getDispatcher()->trigger($ev_name, [$customer, $options]);
946 } elseif (!$before && ($mode === 'insert' || $mode === 'update')) {
947 /**
948 * Trigger event after the creation or the update of the customer.
949 */
950 VBOFactory::getPlatform()->getDispatcher()->trigger('onAfterSaveCustomer', [$customer, ($mode === 'insert')]);
951 }
952 } catch (Throwable $e) {
953 // do nothing
954 }
955
956 return true;
957 }
958
959 /**
960 * Sets the current customer PIN
961 *
962 * @param string $pin
963 */
964 public function setNewPin($pin = '')
965 {
966 $this->new_pin = $pin;
967 }
968
969 /**
970 * Get the current customer PIN
971 */
972 public function getNewPin()
973 {
974 return $this->new_pin;
975 }
976
977 /**
978 * Sets the current customer ID
979 *
980 * @param int $cid
981 */
982 public function setNewCustomerId($cid = 0)
983 {
984 $this->new_customer_id = (int)$cid;
985 }
986
987 /**
988 * Get the current customer ID
989 */
990 public function getNewCustomerId()
991 {
992 return $this->new_customer_id;
993 }
994
995 /**
996 * Explanation of the XML error
997 *
998 * @param object $error
999 */
1000 public function libxml_display_error($error)
1001 {
1002 $return = "\n";
1003 switch ($error->level) {
1004 case LIBXML_ERR_WARNING :
1005 $return .= "Warning ".$error->code.": ";
1006 break;
1007 case LIBXML_ERR_ERROR :
1008 $return .= "Error ".$error->code.": ";
1009 break;
1010 case LIBXML_ERR_FATAL :
1011 $return .= "Fatal Error ".$error->code.": ";
1012 break;
1013 }
1014 $return .= trim($error->message);
1015 if ($error->file) {
1016 $return .= " in ".$error->file;
1017 }
1018 $return .= " on line ".$error->line."\n";
1019 return $return;
1020 }
1021
1022 /**
1023 * Get the XML errors occurred
1024 */
1025 public function libxml_display_errors()
1026 {
1027 $errorstr = "";
1028 $errors = libxml_get_errors();
1029 foreach ($errors as $error) {
1030 $errorstr .= $this->libxml_display_error($error);
1031 }
1032 libxml_clear_errors();
1033 return $errorstr;
1034 }
1035
1036 private function setError($str)
1037 {
1038 $this->error .= $str."\n";
1039 }
1040
1041 public function getError()
1042 {
1043 return nl2br(rtrim($this->error, "\n"));
1044 }
1045
1046 /**
1047 * Tells whether Vik Booking is up to date to support the customer picture.
1048 * May be used by VCM to detect if this feature is available in Vik Booking.
1049 *
1050 * @return bool
1051 *
1052 * @since 1.15.3 (J) - 1.5.5 (WP)
1053 * @requires VCM >= 1.8.6
1054 */
1055 public function supportsProfileAvatar()
1056 {
1057 return true;
1058 }
1059
1060 /**
1061 * Tells whether Vik Booking is up to date to support the state/province.
1062 * May be used by VCM to detect if this feature is available in Vik Booking.
1063 *
1064 * @return bool
1065 *
1066 * @since 1.16.1 (J) - 1.6.1 (WP)
1067 * @requires VCM >= 1.8.12
1068 */
1069 public function supportsStateProvince()
1070 {
1071 return true;
1072 }
1073 }
1074