PluginProbe
VikBooking Hotel Booking Engine & PMS / trunk
VikBooking Hotel Booking Engine & PMS vtrunk
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 1.7.3 All 36 releases
vikbooking / admin / helpers / report / top_countries.php

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

521 lines 16.2 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(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, `or`.`id` ASC;";
206 $this->dbo->setQuery($q);
207 $records = $this->dbo->loadAssocList();
208
209 if (!$records) {
210 $this->setError(JText::translate('VBOREPORTSERRNORESERV'));
211 return false;
212 }
213
214 //nest records with multiple rooms booked inside sub-array
215 $bookings = array();
216 foreach ($records as $v) {
217 if (!isset($bookings[$v['id']])) {
218 $bookings[$v['id']] = array();
219 }
220 //calculate the from_ts and to_ts values for later comparison
221 $in_info = getdate($v['checkin']);
222 $out_info = getdate($v['checkout']);
223 $v['from_ts'] = mktime(0, 0, 0, $in_info['mon'], $in_info['mday'], $in_info['year']);
224 $v['to_ts'] = mktime(23, 59, 59, $out_info['mon'], ($out_info['mday'] - 1), $out_info['year']);
225 //
226 array_push($bookings[$v['id']], $v);
227 }
228
229 //define the columns of the report
230 $this->cols = array(
231 //country
232 array(
233 'key' => 'country',
234 'sortable' => 1,
235 'label' => JText::translate('VBOREPORTTOPCOUNTRIESC')
236 ),
237 //rooms sold
238 array(
239 'key' => 'rooms_sold',
240 'attr' => array(
241 'class="center"'
242 ),
243 'sortable' => 1,
244 'label' => JText::translate('VBOREPORTREVENUERSOLD')
245 ),
246 //total bookings
247 array(
248 'key' => 'tot_bookings',
249 'attr' => array(
250 'class="center"'
251 ),
252 'sortable' => 1,
253 'label' => JText::translate('VBOREPORTREVENUETOTB')
254 ),
255 //IBE revenue
256 array(
257 'key' => 'ibe_revenue',
258 'attr' => array(
259 'class="center"'
260 ),
261 'sortable' => 1,
262 'label' => JText::translate('VBOREPORTREVENUEREVWEB')
263 ),
264 //OTAs revenue
265 array(
266 'key' => 'ota_revenue',
267 'attr' => array(
268 'class="center"'
269 ),
270 'sortable' => 1,
271 'label' => JText::translate('VBOREPORTREVENUEREVOTA')
272 ),
273 //Options/Extras
274 array(
275 'key' => 'opts',
276 'attr' => array(
277 'class="center"'
278 ),
279 'sortable' => 1,
280 'label' => JText::translate('VBOREPORTOPTIONSEXTRAS'),
281 'tip' => JText::translate('VBOREPORTOPTIONSEXTRASHELP')
282 ),
283 //Taxes
284 array(
285 'key' => 'taxes',
286 'attr' => array(
287 'class="center"'
288 ),
289 'sortable' => 1,
290 'label' => JText::translate('VBOREPORTREVENUETAX')
291 ),
292 //Revenue
293 array(
294 'key' => 'revenue',
295 'attr' => array(
296 'class="center"'
297 ),
298 'sortable' => 1,
299 'label' => JText::translate('VBOREPORTREVENUEREV')
300 )
301 );
302
303 //loop over the bookings to build the top countries
304 $to_info = getdate($to_ts);
305 $to_ts_midnight = mktime(0, 0, 0, $to_info['mon'], $to_info['mday'], $to_info['year']);
306 $top_countries = array();
307 $country_stats = array(
308 'rooms_sold' => 0,
309 'tot_bookings' => 0,
310 'ibe_revenue' => 0,
311 'ota_revenue' => 0,
312 'opts' => 0,
313 'taxes' => 0,
314 'revenue' => 0
315 );
316 foreach ($bookings as $gbook) {
317 $useful_nights = $gbook[0]['days'];
318 if ($gbook[0]['from_ts'] < $from_ts || $gbook[0]['to_ts'] > $to_ts_midnight) {
319 //the dates of the booking exceed the filter, so we need to calculate the useful nights between the date interval filter
320 $useful_nights = 0;
321 $book_from_info = getdate($gbook[0]['from_ts']);
322 for ($i = 0; $i < $gbook[0]['days']; $i++) {
323 $book_night_ts = mktime(0, 0, 0, $book_from_info['mon'], ($book_from_info['mday'] + $i), $book_from_info['year']);
324 if ($book_night_ts >= $from_ts && $book_night_ts <= $to_ts_midnight) {
325 $useful_nights++;
326 }
327 }
328 }
329 if ($useful_nights < 1) {
330 continue;
331 }
332 $country = 'unknown';
333 if (!empty($gbook[0]['country'])) {
334 $country = $gbook[0]['country'];
335 } elseif (!empty($gbook[0]['customer_country'])) {
336 $country = $gbook[0]['customer_country'];
337 }
338 if (!isset($top_countries[$country])) {
339 $top_countries[$country] = $country_stats;
340 }
341 $top_countries[$country]['rooms_sold'] += $gbook[0]['roomsnum'];
342 $top_countries[$country]['tot_bookings']++;
343 //calculate net revenue and taxes
344 $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'];
345 $tot_net = $tot_net / (int)$gbook[0]['days'] * $useful_nights;
346 if (!empty($gbook[0]['idorderota']) && !empty($gbook[0]['channel'])) {
347 $top_countries[$country]['ota_revenue'] += $tot_net;
348 } else {
349 $top_countries[$country]['ibe_revenue'] += $tot_net;
350 }
351 $get_opts = (float)$gbook[0]['tot_city_taxes'] + (float)$gbook[0]['tot_fees'] + (float)$gbook[0]['cmms'];
352 $get_room_costs = 0;
353 //loop over the rooms booked to sum up the rooms costs
354 foreach ($gbook as $b) {
355 $get_room_costs += !empty($b['cust_cost']) ? (float)$b['cust_cost'] : (!empty($b['room_cost']) ? (float)$b['room_cost'] : 0);
356 }
357 //if there are no rooms costs, we set the options/extras to 0 or we may give an invalid result
358 $tot_opts = $get_room_costs > 0 && $gbook[0]['total'] > $get_room_costs ? ($gbook[0]['total'] - $get_opts - $get_room_costs) : 0;
359 $tot_opts = $tot_opts >= 0 ? $tot_opts : 0;
360 $top_countries[$country]['opts'] += $tot_opts;
361 //
362 $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;
363 $top_countries[$country]['revenue'] += $tot_net;
364 }
365
366 $countries_map = $this->getCountriesMap(array_keys($top_countries));
367
368 //loop over the top countries to build the rows of the report
369 foreach ($top_countries as $country => $data) {
370 //push data in the rows array as a new row
371 array_push($this->rows, array(
372 array(
373 'key' => 'country',
374 'attr' => array(
375 'class="vbo-report-topcountries-countryname"'
376 ),
377 'callback' => function ($val) use ($country) {
378 if (file_exists(VBO_ADMIN_PATH.DIRECTORY_SEPARATOR.'resources'.DIRECTORY_SEPARATOR.'countries'.DIRECTORY_SEPARATOR.$country.'.png')) {
379 return $val.'<img src="'.VBO_ADMIN_URI.'resources/countries/'.$country.'.png" title="'.$country.'" class="vbo-country-flag vbo-country-flag-left" />';
380 }
381 return $val;
382 },
383 'no_csv_callback' => 1,
384 'value' => (isset($countries_map[$country]) ? $countries_map[$country] : $country)
385 ),
386 array(
387 'key' => 'rooms_sold',
388 'attr' => array(
389 'class="center"'
390 ),
391 'value' => $data['rooms_sold']
392 ),
393 array(
394 'key' => 'tot_bookings',
395 'attr' => array(
396 'class="center"'
397 ),
398 'value' => $data['tot_bookings']
399 ),
400 array(
401 'key' => 'ibe_revenue',
402 'attr' => array(
403 'class="center"'
404 ),
405 'callback' => function ($val) use ($currency_symb) {
406 return $currency_symb.' '.VikBooking::numberFormat($val);
407 },
408 'value' => $data['ibe_revenue']
409 ),
410 array(
411 'key' => 'ota_revenue',
412 'attr' => array(
413 'class="center"'
414 ),
415 'callback' => function ($val) use ($currency_symb) {
416 return $currency_symb.' '.VikBooking::numberFormat($val);
417 },
418 'value' => $data['ota_revenue']
419 ),
420 array(
421 'key' => 'opts',
422 'attr' => array(
423 'class="center"'
424 ),
425 'callback' => function ($val) use ($currency_symb) {
426 return $currency_symb.' '.VikBooking::numberFormat($val);
427 },
428 'value' => $data['opts']
429 ),
430 array(
431 'key' => 'taxes',
432 'attr' => array(
433 'class="center"'
434 ),
435 'callback' => function ($val) use ($currency_symb) {
436 return $currency_symb.' '.VikBooking::numberFormat($val);
437 },
438 'value' => $data['taxes']
439 ),
440 array(
441 'key' => 'revenue',
442 'attr' => array(
443 'class="center"'
444 ),
445 'callback' => function ($val) use ($currency_symb) {
446 return $currency_symb.' '.VikBooking::numberFormat($val);
447 },
448 'value' => $data['revenue']
449 )
450 ));
451 }
452
453 //sort rows
454 $this->sortRows($pkrsort, $pkrorder);
455
456 //Debug
457 if ($this->debug) {
458 $this->setWarning('path to report file = '.urlencode(dirname(__FILE__)).'<br/>');
459 $this->setWarning('$bookings:<pre>'.print_r($bookings, true).'</pre><br/>');
460 }
461 //
462
463 return true;
464 }
465
466 /**
467 * Registers the name to give to the CSV file being exported.
468 *
469 * @return void
470 *
471 * @since 1.16.1 (J) - 1.6.1 (WP)
472 */
473 private function registerExportCSVFileName()
474 {
475 $pfromdate = VikRequest::getString('fromdate', '', 'request');
476 $ptodate = VikRequest::getString('todate', '', 'request');
477
478 $this->setExportCSVFileName($this->reportName . '-' . str_replace('/', '_', $pfromdate) . '-' . str_replace('/', '_', $ptodate) . '.csv');
479 }
480
481 /**
482 * Maps the 3-char country codes to their full names.
483 * Translates also the 'unknown' country.
484 *
485 * @param array $countries
486 *
487 * @return array
488 */
489 private function getCountriesMap($countries)
490 {
491 $map = array();
492
493 if (in_array('unknown', $countries)) {
494 $map['unknown'] = JText::translate('VBOREPORTTOPCUNKNC');
495 foreach ($countries as $k => $v) {
496 if ($v == 'unknown') {
497 unset($countries[$k]);
498 }
499 }
500 }
501
502 if (count($countries)) {
503 $clauses = array();
504 foreach ($countries as $country) {
505 array_push($clauses, $this->dbo->quote($country));
506 }
507 $q = "SELECT `country_name`,`country_3_code` FROM `#__vikbooking_countries` WHERE `country_3_code` IN (".implode(', ', $clauses).");";
508 $this->dbo->setQuery($q);
509 $this->dbo->execute();
510 if ($this->dbo->getNumRows() > 0) {
511 $records = $this->dbo->loadAssocList();
512 foreach ($records as $v) {
513 $map[$v['country_3_code']] = $v['country_name'];
514 }
515 }
516 }
517
518 return $map;
519 }
520 }
521