PluginProbe
VikBooking Hotel Booking Engine & PMS / 1.6.5
VikBooking Hotel Booking Engine & PMS v1.6.5
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.5, at site/helpers/cpin.php

1,028 lines 31.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 /**
502 * Rather than statically parsing the fieldflags, we build them automatically.
503 *
504 * @since 1.11
505 */
506 $fieldkeys = [];
507 $fieldvals = [];
508 $updfields = [];
509 foreach ($this->fieldflags as $flagk => $flagv) {
510 array_push($fieldkeys, "`{$flagk}`");
511 array_push($fieldvals, "{$flagv}");
512 array_push($updfields, "`{$flagk}`=".$this->dbo->quote($flagv));
513 }
514 if (count($fieldvals)) {
515 $fieldvals = array_map(array($this->dbo, 'quote'), $fieldvals);
516 }
517 //
518
519 $customer = $this->customerExists($email, $first_name, $last_name);
520 if ($customer === false) {
521 $new_pin = $this->generateUniquePin();
522 $user = JFactory::getUser();
523 $q = "INSERT INTO `#__vikbooking_customers` (`first_name`,`last_name`,`email`,`phone`,`country`,`cfields`,`pin`,`ujid`".(count($fieldkeys) ? ', '.implode(', ', $fieldkeys) : '').") VALUES(".$this->dbo->quote($first_name).", ".$this->dbo->quote($last_name).", ".$this->dbo->quote($email).", ".$this->dbo->quote($phone_number).", ".$this->dbo->quote($country).", ".(is_array($cfields) && count($cfields) ? $this->dbo->quote(json_encode($cfields)) : "NULL").", ".$this->dbo->quote($new_pin).", ".($this->is_admin ? '0' : intval($user->id)).(count($fieldvals) ? ', '.implode(', ', $fieldvals) : '').");";
524 $this->dbo->setQuery($q);
525 $this->dbo->execute();
526 $new_customer_id = $this->dbo->insertid();
527 if (!empty($new_customer_id)) {
528 $this->setNewPin($new_pin);
529 $this->setNewCustomerId($new_customer_id);
530 $this->pluginCustomerSync($new_customer_id, 'insert');
531 }
532 } elseif (is_array($customer)) {
533 $this->setNewPin($customer['pin']);
534 $this->setNewCustomerId($customer['id']);
535 $q = "UPDATE `#__vikbooking_customers` SET `first_name`=".$this->dbo->quote($first_name).",`last_name`=".$this->dbo->quote($last_name).",`email`=".$this->dbo->quote($email).",`phone`=".$this->dbo->quote($phone_number).",`country`=".$this->dbo->quote($country).(!$this->is_admin ? ",`cfields`=".(is_array($cfields) && count($cfields) ? $this->dbo->quote(json_encode($cfields)) : "NULL") : "").(count($updfields) ? ', '.implode(', ', $updfields) : '')." WHERE `id`=".$customer['id'].";";
536 $this->dbo->setQuery($q);
537 $this->dbo->execute();
538 $this->pluginCustomerSync($customer['id'], 'update');
539 }
540 //unset extra info
541 $this->fieldflags = [];
542 //
543 return !$this->is_admin ? $this->storeCustomerCookie() : true;
544 }
545
546 public function storeCustomerCookie()
547 {
548 $pin = $this->getNewPin();
549 $customer_id = $this->getNewCustomerId();
550 if (empty($pin) || empty($customer_id)) {
551 return false;
552 }
553 $this->setPinCookie($pin);
554 return true;
555 }
556
557 /**
558 * Stores a relation between the Customer ID and the Booking ID
559 * This method should be called after the saveCustomerDetails() because
560 * it requires the methods setNewPin and setNewCustomerId to be called before.
561 * Since VBO 1.9 this method also calculates and sets the commissions
562 * amount if the customer is a sales channel.
563 * Requires the records in _ordersrooms to be stored before being called.
564 *
565 * @param int orderid the ID of the VBO order
566 *
567 * @return boolean
568 */
569 public function saveCustomerBooking($orderid)
570 {
571 $pin = $this->getNewPin();
572 $customer_id = $this->getNewCustomerId();
573 if (empty($orderid) || empty($pin) || empty($customer_id)) {
574 return false;
575 }
576
577 $q = "SELECT * FROM `#__vikbooking_ordersrooms` WHERE `idorder`=".(int)$orderid.";";
578 $this->dbo->setQuery($q);
579 $orders_rooms = $this->dbo->loadAssocList();
580 if (!$orders_rooms) {
581 return false;
582 }
583
584 $q = "DELETE FROM `#__vikbooking_customers_orders` WHERE `idorder`=".$this->dbo->quote($orderid).";";
585 $this->dbo->setQuery($q);
586 $this->dbo->execute();
587
588 $q = "INSERT INTO `#__vikbooking_customers_orders` (`idcustomer`,`idorder`) VALUES(".$this->dbo->quote($customer_id).", ".$this->dbo->quote($orderid).");";
589 $this->dbo->setQuery($q);
590 $this->dbo->execute();
591
592 // 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
593 if (empty($orders_rooms[0]['t_first_name']) && empty($orders_rooms[0]['t_last_name'])) {
594 $customer_info = $this->getCustomerByID($customer_id);
595 $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;";
596 $this->dbo->setQuery($q);
597 $this->dbo->execute();
598
599 // update the country as well
600 if (!empty($customer_info['country'])) {
601 $q = "UPDATE `#__vikbooking_orders` SET `country`=".$this->dbo->quote($customer_info['country'])." WHERE `id`=".(int)$orderid.";";
602 $this->dbo->setQuery($q);
603 $this->dbo->execute();
604 }
605 }
606
607 // commissions for customers that are sales channels
608 $this->updateBookingCommissions($orderid, $customer_id);
609
610 return true;
611 }
612
613 /**
614 * Changes the customer assigned to the booking by
615 * re-calculating the amount of commissions (if any).
616 *
617 * @param int $orderid the booking id.
618 * @param int $customer_id the id of the new customer.
619 *
620 * @return boolean
621 *
622 * @since 1.11
623 */
624 public function updateCustomerBooking($orderid, $customer_id)
625 {
626 if (empty($orderid) || empty($customer_id)) {
627 return false;
628 }
629 $new_customer = $this->getCustomerByID($customer_id);
630 if (!count($new_customer)) {
631 // invalid customer ID given
632 return false;
633 }
634 $old_customer = $this->getCustomerFromBooking($orderid);
635 if (count($old_customer) && (int)$old_customer['ischannel'] > 0) {
636 // unset first the old commissions and channel name
637 $q = "UPDATE `#__vikbooking_orders` SET `channel`=NULL, `cmms`=NULL WHERE `id`=".(int)$orderid.";";
638 $this->dbo->setQuery($q);
639 $this->dbo->execute();
640 }
641 if (count($old_customer)) {
642 // update reference
643 $q = "UPDATE `#__vikbooking_customers_orders` SET `idcustomer`=".(int)$new_customer['id']." WHERE `idorder`=".(int)$orderid.";";
644 $this->dbo->setQuery($q);
645 $this->dbo->execute();
646 if (!empty($new_customer['country']) && strlen($new_customer['country']) == 3) {
647 // update booking country
648 $q = "UPDATE `#__vikbooking_orders` SET `country`=" . $this->dbo->quote($new_customer['country']) . " WHERE `id`=" . (int)$orderid . ";";
649 $this->dbo->setQuery($q);
650 $this->dbo->execute();
651 }
652 } else {
653 // insert relation
654 $q = "INSERT INTO `#__vikbooking_customers_orders` (`idcustomer`,`idorder`) VALUES(".(int)$new_customer['id'].", ".(int)$orderid.");";
655 $this->dbo->setQuery($q);
656 $this->dbo->execute();
657 if (!empty($new_customer['country']) && strlen($new_customer['country']) == 3) {
658 // update booking country
659 $q = "UPDATE `#__vikbooking_orders` SET `country`=" . $this->dbo->quote($new_customer['country']) . " WHERE `id`=" . (int)$orderid . ";";
660 $this->dbo->setQuery($q);
661 $this->dbo->execute();
662 }
663 }
664
665 // update commissions and sale channel information
666 return $this->updateBookingCommissions($orderid, $new_customer['id']);
667 }
668
669 /**
670 * Calculates and sets the commissions amount if the customer
671 * is a "sales channel" with a percentage greater than 0.
672 *
673 * @param int $orderid
674 * @param int $customer_id
675 *
676 * @since 1.9
677 */
678 public function updateBookingCommissions($orderid, $customer_id = 0)
679 {
680 if (empty($customer_id)) {
681 $customer_id = $this->getNewCustomerId();
682 }
683
684 if (empty($orderid) || empty($customer_id)) {
685 return false;
686 }
687
688 $customer = $this->getCustomerByID($customer_id);
689 if ((int)$customer['ischannel'] > 0 && !empty($customer['chdata']) && isset($customer['chdata']['commission']) && $customer['chdata']['commission'] > 0.00) {
690 $q = "SELECT * FROM `#__vikbooking_orders` WHERE `id`=".$this->dbo->quote($orderid).";";
691 $this->dbo->setQuery($q);
692 $order_info = $this->dbo->loadAssoc();
693 if ($order_info) {
694 if ((float)$order_info['total'] > 0.00) {
695 $cmms_calc_base = $this->calcCommissionsBaseAmount($order_info, $customer);
696 $cmms_amount = $cmms_calc_base * $customer['chdata']['commission'] / 100;
697 $source_name = 'customer'.$customer['id'].(array_key_exists('chname', $customer['chdata']) ? '_'.$customer['chdata']['chname'] : '');
698
699 $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']).";";
700 $this->dbo->setQuery($q);
701 $this->dbo->execute();
702
703 return true;
704 }
705 }
706 }
707
708 return false;
709 }
710
711 /**
712 * Calculates the base amount on which the commissions should be applied.
713 * Considers the parameters for this customer sales channel for taxes and rooms rates.
714 *
715 * @param array $order_info
716 * @param array $customer
717 */
718 private function calcCommissionsBaseAmount($order_info, $customer)
719 {
720 $cmms_calc_base = $order_info['total'];
721
722 if (!empty($customer['chdata']) && array_key_exists('calccmmon', $customer['chdata']) && (int)$customer['chdata']['calccmmon'] > 0) {
723 // commissions based on room rates only
724 $q = "SELECT `or`.* FROM `#__vikbooking_ordersrooms` AS `or` WHERE `or`.`idorder`='" . (int)$order_info['id'] . "';";
725 $this->dbo->setQuery($q);
726 $order_rooms = $this->dbo->loadAssocList();
727 if ($order_rooms) {
728 $map_cost_tax = [];
729 foreach ($order_rooms as $or) {
730 if (!((float)$or['room_cost'] > 0.00) && !((float)$or['cust_cost'] > 0.00)) {
731 // missing information about the room cost - cannot proceed
732 continue;
733 }
734 $map_cost_tax[] = array(
735 'amount' => ((float)$or['room_cost'] > 0.00 ? $or['room_cost'] : $or['cust_cost']),
736 'taxid' => (!empty($or['cust_cost']) ? intval($or['cust_idiva']) : $this->getTaxIdFromTar($or['idtar']))
737 );
738 }
739 if (count($map_cost_tax) == count($order_rooms)) {
740 // all rooms have a custom cost or a room cost set. We can proceed
741 if (array_key_exists('applycmmon', $customer['chdata']) && (int)$customer['chdata']['applycmmon'] > 0) {
742 // commissions based on amounts tax excluded
743 if ($this->pricesTaxIncluded()) {
744 // prices are tax included so update the amounts in $map_cost_tax
745 foreach ($map_cost_tax as $ctk => $ctv) {
746 if (!((float)$ctv['amount'] > 0.00) || empty($ctv['taxid'])) {
747 continue;
748 }
749 list($aliq, $taxcap) = $this->getAliqFromTaxId($ctv['taxid']);
750 if ((float)$aliq > 0.00) {
751 $op_div = (100 + $aliq) / 100;
752 $tmp_op = $ctv['amount'] / $op_div;
753 /**
754 * Tax Cap implementation
755 *
756 * @since 1.12
757 */
758 if ($taxcap > 0 && ($ctv['amount'] - $tmp_op) > $taxcap) {
759 $tmp_op = $ctv['amount'] - $taxcap;
760 }
761 $map_cost_tax[$ctk]['amount'] = $tmp_op;
762 }
763 }
764 }
765 }
766 // sum all the amounts to get the base amount where commissions will be applied
767 $sum = 0;
768 foreach ($map_cost_tax as $k => $map) {
769 $sum += $map['amount'];
770 }
771 $cmms_calc_base = $sum;
772 }
773 }
774 }
775
776 return $cmms_calc_base;
777 }
778
779 private function pricesTaxIncluded()
780 {
781 if (!class_exists('VikBooking')) {
782 require_once(VBO_SITE_PATH.DIRECTORY_SEPARATOR.'helpers'.DIRECTORY_SEPARATOR.'lib.vikbooking.php');
783 }
784 return VikBooking::ivaInclusa();
785 }
786
787 /**
788 * Retrieves the aliquot and tax cap of the tax ID passed.
789 * Returns 0 if nothing is found, the Tax Aliq otherwise.
790 *
791 * @param int $taxid the ID of the tax rate
792 *
793 * @return array the aliquote as 0th array value, the tax cap as 1st value.
794 *
795 * @since 1.12 this private method used to return just the aliquote.
796 */
797 private function getAliqFromTaxId($taxid)
798 {
799 $aliq = 0;
800 $taxcap = 0;
801 if (intval($taxid) > 0) {
802 $q = "SELECT `i`.`aliq`,`i`.`taxcap` FROM `#__vikbooking_iva` AS `i` WHERE `i`.`id`=" . (int)$taxid;
803 $this->dbo->setQuery($q, 0, 1);
804 $tax_info = $this->dbo->loadAssoc();
805 if ($tax_info) {
806 $aliq = $tax_info['aliq'];
807 $taxcap = $tax_info['taxcap'];
808 }
809 }
810
811 return [$aliq, $taxcap];
812 }
813
814 /**
815 * Retrieves the ID of the tax used for the tariff passed.
816 * Returns 0 if nothing is found, the Tax ID otherwise.
817 *
818 * @param int $idtar
819 */
820 private function getTaxIdFromTar($idtar)
821 {
822 $taxid = 0;
823 if (intval($idtar) > 0) {
824 $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;
825 $this->dbo->setQuery($q, 0, 1);
826 $tax_info = $this->dbo->loadAssoc();
827 if ($tax_info) {
828 $taxid = (int)$tax_info['idiva'];
829 }
830 }
831
832 return $taxid;
833 }
834
835 /**
836 * Takes the Customer PIN from the Order ID
837 *
838 * @param int $orderid
839 */
840 public function getPinCodeByOrderId($orderid)
841 {
842 $pin = '';
843 if (!empty($orderid)) {
844 $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;";
845 $this->dbo->setQuery($q);
846 $custdata = $this->dbo->loadAssocList();
847 if ($custdata) {
848 if (!empty($custdata[0]['pin'])) {
849 $pin = $custdata[0]['pin'];
850 }
851 }
852 }
853
854 return $pin;
855 }
856
857 /**
858 * Invokes the VikCustomerSync Plugin.
859 * Requires: ID of the customer and mode (insert/update/delete)
860 *
861 * @param int $customer_id
862 * @param string $mode
863 */
864 public function pluginCustomerSync($customer_id, $mode)
865 {
866 $app = JFactory::getApplication();
867
868 $q = "SELECT * FROM `#__vikbooking_customers` WHERE `id`=" . (int)$customer_id;
869 $this->dbo->setQuery($q, 0, 1);
870 $customer = $this->dbo->loadAssoc();
871 if (!$customer) {
872 return false;
873 }
874
875 // make sure to import this type of plugins
876 JPluginHelper::importPlugin('e4j');
877
878 // get the event name
879 if ($mode == 'insert') {
880 // trigger plugin -> customer creation
881 $ev_name = 'onCustomerInsert';
882 } elseif ($mode == 'update') {
883 // trigger plugin -> customer update
884 $ev_name = 'onCustomerUpdate';
885 } elseif ($mode == 'delete') {
886 // trigger plugin -> customer delete
887 $ev_name = 'onCustomerDelete';
888 } else {
889 return false;
890 }
891
892
893 // event options
894 $options = array(
895 'alias' => 'com_vikbooking',
896 'version' => (defined('VIKBOOKING_SOFTWARE_VERSION') ? VIKBOOKING_SOFTWARE_VERSION : E4J_SOFTWARE_VERSION),
897 'admin' => VikBooking::isAdmin(),
898 'call' => __FUNCTION__
899 );
900
901 try {
902 /**
903 * Trigger event for the creation, update or deletion of the customer.
904 */
905 VBOFactory::getPlatform()->getDispatcher()->trigger($ev_name, [&$customer, &$options]);
906 } catch (Exception $e) {
907 // do nothing
908 }
909
910 return true;
911 }
912
913 /**
914 * Sets the current customer PIN
915 *
916 * @param string $pin
917 */
918 public function setNewPin($pin = '')
919 {
920 $this->new_pin = $pin;
921 }
922
923 /**
924 * Get the current customer PIN
925 */
926 public function getNewPin()
927 {
928 return $this->new_pin;
929 }
930
931 /**
932 * Sets the current customer ID
933 *
934 * @param int $cid
935 */
936 public function setNewCustomerId($cid = 0)
937 {
938 $this->new_customer_id = (int)$cid;
939 }
940
941 /**
942 * Get the current customer ID
943 */
944 public function getNewCustomerId()
945 {
946 return $this->new_customer_id;
947 }
948
949 /**
950 * Explanation of the XML error
951 *
952 * @param object $error
953 */
954 public function libxml_display_error($error)
955 {
956 $return = "\n";
957 switch ($error->level) {
958 case LIBXML_ERR_WARNING :
959 $return .= "Warning ".$error->code.": ";
960 break;
961 case LIBXML_ERR_ERROR :
962 $return .= "Error ".$error->code.": ";
963 break;
964 case LIBXML_ERR_FATAL :
965 $return .= "Fatal Error ".$error->code.": ";
966 break;
967 }
968 $return .= trim($error->message);
969 if ($error->file) {
970 $return .= " in ".$error->file;
971 }
972 $return .= " on line ".$error->line."\n";
973 return $return;
974 }
975
976 /**
977 * Get the XML errors occurred
978 */
979 public function libxml_display_errors()
980 {
981 $errorstr = "";
982 $errors = libxml_get_errors();
983 foreach ($errors as $error) {
984 $errorstr .= $this->libxml_display_error($error);
985 }
986 libxml_clear_errors();
987 return $errorstr;
988 }
989
990 private function setError($str)
991 {
992 $this->error .= $str."\n";
993 }
994
995 public function getError()
996 {
997 return nl2br(rtrim($this->error, "\n"));
998 }
999
1000 /**
1001 * Tells whether Vik Booking is up to date to support the customer picture.
1002 * May be used by VCM to detect if this feature is available in Vik Booking.
1003 *
1004 * @return bool
1005 *
1006 * @since 1.15.3 (J) - 1.5.5 (WP)
1007 * @requires VCM >= 1.8.6
1008 */
1009 public function supportsProfileAvatar()
1010 {
1011 return true;
1012 }
1013
1014 /**
1015 * Tells whether Vik Booking is up to date to support the state/province.
1016 * May be used by VCM to detect if this feature is available in Vik Booking.
1017 *
1018 * @return bool
1019 *
1020 * @since 1.16.1 (J) - 1.6.1 (WP)
1021 * @requires VCM >= 1.8.12
1022 */
1023 public function supportsStateProvince()
1024 {
1025 return true;
1026 }
1027 }
1028