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 / widgets / finance.php

finance.php in VikBooking Hotel Booking Engine & PMS trunk, at admin/helpers/widgets/finance.php

1,769 lines 68.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 * Class handler for admin widget "finance".
15 *
16 * @since 1.16.0 (J) - 1.6.0 (WP)
17 */
18 class VikBookingAdminWidgetFinance extends VikBookingAdminWidget
19 {
20 /**
21 * The instance counter of this widget. Since we do not load individual parameters
22 * for each widget's instance, we use a static counter to determine its settings.
23 *
24 * @var int
25 */
26 protected static $instance_counter = -1;
27
28 /**
29 * Class constructor will define the widget name and identifier.
30 */
31 public function __construct()
32 {
33 // call parent constructor
34 parent::__construct();
35
36 $this->widgetName = JText::translate('VBO_W_FINANCE_TITLE');
37 $this->widgetDescr = JText::translate('VBO_W_FINANCE_DESCR');
38 $this->widgetId = basename(__FILE__, '.php');
39
40 // define widget and icon and style name
41 $this->widgetIcon = '<i class="' . VikBookingIcons::i('piggy-bank') . '"></i>';
42 $this->widgetStyleName = 'red';
43 }
44
45 /**
46 * Preload the necessary CSS/JS assets.
47 *
48 * @return void
49 */
50 public function preload()
51 {
52 // load assets for datepicker
53 $this->vbo_app->loadDatePicker();
54
55 // load assets for contextual menu
56 $this->vbo_app->loadContextMenuAssets();
57
58 // check for daily welcome message with stats
59 $this->setupWelcomeStats();
60
61 // lang vars for JS
62 JText::script('VBO_COMPARE_WITH_LAST_Y');
63 JText::script('VBO_COMPARE_WITH_PREV_Q');
64 JText::script('VBO_COMPARE_WITH_PREV_M');
65 JText::script('VBO_COMPARE_WITH_PREV_W');
66 JText::script('VBO_COMPARE_WITH_PREV_D');
67 JText::script('VBNOTRACKINGS');
68 JText::script('VBO_ERR_LOAD_RESULTS');
69 JText::script('VBNOROOMSFOUND');
70 JText::script('VBO_SEARCHING');
71 JText::script('VBROOMFILTER');
72 }
73
74 /**
75 * Custom method for this widget only to load the financial statistics.
76 * The method is called by the admin controller through an AJAX request.
77 * The visibility should be public, it should not exit the process, and
78 * any content sent to output will be returned to the AJAX response.
79 * In this case we return an array because this method requires "return":1.
80 *
81 * It's the actual rendering of the widget which also allows navigation.
82 */
83 public function loadFinancialStats()
84 {
85 $app = JFactory::getApplication();
86
87 $wrapper = $app->input->getString('wrapper', '');
88
89 $fromdate = $app->input->getString('fromdate', '');
90 $todate = $app->input->getString('todate', '');
91 $step = $app->input->getString('step', 'quarter');
92 $date_dir = $app->input->getInt('date_dir', 0);
93 $room_ids = array_filter((array) $app->input->getInt('room_ids', []));
94
95 if (empty($fromdate)) {
96 // default to current quarter (3 full months)
97 $now_info = getdate();
98 $end_info = getdate(mktime(0, 0, 0, ($now_info['mon'] + 2), 1, $now_info['year']));
99 $to_ts = mktime(23, 59, 59, $end_info['mon'], date('t', $end_info[0]), $end_info['year']);
100 $fromdate = date('Y-m-d', mktime(0, 0, 0, $now_info['mon'], 1, $now_info['year']));
101 $todate = date('Y-m-d', $to_ts);
102 }
103
104 // convert dates to timestamps to support custom date formats
105 $from_ts = VikBooking::getDateTimestamp($fromdate);
106 $to_ts = VikBooking::getDateTimestamp($todate, 23, 59, 59);
107
108 // check dates navigation and step
109 $step_name = '';
110 if ($date_dir > 0 || $date_dir < 0) {
111 // calculate forward or backward dates for navigation
112 list($from_ts, $to_ts, $step_name) = $this->calcDatesNavigation($from_ts, $to_ts, $step, $date_dir);
113 }
114
115 // convert final dates to Y-m-d
116 $fromdate = date('Y-m-d', $from_ts);
117 $todate = date('Y-m-d', $to_ts);
118
119 // stats calculation type
120 $calc_type = !strcasecmp($step, 'booking_dates') ? 'booking_dates' : 'stay_dates';
121
122 // access the finance helper object
123 $finance = VBOTaxonomyFinance::getInstance();
124
125 // currency symbol
126 $currencysymb = VikBooking::getCurrencySymb();
127
128 // get the financial stats for the requested dates
129 try {
130 $stats = $finance->getStats($fromdate, $todate, $room_ids, $calc_type);
131 } catch (Exception $e) {
132 // make the AJAX request fail nicely
133 VBOHttpDocument::getInstance()->close($e->getCode(), $e->getMessage());
134 }
135
136 // language string identifier for step-comparison
137 $js_step_comp_str = 'VBO_COMPARE_WITH_PREV_Q';
138 if ($step == 'month') {
139 $js_step_comp_str = 'VBO_COMPARE_WITH_PREV_M';
140 } elseif ($step == 'week') {
141 $js_step_comp_str = 'VBO_COMPARE_WITH_PREV_W';
142 } elseif (!strcasecmp($step, 'booking_dates')) {
143 $js_step_comp_str = 'VBO_COMPARE_WITH_PREV_D';
144 }
145
146 // start output buffering
147 ob_start();
148
149 if ($room_ids) {
150 // get the details of the rooms filtered
151 $rooms_filtered = VikBooking::getAvailabilityInstance()->loadRooms($room_ids);
152
153 if ($rooms_filtered) {
154 ?>
155 <div class="vbo-widget-finance-rooms-filtered-cont">
156 <?php echo implode(', ', array_column($rooms_filtered, 'name')); ?>
157 </div>
158 <?php
159 }
160 }
161
162 ?>
163 <div class="vbo-widget-finance-data-blocks">
164
165 <div class="vbo-widget-finance-data-block" data-typestat="gross_revenue">
166 <div class="vbo-widget-finance-stat">
167 <div class="vbo-widget-finance-stat-info">
168 <span class="vbo-widget-finance-stat-name"><?php echo JText::translate('VBO_GROSS_BOOKING_VALUE'); ?></span>
169 <span class="vbo-widget-finance-stat-cmd"><?php VikBookingIcons::e('ellipsis-v'); ?></span>
170 </div>
171 <div class="vbo-widget-finance-stat-amount">
172 <span class="vbo-widget-finance-stat-amount-value vbo-tooltip vbo-tooltip-top" data-tooltiptext="<?php echo VikBooking::formatCurrencyNumber(VikBooking::numberFormat($stats['gross_revenue']), $currencysymb); ?>">
173 <?php echo VikBooking::formatCurrencyNumber($finance->numberFormatShort($stats['gross_revenue']), $currencysymb, ['<span class="vbo-currency">%s</span>', '<span class="vbo-price">%s</span>']); ?>
174 </span>
175 </div>
176 </div>
177 </div>
178
179 <div class="vbo-widget-finance-data-block" data-typestat="taxes">
180 <div class="vbo-widget-finance-stat">
181 <div class="vbo-widget-finance-stat-info">
182 <span class="vbo-widget-finance-stat-name"><?php echo JText::translate('VBOINVCOLTAX'); ?></span>
183 <span class="vbo-widget-finance-stat-cmd"><?php VikBookingIcons::e('ellipsis-v'); ?></span>
184 </div>
185 <div class="vbo-widget-finance-stat-amount">
186 <span class="vbo-widget-finance-stat-amount-value vbo-tooltip vbo-tooltip-top" data-tooltiptext="<?php echo VikBooking::formatCurrencyNumber(VikBooking::numberFormat($stats['taxes']), $currencysymb); ?>">
187 <?php echo VikBooking::formatCurrencyNumber($finance->numberFormatShort($stats['taxes']), $currencysymb, ['<span class="vbo-currency">%s</span>', '<span class="vbo-price">%s</span>']); ?>
188 </span>
189 </div>
190 </div>
191 </div>
192
193 <div class="vbo-widget-finance-data-block" data-typestat="cmms">
194 <div class="vbo-widget-finance-stat">
195 <div class="vbo-widget-finance-stat-info">
196 <span class="vbo-widget-finance-stat-name"><?php echo JText::translate('VBTOTALCOMMISSIONS'); ?></span>
197 <span class="vbo-widget-finance-stat-cmd"><?php VikBookingIcons::e('ellipsis-v'); ?></span>
198 </div>
199 <div class="vbo-widget-finance-stat-amount">
200 <span class="vbo-widget-finance-stat-amount-value vbo-tooltip vbo-tooltip-top" data-tooltiptext="<?php echo VikBooking::formatCurrencyNumber(VikBooking::numberFormat($stats['cmms']), $currencysymb); ?>">
201 <?php echo VikBooking::formatCurrencyNumber($finance->numberFormatShort($stats['cmms']), $currencysymb, ['<span class="vbo-currency">%s</span>', '<span class="vbo-price">%s</span>']); ?>
202 </span>
203 </div>
204 </div>
205 </div>
206
207 <div class="vbo-widget-finance-data-block" data-typestat="revenue">
208 <div class="vbo-widget-finance-stat">
209 <div class="vbo-widget-finance-stat-info">
210 <span class="vbo-widget-finance-stat-name"><?php echo JText::translate('VBOREPORTREVENUE'); ?></span>
211 <span class="vbo-widget-finance-stat-cmd"><?php VikBookingIcons::e('ellipsis-v'); ?></span>
212 </div>
213 <div class="vbo-widget-finance-stat-amount">
214 <span class="vbo-widget-finance-stat-amount-value vbo-tooltip vbo-tooltip-top" data-tooltiptext="<?php echo VikBooking::formatCurrencyNumber(VikBooking::numberFormat($stats['revenue']), $currencysymb); ?>">
215 <?php echo VikBooking::formatCurrencyNumber($finance->numberFormatShort($stats['revenue']), $currencysymb, ['<span class="vbo-currency">%s</span>', '<span class="vbo-price">%s</span>']); ?>
216 </span>
217 </div>
218 </div>
219 </div>
220
221 <div class="vbo-widget-finance-data-block" data-typestat="ibe_revenue">
222 <div class="vbo-widget-finance-stat">
223 <div class="vbo-widget-finance-stat-info">
224 <span class="vbo-widget-finance-stat-name"><?php echo JText::translate('VBOREPORTREVENUEREVWEB'); ?></span>
225 <span class="vbo-widget-finance-stat-cmd"><?php VikBookingIcons::e('ellipsis-v'); ?></span>
226 </div>
227 <div class="vbo-widget-finance-stat-amount">
228 <span class="vbo-widget-finance-stat-amount-value vbo-tooltip vbo-tooltip-top" data-tooltiptext="<?php echo VikBooking::formatCurrencyNumber(VikBooking::numberFormat($stats['ibe_revenue']), $currencysymb); ?>">
229 <?php echo VikBooking::formatCurrencyNumber($finance->numberFormatShort($stats['ibe_revenue']), $currencysymb, ['<span class="vbo-currency">%s</span>', '<span class="vbo-price">%s</span>']); ?>
230 </span>
231 </div>
232 </div>
233 </div>
234
235 <div class="vbo-widget-finance-data-block" data-typestat="ota_revenue">
236 <div class="vbo-widget-finance-stat">
237 <div class="vbo-widget-finance-stat-info">
238 <span class="vbo-widget-finance-stat-name"><?php echo JText::translate('VBOREPORTREVENUEREVOTA'); ?></span>
239 <span class="vbo-widget-finance-stat-cmd"><?php VikBookingIcons::e('ellipsis-v'); ?></span>
240 </div>
241 <div class="vbo-widget-finance-stat-amount">
242 <span class="vbo-widget-finance-stat-amount-value vbo-tooltip vbo-tooltip-top" data-tooltiptext="<?php echo VikBooking::formatCurrencyNumber(VikBooking::numberFormat($stats['ota_revenue']), $currencysymb); ?>">
243 <?php echo VikBooking::formatCurrencyNumber($finance->numberFormatShort($stats['ota_revenue']), $currencysymb, ['<span class="vbo-currency">%s</span>', '<span class="vbo-price">%s</span>']); ?>
244 </span>
245 </div>
246 </div>
247 </div>
248
249 <?php
250 if ($stats['tot_vat'] > 0) {
251 ?>
252 <div class="vbo-widget-finance-data-block" data-typestat="tot_vat">
253 <div class="vbo-widget-finance-stat">
254 <div class="vbo-widget-finance-stat-info">
255 <span class="vbo-widget-finance-stat-name"><?php echo JText::translate('VBO_INV_VATGST'); ?></span>
256 <span class="vbo-widget-finance-stat-cmd"><?php VikBookingIcons::e('ellipsis-v'); ?></span>
257 </div>
258 <div class="vbo-widget-finance-stat-amount">
259 <span class="vbo-widget-finance-stat-amount-value vbo-tooltip vbo-tooltip-top" data-tooltiptext="<?php echo VikBooking::formatCurrencyNumber(VikBooking::numberFormat($stats['tot_vat']), $currencysymb); ?>">
260 <?php echo VikBooking::formatCurrencyNumber($finance->numberFormatShort($stats['tot_vat']), $currencysymb, ['<span class="vbo-currency">%s</span>', '<span class="vbo-price">%s</span>']); ?>
261 </span>
262 </div>
263 </div>
264 </div>
265 <?php
266 }
267 if ($stats['city_taxes'] > 0) {
268 ?>
269 <div class="vbo-widget-finance-data-block" data-typestat="city_taxes">
270 <div class="vbo-widget-finance-stat">
271 <div class="vbo-widget-finance-stat-info">
272 <span class="vbo-widget-finance-stat-name"><?php echo JText::translate('VBTOTALCITYTAX'); ?></span>
273 <span class="vbo-widget-finance-stat-cmd"><?php VikBookingIcons::e('ellipsis-v'); ?></span>
274 </div>
275 <div class="vbo-widget-finance-stat-amount">
276 <span class="vbo-widget-finance-stat-amount-value vbo-tooltip vbo-tooltip-top" data-tooltiptext="<?php echo VikBooking::formatCurrencyNumber(VikBooking::numberFormat($stats['city_taxes']), $currencysymb); ?>">
277 <?php echo VikBooking::formatCurrencyNumber($finance->numberFormatShort($stats['city_taxes']), $currencysymb, ['<span class="vbo-currency">%s</span>', '<span class="vbo-price">%s</span>']); ?>
278 </span>
279 </div>
280 </div>
281 </div>
282 <?php
283 }
284 if ($stats['cmm_savings'] > 0) {
285 ?>
286 <div class="vbo-widget-finance-data-block" data-typestat="ota_avg_cmms">
287 <div class="vbo-widget-finance-stat">
288 <div class="vbo-widget-finance-stat-info">
289 <span class="vbo-widget-finance-stat-name"><?php echo JText::translate('VBO_AVG_COMMISSIONS'); ?></span>
290 <span class="vbo-widget-finance-stat-cmd"><?php VikBookingIcons::e('ellipsis-v'); ?></span>
291 </div>
292 <div class="vbo-widget-finance-stat-amount">
293 <span class="vbo-widget-finance-stat-amount-value">
294 <span><?php echo $stats['ota_avg_cmms']; ?>%</span>
295 </span>
296 </div>
297 </div>
298 </div>
299
300 <div class="vbo-widget-finance-data-block" data-typestat="cmm_savings">
301 <div class="vbo-widget-finance-stat">
302 <div class="vbo-widget-finance-stat-info">
303 <span class="vbo-widget-finance-stat-name"><?php echo JText::translate('VBO_COMMISSION_SAVINGS'); ?></span>
304 <span class="vbo-widget-finance-stat-cmd"><?php VikBookingIcons::e('ellipsis-v'); ?></span>
305 </div>
306 <div class="vbo-widget-finance-stat-amount">
307 <span class="vbo-widget-finance-stat-amount-value vbo-tooltip vbo-tooltip-top" data-tooltiptext="<?php echo VikBooking::formatCurrencyNumber(VikBooking::numberFormat($stats['cmm_savings']), $currencysymb); ?>">
308 <?php echo VikBooking::formatCurrencyNumber($finance->numberFormatShort($stats['cmm_savings']), $currencysymb, ['<span class="vbo-currency">%s</span>', '<span class="vbo-price">%s</span>']); ?>
309 </span>
310 </div>
311 </div>
312 </div>
313 <?php
314 }
315
316 /**
317 * Return the information about the damage deposits collected/held.
318 *
319 * @since 1.18.0 (J) - 1.8.0 (WP)
320 */
321 if ($stats['damage_deposits'] > 0) {
322 ?>
323 <div class="vbo-widget-finance-data-block" data-typestat="damage_deposits">
324 <div class="vbo-widget-finance-stat">
325 <div class="vbo-widget-finance-stat-info">
326 <span class="vbo-widget-finance-stat-name"><?php echo JText::translate('VBO_TOT_DAMAGE_DEPOSITS'); ?></span>
327 <span class="vbo-widget-finance-stat-cmd"><?php VikBookingIcons::e('ellipsis-v'); ?></span>
328 </div>
329 <div class="vbo-widget-finance-stat-amount">
330 <span class="vbo-widget-finance-stat-amount-value vbo-tooltip vbo-tooltip-top" data-tooltiptext="<?php echo VikBooking::formatCurrencyNumber(VikBooking::numberFormat($stats['damage_deposits']), $currencysymb); ?>">
331 <?php echo VikBooking::formatCurrencyNumber($finance->numberFormatShort($stats['damage_deposits']), $currencysymb, ['<span class="vbo-currency">%s</span>', '<span class="vbo-price">%s</span>']); ?>
332 </span>
333 </div>
334 </div>
335 </div>
336 <?php
337 }
338 ?>
339
340 <div class="vbo-widget-finance-data-block" data-typestat="tot_bookings">
341 <div class="vbo-widget-finance-stat">
342 <div class="vbo-widget-finance-stat-info">
343 <span class="vbo-widget-finance-stat-name"><?php echo JText::translate('VBCUSTOMERTOTBOOKINGS'); ?></span>
344 <span class="vbo-widget-finance-stat-cmd"><?php VikBookingIcons::e('ellipsis-v'); ?></span>
345 </div>
346 <div class="vbo-widget-finance-stat-amount">
347 <span class="vbo-widget-finance-stat-amount-value">
348 <span><?php echo $stats['tot_bookings']; ?></span>
349 </span>
350 </div>
351 </div>
352 </div>
353
354 <div class="vbo-widget-finance-data-block" data-typestat="nights_booked">
355 <div class="vbo-widget-finance-stat">
356 <div class="vbo-widget-finance-stat-info">
357 <span class="vbo-widget-finance-stat-name"><?php echo JText::translate('VBOGRAPHTOTNIGHTSLBL'); ?></span>
358 <span class="vbo-widget-finance-stat-cmd"><?php VikBookingIcons::e('ellipsis-v'); ?></span>
359 </div>
360 <div class="vbo-widget-finance-stat-amount">
361 <span class="vbo-widget-finance-stat-amount-value vbo-tooltip vbo-tooltip-top" data-tooltiptext="<?php echo htmlspecialchars(trim(str_replace('%', '', JText::translate('VBOREPORTREVENUEPOCC'))) . ' ' . $stats['occupancy'] . '%'); ?>">
362 <span><?php echo $stats['nights_booked'] . '/' . $stats['tot_inventory']; ?></span>
363 </span>
364 </div>
365 </div>
366 </div>
367
368 <div class="vbo-widget-finance-data-block" data-typestat="avg_los">
369 <div class="vbo-widget-finance-stat">
370 <div class="vbo-widget-finance-stat-info">
371 <span class="vbo-widget-finance-stat-name"><?php echo JText::translate('VBTRKAVGLOS'); ?></span>
372 <span class="vbo-widget-finance-stat-cmd"><?php VikBookingIcons::e('ellipsis-v'); ?></span>
373 </div>
374 <div class="vbo-widget-finance-stat-amount">
375 <span class="vbo-widget-finance-stat-amount-value">
376 <span><?php echo $stats['avg_los']; ?></span>
377 </span>
378 </div>
379 </div>
380 </div>
381
382 <div class="vbo-widget-finance-data-block" data-typestat="abw">
383 <div class="vbo-widget-finance-stat">
384 <div class="vbo-widget-finance-stat-info">
385 <span class="vbo-widget-finance-stat-name"><?php echo JText::translate('VBO_AVG_BOOK_WINDOW'); ?></span>
386 <span class="vbo-widget-finance-stat-cmd"><?php VikBookingIcons::e('ellipsis-v'); ?></span>
387 </div>
388 <div class="vbo-widget-finance-stat-amount">
389 <span class="vbo-widget-finance-stat-amount-value vbo-tooltip vbo-tooltip-top" data-tooltiptext="<?php echo htmlspecialchars(JText::translate('VBOCRONSMSREMPARAMBEFD')); ?>">
390 <span><?php echo round($stats['abw'], 1); ?></span>
391 </span>
392 </div>
393 </div>
394 </div>
395
396 <div class="vbo-widget-finance-data-block" data-typestat="rooms_booked">
397 <div class="vbo-widget-finance-stat">
398 <div class="vbo-widget-finance-stat-info">
399 <span class="vbo-widget-finance-stat-name"><?php echo JText::translate('VBLIBTEN'); ?></span>
400 <span class="vbo-widget-finance-stat-cmd"><?php VikBookingIcons::e('ellipsis-v'); ?></span>
401 </div>
402 <div class="vbo-widget-finance-stat-amount">
403 <span class="vbo-widget-finance-stat-amount-value vbo-tooltip vbo-tooltip-top" data-tooltiptext="<?php echo htmlspecialchars(JText::translate('VBOGRAPHTOTUNITSLBL') . ' ' . $stats['room_units']); ?>">
404 <span><?php echo $stats['rooms_booked']; ?></span>
405 </span>
406 </div>
407 </div>
408 </div>
409
410 <div class="vbo-widget-finance-data-block" data-typestat="cancellations_amt">
411 <div class="vbo-widget-finance-stat">
412 <div class="vbo-widget-finance-stat-info">
413 <span class="vbo-widget-finance-stat-name"><?php echo JText::translate('VBO_CANCELLATIONS'); ?></span>
414 <span class="vbo-widget-finance-stat-cmd"><?php VikBookingIcons::e('ellipsis-v'); ?></span>
415 </div>
416 <div class="vbo-widget-finance-stat-amount">
417 <span class="vbo-widget-finance-stat-amount-value vbo-tooltip vbo-tooltip-top" data-tooltiptext="<?php echo VikBooking::formatCurrencyNumber(VikBooking::numberFormat($stats['cancellations_amt']), $currencysymb) . ($stats['tot_cancellations'] ? ' (' . $stats['tot_cancellations'] . ')' : ''); ?>">
418 <?php echo VikBooking::formatCurrencyNumber($finance->numberFormatShort($stats['cancellations_amt']), $currencysymb, ['<span class="vbo-currency">%s</span>', '<span class="vbo-price">%s</span>']); ?>
419 </span>
420 </div>
421 </div>
422 </div>
423
424 </div>
425
426 <div class="vbo-widget-finance-data-block-rankings">
427
428 <div class="vbo-widget-finance-data-block-rank" data-typestat="country_ranks">
429 <div class="vbo-widget-finance-stat">
430 <div class="vbo-widget-finance-stat-info">
431 <span class="vbo-widget-finance-stat-name"><?php echo JText::translate('VBOSTATSTOPCOUNTRIES'); ?></span>
432 </div>
433 <div class="vbo-widget-finance-stat-ranks">
434 <?php
435 foreach ($stats['country_ranks'] as $country_rank) {
436 ?>
437 <div class="vbo-widget-finance-stat-rank">
438 <div class="vbo-widget-finance-stat-rank-logo">
439 <?php
440 if (is_file(VBO_ADMIN_PATH . DIRECTORY_SEPARATOR . 'resources' . DIRECTORY_SEPARATOR . 'countries' . DIRECTORY_SEPARATOR . $country_rank['code'] . '.png')) {
441 ?>
442 <img src="<?php echo VBO_ADMIN_URI . 'resources/countries/' . $country_rank['code'] . '.png'; ?>" />
443 <?php
444 } else {
445 VikBookingIcons::e('globe');
446 }
447 ?>
448 </div>
449 <div class="vbo-widget-finance-stat-rank-score">
450 <div class="vbo-widget-finance-stat-rank-name">
451 <span><?php echo $country_rank['name']; ?></span>
452 </div>
453 <div class="vbo-widget-finance-stat-rank-amount">
454 <?php
455 echo VikBooking::formatCurrencyNumber(
456 $finance->numberFormatShort($country_rank['revenue']),
457 $currencysymb,
458 [
459 '<span class="vbo-currency">%s</span>',
460 '<span class="vbo-price vbo-tooltip vbo-tooltip-top" data-tooltiptext="' . VikBooking::formatCurrencyNumber(VikBooking::numberFormat($country_rank['revenue']), $currencysymb) . '">%s</span>',
461 ]
462 );
463 ?>
464 </div>
465 <div class="vbo-widget-finance-stat-rank-pcent">
466 <progress class="vbo-widget-finance-progress" value="<?php echo $country_rank['pcent']; ?>" max="100"><?php echo $country_rank['pcent']; ?>%</progress>
467 </div>
468 </div>
469 </div>
470 <?php
471 }
472 ?>
473 </div>
474 </div>
475 </div>
476
477 <div class="vbo-widget-finance-data-block-rank" data-typestat="pos_revenue">
478 <div class="vbo-widget-finance-stat">
479 <div class="vbo-widget-finance-stat-info">
480 <span class="vbo-widget-finance-stat-name"><?php echo JText::translate('VBOCHANNELS'); ?></span>
481 </div>
482 <div class="vbo-widget-finance-stat-ranks">
483 <?php
484 foreach ($stats['pos_revenue'] as $pos_revenue) {
485 $say_pos_name = ucfirst($pos_revenue['name']);
486 ?>
487 <div class="vbo-widget-finance-stat-rank">
488 <div class="vbo-widget-finance-stat-rank-logo">
489 <?php
490 if (!empty($pos_revenue['logo'])) {
491 ?>
492 <img src="<?php echo $pos_revenue['logo']; ?>" />
493 <?php
494 // adjust channel name
495 $say_pos_name = strtolower($pos_revenue['name']) == 'airbnbapi' ? 'Airbnb' : $say_pos_name;
496 $say_pos_name = strtolower($pos_revenue['name']) == 'googlehotel' ? 'Google Hotel' : $say_pos_name;
497 } elseif (!strcasecmp($pos_revenue['name'], 'website')) {
498 VikBookingIcons::e('hotel');
499 // adjust channel name
500 $say_pos_name = JText::translate('VBORDFROMSITE');
501 } else {
502 VikBookingIcons::e('globe');
503 }
504 ?>
505 </div>
506 <div class="vbo-widget-finance-stat-rank-score">
507 <div class="vbo-widget-finance-stat-rank-name">
508 <span><?php echo $say_pos_name; ?></span>
509 </div>
510 <div class="vbo-widget-finance-stat-rank-amount">
511 <?php
512 echo VikBooking::formatCurrencyNumber(
513 $finance->numberFormatShort($pos_revenue['revenue']),
514 $currencysymb,
515 [
516 '<span class="vbo-currency">%s</span>',
517 '<span class="vbo-price vbo-tooltip vbo-tooltip-top" data-tooltiptext="' . VikBooking::formatCurrencyNumber(VikBooking::numberFormat($pos_revenue['revenue']), $currencysymb) . '">%s</span>',
518 ]
519 );
520 ?>
521 </div>
522 <div class="vbo-widget-finance-stat-rank-pcent">
523 <progress class="vbo-widget-finance-progress" value="<?php echo $pos_revenue['pcent']; ?>" max="100"><?php echo $pos_revenue['pcent']; ?>%</progress>
524 </div>
525 </div>
526 </div>
527 <?php
528 }
529 ?>
530 </div>
531 </div>
532 </div>
533
534 </div>
535
536 <input type="hidden" class="vbo-widget-finance-json-stats" value="" />
537 <input type="hidden" class="vbo-widget-finance-stats-from" value="" />
538 <input type="hidden" class="vbo-widget-finance-stats-to" value="" />
539
540 <script type="text/javascript">
541 jQuery(function() {
542
543 let vbo_wfinance_compare_auto = jQuery('#<?php echo $wrapper; ?>').find('.vbo-finance-compare-auto').val();
544
545 let vbo_wfinance_ctx_btns = [
546 {
547 icon: '<?php echo VikBookingIcons::i('balance-scale-left'); ?>',
548 text: Joomla.JText._('<?php echo $js_step_comp_str; ?>'),
549 separator: false,
550 action: (root, config) => {
551 vboWidgetFinanceCompare('<?php echo $wrapper; ?>', '<?php echo $step; ?>');
552 },
553 },
554 {
555 icon: '<?php echo VikBookingIcons::i('balance-scale-right'); ?>',
556 text: Joomla.JText._('VBO_COMPARE_WITH_LAST_Y'),
557 separator: false,
558 action: (root, config) => {
559 vboWidgetFinanceCompare('<?php echo $wrapper; ?>', 'year');
560 },
561 },
562 ];
563
564 if (vbo_wfinance_compare_auto.length) {
565 vbo_wfinance_ctx_btns[1].separator = true;
566 vbo_wfinance_ctx_btns.push({
567 icon: '<?php echo VikBookingIcons::i('ban'); ?>',
568 text: Joomla.JText._((vbo_wfinance_compare_auto == 'year' ? 'VBO_COMPARE_WITH_LAST_Y' : '<?php echo $js_step_comp_str; ?>')),
569 class: 'vbo-context-menu-entry-danger',
570 separator: false,
571 action: (root, config) => {
572 // turn off the flag to automatically compare data
573 jQuery('#<?php echo $wrapper; ?>').find('.vbo-finance-compare-auto').val('');
574 // reload data
575 vboWidgetFinanceLoad('<?php echo $wrapper; ?>');
576 },
577 });
578 }
579
580 jQuery('#<?php echo $wrapper; ?>').find('.vbo-widget-finance-stat-cmd').vboContextMenu({
581 placement: 'bottom-right',
582 buttons: vbo_wfinance_ctx_btns,
583 });
584 });
585 </script>
586
587 <?php
588
589 // get the HTML buffer
590 $html_content = ob_get_contents();
591 ob_end_clean();
592
593 // take care of the readable dates interval
594 list($format_from_date, $format_to_date) = $this->parseReadableDateInterval($from_ts, $to_ts, $this->df, $step);
595
596 // return an associative array of values
597 return [
598 'html' => $html_content,
599 'fromdate' => $fromdate,
600 'f_fromdate' => $format_from_date,
601 'todate' => $todate,
602 'f_todate' => $format_to_date,
603 'step' => $step,
604 'step_name' => $step_name,
605 'stats' => $stats,
606 ];
607 }
608
609 /**
610 * Custom method for this widget only to load the comparison values.
611 * The method is called by the admin controller through an AJAX request.
612 * The visibility should be public, it should not exit the process, and
613 * any content sent to output will be returned to the AJAX response.
614 * In this case we return an array because this method requires "return":1.
615 *
616 * Returns the necessary HTML to display comparison values with past dates.
617 */
618 public function loadComparisonStats()
619 {
620 $app = JFactory::getApplication();
621
622 $wrapper = $app->input->getString('wrapper', '');
623
624 $fromdate = $app->input->getString('fromdate', '');
625 $todate = $app->input->getString('todate', '');
626 $step = $app->input->getString('step', 'quarter');
627 $prev_stats = $app->input->get('stats', [], 'array');
628 $date_dir = -1;
629
630 if (empty($fromdate) || empty($todate) || empty($prev_stats)) {
631 // make the AJAX request fail nicely
632 VBOHttpDocument::getInstance()->close(500, 'Missing data to calculate comparison values');
633 }
634
635 // convert dates to timestamps to support custom date formats
636 $from_ts = VikBooking::getDateTimestamp($fromdate);
637 $to_ts = VikBooking::getDateTimestamp($todate, 23, 59, 59);
638
639 // stats calculation type
640 $calc_type = 'stay_dates';
641 if (!strcasecmp($step, 'booking_dates')) {
642 $calc_type = 'booking_dates';
643 }
644
645 // calculate backward dates for comparison
646 list($from_ts, $to_ts, $step_name) = $this->calcDatesNavigation($from_ts, $to_ts, $step, $date_dir);
647
648 // convert final dates to Y-m-d
649 $fromdate = date('Y-m-d', $from_ts);
650 $todate = date('Y-m-d', $to_ts);
651
652 // access the finance helper object
653 $finance = VBOTaxonomyFinance::getInstance();
654
655 // currency symbol
656 $currencysymb = VikBooking::getCurrencySymb();
657
658 // get the financial stats for the requested dates and use them for comparison
659 try {
660 $compare_stats = $finance->getStats($fromdate, $todate, [], $calc_type);
661 } catch (Exception $e) {
662 // make the AJAX request fail nicely
663 VBOHttpDocument::getInstance()->close($e->getCode(), $e->getMessage());
664 }
665
666 // define the "compare vs" string
667 switch ($step) {
668 case 'year':
669 $compare_vs_str = JText::translate('VBO_VS_LAST_Y');
670 break;
671 case 'quarter':
672 $compare_vs_str = JText::translate('VBO_VS_PREV_Q');
673 break;
674 case 'month':
675 $compare_vs_str = JText::translate('VBO_VS_PREV_M');
676 break;
677 case 'week':
678 $compare_vs_str = JText::translate('VBO_VS_PREV_W');
679 break;
680 case 'booking_dates':
681 $compare_vs_str = JText::translate('VBO_VS_PREV_D');
682 break;
683 default:
684 $compare_vs_str = '';
685 break;
686 }
687
688 // build comparison values
689 $comparison = [];
690
691 // stats that require percent values for comparison
692 $pcent_stats = [
693 'gross_revenue' => [],
694 'taxes' => [],
695 'tot_vat' => [],
696 'city_taxes' => [],
697 'cmms' => [
698 'reverse' => 1,
699 ],
700 'revenue' => [],
701 'ibe_revenue' => [],
702 'ota_revenue' => [],
703 'ota_avg_cmms' => [
704 'reverse' => 1,
705 'no_pcent' => 1,
706 'fixednum' => 1,
707 ],
708 'cmm_savings' => [],
709 'damage_deposits' => [],
710 'tot_bookings' => [
711 'fixednum' => 1,
712 ],
713 'nights_booked' => [
714 'fixednum' => 1,
715 ],
716 'avg_los' => [
717 'fixednum' => 1,
718 ],
719 'abw' => [
720 'fixednum' => 1,
721 ],
722 'rooms_booked' => [
723 'fixednum' => 1,
724 ],
725 'cancellations_amt' => [
726 'reverse' => 1,
727 ],
728 ];
729
730 foreach ($pcent_stats as $stat_name => $stat_opt) {
731 if (!isset($compare_stats[$stat_name])) {
732 continue;
733 }
734
735 // make sure the previous value is set
736 $prev_stats[$stat_name] = !isset($prev_stats[$stat_name]) ? 0 : $prev_stats[$stat_name];
737
738 // difference
739 $diff = isset($stat_opt['reverse']) ? ($compare_stats[$stat_name] - $prev_stats[$stat_name]) : ($prev_stats[$stat_name] - $compare_stats[$stat_name]);
740
741 // calculate values
742 $comparison[$stat_name] = [
743 'amount' => $compare_stats[$stat_name],
744 'amount_f' => VikBooking::numberFormat($compare_stats[$stat_name]),
745 'amount_s' => $finance->numberFormatShort($compare_stats[$stat_name]),
746 'diff' => $diff,
747 'diff_f' => VikBooking::numberFormat($diff),
748 'diff_s' => $finance->numberFormatShort($diff),
749 'pcent' => isset($stat_opt['no_pcent']) ? null : $finance->calcAbsPercent($prev_stats[$stat_name], $compare_stats[$stat_name]),
750 'reverse' => $stat_opt['reverse'] ?? false,
751 ];
752
753 // inject property for fixed number with no currency
754 if (isset($stat_opt['fixednum']) && $stat_opt['fixednum']) {
755 $comparison[$stat_name]['fixednum'] = $stat_opt['fixednum'];
756 }
757 }
758
759 // return an associative array of values
760 return [
761 'no_data' => ($compare_stats['tot_bookings'] < 1 ? 1 : 0),
762 'compare' => $comparison,
763 'vs_str' => $compare_vs_str,
764 'currency' => $currencysymb,
765 'stats' => $compare_stats,
766 ];
767 }
768
769 /**
770 * Custom method for this widget only to retrieve the daily
771 * welcome statistics about the bookings of the day before.
772 *
773 * @return array the associative list of statistics.
774 */
775 public function getWelcomeStats()
776 {
777 // get yesterday's date
778 $yesterday = date('Y-m-d', strtotime('yesterday'));
779
780 // access the finance helper object
781 $finance = VBOTaxonomyFinance::getInstance();
782
783 // get the financial stats for the requested dates
784 try {
785 $stats = $finance->getStats($yesterday, $yesterday, $rooms = [], $type = 'booking_dates');
786 } catch (Exception $e) {
787 // make the AJAX request fail nicely
788 VBOHttpDocument::getInstance()->close($e->getCode(), $e->getMessage());
789 }
790
791 return $stats;
792 }
793
794
795 /**
796 * Main method to invoke the widget. Contents will be loaded
797 * through AJAX requests, not via PHP when the page loads.
798 *
799 * @param ?VBOMultitaskData $data
800 *
801 * @return void
802 */
803 public function render(?VBOMultitaskData $data = null)
804 {
805 // increase widget's instance counter
806 static::$instance_counter++;
807
808 // check whether the widget is being rendered via AJAX when adding it through the customizer
809 $is_ajax = $this->isAjaxRendering();
810
811 // generate a unique ID for the sticky notes wrapper instance
812 $wrapper_instance = !$is_ajax ? static::$instance_counter : rand();
813 $wrapper_id = 'vbo-widget-finance-' . $wrapper_instance;
814
815 // check permissions
816 $vbo_auth_pricing = JFactory::getUser()->authorise('core.vbo.pricing', 'com_vikbooking');
817 $vbo_auth_bookings = JFactory::getUser()->authorise('core.vbo.bookings', 'com_vikbooking');
818 if (!$vbo_auth_pricing || !$vbo_auth_bookings) {
819 // base permissions are not met
820 return;
821 }
822
823 // we make use of the rates flow and occupancy ranking report helper classes
824 $report = VikBooking::getReportInstance('rates_flow');
825 if (!$report) {
826 // display nothing
827 return;
828 }
829
830 // get minimum and maximum nights updated for dates filters
831 list($mindate, $maxdate) = $this->getMinDatesFinance();
832 $mindate = empty($mindate) ? time() : $mindate;
833 $maxdate = empty($maxdate) ? $mindate : $maxdate;
834
835 // dates navigation preference
836 $cookie = JFactory::getApplication()->input->cookie;
837 $cookie_step = $cookie->get('vbo_widget_finance_step', 'quarter', 'string');
838
839 // determine default period name and dates
840 $df = $report->getDateFormat();
841 $dtpicker_df = $report->getDateFormat('jui');
842 $now_info = getdate();
843 if ($cookie_step == 'week') {
844 // next week
845 $from_ts = mktime(0, 0, 0, $now_info['mon'], $now_info['mday'], $now_info['year']);
846 $to_ts = mktime(0, 0, 0, $now_info['mon'], ($now_info['mday'] + 7), $now_info['year']);
847 $fromdate = date($df, $from_ts);
848 $todate = date($df, $to_ts);
849 $period_name = JText::translate('VBOWEEK');
850 } elseif ($cookie_step == 'month') {
851 // current full month
852 $from_ts = mktime(0, 0, 0, $now_info['mon'], 1, $now_info['year']);
853 $fromdate = date($df, $from_ts);
854 $to_ts = mktime(23, 59, 59, $now_info['mon'], date('t', $now_info[0]), $now_info['year']);
855 $todate = date($df, $to_ts);
856 $period_name = JText::translate('VBPVIEWRESTRICTIONSTWO');
857 } else {
858 // default to next 3 (total) months from today's month
859 $from_ts = mktime(0, 0, 0, $now_info['mon'], 1, $now_info['year']);
860 $fromdate = date($df, $from_ts);
861 $end_info = getdate(mktime(0, 0, 0, ($now_info['mon'] + 2), 1, $now_info['year']));
862 $to_ts = mktime(23, 59, 59, $end_info['mon'], date('t', $end_info[0]), $end_info['year']);
863 $todate = date($df, $to_ts);
864 $period_name = JText::translate('VBO_QUARTER');
865 }
866
867 // check multitask data
868 $rooms_filtered = [];
869 if ($data) {
870 $inj_fromdate = $data->get('fromdate', $this->options()->get('fromdate', ''));
871 $inj_todate = $data->get('todate', $this->options()->get('todate', ''));
872 $inj_type = $data->get('type', $this->options()->get('type', ''));
873
874 // check if some room IDs were filtered
875 $rooms_filtered = (array) $data->get('room_ids', $this->options()->get('room_ids', []));
876
877 if (preg_match("/^\d{4}-\d{2}-\d{2}$/", $inj_fromdate) && preg_match("/^\d{4}-\d{2}-\d{2}$/", $inj_todate)) {
878 // valid dates injected in Y-m-d format
879 $fromdate = $inj_fromdate;
880 $todate = $inj_todate;
881 $to_info = getdate(strtotime($todate));
882 $from_ts = strtotime($fromdate);
883 $to_ts = mktime(23, 59, 59, $to_info['mon'], $to_info['mday'], $to_info['year']);
884 if (!empty($inj_type)) {
885 $cookie_step = $inj_type;
886 if (!strcasecmp($inj_type, 'booking_dates')) {
887 $period_name = JText::translate('VBOSTATSMODETS');
888 }
889 }
890 }
891 }
892
893 // take care of the readable dates interval
894 list($format_from_date, $format_to_date) = $this->parseReadableDateInterval($from_ts, $to_ts, $df, $cookie_step);
895 $interval_name = $format_from_date . ' - ' . $format_to_date;
896 if ($format_from_date == $format_to_date) {
897 $interval_name = $format_from_date;
898 }
899
900 ?>
901 <div id="<?php echo $wrapper_id; ?>" class="vbo-admin-widget-wrapper" data-instance="<?php echo $wrapper_instance; ?>">
902 <div class="vbo-admin-widget-head">
903 <div class="vbo-admin-widget-head-inline">
904 <h4><?php echo $this->widgetIcon; ?> <span><?php echo $this->widgetName; ?></span></h4>
905 <div class="vbo-admin-widget-head-commands">
906
907 <div class="vbo-reportwidget-commands">
908 <div class="vbo-reportwidget-commands-main">
909 <div class="vbo-reportwidget-command-dates">
910 <div class="vbo-reportwidget-period-name"><?php echo $period_name; ?></div>
911 <div class="vbo-reportwidget-period-date"><?php echo $interval_name; ?></div>
912 </div>
913 <div class="vbo-reportwidget-command-chevron vbo-reportwidget-command-prev">
914 <span class="vbo-widget-finance-dt-prev" onclick="vboWidgetFinanceDatesNav('<?php echo $wrapper_id; ?>', -1);"><?php VikBookingIcons::e('chevron-left'); ?></span>
915 </div>
916 <div class="vbo-reportwidget-command-chevron vbo-reportwidget-command-next">
917 <span class="vbo-widget-finance-dt-next" onclick="vboWidgetFinanceDatesNav('<?php echo $wrapper_id; ?>', 1);"><?php VikBookingIcons::e('chevron-right'); ?></span>
918 </div>
919 </div>
920 <div class="vbo-reportwidget-command-dots">
921 <span class="vbo-widget-command-togglefilters vbo-widget-finance-togglefilters" onclick="vboWidgetFinanceToggleFilters('<?php echo $wrapper_id; ?>');"><?php VikBookingIcons::e('ellipsis-v'); ?></span>
922 </div>
923 </div>
924 <div class="vbo-reportwidget-filters">
925 <div class="vbo-reportwidget-filter">
926 <span class="vbo-reportwidget-datepicker">
927 <?php VikBookingIcons::e('calendar', 'vbo-widget-finance-caltrigger'); ?>
928 <input type="text" class="vbo-finance-dtpicker-from" value="<?php echo $fromdate; ?>" />
929 </span>
930 </div>
931 <div class="vbo-reportwidget-filter">
932 <span class="vbo-reportwidget-datepicker">
933 <?php VikBookingIcons::e('calendar', 'vbo-widget-finance-caltrigger'); ?>
934 <input type="text" class="vbo-finance-dtpicker-to" value="<?php echo $todate; ?>" />
935 </span>
936 </div>
937 <div class="vbo-reportwidget-filter">
938 <select class="vbo-finance-period-nav" onchange="vboWidgetFinanceChangePeriod(this.value);">
939 <option value="week"<?php echo $cookie_step == 'week' ? ' selected="selected"' : ''; ?>><?php echo JText::translate('VBOWEEK'); ?></option>
940 <option value="month"<?php echo $cookie_step == 'month' ? ' selected="selected"' : ''; ?>><?php echo JText::translate('VBPVIEWRESTRICTIONSTWO'); ?></option>
941 <option value="quarter"<?php echo $cookie_step == 'quarter' ? ' selected="selected"' : ''; ?>><?php echo JText::translate('VBO_QUARTER'); ?></option>
942 <option value="booking_dates"<?php echo !strcasecmp($cookie_step, 'booking_dates') ? ' selected="selected"' : ''; ?>><?php echo JText::translate('VBPCHOOSEBUSYORDATE'); ?></option>
943 </select>
944 </div>
945 <div class="vbo-reportwidget-filter vbo-widget-finance-rooms-filter-wrap">
946 <select class="vbo-widget-finance-rooms-filter" multiple="multiple">
947 <?php
948 foreach ($rooms_filtered as $room_filtered) {
949 if ($room_info = VikBooking::getRoomInfo($room_filtered, ['id', 'name'], true)) {
950 ?>
951 <option value="<?php echo $room_info['id']; ?>" selected="selected"><?php echo $room_info['name']; ?></option>
952 <?php
953 }
954 }
955 ?>
956 </select>
957 </div>
958 <div class="vbo-reportwidget-filter vbo-reportwidget-filter-confirm">
959 <input type="hidden" class="vbo-finance-compare-auto" value="" />
960 <button type="button" class="btn vbo-config-btn" onclick="vboWidgetFinanceChangeDates('<?php echo $wrapper_id; ?>');"><?php echo JText::translate('VBADMINNOTESUPD'); ?></button>
961 </div>
962 </div>
963
964 </div>
965 </div>
966 </div>
967 <div class="vbo-widget-finance-wrap">
968 <div class="vbo-widget-finance-inner">
969 <div class="vbo-widget-finance-list">
970 <div class="vbo-widget-finance-skeleton-blocks">
971 <?php
972 // display a few loading skeletons
973 for ($i = 0; $i < 10; $i++) {
974 ?>
975 <div class="vbo-widget-finance-skeleton-block">
976 <div class="vbo-widget-finance-skeleton-top">
977 <div class="vbo-skeleton-loading vbo-skeleton-loading-finance-top"></div>
978 </div>
979 <div class="vbo-widget-finance-skeleton-bottom">
980 <div class="vbo-skeleton-loading vbo-skeleton-loading-finance-bottom"></div>
981 </div>
982 </div>
983 <?php
984 }
985 ?>
986 </div>
987 </div>
988 </div>
989 </div>
990 </div>
991 <?php
992
993 if (static::$instance_counter === 0 || $is_ajax) {
994 /**
995 * Print the JS code only once for all instances of this widget.
996 * The real rendering is made through AJAX, not when the page loads.
997 */
998 ?>
999 <script type="text/javascript">
1000
1001 /**
1002 * Display the loading skeletons.
1003 */
1004 function vboWidgetFinanceSkeletons(wrapper) {
1005 var widget_instance = jQuery('#' + wrapper);
1006 if (!widget_instance.length) {
1007 return false;
1008 }
1009 widget_instance.find('.vbo-widget-finance-list').html('');
1010 var skeleton = '';
1011 skeleton += '<div class="vbo-widget-finance-skeleton-blocks">' + "\n";
1012 for (var i = 0; i < 10; i++) {
1013 skeleton += '<div class="vbo-widget-finance-skeleton-block">';
1014 skeleton += ' <div class="vbo-widget-finance-skeleton-top">';
1015 skeleton += ' <div class="vbo-skeleton-loading vbo-skeleton-loading-finance-top"></div>';
1016 skeleton += ' </div>';
1017 skeleton += ' <div class="vbo-widget-finance-skeleton-bottom">';
1018 skeleton += ' <div class="vbo-skeleton-loading vbo-skeleton-loading-finance-bottom"></div>';
1019 skeleton += ' </div>';
1020 skeleton += '</div>' + "\n";
1021 }
1022 skeleton += '</div>' + "\n";
1023 // append skeletons
1024 jQuery(skeleton).appendTo(widget_instance.find('.vbo-widget-finance-list'));
1025 }
1026
1027 /**
1028 * Perform the request to load the financial stats.
1029 */
1030 function vboWidgetFinanceLoad(wrapper, dates_direction) {
1031 var widget_instance = jQuery('#' + wrapper);
1032 if (!widget_instance.length) {
1033 return false;
1034 }
1035
1036 // check if a navigation of dates was requested (0 = no dates nav)
1037 if (typeof dates_direction === 'undefined') {
1038 dates_direction = 0;
1039 }
1040
1041 // get vars for making the request
1042 var from_date = widget_instance.find('.vbo-finance-dtpicker-from').val();
1043 var to_date = widget_instance.find('.vbo-finance-dtpicker-to').val();
1044 var dates_step = widget_instance.find('.vbo-finance-period-nav').val();
1045 var room_ids = widget_instance.find('select.vbo-widget-finance-rooms-filter').val();
1046 var auto_compare = widget_instance.find('.vbo-finance-compare-auto').val();
1047
1048 // the widget method to call
1049 var call_method = 'loadFinancialStats';
1050
1051 // make a request to load the financial stats
1052 VBOCore.doAjax(
1053 "<?php echo $this->getExecWidgetAjaxUri(); ?>",
1054 {
1055 widget_id: "<?php echo $this->getIdentifier(); ?>",
1056 call: call_method,
1057 return: 1,
1058 fromdate: from_date,
1059 todate: to_date,
1060 step: dates_step,
1061 room_ids: room_ids,
1062 date_dir: dates_direction,
1063 wrapper: wrapper,
1064 tmpl: "component"
1065 },
1066 (response) => {
1067 try {
1068 var obj_res = typeof response === 'string' ? JSON.parse(response) : response;
1069 if (!obj_res.hasOwnProperty(call_method)) {
1070 console.error('Unexpected JSON response', obj_res);
1071 return false;
1072 }
1073
1074 // replace HTML with new stats
1075 widget_instance.find('.vbo-widget-finance-list').html(obj_res[call_method]['html']);
1076
1077 // populate JSON data for comparison
1078 widget_instance.find('input.vbo-widget-finance-json-stats').val(JSON.stringify(obj_res[call_method]['stats']));
1079 widget_instance.find('input.vbo-widget-finance-stats-from').val(obj_res[call_method]['fromdate']);
1080 widget_instance.find('input.vbo-widget-finance-stats-to').val(obj_res[call_method]['todate']);
1081
1082 if (dates_direction > 0 || dates_direction < 0) {
1083 // set new dates calculated - note: if maxDate or minDate is exceeded, the datepicker calendars won't update!
1084 try {
1085 // construct a solid date object without using the raw Y-m-d string as only argument
1086 var dfrom_parts = obj_res[call_method]['fromdate'].split('-');
1087 var dfrom_obj = new Date(parseInt(dfrom_parts[0]), (parseInt(dfrom_parts[1]) - 1), parseInt(dfrom_parts[2]), 0, 0, 0);
1088 widget_instance.find('.vbo-finance-dtpicker-from').datepicker('setDate', dfrom_obj);
1089
1090 // restore the original minimum and maximum dates for the "to date" calendar to avoid issues setting a date
1091 widget_instance.find('.vbo-finance-dtpicker-to').datepicker('option', {
1092 minDate: "<?php echo date($df, $mindate); ?>",
1093 maxDate: "<?php echo date($df, $maxdate); ?>",
1094 });
1095
1096 // do the same for the to date by using a precise date object instance
1097 var dto_parts = obj_res[call_method]['todate'].split('-');
1098 var dto_obj = new Date(parseInt(dto_parts[0]), (parseInt(dto_parts[1]) - 1), parseInt(dto_parts[2]), 0, 0, 0);
1099 widget_instance.find('.vbo-finance-dtpicker-to').datepicker('setDate', dto_obj);
1100 } catch(e) {
1101 // just log the error
1102 console.error(e);
1103 }
1104 }
1105
1106 // update step name
1107 if (obj_res[call_method]['step_name'] && obj_res[call_method]['step_name'].length) {
1108 widget_instance.find('.vbo-reportwidget-period-name').text(obj_res[call_method]['step_name']);
1109 }
1110
1111 // update readable dates interval
1112 var readable_period = obj_res[call_method]['f_fromdate'] + ' - ' + obj_res[call_method]['f_todate'];
1113 if (obj_res[call_method]['f_fromdate'] == obj_res[call_method]['f_todate']) {
1114 // a full month does not need an interval
1115 readable_period = obj_res[call_method]['f_fromdate'];
1116 }
1117 widget_instance.find('.vbo-reportwidget-period-date').text(readable_period);
1118
1119 if (auto_compare.length) {
1120 // trigger the stats comparison
1121 setTimeout(() => {
1122 vboWidgetFinanceCompare(wrapper, (auto_compare == 'year' ? 'year' : dates_step));
1123 }, 400);
1124 }
1125 } catch(err) {
1126 console.error('could not parse JSON response', err, response);
1127 }
1128 },
1129 (error) => {
1130 // remove the skeleton loading
1131 widget_instance.find('.vbo-widget-finance-list').find('.vbo-widget-finance-skeleton-blocks').remove();
1132 console.error(error);
1133 alert(error.responseText);
1134 }
1135 );
1136 }
1137
1138 /**
1139 * Load stats comparison values.
1140 */
1141 function vboWidgetFinanceCompare(wrapper, period) {
1142 var widget_instance = jQuery('#' + wrapper);
1143 if (!widget_instance.length) {
1144 return false;
1145 }
1146
1147 // append loading elements
1148 widget_instance.find('.vbo-widget-finance-data-blocks').find('.vbo-widget-finance-stat').each(function(k, v) {
1149 if (jQuery(this).find('.vbo-widget-finance-stat-compare').length) {
1150 jQuery(this).find('.vbo-widget-finance-stat-compare').remove();
1151 }
1152 var comparison_el = jQuery('<div></div>').addClass('vbo-widget-finance-stat-compare');
1153 var loading_el = jQuery('<div></div>').addClass('vbo-widget-finance-compare-amount');
1154 loading_el.append('<i class="<?php echo VikBookingIcons::i('circle-notch', 'fa-spin fa-fw'); ?>"></i>');
1155 comparison_el.append(loading_el);
1156 jQuery(this).append(comparison_el);
1157 });
1158
1159 // get vars for making the request
1160 var prev_stats = JSON.parse(widget_instance.find('.vbo-widget-finance-json-stats').val());
1161 var from_date = widget_instance.find('.vbo-widget-finance-stats-from').val();
1162 var to_date = widget_instance.find('.vbo-widget-finance-stats-to').val();
1163
1164 // the widget method to call
1165 var call_method = 'loadComparisonStats';
1166
1167 // make a request to load the comparison values
1168 VBOCore.doAjax(
1169 "<?php echo $this->getExecWidgetAjaxUri(); ?>",
1170 {
1171 widget_id: "<?php echo $this->getIdentifier(); ?>",
1172 call: call_method,
1173 return: 1,
1174 fromdate: from_date,
1175 todate: to_date,
1176 step: period,
1177 stats: prev_stats,
1178 wrapper: wrapper,
1179 tmpl: "component"
1180 },
1181 (response) => {
1182 try {
1183 var obj_res = typeof response === 'string' ? JSON.parse(response) : response;
1184 if (!obj_res.hasOwnProperty(call_method)) {
1185 console.error('Unexpected JSON response', obj_res);
1186 return false;
1187 }
1188
1189 // turn on the flag to automatically compare data
1190 widget_instance.find('.vbo-finance-compare-auto').val(period);
1191
1192 if (obj_res[call_method]['no_data']) {
1193 // no data available for comparison
1194 widget_instance.find('.vbo-widget-finance-data-blocks').find('.vbo-widget-finance-stat').each(function(k, v) {
1195 if (jQuery(this).find('.vbo-widget-finance-stat-compare').length) {
1196 jQuery(this).find('.vbo-widget-finance-stat-compare').remove();
1197 }
1198 let comparison_el = jQuery('<div></div>').addClass('vbo-widget-finance-stat-compare');
1199 let amount_el = jQuery('<div></div>').addClass('vbo-widget-finance-compare-amount');
1200 comparison_el.append(amount_el);
1201 let pcent_el = jQuery('<div></div>').addClass('vbo-widget-finance-compare-pcent');
1202 pcent_el.append('<span></span>').addClass('vbo-widget-finance-compare-txt').text(Joomla.JText._('VBNOTRACKINGS'));
1203 comparison_el.append(pcent_el);
1204 jQuery(this).append(comparison_el);
1205 });
1206
1207 // do not proceed
1208 return;
1209 }
1210
1211 // parse all comparison values
1212 for (let stat_name in obj_res[call_method]['compare']) {
1213 if (!obj_res[call_method]['compare'].hasOwnProperty(stat_name)) {
1214 continue;
1215 }
1216
1217 let stat_elem = widget_instance.find('.vbo-widget-finance-data-block[data-typestat="' + stat_name + '"]');
1218 if (!stat_elem || !stat_elem.length) {
1219 continue;
1220 }
1221
1222 // clean up loading element
1223 if (stat_elem.find('.vbo-widget-finance-stat-compare').length) {
1224 stat_elem.find('.vbo-widget-finance-stat-compare').remove();
1225 }
1226
1227 // check if currency is needed
1228 let use_currency = (!obj_res[call_method]['compare'][stat_name].hasOwnProperty('fixednum') || !obj_res[call_method]['compare'][stat_name]['fixednum']);
1229
1230 // the new comparison element
1231 let comparison_el = jQuery('<div></div>').addClass('vbo-widget-finance-stat-compare');
1232
1233 // amount block
1234 let amount_block = jQuery('<div></div>').addClass('vbo-widget-finance-compare-amount');
1235 let amount_value = jQuery('<span></span>').addClass('vbo-widget-finance-compare-amount-value');
1236 if (use_currency) {
1237 amount_value.addClass('vbo-tooltip vbo-tooltip-top').attr('data-tooltiptext', obj_res[call_method]['currency'] + ' ' + obj_res[call_method]['compare'][stat_name]['amount_f']);
1238 amount_value.append('<span class="vbo-currency">' + obj_res[call_method]['currency'] + '</span>');
1239 }
1240 if (obj_res[call_method]['compare'][stat_name]['pcent'] != null) {
1241 amount_value.append('<span class="vbo-price">' + obj_res[call_method]['compare'][stat_name]['amount_s'] + '</span>');
1242 } else {
1243 amount_value.append('<span class="vbo-price">' + obj_res[call_method]['compare'][stat_name]['amount'] + '%</span>');
1244 }
1245 amount_block.append(amount_value);
1246
1247 // percent block
1248 let pcent_block = jQuery('<div></div>').addClass('vbo-widget-finance-compare-pcent');
1249 let pcent_icon = '';
1250 if (obj_res[call_method]['compare'][stat_name]['diff'] == 0) {
1251 pcent_block.addClass('vbo-widget-finance-compare-pcent-equal');
1252 pcent_icon = '<?php VikBookingIcons::e('equals') ?>';
1253 } else if (obj_res[call_method]['compare'][stat_name]['diff'] > 0) {
1254 pcent_block.addClass('vbo-widget-finance-compare-pcent-up');
1255 pcent_icon = obj_res[call_method]['compare'][stat_name]['reverse'] ? '<?php VikBookingIcons::e('arrow-down') ?>' : '<?php VikBookingIcons::e('arrow-up') ?>';
1256 } else if (obj_res[call_method]['compare'][stat_name]['diff'] < 0) {
1257 pcent_block.addClass('vbo-widget-finance-compare-pcent-down');
1258 pcent_icon = obj_res[call_method]['compare'][stat_name]['reverse'] ? '<?php VikBookingIcons::e('arrow-up') ?>' : '<?php VikBookingIcons::e('arrow-down') ?>';
1259 }
1260 if (obj_res[call_method]['compare'][stat_name]['pcent'] != null) {
1261 pcent_block.append('<span class="vbo-widget-finance-compare-val">' + pcent_icon + ' ' + obj_res[call_method]['compare'][stat_name]['pcent'] + '%</span>');
1262 }
1263 pcent_block.append('<span class="vbo-widget-finance-compare-txt">' + obj_res[call_method]['vs_str'] + '</span>');
1264
1265 // append amount block
1266 comparison_el.append(amount_block);
1267 // append percent block
1268 comparison_el.append(pcent_block);
1269
1270 // append comparison element
1271 stat_elem.find('.vbo-widget-finance-stat').append(comparison_el);
1272 }
1273 } catch(err) {
1274 console.error('could not parse JSON response', err, response);
1275 }
1276 },
1277 (error) => {
1278 // remove loading icons
1279 widget_instance.find('.vbo-widget-finance-data-blocks').find('.vbo-widget-finance-stat-compare').remove();
1280 console.error(error);
1281 alert(error.responseText);
1282 }
1283 );
1284
1285 }
1286
1287 /**
1288 * Navigate between the date steps and load the stats.
1289 */
1290 function vboWidgetFinanceDatesNav(wrapper, direction) {
1291 var widget_instance = jQuery('#' + wrapper);
1292 if (!widget_instance.length) {
1293 return false;
1294 }
1295
1296 // show loading skeletons
1297 vboWidgetFinanceSkeletons(wrapper);
1298
1299 // launch dates navigation and load records
1300 vboWidgetFinanceLoad(wrapper, direction);
1301 }
1302
1303 /**
1304 * Load stats for the selected dates.
1305 */
1306 function vboWidgetFinanceChangeDates(wrapper) {
1307 var widget_instance = jQuery('#' + wrapper);
1308 if (!widget_instance.length) {
1309 return false;
1310 }
1311
1312 // hide filters when making a new request
1313 widget_instance.find('.vbo-reportwidget-filters').hide();
1314
1315 // show loading skeletons
1316 vboWidgetFinanceSkeletons(wrapper);
1317
1318 // load data
1319 vboWidgetFinanceLoad(wrapper);
1320 }
1321
1322 /**
1323 * Toggle dates and step filters.
1324 */
1325 function vboWidgetFinanceToggleFilters(wrapper) {
1326 var widget_instance = jQuery('#' + wrapper);
1327 if (!widget_instance.length) {
1328 return false;
1329 }
1330
1331 widget_instance.find('.vbo-reportwidget-filters').toggle();
1332 }
1333
1334 /**
1335 * Datepicker dates selection.
1336 */
1337 function vboWidgetFinanceCheckDates(selectedDate, inst) {
1338 if (selectedDate === null || inst === null) {
1339 return;
1340 }
1341 var cur_from_date = jQuery(this).val();
1342 if (jQuery(this).hasClass("vbo-finance-dtpicker-from") && cur_from_date.length) {
1343 var nowstart = jQuery(this).datepicker("getDate");
1344 var nowstartdate = new Date(nowstart.getTime());
1345 jQuery(".vbo-finance-dtpicker-to").datepicker("option", {minDate: nowstartdate});
1346 }
1347 }
1348
1349 /**
1350 * Navigation period cookie onchange.
1351 */
1352 function vboWidgetFinanceChangePeriod(period) {
1353 // always exclude "booking_dates"
1354 if (period && period === 'booking_dates') {
1355 return;
1356 }
1357 // update cookie for the step selected
1358 var nd = new Date();
1359 nd.setTime(nd.getTime() + (365*24*60*60*1000));
1360 document.cookie = "vbo_widget_finance_step=" + period + "; expires=" + nd.toUTCString() + "; path=/; SameSite=Lax";
1361 }
1362
1363 </script>
1364 <?php
1365 }
1366 ?>
1367
1368 <script type="text/javascript">
1369
1370 jQuery(function() {
1371
1372 // render datepicker calendars for dates navigation
1373 jQuery('#<?php echo $wrapper_id; ?>').find('.vbo-finance-dtpicker-from, .vbo-finance-dtpicker-to').datepicker({
1374 minDate: "<?php echo date($df, $mindate); ?>",
1375 maxDate: "<?php echo date($df, $maxdate); ?>",
1376 yearRange: "<?php echo date('Y', $mindate); ?>:<?php echo date('Y', $maxdate); ?>",
1377 changeMonth: true,
1378 changeYear: true,
1379 dateFormat: "<?php echo $dtpicker_df; ?>",
1380 onSelect: vboWidgetFinanceCheckDates
1381 });
1382
1383 // triggering for datepicker calendar icons
1384 jQuery('i.vbo-widget-finance-caltrigger').click(function() {
1385 var jdp = jQuery(this).parent().find('input.hasDatepicker');
1386 if (jdp.length) {
1387 jdp.focus();
1388 }
1389 });
1390
1391 // register room filters search via AJAX
1392 jQuery('#<?php echo $wrapper_id; ?>').find('select.vbo-widget-finance-rooms-filter').select2({
1393 placeholder: Joomla.JText._('VBROOMFILTER'),
1394 language: {
1395 errorLoading: () => {
1396 return Joomla.JText._('VBO_ERR_LOAD_RESULTS');
1397 },
1398 noResults: () => {
1399 return Joomla.JText._('VBNOROOMSFOUND');
1400 },
1401 searching: () => {
1402 return Joomla.JText._('VBO_SEARCHING');
1403 },
1404 },
1405 ajax: {
1406 delay: 350,
1407 url: "<?php echo VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=bookings.rooms_search'); ?>",
1408 dataType: 'json',
1409 },
1410 templateResult: (element) => {
1411 if (!element.img) {
1412 return element.text;
1413 }
1414 return jQuery('<span class="vbo-sel2-element-img"><img src="' + element.img + '" /> <span>' + element.text + '</span></span>');
1415 },
1416 });
1417
1418 // when document is ready, load stats for this widget's instance
1419 vboWidgetFinanceLoad('<?php echo $wrapper_id; ?>');
1420
1421 });
1422
1423 </script>
1424
1425 <?php
1426 }
1427
1428 /**
1429 * Returns an array with the minimum and maximum dates booked.
1430 *
1431 * @return array to be used with list() to get the min/max stay date timestamps.
1432 */
1433 protected function getMinDatesFinance()
1434 {
1435 $dbo = JFactory::getDbo();
1436
1437 $mindate = null;
1438 $maxdate = null;
1439
1440 $q = "SELECT MIN(`checkin`) AS `mindate`, MAX(`checkout`) AS `maxdate` FROM `#__vikbooking_orders`
1441 WHERE `status` = 'confirmed' AND `closure` = 0";
1442 $dbo->setQuery($q);
1443 $data = $dbo->loadAssoc();
1444 if ($data) {
1445 if (!empty($data['mindate']) && !empty($data['maxdate'])) {
1446 $mindate = $data['mindate'];
1447 $maxdate = $data['maxdate'];
1448 }
1449 }
1450
1451 return [$mindate, $maxdate];
1452 }
1453
1454 /**
1455 * Calculates the new dates interval for navigation according to step.
1456 * Internal method for this widget only.
1457 *
1458 * @param int $from_ts current from date timestamp.
1459 * @param int $to_ts current end date timestamp.
1460 * @param string $step the navigation step to take (week, month, quarter or booking_dates).
1461 * @param int $date_dir less than 0 backward nav, more than 0 forward.
1462 *
1463 * @return array the new from and end date timestamps, plus the step name.
1464 */
1465 protected function calcDatesNavigation($from_ts, $to_ts, $step, $date_dir)
1466 {
1467 if (empty($from_ts) || (!($date_dir > 0) && !($date_dir < 0))) {
1468 return [$from_ts, $to_ts, ''];
1469 }
1470
1471 // start/end date information
1472 $from_info = getdate($from_ts);
1473 $to_info = getdate($to_ts);
1474
1475 // next or prev day (booking_dates)
1476 if (!strcasecmp($step, 'booking_dates')) {
1477 if ($date_dir > 0) {
1478 // forward dates navigation
1479 $from_ts = mktime(0, 0, 0, $to_info['mon'], ($to_info['mday'] + 1), $to_info['year']);
1480 $to_ts = mktime(23, 59, 59, $to_info['mon'], ($to_info['mday'] + 1), $to_info['year']);
1481 } elseif ($date_dir < 0) {
1482 // backward dates navigation
1483 $from_ts = mktime(0, 0, 0, $from_info['mon'], ($from_info['mday'] - 1), $from_info['year']);
1484 $to_ts = mktime(23, 59, 59, $from_info['mon'], ($from_info['mday'] - 1), $from_info['year']);
1485 }
1486
1487 return [$from_ts, $to_ts, JText::translate('VBPCHOOSEBUSYORDATE')];
1488 }
1489
1490 // next or prev week
1491 if ($step == 'week') {
1492 if ($date_dir > 0) {
1493 // forward dates navigation
1494 $from_ts = $to_ts;
1495 $to_ts = strtotime("+1 week", $to_ts);
1496 } elseif ($date_dir < 0) {
1497 // backward dates navigation
1498 $to_ts = $from_ts;
1499 $from_ts = strtotime("-1 week", $from_ts);
1500 }
1501
1502 return [$from_ts, $to_ts, JText::translate('VBOWEEK')];
1503 }
1504
1505 // next or prev month
1506 if ($step == 'month') {
1507 if ($date_dir > 0) {
1508 // forward dates navigation
1509 $from_ts = mktime(0, 0, 0, ($from_info['mon'] + 1), 1, $from_info['year']);
1510 $to_ts = mktime(0, 0, 0, ($from_info['mon'] + 1), date('t', $from_ts), $from_info['year']);
1511 } elseif ($date_dir < 0) {
1512 // backward dates navigation
1513 $from_ts = mktime(0, 0, 0, ($from_info['mon'] - 1), 1, $from_info['year']);
1514 $to_ts = mktime(0, 0, 0, ($from_info['mon'] - 1), date('t', $from_ts), $from_info['year']);
1515 }
1516
1517 return [$from_ts, $to_ts, JText::translate('VBPVIEWRESTRICTIONSTWO')];
1518 }
1519
1520 // next or prev year (keep the same exact dates)
1521 if ($step == 'year') {
1522 if ($date_dir > 0) {
1523 // forward dates navigation
1524 $from_ts = mktime(0, 0, 0, $from_info['mon'], $from_info['mday'], ($from_info['year'] + 1));
1525 $to_ts = mktime(0, 0, 0, $to_info['mon'], $to_info['mday'], ($to_info['year'] + 1));
1526 } elseif ($date_dir < 0) {
1527 // backward dates navigation
1528 $from_ts = mktime(0, 0, 0, $from_info['mon'], $from_info['mday'], ($from_info['year'] - 1));
1529 $to_ts = mktime(0, 0, 0, $to_info['mon'], $to_info['mday'], ($to_info['year'] - 1));
1530 }
1531
1532 return [$from_ts, $to_ts, JText::translate('VBCONFIGSEARCHPMAXDATEYEARS')];
1533 }
1534
1535 // next or prev quarter by default
1536 if ($date_dir > 0) {
1537 // forward dates navigation
1538 $from_ts = mktime(0, 0, 0, ($from_info['mon'] + 3), 1, $from_info['year']);
1539 $end_ts = mktime(0, 0, 0, ($from_info['mon'] + 5), 1, $from_info['year']);
1540 $to_ts = mktime(0, 0, 0, ($from_info['mon'] + 5), date('t', $end_ts), $from_info['year']);
1541 } elseif ($date_dir < 0) {
1542 // backward dates navigation
1543 $from_ts = mktime(0, 0, 0, ($from_info['mon'] - 3), 1, $from_info['year']);
1544 $end_ts = mktime(0, 0, 0, ($from_info['mon'] - 1), 1, $from_info['year']);
1545 $to_ts = mktime(0, 0, 0, ($from_info['mon'] - 1), date('t', $end_ts), $from_info['year']);
1546 }
1547
1548 return [$from_ts, $to_ts, JText::translate('VBO_QUARTER')];
1549 }
1550
1551 /**
1552 * Parse an interval of date timestamps into a readable interval of dates according to step.
1553 * Internal method for this widget only.
1554 *
1555 * @param int $from_ts current from date timestamp.
1556 * @param int $to_ts current end date timestamp.
1557 * @param string $vbo_df the date format in VBO.
1558 * @param string $step the navigation step to take (week, month or quarter).
1559 *
1560 * @return array the formatted from and to date interval strings.
1561 */
1562 protected function parseReadableDateInterval($from_ts, $to_ts, $vbo_df, $step)
1563 {
1564 $from_info = getdate($from_ts);
1565 $to_info = getdate($to_ts);
1566 $format_from_date = date($vbo_df, $from_ts);
1567 $format_to_date = date($vbo_df, $to_ts);
1568
1569 if ($step == 'week') {
1570 // include day of week
1571 $format_from_date = VikBooking::sayWeekDay($from_info['wday']) . ', ' . $format_from_date;
1572 $format_to_date = VikBooking::sayWeekDay($to_info['wday']) . ', ' . $format_to_date;
1573 } elseif ($step == 'month') {
1574 // check if it's a full month to say its name
1575 if ($from_info['mon'] == $to_info['mon'] && $from_info['year'] == $to_info['year']) {
1576 if ($from_info['mday'] == 1 && $to_info['mday'] == date('t', $to_ts)) {
1577 $format_from_date = VikBooking::sayMonth($from_info['mon']) . ' ' . $from_info['year'];
1578 $format_to_date = VikBooking::sayMonth($to_info['mon']) . ' ' . $to_info['year'];
1579 }
1580 }
1581 } elseif ($step == 'quarter') {
1582 // check if it's a full quarter
1583 if ($from_info['mday'] == 1 && $to_info['mday'] == date('t', $to_ts)) {
1584 $format_from_date = VikBooking::sayMonth($from_info['mon']) . ' ' . $from_info['year'];
1585 $format_to_date = VikBooking::sayMonth($to_info['mon']) . ' ' . $to_info['year'];
1586 }
1587 }
1588
1589 return [$format_from_date, $format_to_date];
1590 }
1591
1592 /**
1593 * Sets up the necessary script to load the welcome message for the user.
1594 * On WordPress this method may run outside Vik Booking, and so Javascript,
1595 * may not be able to access language definitions from the DOM.
1596 *
1597 * @return bool true if welcome was set up for the user, or false.
1598 */
1599 protected function setupWelcomeStats()
1600 {
1601 // avoid welcome stats on certain pages
1602 if (VBOPlatformDetection::isWordPress()) {
1603 global $pagenow;
1604 $skip_pages = ['update.php', 'plugins.php', 'plugin-install.php'];
1605 if (isset($pagenow) && in_array($pagenow, $skip_pages)) {
1606 return false;
1607 }
1608 }
1609
1610 $admin_user = JFactory::getUser();
1611 $admin_user_name = $admin_user->name;
1612
1613 if (!$admin_user_name || !$admin_user->authorise('core.vbo.bookings', 'com_vikbooking')) {
1614 // user not authorized
1615 return false;
1616 }
1617
1618 // get yesterday's date
1619 $yesterday_info = getdate(strtotime('yesterday'));
1620 $yesterday_read = implode(' ', [VikBooking::sayWeekDay($yesterday_info['wday'], true), $yesterday_info['mday'], VikBooking::sayMonth($yesterday_info['mon'], true)]);
1621 $yesterday_read = htmlspecialchars($yesterday_read);
1622 $yesterday = date('Y-m-d', $yesterday_info[0]);
1623
1624 // the payload for getting the welcome stats for yesterday
1625 $payload = [
1626 'fromdate' => $yesterday,
1627 'todate' => $yesterday,
1628 'type' => 'booking_dates',
1629 ];
1630
1631 // check if we are inside Vik Booking
1632 $in_vbo = (int)(JFactory::getApplication()->input->get('option', '') === 'com_vikbooking');
1633 $vbo_uri = VBOPlatformDetection::isWordPress() ? 'admin.php?page=vikbooking' : 'index.php?option=com_vikbooking';
1634 if (!$in_vbo && VBOPlatformDetection::isWordPress()) {
1635 // append query string value to render the admin widget
1636 $q_cmds = [
1637 'load_widget' => $this->widgetId,
1638 'multitask_data' => $payload,
1639 ];
1640 $vbo_uri .= '&' . http_build_query($q_cmds);
1641 }
1642
1643 // build data (the JS code may not be able to access script language definitions if running outside Vik Booking)
1644 $aj_endpoint = $this->getExecWidgetAjaxUri();
1645 $widget_id = $this->getIdentifier();
1646 $website_logo = $this->getIconUrl();
1647 $website_logo = $website_logo ? $website_logo : '';
1648 $greetings = VikBooking::strTrimLiteral(htmlspecialchars(JText::sprintf('VBO_WELCOME_ADMIN_USER', $admin_user_name)));
1649 $greetings_ms = VikBooking::strTrimLiteral(htmlspecialchars(JText::translate('VBO_BOOKINGS_YESTERDAY')));
1650 $modal_title = VikBooking::strTrimLiteral(htmlspecialchars(JText::translate('VBOYESTERDAY') . ', ' . $yesterday_read));
1651 $payload_json = json_encode($payload);
1652 $sugg_notifs_btn = '<button class="vbo-suggest-notifications-btn" type="button"><i class="' . VikBookingIcons::i('bell', 'can-shake') . '"></i></button>';
1653
1654 JFactory::getDocument()->addScriptDeclaration(
1655 <<<JS
1656 (function($) {
1657 'use strict';
1658
1659 $(function() {
1660 try {
1661 if (!VBOCore.storageSupported() || VBOCore.storageGetItem('vbo_finance_last_dt') == '$yesterday') {
1662 return false;
1663 }
1664 } catch(e) {
1665 return false;
1666 }
1667
1668 let call_method = 'getWelcomeStats';
1669 VBOCore.doAjax(
1670 "$aj_endpoint",
1671 {
1672 widget_id: "$widget_id",
1673 call: call_method,
1674 return: 1,
1675 tmpl: "component"
1676 },
1677 (response) => {
1678 try {
1679 var obj_res = typeof response === 'string' ? JSON.parse(response) : response;
1680 if (!obj_res.hasOwnProperty(call_method)) {
1681 throw new Error('Unexpected JSON response');
1682 }
1683
1684 VBOCore.storageSetItem('vbo_finance_last_dt', '$yesterday');
1685
1686 if (!obj_res[call_method].hasOwnProperty('tot_bookings') || !obj_res[call_method]['tot_bookings']) {
1687 return;
1688 }
1689
1690 let vbo_notif_click_handler = 'VBOCore.handleGoto';
1691 if ($in_vbo) {
1692 vbo_notif_click_handler = () => {
1693 try {
1694 let yesterday_modal = VBOCore.displayModal({
1695 suffix: 'finance-welcome',
1696 extra_class: 'vbo-modal-rounded vbo-modal-tall vbo-modal-nofooter',
1697 title: '<span class="vbo-suggest-notifications-wrap"><span>$modal_title</span>' + (VBOCore.notificationsEnabled() === false ? '$sugg_notifs_btn' : '') + '</span>',
1698 dismiss_event: 'vbo-dismiss-modal-finance-welcome',
1699 loading_event: 'vbo-loading-modal-finance-welcome',
1700 });
1701
1702 VBOCore.suggestNotifications('.vbo-suggest-notifications-btn');
1703 VBOCore.emitEvent('vbo-loading-modal-finance-welcome');
1704
1705 VBOCore.renderAdminWidget('$this->widgetId', $payload_json).then((content) => {
1706 VBOCore.emitEvent('vbo-loading-modal-finance-welcome');
1707 yesterday_modal.append(content);
1708 }).catch((error) => {
1709 VBOCore.emitEvent('vbo-dismiss-modal-finance-welcome');
1710 alert(error);
1711 });
1712 } catch(e) {
1713 document.location.href = '$vbo_uri';
1714 }
1715 };
1716 }
1717
1718 setTimeout(() => {
1719 VBOCore.dispatchNotification({
1720 title: '$greetings',
1721 message: ('$greetings_ms').replace('%d', obj_res[call_method]['tot_bookings']),
1722 icon: '$website_logo',
1723 onclick: vbo_notif_click_handler,
1724 gotourl: '$vbo_uri',
1725 });
1726 }, 500);
1727 } catch(e) {
1728 console.error(e);
1729 }
1730 },
1731 (error) => {
1732 console.error(error.responseText);
1733 }
1734 );
1735 });
1736 })(jQuery);
1737 JS
1738 );
1739
1740 return true;
1741 }
1742
1743 /**
1744 * Returns the URL to the default site logo.
1745 *
1746 * @return string|null
1747 */
1748 protected function getIconUrl()
1749 {
1750 $config = VBOFactory::getConfig();
1751
1752 // back-end custom logo
1753 $use_logo = $config->get('backlogo');
1754 if (empty($use_logo) || !strcasecmp($use_logo, 'vikbooking.png')) {
1755 // fallback to company (site) logo
1756 $use_logo = $config->get('sitelogo');
1757 }
1758
1759 if (!empty($use_logo) && strcasecmp($use_logo, 'vikbooking.png')) {
1760 // uploaded logo found
1761 $use_logo = VBO_ADMIN_URI . 'resources/' . $use_logo;
1762 } else {
1763 $use_logo = null;
1764 }
1765
1766 return $use_logo;
1767 }
1768 }
1769