PluginProbe ʕ •ᴥ•ʔ
VikAppointments Services Booking Calendar / 1.2.21
VikAppointments Services Booking Calendar v1.2.21
1.2.21 1.2.20 trunk 1.2.17 1.2.18 1.2.19
vikappointments / site / helpers / libraries / statistics / widgets / subscriptions / items / chart.php
vikappointments / site / helpers / libraries / statistics / widgets / subscriptions / items Last commit date
chart.php 4 days ago count.php 4 days ago index.html 4 days ago
chart.php
473 lines
1 <?php
2 /**
3 * @package VikAppointments
4 * @subpackage core
5 * @author E4J s.r.l.
6 * @copyright Copyright (C) 2021 E4J s.r.l. All Rights Reserved.
7 * @license http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
8 * @link https://vikwp.com
9 */
10
11 // No direct access
12 defined('ABSPATH') or die('No script kiddies please!');
13
14 /**
15 * Widget used to draw a trend chart of each supported subscription.
16 *
17 * @since 1.7
18 */
19 class VAPStatisticsWidgetSubscriptionsItemsChart extends VAPStatisticsWidget
20 {
21 /**
22 * Internal flag used to check whether the current user
23 * owns enough permissions to access the financial data.
24 *
25 * Use true by default in case the widget is displayed
26 * without checking the permissions.
27 *
28 * @var boolean
29 */
30 protected $hasFinanceAccess = true;
31
32 /**
33 * Use the layout provided by "Packages - Items Revenue" widget.
34 *
35 * @var string
36 */
37 protected $layoutId = 'packages.items.chart';
38
39 /**
40 * Checks whether the specified group is supported by the widget.
41 *
42 * @param string $group The group to check.
43 *
44 * @return boolean True if supported, false otherwise.
45 */
46 public function isSupported($group)
47 {
48 return empty($group) || $group == '*' || $group == 'dashboard' || $group == 'subscriptions';
49 }
50
51 /**
52 * Checks whether the specified user is capable to access this widget.
53 *
54 * @param JUser $user The user instance.
55 *
56 * @return boolean True if capable, false otherwise.
57 */
58 public function checkPermissions($user)
59 {
60 // detected finance visibility
61 $this->hasFinanceAccess = $user->authorise('core.access.analytics.finance', 'com_vikappointments');
62
63 return $user->authorise('core.access.analytics.subscriptions', 'com_vikappointments');
64 }
65
66 /**
67 * Returns to the caller the financial rights of the user.
68 *
69 * @return boolean True if capable, false otherwise.
70 */
71 public function hasFinanceAccess()
72 {
73 return $this->hasFinanceAccess;
74 }
75
76 /**
77 * Override this method to return a configuration form of the widget.
78 *
79 * @return array
80 */
81 public function getForm()
82 {
83 $form = array(
84 /**
85 * The initial date to take when a new session starts.
86 *
87 * @var select
88 */
89 'range' => array(
90 'type' => 'select',
91 'label' => JText::translate('VAP_CHART_INITIAL_RANGE_FIELD'),
92 'help' => JText::translate('VAP_CHART_INITIAL_RANGE_FIELD_HELP'),
93 'default' => '-6 months',
94 'options' => array(
95 '-1 week' => JText::plural('VAP_LAST_N_WEEKS', 1),
96 '-2 weeks' => JText::plural('VAP_LAST_N_WEEKS', 2),
97 '-1 month' => JText::plural('VAP_LAST_N_MONTHS', 1),
98 '-3 months' => JText::plural('VAP_LAST_N_MONTHS', 3),
99 '-6 months' => JText::plural('VAP_LAST_N_MONTHS', 6),
100 '-9 months' => JText::plural('VAP_LAST_N_MONTHS', 9),
101 '-12 months' => JText::plural('VAP_LAST_N_MONTHS', 12),
102 ),
103 ),
104
105 /**
106 * The initial date of the range.
107 *
108 * The parameter is VOLATILE because, every time the session
109 * ends, we need to restore the field to an empty value, just
110 * to obtain the current date.
111 *
112 * @var date
113 */
114 'datefrom' => array(
115 'type' => 'date',
116 'label' => JText::translate('VAPEXPORTRES3'),
117 'volatile' => true,
118 ),
119
120 /**
121 * The ending date of the range.
122 *
123 * The parameter is VOLATILE because, every time the session
124 * ends, we need to restore the field to an empty value, just
125 * to obtain the current date.
126 *
127 * @var date
128 */
129 'dateto' => array(
130 'type' => 'date',
131 'label' => JText::translate('VAPEXPORTRES4'),
132 'volatile' => true,
133 ),
134
135 /**
136 * The type of chart to display (line or bar).
137 *
138 * @var select
139 */
140 'chart' => array(
141 'type' => 'select',
142 'label' => JText::translate('VAP_CHART_TYPE'),
143 'default' => 'line',
144 'options' => array(
145 'line' => JText::translate('VAP_CHART_TYPE_LINE'),
146 'bar' => JText::translate('VAP_CHART_TYPE_BAR'),
147 ),
148 ),
149
150 /**
151 * The entity of the chart (reservations count or total earning).
152 *
153 * @var select
154 */
155 'valuetype' => array(
156 'type' => 'select',
157 'label' => JText::translate('VAP_CHART_VALUE_TYPE_FIELD'),
158 'help' => JText::translate('VAP_CHART_VALUE_TYPE_FIELD_HELP'),
159 'default' => 'total',
160 'options' => array(
161 'total' => JText::translate('VAPREPORTSVALUETYPEOPT1'),
162 'count' => JText::translate('VAPREPORTSVALUETYPEOPT2'),
163 ),
164 ),
165
166 /**
167 * Select a group to automatically track all the assigned subscriptions.
168 *
169 * @var select
170 */
171 'group' => array(
172 'type' => 'select',
173 'label' => JText::translate('VAPMANAGESERVICE10'),
174 'options' => array(
175 'customers' => JText::translate('VAPMENUCUSTOMERS'),
176 'employees' => JText::translate('VAPMENUEMPLOYEES'),
177 ),
178 ),
179 );
180
181 // check whether the user owns enough permissions to see financial data
182 if (!$this->hasFinanceAccess)
183 {
184 // change type to hidden to avoid its selection
185 $form['valuetype']['type'] = 'hidden';
186 // display by default the total number of subscriptions
187 $form['valuetype']['default'] = 'count';
188 }
189
190 // fetch default group
191 VAPLoader::import('libraries.models.subscriptions');
192 $form['group']['default'] = VAPSubscriptions::getPreferredGroup();
193
194 return $form;
195 }
196
197 /**
198 * Checks whether the widget is able to export the fetched data.
199 *
200 * @return array A list of supported exportable functions.
201 */
202 public function isExportable()
203 {
204 return ['export', 'print'];
205 }
206
207 /**
208 * Loads the dataset(s) that will be recovered asynchronously
209 * for being displayed within the widget.
210 *
211 * It is possible to return an array of records to be passed
212 * to a chart or directly the HTML to replace.
213 *
214 * @return mixed
215 */
216 public function getData()
217 {
218 $filters = array();
219 $filters['group'] = $this->getOption('group') == 'employees' ? 1 : 0;
220 $filters['valuetype'] = $this->getOption('valuetype', 'total');
221 $filters['datefrom'] = $this->getOption('datefrom');
222 $filters['dateto'] = $this->getOption('dateto');
223 $filters['range'] = $this->getOption('range', '-6 months');
224
225 if (!$this->hasFinanceAccess)
226 {
227 // always display the total number of subscriptions in case
228 // the user cannot access financial data
229 $filters['valuetype'] = 'count';
230 }
231
232 $tz = JFactory::getUser()->getTimezone();
233
234 // use default range in case one of the specified dates is empty
235 if (VAPDateHelper::isNull($filters['datefrom']) || VAPDateHelper::isNull($filters['dateto']))
236 {
237 // create current date by using the timezone of the logged-in user
238 $filters['dateto'] = JFactory::getDate('now', $tz);
239 // set ending date to current one, 1 second before tomorrow
240 $filters['dateto']->modify('23:59:59');
241
242 if (strpos($filters['range'], 'months') !== false)
243 {
244 // set ending date to the end of the month
245 $filters['dateto']->modify($filters['dateto']->format('Y-m-t'));
246 }
247
248 // set starting date to current one at midnight
249 $filters['datefrom'] = clone $filters['dateto'];
250 // go to next day to properly calculate the starting date
251 $filters['datefrom']->modify('tomorrow 00:00:00');
252 // subtract given range
253 $filters['datefrom']->modify($filters['range']);
254 }
255 else
256 {
257 // convert specified dates to SQL format
258 $filters['datefrom'] = JFactory::getDate(VAPDateHelper::getDate($filters['datefrom'], 0, 0, 0), $tz);
259 $filters['dateto'] = JFactory::getDate(VAPDateHelper::getDate($filters['dateto'], 23, 59, 59), $tz);
260 }
261
262 // temporary option that can be used to extend the date format at runtime
263 $extended = $this->getOption('extended', false);
264
265 // import subscriptions helper
266 VAPLoader::import('libraries.statistics.helpers.subscriptions');
267 // fetch items trend
268 $data = VAPStatisticsHelperSubscriptions::getItemsTrend($filters['datefrom'], $filters['dateto'], $filters['valuetype'], $filters['group'], $extended);
269
270 return $data;
271 }
272
273 /**
274 * Create adapter for export method.
275 * This widget only supports "print" export function.
276 *
277 * @param mixed $rule The requested export type.
278 *
279 * @return void
280 */
281 public function export($rule = null)
282 {
283 if ($rule == 'print')
284 {
285 // auto-print the document
286 JHtml::fetch('vaphtml.sitescripts.winprint', 256);
287
288 // auto-fetch the widget data
289 $data = array(
290 'data' => $this->getData(),
291 'legend' => true,
292 );
293
294 // display widget with fetched data
295 echo $this->display($data);
296 }
297 else
298 {
299 // temporarily obtain all the columns and take an extended date format
300 $this->setOption('valuetype', ['total', 'tax', 'net', 'count']);
301 $this->setOption('extended', true);
302
303 // auto-fetch the widget data
304 $data = $this->getData();
305
306 $app = JFactory::getApplication();
307
308 $path = null;
309
310 // create file name
311 $filename = JFilterOutput::stringURLSafe('subscr-reports');
312
313 if ($rule != 'file')
314 {
315 // send headers for CSV download
316 $app->setHeader('Cache-Control', 'no-store, no-cache');
317 $app->setHeader('Content-Type', 'text/csv; charset=UTF-8');
318 $app->setHeader('Content-Disposition', 'attachment; filename="' . htmlspecialchars($filename) . '.csv"');
319 $app->sendHeaders();
320
321 // send bytes through PHP output
322 $handle = fopen('php://output', 'w');
323 }
324 else
325 {
326 // create file path
327 $path = rtrim($app->get('tmp_path'), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
328
329 $count = 1;
330
331 // append a counter next to the file name as long as the file already exists
332 while (is_file($path . $filename . ($count > 1 ? '-' . $count : '') . '.csv'))
333 {
334 $count++;
335 }
336
337 // create path
338 $path = $path . $filename . ($count > 1 ? '-' . $count : '') . '.csv';
339
340 // send bytes through file
341 $handle = fopen($path, 'w');
342 }
343
344 // create table heading
345 $head = array(
346 // date
347 JText::translate('VAPMANAGEREVIEW4'),
348 // subscription
349 JText::translate('VAPMANAGESUBSCRORD5'),
350 // total gross
351 JText::translate('VAPTOTALGROSS'),
352 // total tax
353 JText::translate('VAPTOTALTAX'),
354 // total net
355 JText::translate('VAPTOTALNET'),
356 // count
357 JText::translate('VAPQUANTITY'),
358 );
359
360 // include table heading
361 fputcsv($handle, $head, ',', '"', $escape = '');
362
363 $currency = VAPFactory::getCurrency();
364
365 $sumTotal = $sumTax = $sumNet = $sumCount = 0;
366
367 // iterate all records
368 foreach ($data as $date => $subscriptions)
369 {
370 $row = array(
371 // date
372 $date,
373 // subscription
374 '',
375 // total gross
376 '',
377 // total tax
378 '',
379 // total net
380 '',
381 // count
382 '',
383 );
384
385 // include table row
386 fputcsv($handle, $row, ',', '"', $escape = '');
387
388 $subTotal = $subTax = $subNet = $subCount = 0;
389
390 foreach ($subscriptions as $subscr)
391 {
392 $row = array(
393 // date
394 '',
395 // subscription
396 $subscr['name'],
397 // total gross
398 $currency->format($subscr['total']),
399 // total tax
400 $currency->format($subscr['tax']),
401 // net
402 $currency->format($subscr['net']),
403 // count
404 $subscr['count'],
405 );
406
407 // sum totals
408 $subTotal += $subscr['total'];
409 $subTax += $subscr['tax'];
410 $subNet += $subscr['net'];
411 $subCount += $subscr['count'];
412
413 // include table row
414 fputcsv($handle, $row, ',', '"', $escape = '');
415 }
416
417 // sum grand totals
418 $sumTotal += $subTotal;
419 $sumTax += $subTax;
420 $sumNet += $subNet;
421 $sumCount += $subCount;
422
423 $row = array(
424 // date
425 '',
426 // subscription
427 '',
428 // total gross
429 $currency->format($subTotal),
430 // total tax
431 $currency->format($subTax),
432 // total net
433 $currency->format($subNet),
434 // count
435 $subCount,
436 );
437
438 // include table row
439 fputcsv($handle, $row, ',', '"', $escape = '');
440 }
441
442 $row = array(
443 // date
444 '',
445 // subscription
446 '',
447 // total gross
448 $currency->format($sumTotal),
449 // total tax
450 $currency->format($sumTax),
451 // total net
452 $currency->format($sumNet),
453 // count
454 $sumCount,
455 );
456
457 // include table row
458 fputcsv($handle, $row, ',', '"', $escape = '');
459
460 fclose($handle);
461
462 if ($rule != 'file')
463 {
464 // terminate session in case of download
465 $app->close();
466 }
467
468 // return file path otherwise
469 return $path;
470 }
471 }
472 }
473