PluginProbe
VikBooking Hotel Booking Engine & PMS / 1.6.9
VikBooking Hotel Booking Engine & PMS v1.6.9
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.9, at admin/helpers/report/report.php

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