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

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

1,181 lines 27.0 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 * PMS Report abstract-parent class that all drivers should extend.
15 */
16 #[\AllowDynamicProperties]
17 abstract class VikBookingReport
18 {
19 /**
20 * @var string
21 */
22 protected $reportName = '';
23
24 /**
25 * @var string
26 */
27 protected $reportFile = '';
28
29 /**
30 * @var array
31 */
32 protected $reportFilters = [];
33
34 /**
35 * @var string
36 */
37 protected $reportScript = '';
38
39 /**
40 * @var string
41 */
42 protected $warning = '';
43
44 /**
45 * @var string
46 */
47 protected $error = '';
48
49 /**
50 * @var object
51 */
52 protected $dbo;
53
54 /**
55 * @var array
56 */
57 protected $cols = [];
58
59 /**
60 * @var array
61 */
62 protected $rows = [];
63
64 /**
65 * @var array
66 */
67 protected $footerRow = [];
68
69 /**
70 * An array of custom options to be passed to the report.
71 * Reports can use them before generating the report data.
72 *
73 * @var array
74 *
75 * @since 1.15.0 (J) - 1.5.0 (WP)
76 */
77 protected $options = [];
78
79 /**
80 * @var resource
81 *
82 * @since 1.16.1 (J) - 1.6.1 (WP)
83 */
84 protected $fp_export = null;
85
86 /**
87 * @var string
88 *
89 * @since 1.16.1 (J) - 1.6.1 (WP)
90 */
91 protected $csv_export_format = 'csv';
92
93 /**
94 * @var string
95 *
96 * @since 1.16.1 (J) - 1.6.1 (WP)
97 */
98 protected $csv_export_fname = '';
99
100 /**
101 * Class constructor should define the name of
102 * the report and the filters to be displayed.
103 */
104 public function __construct()
105 {
106 $this->dbo = JFactory::getDbo();
107 }
108
109 /**
110 * Extending Classes should define this method
111 * to get the name of the report.
112 */
113 abstract public function getName();
114
115 /**
116 * Extending Classes should define this method
117 * to get the name of class file.
118 */
119 abstract public function getFileName();
120
121 /**
122 * Extending Classes should define this method
123 * to get the filters of the report.
124 */
125 abstract public function getFilters();
126
127 /**
128 * Extending Classes should define this method
129 * to generate the report data (cols and rows).
130 */
131 abstract public function getReportData();
132
133 /**
134 * Main method to generate the report columns and rows. Stores on the current
135 * resource the CSV lines and forces the browser to download the file. In case
136 * of errors, the process is not terminated to let the View display the errors.
137 *
138 * @return void|bool script termination on success, false otherwise.
139 *
140 * @since 1.16.1 (J) - 1.6.1 (WP) drivers no longer need to implement it,
141 * unless the driver needs to override it.
142 */
143 public function exportCSV()
144 {
145 // grab all report lines to export
146 $csvlines = $this->getExportCSVLines();
147
148 if (!$csvlines) {
149 // no data to export
150 return false;
151 }
152
153 // force the download of the CSV file
154 $this->outputHeaders();
155
156 // send lines to output
157 $this->outputCSV($csvlines);
158
159 exit;
160 }
161
162 /**
163 * Returns the name to give to the CSV (or custom) file being exported.
164 *
165 * @param bool $cut_suffix if true the file name suffix will be cut off.
166 * @param string $suffix the optional file name suffix, hence the extension.
167 * @param bool $pretty if true, dashes will be converted to spaces and more.
168 *
169 * @return string
170 *
171 * @since 1.16.1 (J) - 1.6.1 (WP)
172 */
173 public function getExportCSVFileName($cut_suffix = false, $suffix = '.csv', $pretty = false)
174 {
175 if ($this->csv_export_fname) {
176 // use the exact report file name set
177 $export_fname = $this->csv_export_fname;
178 } else {
179 // use a generic file name
180 $export_fname = date('Y-m-d_H.i.s') . '-' . $this->reportFile . '.csv';
181 }
182
183 if ($cut_suffix) {
184 // cut off the file name suffix
185 if (!$suffix) {
186 // detect file extension
187 $suffix = substr($export_fname, strrpos($export_fname, '.'));
188 }
189 $export_fname = basename($export_fname, $suffix);
190 }
191
192 if ($pretty) {
193 // get the default date separator char
194 $datesep = VikBooking::getDateSeparator();
195 // add a dash between a range of dates
196 $export_fname = preg_replace("/([0-9])-([0-9])/i", '$1 - $2', $export_fname);
197 // add an empty space between report name and channel name
198 $export_fname = preg_replace("/([A-Z])-([A-Z])/i", '$1 $2', $export_fname);
199 // add an empty space between report name and date
200 $export_fname = preg_replace("/([A-Z0-9])-([0-9])/i", '$1 $2', $export_fname);
201 // convert the dashes to the actual date separator char
202 $export_fname = preg_replace("/([0-9])_([0-9])/i", '$1' . $datesep . '$2', $export_fname);
203 }
204
205 return $export_fname;
206 }
207
208 /**
209 * Sets the name to give to the CSV (or custom) file being exported.
210 *
211 * @param string $fname the full name of the file to export.
212 *
213 * @return self
214 *
215 * @since 1.16.1 (J) - 1.6.1 (WP)
216 */
217 public function setExportCSVFileName($fname)
218 {
219 $this->csv_export_fname = (string)$fname;
220
221 return $this;
222 }
223
224 /**
225 * Builds the list of the CSV lines to be exported from the current report data.
226 *
227 * @param bool $no_data true for actually not letting the report run.
228 *
229 * @return array list of CSV lines containing lists of CSV fields.
230 *
231 * @since 1.16.1 (J) - 1.6.1 (WP)
232 */
233 public function getExportCSVLines($no_data = false)
234 {
235 if (!$no_data && !$this->getReportData()) {
236 // nothing to export
237 return [];
238 }
239
240 // list of CSV lines
241 $csvlines = [];
242
243 if (!strcasecmp($this->csv_export_format, 'excel')) {
244 // add instructions for Excel
245
246 // UTF-8 BOM (not needed because we convert the encoding to UTF-16LE, which has BOM)
247 // $csvlines[] = chr(0xEF) . chr(0xBB) . chr(0xBF);
248
249 // define the separator
250 $csvlines[] = "sep=;\n";
251 }
252
253 // push the head of the CSV file
254 $csvcols = [];
255 foreach ($this->cols as $col) {
256 if (!is_array($col) || isset($col['ignore_export'])) {
257 // skip column
258 continue;
259 }
260
261 // push column label
262 $csvcols[] = $this->encodeExportCSVField($col['label']);
263 }
264
265 // push all columns
266 $csvlines[] = $csvcols;
267
268 // push the rows of the CSV file
269 foreach ($this->rows as $row) {
270 $csvrow = [];
271 foreach ($row as $field) {
272 if (!is_array($field) || isset($field['ignore_export'])) {
273 // skip value
274 continue;
275 }
276
277 // build value for export
278 $export_value = $field['value'];
279 if (!isset($field['no_export_callback']) && !isset($field['no_csv_callback'])) {
280 if (isset($field['export_callback']) && is_callable($field['export_callback'])) {
281 // trigger closure callback to prepare the value for export
282 $export_value = $field['export_callback']($field['value']);
283 } elseif (isset($field['callback']) && is_callable($field['callback'])) {
284 // trigger closure callback to prepare the value for export
285 $export_value = $field['callback']($field['value']);
286 }
287 }
288
289 // apply encoding and transliteration, if needed
290 $enc_trans_value = $this->encodeExportCSVField($export_value);
291
292 // make sure transliteration or encoding did not break the string
293 if (empty($enc_trans_value) && !empty($export_value)) {
294 // fallback to original and raw value
295 $enc_trans_value = $export_value;
296 }
297
298 // push row value
299 $csvrow[] = $enc_trans_value;
300 }
301
302 // push the whole row
303 $csvlines[] = $csvrow;
304 }
305
306 // return the list of lines
307 return $csvlines;
308 }
309
310 /**
311 * Checks if a custom resource file pointer to export the file has been set.
312 *
313 * @return bool
314 *
315 * @since 1.16.1 (J) - 1.6.1 (WP)
316 */
317 public function hasExportHandler()
318 {
319 return is_resource($this->fp_export);
320 }
321
322 /**
323 * Returns the resource file pointer on which the CSV lines will be exported.
324 *
325 * @return resource
326 *
327 * @since 1.16.1 (J) - 1.6.1 (WP)
328 */
329 public function getExportCSVHandler()
330 {
331 if (is_null($this->fp_export)) {
332 // set the default resource file pointer (output)
333 $this->setExportCSVHandler();
334 }
335
336 return $this->fp_export;
337 }
338
339 /**
340 * Sets the resource file pointer on which the CSV lines will be exported.
341 * Useful in case the export should be made on a file rather than on output.
342 *
343 * @param string|resource $filename file path, identifier or resource.
344 * @param string $mode the mode for opening the file pointer.
345 *
346 * @return self
347 *
348 * @since 1.16.1 (J) - 1.6.1 (WP)
349 */
350 public function setExportCSVHandler($filename = 'php://output', $mode = 'w')
351 {
352 // set resource file pointer
353 if (is_resource($filename)) {
354 $this->fp_export = $filename;
355 } else {
356 $this->fp_export = fopen($filename, $mode);
357 }
358
359 return $this;
360 }
361
362 /**
363 * Sets the current type of CSV export format.
364 *
365 * @param string $format either "csv" or "excel".
366 *
367 * @return self
368 *
369 * @since 1.16.1 (J) - 1.6.1 (WP)
370 */
371 public function setExportCSVFormat($format)
372 {
373 $export_format = 'csv';
374 if (!strcasecmp($format, 'excel')) {
375 $export_format = 'excel';
376 }
377
378 // set format type
379 $this->csv_export_format = $export_format;
380
381 return $this;
382 }
383
384 /**
385 * Sends to output the necessary headers to download the export file.
386 *
387 * @param array $headers optional additional or actual headers.
388 * @param bool $replace if true, only the provided headers will be used.
389 *
390 * @return void
391 */
392 public function outputHeaders(array $headers = [], $replace = false)
393 {
394 foreach ($headers as $header) {
395 // send custom header
396 header($header);
397 }
398
399 if ($replace) {
400 // do not send any other header
401 return;
402 }
403
404 // force the download of the CSV file
405 if (!strcasecmp($this->csv_export_format, 'excel')) {
406 // file compatible with Excel
407 header('Content-type: text/csv; charset=UTF-16LE');
408 } else {
409 // regular CSV
410 header('Content-type: text/csv; charset=UTF-8');
411 }
412 header('Cache-Control: no-store, no-cache');
413 header('Content-Disposition: attachment; filename="' . addslashes(basename($this->getExportCSVFileName(), '.csv')) . '.csv"');
414 }
415
416 /**
417 * Writes the CSV lines to export onto the current resource handler.
418 *
419 * @return bool
420 *
421 * @since 1.16.1 (J) - 1.6.1 (WP)
422 */
423 public function outputCSV(array $csvlines)
424 {
425 // get resource file pointer
426 $fp = $this->getExportCSVHandler();
427
428 if (!$fp) {
429 // resource file pointer unavailable
430 return false;
431 }
432
433 // default CSV delimiter and enclosure
434 $separator = ',';
435 $enclosure = "\"";
436 if (!strcasecmp($this->csv_export_format, 'excel')) {
437 // for Excel we use the semicolon as separator and double quotes as enclosure
438 $separator = ';';
439 }
440
441 // send lines to output
442 foreach ($csvlines as $csvline) {
443 if (is_string($csvline)) {
444 // must be the first line instructions for the export format
445 fputs($fp, $csvline);
446
447 // go to the next line
448 continue;
449 }
450
451 // put the array of values as a new CSV line
452 fputcsv($fp, $csvline, $separator, $enclosure);
453 }
454
455 // close the file pointer
456 fclose($fp);
457
458 return true;
459 }
460
461 /**
462 * Loads a specific report class and returns its instance.
463 * Should be called for instantiating any report sub-class.
464 *
465 * @param string $report the report file name (i.e. "revenue").
466 *
467 * @return mixed false or requested report object.
468 */
469 public static function getInstanceOf($report)
470 {
471 if (empty($report) || !is_string($report)) {
472 return false;
473 }
474
475 if (substr($report, -4) != '.php') {
476 $report .= '.php';
477 }
478
479 $report_path = dirname(__FILE__) . DIRECTORY_SEPARATOR . $report;
480 $classname = 'VikBookingReport' . str_replace(' ', '', ucwords(str_replace('.php', '', str_replace('_', ' ', $report))));
481
482 if (!is_file($report_path)) {
483 /**
484 * Trigger event to let other plugins register additional drivers.
485 *
486 * @since 1.16.0 (J) - 1.6.0 (WP)
487 */
488 $list = JFactory::getApplication()->triggerEvent('onLoadPmsReports');
489 foreach ($list as $chunk) {
490 if (!is_array($chunk) || !$chunk) {
491 continue;
492 }
493 foreach ($chunk as $thirdp_report) {
494 if (basename($thirdp_report) == $report) {
495 // driver found
496 $report_path = $thirdp_report;
497 break;
498 }
499 }
500 }
501 }
502
503 if (!is_file($report_path)) {
504 // report driver file not found
505 return false;
506 }
507
508 // load report
509 require_once $report_path;
510
511 if (class_exists($classname)) {
512 // return the instance of the report object found
513 return new $classname;
514 }
515
516 return false;
517 }
518
519 /**
520 * Injects request variables for the report like if some filters were set.
521 *
522 * @param array $vars associative list of request vars to inject.
523 *
524 * @return void
525 *
526 * @since 1.16.1 (J) - 1.6.1 (WP) static-context used to construct the report object later.
527 */
528 public static function setRequestVars(array $vars)
529 {
530 foreach ($vars as $key => $value) {
531 /**
532 * For more safety across different platforms and versions (J3/J4 or WP)
533 * we inject values in the super global array as well as in the input object.
534 */
535 VikRequest::setVar($key, $value, 'request');
536 VikRequest::setVar($key, $value);
537 }
538 }
539
540 /**
541 * Proxy for object-context to inject request variables for the report.
542 *
543 * @param array $params associative list of request vars to inject.
544 *
545 * @return self
546 */
547 public function injectParams($params)
548 {
549 if (is_array($params) && $params) {
550 self::setRequestVars($params);
551 }
552
553 return $this;
554 }
555
556 /**
557 * Loads Charts CSS/JS assets.
558 *
559 * @return self
560 */
561 public function loadChartsAssets()
562 {
563 $document = JFactory::getDocument();
564 $document->addStyleSheet(VBO_ADMIN_URI . 'resources/Chart.min.css', ['version' => VIKBOOKING_SOFTWARE_VERSION]);
565 $document->addScript(VBO_ADMIN_URI . 'resources/Chart.min.js', ['version' => VIKBOOKING_SOFTWARE_VERSION]);
566
567 return $this;
568 }
569
570 /**
571 * Loads the jQuery UI Datepicker.
572 * Method used only by sub-classes.
573 *
574 * @return self
575 */
576 protected function loadDatePicker()
577 {
578 $vbo_app = VikBooking::getVboApplication();
579 $vbo_app->loadDatePicker();
580
581 return $this;
582 }
583
584 /**
585 * Applies the proper encoding to the field being added to
586 * the CSV lines for export, depending on CSV or Excel.
587 *
588 * @param string $field the value being added to the export line.
589 *
590 * @return string either the original or the properly encoded field.
591 *
592 * @since 1.16.1 (J) - 1.6.1 (WP)
593 */
594 protected function encodeExportCSVField($field)
595 {
596 if (!is_string($field) || !strcasecmp($this->csv_export_format, 'csv')) {
597 // apply no encoding in case of regular CSV or if non-string data type
598 return $field;
599 }
600
601 // process the Excel-like string field
602 if (preg_match('/[\\x80-\\xff]/', $field)) {
603 // UTF-8 encoding detected
604 if (function_exists('transliterator_transliterate')) {
605 // if Transliterator is available (PECL intl >= 2.0.0), transliterate to ASCII
606 $field = transliterator_transliterate('Any-Latin; Latin-ASCII;', $field);
607 }
608
609 // attempt to convert UTF-8 to ASCII to support currencies
610 $field = iconv("UTF-8", "ASCII//TRANSLIT//IGNORE", $field);
611 }
612
613 if (!function_exists('mb_convert_encoding')) {
614 // abort to prevent server errors
615 return $field;
616 }
617
618 // convert encoding to UTF-16LE (low-endian with BOM)
619 return mb_convert_encoding($field, 'UTF-16LE', ['ASCII', 'UTF-8', 'ISO-8859-1']);
620 }
621
622 /**
623 * Loads all the rooms in VBO and returns the array.
624 *
625 * @return array
626 */
627 protected function getRooms()
628 {
629 $q = "SELECT * FROM `#__vikbooking_rooms` ORDER BY `name` ASC;";
630 $this->dbo->setQuery($q);
631 $rooms = $this->dbo->loadAssocList();
632
633 return $rooms;
634 }
635
636 /**
637 * Loads all the rate plans in VBO and returns the array.
638 *
639 * @return array
640 *
641 * @since 1.15.0 (J) - 1.5.0 (WP)
642 */
643 protected function getRatePlans()
644 {
645 $q = "SELECT * FROM `#__vikbooking_prices` ORDER BY `name` ASC;";
646 $this->dbo->setQuery($q);
647 $rplans = $this->dbo->loadAssocList();
648
649 return VikBooking::sortRatePlans($rplans);
650 }
651
652 /**
653 * Returns the number of total units for all rooms, or for a specific room.
654 * By default, the rooms unpublished are skipped, and all rooms are used.
655 *
656 * @param [mixed] $idroom int or array.
657 * @param [int] $published true or false.
658 *
659 * @return int
660 */
661 protected function countRooms($idroom = 0, $published = 1)
662 {
663 $clauses = [];
664 if (is_int($idroom) && $idroom > 0) {
665 $clauses[] = "`id`=".(int)$idroom;
666 } elseif (is_array($idroom) && $idroom) {
667 $clauses[] = "`id` IN (" . implode(', ', $idroom) . ")";
668 }
669 if ($published) {
670 $clauses[] = "`avail`=1";
671 }
672
673 $q = "SELECT SUM(`units`) FROM `#__vikbooking_rooms`".($clauses ? " WHERE ".implode(' AND ', $clauses) : "").";";
674 $this->dbo->setQuery($q);
675 $totrooms = (int)$this->dbo->loadResult();
676
677 return $totrooms;
678 }
679
680 /**
681 * Concatenates the JavaScript rules.
682 * Method used only by sub-classes.
683 *
684 * @param string $str
685 *
686 * @return self
687 */
688 protected function setScript($str)
689 {
690 $this->reportScript .= $str."\n";
691
692 return $this;
693 }
694
695 /**
696 * Gets the current script string.
697 *
698 * @return string
699 */
700 public function getScript()
701 {
702 return rtrim($this->reportScript, "\n");
703 }
704
705 /**
706 * Returns the date format in VBO for date, jQuery UI, Joomla/WordPress.
707 * The visibility of this method should be public for anyone who needs it.
708 *
709 * @param string $type
710 *
711 * @return string
712 */
713 public function getDateFormat($type = 'date')
714 {
715 $nowdf = VikBooking::getDateFormat();
716 if ($nowdf == "%d/%m/%Y") {
717 $df = 'd/m/Y';
718 $juidf = 'dd/mm/yy';
719 } elseif ($nowdf == "%m/%d/%Y") {
720 $df = 'm/d/Y';
721 $juidf = 'mm/dd/yy';
722 } else {
723 $df = 'Y/m/d';
724 $juidf = 'yy/mm/dd';
725 }
726
727 switch ($type) {
728 case 'jui':
729 return $juidf;
730 case 'joomla':
731 case 'wordpress':
732 return $nowdf;
733 default:
734 return $df;
735 }
736 }
737
738 /**
739 * Returns the translated weekday.
740 * Uses the back-end language definitions.
741 *
742 * @param int $wday
743 * @param string $type use 'long' for the full name of the week, short for the 3-char version
744 *
745 * @return string
746 */
747 protected function getWdayString($wday, $type = 'long')
748 {
749 $wdays_map_long = [
750 JText::translate('VBWEEKDAYZERO'),
751 JText::translate('VBWEEKDAYONE'),
752 JText::translate('VBWEEKDAYTWO'),
753 JText::translate('VBWEEKDAYTHREE'),
754 JText::translate('VBWEEKDAYFOUR'),
755 JText::translate('VBWEEKDAYFIVE'),
756 JText::translate('VBWEEKDAYSIX')
757 ];
758
759 $wdays_map_short = [
760 JText::translate('VBSUN'),
761 JText::translate('VBMON'),
762 JText::translate('VBTUE'),
763 JText::translate('VBWED'),
764 JText::translate('VBTHU'),
765 JText::translate('VBFRI'),
766 JText::translate('VBSAT')
767 ];
768
769 if ($type != 'long') {
770 return isset($wdays_map_short[(int)$wday]) ? $wdays_map_short[(int)$wday] : '';
771 }
772
773 return isset($wdays_map_long[(int)$wday]) ? $wdays_map_long[(int)$wday] : '';
774 }
775
776 /**
777 * Sets the columns for this report.
778 *
779 * @param array $arr
780 *
781 * @return self
782 */
783 public function setReportCols($arr)
784 {
785 $this->cols = $arr;
786
787 return $this;
788 }
789
790 /**
791 * Returns the columns for this report.
792 * Should be called after getReportData()
793 * or the returned array will be empty.
794 *
795 * @return array
796 */
797 public function getReportCols()
798 {
799 return $this->cols;
800 }
801
802 /**
803 * Sorts the rows of the report by key.
804 *
805 * @param string $krsort the key attribute of the array pairs
806 * @param string $krorder ascending (ASC) or descending (DESC)
807 *
808 * @return void
809 */
810 protected function sortRows($krsort, $krorder)
811 {
812 if (empty($krsort) || !$this->rows) {
813 return;
814 }
815
816 $map = [];
817 foreach ($this->rows as $k => $row) {
818 foreach ($row as $kk => $v) {
819 if (isset($v['key']) && $v['key'] == $krsort) {
820 $map[$k] = $v['value'];
821 }
822 }
823 }
824 if (!$map) {
825 return;
826 }
827
828 if ($krorder == 'ASC') {
829 asort($map);
830 } else {
831 arsort($map);
832 }
833
834 $sorted = [];
835 foreach ($map as $k => $v) {
836 $sorted[$k] = $this->rows[$k];
837 }
838
839 $this->rows = $sorted;
840 }
841
842 /**
843 * Sets the rows for this report.
844 *
845 * @param array $arr
846 *
847 * @return self
848 */
849 public function setReportRows($arr)
850 {
851 $this->rows = $arr;
852
853 return $this;
854 }
855
856 /**
857 * Returns the rows for this report.
858 * Should be called after getReportData()
859 * or the returned array will be empty.
860 *
861 * @return array
862 */
863 public function getReportRows()
864 {
865 return $this->rows;
866 }
867
868 /**
869 * This method returns one or more rows (given the depth) generated by
870 * the current report invoked. It is useful to clean up the callbacks
871 * of the various cell-rows, to obtain a parsable result.
872 * Can be called as first method, by skipping also getReportData().
873 *
874 * @param int $depth how many records to obtain, null for all.
875 *
876 * @return array the queried report value in the given depth.
877 *
878 * @uses getReportData()
879 */
880 public function getReportValues($depth = null)
881 {
882 if (!$this->rows && !$this->getReportData()) {
883 return [];
884 }
885
886 $report_values = [];
887
888 foreach ($this->rows as $rk => $row) {
889 $report_values[$rk] = [];
890 foreach ($row as $col => $coldata) {
891 $display_value = $coldata['value'];
892 if (isset($coldata['callback']) && is_callable($coldata['callback'])) {
893 // launch callback
894 $display_value = $coldata['callback']($coldata['value']);
895 }
896 // push column value
897 $report_values[$rk][$coldata['key']] = [
898 'value' => $coldata['value'],
899 'display_value' => $display_value,
900 ];
901 /**
902 * We also pass along any reserved key for this row-data.
903 *
904 * @since 1.15.0 (J) - 1.5.0 (WP)
905 */
906 foreach ($coldata as $res_key => $data_val) {
907 if (substr($res_key, 0, 1) == '_') {
908 // push this reserved key
909 $report_values[$rk][$coldata['key']][$res_key] = $data_val;
910 }
911 }
912 }
913 }
914
915 if (!$report_values) {
916 return [];
917 }
918
919 if ($depth === 1) {
920 // get an associative array with the first row calculated
921 return $report_values[0];
922 }
923
924 if (is_int($depth) && $depth > 0 && count($report_values) >= $depth) {
925 // get the requested portion of the array
926 return array_slice($report_values, 0, $depth);
927 }
928
929 return $report_values;
930 }
931
932 /**
933 * Maps the columns labels to an associative array to be used for the values.
934 *
935 * @return array associative list of column keys and related values.
936 */
937 public function getColumnsValues()
938 {
939 if (!$this->cols) {
940 return [];
941 }
942
943 $col_values = [];
944
945 foreach ($this->cols as $col) {
946 if (!isset($col['key'])) {
947 continue;
948 }
949 $col_values[$col['key']] = $col;
950 unset($col_values[$col['key']]['key']);
951 }
952
953 return $col_values;
954 }
955
956 /**
957 * Gets a property defined by the report. Useful to get custom
958 * properties set up by a specific report maybe for the Chart.
959 *
960 * @param string $property the name of the property needed.
961 * @param mixed $def default value to return.
962 *
963 * @return mixed false on failure, property requested otherwise.
964 */
965 public function getProperty($property, $def = false)
966 {
967 if (isset($this->{$property})) {
968 return $this->{$property};
969 }
970
971 return $def;
972 }
973
974 /**
975 * Counts the number of days of difference between two timestamps.
976 *
977 * @param int $to_ts the target end date timestamp.
978 * @param int $from_ts the starting date timestamp.
979 *
980 * @return int the days of difference between from and to timestamps.
981 */
982 public function countDaysTo($to_ts, $from_ts = 0)
983 {
984 if (empty($from_ts)) {
985 $from_ts = time();
986 }
987
988 // whether DateTime can be used
989 $usedt = false;
990
991 if (class_exists('DateTime')) {
992 $from_date = new DateTime(date('Y-m-d', $from_ts));
993 if (method_exists($from_date, 'diff')) {
994 $usedt = true;
995 }
996 }
997
998 if ($usedt) {
999 $to_date = new DateTime(date('Y-m-d', $to_ts));
1000 $daysdiff = (int)$from_date->diff($to_date)->format('%a');
1001 if ($to_ts < $from_ts) {
1002 // we need a negative integer number
1003 $daysdiff = $daysdiff - ($daysdiff * 2);
1004 }
1005 return $daysdiff;
1006 }
1007
1008 return (int)round(($to_ts - $from_ts) / 86400);
1009 }
1010
1011 /**
1012 * Counts the average difference between two integers.
1013 *
1014 * @param int $in_days_from days to the lowest timestamp.
1015 * @param int $in_days_to days to the highest timestamp.
1016 *
1017 * @return int the average number between the two values.
1018 */
1019 public function countAverageDays($in_days_from, $in_days_to)
1020 {
1021 return (int)floor(($in_days_from + $in_days_to) / 2);
1022 }
1023
1024 /**
1025 * Sets the footer row (the totals) for this report.
1026 *
1027 * @param array $arr
1028 *
1029 * @return self
1030 */
1031 protected function setReportFooterRow($arr)
1032 {
1033 $this->footerRow = $arr;
1034
1035 return $this;
1036 }
1037
1038 /**
1039 * Returns the footer row for this report.
1040 * Should be called after getReportData()
1041 * or the returned array will be empty.
1042 *
1043 * @return array
1044 */
1045 public function getReportFooterRow()
1046 {
1047 return $this->footerRow;
1048 }
1049
1050 /**
1051 * Sub-classes can extend this method to define the
1052 * the canvas HTML tag for rendenring the Chart.
1053 * Any necessary script shall be set within this method.
1054 * Data can be passed as a mixed value through the argument.
1055 * This is the first method to be called when working with the Chart.
1056 *
1057 * @param mixed $data any necessary value to render the Chart.
1058 *
1059 * @return string the HTML of the canvas element.
1060 */
1061 public function getChart($data = null)
1062 {
1063 return '';
1064 }
1065
1066 /**
1067 * Sub-classes can extend this method to define the
1068 * the title of the Chart to be rendered.
1069 *
1070 * @return string the title of the Chart.
1071 */
1072 public function getChartTitle()
1073 {
1074 return '';
1075 }
1076
1077 /**
1078 * Sub-classes can extend this method to define
1079 * the meta data for the Chart containing stats.
1080 * An array for each meta-data should be returned.
1081 *
1082 * @param mixed $position string for the meta-data position
1083 * in the Chart (top, right, bottom).
1084 * @param mixed $data some arguments to be passed.
1085 *
1086 * @return array
1087 */
1088 public function getChartMetaData($position = null, $data = null)
1089 {
1090 return [];
1091 }
1092
1093 /**
1094 * Sets an array of custom options for this report. Useful to inject
1095 * params before getting the report data and changing the behavior.
1096 *
1097 * @param array $arr
1098 *
1099 * @return self
1100 *
1101 * @since 1.15.0 (J) - 1.5.0 (WP)
1102 */
1103 public function setReportOptions($options = [])
1104 {
1105 $this->options = $options;
1106
1107 return $this;
1108 }
1109
1110 /**
1111 * Returns the custom options for the report. Useful to
1112 * behave differently depending on who calls the report.
1113 * By default, the method returns an instance of JObject
1114 * to easily access all custom options defined, if any.
1115 *
1116 * @param bool $registry true to get a JObject instance.
1117 *
1118 * @return mixed instance of JObject or raw array.
1119 *
1120 * @since 1.15.0 (J) - 1.5.0 (WP)
1121 */
1122 public function getReportOptions($registry = true)
1123 {
1124 if ($registry) {
1125 return new JObject($this->options);
1126 }
1127
1128 return $this->options;
1129 }
1130
1131 /**
1132 * Sets warning messages by concatenating the existing ones.
1133 * Method used only by sub-classes.
1134 *
1135 * @param string $str
1136 *
1137 * @return self
1138 */
1139 protected function setWarning($str)
1140 {
1141 $this->warning .= $str."\n";
1142
1143 return $this;
1144 }
1145
1146 /**
1147 * Gets the current warning string.
1148 *
1149 * @return string
1150 */
1151 public function getWarning()
1152 {
1153 return rtrim($this->warning, "\n");
1154 }
1155
1156 /**
1157 * Sets errors by concatenating the existing ones.
1158 * Method used only by sub-classes.
1159 *
1160 * @param string $str
1161 *
1162 * @return self
1163 */
1164 protected function setError($str)
1165 {
1166 $this->error .= $str."\n";
1167
1168 return $this;
1169 }
1170
1171 /**
1172 * Gets the current error string.
1173 *
1174 * @return string
1175 */
1176 public function getError()
1177 {
1178 return rtrim($this->error, "\n");
1179 }
1180 }
1181