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

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

1,838 lines 51.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 srl
6 * @copyright Copyright (C) 2025 E4J srl. 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 * @var array
102 *
103 * @since 1.17.1 (J) - 1.7.1 (WP)
104 */
105 protected $actionData = [];
106
107 /**
108 * @var array
109 *
110 * @since 1.17.1 (J) - 1.7.1 (WP)
111 */
112 protected $resourceFiles = [];
113
114 /**
115 * @var ?string
116 *
117 * @since 1.17.1 (J) - 1.7.1 (WP)
118 */
119 protected $scope = null;
120
121 /**
122 * @var array
123 *
124 * @since 1.18.6 (J) - 1.8.6 (WP)
125 */
126 protected $reportRowClasses = [];
127
128 /**
129 * Class constructor should define the name of
130 * the report and the filters to be displayed.
131 */
132 public function __construct()
133 {
134 $this->dbo = JFactory::getDbo();
135 }
136
137 /**
138 * Extending Classes should define this method
139 * to get the name of the report.
140 */
141 abstract public function getName();
142
143 /**
144 * Extending Classes should define this method
145 * to get the name of class file.
146 */
147 abstract public function getFileName();
148
149 /**
150 * Extending Classes should define this method
151 * to get the filters of the report.
152 */
153 abstract public function getFilters();
154
155 /**
156 * Extending Classes should define this method
157 * to generate the report data (cols and rows).
158 */
159 abstract public function getReportData();
160
161 /**
162 * Allows the report to perform a preflight compatibility check with the environment.
163 *
164 * @return ?bool False if the report is not compatible with the current environment.
165 *
166 * @since 1.18.5 (J) - 1.8.5 (WP)
167 */
168 public function preflight()
169 {
170 return true;
171 }
172
173 /**
174 * Allows the report to define the sub-filters template.
175 *
176 * @return ?string
177 *
178 * @since 1.18.6 (J) - 1.8.6 (WP)
179 */
180 public function getSubFiltersTpl()
181 {
182 return null;
183 }
184
185 /**
186 * Allows reports to define the default layout type to display.
187 *
188 * @param bool $hasChart True if the report has defined a chart.
189 *
190 * @return string
191 *
192 * @since 1.18.6 (J) - 1.8.6 (WP)
193 */
194 public function getDefaultLayoutType(bool $hasChart = false)
195 {
196 return $hasChart ? 'sheetnchart' : 'sheet';
197 }
198
199 /**
200 * Returns the AJAX endpoint URL for the current report.
201 *
202 * @return string
203 *
204 * @since 1.18.6 (J) - 1.8.6 (WP)
205 */
206 public function getAjaxUrl()
207 {
208 return VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=invoke_report&report=' . $this->reportFile);
209 }
210
211 /**
212 * Main method to generate the report columns and rows. Stores on the current
213 * resource the CSV lines and forces the browser to download the file. In case
214 * of errors, the process is not terminated to let the View display the errors.
215 *
216 * @return void|bool script termination on success, false otherwise.
217 *
218 * @since 1.16.1 (J) - 1.6.1 (WP) drivers no longer need to implement it,
219 * unless the driver needs to override it.
220 */
221 public function exportCSV()
222 {
223 // grab all report lines to export
224 $csvlines = $this->getExportCSVLines();
225
226 if (!$csvlines) {
227 // no data to export
228 return false;
229 }
230
231 // force the download of the CSV file
232 $this->outputHeaders();
233
234 /**
235 * Trigger event to allow third-party plugins to set additional headers or contents to output.
236 *
237 * @since 1.16.8 (J) - 1.6.8 (WP)
238 */
239 VBOFactory::getPlatform()->getDispatcher()->trigger('onBeforeExportOutputCSV', [$this, $this->csv_export_format]);
240
241 // send lines to output
242 $this->outputCSV($csvlines);
243
244 exit;
245 }
246
247 /**
248 * Returns the name to give to the CSV (or custom) file being exported.
249 *
250 * @param bool $cut_suffix if true the file name suffix will be cut off.
251 * @param string $suffix the optional file name suffix, hence the extension.
252 * @param bool $pretty if true, dashes will be converted to spaces and more.
253 *
254 * @return string
255 *
256 * @since 1.16.1 (J) - 1.6.1 (WP)
257 */
258 public function getExportCSVFileName($cut_suffix = false, $suffix = '.csv', $pretty = false)
259 {
260 if ($this->csv_export_fname) {
261 // use the exact report file name set
262 $export_fname = $this->csv_export_fname;
263 } else {
264 // use a generic file name
265 $export_fname = date('Y-m-d_H.i.s') . '-' . $this->reportFile . '.csv';
266 }
267
268 if ($cut_suffix) {
269 // cut off the file name suffix
270 if (!$suffix) {
271 // detect file extension
272 $suffix = substr($export_fname, strrpos($export_fname, '.'));
273 }
274 $export_fname = basename($export_fname, $suffix);
275 }
276
277 if ($pretty) {
278 // get the default date separator char
279 $datesep = VikBooking::getDateSeparator();
280 // add a dash between a range of dates
281 $export_fname = preg_replace("/([0-9])-([0-9])/i", '$1 - $2', $export_fname);
282 // add an empty space between report name and channel name
283 $export_fname = preg_replace("/([A-Z])-([A-Z])/i", '$1 $2', $export_fname);
284 // add an empty space between report name and date
285 $export_fname = preg_replace("/([A-Z0-9])-([0-9])/i", '$1 $2', $export_fname);
286 // convert the dashes to the actual date separator char
287 $export_fname = preg_replace("/([0-9])_([0-9])/i", '$1' . $datesep . '$2', $export_fname);
288 }
289
290 return $export_fname;
291 }
292
293 /**
294 * Sets the name to give to the CSV (or custom) file being exported.
295 *
296 * @param string $fname the full name of the file to export.
297 *
298 * @return self
299 *
300 * @since 1.16.1 (J) - 1.6.1 (WP)
301 */
302 public function setExportCSVFileName($fname)
303 {
304 $this->csv_export_fname = (string)$fname;
305
306 return $this;
307 }
308
309 /**
310 * Builds the list of the CSV lines to be exported from the current report data.
311 *
312 * @param bool $no_data true for actually not letting the report run.
313 *
314 * @return array list of CSV lines containing lists of CSV fields.
315 *
316 * @since 1.16.1 (J) - 1.6.1 (WP)
317 */
318 public function getExportCSVLines($no_data = false)
319 {
320 if (!$no_data && !$this->getReportData()) {
321 // nothing to export
322 return [];
323 }
324
325 // list of CSV lines
326 $csvlines = [];
327
328 if (!strcasecmp($this->csv_export_format, 'excel')) {
329 // add instructions for Excel
330
331 // UTF-8 BOM (not needed because we convert the encoding to UTF-16LE, which has BOM)
332 // $csvlines[] = chr(0xEF) . chr(0xBB) . chr(0xBF);
333
334 // define the separator
335 $csvlines[] = "sep=;\n";
336 }
337
338 // push the head of the CSV file
339 $csvcols = [];
340 foreach ($this->cols as $col) {
341 if (!is_array($col) || isset($col['ignore_export'])) {
342 // skip column
343 continue;
344 }
345
346 // push column label
347 $csvcols[] = $this->encodeExportCSVField($col['label']);
348 }
349
350 // push all columns
351 $csvlines[] = $csvcols;
352
353 // push the rows + footer row of the CSV file
354 foreach (array_merge($this->rows, $this->footerRow) as $row) {
355 $csvrow = [];
356 foreach ($row as $field) {
357 if (!is_array($field) || isset($field['ignore_export'])) {
358 // skip value
359 continue;
360 }
361
362 // build value for export
363 $export_value = $field['value'];
364 if (!isset($field['no_export_callback']) && !isset($field['no_csv_callback'])) {
365 if (is_callable($field['export_callback'] ?? null)) {
366 // trigger closure callback to prepare the value for export
367 $export_value = $field['export_callback']($field['value']);
368 } elseif (is_callable($field['callback_export'] ?? null)) {
369 // trigger closure callback to prepare the value for export
370 $export_value = $field['callback_export']($field['value']);
371 } elseif (is_callable($field['callback'] ?? null)) {
372 // trigger closure callback to prepare the value for export
373 $export_value = $field['callback']($field['value']);
374 }
375 }
376
377 // ensure no HTML is present
378 $export_value = strip_tags($export_value);
379
380 // apply encoding and transliteration, if needed
381 $enc_trans_value = $this->encodeExportCSVField($export_value);
382
383 // make sure transliteration or encoding did not break the string
384 if (empty($enc_trans_value) && !empty($export_value)) {
385 // fallback to original and raw value
386 $enc_trans_value = $export_value;
387 }
388
389 // push row value
390 $csvrow[] = $enc_trans_value;
391 }
392
393 // push the whole row
394 $csvlines[] = $csvrow;
395 }
396
397 // return the list of lines
398 return $csvlines;
399 }
400
401 /**
402 * Checks if a custom resource file pointer to export the file has been set.
403 *
404 * @return bool
405 *
406 * @since 1.16.1 (J) - 1.6.1 (WP)
407 */
408 public function hasExportHandler()
409 {
410 return is_resource($this->fp_export);
411 }
412
413 /**
414 * Returns the resource file pointer on which the CSV lines will be exported.
415 *
416 * @return resource
417 *
418 * @since 1.16.1 (J) - 1.6.1 (WP)
419 */
420 public function getExportCSVHandler()
421 {
422 if (is_null($this->fp_export)) {
423 // set the default resource file pointer (output)
424 $this->setExportCSVHandler();
425 }
426
427 return $this->fp_export;
428 }
429
430 /**
431 * Sets the resource file pointer on which the CSV lines will be exported.
432 * Useful in case the export should be made on a file rather than on output.
433 *
434 * @param string|resource $filename file path, identifier or resource.
435 * @param string $mode the mode for opening the file pointer.
436 *
437 * @return self
438 *
439 * @since 1.16.1 (J) - 1.6.1 (WP)
440 */
441 public function setExportCSVHandler($filename = 'php://output', $mode = 'w')
442 {
443 // set resource file pointer
444 if (is_resource($filename)) {
445 $this->fp_export = $filename;
446 } else {
447 $this->fp_export = fopen($filename, $mode);
448 }
449
450 return $this;
451 }
452
453 /**
454 * Sets the current type of CSV export format.
455 *
456 * @param string $format either "csv" or "excel".
457 *
458 * @return self
459 *
460 * @since 1.16.1 (J) - 1.6.1 (WP)
461 */
462 public function setExportCSVFormat($format)
463 {
464 $export_format = 'csv';
465 if (!strcasecmp($format, 'excel')) {
466 $export_format = 'excel';
467 }
468
469 // set format type
470 $this->csv_export_format = $export_format;
471
472 return $this;
473 }
474
475 /**
476 * Sends to output the necessary headers to download the export file.
477 *
478 * @param array $headers optional additional or actual headers.
479 * @param bool $replace if true, only the provided headers will be used.
480 *
481 * @return void
482 */
483 public function outputHeaders(array $headers = [], $replace = false)
484 {
485 foreach ($headers as $header) {
486 // send custom header
487 header($header);
488 }
489
490 if ($replace) {
491 // do not send any other header
492 return;
493 }
494
495 // force the download of the CSV file
496 if (!strcasecmp($this->csv_export_format, 'excel')) {
497 // file compatible with Excel
498 header('Content-type: text/csv; charset=UTF-16LE');
499 } else {
500 // regular CSV
501 header('Content-type: text/csv; charset=UTF-8');
502 }
503 header('Cache-Control: no-store, no-cache');
504 header('Content-Disposition: attachment; filename="' . addslashes(basename($this->getExportCSVFileName(), '.csv')) . '.csv"');
505 }
506
507 /**
508 * Writes the CSV lines to export onto the current resource handler.
509 *
510 * @return bool
511 *
512 * @since 1.16.1 (J) - 1.6.1 (WP)
513 */
514 public function outputCSV(array $csvlines)
515 {
516 // get resource file pointer
517 $fp = $this->getExportCSVHandler();
518
519 if (!$fp) {
520 // resource file pointer unavailable
521 return false;
522 }
523
524 // default CSV delimiter and enclosure
525 $separator = ',';
526 $enclosure = '"';
527 if (!strcasecmp($this->csv_export_format, 'excel')) {
528 // for Excel we use the semicolon as separator and double quotes as enclosure
529 $separator = ';';
530 }
531
532 // send lines to output
533 foreach ($csvlines as $csvline) {
534 if (is_string($csvline)) {
535 // must be the first line instructions for the export format
536 fputs($fp, $csvline);
537
538 // go to the next line
539 continue;
540 }
541
542 // put the array of values as a new CSV line
543 fputcsv($fp, $csvline, $separator, $enclosure, $escape = '');
544 }
545
546 // close the file pointer
547 fclose($fp);
548
549 return true;
550 }
551
552 /**
553 * Loads a specific report class and returns its instance.
554 * Should be called for instantiating any report sub-class.
555 *
556 * @param string $report the report file name (i.e. "revenue").
557 *
558 * @return mixed false or requested report object.
559 */
560 public static function getInstanceOf($report)
561 {
562 if (empty($report) || !is_string($report)) {
563 return false;
564 }
565
566 if (substr($report, -4) != '.php') {
567 $report .= '.php';
568 }
569
570 $report_path = dirname(__FILE__) . DIRECTORY_SEPARATOR . $report;
571 $classname = 'VikBookingReport' . str_replace(' ', '', ucwords(str_replace('.php', '', str_replace('_', ' ', $report))));
572
573 if (!is_file($report_path)) {
574 /**
575 * Trigger event to let other plugins register additional drivers.
576 *
577 * @since 1.16.0 (J) - 1.6.0 (WP)
578 */
579 $list = VBOFactory::getPlatform()->getDispatcher()->filter('onLoadPmsReports');
580 foreach ($list as $chunk) {
581 if (!is_array($chunk) || !$chunk) {
582 continue;
583 }
584 foreach ($chunk as $thirdp_report) {
585 if (basename($thirdp_report) == $report) {
586 // driver found
587 $report_path = $thirdp_report;
588 break;
589 }
590 }
591 }
592 }
593
594 if (!is_file($report_path)) {
595 // report driver file not found
596 return false;
597 }
598
599 // load report
600 require_once $report_path;
601
602 if (!class_exists($classname)) {
603 // report class does not exist
604 return false;
605 }
606
607 // instantiate the report object
608 $reportInstance = new $classname;
609
610 // ensure the report is compatible with the current environment
611 if ($reportInstance->preflight() === false) {
612 // the report is not compatible
613 return false;
614 }
615
616 // return the instance of the report object found
617 return $reportInstance;
618 }
619
620 /**
621 * Injects request variables for the report like if some filters were set.
622 *
623 * @param array $vars associative list of request vars to inject.
624 *
625 * @return void
626 *
627 * @since 1.16.1 (J) - 1.6.1 (WP) static-context used to construct the report object later.
628 */
629 public static function setRequestVars(array $vars)
630 {
631 foreach ($vars as $key => $value) {
632 /**
633 * For more safety across different platforms and versions (J3/J4 or WP)
634 * we inject values in the super global array as well as in the input object.
635 */
636 VikRequest::setVar($key, $value, 'request');
637 VikRequest::setVar($key, $value);
638 }
639 }
640
641 /**
642 * Proxy for object-context to inject request variables for the report.
643 *
644 * @param array $params associative list of request vars to inject.
645 *
646 * @return self
647 */
648 public function injectParams($params)
649 {
650 if (is_array($params) && $params) {
651 self::setRequestVars($params);
652 }
653
654 return $this;
655 }
656
657 /**
658 * Loads Charts CSS/JS assets.
659 *
660 * @return self
661 */
662 public function loadChartsAssets()
663 {
664 $document = JFactory::getDocument();
665 $document->addStyleSheet(VBO_ADMIN_URI . 'resources/Chart.min.css', ['version' => VIKBOOKING_SOFTWARE_VERSION]);
666 $document->addScript(VBO_ADMIN_URI . 'resources/Chart.min.js', ['version' => VIKBOOKING_SOFTWARE_VERSION]);
667
668 return $this;
669 }
670
671 /**
672 * Loads the jQuery UI Datepicker.
673 * Method used only by sub-classes.
674 *
675 * @return self
676 */
677 protected function loadDatePicker()
678 {
679 $vbo_app = VikBooking::getVboApplication();
680 $vbo_app->loadDatePicker();
681
682 return $this;
683 }
684
685 /**
686 * Parses a report row into an associative list of key-value pairs.
687 *
688 * @param array $row The report row to parse.
689 * @param bool $format True to format values with callbacks.
690 *
691 * @return array Associative list of row keys and values.
692 *
693 * @since 1.18.4 (J) - 1.8.4 (WP)
694 */
695 protected function getAssocRowFields(array $row, bool $format = false)
696 {
697 $row_data = [];
698
699 foreach ($row as $field) {
700 $field_val = $field['value'];
701 if (!isset($field['callback_export']) && isset($field['callback'])) {
702 $field['callback_export'] = $field['callback'];
703 }
704 if ($format === true && !($field['no_export_callback'] ?? 0) && is_callable($field['callback_export'] ?? null)) {
705 $field_val = $field['callback_export']($field_val);
706 }
707 $row_data[$field['key']] = $field_val;
708 }
709
710 return $row_data;
711 }
712
713 /**
714 * Gets the CSS classes for each report row.
715 *
716 * @return array
717 *
718 * @since 1.18.6 (J) - 1.8.6 (WP)
719 */
720 public function getReportRowClasses()
721 {
722 return $this->reportRowClasses;
723 }
724
725 /**
726 * Sets the CSS classes for each report row.
727 *
728 * @param array $rowClasses List of row class values.
729 *
730 * @return void
731 *
732 * @since 1.18.6 (J) - 1.8.6 (WP)
733 */
734 protected function setReportRowClasses(array $rowClasses)
735 {
736 $this->reportRowClasses = $rowClasses;
737
738 return;
739 }
740
741 /**
742 * Applies the proper encoding to the field being added to
743 * the CSV lines for export, depending on CSV or Excel.
744 *
745 * @param string $field the value being added to the export line.
746 *
747 * @return string either the original or the properly encoded field.
748 *
749 * @since 1.16.1 (J) - 1.6.1 (WP)
750 * @since 1.16.8 (J) - 1.6.8 (WP) introduced hook to allow to manipulate encoding.
751 */
752 protected function encodeExportCSVField($field)
753 {
754 /**
755 * Trigger event to allow third-party plugins to manipulate encoding,
756 * in case certain dependencies are not available, such as "mb" support,
757 * PECL intl for "transliterator_transliterate" or "iconv" to ASCII.
758 *
759 * @since 1.16.8 (J) - 1.6.8 (WP)
760 */
761 $apply_encoding = VBOFactory::getPlatform()->getDispatcher()->filter('onBeforeEncodingCSVField', [&$field, $this->csv_export_format]);
762
763 if (in_array(false, $apply_encoding, true)) {
764 // the hook ordered to not proceed with applying any encoding
765 return $field;
766 }
767
768 if (!is_string($field) || !strcasecmp($this->csv_export_format, 'csv')) {
769 // apply no encoding in case of regular CSV or if non-string data type
770 return $field;
771 }
772
773 // process the Excel-like string field
774 if (preg_match('/[\\x80-\\xff]/', $field)) {
775 // UTF-8 encoding detected
776 if (function_exists('transliterator_transliterate')) {
777 // if Transliterator is available (PECL intl >= 2.0.0), transliterate to ASCII
778 $field = transliterator_transliterate('Any-Latin; Latin-ASCII;', $field);
779 }
780
781 // attempt to convert UTF-8 to ASCII to support currencies
782 $field = iconv("UTF-8", "ASCII//TRANSLIT//IGNORE", $field);
783 }
784
785 if (!function_exists('mb_convert_encoding')) {
786 // abort to prevent server errors
787 return $field;
788 }
789
790 // convert encoding to UTF-16LE (low-endian with BOM)
791 return mb_convert_encoding($field, 'UTF-16LE', ['ASCII', 'UTF-8', 'ISO-8859-1']);
792 }
793
794 /**
795 * Used to apply transliteration over UTF-8 characters for having only latins chars.
796 *
797 * @param string $value The original string value.
798 *
799 * @return string The transliterated string or the original string.
800 *
801 * @since 1.18.0 (J) - 1.8.0 (WP)
802 */
803 protected function transliterateToAscii(string $value)
804 {
805 if (!preg_match('/[\\x80-\\xff]/', $value)) {
806 // no UTF-8 encoding (special character) detected
807 return $value;
808 }
809
810 // make a safe copy of the original string
811 $copy_value = $value;
812
813 if (function_exists('transliterator_transliterate')) {
814 // if Transliterator is available (PECL intl >= 2.0.0), transliterate to ASCII
815 $copy_value = transliterator_transliterate('Any-Latin; Latin-ASCII;', $copy_value);
816 }
817
818 if (function_exists('iconv')) {
819 // attempt to convert UTF-8 to ASCII
820 $copy_value = iconv("UTF-8", "ASCII//TRANSLIT//IGNORE", $copy_value);
821 }
822
823 if (empty($copy_value)) {
824 // revert to the original string value
825 $copy_value = $value;
826 }
827
828 return $copy_value;
829 }
830
831 /**
832 * Loads all the rooms in VBO and returns the array.
833 *
834 * @return array
835 */
836 protected function getRooms()
837 {
838 $q = "SELECT * FROM `#__vikbooking_rooms` ORDER BY `name` ASC;";
839 $this->dbo->setQuery($q);
840 $rooms = $this->dbo->loadAssocList();
841
842 return $rooms;
843 }
844
845 /**
846 * Loads all the rate plans in VBO and returns the array.
847 *
848 * @return array
849 *
850 * @since 1.15.0 (J) - 1.5.0 (WP)
851 */
852 protected function getRatePlans()
853 {
854 $q = "SELECT * FROM `#__vikbooking_prices` ORDER BY `name` ASC;";
855 $this->dbo->setQuery($q);
856 $rplans = $this->dbo->loadAssocList();
857
858 return VikBooking::sortRatePlans($rplans);
859 }
860
861 /**
862 * Returns the number of total units for all rooms, or for a specific room.
863 * By default, the rooms unpublished are skipped, and all rooms are used.
864 *
865 * @param [mixed] $idroom int or array.
866 * @param [int] $published true or false.
867 *
868 * @return int
869 */
870 protected function countRooms($idroom = 0, $published = 1)
871 {
872 $clauses = [];
873 if (is_int($idroom) && $idroom > 0) {
874 $clauses[] = "`id`=".(int)$idroom;
875 } elseif (is_array($idroom) && $idroom) {
876 $clauses[] = "`id` IN (" . implode(', ', $idroom) . ")";
877 }
878 if ($published) {
879 $clauses[] = "`avail`=1";
880 }
881
882 $q = "SELECT SUM(`units`) FROM `#__vikbooking_rooms`".($clauses ? " WHERE ".implode(' AND ', $clauses) : "").";";
883 $this->dbo->setQuery($q);
884 $totrooms = (int)$this->dbo->loadResult();
885
886 return $totrooms;
887 }
888
889 /**
890 * Concatenates the JavaScript rules.
891 * Method used only by sub-classes.
892 *
893 * @param string $str
894 *
895 * @return self
896 */
897 protected function setScript($str)
898 {
899 $this->reportScript .= $str."\n";
900
901 return $this;
902 }
903
904 /**
905 * Gets the current script string.
906 *
907 * @return string
908 */
909 public function getScript()
910 {
911 return rtrim($this->reportScript, "\n");
912 }
913
914 /**
915 * Returns the date format in VBO for date, jQuery UI, Joomla/WordPress.
916 * The visibility of this method should be public for anyone who needs it.
917 *
918 * @param string $type
919 *
920 * @return string
921 */
922 public function getDateFormat($type = 'date')
923 {
924 $nowdf = VikBooking::getDateFormat();
925 if ($nowdf == "%d/%m/%Y") {
926 $df = 'd/m/Y';
927 $juidf = 'dd/mm/yy';
928 } elseif ($nowdf == "%m/%d/%Y") {
929 $df = 'm/d/Y';
930 $juidf = 'mm/dd/yy';
931 } else {
932 $df = 'Y/m/d';
933 $juidf = 'yy/mm/dd';
934 }
935
936 switch ($type) {
937 case 'jui':
938 return $juidf;
939 case 'joomla':
940 case 'wordpress':
941 return $nowdf;
942 default:
943 return $df;
944 }
945 }
946
947 /**
948 * Returns the translated weekday.
949 * Uses the back-end language definitions.
950 *
951 * @param int $wday
952 * @param string $type use 'long' for the full name of the week, short for the 3-char version
953 *
954 * @return string
955 */
956 protected function getWdayString($wday, $type = 'long')
957 {
958 $wdays_map_long = [
959 JText::translate('VBWEEKDAYZERO'),
960 JText::translate('VBWEEKDAYONE'),
961 JText::translate('VBWEEKDAYTWO'),
962 JText::translate('VBWEEKDAYTHREE'),
963 JText::translate('VBWEEKDAYFOUR'),
964 JText::translate('VBWEEKDAYFIVE'),
965 JText::translate('VBWEEKDAYSIX')
966 ];
967
968 $wdays_map_short = [
969 JText::translate('VBSUN'),
970 JText::translate('VBMON'),
971 JText::translate('VBTUE'),
972 JText::translate('VBWED'),
973 JText::translate('VBTHU'),
974 JText::translate('VBFRI'),
975 JText::translate('VBSAT')
976 ];
977
978 if ($type != 'long') {
979 return isset($wdays_map_short[(int)$wday]) ? $wdays_map_short[(int)$wday] : '';
980 }
981
982 return isset($wdays_map_long[(int)$wday]) ? $wdays_map_long[(int)$wday] : '';
983 }
984
985 /**
986 * Sets the columns for this report.
987 *
988 * @param array $arr
989 *
990 * @return self
991 */
992 public function setReportCols($arr)
993 {
994 $this->cols = $arr;
995
996 return $this;
997 }
998
999 /**
1000 * Returns the columns for this report.
1001 * Should be called after getReportData()
1002 * or the returned array will be empty.
1003 *
1004 * @return array
1005 */
1006 public function getReportCols()
1007 {
1008 return $this->cols;
1009 }
1010
1011 /**
1012 * Sorts the rows of the report by key.
1013 *
1014 * @param string $krsort the key attribute of the array pairs
1015 * @param string $krorder ascending (ASC) or descending (DESC)
1016 *
1017 * @return void
1018 */
1019 protected function sortRows($krsort, $krorder)
1020 {
1021 if (empty($krsort) || !$this->rows) {
1022 return;
1023 }
1024
1025 $map = [];
1026 foreach ($this->rows as $k => $row) {
1027 foreach ($row as $kk => $v) {
1028 if (isset($v['key']) && $v['key'] == $krsort) {
1029 $map[$k] = $v['value'];
1030 }
1031 }
1032 }
1033 if (!$map) {
1034 return;
1035 }
1036
1037 if ($krorder == 'ASC') {
1038 asort($map);
1039 } else {
1040 arsort($map);
1041 }
1042
1043 $sorted = [];
1044 foreach ($map as $k => $v) {
1045 $sorted[$k] = $this->rows[$k];
1046 }
1047
1048 $this->rows = $sorted;
1049 }
1050
1051 /**
1052 * Sets the rows for this report.
1053 *
1054 * @param array $arr
1055 *
1056 * @return self
1057 */
1058 public function setReportRows($arr)
1059 {
1060 $this->rows = $arr;
1061
1062 return $this;
1063 }
1064
1065 /**
1066 * Returns the rows for this report.
1067 * Should be called after getReportData()
1068 * or the returned array will be empty.
1069 *
1070 * @return array
1071 */
1072 public function getReportRows()
1073 {
1074 return $this->rows;
1075 }
1076
1077 /**
1078 * This method returns one or more rows (given the depth) generated by
1079 * the current report invoked. It is useful to clean up the callbacks
1080 * of the various cell-rows, to obtain a parsable result.
1081 * Can be called as first method, by skipping also getReportData().
1082 *
1083 * @param ?int $depth how many records to obtain, null for all.
1084 *
1085 * @return array the queried report value in the given depth.
1086 *
1087 * @uses getReportData()
1088 */
1089 public function getReportValues(?int $depth = null)
1090 {
1091 if (!$this->rows && !$this->getReportData()) {
1092 return [];
1093 }
1094
1095 $report_values = [];
1096
1097 foreach ($this->rows as $rk => $row) {
1098 $report_values[$rk] = [];
1099 foreach ($row as $col => $coldata) {
1100 $display_value = $coldata['value'];
1101 if (isset($coldata['callback']) && is_callable($coldata['callback'])) {
1102 // launch callback
1103 $display_value = $coldata['callback']($coldata['value']);
1104 }
1105 // push column value
1106 $report_values[$rk][$coldata['key']] = [
1107 'value' => $coldata['value'],
1108 'display_value' => $display_value,
1109 ];
1110 /**
1111 * We also pass along any reserved key for this row-data.
1112 *
1113 * @since 1.15.0 (J) - 1.5.0 (WP)
1114 */
1115 foreach ($coldata as $res_key => $data_val) {
1116 if (substr($res_key, 0, 1) == '_') {
1117 // push this reserved key
1118 $report_values[$rk][$coldata['key']][$res_key] = $data_val;
1119 }
1120 }
1121 }
1122 }
1123
1124 if (!$report_values) {
1125 return [];
1126 }
1127
1128 if ($depth === 1) {
1129 // get an associative array with the first row calculated
1130 return $report_values[0];
1131 }
1132
1133 if (is_int($depth) && $depth > 0 && count($report_values) >= $depth) {
1134 // get the requested portion of the array
1135 return array_slice($report_values, 0, $depth);
1136 }
1137
1138 return $report_values;
1139 }
1140
1141 /**
1142 * Maps the columns labels to an associative array to be used for the values.
1143 *
1144 * @return array associative list of column keys and related values.
1145 */
1146 public function getColumnsValues()
1147 {
1148 if (!$this->cols) {
1149 return [];
1150 }
1151
1152 $col_values = [];
1153
1154 foreach ($this->cols as $col) {
1155 if (!isset($col['key'])) {
1156 continue;
1157 }
1158 $col_values[$col['key']] = $col;
1159 unset($col_values[$col['key']]['key']);
1160 }
1161
1162 return $col_values;
1163 }
1164
1165 /**
1166 * Gets a property defined by the report. Useful to get custom
1167 * properties set up by a specific report maybe for the Chart.
1168 *
1169 * @param string $property the name of the property needed.
1170 * @param mixed $def default value to return.
1171 *
1172 * @return mixed false on failure, property requested otherwise.
1173 */
1174 public function getProperty($property, $def = false)
1175 {
1176 if (isset($this->{$property})) {
1177 return $this->{$property};
1178 }
1179
1180 return $def;
1181 }
1182
1183 /**
1184 * Counts the number of days of difference between two timestamps.
1185 *
1186 * @param int $to_ts the target end date timestamp.
1187 * @param int $from_ts the starting date timestamp.
1188 *
1189 * @return int the days of difference between from and to timestamps.
1190 */
1191 public function countDaysTo($to_ts, $from_ts = 0)
1192 {
1193 if (empty($from_ts)) {
1194 $from_ts = time();
1195 }
1196
1197 // whether DateTime can be used
1198 $usedt = false;
1199
1200 if (class_exists('DateTime')) {
1201 $from_date = new DateTime(date('Y-m-d', $from_ts));
1202 if (method_exists($from_date, 'diff')) {
1203 $usedt = true;
1204 }
1205 }
1206
1207 if ($usedt) {
1208 $to_date = new DateTime(date('Y-m-d', $to_ts));
1209 $daysdiff = (int)$from_date->diff($to_date)->format('%a');
1210 if ($to_ts < $from_ts) {
1211 // we need a negative integer number
1212 $daysdiff = $daysdiff - ($daysdiff * 2);
1213 }
1214 return $daysdiff;
1215 }
1216
1217 return (int)round(($to_ts - $from_ts) / 86400);
1218 }
1219
1220 /**
1221 * Counts the average difference between two integers.
1222 *
1223 * @param int $in_days_from days to the lowest timestamp.
1224 * @param int $in_days_to days to the highest timestamp.
1225 *
1226 * @return int the average number between the two values.
1227 */
1228 public function countAverageDays($in_days_from, $in_days_to)
1229 {
1230 return (int)floor(($in_days_from + $in_days_to) / 2);
1231 }
1232
1233 /**
1234 * Sets the footer row (the totals) for this report.
1235 *
1236 * @param array $arr
1237 *
1238 * @return self
1239 */
1240 protected function setReportFooterRow($arr)
1241 {
1242 $this->footerRow = $arr;
1243
1244 return $this;
1245 }
1246
1247 /**
1248 * Returns the footer row for this report.
1249 * Should be called after getReportData()
1250 * or the returned array will be empty.
1251 *
1252 * @return array
1253 */
1254 public function getReportFooterRow()
1255 {
1256 return $this->footerRow;
1257 }
1258
1259 /**
1260 * Sub-classes can extend this method to define the
1261 * the canvas HTML tag for rendenring the Chart.
1262 * Any necessary script shall be set within this method.
1263 * Data can be passed as a mixed value through the argument.
1264 * This is the first method to be called when working with the Chart.
1265 *
1266 * @param ?array $data any necessary value to render the Chart.
1267 *
1268 * @return string the HTML of the canvas element.
1269 */
1270 public function getChart(?array $data = null)
1271 {
1272 return '';
1273 }
1274
1275 /**
1276 * Sub-classes can extend this method to define the
1277 * the title of the Chart to be rendered.
1278 *
1279 * @return string the title of the Chart.
1280 */
1281 public function getChartTitle()
1282 {
1283 return '';
1284 }
1285
1286 /**
1287 * Sub-classes can extend this method to define
1288 * the meta data for the Chart containing stats.
1289 * An array for each meta-data should be returned.
1290 *
1291 * @param mixed $position string for the meta-data position
1292 * in the Chart (top, right, bottom).
1293 * @param mixed $data some arguments to be passed.
1294 *
1295 * @return array
1296 */
1297 public function getChartMetaData($position = null, $data = null)
1298 {
1299 return [];
1300 }
1301
1302 /**
1303 * Sets an array of custom options for this report. Useful to inject
1304 * params before getting the report data and changing the behavior.
1305 *
1306 * @param array $options The associative options to set.
1307 *
1308 * @return self
1309 *
1310 * @since 1.15.0 (J) - 1.5.0 (WP)
1311 */
1312 public function setReportOptions(array $options = [])
1313 {
1314 $this->options = $options;
1315
1316 return $this;
1317 }
1318
1319 /**
1320 * Returns the custom options for the report. Useful to
1321 * behave differently depending on who calls the report.
1322 *
1323 * @param bool $registry True to wrap options into a registry.
1324 * @param bool $profileListings True to merge report profile listings setting.
1325 *
1326 * @return JObject|array
1327 *
1328 * @since 1.15.0 (J) - 1.5.0 (WP)
1329 * @since 1.18.7 (J) - 1.8.7 (WP) added argument $profileListings.
1330 */
1331 public function getReportOptions(bool $registry = true, bool $profileListings = true)
1332 {
1333 if ($profileListings && $this->allowsProfileListings()) {
1334 // load report settings
1335 $reportSettings = $this->loadSettings();
1336 if ($reportSettings['_listings'] ?? null) {
1337 // options should force profile listings
1338 $this->options['listings'] = (array) $reportSettings['_listings'];
1339 }
1340 }
1341
1342 if ($registry) {
1343 return new JObject($this->options);
1344 }
1345
1346 return $this->options;
1347 }
1348
1349 /**
1350 * Defines an associative list of action data.
1351 *
1352 * @param array $data Associative list of action data.
1353 *
1354 * @return self
1355 *
1356 * @since 1.17.1 (J) - 1.7.1 (WP)
1357 */
1358 public function setActionData(array $data)
1359 {
1360 $this->actionData = $data;
1361
1362 return $this;
1363 }
1364
1365 /**
1366 * Returns the action data, either raw or as a registry.
1367 *
1368 * @param bool $registry True to get a JObject instance.
1369 *
1370 * @return array|JObject
1371 *
1372 * @since 1.17.1 (J) - 1.7.1 (WP)
1373 */
1374 public function getActionData($registry = true)
1375 {
1376 if ($registry) {
1377 return new JObject($this->actionData);
1378 }
1379
1380 return $this->actionData;
1381 }
1382
1383 /**
1384 * Sets the global scope for the report.
1385 *
1386 * @param string $scope The scope to set.
1387 *
1388 * @return self
1389 *
1390 * @since 1.17.1 (J) - 1.7.1 (WP)
1391 */
1392 public function setScope($scope)
1393 {
1394 $this->scope = $scope;
1395
1396 return $this;
1397 }
1398
1399 /**
1400 * Returns the global scope of the invoked report.
1401 *
1402 * @return ?string
1403 *
1404 * @since 1.17.1 (J) - 1.7.1 (WP)
1405 */
1406 public function getScope()
1407 {
1408 return $this->scope;
1409 }
1410
1411 /**
1412 * Returns the path to the PMS media data directory.
1413 *
1414 * @return string
1415 *
1416 * @since 1.17.1 (J) - 1.7.1 (WP)
1417 */
1418 public function getDataMediaPath()
1419 {
1420 return implode(DIRECTORY_SEPARATOR, [VBO_ADMIN_PATH, 'resources', 'pmsdata']);
1421 }
1422
1423 /**
1424 * Returns the URL to the PMS media data directory.
1425 *
1426 * @return string
1427 *
1428 * @since 1.17.1 (J) - 1.7.1 (WP)
1429 */
1430 public function getDataMediaUrl()
1431 {
1432 return VBO_ADMIN_URI . 'resources/pmsdata/';
1433 }
1434
1435 /**
1436 * Returns the optional report custom setting fields at profile level.
1437 * This method allows to load the report setting fields by injecting
1438 * profile-level (protected) preferences, such as listings filtered.
1439 *
1440 * @return array
1441 *
1442 * @since 1.18.7 (J) - 1.8.7 (WP)
1443 */
1444 public function getProfileSettingFields()
1445 {
1446 // load all report setting fields
1447 $reportSettingFields = $this->getSettingFields();
1448
1449 if ($reportSettingFields && $this->allowsProfileListings()) {
1450 // set protected listing setting field
1451 $reportSettingFields['_listings'] = [
1452 'type' => 'listings',
1453 'label' => JText::translate('VBO_LISTINGS'),
1454 'help' => sprintf('(%s)', JText::translate('VBOFILTEISROPTIONAL')),
1455 'multiple' => true,
1456 'asset_options' => [
1457 'placeholder' => JText::translate('VBO_LISTINGS'),
1458 'allowClear' => true,
1459 ],
1460 ];
1461 }
1462
1463 // return the full list of setting fields
1464 return $reportSettingFields;
1465 }
1466
1467 /**
1468 * Returns the optional report custom setting fields.
1469 *
1470 * @return array
1471 *
1472 * @since 1.17.1 (J) - 1.7.1 (WP)
1473 */
1474 public function getSettingFields()
1475 {
1476 return [];
1477 }
1478
1479 /**
1480 * Tells whether the current report allows to save
1481 * the settings across multiple profile identifiers.
1482 *
1483 * @return bool
1484 *
1485 * @since 1.17.7 (J) - 1.7.7 (WP)
1486 */
1487 public function allowsProfileSettings()
1488 {
1489 // report profile settings disabled by default
1490 return false;
1491 }
1492
1493 /**
1494 * Tells whether the current report allows to save
1495 * the listings across multiple profile identifiers.
1496 *
1497 * @return bool
1498 *
1499 * @since 1.18.7 (J) - 1.8.7 (WP)
1500 */
1501 public function allowsProfileListings()
1502 {
1503 // report profile listings enabled as long as multiple profiles are enabled
1504 return $this->allowsProfileSettings();
1505 }
1506
1507 /**
1508 * Returns the report currently active profile identifier, if any.
1509 *
1510 * @return string
1511 *
1512 * @since 1.17.7 (J) - 1.7.7 (WP)
1513 */
1514 public function getActiveProfile()
1515 {
1516 $profile = (string) VBOFactory::getConfig()->getString('report_active_profile_' . $this->getFileName(), '');
1517
1518 return $profile;
1519 }
1520
1521 /**
1522 * Sets the report currently active profile identifier.
1523 *
1524 * @param string $profile_id The profile identifier to set.
1525 *
1526 * @return void
1527 *
1528 * @since 1.17.7 (J) - 1.7.7 (WP)
1529 */
1530 public function setActiveProfile(string $profile_id)
1531 {
1532 VBOFactory::getConfig()->set('report_active_profile_' . $this->getFileName(), $profile_id);
1533 }
1534
1535 /**
1536 * Returns an associative list for the report setting profiles available.
1537 *
1538 * @return array
1539 *
1540 * @since 1.17.7 (J) - 1.7.7 (WP)
1541 */
1542 public function getSettingProfiles()
1543 {
1544 return (array) VBOFactory::getConfig()->getArray('report_profile_list_' . $this->getFileName(), []);
1545 }
1546
1547 /**
1548 * Sets a new report setting profile identifier.
1549 *
1550 * @param string $profile_name The profile name.
1551 *
1552 * @return array List of profile identifier and name.
1553 *
1554 * @since 1.17.7 (J) - 1.7.7 (WP)
1555 */
1556 public function setSettingProfile(string $profile_name)
1557 {
1558 // get all report profiles
1559 $profiles = $this->getSettingProfiles();
1560
1561 // build profile identifier
1562 $profile_id = preg_replace('/[^A-Z0-9]/i', '', strtolower($profile_name));
1563 $profile_id = $profile_id ?: uniqid();
1564
1565 // set report profile
1566 $profiles[$profile_id] = $profile_name;
1567 VBOFactory::getConfig()->set('report_profile_list_' . $this->getFileName(), $profiles);
1568
1569 return [$profile_id, $profile_name];
1570 }
1571
1572 /**
1573 * Clears all profile settings and related data.
1574 *
1575 * @return void
1576 *
1577 * @since 1.17.7 (J) - 1.7.7 (WP)
1578 */
1579 public function clearProfiles()
1580 {
1581 // unset active profile
1582 VBOFactory::getConfig()->set('report_active_profile_' . $this->getFileName(), '');
1583
1584 // unset profiles list
1585 VBOFactory::getConfig()->set('report_profile_list_' . $this->getFileName(), []);
1586
1587 // clear profile settings
1588 VBOFactory::getConfig()->set('report_profile_settings_' . $this->getFileName(), []);
1589 }
1590
1591 /**
1592 * Returns the current report custom settings, optionally loaded from a
1593 * given profile identifier in case the report supports multiple settings.
1594 *
1595 * @param string $profile Optional settings profile identifier.
1596 *
1597 * @return array
1598 *
1599 * @since 1.17.1 (J) - 1.7.1 (WP)
1600 */
1601 public function loadSettings(string $profile = '')
1602 {
1603 // access report current settings
1604 $current_settings = (array) VBOFactory::getConfig()->getArray('report_settings_' . $this->getFileName(), []);
1605
1606 // default settings
1607 $default_settings = [];
1608
1609 if (!$profile && $this->allowsProfileSettings()) {
1610 // load current profile settings
1611 $profile = $this->getActiveProfile();
1612 $default_settings = $current_settings;
1613 }
1614
1615 if ($profile) {
1616 // check if the requested profile settings are available
1617 $profile_settings = (array) VBOFactory::getConfig()->getArray('report_profile_settings_' . $this->getFileName(), []);
1618
1619 // overwrite report current settings
1620 $current_settings = $profile_settings[$profile] ?? $default_settings;
1621 }
1622
1623 return $current_settings;
1624 }
1625
1626 /**
1627 * Saves the report custom settings defined.
1628 * The visibility should be public.
1629 *
1630 * @param array $data The associative list of settings to save.
1631 * @param bool $merge If true, the previous settings will be merged.
1632 * @param string $profile Optional settings profile identifier.
1633 *
1634 * @return void
1635 *
1636 * @since 1.17.1 (J) - 1.7.1 (WP)
1637 * @since 1.17.7 (J) - 1.7.7 (WP) added 3rd argument $profile and related support.
1638 */
1639 public function saveSettings(array $data, $merge = true, string $profile = '')
1640 {
1641 if ($merge) {
1642 // build report global settings
1643 $data = array_merge($this->loadSettings($profile), $data);
1644 }
1645
1646 // save report global settings
1647 VBOFactory::getConfig()->set('report_settings_' . $this->getFileName(), $data);
1648
1649 if ($profile) {
1650 // get report current profile settings
1651 $profile_settings = (array) VBOFactory::getConfig()->getArray('report_profile_settings_' . $this->getFileName(), []);
1652
1653 // set new profile settings
1654 $profile_settings[$profile] = $data;
1655
1656 // save report profile settings
1657 VBOFactory::getConfig()->set('report_profile_settings_' . $this->getFileName(), $profile_settings);
1658 }
1659 }
1660
1661 /**
1662 * Returns a numeric list of scoped extra actions.
1663 *
1664 * @param string $scope Optional scope identifier (cron, web, etc..).
1665 * @param bool $visible If true, the hidden actions will not be returned.
1666 *
1667 * @return array
1668 *
1669 * @since 1.17.1 (J) - 1.7.1 (WP)
1670 */
1671 public function getScopedActions($scope = null, $visible = true)
1672 {
1673 return [];
1674 }
1675
1676 /**
1677 * Executes a custom scoped action within the report.
1678 *
1679 * @param string $action The custom action to invoke.
1680 * @param string $scope Optional scope identifier (cron, web, etc..).
1681 * @param array $data Optional data for the action to execute.
1682 *
1683 * @return mixed
1684 *
1685 * @throws Exception
1686 *
1687 * @since 1.17.1 (J) - 1.7.1 (WP)
1688 */
1689 public function executeAction($action, $scope = null, array $data = [])
1690 {
1691 if (is_null($scope)) {
1692 $scope = $this->getScope();
1693 }
1694
1695 if (!$data) {
1696 $data = $this->getActionData($registry = false);
1697 }
1698
1699 $callable = [$this, $action];
1700
1701 if (!is_callable($callable)) {
1702 throw new Exception('Could not call the requested report action.', 500);
1703 }
1704
1705 return call_user_func_array($callable, [$scope, $data]);
1706 }
1707
1708 /**
1709 * Proxy for executing a custom scoped action and returning a property.
1710 *
1711 * @param string $action The custom action to invoke.
1712 * @param string $return The action result property to return.
1713 * @param string $scope Optional scope identifier (cron, web, etc..).
1714 * @param array $data Optional data for the action to execute.
1715 *
1716 * @return mixed
1717 *
1718 * @throws Exception
1719 *
1720 * @since 1.17.1 (J) - 1.7.1 (WP)
1721 */
1722 public function _callActionReturn($action, $return, $scope = null, array $data = [])
1723 {
1724 if (is_null($scope)) {
1725 $scope = $this->getScope();
1726 }
1727
1728 if (!$data) {
1729 $data = $this->getActionData($registry = false);
1730 }
1731
1732 $result = (array) $this->executeAction($action, $scope, $data);
1733
1734 if (!isset($result[$return])) {
1735 throw new Exception('Could not return the requested action result property.', 500);
1736 }
1737
1738 return $result[$return];
1739 }
1740
1741 /**
1742 * Defines a new resource file generated through a custom action.
1743 *
1744 * @param array $data Resource file data to bind.
1745 *
1746 * @return self
1747 *
1748 * @since 1.17.1 (J) - 1.7.1 (WP)
1749 */
1750 protected function defineResourceFile(array $data)
1751 {
1752 $element = new VBOReportResourceElement($data);
1753
1754 if ($element->getUrl()) {
1755 // ensure the resource is available
1756 $this->resourceFiles[] = $element;
1757 }
1758
1759 return $this;
1760 }
1761
1762 /**
1763 * Returns the resource files generated through custom actions.
1764 *
1765 * @return array
1766 *
1767 * @since 1.17.1 (J) - 1.7.1 (WP)
1768 */
1769 public function getResourceFiles()
1770 {
1771 return $this->resourceFiles;
1772 }
1773
1774 /**
1775 * Sets warning messages by concatenating the existing ones.
1776 * Method used only by sub-classes.
1777 *
1778 * @param string $str
1779 *
1780 * @return self
1781 */
1782 protected function setWarning($str)
1783 {
1784 $this->warning .= $str."\n";
1785
1786 return $this;
1787 }
1788
1789 /**
1790 * Gets the current warning string.
1791 *
1792 * @return string
1793 */
1794 public function getWarning()
1795 {
1796 return rtrim($this->warning, "\n");
1797 }
1798
1799 /**
1800 * Resets any errors previously set.
1801 *
1802 * @return self
1803 *
1804 * @since 1.18.3 (J) - 1.8.3 (WP)
1805 */
1806 protected function resetErrors()
1807 {
1808 $this->error = '';
1809
1810 return $this;
1811 }
1812
1813 /**
1814 * Sets errors by concatenating the existing ones.
1815 * Method used only by sub-classes.
1816 *
1817 * @param string $str
1818 *
1819 * @return self
1820 */
1821 protected function setError($str)
1822 {
1823 $this->error .= $str."\n";
1824
1825 return $this;
1826 }
1827
1828 /**
1829 * Gets the current error string.
1830 *
1831 * @return string
1832 */
1833 public function getError()
1834 {
1835 return rtrim($this->error, "\n");
1836 }
1837 }
1838