Merge commit '00495a36b' into gli-version_upgrade

This commit is contained in:
shazar rahman
2026-10-30 18:06:51 +05:30
60 changed files with 9579 additions and 445 deletions

View File

@@ -1,12 +0,0 @@
-----------------------------
# V1.0.0 to V1.0.1
-----------------------------
## Improved/Changed Features:
* [CO] : [#1464] Updated for PHP version 8.1 to 8.4 and MYSql 8 compatibility
Changelog
v1.0.0
- first release

View File

@@ -1 +0,0 @@
## Channel Manager Connector

View File

@@ -1,65 +0,0 @@
<?php
/**
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License version 3.0
* that is bundled with this package in the file LICENSE.md
* It is also available through the world-wide-web at this URL:
* https://opensource.org/license/osl-3-0-php
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to support@qloapps.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade this module to a newer
* versions in the future. If you wish to customize this module for your needs
* please refer to https://store.webkul.com/customisation-guidelines for more information.
*
* @author Webkul IN
* @copyright Since 2010 Webkul
* @license https://opensource.org/license/osl-3-0-php Open Software License version 3.0
*/
class QcmcChannelManagerBooking extends ObjectModel
{
public $id_order;
public $date_add;
public static $definition = array(
'table' => 'qcmc_channel_manager_booking',
'primary' => 'id_channel_manager_booking',
'fields' => array(
'id_order' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedInt'),
'date_add' => array('type' => self::TYPE_DATE, 'validate' => 'isDate', 'copy_post' => false),
),
);
const QCMC_CHANNEL_MANAGER_IP = '54.216.64.42';
/**
* Returns bookings created by api by channel manager
* @param integer $idOrder : send id_order if you want booking of a particular order
* @param string $orderWay : Send order way (DESC|ASC) sorted by date_add
* @return [array of bookings | false]
*/
public static function getChannelManagerBookings($idOrder = 0, $orderWay = 'ASC')
{
$sql = 'SELECT * FROM `'._DB_PREFIX_.'qcmc_channel_manager_booking` WHERE 1';
if ($idOrder) {
$sql .= ' AND `id_order` = '.(int)$idOrder;
}
$sql .= ' ORDER BY `date_add` '.$orderWay;
if ($result = Db::getInstance()->executeS($sql)) {
if ($idOrder) {
// if needs only one order row then send only one row from the array
$result = $result[0];
}
}
return $result;
}
}

View File

@@ -1,182 +0,0 @@
<?php
/**
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License version 3.0
* that is bundled with this package in the file LICENSE.md
* It is also available through the world-wide-web at this URL:
* https://opensource.org/license/osl-3-0-php
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to support@qloapps.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade this module to a newer
* versions in the future. If you wish to customize this module for your needs
* please refer to https://store.webkul.com/customisation-guidelines for more information.
*
* @author Webkul IN
* @copyright Since 2010 Webkul
* @license https://opensource.org/license/osl-3-0-php Open Software License version 3.0
*/
if (!defined('_PS_VERSION_')) {
exit;
}
require_once dirname(__FILE__).'/classes/QcmcClassInclude.php';
class QloChannelManagerConnector extends Module
{
public function __construct()
{
$this->name = 'qlochannelmanagerconnector';
$this->tab = 'analytics_stats';
$this->version = '1.0.1';
$this->author = 'Webkul';
$this->bootstrap = true;
$this->ps_versions_compliancy = array('min' => '1.6', 'max' => '1.6');
parent::__construct();
$this->displayName = $this->l('Channel Manager Connector');
$this->description = $this->l('This module checks connection with channel manager and create logs for the bookings sent from channel manager.');
}
public function hookDisplayBackOfficeHeader()
{
$this->context->controller->addCSS($this->_path.'views/css/admin/admin_tab_logo.css');
}
public function hookDisplayAdminListBefore()
{
// This tpl will only display when at least one booking has come from channel manager
if ('AdminQloappsChannelManagerConnector' == $this->context->controller->controller_name
&& QcmcChannelManagerBooking::getChannelManagerBookings()
) {
return $this->display(__FILE__, 'channel_manager_connection_info.tpl');
}
}
public function hookActionAdminControllerSetMedia()
{
// This CSS will only apply when at least one booking has come from channel manager
if ('AdminQloappsChannelManagerConnector' == $this->context->controller->controller_name
&& QcmcChannelManagerBooking::getChannelManagerBookings()
) {
$this->context->controller->addCSS(_MODULE_DIR_.$this->name.'/views/css/hook/wk_cm_connection_info.css');
}
}
public function hookActionValidateOrder($data)
{
// If the order request is coming from channel manager ip then only enter in database
if (Tools::getRemoteAddr() == QcmcChannelManagerBooking::QCMC_CHANNEL_MANAGER_IP) {
$order = $data['order'];
if (!QcmcChannelManagerBooking::getChannelManagerBookings($order->id)) {
$objChannelManagerBooking = new QcmcChannelManagerBooking();
$objChannelManagerBooking->id_order = $order->id;
$objChannelManagerBooking->save();
}
}
}
public function callInstallTab()
{
$this->installTab('AdminQloappsChannelManagerConnector', 'Channel Manager');
return true;
}
public function installTab($class_name, $tab_name, $tab_parent_name = false, $need_tab = true)
{
$tab = new Tab();
$tab->active = 1;
$tab->class_name = $class_name;
$tab->name = array();
foreach (Language::getLanguages(true) as $lang) {
$tab->name[$lang['id_lang']] = $tab_name;
}
if ($tab_parent_name) {
$tab->id_parent = (int) Tab::getIdFromClassName($tab_parent_name);
} elseif (!$need_tab) {
$tab->id_parent = -1;
} else {
$tab->id_parent = 0;
}
$tab->module = $this->name;
$res = $tab->add();
//Set position Tab
$objTab = new Tab($tab->id);
// position of the tab will be after hotelreservationsystem module tab
// without calling this function we are getting hotelreservationsystem module tab cache object with old position
ObjectModel::disableCache();
if (Validate::isLoadedObject(
$objTabForPosition = Tab::getInstanceFromClassName('AdminHotelReservationSystemManagement')
)) {
$objTab->updatePosition(0, ($objTabForPosition->position + 1));
} else {
$objTab->updatePosition(0, 6);
}
// enable cache variable again
ObjectModel::enableCache();
return $res;
}
public function registerModuleHooks()
{
return $this->registerHook(
array(
'actionValidateOrder',
'displayBackOfficeHeader',
'displayAdminListBefore',
'actionAdminControllerSetMedia'
)
);
}
public function install()
{
$objModuleDb = new QcmcChannelManagerConnectorDb();
if (!parent::install()
|| !$objModuleDb->createTables()
|| !$this->registerModuleHooks()
|| !$this->callInstallTab()
) {
return false;
}
return true;
}
public function uninstall()
{
$objModuleDb = new QcmcChannelManagerConnectorDb();
if (!parent::uninstall()
|| !$objModuleDb->dropTables()
|| !$this->uninstallTab()
) {
return false;
}
return true;
}
public function uninstallTab()
{
$moduleTabs = Tab::getCollectionFromModule($this->name);
if (!empty($moduleTabs)) {
foreach ($moduleTabs as $moduleTab) {
$moduleTab->delete();
}
return true;
}
return false;
}
}

View File

@@ -1,52 +0,0 @@
/**
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License version 3.0
* that is bundled with this package in the file LICENSE.md
* It is also available through the world-wide-web at this URL:
* https://opensource.org/license/osl-3-0-php
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to support@qloapps.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade this module to a newer
* versions in the future. If you wish to customize this module for your needs
* please refer to https://store.webkul.com/customisation-guidelines for more information.
*
* @author Webkul IN
* @copyright Since 2010 Webkul
* @license https://opensource.org/license/osl-3-0-php Open Software License version 3.0
*/
#content.bootstrap .panel .panel-heading {
text-transform: none;
height: auto;
margin: -20px -16px 25px -16px;
padding: 15px;
color: #555;
font-size: 16px;
border-bottom: 1px solid #d4d9dd;
}
#form-qcmc_channel_manager_booking .table {
border: 1px solid #ddd;
}
#form-qcmc_channel_manager_booking .table thead>tr>th {
font-weight: bold;
font-size: 13px;
vertical-align: middle;
padding: 15px;
background-color: #ecf6fb;
border-bottom: 1px solid #ddd;
}
#form-qcmc_channel_manager_booking .table tbody>tr>td {
font-size: 13px;
color: #555;
vertical-align: middle;
padding: 15px;
border-bottom: 1px solid #ddd;
}
#form-qcmc_channel_manager_booking .pagination {
font-size: 14px;
}

View File

@@ -1,32 +0,0 @@
<?php
/**
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License version 3.0
* that is bundled with this package in the file LICENSE.md
* It is also available through the world-wide-web at this URL:
* https://opensource.org/license/osl-3-0-php
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to support@qloapps.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade this module to a newer
* versions in the future. If you wish to customize this module for your needs
* please refer to https://store.webkul.com/customisation-guidelines for more information.
*
* @author Webkul IN
* @copyright Since 2010 Webkul
* @license https://opensource.org/license/osl-3-0-php Open Software License version 3.0
*/
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');
header('Location: ../../../');
exit;

View File

@@ -0,0 +1,30 @@
---------------------------------------------------------------
# V7.0.0 | COMPATIBLE:: QloApps V1.8.x
---------------------------------------------------------------
+ [BO]: Addition of booking source and business source
- [BO]: Removed all remaining references to the "extra demands" feature
+ [BO]: Absorbed the Channel Manager Bookings admin list (previously provided by the separate `qlochannelmanagerconnector` module) — new `AdminQloappsChannelManagerConnector` tab lists orders detected via `orders.id_channel_manager_booking` (with `source` fallback), no separate DB table
+ [BO]: `qlochannelmanagerconnector` module (if present) is now automatically uninstalled and deleted from disk on install/upgrade of this module
+ [BO]: Channel Manager API booking creation no longer produces raw PHP fatal errors for any kind of unexpected failure — all errors now return as structured JSON
+ [BO]: Invalid/malformed guest firstname, lastname, or email no longer breaks booking creation — falls back to a sanitized/dummy value instead of crashing or rejecting the request
+ [BO]: Fixed partial-booking date-range fragmentation bug — a room with a gap in availability that becomes available again now books as one continuous range instead of splitting into multiple incorrect entries
+ [BO]: `id_pms_booking` is now returned even when booking creation succeeds but a later processing step fails, so the Channel Manager can tell the booking genuinely exists in the PMS
---------------------------------------------------------------
# V5.0.4 | COMPATIBLE:: QloApps V1.7.x
---------------------------------------------------------------
+ [BO]: `rate_plan_name` column support in `qlo_htl_booking_detail` table
+ [BO]: Hook `displayOrderRoomsBookingsTableHeading` — renders Rate Plan column header in admin order detail room table
+ [BO]: Hook `displayOrderRoomsBookingsTableData` — renders Rate Plan cell per room row in admin order detail
---------------------------------------------------------------
# V5.0.0 to V5.0.3 | COMPATIBLE:: QloApps V1.7.x
---------------------------------------------------------------
---------------------------------------------------------------
# V4.0.0 to V4.0.2 | COMPATIBLE:: QloApps V1.7.x
---------------------------------------------------------------
---------------------------------------------------------------
# V1.0.0 to V2.0.4 | COMPATIBLE:: QloApps V1.6.1.0
---------------------------------------------------------------

View File

@@ -0,0 +1,91 @@
### QloApps PMS & Channel Manager Connector
### User Guide Documentation link:
https://qloapps.com/qloapps-pms-channel-manager-connector/
### Support Policy:
https://store.webkul.com/support.html/
### Store link:
https://store.webkul.com/qloapps-pms-channel-manager-connector.html
### Explore Modules:
https://store.webkul.com/Qloapps.html
### Explore Addons:
https://qloapps.com/addons/
### Module Versions and Compatibility:
- **Current version:** `7.0.0`
- Module version `7.0.0` compatible with QloApps version `1.8.x`
- Module version `5.0.6` compatible with QloApps version `1.8.x`
- Module version `5.0.5` compatible with QloApps version `1.7.x`
- Module version `5.0.4` compatible with QloApps version `1.7.x`
- Module version `5.0.3` compatible with QloApps version `1.7.x`
- Module version `5.0.2` compatible with QloApps version `1.7.x`
- Module version `5.0.1` compatible with QloApps version `1.7.x`
- Module version `5.0.0` compatible with QloApps version `1.7.x`
- Module version `4.0.2` compatible with QloApps version `1.7.x`
- Module version `4.0.1` compatible with QloApps version `1.7.x`
- Module version `4.0.0` compatible with QloApps version `1.7.x`
- Module version `1.0.0`, `1.1.0`, `1.1.1`, `1.1.2`, `1.2.0`, `2.0.0`, `2.0.1`, compatible with QloApps version `1.6.1.0`
### Refund Policy:
https://store.webkul.com/refund-policy.html/
---
### QloApps Hook Integration (v5.0.4+)
This module adds a **Rate Plan** column to the order rooms table in the admin order detail view. For this to work, the following two hooks must be present in QloApps core template files.
#### 1. Column Header Hook — `displayOrderRoomsBookingsTableHeading`
**File:**
```
adminhtl/themes/default/template/controllers/orders/_rooms_informaion_table.tpl
```
**Line:** 43 (May Vary)
Add inside the `<thead><tr>` block, after the last `<th>`:
```smarty
{hook h='displayOrderRoomsBookingsTableHeading' order=$order}
```
---
#### 2. Row Data Hook — `displayOrderRoomsBookingsTableData`
**File:**
```
adminhtl/themes/default/template/controllers/orders/_product_line.tpl
```
**Line:** 88 (May Vary)
Add inside the `<tr class="product-line-row">` block, before `</tr>`:
```smarty
{hook h='displayOrderRoomsBookingsTableData' order=$order data=$data}
```
---

View File

@@ -0,0 +1,796 @@
<?php
/**
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License version 3.0
* that is bundled with this package in the file LICENSE.md
* It is also available through the world-wide-web at this URL:
* https://opensource.org/license/osl-3-0-php
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to support@qloapps.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade this module to a newer
* versions in the future. If you wish to customize this module for your needs
* please refer to https://store.webkul.com/customisation-guidelines for more information.
*
* @author Webkul IN
* @copyright Since 2010 Webkul
* @license https://opensource.org/license/osl-3-0-php Open Software License version 3.0
*/
class QcmcAri extends QmkWebService
{
//function called by cron to process the queue data pending for processing, push ari hotel wise in foreach
public function processAriUpdateData()
{
$objAriUpdates = new QcmcAriUpdates();
$objChannelManagerApiService = new QcmcChannelManagerApiService();
$objConnectorModule = Module::getInstanceByName('qlocmconnector');
$pendingRowsToProcess = $objAriUpdates->getPendingAriUpdateRows();
if ($pendingRowsToProcess) {
$flatAriUpdateDataPayload = $this->buildAriUpdatePayload($pendingRowsToProcess);
foreach ($flatAriUpdateDataPayload as $hotelPayload) {
$hotelId = (int)$hotelPayload['id_pms_property'];
$rowsForHotel = array_filter($pendingRowsToProcess, function ($row) use ($hotelId) {
return (int)$row['id_hotel'] === $hotelId;
});
if (empty($rowsForHotel)) {
continue;
}
$rowIdsForHotel = array_column($rowsForHotel, 'id_ari_updates');
$objAriUpdates->markProcessingAriUpdateData($rowIdsForHotel);
$result = $objChannelManagerApiService->pushARI($hotelPayload);
if (isset($result['success']) && $result['success']) {
$objAriUpdates->deleteAriUpdateDataByIds($rowIdsForHotel);
} else {
$objAriUpdates->resetAriUpdateDataToPending($rowIdsForHotel);
if (isset($result['http_code']) && $result['http_code']==401) {
return false;
}
}
}
}
return true;
}
//function to build the payload as per pms standard and calculate availability on pms side
private function buildAriUpdatePayload($flatAriUpdateRows)
{
$flatAriUpdatePayloadDateWise = [];
//build flat items without calling getAvailableRoomsByDate
foreach ($flatAriUpdateRows as $flatAriUpdateRow) {
if (empty($flatAriUpdateRow['ari_data'])) {
continue;
}
$flatAriUpdateRowData = json_decode($flatAriUpdateRow['ari_data'], true);
if (!is_array($flatAriUpdateRowData)) {
continue;
}
$flatAriUpdateItem = [
'id_pms_property' => (int)$flatAriUpdateRow['id_hotel'],
'id_pms_room_type' => (int)$flatAriUpdateRow['id_room_type'],
'date' => date('Y-m-d', strtotime($flatAriUpdateRow['date'])),
];
//this is just a marker to check later that availabilty must be added
if (array_key_exists('inventory', $flatAriUpdateRowData)) {
$flatAriUpdateItem['need_availability'] = true;
}
if (array_key_exists('min_booking_offset', $flatAriUpdateRowData)) {
$flatAriUpdateItem['min_booking_offset'] = $flatAriUpdateRowData['min_booking_offset'];
} else {
// Get default min_booking_offset from hotel settings
$flatAriUpdateItem['min_booking_offset'] = HotelOrderRestrictDate::getMinimumBookingOffset(
(int)$flatAriUpdateRow['id_hotel']
);
}
if (array_key_exists('max_booking_offset', $flatAriUpdateRowData)) {
$flatAriUpdateItem['max_booking_offset'] = $flatAriUpdateRowData['max_booking_offset'];
} else {
// Get default max_booking_offset from hotel settings
$flatAriUpdateItem['max_booking_offset'] = HotelOrderRestrictDate::getMaximumCheckoutOffset(
(int)$flatAriUpdateRow['id_hotel']
);
}
// Get LOS (length of stay) restrictions
$flatAriUpdateItem['los'] = $this->getLosRestrictionForDate(
(int)$flatAriUpdateRow['id_room_type'],
$flatAriUpdateRow['date']
);
$flatAriUpdatePayloadDateWise[] = $flatAriUpdateItem;
}
if (empty($flatAriUpdatePayloadDateWise)) {
return [];
}
//calculate per-hotel min/max date so we can fetch booking data in one call per hotel
$hotelDateRanges = [];
foreach ($flatAriUpdatePayloadDateWise as &$datewiseAriUpdateData) {
$idHotel = $datewiseAriUpdateData['id_pms_property'];
$idProduct = $datewiseAriUpdateData['id_pms_room_type'];
$date = $datewiseAriUpdateData['date'];
if (!isset($hotelDateRanges[$idHotel])) {
$hotelDateRanges[$idHotel] = ['min' => $date, 'max' => $date];
} else {
if ($date < $hotelDateRanges[$idHotel]['min']) {
$hotelDateRanges[$idHotel]['min'] = $date;
}
if ($date > $hotelDateRanges[$idHotel]['max']) {
$hotelDateRanges[$idHotel]['max'] = $date;
}
}
$totalBookingPrice = HotelRoomTypeFeaturePricing::getRoomTypeTotalPrice(
$idProduct,
$datewiseAriUpdateData['date'],
date('Y-m-d H:i:s', strtotime('+1 day', strtotime($datewiseAriUpdateData['date']))),
1
);
$datewiseAriUpdateData['date_from'] = $datewiseAriUpdateData['date'];
$datewiseAriUpdateData['date_to'] = $datewiseAriUpdateData['date'];
$datewiseAriUpdateData['price_with_tax'] = $totalBookingPrice['total_price_tax_incl'];
$datewiseAriUpdateData['price_without_tax'] = $totalBookingPrice['total_price_tax_excl'];
$datewiseAriUpdateData['tax'] = $totalBookingPrice['total_price_tax_incl'] - $totalBookingPrice['total_price_tax_excl'];
unset($datewiseAriUpdateData['date']);
}
unset($datewiseAriUpdateData);
//For each hotel call getBookingData once and build an avialability map
$availabilityMap = [];
foreach ($hotelDateRanges as $hotelId => $dateRange) {
$startDate = strtotime($dateRange['min']);
$endDate = strtotime($dateRange['max']);
// call booking data only one time per hotel
$bookingParams = [
'date_from' => date('Y-m-d 00:00:00', $startDate),
'date_to' => date('Y-m-d 23:59:59', $endDate),
'hotel_id' => $hotelId,
'search_available' => 1,
'search_booked' => 1,
'search_unavai' => 1,
'search_partial' => 0,
'search_cart_rms' => 0,
'only_search_data' => 1,
];
$objBookingDtl = new HotelBookingDetail();
$bookingData = $objBookingDtl->getBookingData($bookingParams);
if (!isset($availabilityMap[$hotelId])) {
$availabilityMap[$hotelId] = [];
}
if (!empty($bookingData['rm_data'])) {
foreach ($bookingData['rm_data'] as $roomTypeId => $roomTypeData) {
if (!isset($availabilityMap[$hotelId][$roomTypeId])) {
$availabilityMap[$hotelId][$roomTypeId] = [];
}
// number of units for this room-type
$numRooms = (int)($roomTypeData['numberOfRooms'] ?? 0);
$unAvailableRooms = [];
if (!empty($roomTypeData['data']['booked'])) {
foreach ($roomTypeData['data']['booked'] as $bookedRow) {
if (!empty($bookedRow['detail'])) {
foreach ($bookedRow['detail'] as $bookedDetail) {
$unAvailableRooms[] = [
'date_from' => strtotime($bookedDetail['date_from']),
'date_to' => strtotime($bookedDetail['date_to']),
];
}
}
}
}
if (count($roomTypeData['data']['unavailable'])) {
foreach ($roomTypeData['data']['unavailable'] as $unavailableRooms) {
foreach ($unavailableRooms['detail'] as $unavailableDetail) {
if ($unavailableDetail['id_status'] == HotelRoomInformation::STATUS_INACTIVE
) {
$unAvailableRooms[] = [
'date_from' => $startDate,
'date_to' => $endDate,
];
} else if ($unavailableDetail['id_status'] == HotelRoomInformation::STATUS_TEMPORARY_INACTIVE
&& $unavailableDetail['date_from'] && $unavailableDetail['date_to']
) {
$unAvailableRooms[] = [
'date_from' => strtotime($unavailableDetail['date_from']),
'date_to' => strtotime($unavailableDetail['date_to']),
];
}
}
}
}
for ($date = $startDate; $date <= $endDate; $date = strtotime('+1 day', $date)) {
$dateStr = date('Y-m-d', $date);
$unAvailableRoomCount = 0;
if (!empty($unAvailableRooms)) {
foreach ($unAvailableRooms as $unAvailableRoom) {
if ($date >= $unAvailableRoom['date_from'] && $date < $unAvailableRoom['date_to']) {
$unAvailableRoomCount++;
}
}
}
$available = max(0, $numRooms - $unAvailableRoomCount);
$availabilityMap[$hotelId][$roomTypeId][$dateStr] = $available;
}
}
}
}
foreach ($flatAriUpdatePayloadDateWise as &$payloadItem) {
if (!empty($payloadItem['need_availability'])) {
$available = $availabilityMap[$payloadItem['id_pms_property']][$payloadItem['id_pms_room_type']][$payloadItem['date_to']] ?? 0;
$payloadItem['availability'] = (int)$available;
unset($payloadItem['need_availability']);
}
}
unset($payloadItem);
//sort and merge consecutive dates
usort($flatAriUpdatePayloadDateWise, function($firstCmp, $secondCmp) {
if ($firstCmp['id_pms_property'] !== $secondCmp['id_pms_property']) {
return $firstCmp['id_pms_property'] <=> $secondCmp['id_pms_property'];
}
if ($firstCmp['id_pms_room_type'] !== $secondCmp['id_pms_room_type']) {
return $firstCmp['id_pms_room_type'] <=> $secondCmp['id_pms_room_type'];
}
return strcmp($firstCmp['date_to'], $secondCmp['date_to']);
});
$mergedAriUpdatePayload = [];
$currentData = [];
foreach ($flatAriUpdatePayloadDateWise as $mergeFlatAriUpdateItem) {
if(empty($currentData)) {
$currentData = $mergeFlatAriUpdateItem;
} else {
if (
(strtotime($currentData['date_to'] . ' +1 day') == strtotime($mergeFlatAriUpdateItem['date_from']))
&& $currentData['id_pms_property'] == $mergeFlatAriUpdateItem['id_pms_property']
&& $currentData['id_pms_room_type'] == $mergeFlatAriUpdateItem['id_pms_room_type']
&& $currentData['price_with_tax'] == $mergeFlatAriUpdateItem['price_with_tax']
&& $currentData['min_booking_offset'] == $mergeFlatAriUpdateItem['min_booking_offset']
&& $currentData['max_booking_offset'] == $mergeFlatAriUpdateItem['max_booking_offset']
&& $currentData['availability'] == $mergeFlatAriUpdateItem['availability']
&& (
isset($currentData['los']) && isset($mergeFlatAriUpdateItem['los'])
&& $currentData['los']['min_los'] == $mergeFlatAriUpdateItem['los']['min_los']
&& $currentData['los']['max_los'] == $mergeFlatAriUpdateItem['los']['max_los']
) && $currentData['price_without_tax'] == $mergeFlatAriUpdateItem['price_without_tax']
&& $currentData['tax'] == $mergeFlatAriUpdateItem['tax']
) {
$currentData['date_to'] = $mergeFlatAriUpdateItem['date_to'];
} else {
$mergedAriUpdatePayload[] = $currentData;
$currentData = $mergeFlatAriUpdateItem;
}
}
}
if ($currentData) {
$mergedAriUpdatePayload[] = $currentData;
}
$finalAriUpdatePayload = [];
foreach ($mergedAriUpdatePayload as $row) {
if (!isset($finalAriUpdatePayload[$row['id_pms_property']])) {
$finalAriUpdatePayload[$row['id_pms_property']] = [
'id_property' => '',
'id_pms_property' => (string)$row['id_pms_property'],
'ari_info' => []
];
}
// Use LOS restrictions from merged data or get from database
$losRestriction = $row['los'] ?? $this->getLosRestrictionForDate(
(int)$row['id_pms_room_type'],
$row['date_from']
);
// Get booking offset from merged data or use defaults
$minBookingOffset = $row['min_booking_offset'] ?? HotelOrderRestrictDate::getMinimumBookingOffset((int)$row['id_pms_property']);
$maxBookingOffset = $row['max_booking_offset'] ?? HotelOrderRestrictDate::getMaximumCheckoutOffset((int)$row['id_pms_property']);
$ariInfo = [
'id_room_type' => '',
'id_pms_room_type' => (string)$row['id_pms_room_type'],
'date_from' => $row['date_from'],
'date_to' => $row['date_to'],
'weekdays' => ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'],
'closed' => 0,
'rates' => [
json_encode([
"id_rate_plan"=> "0",
"id_pms_rate_plan" => "0",
'price_with_tax' => isset($row['price_with_tax']) ? (float)$row['price_with_tax'] : 0,
'price_without_tax' => isset($row['price_without_tax']) ? (float)$row['price_without_tax'] : 0,
'tax' => isset($row['tax']) ? (float)$row['tax'] : 0,
"occupancy_based_price" => json_encode([]),
"min_los_arrival" => 0,
"max_los_arrival" => 0,
"min_los_stay" => isset($losRestriction['min_los']) ? (int)$losRestriction['min_los'] : 1,
"max_los_stay" => isset($losRestriction['max_los']) ? (int)$losRestriction['max_los'] : 30,
"stop_sell" => 0,
"close_to_arrival" => 0,
"close_to_departure" => 0,
"min_advanced_booking_offset" => (int)$minBookingOffset,
"max_advanced_booking_offset" => (int)$maxBookingOffset
])
]
];
if (isset($row['availability'])) {
$ariInfo['availability'] = (int)$row['availability'];
}
Hook::exec('actionChannelManagerAriPayloadModifier', array(
'ariInfo' => &$ariInfo,
'idHotel' => $row['id_pms_property'],
'idRoomType' => $row['id_pms_room_type'],
'dateFrom' => $row['date_from'],
'dateTo' => $row['date_to'],
));
$finalAriUpdatePayload[$row['id_pms_property']]['ari_info'][] = $ariInfo;
}
return array_values($finalAriUpdatePayload);
}
//function to get ari of specific property for channel manager
public function getAriByIdProperty($params)
{
$finalAriUpdatePayload = [];
if($dateWiseHotelAri = $this->getHotelAri($params)) {
if($mergeData = $this->mergeHotelAriData($dateWiseHotelAri)) {
foreach ($mergeData as $idHotel => $productWiseAri){
foreach ($productWiseAri as $idProduct => $ariData) {
foreach ($ariData as $key => $ari) {
$ariInfo = [
'id_room_type' => '',
'id_pms_room_type' => (string)$idProduct,
'availability'=> $ari['total_available_rooms'],
'date_from' => $ari['date_from'],
'date_to' => $ari['date_to'],
'weekdays' => ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'],
'closed' => 0,
'rates' => [
json_encode([
"id_rate_plan"=> "0",
"id_pms_rate_plan"=> "0",
'price_with_tax' => $ari['total_price_with_tax'],
'price_without_tax' => $ari['total_price_tax_excl'],
"occupancy_based_price" => json_encode([]),
"min_los_arrival"=> 0,
"max_los_arrival"=> 0,
"min_los_stay"=> $ari['min_los'],
"max_los_stay"=> $ari['max_los'],
"stop_sell"=> 0,
"close_to_arrival"=> 0,
"close_to_departure"=> 0,
"min_advanced_booking_offset"=> $ari['minimum_booking_offset'],
"max_advanced_booking_offset"=> $ari['maximum_booking_offset']
])
],
];
if (!isset($finalAriUpdatePayload[$idHotel])) {
$finalAriUpdatePayload[$idHotel] = [
'id_property' => '',
'id_pms_property' => (string)$idHotel,
'ari_info' => []
];
}
$finalAriUpdatePayload[$idHotel]['ari_info'][] = $ariInfo;
}
}
}
}
}
return json_encode(array(
'success' => true,
'data' => array_values($finalAriUpdatePayload)
));
}
/**
*
* @param array $params Parameters including id_property, id_room_type, date_from, date_to
* @return string JSON encoded ARI response
*/
public function getHotelAri($params)
{
if (!$idProperty = (int)($params['id_property'])) {
return [];
}
$minimumBookingOffset = HotelOrderRestrictDate::getMinimumBookingOffset($idProperty);
$maximumCheckoutOffset = HotelOrderRestrictDate::getMaximumCheckoutOffset($idProperty);
// Get room type IDs
$idProducts = [];
if (!empty($params['id_room_type'])) {
$idProducts = array_filter(array_map('intval', explode('|', $params['id_room_type'])));
} else {
$objHotelRoomType = new HotelRoomType();
if ($roomTypesByIdProperty = $objHotelRoomType->getRoomTypeByHotelId($idProperty, Context::getContext()->language->id)) {
foreach ($roomTypesByIdProperty as $roomTypeInfo) {
if ($roomTypeInfo['active']) {
$idProducts[] = $roomTypeInfo['id_room_type'];
}
}
}
}
if (empty($idProducts)) {
return [];
}
// Get date range
$dateFrom = !empty($params['date_from']) ? $params['date_from'] : date('Y-m-d');
$dateTo = !empty($params['date_to']) ? $params['date_to'] : date('Y-m-d', strtotime(HotelOrderRestrictDate::getMaxOrderDate($idProperty)));
// Build ARI params
$bookingParams = [
'date_from' => date('Y-m-d 00:00:00', strtotime($dateFrom)),
'date_to' => date('Y-m-d 23:59:59', strtotime($dateTo)),
'hotel_id' => $idProperty,
'search_available' => 0,
'search_booked' => 1,
'search_unavai' => 1,
'search_partial' => 0,
'search_cart_rms' => 0,
];
$objBookingDtl = new HotelBookingDetail();
$searchAriData = [];
$productsLosRestriction = $this->getDateWiseRestrictionOfProducts($idProducts, $dateFrom, $dateTo);
foreach ($idProducts as $idProduct) {
$bookingParams['id_room_type'] = $idProduct;
if(!isset($searchAriData[$idProperty])) {
$searchAriData[$idProperty] = array();
}
if(!isset($searchAriData[$idProperty][$idProduct])) {
$searchAriData[$idProperty][$idProduct] = array();
}
$bookingData = $objBookingDtl->getBookingData($bookingParams);
// Calculate date-wise data
for ($currentDate = $dateFrom; $currentDate < $dateTo; $currentDate = date('Y-m-d', strtotime('+1 day', strtotime($currentDate)))) {
$nextDate = date('Y-m-d', strtotime('+1 day', strtotime($currentDate)));
if (!empty($bookingData['rm_data'])) {
foreach ($bookingData['rm_data'] as $roomType) {
$ariDateInfo = [
'date_from' => $currentDate,
'date_to' => $nextDate,
];
$ariDateInfo['total_available_rooms'] = $roomType['numberOfRooms'];
// Get all rooms of product(room type)
$rooms = HotelRoomInformation::getHotelRoomsInfo($idProperty, $idProduct);
$numRooms = count($rooms);
$roomDetail = [];
// Booked rooms
if ($bookingParams['search_booked'] && isset($roomType['data']['booked'])) {
$bookedRoomIds = [];
foreach ($roomType['data']['booked'] as $bookedRow) {
if (!empty($bookedRow['detail'])) {
foreach ($bookedRow['detail'] as $bookDetail) {
if ($currentDate >= date('Y-m-d', strtotime($bookDetail['date_from'])) && $currentDate < date('Y-m-d', strtotime($bookDetail['date_to']))) {
$bookedRoomIds[] = $bookedRow['id_room'];
}
}
}
}
$roomDetail['booked'] = array_values(array_filter($rooms, function($room) use ($bookedRoomIds) {
return in_array($room['id'], $bookedRoomIds);
}));
}
$ariDateInfo['total_available_rooms'] -= count($roomDetail['booked']);
// Unavailable rooms
if ($bookingParams['search_unavai'] && isset($roomType['data']['unavailable'])) {
$unavailableRoomIds = [];
foreach ($roomType['data']['unavailable'] as $unavailRow) {
if (!empty($unavailRow['detail'])) {
foreach ($unavailRow['detail'] as $unavailDetail) {
if(!$unavailDetail['date_from'] || !$unavailDetail['date_to']) {
$unavailableRoomIds[] = $unavailRow['id_room'];
} else if ($currentDate >= date('Y-m-d', strtotime($unavailDetail['date_from'])) && $currentDate < date('Y-m-d', strtotime($unavailDetail['date_to']))) {
$unavailableRoomIds[] = $unavailRow['id_room'];
}
}
}
}
$roomDetail['unavailable'] = array_values(array_filter($rooms, function($room) use ($unavailableRoomIds) {
return in_array($room['id'], $unavailableRoomIds);
}));
}
$ariDateInfo['total_available_rooms'] -= count($roomDetail['unavailable']);
$totalBookingPrice = HotelRoomTypeFeaturePricing::getRoomTypeTotalPrice(
$idProduct,
$currentDate,
date('Y-m-d H:i:s', strtotime('+1 day', strtotime($currentDate))),
1
);
if(isset($productsLosRestriction[$idProduct])
&& isset($productsLosRestriction[$idProduct][$currentDate])
&& $productsLosRestriction[$idProduct][$currentDate]
) {
$ariDateInfo['min_los'] = $productsLosRestriction[$idProduct][$currentDate]['min_los'] ?? 1;
$ariDateInfo['max_los'] = $productsLosRestriction[$idProduct][$currentDate]['max_los'] ?? 30;
} else {
// Use defaults from room type
$objRoomType = new HotelRoomType($idProduct);
$ariDateInfo['min_los'] = (int) $objRoomType->min_los;
$ariDateInfo['max_los'] = (int) $objRoomType->max_los;
}
$ariDateInfo['total_price_with_tax'] = $totalBookingPrice['total_price_tax_incl'];
$ariDateInfo['total_price_tax_excl'] = $totalBookingPrice['total_price_tax_excl'];
$ariDateInfo['tax'] = $totalBookingPrice['total_price_tax_incl'] - $totalBookingPrice['total_price_tax_excl'];
$ariDateInfo['minimum_booking_offset'] = $minimumBookingOffset;
$ariDateInfo['maximum_booking_offset'] = $maximumCheckoutOffset;
$searchAriData[$idProperty][$idProduct][] = $ariDateInfo;
}
} else {
$ariDateInfo = [
'date_from' => $currentDate,
'date_to' => $nextDate,
];
$ariDateInfo['total_available_rooms'] = 0;
$totalBookingPrice = HotelRoomTypeFeaturePricing::getRoomTypeTotalPrice(
$idProduct,
$currentDate,
date('Y-m-d H:i:s', strtotime('+1 day', strtotime($currentDate))),
1
);
if(isset($productsLosRestriction[$idProduct])
&& isset($productsLosRestriction[$idProduct][$currentDate])
&& $productsLosRestriction[$idProduct][$currentDate]
) {
$ariDateInfo['min_los'] = $productsLosRestriction[$idProduct][$currentDate]['min_los'] ?? 1;
$ariDateInfo['max_los'] = $productsLosRestriction[$idProduct][$currentDate]['max_los'] ?? 30;
} else {
// Use defaults from room type
$objRoomType = new HotelRoomType($idProduct);
$ariDateInfo['min_los'] = (int) $objRoomType->min_los;
$ariDateInfo['max_los'] = (int) $objRoomType->max_los;
}
$ariDateInfo['minimum_booking_offset'] = $minimumBookingOffset;
$ariDateInfo['maximum_booking_offset'] = $maximumCheckoutOffset;
$ariDateInfo['total_price_with_tax'] = $totalBookingPrice['total_price_tax_incl'];
$ariDateInfo['total_price_tax_excl'] = $totalBookingPrice['total_price_tax_excl'];
$ariDateInfo['tax'] = $totalBookingPrice['total_price_tax_incl'] - $totalBookingPrice['total_price_tax_excl'];
$searchAriData[$idProperty][$idProduct][] = $ariDateInfo;
}
}
}
return $searchAriData;
}
public function getDateWiseRestrictionOfProducts($idProducts, $dateFrom, $dateTo)
{
$lengthOfStayArr = [];
foreach ($idProducts as $idProduct) {
if(!isset($lengthOfStayArr[$idProduct])) {
$lengthOfStayArr[$idProduct] = [];
}
if (Validate::isLoadedObject($objRoomType = new HotelRoomType($idProduct))) {
$minLosDefault = (int) $objRoomType->min_los;
$maxLosDefault = (int) $objRoomType->max_los;
$objHotelRoomTypeRestrictionDateRange = new HotelRoomTypeRestrictionDateRange();
$lengthStayArr = $objHotelRoomTypeRestrictionDateRange->getRoomTypeLengthOfStayRestriction($idProduct);
// Pre-index restrictions by date for O(1) lookup instead of O(n) per date
$restrictionByDate = [];
foreach ($lengthStayArr as $restriction) {
$dateFromRestriction = $restriction['date_from'];
$dateToRestriction = $restriction['date_to'];
// Build a date-indexed lookup for fast access
for ($restrDate = $dateFromRestriction; $restrDate <= $dateToRestriction; $restrDate = date('Y-m-d', strtotime('+1 day', strtotime($restrDate)))) {
if (!isset($restrictionByDate[$restrDate])) {
$restrictionByDate[$restrDate] = [
'min_los' => isset($restriction['min_los']) && $restriction['min_los'] !== '' ? (int) $restriction['min_los'] : $minLosDefault,
'max_los' => isset($restriction['max_los']) && $restriction['max_los'] !== '' ? (int) $restriction['max_los'] : $maxLosDefault,
];
}
}
}
// Now iterate through requested date range with O(1) lookup per date
for ($currentDate = $dateFrom; $currentDate < $dateTo; $currentDate = date('Y-m-d', strtotime('+1 day', strtotime($currentDate)))) {
if (isset($restrictionByDate[$currentDate])) {
$lengthOfStayArr[$idProduct][$currentDate] = $restrictionByDate[$currentDate];
} else {
// Use defaults if no restriction found for this date
$lengthOfStayArr[$idProduct][$currentDate] = [
'min_los' => $minLosDefault,
'max_los' => $maxLosDefault,
];
}
}
}
}
return $lengthOfStayArr;
}
/**
* Get LOS (Length of Stay) restriction for a specific room type and date
*
* @param int $idRoomType Room type ID
* @param string $date Date in Y-m-d format
* @return array Array with min_los and max_los
*/
public function getLosRestrictionForDate($idRoomType, $date)
{
$result = [
'min_los' => 0,
'max_los' => 0,
];
if (Validate::isLoadedObject($objRoomType = new HotelRoomType($idRoomType))) {
$minLosDefault = (int) $objRoomType->min_los;
$maxLosDefault = (int) $objRoomType->max_los;
$objHotelRoomTypeRestrictionDateRange = new HotelRoomTypeRestrictionDateRange();
$lengthStayArr = $objHotelRoomTypeRestrictionDateRange->getRoomTypeLengthOfStayRestriction($idRoomType);
if (!empty($lengthStayArr)) {
foreach ($lengthStayArr as $restriction) {
$dateFromRestriction = $restriction['date_from'];
$dateToRestriction = $restriction['date_to'];
// Check if the date falls within the restriction range
if ($date >= $dateFromRestriction && $date <= $dateToRestriction) {
$result['min_los'] = isset($restriction['min_los']) && $restriction['min_los'] !== ''
? (int) $restriction['min_los']
: $minLosDefault;
$result['max_los'] = isset($restriction['max_los']) && $restriction['max_los'] !== ''
? (int) $restriction['max_los']
: $maxLosDefault;
break;
}
}
}
// If no restriction found for the date, return defaults
if ($result['min_los'] == 0 && $result['max_los'] == 0) {
$result['min_los'] = $minLosDefault;
$result['max_los'] = $maxLosDefault;
}
}
return $result;
}
public function mergeHotelAriData($ariData)
{
$mergedData = [];
foreach ($ariData as $hotelId => $roomTypeData) {
if(!isset($mergedData[$hotelId])) {
$mergedData[$hotelId] = [];
}
if (!is_array($roomTypeData)) {
continue;
}
foreach ($roomTypeData as $idRoomType => $roomTypeWiseAri) {
if (!is_array($roomTypeWiseAri) || empty($roomTypeWiseAri)) {
continue;
}
// Sort by date_from to ensure chronological order
usort($roomTypeWiseAri, function($a, $b) {
return strcmp($a['date_from'], $b['date_from']);
});
if(!isset($mergedData[$hotelId][$idRoomType])) {
$mergedData[$hotelId][$idRoomType] = [];
}
$temp = [];
foreach ($roomTypeWiseAri as $ari) {
if (empty($temp)) {
// First record - initialize temp
$temp = array(
'date_from' => $ari['date_from'],
'date_to' => $ari['date_to'],
'total_available_rooms' => $ari['total_available_rooms'],
'min_los' => $ari['min_los'],
'max_los' => $ari['max_los'],
'total_price_with_tax' => $ari['total_price_with_tax'],
'total_price_tax_excl' => $ari['total_price_tax_excl'],
'tax' => $ari['tax'],
'minimum_booking_offset' => $ari['minimum_booking_offset'],
'maximum_booking_offset' => $ari['maximum_booking_offset']
);
} else {
// Check if consecutive dates (temp.date_to == ari.date_from) AND all other fields match
if ($temp['date_to'] == $ari['date_from'] &&
$temp['min_los'] == $ari['min_los'] &&
$temp['max_los'] == $ari['max_los'] &&
$temp['total_available_rooms'] == $ari['total_available_rooms'] &&
$temp['total_price_with_tax'] == $ari['total_price_with_tax'] &&
$temp['total_price_tax_excl'] == $ari['total_price_tax_excl'] &&
$temp['tax'] == $ari['tax'] &&
$temp['minimum_booking_offset'] == $ari['minimum_booking_offset'] &&
$temp['maximum_booking_offset'] == $ari['maximum_booking_offset']
) {
// Merge - extend date_to
$temp['date_to'] = $ari['date_to'];
} else {
// Not consecutive or different parameters - push temp and start new
$mergedData[$hotelId][$idRoomType][] = $temp;
$temp = array(
'date_from' => $ari['date_from'],
'date_to' => $ari['date_to'],
'total_available_rooms' => $ari['total_available_rooms'],
'min_los' => $ari['min_los'],
'max_los' => $ari['max_los'],
'total_price_with_tax' => $ari['total_price_with_tax'],
'total_price_tax_excl' => $ari['total_price_tax_excl'],
'tax' => $ari['tax'],
'minimum_booking_offset' => $ari['minimum_booking_offset'],
'maximum_booking_offset' => $ari['maximum_booking_offset']
);
}
}
}
// Push remaining temp data
if (!empty($temp)) {
$mergedData[$hotelId][$idRoomType][] = $temp;
}
}
}
return $mergedData;
}
}

View File

@@ -0,0 +1,172 @@
<?php
/**
* NOTICE OF LICENSE
*
* This source file is subject to the Academic Free License (AFL 3.0)
* that is bundled with this package in the file LICENSE.md
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/afl-3.0.php
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to support@qloapps.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade this module to a newer
* versions in the future. If you wish to customize this module for your needs
* please refer to https://store.webkul.com/customisation-guidelines for more information.
*
* @author Webkul IN
* @copyright Since 2010 Webkul
* @license https://opensource.org/licenses/afl-3.0.php Academic Free License 3.0
*/
class QcmcAriUpdates extends ObjectModel
{
public $id_ari_updates;
public $id_hotel;
public $id_room_type;
public $date;
public $ari_data;
public $status;
public $date_add;
public $date_upd;
public const ARI_SYNC_PROCESSING = 1;
public const ARI_SYNC_NOT_PROCESSED = 0;
public static $definition = array(
'table' => 'ari_updates',
'primary' => 'id_ari_updates',
'fields' => array(
'id_hotel' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true),
'id_room_type' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true),
'date' => array('type' => self::TYPE_DATE, 'validate' => 'isDate', 'required' => true),
'ari_data' => array('type' => self::TYPE_STRING, 'validate' => 'isString'),
'status' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedInt'),
'date_add' => array('type' => self::TYPE_DATE, 'validate' => 'isDate'),
'date_upd' => array('type' => self::TYPE_DATE, 'validate' => 'isDate'),
),
);
//Returns the array of existing ARI update data for a room type, keyed by date
public function getExistingAriUpdateData($idHotel, $idRoomType)
{
$sql = 'SELECT `id_ari_updates` as `id`, `date`, `ari_data`
FROM `'._DB_PREFIX_.'ari_updates`
WHERE id_hotel = '.(int)$idHotel.'
AND id_room_type = '.(int)$idRoomType;
$existingAriData = Db::getInstance()->executeS($sql);
// Reindex array by date
$result = [];
if ($existingAriData) {
foreach ($existingAriData as $value) {
$dateKey = date('Y-m-d', strtotime($value['date']));
$result[$dateKey] = $value;
}
}
return $result;
}
//Returns the row hotel (order) wise where the queue row is not processed
public function getPendingAriUpdateRows()
{
$sql = 'SELECT * FROM `'._DB_PREFIX_.'ari_updates`
WHERE status = ' . (int) self::ARI_SYNC_NOT_PROCESSED . '
ORDER BY id_hotel ASC, date_add ASC';
return Db::getInstance()->executeS($sql);
}
//Universal function to save rows in the table, currently handles availablity only
public function saveAriUpdateRow($idHotel, $idRoomType, $dateFrom, $dateTo, $ariData = [])
{
if (empty($idHotel) || empty($idRoomType)) {
return false;
}
// Set default ari_data if not provided
if(empty($ariData)) {
$ariData['inventory'] = 1;
}
// Set default ari_data if not provided
$defaultAriData = [];
// Merge provided data with defaults
$ariData = array_merge($defaultAriData, $ariData);
// Get existing data for this room type, keyed by date
$getExistingDataByRoomType = $this->getExistingAriUpdateData($idHotel, $idRoomType);
// for loop from dateFrom to dateTo
for ($date = $dateFrom; $date <= $dateTo; $date = date('Y-m-d', strtotime($date . ' +1 day'))) {
if (isset($getExistingDataByRoomType[$date])) {
// Update existing record
$objAriUpdates = new QcmcAriUpdates($getExistingDataByRoomType[$date]['id']);
$existingAriData = json_decode($getExistingDataByRoomType[$date]['ari_data'], true);
if ($existingAriData) {
$ariData = array_merge($existingAriData, $ariData);
}
} else {
// Create new record
$objAriUpdates = new QcmcAriUpdates();
}
$objAriUpdates->id_hotel = (int)$idHotel;
$objAriUpdates->id_room_type = (int)$idRoomType;
$objAriUpdates->date = pSQL($date);
$objAriUpdates->ari_data = json_encode($ariData);
$objAriUpdates->status = self::ARI_SYNC_NOT_PROCESSED;
$objAriUpdates->save();
}
return true;
}
//mark the rows in the table under-processing
public function markProcessingAriUpdateData($ids)
{
if (!empty($ids)) {
foreach ($ids as $id) {
$objAriUpdates = new QcmcAriUpdates((int)$id);
if (Validate::isLoadedObject($objAriUpdates)) {
$objAriUpdates->status = self::ARI_SYNC_PROCESSING;
$objAriUpdates->date_upd = date('Y-m-d H:i:s');
$objAriUpdates->update();
}
}
}
}
//Delete the rows which gets successfully processed
public function deleteAriUpdateDataByIds($ids)
{
if (!empty($ids)) {
foreach ($ids as $id) {
$objAriUpdates = new QcmcAriUpdates((int)$id);
if (Validate::isLoadedObject($objAriUpdates)) {
$objAriUpdates->delete();
}
}
}
}
//If the response return false then mark all the rows to not processed which were marked as processing
public function resetAriUpdateDataToPending($ids)
{
if (!empty($ids)) {
foreach ($ids as $id) {
$objAriUpdates = new QcmcAriUpdates((int)$id);
if (Validate::isLoadedObject($objAriUpdates)) {
$objAriUpdates->status = self::ARI_SYNC_NOT_PROCESSED;
$objAriUpdates->date_upd = date('Y-m-d H:i:s');
$objAriUpdates->update();
}
}
}
}
}

View File

@@ -0,0 +1,308 @@
<?php
/**
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License version 3.0
* that is bundled with this package in the file LICENSE.md
* It is also available through the world-wide-web at this URL:
* https://opensource.org/license/osl-3-0-php
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to support@qloapps.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade this module to a newer
* versions in the future. If you wish to customize this module for your needs
* please refer to https://store.webkul.com/customisation-guidelines for more information.
*
* @author Webkul IN
* @copyright Since 2010 Webkul
* @license https://opensource.org/license/osl-3-0-php Open Software License version 3.0
*/
class QcmcBooking extends QmkWebService
{
public function getBookingList($params)
{
$output = '';
$bookings = array();
if($allOrders = $this->getAllOrdersId($params)) {
$objHotelBookingDetail = new HotelBookingDetail();
$objServiceProductOrderDetail = new ServiceProductOrderDetail();
foreach ($allOrders as $order) {
$booking = array();
$booking['id_booking'] = $order['id_order'];
$objOrder = new Order($booking['id_booking']);
$bookingDetails = $objOrder->getProductsDetail();
$objOrderState = new OrderState($objOrder->current_state);
$objCustomer = new Customer($objOrder->id_customer);
if($customerAddress = $this->getCustomerAddressByIdCutomer($objOrder->id_customer)) {
if(!Validate::isLoadedObject($customerState = new State($customerAddress['id_state']))) {
$customerState = array();
}
} else {
$customerAddress = array();
}
$booking['id_property'] = (int)$params['id_property'];
if(isset($objOrder->id_channel_manager_booking) && $objOrder->id_channel_manager_booking) {
$booking['id_channel_manager_booking'] = $objOrder->id_channel_manager_booking;
}
$booking['currency'] = Currency::getCurrency($objOrder->id_currency)['iso_code'];
$booking['booking_status'] = $this->getBookingStatus($objOrderState, $objOrder);
$booking['payment_status'] = '';
$booking['source'] = $objOrder->source;
$booking['remark'] = '';
$booking['number_of_rooms'] = '';
$booking['booking_date'] = $objOrder->date_add;
$booking['modification_date'] = $objOrder->date_upd;
$booking['guest_detail']['firstname'] = $objCustomer->firstname;
$booking['guest_detail']['lastname'] = $objCustomer->lastname;
$booking['guest_detail']['email'] = $objCustomer->email;
$booking['guest_detail']['phone'] = $objCustomer->phone;
$booking['guest_detail']['address'] = isset($customerAddress['address1']) ? $customerAddress['address1'] : '';
$booking['guest_detail']['state'] = isset($customerState->name)?$customerState->name : '';
$booking['guest_detail']['city'] = isset($customerAddress['city']) ? $customerAddress['city'] : '';
$booking['guest_detail']['zip'] = isset($customerAddress['postcode']) ? $customerAddress['postcode'] : '';
$booking['guest_detail']['state_code'] = isset($customerState->iso_code)?$customerState->iso_code: '';
$booking['guest_detail']['country_code'] = isset($customerState->id_country) ? Country::getIsoById($customerState->id_country) : '';
$booking['price_details']['total_price_with_tax'] = $objOrder->total_paid_tax_incl;
$booking['price_details']['total_tax'] = Tools::ps_round(($objOrder->total_paid_tax_incl - $objOrder->total_paid_tax_excl), _PS_PRICE_COMPUTE_PRECISION_);
$booking['price_details']['total_paid'] = $objOrder->total_paid_real;
$roomTypes = array();
foreach ($bookingDetails as $bookingRow) {
if ($bookingRow['is_booking_product']) {
foreach ($objHotelBookingDetail->getBookedRoomsByIdOrderDetail($bookingRow['id_order_detail'], $bookingRow['product_id']) as $value) {
$productId = $bookingRow['product_id'] ?? '';
$dateFrom = !empty($value['date_from']) ? date('Y-m-d', strtotime($value['date_from'])) : '';
$dateTo = !empty($value['date_to']) ? date('Y-m-d', strtotime($value['date_to'])) : '';
$key = $productId . '_' . $dateFrom . '_' . $dateTo;
if (!isset($roomTypes[$key])) {
$roomTypes[$key] = array();
}
if (!isset($roomTypes[$key]['rooms']) && isset($roomTypes[$key]['rooms'])) {
$roomTypes[$key]['rooms'] = array();
}
if (!isset($roomTypes[$key]['total_price_with_tax'])) {
$roomTypes[$key]['total_price_with_tax'] = $value['total_price_tax_incl'];
} else {
$roomTypes[$key]['total_price_with_tax'] += $value['total_price_tax_incl'];
}
if (!isset($roomTypes[$key]['total_tax'])) {
$roomTypes[$key]['total_tax'] = $value['total_price_tax_excl'];
} else {
$roomTypes[$key]['total_tax'] += $value['total_price_tax_excl'];
}
$roomTypes[$key]['id_room_type'] = $bookingRow['product_id'];
$roomTypes[$key]['id_rate_plan'] = '';
$roomTypes[$key]['check_in_date'] = $value['date_from'];
$roomTypes[$key]['check_out_date'] = $value['date_to'];
$data = array();
$data['total_price_with_tax'] = $value['total_price_tax_incl'];
$data['total_tax'] = $value['total_price_tax_excl'];
$data['occupancy'] = array(
'adults' => $value['adults'],
'children' => $value['children'],
'children_ages' => $value['child_ages'],
'infants' => '',
);
$additionalServices = $objServiceProductOrderDetail->getroomTypeServiceProducts(
$objOrder->id,
0,
0,
0,
0,
0,
0,
0,
0,
null,
0,
0,
$value['id']
);
$serviceData = array();
$additionalServices = array_shift($additionalServices);
if (is_array($additionalServices) && count($additionalServices) && isset($additionalServices['additional_services'])) {
foreach ($additionalServices['additional_services'] as $service) {
$serviceItem = array();
$serviceItem['name'] = $service['name'];
$serviceItem['quantity'] = $service['quantity'];
$serviceItem['total_price_with_tax'] = $service['total_price_tax_incl'];
$serviceItem['total_tax'] = ($service['total_price_tax_incl'] - $service['total_price_tax_excl']);
if (WebserviceSpecificManagementCmApi::isPerNightPriceCalculationMethod($service['price_calculation_method'])) {
$serviceItem['price_mode'] = 'per_night';
} else {
$serviceItem['price_mode'] = 'per_stay';
}
$serviceItem['price_per_unit'] = $serviceItem['total_price_with_tax']-$serviceItem['total_tax'];
$serviceItem['taxes'] = $service['product_tax'];
$serviceData[] = $serviceItem;
}
}
$data['services'] = $serviceData;
$roomTypes[$key]['rooms'][] = $data;
// initially set number_of_rooms as 0
if (!isset($roomTypes[$key]['number_of_rooms'])) {
$roomTypes[$key]['number_of_rooms'] = 0;
}
// if a room is refunded or cancelled then the count will not be sent to the api response
// as we can not change the status room type booking wise for the API response
// Refunds come from completed order returns; the old is_refunded column
// (shops upgraded from 1.7) is no longer written, so it only adds older rooms.
$isRefunded = !empty($value['is_refunded'])
|| (method_exists('OrderReturn', 'hasCompletelyRefundedBooking')
&& (new OrderReturn())->hasCompletelyRefundedBooking($value['id']));
if (!$isRefunded
&& isset($roomTypes[$key]['number_of_rooms'])
) {
$roomTypes[$key]['number_of_rooms'] += 1;
}
}
}
}
$booking['room_bookings'] = array_values($roomTypes);
$bookings[] = $booking;
}
}
return json_encode(array(
'success' => true,
'data' => $bookings
));
}
private function getAllOrdersId($params = [])
{
if (count($params)) {
$sql = 'SELECT distinct o.`id_order` FROM `'._DB_PREFIX_.'orders` o LEFT JOIN `'._DB_PREFIX_.'htl_booking_detail` hbd ON o.`id_order` = hbd.`id_order` WHERE true ';
if (isset($params['id_property']) && $params['id_property']) {
$sql .= " AND hbd.`id_hotel` = ".(int)$params['id_property'];
}
if (isset($params['check_in']) && $params['check_in']) {
if (isset($params['check_in']['gte']) && $params['check_in']['gte']) {
$sql .= " AND hbd.`date_to` >= '".pSQL($params['check_in']['gte'])."'";
}
}
if (isset($params['check_in']) && $params['check_in']) {
if (isset($params['check_in']['gt']) && $params['check_in']['gt']) {
$sql .= " AND hbd.`date_to` > '".pSQL($params['check_in']['gt'])."'";
}
}
if (isset($params['check_in']) && $params['check_in']) {
if (isset($params['check_in']['lte']) && $params['check_in']['lte']) {
$sql .= " AND hbd.`date_to` <= '".pSQL($params['check_in']['lte'])."'";
}
}
if (isset($params['check_in']) && $params['check_in']) {
if (isset($params['check_in']['lt']) && $params['check_in']['lt']) {
$sql .= " AND hbd.`date_to` < '".pSQL($params['check_in']['lt'])."'";
}
}
if (isset($params['check_out']) && $params['check_out']) {
if (isset($params['check_out']['gte']) && $params['check_out']['gte']) {
$sql .= " AND hbd.`date_to` >= '".pSQL($params['check_out']['gte'])."'";
}
}
if (isset($params['check_out']) && $params['check_out']) {
if (isset($params['check_out']['gt']) && $params['check_out']['gt']) {
$sql .= " AND hbd.`date_to` > '".pSQL($params['check_out']['gt'])."'";
}
}
if (isset($params['check_out']) && $params['check_out']) {
if (isset($params['check_out']['lte']) && $params['check_out']['lte']) {
$sql .= " AND hbd.`date_to` <= '".pSQL($params['check_out']['lte'])."'";
}
}
if (isset($params['check_out']) && $params['check_out']) {
if (isset($params['check_out']['lt']) && $params['check_out']['lt']) {
$sql .= " AND hbd.`date_to` < '".pSQL($params['check_out']['lt'])."'";
}
}
if (isset($params['date_updated']) && $params['date_updated']) {
if (isset($params['date_updated']['gte']) && $params['date_updated']['gte']) {
$sql .= " AND o.`date_upd` >= '".pSQL($params['date_updated']['gte'])."'";
}
}
if (isset($params['date_updated']) && $params['date_updated']) {
if (isset($params['date_updated']['gt']) && $params['date_updated']['gt']) {
$sql .= " AND o.`date_upd` > '".pSQL($params['date_updated']['gt'])."'";
}
}
if (isset($params['date_updated']) && $params['date_updated']) {
if (isset($params['date_updated']['lte']) && $params['date_updated']['lte']) {
$sql .= " AND o.`date_upd` <= '".pSQL($params['date_updated']['lte'])."'";
}
}
if (isset($params['date_updated']) && $params['date_updated']) {
if (isset($params['date_updated']['lt']) && $params['date_updated']['lt']) {
$sql .= " AND o.`date_upd` < '".pSQL($params['date_updated']['lt'])."'";
}
}
return Db::getInstance()->executeS($sql);
} else {
return Db::getInstance()->executeS('SELECT `id_order` FROM `'._DB_PREFIX_.'orders` WHERE 1');
}
}
private function getCustomerAddressByIdCutomer($idCustomer = 0)
{
return Db::getInstance()->getRow(
'SELECT * FROM `'._DB_PREFIX_.'address` WHERE id_customer = '.(int)$idCustomer
);
}
private function getBookingStatus($objOrderState, $objOrder)
{
$dateAdd = new DateTime($objOrder->date_add);
$dateUpd = new DateTime($objOrder->date_upd);
$isMoreThanOneMinute = (abs($dateAdd->getTimestamp() - $dateUpd->getTimestamp()) > 60);
if (($objOrderState->id == Configuration::get('PS_OS_CANCELED')) || ($objOrderState->id == Configuration::get('PS_OS_REFUND'))) {
return 'cancelled';
} elseif ($isMoreThanOneMinute) {
return 'modified';
} else {
return 'new';
}
}
}

View File

@@ -0,0 +1,73 @@
<?php
/**
* NOTICE OF LICENSE
*
* This source file is subject to the Academic Free License (AFL 3.0)
* that is bundled with this package in the file LICENSE.md
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/afl-3.0.php
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to support@qloapps.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade this module to a newer
* versions in the future. If you wish to customize this module for your needs
* please refer to https://store.webkul.com/customisation-guidelines for more information.
*
* @author Webkul IN
* @copyright Since 2010 Webkul
* @license https://opensource.org/licenses/afl-3.0.php Academic Free License 3.0
*/
class QcmcCMConnectorDb
{
/**
* Prepare the SQL statement and create the Universal payment table.
*
* @return bool
*/
public function createTables()
{
$sqls = array(
"CREATE TABLE IF NOT EXISTS "._DB_PREFIX_."ari_updates (
`id_ari_updates` INT AUTO_INCREMENT,
`id_hotel` INT NOT NULL,
`id_room_type` INT NOT NULL,
`date` DATETIME NOT NULL,
`ari_data` LONGTEXT NULL,
`status` TINYINT(1) NOT NULL DEFAULT 0,
`date_add` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`date_upd` DATETIME NULL,
PRIMARY KEY (`id_ari_updates`)
)ENGINE=" . _MYSQL_ENGINE_ ." DEFAULT CHARSET=utf8",
"ALTER TABLE `"._DB_PREFIX_."orders`
ADD COLUMN `id_channel_manager_booking` VARCHAR(255) NULL",
"ALTER TABLE `" . _DB_PREFIX_ . "htl_booking_detail` ADD COLUMN `rate_plan_name` VARCHAR(255) DEFAULT NULL"
);
foreach ($sqls as $sql) {
if (!Db::getInstance()->execute(trim($sql))) {
return false;
}
}
return true;
}
public function dropTables()
{
$sqls = array(
'DROP TABLE IF EXISTS`'._DB_PREFIX_.'ari_updates`',
"ALTER TABLE `"._DB_PREFIX_."orders` DROP COLUMN `id_channel_manager_booking`",
"ALTER TABLE `" . _DB_PREFIX_ . "htl_booking_detail` DROP COLUMN `rate_plan_name`"
);
foreach ($sqls as $sql) {
if (!Db::getInstance()->execute(trim($sql))) {
return false;
}
}
return true;
}
}

View File

@@ -0,0 +1,174 @@
<?php
/**
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License version 3.0
* that is bundled with this package in the file LICENSE.md
* It is also available through the world-wide-web at this URL:
* https://opensource.org/license/osl-3-0-php
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to support@qloapps.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade this module to a newer
* versions in the future. If you wish to customize this module for your needs
* please refer to https://store.webkul.com/customisation-guidelines for more information.
*
* @author Webkul IN
* @copyright Since 2010 Webkul
* @license https://opensource.org/license/osl-3-0-php Open Software License version 3.0
*/
class QcmcChannelManagerApiService
{
private $logFile;
private $logDir;
private $baseUrl;
private $curlConfig = [];
public function __construct()
{
$moduleDir = _PS_MODULE_DIR_ . 'qlocmconnector/';
$this->logDir = $moduleDir . 'logs';
$this->logFile = $this->logDir . '/' . date('Y-m-d') . '.log';
$this->baseUrl = 'https://channels.qloapps.com/';
}
private function setMethod($method = 'POST')
{
$this->curlConfig['method'] = strtoupper($method);
}
private function setEndpoint($url)
{
$this->curlConfig['url'] = $this->baseUrl.$url;
}
private function addHeaders(array $headers)
{
$this->curlConfig['headers'] = array_merge(
$this->curlConfig['headers'] ?? [],
$headers
);
}
private function setBody($body)
{
$this->curlConfig['body'] = $body;
}
private function getResult()
{
$ch = curl_init($this->curlConfig['url']);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $this->curlConfig['method']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
if (!empty($this->curlConfig['body'])) {
curl_setopt($ch, CURLOPT_POSTFIELDS, $this->curlConfig['body']);
}
if (!empty($this->curlConfig['headers'])) {
curl_setopt($ch, CURLOPT_HTTPHEADER, $this->curlConfig['headers']);
}
if ($result = curl_exec($ch)) {
$result = json_decode($result, true);
} else {
$result = [];
}
$result['http_code'] = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if (!is_dir($this->logDir)) {
@mkdir($this->logDir, 0755, true);
}
$data = date('[Y-m-d H:i e] ') . PHP_EOL;
$data .= 'Channel Manager API called: ' . $this->curlConfig['url'] . PHP_EOL;
if (isset($this->curlConfig['body']) && $this->curlConfig['body']) {
$data .= 'Request : ' . $this->curlConfig['body'] . PHP_EOL;
}
$data .= 'Response : ' . json_encode($result) . PHP_EOL;
$data .= '___________________________________________';
$data .= PHP_EOL . PHP_EOL;
error_log($data, 3, $this->logFile);
$this->curlConfig = [];
return $result;
}
public function pushARI($payload)
{
$this->setEndpoint('api/v1/push_ari');
$this->setMethod('POST');
$jsonBody = json_encode($payload, JSON_UNESCAPED_SLASHES);
if ($jsonBody === false) {
return false;
}
$accessToken = Configuration::get('QCMC_CM_ACCESS_TOKEN');
$this->addHeaders([
'Content-Type: application/json',
'X-Requested-With: XMLHttpRequest',
'X-PMS-REQUEST: true',
'Authorization: Bearer ' . $accessToken,
]);
$this->setBody($jsonBody);
$result = $this->getResult();
return $result;
}
public function getAccessToken($clientId, $clientSecret)
{
$this->setEndpoint('oauth/token');
$this->setMethod('POST');
$this->addHeaders([
'Content-Type: application/x-www-form-urlencoded',
]);
$postFields = http_build_query([
'grant_type' => 'client_credentials',
'client_id' => $clientId,
'client_secret' => $clientSecret,
]);
$this->setBody($postFields);
$response = $this->getResult();
return $response;
}
public function updateAccessToken($accessToken)
{
if(Configuration::updateValue('QCMC_CM_ACCESS_TOKEN', $accessToken)) {
return true;
}
return false;
}
public static function isAllowedRemoteAddr($remoteAddr)
{
$alwRemoteAddr = array(
'54.216.64.42',
'52.208.202.101',
);
return in_array($remoteAddr, $alwRemoteAddr);
}
}

View File

@@ -0,0 +1,70 @@
<?php
/**
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License version 3.0
* that is bundled with this package in the file LICENSE.md
* It is also available through the world-wide-web at this URL:
* https://opensource.org/license/osl-3-0-php
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to support@qloapps.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade this module to a newer
* versions in the future. If you wish to customize this module for your needs
* please refer to https://store.webkul.com/customisation-guidelines for more information.
*
* @author Webkul IN
* @copyright Since 2010 Webkul
* @license https://opensource.org/license/osl-3-0-php Open Software License version 3.0
*/
if (!defined('_PS_VERSION_')) {
exit;
}
class QcmcChannelManagerOrder
{
/**
* An order is treated as channel-manager-sourced if id_channel_manager_booking is set,
* OR if source isn't the shop's own domain (covers CM bookings where the channel manager
* didn't send id_channel_manager_booking in the booking_notification request).
*/
const CM_WHERE = " ( `id_channel_manager_booking` IS NOT NULL AND `id_channel_manager_booking` != '' )
OR `source` != '%s' ";
public static function hasChannelManagerBookings()
{
// Db::getValue() -> Db::getRow() already appends its own "LIMIT 1" — don't add one here.
$sql = 'SELECT 1 FROM `'._DB_PREFIX_.'orders` WHERE '.self::getWhere();
return (bool) Db::getInstance()->getValue($sql);
}
public static function getLastChannelManagerBookingDate()
{
$sql = 'SELECT `date_add` FROM `'._DB_PREFIX_.'orders` WHERE '.self::getWhere().'
ORDER BY `date_add` DESC';
return Db::getInstance()->getValue($sql);
}
/**
* Connected if a booking has come from the channel manager, or the channel manager
* API credentials (client id and client secret) are saved.
*
* @return bool
*/
public static function isChannelManagerConnected()
{
return self::hasChannelManagerBookings()
|| (Configuration::get('QCMC_CM_CLIENT_ID') && Configuration::get('QCMC_CM_CLIENT_SECRET'));
}
protected static function getWhere()
{
return sprintf(self::CM_WHERE, pSQL(Configuration::get('PS_SHOP_DOMAIN')));
}
}

View File

@@ -0,0 +1,60 @@
<?php
/**
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License version 3.0
* that is bundled with this package in the file LICENSE.md
* It is also available through the world-wide-web at this URL:
* https://opensource.org/license/osl-3-0-php
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to support@qloapps.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade this module to a newer
* versions in the future. If you wish to customize this module for your needs
* please refer to https://store.webkul.com/customisation-guidelines for more information.
*
* @author Webkul IN
* @copyright Since 2010 Webkul
* @license https://opensource.org/license/osl-3-0-php Open Software License version 3.0
*/
class QcmcProperty extends QmkWebService
{
public function getHotelList()
{
$objHotelBranchInfo = new HotelBranchInformation();
$allHotels = $objHotelBranchInfo->getAllHotels();
$hotels = array();
foreach ($allHotels as $hotel) {
$response = array();
$response['id'] = $hotel['id'];
$response['id_category'] = $hotel['id_category'];
$objHotelBranch = new HotelBranchInformation($response['id'], $this->context->language->id);
$response['name'] = $objHotelBranch->hotel_name;
$response['property_type'] = $this->objModule->l('hotel', 'QcmcChannelManagerProperty');
$response['description'] = $objHotelBranch->description;
$response['email'] = $objHotelBranch->email;
$response['phone'] = $objHotelBranch->phone;
$response['currency'] = Currency::getCurrency($this->context->currency->id)['iso_code'];
$response['country_code'] = Country::getIsoById($objHotelBranch->id_country);
$response['state'] = State::getNameById($objHotelBranch->id_state);
$response['city'] = $objHotelBranch->city;
$response['address1'] = $objHotelBranch->address;
$response['address2'] = '';
$response['zip_code'] = $objHotelBranch->zipcode;
$response['latitude'] = $objHotelBranch->latitude;
$response['longitude'] = $objHotelBranch->longitude;
$response['timezone'] = Configuration::get('PS_TIMEZONE');
$hotels[] = $response;
}
return json_encode(array(
'success' => true,
'data' => $hotels
));
}
}

View File

@@ -21,5 +21,14 @@
* @license https://opensource.org/license/osl-3-0-php Open Software License version 3.0
*/
require_once _PS_MODULE_DIR_.'qlochannelmanagerconnector/classes/QcmcChannelManagerConnectorDb.php';
require_once _PS_MODULE_DIR_.'qlochannelmanagerconnector/classes/QcmcChannelManagerBooking.php';
include_once "QcmcWebService.php";
include_once "QcmcBooking.php";
include_once "QcmcProperty.php";
include_once "QcmcRoomType.php";
include_once "QcmcChannelManagerApiService.php";
include_once "QcmcCMConnectorDb.php";
include_once "QcmcAri.php";
include_once "QcmcAriUpdates.php";
include_once "QcmcChannelManagerOrder.php";
include_once "WebserviceSpecificManagementCmApi.php";
include_once "QcmcSourceHelper.php";

View File

@@ -0,0 +1,61 @@
<?php
/**
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License version 3.0
* that is bundled with this package in the file LICENSE.md
* It is also available through the world-wide-web at this URL:
* https://opensource.org/license/osl-3-0-php
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to support@qloapps.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade this module to a newer
* versions in the future. If you wish to customize this module for your needs
* please refer to https://store.webkul.com/customisation-guidelines for more information.
*
* @author Webkul IN
* @copyright Since 2010 Webkul
* @license https://opensource.org/license/osl-3-0-php Open Software License version 3.0
*/
class QcmcRoomType extends QmkWebService
{
public function getRoomTypeList($idProperty)
{
$objHotelBranch = new HotelBranchInformation($idProperty, $this->context->language->id);
if ($objHotelBranch->id) {
$objHotelRoomType = new HotelRoomType();
$allRoomTypes = $objHotelRoomType->getRoomTypeByHotelId($idProperty, $this->context->language->id);
$roomTypes = array();
foreach ($allRoomTypes as $room) {
$response = array();
$response['id'] = $room['id_product'];
$objHotelRoomType = new HotelRoomType($response['id'], $this->context->language->id);
$objHotelRoomInformation = new HotelRoomInformation();
$response['id_product'] = $room['id_product'];
$response['name'] = $room['room_type'];
$response['total_rooms'] = count($objHotelRoomInformation->getHotelRoomsInfo($idProperty, $response['id']));
$response['base_occupancy'] = $objHotelRoomType->adults;
$response['max_adults'] = $objHotelRoomType->max_adults;
$response['max_children'] = $objHotelRoomType->max_children;
$response['max_infants'] = $objHotelRoomType->max_children;
$roomTypes[] = $response;
}
return json_encode(array(
'success' => true,
'data' => $roomTypes
));
} else {
return json_encode(array(
'success' => false,
'data' => [],
'message' => $this->objModule->l('Property not found.', 'QcmcChannelManagerRoomType'),
));
}
}
}

View File

@@ -0,0 +1,181 @@
<?php
/**
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License version 3.0
* that is bundled with this package in the file LICENSE.md
* It is also available through the world-wide-web at this URL:
* https://opensource.org/license/osl-3-0-php
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to support@qloapps.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade this module to a newer
* versions in the future. If you wish to customize this module for your needs
* please refer to https://store.webkul.com/customisation-guidelines for more information.
*
* @author Webkul IN
* @copyright Since 2010 Webkul
* @license https://opensource.org/license/osl-3-0-php Open Software License version 3.0
*/
if (!defined('_PS_VERSION_')) {
exit;
}
class QcmcSourceHelper
{
private $bookingSourceMapping = [
'booking_com' => [
'code' => 'BOOKING_COM',
'businessSourceCode' => 'OTA',
],
'expedia' => [
'code' => 'EXPEDIA',
'businessSourceCode' => 'OTA',
],
'goibibo_and_makemytrip' => [
'code' => 'GOMMT',
'businessSourceCode' => 'OTA',
],
'agoda' => [
'code' => 'AGODA',
'businessSourceCode' => 'OTA',
],
'bakuun' => [
'code' => 'BAKUUN',
'businessSourceCode' => 'OTA',
],
'despegar' => [
'code' => 'DESPEGAR',
'businessSourceCode' => 'OTA',
],
'yatra' => [
'code' => 'YATRA',
'businessSourceCode' => 'OTA',
],
'airbnb' => [
'code' => 'AIRBNB',
'businessSourceCode' => 'OTA',
],
'hostelworld' => [
'code' => 'HOSTELWORLD',
'businessSourceCode' => 'OTA',
],
'hyperguest' => [
'code' => 'HYPERGUEST',
'businessSourceCode' => 'OTA',
],
'trip_com_ctrip' => [
'code' => 'TRIP_COM',
'businessSourceCode' => 'OTA',
],
'hotelbeds' => [
'code' => 'HOTELBEDS',
'businessSourceCode' => 'OTA',
],
];
public function __construct()
{
if (!self::coreClassesExist()) {
throw new Exception('BusinessSource and Source classes are required for QcmcSourceHelper to work.');
}
}
public static function coreClassesExist()
{
return class_exists('BusinessSource') && class_exists('Source');
}
/**
* Resolves the id_source for a channel-manager booking/business source name,
* creating the BusinessSource/Source rows on first use if they don't exist yet.
*/
public function getBookingSourceId($cmBookingSourceName, $cmBusinessSourceName = null)
{
$key = self::formatSourceKey($cmBookingSourceName);
$bookingSourceCode = isset($this->bookingSourceMapping[$key])
? $this->bookingSourceMapping[$key]['code']
: $this->formatSourceCode($cmBookingSourceName);
if ($idSource = Source::getIdByCode($bookingSourceCode)) {
return $idSource;
}
$businessSourceCode = $this->formatSourceCode($cmBusinessSourceName);
$idBusinessSource = $this->getBusinessSourceIdByCode($businessSourceCode)
?: $this->createBusinessSource($businessSourceCode, $cmBusinessSourceName);
return $this->createSource($bookingSourceCode, $idBusinessSource, $cmBookingSourceName);
}
private function getBusinessSourceIdByCode($code)
{
return (int)Db::getInstance()->getValue(
'SELECT `id_business_source` FROM `'._DB_PREFIX_.'business_source`
WHERE `code` = \''.pSQL($code).'\' AND `deleted` = 0'
);
}
private function createBusinessSource($code, $sourceName)
{
$name = array();
foreach (Language::getLanguages(false) as $lang) {
$name[(int)$lang['id_lang']] = ucwords($sourceName);
}
$objBusinessSource = new BusinessSource();
$objBusinessSource->code = $code;
$objBusinessSource->name = $name;
$objBusinessSource->position = BusinessSource::getHigherPosition() + 1;
$objBusinessSource->unremovable = 0;
$objBusinessSource->active = 1;
$objBusinessSource->deleted = 0;
$objBusinessSource->save();
return (int)$objBusinessSource->id;
}
private function createSource($code, $idBusinessSource, $sourceName)
{
$name = array();
foreach (Language::getLanguages(false) as $lang) {
$name[(int)$lang['id_lang']] = ucwords($sourceName);
}
$objSource = new Source();
$objSource->code = $code;
$objSource->id_business_source = $idBusinessSource;
$objSource->name = $name;
$objSource->position = Source::getHigherPosition($idBusinessSource) + 1;
$objSource->unremovable = 0;
$objSource->active = 1;
$objSource->deleted = 0;
$objSource->save();
return (int)$objSource->id;
}
public function getBusinessSourceInfo($cmBookingSourceName)
{
$key = self::formatSourceKey($cmBookingSourceName);
return $this->bookingSourceMapping[$key] ?? null;
}
private static function formatSourceKey($sourceName)
{
return trim(preg_replace('/[^a-z0-9]+/', '_', strtolower($sourceName)), '_');
}
private static function formatSourceCode($sourceName)
{
return trim(preg_replace('/[^A-Z0-9]+/', '_', strtoupper($sourceName)), '_');
}
}

View File

@@ -0,0 +1,310 @@
<?php
/**
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License version 3.0
* that is bundled with this package in the file LICENSE.md
* It is also available through the world-wide-web at this URL:
* https://opensource.org/license/osl-3-0-php
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to support@qloapps.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade this module to a newer
* versions in the future. If you wish to customize this module for your needs
* please refer to https://store.webkul.com/customisation-guidelines for more information.
*
* @author Webkul IN
* @copyright Since 2010 Webkul
* @license https://opensource.org/license/osl-3-0-php Open Software License version 3.0
*/
class QmkWebService
{
protected $objOutput;
protected $output;
protected $wsObject;
protected $objModule;
protected $http;
protected $width;
protected $jsonOutput;
protected $outputFormat;
public $parent;
public $context;
public function __construct($objOutput = null, $wsObject = null, $output = null, $outputFormat = 'xml')
{
$this->objModule = Module::getInstanceByName('qlocmconnector');
$this->objOutput = $objOutput;
$this->wsObject = $wsObject;
$this->output = $output;
$this->outputFormat = $outputFormat;
$this->setContext();
}
public function setContext()
{
$this->context = Context::getContext();
$this->context->controller = new FrontController();
$unidentifiedGroupCustomer = new Customer();
if (Group::isFeatureActive()) {
$unidentifiedGroupCustomer->id_default_group = (int) Configuration::get('PS_UNIDENTIFIED_GROUP');
}
// Set Customer
$this->context->customer = $unidentifiedGroupCustomer;
if (isset($this->wsObject->urlFragments['id_customer'])) {
$customer = new Customer($this->wsObject->urlFragments['id_customer']);
if (Validate::isLoadedObject($customer) && $customer->active) {
$this->context->customer = $customer;
$this->context->customer->logged = 1; // Assuming that when getting id_customer, customer is logged in.
}
} else {
if (isset($this->wsObject->urlSegment[2]) && $this->wsObject->urlSegment[2] == 'payment') {
if (isset($this->wsObject->urlSegment[3]) && $this->wsObject->urlSegment[3]) {
$this->context->customer = new Customer($this->wsObject->urlSegment[3]);
} else {
$urlObjCart = new Cart($this->wsObject->urlFragments['id_cart']);
$this->context->customer = new Customer($urlObjCart->id_customer);
}
}
}
// Set Cart
if (isset($this->wsObject->urlFragments['id_cart'])) {
$this->context->cart = new Cart($this->wsObject->urlFragments['id_cart']);
if (isset($this->wsObject->urlFragments['id_customer']) && !$this->wsObject->urlFragments['id_customer']) {
$this->context->customer = new Customer($this->context->cart->id_customer);
}
}
// Set language
$language = new Language((int) Configuration::get('PS_LANG_DEFAULT'));
if (isset($this->wsObject->urlFragments['id_lang'])) {
$requestedlanguage = new Language($this->wsObject->urlFragments['id_lang']);
if (Validate::isLoadedObject($requestedlanguage) && $requestedlanguage->active) {
$language = $requestedlanguage;
}
}
$this->context->language = $language;
// Set Currency
$currency = new Currency((int) Configuration::get('PS_CURRENCY_DEFAULT'));
if (isset($this->wsObject->urlFragments['id_currency'])) {
$requestedCurrency = new Currency($this->wsObject->urlFragments['id_currency']);
if (Validate::isLoadedObject($requestedCurrency)
&& $requestedCurrency->active
&& !$requestedCurrency->deleted
) {
$currency = $requestedCurrency;
}
}
$this->context->currency = $currency;
if (isset($this->wsObject->urlFragments['id_guest'])
&& $this->wsObject->urlFragments['id_guest']
) {
$objGuest = new Guest((int) $this->wsObject->urlFragments['id_guest']);
$this->context->cookie->id_guest = $objGuest->id;
}
if (Validate::isLoadedObject($this->context->customer) && $this->context->customer->active) {
$this->context->customer->logged = 1; // hook exec lisy cache build on the basis of groups
if ($customerGuestId = Guest::getFromCustomer((int) $this->context->customer->id)) {
$this->context->customer->id_guest = $customerGuestId;
}
$this->context->customer->save();
} else {
$this->context->customer = $unidentifiedGroupCustomer;
if (!isset($this->context->cart)) {
$this->context->cart = new Cart();
}
}
if (Validate::isLoadedObject($this->context->cart)) {
$this->context->cart->id_currency = $this->context->currency->id;
$this->context->cart->id_lang = $this->context->language->id;
if ($this->context->customer->id) {
$this->context->cart->id_customer = $this->context->customer->id;
if ($customerAddressId = Address::getFirstCustomerAddressId($this->context->customer->id)) {
$this->context->cart->id_address_invoice = $customerAddressId;
}
}
$this->context->cart->save();
}
// to update product $_taxCalculationMethod according to context
Product::getTaxCalculationMethod((int) $this->context->customer->id);
}
public function renderOutputUsingArray($response, $keyToIgnore = array(), $parentKey = '', $useEmpty = false)
{
$output = '';
foreach ($response as $key => $res) {
if (in_array($key, $keyToIgnore) && $key) {
continue;
}
$currentKey = $key;
if (gettype($key) == 'integer' || (int) $key) {
$key = $parentKey;
}
if (is_array($res) && count($res)) {
$output .= $this->renderHeader($key);
$output .= $this->renderOutputUsingArray($res, $keyToIgnore, $key, $useEmpty);
$output .= $this->renderFooter($key);
} else {
if (empty($res) && !$useEmpty && !in_array($key, array('prefix', 'suffix'))) {
$res = 0;
}
if (isset($this->wsObject->urlFragments['schema'])) {
if ($this->wsObject->urlFragments['schema'] == 'blank' || $this->wsObject->urlFragments['schema'] == 'synopsis') {
$res = null;
} else {
throw new WebserviceException(
$this->objModule->l('Please select a schema of type \'synopsis\' to get the whole schema informations (which fields are required, which kind of content...) or \'blank\' to get an empty schema to fill before using POST request.', 'QmkWebService'),
array(100, 400)
);
}
}
$output .= $this->objOutput->objectRender->renderField(
array(
'sqlId' => $key,
'value' => $res
)
);
}
}
return $output;
}
public function renderHeader($parentKey = '')
{
if ($parentKey != '') {
$this->parent[] = $parentKey;
return $this->objOutput->objectRender->renderNodeHeader($parentKey,
array('objectNodeName' => $parentKey)
);
}
return '';
}
public function renderFooter($parentKey = '', $multiple = false)
{
if ($parentKey != '') {
array_pop($this->parent);
return $this->objOutput->objectRender->renderNodeFooter($parentKey,
array('objectNodeName' => $parentKey)
);
}
return '';
}
public function renderResponse($message, $status =null, $args = array(), $parentKey ='', $errors = array(), $root = 'response')
{
$jsonOutput = array();
$output = '';
$output .= $this->renderHeader($root);
if ($status !== null) {
$args['status'] = (bool) $status;
} else {
$args['status'] = false;
}
$args['message'] = $message;
$output .= $this->renderOutputUsingArray($args, array(), $parentKey);
$jsonOutput[$root] = $args;
if (isset($errors) && count($errors) > 0) {
$output .= $this->renderHeader('errors');
$output .= $this->renderOutputUsingArray($errors, array(), 'error');
$jsonOutput[$root]['errors'] = $errors['error'];
$output .= $this->renderFooter('errors');
}
$output .= $this->renderFooter($root);
if ($this->outputFormat == 'json') {
return json_encode($jsonOutput);
}
return $output;
}
public function resizeImage($url, $name)
{
// @todo: add code for the resized image here... If needed.
// create new url
$newUrl = $url.$name;
return $this->context->link->getMediaLink($newUrl);
}
public function renderFormFields($fields, $parentKey ='', $parentKeysToIgnore = array())
{
$output = '';
foreach ($fields as $key => $field) {
$currentKey = $key;
if (gettype($key) == 'integer' || (int) $key) {
$key = $parentKey;
}
if (is_array($field) && count($field)) {
$isParent = false;
if ($key != end($this->parent)) {
$isParent = true;
if (!in_array($key, $parentKeysToIgnore)) {
$output .= $this->renderHeader($key);
}
}
$output .= $this->renderFormFields($field, $key, $parentKeysToIgnore);
if ($isParent
|| $key == 'option'
|| $key == 'new_option'
|| $key == 'field'
) {
if ($key == end($this->parent)) {
if (!in_array($key, $parentKeysToIgnore)) {
$output .= $this->renderFooter($key);
}
}
}
} else {
if (isset($this->wsObject->urlFragments['schema'])) {
if ($this->wsObject->urlFragments['schema'] == 'blank' || $this->wsObject->urlFragments['schema'] == 'synopsis') {
$field = null;
} else {
throw new WebserviceException(
$this->objModule->l('Please select a schema of type \'synopsis\' to get the whole schema informations (which fields are required, which kind of content...) or \'blank\' to get an empty schema to fill before using POST request.', 'QmkWebService'),
array(100, 400)
);
}
}
if (is_array($field) && !count($field)) {
$field = '';
}
$output .= $this->objOutput->objectRender->renderField(
array(
'sqlId' => $key,
'value' => $field
)
);
}
}
return $output;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -25,105 +25,110 @@ class AdminQloappsChannelManagerConnectorController extends ModuleAdminControlle
{
public function __construct()
{
$this->table = 'qcmc_channel_manager_booking';
$this->className = 'QcmcChannelManagerBooking';
$this->table = 'order';
$this->className = 'Order';
$this->identifier = 'id_order';
$this->lang = false;
$this->deleted = false;
$this->bootstrap = true;
$this->toolbar_title = $this->l('Channel Manager Bookings');
$this->context = Context::getContext();
$this->identifier = 'id_channel_manager_booking';
parent::__construct();
$this->_join .= ' INNER JOIN `'._DB_PREFIX_.'orders` ord ON (a.id_order = ord.`id_order`)';
$this->_select .= ' ord.`source`, ord.`total_paid`, ord.`total_paid_real`, IF(a.id_order, 1, 0) badge_success';
// 'a' is the orders row itself now (no separate booking table) — an order is CM-sourced
// if id_channel_manager_booking is set, OR its source isn't the shop's own domain (covers
// CM bookings where the channel manager never sent id_channel_manager_booking).
$this->_where = ' AND ( ( a.`id_channel_manager_booking` IS NOT NULL AND a.`id_channel_manager_booking` != \'\' )
OR a.`source` != \''.pSQL(Configuration::get('PS_SHOP_DOMAIN')).'\' ) ';
$this->_orderBy = 'date_add';
$this->_orderWay = 'DESC';
$this->fields_list = array(
'id_order' => array(
'title' => $this->l('Id order'),
'align' => 'center',
'havingFilter' => true,
'align' => 'text-center',
'class' => 'fixed-width-xs',
'filter_key' => 'a!id_order',
'callback' => 'getOrderLink',
),
'source' => array(
'title' => $this->l('Channel'),
'align' => 'center',
'align' => 'text-center',
'filter_key' => 'a!source',
),
'total_paid' => array(
'title' => $this->l('Order total'),
'align' => 'center',
'align' => 'text-center',
'type' => 'range',
'validation' => 'isFloat',
'filter_key' => 'a!total_paid',
'callback' => 'setPriceCurrency',
'badge_success' => true,
),
'total_paid_real' => array(
'title' => $this->l('Recieved amount'),
'align' => 'center',
'align' => 'text-center',
'type' => 'range',
'validation' => 'isFloat',
'filter_key' => 'a!total_paid_real',
'callback' => 'setPriceCurrencyWithBadge',
),
'date_add' => array(
'title' => $this->l('Created on'),
'align' => 'center',
'align' => 'text-center',
'type' => 'datetime',
'filter_key' => 'a!date_add',
),
);
$this->list_no_link = true;
}
public function getOrderLink($id_order, $row)
public function getOrderLink($idOrder, $row)
{
$displayData = '';
if ($id_order) {
$displayData .= '#'.$id_order;
// $displayData .= '<a target="_blank" href="'.$this->context->link->getAdminLink('AdminOrders').'&id_order='.$id_order.
// '&vieworder">#'.$id_order.'</a>';
if ($idOrder) {
$displayData .= '#'.$idOrder;
}
return $displayData;
}
public function setPriceCurrency($value, $row)
{
if (Validate::isLoadedObject($objOrder = new Order($row['id_order']))) {
return Tools::displayPrice($value, (int)$objOrder->id_currency);
}
return Tools::displayPrice($value, (int)$row['id_currency']);
}
public function setPriceCurrencyWithBadge($value, $row)
{
$displayData = '';
if (Validate::isLoadedObject($objOrder = new Order($row['id_order']))) {
$displayData .= '<span class="badge '.(($row['total_paid'] == $row['total_paid_real']) ? 'badge-success' : 'badge-danger').'">';
$displayData .= Tools::displayPrice($value, (int)$objOrder->id_currency);
$displayData .= '</span>';
}
$displayData .= '<span class="badge '.(($row['total_paid'] == $row['total_paid_real']) ? 'badge-success' : 'badge-danger').'">';
$displayData .= Tools::displayPrice($value, (int)$row['id_currency']);
$displayData .= '</span>';
return $displayData;
}
public function displayViewLink($token, $idRow, $name = null)
{
if (Validate::isLoadedObject($objChannelManagerBooking = new QcmcChannelManagerBooking($idRow))) {
return '<a class="btn btn-default" href="'.$this->context->link->getAdminLink('AdminOrders').'&id_order='.$objChannelManagerBooking->id_order.
'&vieworder" title="'.$this->l('view details').'"><i class="icon-search-plus"></i> '.$this->l('View Order Detail').'</a>';
}
return '<a class="btn btn-default" href="'.$this->context->link->getAdminLink('AdminOrders').'&id_order='.(int)$idRow.
'&vieworder" title="'.$this->l('view details').'"><i class="icon-search-plus"></i> '.$this->l('View Order Detail').'</a>';
}
public function renderList()
{
if ($channelManagerBookings = QcmcChannelManagerBooking::getChannelManagerBookings(0, 'DESC')) {
if (QcmcChannelManagerOrder::isChannelManagerConnected()) {
$this->context->smarty->assign(
array (
'icon' => 'icon-list',
'toolbar_title' => 'icon-list',
// As we get the bookings in descending order according to the date_add. So in the 0 index last booking will be found
'last_booking_datetime' => Tools::displayDate($channelManagerBookings[0]['date_add'], null, true),
'last_booking_datetime' => Tools::displayDate(QcmcChannelManagerOrder::getLastChannelManagerBookingDate(), null, true),
'is_cm_connected' => QcmcChannelManagerOrder::isChannelManagerConnected(),
)
);
// because in helper list tpl_vars is given priority and we need different List title than list title
// In HelperList.php assigned: 'title' => array_key_exists('title', $this->tpl_vars) ? $this->tpl_vars['title'] : $this->title
$this->tpl_list_vars['title'] = $this->l('Below is the list of all the bookings created by channel manager.');
$this->tpl_list_vars['title'] = $this->l('Channel Manager Bookings');
unset($this->toolbar_btn['new']);
@@ -135,11 +140,12 @@ class AdminQloappsChannelManagerConnectorController extends ModuleAdminControlle
array (
'module_dir' => _MODULE_DIR_,
'current_datetime' => Tools::displayDate(date('Y-m-d H:i:s'), null, true),
'is_cm_connected' => QcmcChannelManagerOrder::isChannelManagerConnected(),
)
);
$this->content .= $this->context->smarty->fetch(
_PS_MODULE_DIR_.
'qlochannelmanagerconnector/views/templates/admin/qloapps_channel_manager_connector/channel_manager_connect_info.tpl'
'qlocmconnector/views/templates/admin/qloapps_channel_manager_connector/qcmc_channel_manager_connect_info.tpl'
);
}
}
@@ -148,11 +154,10 @@ class AdminQloappsChannelManagerConnectorController extends ModuleAdminControlle
{
parent::setMedia();
if (QcmcChannelManagerBooking::getChannelManagerBookings()) {
$this->addCSS(_MODULE_DIR_.'qlochannelmanagerconnector/views/css/admin/wk_cm_booking_list.css');
$this->addJS(_MODULE_DIR_.'qlochannelmanagerconnector/views/js/admin/wk_cm_booking_list.js');
if (QcmcChannelManagerOrder::isChannelManagerConnected()) {
$this->addJS(_MODULE_DIR_.'qlocmconnector/views/js/admin/qcmc_cm_booking_list.js');
} else {
$this->addCSS(_MODULE_DIR_.'qlochannelmanagerconnector/views/css/admin/wk_cm_info.css');
$this->addCSS(_MODULE_DIR_.'qlocmconnector/views/css/admin/qcmc_cm_info.css');
}
}
}

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1,103 @@
<?php
/**
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License version 3.0
* that is bundled with this package in the file LICENSE.md
* It is also available through the world-wide-web at this URL:
* https://opensource.org/license/osl-3-0-php
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to support@qloapps.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade this module to a newer
* versions in the future. If you wish to customize this module for your needs
* please refer to https://store.webkul.com/customisation-guidelines for more information.
*
* @author Webkul IN
* @copyright Since 2010 Webkul
* @license https://opensource.org/license/osl-3-0-php Open Software License version 3.0
*/
if (!defined('_PS_ADMIN_DIR_')) {
define('_PS_ADMIN_DIR_', getcwd());
}
include(_PS_ADMIN_DIR_.'/../../config/config.inc.php');
require_once _PS_MODULE_DIR_ . 'qlocmconnector/classes/QcmcChannelManagerApiService.php';
$logDir = _PS_MODULE_DIR_ . 'qlocmconnector/logs';
$markerFile = $logDir . '/.last_cleanup';
$logFile = $logDir . '/' . date('Y-m-d') . '.log';
if (!is_dir($logDir)) {
mkdir($logDir, 0755, true);
}
// simple logger
function qcmc_log($message)
{
global $logFile;
$timestamp = date('[Y-m-d H:i e] ');
error_log($timestamp . $message . PHP_EOL, 3, $logFile);
}
$objCMConnector = Module::getInstanceByName('qlocmconnector');
$objChannelManagerApiService = new QcmcChannelManagerApiService();
$objAri = new QcmcAri();
if (!Tools::getValue('token') || (Tools::getValue('token') != $objCMConnector->secure_key)) {
qcmc_log("Failed to get or refresh access token. Aborting cron.");
die('Something went wrong.');
}
try {
$clientId = Configuration::get('QCMC_CM_CLIENT_ID');
$clientSecret = Configuration::get('QCMC_CM_CLIENT_SECRET');
if($clientId && $clientSecret) {
if(Configuration::get('QCMC_CM_ACCESS_TOKEN')) {
if(!$objAri->processAriUpdateData()) {
$result = $objChannelManagerApiService->getAccessToken($clientId, $clientSecret);
if(isset($result['access_token'])) {
if($objChannelManagerApiService->updateAccessToken($result['access_token'])) {
$objAri->processAriUpdateData();
}
} else {
qcmc_log("Error getting access token");
}
}
} else {
$result = $objChannelManagerApiService->getAccessToken($clientId, $clientSecret);
if(isset($result['access_token'])) {
if($objChannelManagerApiService->updateAccessToken($result['access_token'])) {
$objAri->processAriUpdateData();
}
} else {
qcmc_log("Error getting access token");
}
}
}
} catch (Exception $e) {
qcmc_log("Fatal error in cron: " . $e->getMessage());
}
echo "Process Completed successsfully";
if (!file_exists($markerFile) || ((time() - (int)@file_get_contents($markerFile)) >= 86400)) {
foreach (glob($logDir . "/*.log") as $file) {
if (preg_match('/^\d{4}-\d{2}-\d{2}$/', basename($file, '.log'))) {
$fileDate = strtotime(basename($file, '.log'));
if ($fileDate !== false && $fileDate < strtotime('-60 days')) {
@unlink($file);
}
}
}
@file_put_contents($markerFile, time());
}
die;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,110 @@
<?php
/**
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License version 3.0
* that is bundled with this package in the file LICENSE.md
* It is also available through the world-wide-web at this URL:
* https://opensource.org/license/osl-3-0-php
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to support@qloapps.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade this module to a newer
* versions in the future. If you wish to customize this module for your needs
* please refer to https://store.webkul.com/customisation-guidelines for more information.
*
* @author Webkul IN
* @copyright Since 2010 Webkul
* @license https://opensource.org/license/osl-3-0-php Open Software License version 3.0
*/
if (!defined('_PS_VERSION_'))
exit;
function upgrade_module_5_0_0($module)
{
$objCMConnector = new UpgradeQloCMConnector500($module);
return (
$objCMConnector->createTables()
&& $objCMConnector->addConfigKeys()
&& $objCMConnector->registerHooks()
);
}
class UpgradeQloCMConnector500
{
public function __construct($module)
{
$this->module = $module;
}
public function createTables()
{
$sql = array(
"CREATE TABLE IF NOT EXISTS "._DB_PREFIX_."ari_updates (
`id_ari_updates` INT AUTO_INCREMENT,
`id_hotel` INT NOT NULL,
`id_room_type` INT NOT NULL,
`date` DATETIME NOT NULL,
`ari_data` LONGTEXT NULL,
`status` TINYINT(1) NOT NULL DEFAULT 0,
`date_add` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`date_upd` DATETIME NULL,
PRIMARY KEY (`id_ari_updates`)
)ENGINE=" . _MYSQL_ENGINE_ ." DEFAULT CHARSET=utf8",
"ALTER TABLE `"._DB_PREFIX_."orders`
ADD COLUMN `id_channel_manager_booking` VARCHAR(255) NULL",
);
$objDb = Db::getInstance();
foreach ($sql as $query) {
if (!$objDb->execute(trim($query))) {
return false;
}
}
return true;
}
public function addConfigKeys()
{
$configKeys = [
'QCMC_CM_CLIENT_ID',
'QCMC_CM_CLIENT_SECRET',
];
foreach ($configKeys as $key) {
if (!Configuration::hasKey($key)) {
$success = Configuration::updateValue($key, '', false, null, null);
if (!$success) {
return false;
}
}
}
return true;
}
public function registerHooks()
{
return $this->module->registerHook(
array(
'actionRoomReallocateAfter',
'actionObjectHotelRoomDisableDatesAddAfter',
'actionObjectHotelRoomDisableDatesDeleteAfter',
'actionValidateRoomInformation',
'actionObjectHotelRoomInformationAddAfter',
'actionObjectHotelRoomInformationDeleteAfter',
'actionObjectHotelBookingDetailAddAfter',
'actionObjectHotelBookingDetailUpdateBefore',
'actionObjectHotelBookingDetailDeleteAfter',
'actionObjectOrderPropertiesModifier',
'actionObjectOrderDefinitionModifier',
'actionObjectOrderAddBefore',
'actionObjectOrderUpdateAfter'
)
);
}
}

View File

@@ -0,0 +1,50 @@
<?php
/**
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License version 3.0
* that is bundled with this package in the file LICENSE.md
* It is also available through the world-wide-web at this URL:
* https://opensource.org/license/osl-3-0-php
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to support@qloapps.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade this module to a newer
* versions in the future. If you wish to customize this module for your needs
* please refer to https://store.webkul.com/customisation-guidelines for more information.
*
* @author Webkul IN
* @copyright Since 2010 Webkul
* @license https://opensource.org/license/osl-3-0-php Open Software License version 3.0
*/
if (!defined('_PS_VERSION_'))
exit;
function upgrade_module_5_0_2($module)
{
$objCMConnector = new UpgradeQloCMConnector502($module);
return $objCMConnector->registerHooks();
}
class UpgradeQloCMConnector502
{
public function __construct($module)
{
$this->module = $module;
}
public function registerHooks()
{
return $this->module->registerHook(
array(
'actionAvailRoomSearchSqlModifier',
'actionPartiallyAvailRoomSearchSqlModifier',
)
);
}
}

View File

@@ -0,0 +1,60 @@
<?php
/**
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License version 3.0
* that is bundled with this package in the file LICENSE.md
* It is also available through the world-wide-web at this URL:
* https://opensource.org/license/osl-3-0-php
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to support@qloapps.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade this module to a newer
* versions in the future. If you wish to customize this module for your needs
* please refer to https://store.webkul.com/customisation-guidelines for more information.
*
* @author Webkul IN
* @copyright Since 2010 Webkul
* @license https://opensource.org/license/osl-3-0-php Open Software License version 3.0
*/
if (!defined('_PS_VERSION_'))
exit;
function upgrade_module_5_0_3($module)
{
$objCMConnector = new UpgradeQloCMConnector503($module);
return (
$objCMConnector->registerHooks()
);
}
class UpgradeQloCMConnector503{
public function __construct($module)
{
$this->module = $module;
}
public function registerHooks()
{
return $this->module->registerHook(
array(
'actionObjectProductUpdateBefore',
'actionObjectHotelRoomTypeUpdateBefore',
'actionObjectHotelRoomTypeRestrictionDateRangeUpdateBefore',
'actionObjectHotelRoomTypeRestrictionDateRangeAddBefore',
'actionObjectHotelOrderRestrictDateUpdateBefore',
'actionObjectHotelRoomTypeRestrictionDateRangeDeleteBefore',
'actionObjectHotelRoomTypeFeaturePricingAddBefore',
'actionObjectHotelRoomTypeFeaturePricingUpdateBefore',
'actionObjectHotelRoomTypeFeaturePricingDeleteBefore',
'actionObjectRoomTypeServiceProductLinkBefore',
'actionObjectRoomTypeServiceProductUnlinkBefore'
)
);
}
}

View File

@@ -0,0 +1,71 @@
<?php
/**
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License version 3.0
* that is bundled with this package in the file LICENSE.md
* It is also available through the world-wide-web at this URL:
* https://opensource.org/license/osl-3-0-php
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to support@qloapps.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade this module to a newer
* versions in the future. If you wish to customize this module for your needs
* please refer to https://store.webkul.com/customisation-guidelines for more information.
*
* @author Webkul IN
* @copyright Since 2010 Webkul
* @license https://opensource.org/license/osl-3-0-php Open Software License version 3.0
*/
if (!defined('_PS_VERSION_'))
exit;
function upgrade_module_5_0_4($module)
{
$objCMConnector = new UpgradeQloCMConnector504($module);
return (
$objCMConnector->registerHooks()
&& $objCMConnector->updateTable()
);
}
class UpgradeQloCMConnector504{
public function __construct($module)
{
$this->module = $module;
}
public function registerHooks()
{
return $this->module->registerHook(
array(
'displayOrderRoomsBookingsTableHeading',
'displayOrderRoomsBookingsTableData',
'actionObjectHotelBookingDetailPropertiesModifier',
'actionObjectHotelBookingDetailDefinitionModifier',
)
);
}
public function updateTable()
{
$exists = Db::getInstance()->getValue(
"SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = '" . pSQL(_DB_PREFIX_ . 'htl_booking_detail') . "'
AND COLUMN_NAME = 'rate_plan_name'"
);
if (!$exists) {
$sql = "ALTER TABLE `" . _DB_PREFIX_ . "htl_booking_detail` ADD COLUMN `rate_plan_name` VARCHAR(255) DEFAULT NULL";
if (!Db::getInstance()->execute($sql)) {
return false;
}
}
return true;
}
}

View File

@@ -21,26 +21,44 @@
* @license https://opensource.org/license/osl-3-0-php Open Software License version 3.0
*/
class QcmcChannelManagerConnectorDb
if (!defined('_PS_VERSION_'))
exit;
function upgrade_module_5_0_5($module)
{
public function createTables()
$objCMConnector = new UpgradeQloCMConnector505($module);
return (
$objCMConnector->registerHooks()
&& $objCMConnector->removeOldModule()
&& $objCMConnector->installTab()
);
}
class UpgradeQloCMConnector505{
public function __construct($module)
{
$sql = 'CREATE TABLE IF NOT EXISTS `'._DB_PREFIX_.'qcmc_channel_manager_booking` (
`id_channel_manager_booking` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`id_order` INT(11) UNSIGNED NOT NULL,
`date_add` DATETIME NOT NULL,
PRIMARY KEY (`id_channel_manager_booking`)
) ENGINE='._MYSQL_ENGINE_.' DEFAULT CHARSET=utf8';
if (!Db::getInstance()->execute(trim($sql))) {
return false;
}
return true;
$this->module = $module;
}
public function dropTables()
public function registerHooks()
{
return Db::getInstance()->execute('DROP TABLE IF EXISTS '._DB_PREFIX_.'qcmc_channel_manager_booking');
return $this->module->registerHook(
array(
'displayAdminListBefore',
'displayBackOfficeHeader',
'actionAdminControllerSetMedia',
)
);
}
public function removeOldModule()
{
return $this->module->removeOldChannelManagerConnectorModule();
}
public function installTab()
{
return $this->module->callInstallTab();
}
}

View File

@@ -92,6 +92,12 @@
line-height: 22px;
color: #ff404b;
}
.qcm_content_wrapper .connect_txt {
font-weight: 600;
font-size: 16px;
line-height: 22px;
color: #2FA368;
}
.qcm_content_wrapper .connect_status_heading {
font-weight: 600;
font-size: 16px;

View File

@@ -26,10 +26,6 @@
color: #333;
text-align: right;
}
.adminqloappschannelmanagerconnector .qcm_booking_title {
font-size: 14px;
margin-bottom: 10px;
}
.adminqloappschannelmanagerconnector .channel_info_type {
margin-left: 10px;
font-weight: bold;
@@ -41,6 +37,9 @@
color: #2FA368;
font-weight: bold;
}
.adminqloappschannelmanagerconnector .channel_connection_status.not_connected {
color: #ff404b;
}
.adminqloappschannelmanagerconnector .channel_info_type .icon-info-circle {
font-size: 16px;
}

View File

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

View File

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

@@ -19,7 +19,7 @@
* @author Webkul IN
* @copyright Since 2010 Webkul
* @license https://opensource.org/license/osl-3-0-php Open Software License version 3.0
*/
*/
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');

View File

@@ -19,7 +19,7 @@
* @author Webkul IN
* @copyright Since 2010 Webkul
* @license https://opensource.org/license/osl-3-0-php Open Software License version 3.0
*/
*/
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');

View File

@@ -0,0 +1,22 @@
<div class="panel">
<div class="panel-heading">
<i class="icon-clock-o"></i>
{l s='Cron job setting' mod='qlocmconnector'}
</div>
{if $curl_missing}
<div class="alert alert-danger">
{l s='CURL extension is not installed or enabled on your server. Cron execution will not work until CURL is available.' mod='qlocmconnector'}
</div>
{/if}
<div class="alert alert-info">
<strong>
{l s='For seamless syncing of availability from the PMS to the Channel Manager automatically, you need to set the following cron job to run every minute' mod='qlocmconnector'}
</strong>
<br><br>
<strong>
<code>* * * * * curl {$cron_url}</code>
</strong>
</div>
</div>

View File

@@ -19,7 +19,7 @@
* @author Webkul IN
* @copyright Since 2010 Webkul
* @license https://opensource.org/license/osl-3-0-php Open Software License version 3.0
*/
*/
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');

View File

@@ -19,7 +19,7 @@
* @author Webkul IN
* @copyright Since 2010 Webkul
* @license https://opensource.org/license/osl-3-0-php Open Software License version 3.0
*/
*/
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');

View File

@@ -0,0 +1,32 @@
<?php
/**
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License version 3.0
* that is bundled with this package in the file LICENSE.md
* It is also available through the world-wide-web at this URL:
* https://opensource.org/license/osl-3-0-php
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to support@qloapps.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade this module to a newer
* versions in the future. If you wish to customize this module for your needs
* please refer to https://store.webkul.com/customisation-guidelines for more information.
*
* @author Webkul IN
* @copyright Since 2010 Webkul
* @license https://opensource.org/license/osl-3-0-php Open Software License version 3.0
*/
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');
header('Location: ../../../');
exit;

View File

@@ -0,0 +1,33 @@
{*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License version 3.0
* that is bundled with this package in the file LICENSE.md
* It is also available through the world-wide-web at this URL:
* https://opensource.org/license/osl-3-0-php
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to support@qloapps.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade this module to a newer
* versions in the future. If you wish to customize this module for your needs
* please refer to https://store.webkul.com/customisation-guidelines for more information.
*
* @author Webkul IN
* @copyright Since 2010 Webkul
* @license https://opensource.org/license/osl-3-0-php Open Software License version 3.0
*}
{extends file="helpers/list/list_header.tpl"}
{block name="panel_heading"}
<div class="panel-heading">
{if is_array($title)}{$title|end}{else}{$title}{/if}
<span class="badge">{$list_total|intval}</span>
</div>
<div class="alert alert-info">
{l s='Below is the list of all the bookings created by QloApps Channel Manager.' mod='qlocmconnector'}
</div>
{/block}

View File

@@ -0,0 +1,32 @@
<?php
/**
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License version 3.0
* that is bundled with this package in the file LICENSE.md
* It is also available through the world-wide-web at this URL:
* https://opensource.org/license/osl-3-0-php
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to support@qloapps.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade this module to a newer
* versions in the future. If you wish to customize this module for your needs
* please refer to https://store.webkul.com/customisation-guidelines for more information.
*
* @author Webkul IN
* @copyright Since 2010 Webkul
* @license https://opensource.org/license/osl-3-0-php Open Software License version 3.0
*/
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');
header('Location: ../../../');
exit;

View File

@@ -26,14 +26,14 @@
<div class="qcm_info_block">
<div class="row">
<div class="col-sm-6 margin-bottom-10">
<span class="connect_status_heading">{l s='Connection status' mod='qlochannelmanagerconnector'} :</span> <span class="not_connect_txt">{l s='Not Connected' mod='qlochannelmanagerconnector'}</span>
<span class="connect_status_heading">{l s='Connection status' mod='qlocmconnector'} :</span> {if $is_cm_connected}<span class="connect_txt">{l s='Connected' mod='qlocmconnector'}</span>{else}<span class="not_connect_txt">{l s='Not Connected' mod='qlocmconnector'}</span>{/if}
</div>
<div class="col-sm-6 channel_connection_info margin-bottom-10">
<span class="channel_info_type">{l s='Last updated' mod='qlochannelmanagerconnector'} :</span> <span>{$current_datetime|escape:'htmlall':'UTF-8'}</span>
<span class="channel_info_type">{l s='Last updated' mod='qlocmconnector'} :</span> <span>{$current_datetime|escape:'htmlall':'UTF-8'}</span>
</div>
<div class="connect_info_txt col-sm-12 margin-bottom-10">
**{l s='Connection status with channel manager is showing according to the bookings fetched from QloApps Channel Manager.' mod='qlochannelmanagerconnector'}
**{l s='Connection status with channel manager is showing according to the bookings fetched from QloApps Channel Manager.' mod='qlocmconnector'}
</div>
</div>
</div>
@@ -44,25 +44,25 @@
<div class="qcm_info_block">
<div class="row flex-display">
<div class="col-md-2 qcm_info_img_container">
<img src="{$link->getMediaLink("`$module_dir`/qlochannelmanagerconnector/views/img/channel_manager_connect.png")|escape:'htmlall':'UTF-8'}" class="img-responsive">
<img src="{$link->getMediaLink("`$module_dir`/qlocmconnector/views/img/channel_manager_connect.png")|escape:'htmlall':'UTF-8'}" class="img-responsive">
</div>
<div class="col-md-10 qcm_info_wrapper">
<div class="qcm_info_block_head">
{l s='How to connect with channel manager?' mod='qlochannelmanagerconnector'}
{l s='How to connect with channel manager?' mod='qlocmconnector'}
</div>
<div class="qcm_info_block_content">
{l s='You can connect with channel manager through few simple steps' mod='qlochannelmanagerconnector'} :
{l s='You can connect with channel manager through few simple steps' mod='qlocmconnector'} :
<ul>
<li>{l s='Enable QloApps webservice from Webservice tab.' mod='qlochannelmanagerconnector'} <a target="blank" href="{$link->getAdminLink('AdminWebservice')}"><i class="icon-external-link"></i></a></li>
<li>{l s='Create your webservice key and enable all the APIs from Webservice tab.' mod='qlochannelmanagerconnector'} <a target="blank" href="{$link->getAdminLink('AdminWebservice')}"><i class="icon-external-link"></i></a></li>
<li>{l s='Create account on Channel Manager' mod='qlochannelmanagerconnector'} <a target="blank" class="qcm-link" href="https://channels.qloapps.com/"><i class="icon-external-link"></i></a>. {l s=' Then enter QloApps webservice credentials under PMS settings of channel manager .' mod='qlochannelmanagerconnector'}</li>
<li>{l s='Synchronize and map QloApps hotels and room types in channel manager.' mod='qlochannelmanagerconnector'}</li>
<li>{l s='Enable QloApps webservice from Webservice tab.' mod='qlocmconnector'} <a target="blank" href="{$link->getAdminLink('AdminWebservice')}"><i class="icon-external-link"></i></a></li>
<li>{l s='Create your webservice key and enable all the APIs from Webservice tab.' mod='qlocmconnector'} <a target="blank" href="{$link->getAdminLink('AdminWebservice')}"><i class="icon-external-link"></i></a></li>
<li>{l s='Create account on Channel Manager' mod='qlocmconnector'} <a target="blank" class="qcm-link" href="https://channels.qloapps.com/"><i class="icon-external-link"></i></a>. {l s=' Then enter QloApps webservice credentials under PMS settings of channel manager .' mod='qlocmconnector'}</li>
<li>{l s='Synchronize and map QloApps hotels and room types in channel manager.' mod='qlocmconnector'}</li>
</ul>
{l s='To read the connection process in detail, visit' mod='qlochannelmanagerconnector'} <a class="qcm-link" href="https://qloapps.com/qloapps-channel-manager/#section-24">{l s='Connection with PMS' mod='qlochannelmanagerconnector'}</a>
{l s='To read the connection process in detail, visit' mod='qlocmconnector'} <a class="qcm-link" href="https://qloapps.com/qloapps-channel-manager/#section-24">{l s='Connection with PMS' mod='qlocmconnector'}</a>
</div>
<div class="qcm_info_explore">
{l s='If you are still not connected with channel manager' mod='qlochannelmanagerconnector'} <a class="qcm-link" href="https://channels.qloapps.com/">{l s='Connect It Now' mod='qlochannelmanagerconnector'}</a>
{l s='If you are still not connected with channel manager' mod='qlocmconnector'} <a class="qcm-link" href="https://channels.qloapps.com/">{l s='Connect It Now' mod='qlocmconnector'}</a>
</div>
</div>
</div>
@@ -72,19 +72,19 @@
<div class="qcm_info_block">
<div class="row flex-display">
<div class="col-md-2 qcm_info_img_container">
<img src="{$link->getMediaLink("`$module_dir`/qlochannelmanagerconnector/views/img/channel_manager.png")|escape:'htmlall':'UTF-8'}" class="img-responsive">
<img src="{$link->getMediaLink("`$module_dir`/qlocmconnector/views/img/channel_manager.png")|escape:'htmlall':'UTF-8'}" class="img-responsive">
</div>
<div class="col-md-10 qcm_info_wrapper">
<div class="qcm_info_block_head">
{l s='What is channel manager?' mod='qlochannelmanagerconnector'}
{l s='What is channel manager?' mod='qlocmconnector'}
</div>
<div class="qcm_info_block_content">
{l s='A channel manager is a centralized software that synchronizes availabilities and details of the property across all platforms i.e. online travel agencies (OTA) and other online distribution channels.' mod='qlochannelmanagerconnector'}
{l s='A channel manager is a centralized software that synchronizes availabilities and details of the property across all platforms i.e. online travel agencies (OTA) and other online distribution channels.' mod='qlocmconnector'}
<br>
{l s='QloApps channel manager synchronizes inventory, rates, restrictions with all connected OTA channels and automated bookings sync connected OTAs and QloApps PMS.' mod='qlochannelmanagerconnector'}
{l s='QloApps channel manager synchronizes inventory, rates, restrictions with all connected OTA channels and automated bookings sync connected OTAs and QloApps PMS.' mod='qlocmconnector'}
</div>
<div class="qcm_info_explore">
{l s='For channel manager information in detail' mod='qlochannelmanagerconnector'} <a class="qcm-link" href="https://qloapps.com/channel-manager/">{l s='Explore Channel Manager' mod='qlochannelmanagerconnector'}</a>
{l s='For channel manager information in detail' mod='qlocmconnector'} <a class="qcm-link" href="https://qloapps.com/channel-manager/">{l s='Explore Channel Manager' mod='qlocmconnector'}</a>
</div>
</div>
</div>
@@ -96,24 +96,24 @@
<div class="qcm_info_block">
<div class="row flex-display">
<div class="col-md-1 qcm_info_img_container">
<img src="{$link->getMediaLink("`$module_dir`/qlochannelmanagerconnector/views/img/channel_manager_advantage.png")|escape:'htmlall':'UTF-8'}" class="img-responsive">
<img src="{$link->getMediaLink("`$module_dir`/qlocmconnector/views/img/channel_manager_advantage.png")|escape:'htmlall':'UTF-8'}" class="img-responsive">
</div>
<div class="col-md-11 qcm_info_wrapper">
<div class="qcm_info_block_head">
{l s='Advantages of Channel Manager' mod='qlochannelmanagerconnector'}
{l s='Advantages of Channel Manager' mod='qlocmconnector'}
</div>
<div class="qcm_info_block_content">
<ul>
<li>{l s='Hoteliers need not to worry about problems like over-bookings, inconsistent inventory management, and missed opportunities.' mod='qlochannelmanagerconnector'}</li>
<li>{l s='Real-time sync with the world\'s most popular OTA channels like Booking, MakeMyTrip, Expedia, Airbnb, Agoda, Google Hotels, and many more.' mod='qlochannelmanagerconnector'}</li>
<li>{l s='Provides an intuitive & analytical dashboard that brings useful data and stats.' mod='qlochannelmanagerconnector'}</li>
<li>{l s='With the one-click rate and inventory updates, directly push availability and rates on the connected OTA channels.' mod='qlochannelmanagerconnector'}</li>
<li>{l s='Already integrated with QloApps PMS that auto-synchronize inventories for the bookings created on QloApps PMS and connected OTA channels.' mod='qlochannelmanagerconnector'}</li>
<li>{l s='Getting more property impressions by travellers on the world’s leading Online Travel Agencies as well as on the QloApps Hotel booking website will boost your online brand visibility and returns you more reservations.' mod='qlochannelmanagerconnector'}</li>
<li>{l s='Hoteliers need not to worry about problems like over-bookings, inconsistent inventory management, and missed opportunities.' mod='qlocmconnector'}</li>
<li>{l s='Real-time sync with the world\'s most popular OTA channels like Booking, MakeMyTrip, Expedia, Airbnb, Agoda, Google Hotels, and many more.' mod='qlocmconnector'}</li>
<li>{l s='Provides an intuitive & analytical dashboard that brings useful data and stats.' mod='qlocmconnector'}</li>
<li>{l s='With the one-click rate and inventory updates, directly push availability and rates on the connected OTA channels.' mod='qlocmconnector'}</li>
<li>{l s='Already integrated with QloApps PMS that auto-synchronize inventories for the bookings created on QloApps PMS and connected OTA channels.' mod='qlocmconnector'}</li>
<li>{l s='Getting more property impressions by travellers on the world’s leading Online Travel Agencies as well as on the QloApps Hotel booking website will boost your online brand visibility and returns you more reservations.' mod='qlocmconnector'}</li>
</ul>
</div>
<div class="qcm_info_explore">
{l s='To know channel manager freatures and work-flow in details' mod='qlochannelmanagerconnector'} <a class="qcm-link" href="https://qloapps.com/qloapps-channel-manager/">{l s='Visit Documentation' mod='qlochannelmanagerconnector'}</a>
{l s='To know channel manager freatures and work-flow in details' mod='qlocmconnector'} <a class="qcm-link" href="https://qloapps.com/qloapps-channel-manager/">{l s='Visit Documentation' mod='qlocmconnector'}</a>
</div>
</div>
</div>

View File

@@ -0,0 +1,32 @@
<?php
/**
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License version 3.0
* that is bundled with this package in the file LICENSE.md
* It is also available through the world-wide-web at this URL:
* https://opensource.org/license/osl-3-0-php
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to support@qloapps.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade this module to a newer
* versions in the future. If you wish to customize this module for your needs
* please refer to https://store.webkul.com/customisation-guidelines for more information.
*
* @author Webkul IN
* @copyright Since 2010 Webkul
* @license https://opensource.org/license/osl-3-0-php Open Software License version 3.0
*/
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');
header('Location: ../../../');
exit;

View File

@@ -21,14 +21,9 @@
*}
<div class="row">
<div class="col-sm-5">
<label class="qcm_booking_title">
<i class="icon icon-users"></i> {l s='Channel Manager Bookings' mod='qlochannelmanagerconnector'}
</label>
</div>
<div class="col-sm-7 channel_connection_info">
<span class="channel_info_type">{l s='Connection status' mod='qlochannelmanagerconnector'} :</span> <span class="channel_connection_status">{l s='Connected' mod='qlochannelmanagerconnector'}</span>
<span class="channel_info_type">{l s='Last updated' mod='qlochannelmanagerconnector'} :</span> <span>{$last_booking_datetime|escape:'htmlall':'UTF-8'}</span>
<span class="channel_info_type connection_criteria"><i id="connection_details" class="icon-info-circle" data-toggle="popover" data-content="{l s='Connection status with channel manager is showing according to the bookings fetched from QloApps Channel Manager.' mod='qlochannelmanagerconnector'}"></i></span>
<div class="col-sm-12 channel_connection_info">
<span class="channel_info_type">{l s='Connection status' mod='qlocmconnector'} :</span> {if $is_cm_connected}<span class="channel_connection_status">{l s='Connected' mod='qlocmconnector'}</span>{else}<span class="channel_connection_status not_connected">{l s='Not Connected' mod='qlocmconnector'}</span>{/if}
<span class="channel_info_type">{l s='Last updated' mod='qlocmconnector'} :</span> <span>{if $last_booking_datetime}{$last_booking_datetime|escape:'htmlall':'UTF-8'}{else}--{/if}</span>
<span class="channel_info_type connection_criteria"><i id="connection_details" class="icon-info-circle" data-toggle="popover" data-content="{l s='Connection status with channel manager is showing according to the bookings fetched from QloApps Channel Manager.' mod='qlocmconnector'}"></i></span>
</div>
</div>

View File

@@ -0,0 +1,32 @@
<?php
/**
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License version 3.0
* that is bundled with this package in the file LICENSE.md
* It is also available through the world-wide-web at this URL:
* https://opensource.org/license/osl-3-0-php
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to support@qloapps.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade this module to a newer
* versions in the future. If you wish to customize this module for your needs
* please refer to https://store.webkul.com/customisation-guidelines for more information.
*
* @author Webkul IN
* @copyright Since 2010 Webkul
* @license https://opensource.org/license/osl-3-0-php Open Software License version 3.0
*/
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');
header('Location: ../../../');
exit;