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

top_countries.php in VikBooking Hotel Booking Engine & PMS 1.6.5, at admin/helpers/report/top_countries.php

523 lines 16.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * @package VikBooking
4 * @subpackage com_vikbooking
5 * @author Alessio Gaggii - e4j - Extensionsforjoomla.com
6 * @copyright Copyright (C) 2018 e4j - Extensionsforjoomla.com. All rights reserved.
7 * @license GNU General Public License version 2 or later; see LICENSE
8 * @link https://vikwp.com
9 */
10
11 defined('ABSPATH') or die('No script kiddies please!');
12
13 /**
14 * Top Countries child Class of VikBookingReport
15 */
16 class VikBookingReportTopCountries extends VikBookingReport
17 {
18 /**
19 * Property 'defaultKeySort' is used by the View that renders the report.
20 */
21 public $defaultKeySort = 'revenue';
22
23 /**
24 * Property 'defaultKeyOrder' is used by the View that renders the report.
25 */
26 public $defaultKeyOrder = 'DESC';
27
28 /**
29 * Property 'exportAllowed' is used by the View to display the export button.
30 */
31 public $exportAllowed = 1;
32
33 /**
34 * Debug mode is activated by passing the value 'e4j_debug' > 0
35 */
36 private $debug;
37
38 /**
39 * Class constructor should define the name of the report and
40 * other vars. Call the parent constructor to define the DB object.
41 */
42 public function __construct()
43 {
44 $this->reportFile = basename(__FILE__, '.php');
45 $this->reportName = JText::translate('VBOREPORT'.strtoupper(str_replace('_', '', $this->reportFile)));
46 $this->reportFilters = array();
47
48 $this->cols = array();
49 $this->rows = array();
50 $this->footerRow = array();
51
52 $this->debug = (VikRequest::getInt('e4j_debug', 0, 'request') > 0);
53
54 $this->registerExportCSVFileName();
55
56 parent::__construct();
57 }
58
59 /**
60 * Returns the name of this report.
61 *
62 * @return string
63 */
64 public function getName()
65 {
66 return $this->reportName;
67 }
68
69 /**
70 * Returns the name of this file without .php.
71 *
72 * @return string
73 */
74 public function getFileName()
75 {
76 return $this->reportFile;
77 }
78
79 /**
80 * Returns the filters of this report.
81 *
82 * @return array
83 */
84 public function getFilters()
85 {
86 if (count($this->reportFilters)) {
87 //do not run this method twice, as it could load JS and CSS files.
88 return $this->reportFilters;
89 }
90
91 //get VBO Application Object
92 $vbo_app = VikBooking::getVboApplication();
93
94 //load the jQuery UI Datepicker
95 $this->loadDatePicker();
96
97 //From Date Filter
98 $filter_opt = array(
99 'label' => '<label for="fromdate">'.JText::translate('VBOREPORTSDATEFROM').'</label>',
100 'html' => '<input type="text" id="fromdate" name="fromdate" value="" class="vbo-report-datepicker vbo-report-datepicker-from" />',
101 'type' => 'calendar',
102 'name' => 'fromdate'
103 );
104 array_push($this->reportFilters, $filter_opt);
105
106 //To Date Filter
107 $filter_opt = array(
108 'label' => '<label for="todate">'.JText::translate('VBOREPORTSDATETO').'</label>',
109 'html' => '<input type="text" id="todate" name="todate" value="" class="vbo-report-datepicker vbo-report-datepicker-to" />',
110 'type' => 'calendar',
111 'name' => 'todate'
112 );
113 array_push($this->reportFilters, $filter_opt);
114
115 //Room ID filter
116 $pidroom = VikRequest::getInt('idroom', '', 'request');
117 $all_rooms = $this->getRooms();
118 $rooms = array();
119 foreach ($all_rooms as $room) {
120 $rooms[$room['id']] = $room['name'];
121 }
122 if (count($rooms)) {
123 $rooms_sel_html = $vbo_app->getNiceSelect($rooms, $pidroom, 'idroom', JText::translate('VBOSTATSALLROOMS'), JText::translate('VBOSTATSALLROOMS'), '', '', 'idroom');
124 $filter_opt = array(
125 'label' => '<label for="idroom">'.JText::translate('VBOREPORTSROOMFILT').'</label>',
126 'html' => $rooms_sel_html,
127 'type' => 'select',
128 'name' => 'idroom'
129 );
130 array_push($this->reportFilters, $filter_opt);
131 }
132
133 //jQuery code for the datepicker calendars and select2
134 $pfromdate = VikRequest::getString('fromdate', '', 'request');
135 $ptodate = VikRequest::getString('todate', '', 'request');
136 $js = 'jQuery(document).ready(function() {
137 jQuery(".vbo-report-datepicker:input").datepicker({
138 maxDate: 0,
139 dateFormat: "'.$this->getDateFormat('jui').'",
140 onSelect: vboReportCheckDates
141 });
142 '.(!empty($pfromdate) ? 'jQuery(".vbo-report-datepicker-from").datepicker("setDate", "'.$pfromdate.'");' : '').'
143 '.(!empty($ptodate) ? 'jQuery(".vbo-report-datepicker-to").datepicker("setDate", "'.$ptodate.'");' : '').'
144 });
145 function vboReportCheckDates(selectedDate, inst) {
146 if (selectedDate === null || inst === null) {
147 return;
148 }
149 var cur_from_date = jQuery(this).val();
150 if (jQuery(this).hasClass("vbo-report-datepicker-from") && cur_from_date.length) {
151 var nowstart = jQuery(this).datepicker("getDate");
152 var nowstartdate = new Date(nowstart.getTime());
153 jQuery(".vbo-report-datepicker-to").datepicker("option", {minDate: nowstartdate});
154 }
155 }';
156 $this->setScript($js);
157
158 return $this->reportFilters;
159 }
160
161 /**
162 * Loads the report data from the DB.
163 * Returns true in case of success, false otherwise.
164 * Sets the columns and rows for the report to be displayed.
165 *
166 * @return boolean
167 */
168 public function getReportData()
169 {
170 if (strlen($this->getError())) {
171 //Export functions may set errors rather than exiting the process, and the View may continue the execution to attempt to render the report.
172 return false;
173 }
174 //Input fields and other vars
175 $pfromdate = VikRequest::getString('fromdate', '', 'request');
176 $ptodate = VikRequest::getString('todate', '', 'request');
177 $pidroom = VikRequest::getInt('idroom', '', 'request');
178 $pkrsort = VikRequest::getString('krsort', $this->defaultKeySort, 'request');
179 $pkrsort = empty($pkrsort) ? $this->defaultKeySort : $pkrsort;
180 $pkrorder = VikRequest::getString('krorder', $this->defaultKeyOrder, 'request');
181 $pkrorder = empty($pkrorder) ? $this->defaultKeyOrder : $pkrorder;
182 $pkrorder = $pkrorder == 'DESC' ? 'DESC' : 'ASC';
183 $currency_symb = VikBooking::getCurrencySymb();
184 $df = $this->getDateFormat();
185 $datesep = VikBooking::getDateSeparator();
186 if (empty($ptodate)) {
187 $ptodate = $pfromdate;
188 }
189 //Get dates timestamps
190 $from_ts = VikBooking::getDateTimestamp($pfromdate, 0, 0);
191 $to_ts = VikBooking::getDateTimestamp($ptodate, 23, 59, 59);
192 if (empty($pfromdate) || empty($from_ts) || empty($to_ts)) {
193 $this->setError(JText::translate('VBOREPORTSERRNODATES'));
194 return false;
195 }
196
197 //Query to obtain the records
198 $records = array();
199 $q = "SELECT `o`.`id`,`o`.`ts`,`o`.`days`,`o`.`checkin`,`o`.`checkout`,`o`.`totpaid`,`o`.`roomsnum`,`o`.`total`,`o`.`idorderota`,`o`.`channel`,`o`.`country`,`o`.`tot_taxes`,".
200 "`o`.`tot_city_taxes`,`o`.`tot_fees`,`o`.`cmms`,`or`.`idorder`,`or`.`idroom`,`or`.`optionals`,`or`.`cust_cost`,`or`.`cust_idiva`,`or`.`extracosts`,`or`.`room_cost`,".
201 "`co`.`idcustomer`,`c`.`country` AS `customer_country` ".
202 "FROM `#__vikbooking_orders` AS `o` LEFT JOIN `#__vikbooking_ordersrooms` AS `or` ON `or`.`idorder`=`o`.`id` ".
203 "LEFT JOIN `#__vikbooking_customers_orders` AS `co` ON `co`.`idorder`=`o`.`id` LEFT JOIN `#__vikbooking_customers` AS `c` ON `c`.`id`=`co`.`idcustomer` ".
204 "WHERE `o`.`status`='confirmed' AND `o`.`closure`=0 AND `o`.`checkout`>=".$from_ts." AND `o`.`checkin`<=".$to_ts." ".(!empty($pidroom) ? "AND `or`.`idroom`=".(int)$pidroom." " : "").
205 "ORDER BY `o`.`checkin` ASC, `o`.`id` ASC;";
206 $this->dbo->setQuery($q);
207 $this->dbo->execute();
208 if ($this->dbo->getNumRows() > 0) {
209 $records = $this->dbo->loadAssocList();
210 }
211 if (!count($records)) {
212 $this->setError(JText::translate('VBOREPORTSERRNORESERV'));
213 return false;
214 }
215
216 //nest records with multiple rooms booked inside sub-array
217 $bookings = array();
218 foreach ($records as $v) {
219 if (!isset($bookings[$v['id']])) {
220 $bookings[$v['id']] = array();
221 }
222 //calculate the from_ts and to_ts values for later comparison
223 $in_info = getdate($v['checkin']);
224 $out_info = getdate($v['checkout']);
225 $v['from_ts'] = mktime(0, 0, 0, $in_info['mon'], $in_info['mday'], $in_info['year']);
226 $v['to_ts'] = mktime(23, 59, 59, $out_info['mon'], ($out_info['mday'] - 1), $out_info['year']);
227 //
228 array_push($bookings[$v['id']], $v);
229 }
230
231 //define the columns of the report
232 $this->cols = array(
233 //country
234 array(
235 'key' => 'country',
236 'sortable' => 1,
237 'label' => JText::translate('VBOREPORTTOPCOUNTRIESC')
238 ),
239 //rooms sold
240 array(
241 'key' => 'rooms_sold',
242 'attr' => array(
243 'class="center"'
244 ),
245 'sortable' => 1,
246 'label' => JText::translate('VBOREPORTREVENUERSOLD')
247 ),
248 //total bookings
249 array(
250 'key' => 'tot_bookings',
251 'attr' => array(
252 'class="center"'
253 ),
254 'sortable' => 1,
255 'label' => JText::translate('VBOREPORTREVENUETOTB')
256 ),
257 //IBE revenue
258 array(
259 'key' => 'ibe_revenue',
260 'attr' => array(
261 'class="center"'
262 ),
263 'sortable' => 1,
264 'label' => JText::translate('VBOREPORTREVENUEREVWEB')
265 ),
266 //OTAs revenue
267 array(
268 'key' => 'ota_revenue',
269 'attr' => array(
270 'class="center"'
271 ),
272 'sortable' => 1,
273 'label' => JText::translate('VBOREPORTREVENUEREVOTA')
274 ),
275 //Options/Extras
276 array(
277 'key' => 'opts',
278 'attr' => array(
279 'class="center"'
280 ),
281 'sortable' => 1,
282 'label' => JText::translate('VBOREPORTOPTIONSEXTRAS'),
283 'tip' => JText::translate('VBOREPORTOPTIONSEXTRASHELP')
284 ),
285 //Taxes
286 array(
287 'key' => 'taxes',
288 'attr' => array(
289 'class="center"'
290 ),
291 'sortable' => 1,
292 'label' => JText::translate('VBOREPORTREVENUETAX')
293 ),
294 //Revenue
295 array(
296 'key' => 'revenue',
297 'attr' => array(
298 'class="center"'
299 ),
300 'sortable' => 1,
301 'label' => JText::translate('VBOREPORTREVENUEREV')
302 )
303 );
304
305 //loop over the bookings to build the top countries
306 $to_info = getdate($to_ts);
307 $to_ts_midnight = mktime(0, 0, 0, $to_info['mon'], $to_info['mday'], $to_info['year']);
308 $top_countries = array();
309 $country_stats = array(
310 'rooms_sold' => 0,
311 'tot_bookings' => 0,
312 'ibe_revenue' => 0,
313 'ota_revenue' => 0,
314 'opts' => 0,
315 'taxes' => 0,
316 'revenue' => 0
317 );
318 foreach ($bookings as $gbook) {
319 $useful_nights = $gbook[0]['days'];
320 if ($gbook[0]['from_ts'] < $from_ts || $gbook[0]['to_ts'] > $to_ts_midnight) {
321 //the dates of the booking exceed the filter, so we need to calculate the useful nights between the date interval filter
322 $useful_nights = 0;
323 $book_from_info = getdate($gbook[0]['from_ts']);
324 for ($i = 0; $i < $gbook[0]['days']; $i++) {
325 $book_night_ts = mktime(0, 0, 0, $book_from_info['mon'], ($book_from_info['mday'] + $i), $book_from_info['year']);
326 if ($book_night_ts >= $from_ts && $book_night_ts <= $to_ts_midnight) {
327 $useful_nights++;
328 }
329 }
330 }
331 if ($useful_nights < 1) {
332 continue;
333 }
334 $country = 'unknown';
335 if (!empty($gbook[0]['country'])) {
336 $country = $gbook[0]['country'];
337 } elseif (!empty($gbook[0]['customer_country'])) {
338 $country = $gbook[0]['customer_country'];
339 }
340 if (!isset($top_countries[$country])) {
341 $top_countries[$country] = $country_stats;
342 }
343 $top_countries[$country]['rooms_sold'] += $gbook[0]['roomsnum'];
344 $top_countries[$country]['tot_bookings']++;
345 //calculate net revenue and taxes
346 $tot_net = $gbook[0]['total'] - (float)$gbook[0]['tot_taxes'] - (float)$gbook[0]['tot_city_taxes'] - (float)$gbook[0]['tot_fees'] - (float)$gbook[0]['cmms'];
347 $tot_net = $tot_net / (int)$gbook[0]['days'] * $useful_nights;
348 if (!empty($gbook[0]['idorderota']) && !empty($gbook[0]['channel'])) {
349 $top_countries[$country]['ota_revenue'] += $tot_net;
350 } else {
351 $top_countries[$country]['ibe_revenue'] += $tot_net;
352 }
353 $get_opts = (float)$gbook[0]['tot_city_taxes'] + (float)$gbook[0]['tot_fees'] + (float)$gbook[0]['cmms'];
354 $get_room_costs = 0;
355 //loop over the rooms booked to sum up the rooms costs
356 foreach ($gbook as $b) {
357 $get_room_costs += !empty($b['cust_cost']) ? (float)$b['cust_cost'] : (!empty($b['room_cost']) ? (float)$b['room_cost'] : 0);
358 }
359 //if there are no rooms costs, we set the options/extras to 0 or we may give an invalid result
360 $tot_opts = $get_room_costs > 0 && $gbook[0]['total'] > $get_room_costs ? ($gbook[0]['total'] - $get_opts - $get_room_costs) : 0;
361 $tot_opts = $tot_opts >= 0 ? $tot_opts : 0;
362 $top_countries[$country]['opts'] += $tot_opts;
363 //
364 $top_countries[$country]['taxes'] += ((float)$gbook[0]['tot_taxes'] + (float)$gbook[0]['tot_city_taxes'] + (float)$gbook[0]['tot_fees'] + (float)$gbook[0]['cmms']) / (int)$gbook[0]['days'] * $useful_nights;
365 $top_countries[$country]['revenue'] += $tot_net;
366 }
367
368 $countries_map = $this->getCountriesMap(array_keys($top_countries));
369
370 //loop over the top countries to build the rows of the report
371 foreach ($top_countries as $country => $data) {
372 //push data in the rows array as a new row
373 array_push($this->rows, array(
374 array(
375 'key' => 'country',
376 'attr' => array(
377 'class="vbo-report-topcountries-countryname"'
378 ),
379 'callback' => function ($val) use ($country) {
380 if (file_exists(VBO_ADMIN_PATH.DIRECTORY_SEPARATOR.'resources'.DIRECTORY_SEPARATOR.'countries'.DIRECTORY_SEPARATOR.$country.'.png')) {
381 return $val.'<img src="'.VBO_ADMIN_URI.'resources/countries/'.$country.'.png" title="'.$country.'" class="vbo-country-flag vbo-country-flag-left" />';
382 }
383 return $val;
384 },
385 'no_csv_callback' => 1,
386 'value' => (isset($countries_map[$country]) ? $countries_map[$country] : $country)
387 ),
388 array(
389 'key' => 'rooms_sold',
390 'attr' => array(
391 'class="center"'
392 ),
393 'value' => $data['rooms_sold']
394 ),
395 array(
396 'key' => 'tot_bookings',
397 'attr' => array(
398 'class="center"'
399 ),
400 'value' => $data['tot_bookings']
401 ),
402 array(
403 'key' => 'ibe_revenue',
404 'attr' => array(
405 'class="center"'
406 ),
407 'callback' => function ($val) use ($currency_symb) {
408 return $currency_symb.' '.VikBooking::numberFormat($val);
409 },
410 'value' => $data['ibe_revenue']
411 ),
412 array(
413 'key' => 'ota_revenue',
414 'attr' => array(
415 'class="center"'
416 ),
417 'callback' => function ($val) use ($currency_symb) {
418 return $currency_symb.' '.VikBooking::numberFormat($val);
419 },
420 'value' => $data['ota_revenue']
421 ),
422 array(
423 'key' => 'opts',
424 'attr' => array(
425 'class="center"'
426 ),
427 'callback' => function ($val) use ($currency_symb) {
428 return $currency_symb.' '.VikBooking::numberFormat($val);
429 },
430 'value' => $data['opts']
431 ),
432 array(
433 'key' => 'taxes',
434 'attr' => array(
435 'class="center"'
436 ),
437 'callback' => function ($val) use ($currency_symb) {
438 return $currency_symb.' '.VikBooking::numberFormat($val);
439 },
440 'value' => $data['taxes']
441 ),
442 array(
443 'key' => 'revenue',
444 'attr' => array(
445 'class="center"'
446 ),
447 'callback' => function ($val) use ($currency_symb) {
448 return $currency_symb.' '.VikBooking::numberFormat($val);
449 },
450 'value' => $data['revenue']
451 )
452 ));
453 }
454
455 //sort rows
456 $this->sortRows($pkrsort, $pkrorder);
457
458 //Debug
459 if ($this->debug) {
460 $this->setWarning('path to report file = '.urlencode(dirname(__FILE__)).'<br/>');
461 $this->setWarning('$bookings:<pre>'.print_r($bookings, true).'</pre><br/>');
462 }
463 //
464
465 return true;
466 }
467
468 /**
469 * Registers the name to give to the CSV file being exported.
470 *
471 * @return void
472 *
473 * @since 1.16.1 (J) - 1.6.1 (WP)
474 */
475 private function registerExportCSVFileName()
476 {
477 $pfromdate = VikRequest::getString('fromdate', '', 'request');
478 $ptodate = VikRequest::getString('todate', '', 'request');
479
480 $this->setExportCSVFileName($this->reportName . '-' . str_replace('/', '_', $pfromdate) . '-' . str_replace('/', '_', $ptodate) . '.csv');
481 }
482
483 /**
484 * Maps the 3-char country codes to their full names.
485 * Translates also the 'unknown' country.
486 *
487 * @param array $countries
488 *
489 * @return array
490 */
491 private function getCountriesMap($countries)
492 {
493 $map = array();
494
495 if (in_array('unknown', $countries)) {
496 $map['unknown'] = JText::translate('VBOREPORTTOPCUNKNC');
497 foreach ($countries as $k => $v) {
498 if ($v == 'unknown') {
499 unset($countries[$k]);
500 }
501 }
502 }
503
504 if (count($countries)) {
505 $clauses = array();
506 foreach ($countries as $country) {
507 array_push($clauses, $this->dbo->quote($country));
508 }
509 $q = "SELECT `country_name`,`country_3_code` FROM `#__vikbooking_countries` WHERE `country_3_code` IN (".implode(', ', $clauses).");";
510 $this->dbo->setQuery($q);
511 $this->dbo->execute();
512 if ($this->dbo->getNumRows() > 0) {
513 $records = $this->dbo->loadAssocList();
514 foreach ($records as $v) {
515 $map[$v['country_3_code']] = $v['country_name'];
516 }
517 }
518 }
519
520 return $map;
521 }
522 }
523