view.html.php
428 lines
| 1 | <?php |
| 2 | /** |
| 3 | * @package VikAppointments |
| 4 | * @subpackage core |
| 5 | * @author E4J s.r.l. |
| 6 | * @copyright Copyright (C) 2021 E4J s.r.l. All Rights Reserved. |
| 7 | * @license http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL |
| 8 | * @link https://vikwp.com |
| 9 | */ |
| 10 | |
| 11 | // No direct access |
| 12 | defined('ABSPATH') or die('No script kiddies please!'); |
| 13 | |
| 14 | /** |
| 15 | * VikAppointments reservation (order) summary view. |
| 16 | * In case the request doesn't provide the ORDER NUMBER |
| 17 | * and the ORDER KEY, a form to search a reservation |
| 18 | * will be displayed. |
| 19 | * |
| 20 | * @since 1.0 |
| 21 | */ |
| 22 | class VikAppointmentsVieworder extends JViewVAP |
| 23 | { |
| 24 | /** |
| 25 | * VikAppointments view display method. |
| 26 | * |
| 27 | * @return void |
| 28 | */ |
| 29 | function display($tpl = null) |
| 30 | { |
| 31 | $app = JFactory::getApplication(); |
| 32 | $input = $app->input; |
| 33 | $config = VAPFactory::getConfig(); |
| 34 | |
| 35 | $oid = $input->get('ordnum', 0, 'uint'); |
| 36 | $sid = $input->get('ordkey', '', 'alnum'); |
| 37 | |
| 38 | $this->itemid = $input->getInt('Itemid', 0); |
| 39 | |
| 40 | $order = null; |
| 41 | |
| 42 | if (!empty($oid) && !empty($sid)) |
| 43 | { |
| 44 | // check whether the appointment has expired |
| 45 | JModelVAP::getInstance('reservation')->checkExpired(array('id' => $oid)); |
| 46 | |
| 47 | try |
| 48 | { |
| 49 | // get order details (filter by ID and SID) |
| 50 | VAPLoader::import('libraries.order.factory'); |
| 51 | $order = VAPOrderFactory::getAppointments($oid, JFactory::getLanguage()->getTag(), array('sid' => $sid)); |
| 52 | } |
| 53 | catch (Exception $e) |
| 54 | { |
| 55 | // reservation not found |
| 56 | } |
| 57 | |
| 58 | if ($order) |
| 59 | { |
| 60 | // check if a payment is required |
| 61 | if ($order->payment) |
| 62 | { |
| 63 | // reload payment details to access the parameters |
| 64 | $payment = JModelVAP::getInstance('payment')->getItem($order->payment->id); |
| 65 | |
| 66 | // apply payment translations |
| 67 | $payment->name = $order->payment->name; |
| 68 | $payment->prenote = $order->payment->notes->beforePurchase; |
| 69 | $payment->note = $order->payment->notes->afterPurchase; |
| 70 | |
| 71 | $paymentData = array(); |
| 72 | |
| 73 | $vik = VAPApplication::getInstance(); |
| 74 | |
| 75 | /** |
| 76 | * The payment URLs are correctly routed for external usage. |
| 77 | * |
| 78 | * @since 1.6 |
| 79 | */ |
| 80 | $return_url = $vik->routeForExternalUse("index.php?option=com_vikappointments&view=order&ordnum={$oid}&ordkey={$sid}", false); |
| 81 | $error_url = $vik->routeForExternalUse("index.php?option=com_vikappointments&view=order&ordnum={$oid}&ordkey={$sid}", false); |
| 82 | |
| 83 | /** |
| 84 | * Include the Notification URL in both the PLAIN and ROUTED formats. |
| 85 | * |
| 86 | * @since 1.7 |
| 87 | */ |
| 88 | $notify_url = "index.php?option=com_vikappointments&task=order.notifypayment&ordnum={$oid}&ordkey={$sid}"; |
| 89 | |
| 90 | if ($order->statusRole == 'PENDING') |
| 91 | { |
| 92 | // check if the customer should pay the full amount (only for PENDING orders) |
| 93 | $this->shouldPayFullAmount($order); |
| 94 | } |
| 95 | |
| 96 | /** |
| 97 | * Calculate the total amount to pay. |
| 98 | * |
| 99 | * @since 1.7.8 |
| 100 | */ |
| 101 | $total_to_pay = VikAppointments::getTotalBeforePayment($order); |
| 102 | |
| 103 | $paymentData['type'] = 'appointments'; |
| 104 | $paymentData['action'] = 'create'; |
| 105 | $paymentData['oid'] = $order->id; |
| 106 | $paymentData['sid'] = $order->sid; |
| 107 | $paymentData['attempt'] = $order->payment_attempt; |
| 108 | $paymentData['transaction_name'] = JText::sprintf('VAPTRANSACTIONNAME', $config->get('agencyname')); |
| 109 | $paymentData['transaction_currency'] = $config->get('currencyname'); |
| 110 | $paymentData['currency_symb'] = $config->get('currencysymb'); |
| 111 | $paymentData['tax'] = 0; |
| 112 | $paymentData['return_url'] = $return_url; |
| 113 | $paymentData['error_url'] = $error_url; |
| 114 | $paymentData['notify_url'] = $vik->routeForExternalUse($notify_url, false); |
| 115 | $paymentData['notify_url_plain'] = JUri::root() . $notify_url; |
| 116 | $paymentData['total_to_pay'] = $total_to_pay; |
| 117 | $paymentData['total_net_price'] = $total_to_pay; |
| 118 | $paymentData['total_tax'] = 0; |
| 119 | $paymentData['leavedeposit'] = false; // @deprecated |
| 120 | $paymentData['payfull'] = true; // @deprecated |
| 121 | $paymentData['payment_info'] = $payment; |
| 122 | $paymentData['details'] = array( |
| 123 | 'purchaser_nominative' => $order->purchaser_nominative, |
| 124 | 'purchaser_mail' => $order->purchaser_mail, |
| 125 | 'purchaser_phone' => $order->purchaser_phone, |
| 126 | ); |
| 127 | |
| 128 | /** |
| 129 | * Added support for customer billing details. |
| 130 | * |
| 131 | * @since 1.7 |
| 132 | */ |
| 133 | $paymentData['billing'] = $order->billing; |
| 134 | |
| 135 | /** |
| 136 | * Trigger event to manipulate the payment details. |
| 137 | * |
| 138 | * @param array &$order The transaction details. |
| 139 | * @param mixed &$params The payment configuration as array or JSON. |
| 140 | * |
| 141 | * @return void |
| 142 | * |
| 143 | * @since 1.6 |
| 144 | */ |
| 145 | VAPFactory::getEventDispatcher()->trigger('onInitPaymentTransaction', array(&$paymentData, &$paymentData['payment_info']->params)); |
| 146 | |
| 147 | // register the payment details |
| 148 | $this->payment = $paymentData; |
| 149 | } |
| 150 | |
| 151 | // register all the locations |
| 152 | $this->locations = array(); |
| 153 | |
| 154 | foreach ($order->appointments as $appointment) |
| 155 | { |
| 156 | if ($appointment->location) |
| 157 | { |
| 158 | // create location marker |
| 159 | $marker = new stdClass; |
| 160 | $marker->lat = $appointment->location->latitude; |
| 161 | $marker->lng = $appointment->location->longitude; |
| 162 | |
| 163 | // check whether the same marker was already in the list |
| 164 | for ($i = 0, $found = false; $i < count($this->locations) && !$found; $i++) |
| 165 | { |
| 166 | $found = ( |
| 167 | $this->locations[$i]->lat === $marker->lat |
| 168 | && $this->locations[$i]->lng === $marker->lng |
| 169 | ); |
| 170 | } |
| 171 | |
| 172 | /** |
| 173 | * Exclude the locations that have not specified the coordinates, |
| 174 | * since it wouldn't be possible to display them through Google Maps. |
| 175 | * |
| 176 | * @since 1.7.1 |
| 177 | */ |
| 178 | if (!$found && $marker->lat !== null && $marker->lng !== null) |
| 179 | { |
| 180 | // create marker label built as [SERVICE], [EMPLOYEE] (optional) <br /> [CHECK-IN] |
| 181 | $marker->label = $appointment->service->name; |
| 182 | |
| 183 | if ($appointment->viewEmp) |
| 184 | { |
| 185 | $marker->label .= ', ' . $appointment->employee->name; |
| 186 | } |
| 187 | |
| 188 | $marker->label .= '<br />' . $appointment->customerCheckin->lc2; |
| 189 | |
| 190 | // register full location address |
| 191 | $marker->address = $appointment->location->text; |
| 192 | |
| 193 | // marker not found, add it in the list |
| 194 | $this->locations[] = $marker; |
| 195 | } |
| 196 | } |
| 197 | } |
| 198 | |
| 199 | // sort order appointments by service check-in |
| 200 | $this->services = $this->sortOrdersByServiceDate($order); |
| 201 | } |
| 202 | else |
| 203 | { |
| 204 | // raise error, reservation not found |
| 205 | $app->enqueueMessage(JText::translate('VAPORDERRESERVATIONERROR'), 'error'); |
| 206 | } |
| 207 | } |
| 208 | |
| 209 | if (!$order) |
| 210 | { |
| 211 | // use "track" layout in case the order |
| 212 | // was not found or in case the order number |
| 213 | // and the order key was not submitted |
| 214 | $this->setLayout('track'); |
| 215 | } |
| 216 | else |
| 217 | { |
| 218 | // print conversion code if needed |
| 219 | VAPLoader::import('libraries.models.conversion'); |
| 220 | VAPConversion::getInstance(array('page' => 'order'))->trackCode($order); |
| 221 | |
| 222 | // register order details on success |
| 223 | $this->order = $order; |
| 224 | |
| 225 | // count the number of confirmed appointments |
| 226 | $this->canUserCancelAll = true; |
| 227 | |
| 228 | $services_id = []; |
| 229 | |
| 230 | foreach ($order->appointments as $appointment) |
| 231 | { |
| 232 | // check whether the user can cancel this appointment |
| 233 | $appointment->canUserCancel = VikAppointments::canUserCancelOrder($appointment); |
| 234 | // update global flag |
| 235 | $this->canUserCancelAll = $this->canUserCancelAll && $appointment->canUserCancel; |
| 236 | |
| 237 | $services_id[] = $appointment->service->id; |
| 238 | } |
| 239 | |
| 240 | /** |
| 241 | * Fetch the custom fields that the user can update, if any. |
| 242 | * |
| 243 | * @since 1.7.7 |
| 244 | */ |
| 245 | $this->editableFields = VikAppointments::getEditableOrderFields($order)->fetch(); |
| 246 | } |
| 247 | |
| 248 | // prepare page content |
| 249 | VikAppointments::prepareContent($this); |
| 250 | |
| 251 | // extend pathway for breadcrumbs module |
| 252 | $this->extendPathway($app); |
| 253 | |
| 254 | // Display the template |
| 255 | parent::display($tpl); |
| 256 | } |
| 257 | |
| 258 | /** |
| 259 | * Sort the appointments by service ID and then by check-in date. |
| 260 | * |
| 261 | * @param VAPOrderAppointment $order The appointment details object. |
| 262 | * |
| 263 | * @return array An array containing the grouped services. |
| 264 | * |
| 265 | * @since 1.4 |
| 266 | */ |
| 267 | protected function sortOrdersByServiceDate($order) |
| 268 | { |
| 269 | $list = $order->appointments; |
| 270 | |
| 271 | usort($list, function($a, $b) |
| 272 | { |
| 273 | // get ordering factor based on service ID |
| 274 | $factor = $a->service->id - $b->service->id; |
| 275 | |
| 276 | if ($factor == 0) |
| 277 | { |
| 278 | // same service ID, get ordering factor based on check-in |
| 279 | $factor = $a->checkin->timestamp - $b->checkin->timestamp; |
| 280 | } |
| 281 | |
| 282 | return $factor; |
| 283 | }); |
| 284 | |
| 285 | $map = array(); |
| 286 | |
| 287 | // group appointments by service |
| 288 | foreach ($list as $app) |
| 289 | { |
| 290 | if (!isset($map[$app->service->id])) |
| 291 | { |
| 292 | $map[$app->service->id] = array(); |
| 293 | } |
| 294 | |
| 295 | $map[$app->service->id][] = $app; |
| 296 | } |
| 297 | |
| 298 | return $map; |
| 299 | } |
| 300 | |
| 301 | /** |
| 302 | * Checks whether the specified user is allowed to pay the full amount |
| 303 | * in place of the requested deposit. |
| 304 | * |
| 305 | * @param VAPOrderAppointment $order The appointment details object. |
| 306 | * |
| 307 | * @return boolean True in case the full amount should be paid, false |
| 308 | * otherwise (or in case the deposit is disabled). |
| 309 | * |
| 310 | * @since 1.6 |
| 311 | */ |
| 312 | protected function shouldPayFullAmount($order) |
| 313 | { |
| 314 | if (VAPFactory::getConfig()->getUint('usedeposit') != 1) |
| 315 | { |
| 316 | // deposit is disabled or doesn't allow the users to pay the full amount |
| 317 | return false; |
| 318 | } |
| 319 | |
| 320 | // value set after checking the input to pay the full amount instead than the deposit |
| 321 | $pay_full_amount = JFactory::getApplication()->input->getUint('payfull', null); |
| 322 | |
| 323 | if (!is_null($pay_full_amount)) |
| 324 | { |
| 325 | // update order details with given decision |
| 326 | $order->skip_deposit = $pay_full_amount; |
| 327 | |
| 328 | $dbo = JFactory::getDbo(); |
| 329 | |
| 330 | // commit in database too |
| 331 | $q = $dbo->getQuery(true) |
| 332 | ->update($dbo->qn('#__vikappointments_reservation')) |
| 333 | ->set($dbo->qn('skip_deposit') . ' = ' . $pay_full_amount) |
| 334 | ->where(array( |
| 335 | $dbo->qn('id') . ' = ' . $order->id, |
| 336 | $dbo->qn('id_parent') . ' = ' . $order->id_parent, |
| 337 | ), 'OR'); |
| 338 | |
| 339 | $dbo->setQuery($q); |
| 340 | $dbo->execute(); |
| 341 | } |
| 342 | |
| 343 | return (bool) $order->skip_deposit; |
| 344 | } |
| 345 | |
| 346 | /** |
| 347 | * Checks whether the payment (if needed) matches the specified position. |
| 348 | * In that case, the payment form/notes will be echoed. |
| 349 | * |
| 350 | * @param string $position The position in which to print the payment. |
| 351 | * |
| 352 | * @return string The HTML to display. |
| 353 | * |
| 354 | * @since 1.7 |
| 355 | */ |
| 356 | protected function displayPayment($position) |
| 357 | { |
| 358 | if (empty($this->payment)) |
| 359 | { |
| 360 | // nothing to display |
| 361 | return ''; |
| 362 | } |
| 363 | |
| 364 | $position = 'vap-payment-position-' . $position; |
| 365 | |
| 366 | // get payment position |
| 367 | $tmp = $this->payment['payment_info']->position; |
| 368 | |
| 369 | if (!$tmp) |
| 370 | { |
| 371 | // use bottom-left by default |
| 372 | $tmp = 'vap-payment-position-bottom-left'; |
| 373 | } |
| 374 | |
| 375 | // compare payment position |
| 376 | if (strpos($tmp, $position) === false) |
| 377 | { |
| 378 | // position doesn't match |
| 379 | return ''; |
| 380 | } |
| 381 | |
| 382 | // build display data |
| 383 | $data = array( |
| 384 | 'data' => $this->payment, |
| 385 | 'order' => $this->order, |
| 386 | 'scope' => 'appointments', |
| 387 | ); |
| 388 | |
| 389 | // get status role to identify the correct payment layout |
| 390 | $status = strtolower($this->order->statusRole); |
| 391 | |
| 392 | if (!$status) |
| 393 | { |
| 394 | // unable to detect the status role... |
| 395 | return ''; |
| 396 | } |
| 397 | |
| 398 | // return payment layout based on current status role |
| 399 | return JLayoutHelper::render('blocks.payment.' . $status, $data); |
| 400 | } |
| 401 | |
| 402 | /** |
| 403 | * Extends the pathway for breadcrumbs module. |
| 404 | * |
| 405 | * @param mixed $app The application instance. |
| 406 | * |
| 407 | * @return void |
| 408 | * |
| 409 | * @since 1.7 |
| 410 | */ |
| 411 | protected function extendPathway($app) |
| 412 | { |
| 413 | $pathway = $app->getPathway(); |
| 414 | $items = $pathway->getPathway(); |
| 415 | $last = end($items); |
| 416 | |
| 417 | // Make sure the order page is not a menu item, otherwise |
| 418 | // the pathway will display something like: |
| 419 | // Home > Menu > Order > [ORDNUM]-[ORDKEY] |
| 420 | if ($last && strpos($last->link, '&view=order') === false && !empty($this->order)) |
| 421 | { |
| 422 | // register link into the Breadcrumb |
| 423 | $link = 'index.php?option=com_vikappointments&view=order&ordnum=' . $this->order->id . '&ordkey=' . $this->order->sid; |
| 424 | $pathway->addItem($this->order->id . '-' . $this->order->sid, $link); |
| 425 | } |
| 426 | } |
| 427 | } |
| 428 |