| @@ -30,14 +30,13 @@ | ||
| 30 | 30 | } |
| 31 | 31 | |
| 32 | 32 | $q = "SELECT `o`.*,(SELECT SUM(`or`.`adults`) FROM `#__vikbooking_ordersrooms` AS `or` WHERE `or`.`idorder`=`o`.`id`) AS `tot_adults` FROM `#__vikbooking_orders` AS `o` WHERE (`o`.`sid`=" . $dbo->quote($sid) . " OR `o`.`idorderota`=" . $dbo->quote($sid) . ") AND `o`.`ts`=" . $dbo->quote($ts); |
| 33 | 33 | $dbo->setQuery($q, 0, 1); |
| 34 | - $dbo->execute(); | |
| 35 | - if (!$dbo->getNumRows()) { | |
| 34 | + $order = $dbo->loadAssoc(); | |
| 35 | + if (!$order) { | |
| 36 | 36 | showSelectVb(JText::translate('VBORDERNOTFOUND')); |
| 37 | 37 | return; |
| 38 | 38 | } |
| 39 | - $order = $dbo->loadAssoc(); | |
| 40 | 39 | |
| 41 | 40 | // availability helper |
| 42 | 41 | $av_helper = VikBooking::getAvailabilityInstance(); |
| 43 | 42 | |
| @@ -59,11 +58,10 @@ | ||
| 59 | 58 | "LEFT JOIN `#__vikbooking_prices` `p` ON `p`.`id`=`d`.`idprice` " . |
| 60 | 59 | "LEFT JOIN `#__vikbooking_iva` `t` ON `t`.`id`=`p`.`idiva` " . |
| 61 | 60 | "WHERE `or`.`idorder`='".$order['id']."' ORDER BY `t`.`aliq` ASC"; |
| 62 | 61 | $dbo->setQuery($q, 0, 1); |
| 63 | - $dbo->execute(); | |
| 64 | - if ($dbo->getNumRows() == 1) { | |
| 65 | - $taxdata = $dbo->loadAssoc(); | |
| 62 | + $taxdata = $dbo->loadAssoc(); | |
| 63 | + if ($taxdata) { | |
| 66 | 64 | $taxes = 0; |
| 67 | 65 | if (!empty($taxdata['aliq'])) { |
| 68 | 66 | $realtotal = round(($order['total'] / ((100 + $taxdata['aliq']) / 100)), 2); |
| 69 | 67 | $taxes = round(($order['total'] - $realtotal), 2); |
| @@ -96,8 +94,24 @@ | ||
| 96 | 94 | |
| 97 | 95 | $dbo->setQuery($q); |
| 98 | 96 | $orderrooms = $dbo->loadAssocList(); |
| 99 | 97 | |
| 98 | + /** | |
| 99 | + * Even if redundant, we read the eventually occupied records by this booking ID to ensure | |
| 100 | + * server-side cache will not produce a booking cancellation after receiving a valid payment. | |
| 101 | + * An eventual list of busy record IDs is passed to the method for checking if the room is | |
| 102 | + * available, because the status of the reservation may actually be confirmed already. | |
| 103 | + * | |
| 104 | + * @since 1.16.9 (J) - 1.6.9 (WP) | |
| 105 | + */ | |
| 106 | + $dbo->setQuery( | |
| 107 | + $dbo->getQuery(true) | |
| 108 | + ->select($dbo->qn('idbusy')) | |
| 109 | + ->from($dbo->qn('#__vikbooking_ordersbusy')) | |
| 110 | + ->where($dbo->qn('idorder') . ' = ' . (int)$order['id']) | |
| 111 | + ); | |
| 112 | + $skip_busy_ids = array_column($dbo->loadAssocList(), 'idbusy'); | |
| 113 | + | |
| 100 | 114 | foreach ($orderrooms as $kor => $or) { |
| 101 | 115 | // determine proper values for this room |
| 102 | 116 | $room_stay_checkin = $order['checkin']; |
| 103 | 117 | $room_stay_checkout = $order['checkout']; |
| @@ -106,26 +120,53 @@ | ||
| 106 | 120 | $room_stay_checkout = !empty($room_stay_dates[$kor]['checkout_ts']) ? $room_stay_dates[$kor]['checkout_ts'] : $room_stay_dates[$kor]['checkout']; |
| 107 | 121 | } |
| 108 | 122 | |
| 109 | 123 | // make sure room is still available |
| 110 | - $roomavail = VikBooking::roomBookable($or['idroom'], $or['units'], $room_stay_checkin, $room_stay_checkout); | |
| 124 | + $roomavail = VikBooking::roomBookable($or['idroom'], $or['units'], $room_stay_checkin, $room_stay_checkout, $skip_busy_ids); | |
| 111 | 125 | if (!$roomavail) { |
| 112 | 126 | break; |
| 113 | 127 | } |
| 114 | 128 | } |
| 115 | 129 | |
| 130 | + // tell if the booking has been automatically cancelled | |
| 131 | + $autoremove = false; | |
| 132 | + | |
| 133 | + // make sure we are not beyond the check-in date and time | |
| 116 | 134 | $today_midnight = mktime(0, 0, 0, date('n'), date('j'), date('Y')); |
| 117 | - $autoremove = false; | |
| 118 | 135 | if ($today_midnight > $order['checkin']) { |
| 119 | 136 | $roomavail = false; |
| 120 | 137 | } |
| 121 | - $minautoremove = VikBooking::getMinutesAutoRemove(); | |
| 122 | - $mins_elapsed = floor((time() - $order['ts']) / 60); | |
| 123 | - if ($minautoremove > 0 && $mins_elapsed > $minautoremove) { | |
| 124 | - $roomavail = false; | |
| 125 | - $autoremove = true; | |
| 138 | + | |
| 139 | + /** | |
| 140 | + * Check if we are dealing with a pending booking solution of a quote. | |
| 141 | + * | |
| 142 | + * @since 1.18.8 (J) - 1.8.8 (WP) | |
| 143 | + */ | |
| 144 | + $quoteValidUntilDt = null; | |
| 145 | + if (!empty($order['idquote'])) { | |
| 146 | + // load quote record | |
| 147 | + $quoteRecord = VBOMvcModel::getInstance('quote')->getItem((int) $order['idquote']); | |
| 148 | + if (!empty($quoteRecord->valid_until)) { | |
| 149 | + $quoteValidUntilDt = JFactory::getDate($quoteRecord->valid_until); | |
| 150 | + } | |
| 126 | 151 | } |
| 127 | 152 | |
| 153 | + if ($quoteValidUntilDt) { | |
| 154 | + // make sure the quote is still valid | |
| 155 | + if (JFactory::getDate('now') > $quoteValidUntilDt) { | |
| 156 | + $roomavail = false; | |
| 157 | + $autoremove = true; | |
| 158 | + } | |
| 159 | + } else { | |
| 160 | + // check global settings for booking auto-cancellation | |
| 161 | + $minautoremove = VikBooking::getMinutesAutoRemove(); | |
| 162 | + $mins_elapsed = floor((time() - $order['ts']) / 60); | |
| 163 | + if ($minautoremove > 0 && $mins_elapsed > $minautoremove) { | |
| 164 | + $roomavail = false; | |
| 165 | + $autoremove = true; | |
| 166 | + } | |
| 167 | + } | |
| 168 | + | |
| 128 | 169 | /** |
| 129 | 170 | * Make sure not to cancel an OTA pending reservation. |
| 130 | 171 | * |
| 131 | 172 | * @since 1.15.4 (J) - 1.5.10 (WP) |
| @@ -148,11 +189,10 @@ | ||
| 148 | 189 | $dbo->setQuery($q); |
| 149 | 190 | $dbo->execute(); |
| 150 | 191 | $q = "SELECT * FROM `#__vikbooking_ordersbusy` WHERE `idorder`=".(int)$order['id'].";"; |
| 151 | 192 | $dbo->setQuery($q); |
| 152 | - $dbo->execute(); | |
| 153 | - if ($dbo->getNumRows() > 0) { | |
| 154 | - $ordbusy = $dbo->loadAssocList(); | |
| 193 | + $ordbusy = $dbo->loadAssocList(); | |
| 194 | + if ($ordbusy) { | |
| 155 | 195 | foreach ($ordbusy as $ob) { |
| 156 | 196 | $q = "DELETE FROM `#__vikbooking_busy` WHERE `id`=".(int)$ob['idbusy'].";"; |
| 157 | 197 | $dbo->setQuery($q); |
| 158 | 198 | $dbo->execute(); |
| @@ -188,9 +228,9 @@ | ||
| 188 | 228 | |
| 189 | 229 | // render the booking details |
| 190 | 230 | |
| 191 | 231 | // set noindex instruction for robots |
| 192 | - $document->setMetaData('robots', 'noindex,follow'); | |
| 232 | + $document->setMetaData('robots', 'noindex, nofollow'); | |
| 193 | 233 | |
| 194 | 234 | // load jQuery |
| 195 | 235 | if (VikBooking::loadJquery()) { |
| 196 | 236 | JHtml::fetch('jquery.framework', true, true); |
| @@ -220,14 +260,29 @@ | ||
| 220 | 260 | } |
| 221 | 261 | } |
| 222 | 262 | } |
| 223 | 263 | $is_package = !empty($order['pkg']) ? true : false; |
| 224 | - $orderrooms = []; | |
| 225 | - $q = "SELECT `or`.`idroom`,`or`.`adults`,`or`.`children`,`or`.`idtar`,`or`.`optionals`,`or`.`roomindex`,`or`.`pkg_id`,`or`.`pkg_name`,`or`.`cust_cost`,`or`.`cust_idiva`,`or`.`extracosts`,`or`.`room_cost`,`or`.`otarplan`,`r`.`id` AS `r_reference_id`,`r`.`name`,`r`.`img`,`r`.`idcarat`,`r`.`fromadult`,`r`.`toadult` FROM `#__vikbooking_ordersrooms` AS `or`,`#__vikbooking_rooms` AS `r` WHERE `or`.`idorder`='".$order['id']."' AND `or`.`idroom`=`r`.`id` ORDER BY `or`.`id` ASC;"; | |
| 226 | - $dbo->setQuery($q); | |
| 227 | - $dbo->execute(); | |
| 228 | - if ($dbo->getNumRows() > 0) { | |
| 229 | - $orderrooms = $dbo->loadAssocList(); | |
| 264 | + | |
| 265 | + // get the rooms booked list | |
| 266 | + $dbo->setQuery( | |
| 267 | + $dbo->getQuery(true) | |
| 268 | + ->select($dbo->qn('or') . '.*') | |
| 269 | + ->select($dbo->qn('r.id', 'r_reference_id')) | |
| 270 | + ->select([ | |
| 271 | + $dbo->qn('r.name'), | |
| 272 | + $dbo->qn('r.img'), | |
| 273 | + $dbo->qn('r.idcarat'), | |
| 274 | + $dbo->qn('r.fromadult'), | |
| 275 | + $dbo->qn('r.toadult'), | |
| 276 | + ]) | |
| 277 | + ->from($dbo->qn('#__vikbooking_ordersrooms', 'or')) | |
| 278 | + ->leftJoin($dbo->qn('#__vikbooking_rooms', 'r') . ' ON ' . $dbo->qn('or.idroom') . ' = ' . $dbo->qn('r.id')) | |
| 279 | + ->where($dbo->qn('or.idorder') . ' = ' . (int)$order['id']) | |
| 280 | + ->order($dbo->qn('or.id') . ' ASC') | |
| 281 | + ); | |
| 282 | + $orderrooms = $dbo->loadAssocList(); | |
| 283 | + | |
| 284 | + if ($orderrooms) { | |
| 230 | 285 | $vbo_tn->translateContents($orderrooms, '#__vikbooking_rooms', array('id' => 'r_reference_id')); |
| 231 | 286 | foreach ($orderrooms as $kor => $or) { |
| 232 | 287 | $num = $kor + 1; |
| 233 | 288 | if ($is_package === true || (!empty($or['cust_cost']) && $or['cust_cost'] > 0.00)) { |
| @@ -235,13 +290,12 @@ | ||
| 235 | 290 | continue; |
| 236 | 291 | } |
| 237 | 292 | $q = "SELECT `t`.*,`p`.`name`,`p`.`free_cancellation`,`p`.`canc_deadline`,`p`.`canc_policy` FROM `#__vikbooking_dispcost` AS `t` LEFT JOIN `#__vikbooking_prices` AS `p` ON `t`.`idprice`=`p`.`id` WHERE `t`.`id`=" . (int)$or['idtar'] . ";"; |
| 238 | 293 | $dbo->setQuery($q); |
| 239 | - $dbo->execute(); | |
| 240 | - if (!$dbo->getNumRows()) { | |
| 294 | + $tar = $dbo->loadAssocList(); | |
| 295 | + if (!$tar) { | |
| 241 | 296 | continue; |
| 242 | 297 | } |
| 243 | - $tar = $dbo->loadAssocList(); | |
| 244 | 298 | $vbo_tn->translateContents($tar, '#__vikbooking_prices', array('id' => 'idprice')); |
| 245 | 299 | |
| 246 | 300 | // determine proper values for this room |
| 247 | 301 | $room_stay_checkin = $order['checkin']; |
| @@ -313,8 +367,30 @@ | ||
| 313 | 367 | $cnodep = $cookie->get('vboFA', '', 'string'); |
| 314 | 368 | $nodep = intval($pnodep) > 0 || intval($cnodep) > 0 ? 1 : 0; |
| 315 | 369 | |
| 316 | 370 | /** |
| 371 | + * Scan the booking and related rooms for damage deposit payment data. | |
| 372 | + * Obtain also the information about possible previous payments. | |
| 373 | + * | |
| 374 | + * @since 1.17.6 (J) - 1.7.6 (WP) | |
| 375 | + */ | |
| 376 | + $damage_deposit_payment = VBORoomHelper::getInstance()->getDamageDepositSplitPayment($order, $orderrooms); | |
| 377 | + $prev_dd_payments = []; | |
| 378 | + if ($damage_deposit_payment['damagedep_gross'] ?? 0) { | |
| 379 | + $prev_dd_payments = VikBooking::getBookingHistoryInstance($order['id']) | |
| 380 | + ->getEventsWithData('PN', function($data) { | |
| 381 | + return (is_object($data) && !empty($data->damage_deposit)); | |
| 382 | + }); | |
| 383 | + if (!$prev_dd_payments) { | |
| 384 | + // check also the first payment event in case of OTA bookings | |
| 385 | + $prev_dd_payments = VikBooking::getBookingHistoryInstance($order['id']) | |
| 386 | + ->getEventsWithData('P0', function($data) { | |
| 387 | + return (is_object($data) && !empty($data->damage_deposit)); | |
| 388 | + }); | |
| 389 | + } | |
| 390 | + } | |
| 391 | + | |
| 392 | + /** | |
| 317 | 393 | * Upselling extra services only for non-cancelled bookings |
| 318 | 394 | * with a check-out date in the future. |
| 319 | 395 | * |
| 320 | 396 | * @since 1.13.0 (J) - 1.3.0 (WP) |
| @@ -342,17 +418,9 @@ | ||
| 342 | 418 | } |
| 343 | 419 | } |
| 344 | 420 | array_push($upsell_data, $room_data); |
| 345 | 421 | } |
| 346 | - $upselling = VikBooking::loadUpsellingData( | |
| 347 | - $upsell_data, | |
| 348 | - array( | |
| 349 | - 'id' => $order['id'], | |
| 350 | - 'checkin' => $order['checkin'], | |
| 351 | - 'checkout' => $order['checkout'], | |
| 352 | - ), | |
| 353 | - $vbo_tn | |
| 354 | - ); | |
| 422 | + $upselling = VikBooking::loadUpsellingData($upsell_data, $order, $vbo_tn); | |
| 355 | 423 | } |
| 356 | 424 | |
| 357 | 425 | /** |
| 358 | 426 | * Trigger first tracking conversion on booking. |
| @@ -370,8 +438,11 @@ | ||
| 370 | 438 | $this->canc_policy = $canc_policy; |
| 371 | 439 | $this->payment = $payment; |
| 372 | 440 | $this->nodep = $nodep; |
| 373 | 441 | $this->upselling = $upselling; |
| 442 | + $this->damage_deposit_payment = $damage_deposit_payment; | |
| 443 | + $this->prev_dd_payments = $prev_dd_payments; | |
| 444 | + $this->quoteValidUntilDt = $quoteValidUntilDt ?? null; | |
| 374 | 445 | $this->vbo_tn = $vbo_tn; |
| 375 | 446 | |
| 376 | 447 | // theme |
| 377 | 448 | $theme = VikBooking::getTheme(); |