0 */ private $debug; /** * Class constructor should define the name of the report and * other vars. Call the parent constructor to define the DB object. */ public function __construct() { $this->reportFile = basename(__FILE__, '.php'); $this->reportName = JText::translate('VBOREPORT'.strtoupper(str_replace('_', '', $this->reportFile))); $this->reportFilters = array(); $this->cols = array(); $this->rows = array(); $this->footerRow = array(); $this->chartScript = ''; $this->chartTitle = ''; $this->chartMetaData = array(); $this->chartJsLabels = array(); $this->chartJsDataSetLabel = ''; $this->chartJsColors = array(); $this->chartJsData = array(); $this->debug = (VikRequest::getInt('e4j_debug', 0, 'request') > 0); $this->registerExportCSVFileName(); parent::__construct(); } /** * Returns the name of this report. * * @return string */ public function getName() { return $this->reportName; } /** * Returns the name of this file without .php. * * @return string */ public function getFileName() { return $this->reportFile; } /** * Returns the filters of this report. * * @return array */ public function getFilters() { if (count($this->reportFilters)) { // do not run this method twice, as it could load JS and CSS files. return $this->reportFilters; } // get VBO Application Object $vbo_app = VikBooking::getVboApplication(); // load the jQuery UI Datepicker $this->loadDatePicker(); // load Charts assets $this->loadChartsAssets(); // from Date Filter $filter_opt = array( 'label' => '', 'html' => '', 'type' => 'calendar', 'name' => 'fromdate' ); array_push($this->reportFilters, $filter_opt); // to Date Filter $filter_opt = array( 'label' => '', 'html' => '', 'type' => 'calendar', 'name' => 'todate' ); array_push($this->reportFilters, $filter_opt); // period type filter $pperiod = VikRequest::getString('period', 'month', 'request'); $periods = array( 'month' => JText::translate('VBPVIEWRESTRICTIONSTWO'), 'week' => JText::translate('VBOWEEK'), 'day' => JText::translate('VBODAY'), ); $periods_sel_html = $vbo_app->getNiceSelect($periods, $pperiod, 'period', '', '', '', '', 'period'); $filter_opt = array( 'label' => '', 'html' => $periods_sel_html, 'type' => 'select', 'name' => 'period' ); array_push($this->reportFilters, $filter_opt); // room ID filter $pidroom = VikRequest::getInt('idroom', '', 'request'); $all_rooms = $this->getRooms(); $rooms = array(); foreach ($all_rooms as $room) { $rooms[$room['id']] = $room['name']; } if (count($rooms)) { $rooms_sel_html = $vbo_app->getNiceSelect($rooms, $pidroom, 'idroom', JText::translate('VBOSTATSALLROOMS'), JText::translate('VBOSTATSALLROOMS'), '', '', 'idroom'); $filter_opt = array( 'label' => '', 'html' => $rooms_sel_html, 'type' => 'select', 'name' => 'idroom' ); array_push($this->reportFilters, $filter_opt); } // channel filter $all_channels = array(); $pchannel = VikRequest::getString('channel', '', 'request'); $q = "SELECT `channel` FROM `#__vikbooking_orders` WHERE `channel` IS NOT NULL GROUP BY `channel`;"; $this->dbo->setQuery($q); $this->dbo->execute(); if ($this->dbo->getNumRows()) { $ord_channels = $this->dbo->loadAssocList(); // push website as first option $all_channels['-1'] = JText::translate('VBORDFROMSITE'); // push all channel names foreach ($ord_channels as $o_channel) { $channel_parts = explode('_', $o_channel['channel']); $channel_name = count($channel_parts) > 1 ? trim($channel_parts[1]) : trim($channel_parts[0]); if (isset($all_channels[$channel_name])) { continue; } $say_channel_name = $channel_name == 'googlehotel' ? 'Google Hotel' : ucwords($channel_name); $all_channels[$channel_name] = $say_channel_name; } // push filter $channels_sel_html = $vbo_app->getNiceSelect($all_channels, $pchannel, 'channel', '- - - -', '- - - -', '', '', 'channel'); $filter_opt = array( 'label' => '', 'html' => $channels_sel_html, 'type' => 'select', 'name' => 'channel' ); array_push($this->reportFilters, $filter_opt); } // get minimum check-in and maximum check-out for dates filters $df = $this->getDateFormat(); $mincheckin = 0; $maxcheckout = 0; $q = "SELECT MIN(`checkin`) AS `mincheckin`, MAX(`checkout`) AS `maxcheckout` FROM `#__vikbooking_orders` WHERE `status`='confirmed' AND `closure`=0;"; $this->dbo->setQuery($q); $this->dbo->execute(); if ($this->dbo->getNumRows()) { $data = $this->dbo->loadAssoc(); if (!empty($data['mincheckin']) && !empty($data['maxcheckout'])) { $mincheckin = $data['mincheckin']; $maxcheckout = $data['maxcheckout']; } } // // jQuery code for the datepicker calendars and select2 $pfromdate = VikRequest::getString('fromdate', '', 'request'); $pfromdate = empty($pfromdate) && !empty($mincheckin) ? date($df, $mincheckin) : $pfromdate; $ptodate = VikRequest::getString('todate', '', 'request'); $ptodate = empty($ptodate) && !empty($maxcheckout) ? date($df, $maxcheckout) : $ptodate; $js = 'jQuery(function() { jQuery(".vbo-report-datepicker:input").datepicker({ '.(!empty($mincheckin) ? 'minDate: "'.date($df, $mincheckin).'", ' : '').' '.(!empty($maxcheckout) ? 'maxDate: "'.date($df, $maxcheckout).'", ' : '').' '.(!empty($mincheckin) && !empty($maxcheckout) ? 'yearRange: "'.(date('Y', $mincheckin)).':'.date('Y', $maxcheckout).'", changeMonth: true, changeYear: true, ' : '').' dateFormat: "'.$this->getDateFormat('jui').'", onSelect: vboReportCheckDates }); '.(!empty($pfromdate) ? 'jQuery(".vbo-report-datepicker-from").datepicker("setDate", "'.$pfromdate.'");' : '').' '.(!empty($ptodate) ? 'jQuery(".vbo-report-datepicker-to").datepicker("setDate", "'.$ptodate.'");' : '').' }); function vboReportCheckDates(selectedDate, inst) { if (selectedDate === null || inst === null) { return; } var cur_from_date = jQuery(this).val(); if (jQuery(this).hasClass("vbo-report-datepicker-from") && cur_from_date.length) { var nowstart = jQuery(this).datepicker("getDate"); var nowstartdate = new Date(nowstart.getTime()); jQuery(".vbo-report-datepicker-to").datepicker("option", {minDate: nowstartdate}); } }'; $this->setScript($js); return $this->reportFilters; } /** * Loads the report data from the DB. * Returns true in case of success, false otherwise. * Sets the columns and rows for the report to be displayed. * * @return boolean */ public function getReportData() { if (strlen($this->getError())) { // export functions may set errors rather than exiting the process, and the View may continue the execution to attempt to render the report. return false; } // input fields and other vars $pfromdate = VikRequest::getString('fromdate', '', 'request'); $ptodate = VikRequest::getString('todate', '', 'request'); $pperiod = VikRequest::getString('period', 'month', 'request'); // idroom can be an array of IDs or just one ID as int/string $pidroom = VikRequest::getVar('idroom', null, 'request'); // $pchannel = VikRequest::getString('channel', '', 'request'); $pkrsort = VikRequest::getString('krsort', $this->defaultKeySort, 'request'); $pkrsort = empty($pkrsort) ? $this->defaultKeySort : $pkrsort; $pkrorder = VikRequest::getString('krorder', $this->defaultKeyOrder, 'request'); $pkrorder = empty($pkrorder) ? $this->defaultKeyOrder : $pkrorder; $pkrorder = $pkrorder == 'DESC' ? 'DESC' : 'ASC'; // bookings max creation date $pmaxdate = VikRequest::getString('maxdate', '', 'request'); $pmaxdate = !empty($pmaxdate) ? VikBooking::getDateTimestamp($pmaxdate, 23, 59, 59) : $pmaxdate; // $currency_symb = VikBooking::getCurrencySymb(); $df = $this->getDateFormat(); $datesep = VikBooking::getDateSeparator(); if (empty($ptodate)) { $ptodate = $pfromdate; } // get dates timestamps $from_ts = VikBooking::getDateTimestamp($pfromdate, 0, 0); $to_ts = VikBooking::getDateTimestamp($ptodate, 23, 59, 59); if (empty($pfromdate) || empty($from_ts) || empty($to_ts)) { $this->setError(JText::translate('VBOREPORTSERRNODATES')); return false; } // months map $months_map = array( JText::translate('VBMONTHONE'), JText::translate('VBMONTHTWO'), JText::translate('VBMONTHTHREE'), JText::translate('VBMONTHFOUR'), JText::translate('VBMONTHFIVE'), JText::translate('VBMONTHSIX'), JText::translate('VBMONTHSEVEN'), JText::translate('VBMONTHEIGHT'), JText::translate('VBMONTHNINE'), JText::translate('VBMONTHTEN'), JText::translate('VBMONTHELEVEN'), JText::translate('VBMONTHTWELVE'), ); // query to obtain the records $q = "SELECT `o`.`id`,`o`.`ts`,`o`.`days`,`o`.`checkin`,`o`.`checkout`,`o`.`totpaid`,`o`.`roomsnum`,`o`.`total`,`o`.`idorderota`,`o`.`channel`,`o`.`country`,`o`.`tot_taxes`," . "`o`.`tot_city_taxes`,`o`.`tot_fees`,`o`.`cmms`,`or`.`idorder`,`or`.`idroom`,`or`.`optionals`,`or`.`cust_cost`,`or`.`cust_idiva`,`or`.`extracosts`,`or`.`room_cost`,`r`.`name` AS `room_name` " . "FROM `#__vikbooking_orders` AS `o` LEFT JOIN `#__vikbooking_ordersrooms` AS `or` ON `or`.`idorder`=`o`.`id` " . "LEFT JOIN `#__vikbooking_rooms` AS `r` ON `or`.`idroom`=`r`.`id` " . "WHERE ".(!empty($pmaxdate) ? "`o`.`ts`<={$pmaxdate} AND " : "")."`r`.`name` IS NOT NULL AND `o`.`status`='confirmed' AND `o`.`closure`=0 AND `o`.`checkout`>={$from_ts} AND `o`.`checkin`<={$to_ts} " . (!empty($pidroom) && !is_array($pidroom) ? "AND `or`.`idroom`=" . (int)$pidroom . " " : (is_array($pidroom) && count($pidroom) ? "AND `or`.`idroom` IN (" . implode(', ', $pidroom) . ") " : '')) . (strlen($pchannel) ? "AND `o`.`channel` " . ($pchannel == '-1' ? 'IS NULL' : "LIKE " . $this->dbo->quote("%{$pchannel}%")) . ' ' : '') . "ORDER BY `o`.`checkin` ASC, `o`.`id` ASC, `or`.`id` ASC;"; $this->dbo->setQuery($q); $records = $this->dbo->loadAssocList(); $dummy_values = false; if (!count($records)) { if ($pperiod != 'full') { // when using the regular report interface, we display an error in case of no bookings found $this->setError(JText::translate('VBOREPORTSERRNORESERV')); return false; } // layout file building the chart prefers empty values rather than an error $dummy_values = true; // populate array with one dummy booking with empty values $records = array( array( 'id' => -1, 'ts' => time(), 'days' => 1, 'checkin' => $from_ts, 'checkout' => strtotime("+1 day", $from_ts), 'totpaid' => 0, 'roomsnum' => 1, 'total' => 0, 'idorderota' => null, 'channel' => null, 'country' => null, 'tot_taxes' => 0, 'tot_city_taxes' => 0, 'tot_fees' => 0, 'cmms' => 0, 'idorder' => -1, 'idroom' => -1, 'optionals' => null, 'cust_cost' => null, 'cust_idiva' => null, 'extracosts' => null, 'room_cost' => 0, ), ); } // nest records with multiple rooms booked inside sub-array $bookings = array(); foreach ($records as $v) { if (!isset($bookings[$v['id']])) { $bookings[$v['id']] = array(); } // calculate the from_ts and to_ts values for later comparison $in_info = getdate($v['checkin']); $out_info = getdate($v['checkout']); // these two properties are necessary for many other controls below $v['from_ts'] = mktime(0, 0, 0, $in_info['mon'], $in_info['mday'], $in_info['year']); $v['to_ts'] = mktime(23, 59, 59, $out_info['mon'], ($out_info['mday'] - 1), $out_info['year']); // array_push($bookings[$v['id']], $v); } // first day of the week for weekly periods (0 for Sunday till 6 for Saturday) $firstwday = (int)VikBooking::getFirstWeekDay(); // we make it end to the day before as weeks should start on this weekday $firstwday -= 1; $firstwday = $firstwday < 0 ? 6 : $firstwday; // build ranges of periods by looping over the dates of the report $ranges = array(); $from_info = getdate($from_ts); $to_info = getdate($to_ts); $cur_month = array('from_ts' => $from_info[0]); $cur_week = array('from_ts' => $from_info[0]); while ($from_info[0] <= $to_info[0]) { if ($pperiod == 'month') { if (date('n', $from_info[0]) != date('n', $cur_month['from_ts'])) { // month has changed, set to_ts to previous day at midnight $cur_month['to_ts'] = mktime(23, 59, 59, $from_info['mon'], ($from_info['mday'] - 1), $from_info['year']); // push month delimiter to ranges array_push($ranges, array( 'from_ts' => $cur_month['from_ts'], 'to_ts' => $cur_month['to_ts'], )); // reset current month handler to current day (1st of the new month) $cur_month = array( 'from_ts' => $from_info[0] ); } } elseif ($pperiod == 'week') { if (!isset($cur_week['from_ts'])) { // 1st day of the new week $cur_week['from_ts'] = $from_info[0]; } if ($from_info[0] != $cur_week['from_ts'] && (int)$from_info['wday'] == $firstwday) { // not the first day of the loop, but same weekday, so it's the week after $cur_week['to_ts'] = mktime(23, 59, 59, $from_info['mon'], $from_info['mday'], $from_info['year']); // push week delimiter to ranges array_push($ranges, array( 'from_ts' => $cur_week['from_ts'], 'to_ts' => $cur_week['to_ts'], )); // reset current week handler $cur_week = array(); } } elseif ($pperiod == 'day') { // push the range until the end of the current day array_push($ranges, array( 'from_ts' => $from_info[0], 'to_ts' => mktime(23, 59, 59, $from_info['mon'], $from_info['mday'], $from_info['year']), )); } else { // (full) push the range until the "to date" array_push($ranges, array( 'from_ts' => $from_info[0], 'to_ts' => $to_info[0], )); // do not loop any further date as we need the entire range requested break; } // next day iteration $from_info = getdate(mktime(0, 0, 0, $from_info['mon'], ($from_info['mday'] + 1), $from_info['year'])); } // finalize ranges of period delimiters in case the loop ended on a non-precise date if ($pperiod == 'month' && date('Y-m-d', $cur_month['from_ts']) != date('Y-m-d', $to_info[0])) { // push last month delimiter to ranges array_push($ranges, array( 'from_ts' => $cur_month['from_ts'], 'to_ts' => $to_info[0], )); } elseif ($pperiod == 'week' && isset($cur_week['from_ts'])) { // push last week delimiter to ranges array_push($ranges, array( 'from_ts' => $cur_week['from_ts'], 'to_ts' => $to_info[0], )); } // total number of rooms $total_rooms_units = $this->countRooms($pidroom) ?: 1; // define the columns of the report $this->cols = array( // date array( 'key' => 'day', 'sortable' => 1, 'label' => JText::translate('VBOREPORTREVENUEDAY') ), // rooms sold array( 'key' => 'rooms_sold', 'attr' => array( 'class="center"' ), 'sortable' => 1, 'label' => JText::translate('VBOREPORTREVENUERSOLD'), 'tip' => JText::sprintf('VBOREPORTTOTROOMSHELP', $total_rooms_units) ), // nights booked array( 'key' => 'nights_booked', 'attr' => array( 'class="center"' ), 'sortable' => 1, 'label' => JText::translate('VBOGRAPHTOTNIGHTSLBL') ), // total bookings array( 'key' => 'tot_bookings', 'attr' => array( 'class="center"' ), 'sortable' => 1, 'label' => JText::translate('VBOREPORTREVENUETOTB') ), // % occupancy array( 'key' => 'occupancy', 'attr' => array( 'class="center"' ), 'sortable' => 1, 'label' => JText::translate('VBOREPORTREVENUEPOCC') ), // IBE revenue array( 'key' => 'ibe_revenue', 'attr' => array( 'class="center vbo-report-col-hideable"' ), 'sortable' => 1, 'label' => JText::translate('VBOREPORTREVENUEREVWEB') ), // OTAs revenue array( 'key' => 'ota_revenue', 'attr' => array( 'class="center vbo-report-col-hideable"' ), 'sortable' => 1, 'label' => JText::translate('VBOREPORTREVENUEREVOTA') ), // ADR array( 'key' => 'adr', 'attr' => array( 'class="center vbo-report-col-hideable"' ), 'sortable' => 1, 'label' => JText::translate('VBOREPORTREVENUEADR'), 'tip' => JText::translate('VBOREPORTREVENUEADRHELP') ), // RevPAR array( 'key' => 'revpar', 'attr' => array( 'class="center vbo-report-col-hideable"' ), 'sortable' => 1, 'label' => JText::translate('VBOREPORTREVENUEREVPAR'), 'tip' => JText::translate('VBOREPORTREVENUEREVPARH') ), // Taxes array( 'key' => 'taxes', 'attr' => array( 'class="center vbo-report-col-hideable"' ), 'sortable' => 1, 'label' => JText::translate('VBOREPORTREVENUETAX') ), // Revenue array( 'key' => 'revenue', 'attr' => array( 'class="center"' ), 'sortable' => 1, 'label' => JText::translate('VBOREPORTREVENUEREV') ) ); // loop over the ranges to build the rows foreach ($ranges as $ind => $range) { // prepare default fields for this row $range_ts_from = $range['from_ts']; $info_ts_from = getdate($range['from_ts']); $range_ts_to = $range['to_ts']; $info_ts_to = getdate($range['to_ts']); $curwday_from = $this->getWdayString($info_ts_from['wday'], 'short'); $curwday_to = $this->getWdayString($info_ts_to['wday'], 'short'); $range_same_day = (date('Y-m-d', $range_ts_from) == date('Y-m-d', $range_ts_to)); $rooms_sold = 0; $nights_booked = 0; $tot_bookings = 0; $occupancy = 0; $ibe_revenue = 0; $ota_revenue = 0; $adr = 0; $revpar = 0; $taxes = 0; $revenue = 0; // count the days in this range if (!isset($ranges[$ind]['days'])) { $ranges[$ind]['days'] = $this->countDaysInRange($info_ts_from, $info_ts_to); } // maximum occupancy of this range is given by the days in the range times the total rooms units $range_max_occupancy = $ranges[$ind]['days'] * $total_rooms_units; $range_max_occupancy = $range_max_occupancy < 1 ? 1 : $range_max_occupancy; // calculate the report details for this day foreach ($bookings as $gbook) { if ($dummy_values) { // we need all values to be left as 0 break; } if ( // range start date is between the check-in and check-out of this booking $range['from_ts'] >= $gbook[0]['from_ts'] && $range['from_ts'] <= $gbook[0]['to_ts'] || // range end date is between the check-in and check-out of this booking $range['to_ts'] >= $gbook[0]['from_ts'] && $range['to_ts'] <= $gbook[0]['to_ts'] || // range start and end dates include this booking (probably a long period or a short booking) $range['from_ts'] <= $gbook[0]['from_ts'] && $range['to_ts'] >= $gbook[0]['to_ts'] ) { // this booking affects the current range of dates if (!isset($ranges[$ind]['bookings'])) { $ranges[$ind]['bookings'] = array(); } array_push($ranges[$ind]['bookings'], $gbook[0]['id']); // increase values $rooms_sold += $gbook[0]['roomsnum']; // nights booked is per rooms booked, but $booking_nights is the total nights booked per booking, not per room $booking_nights = $this->countNightsBookedRange($info_ts_from, $info_ts_to, $gbook[0]); $nights_booked += $booking_nights * $gbook[0]['roomsnum']; $tot_bookings++; // calculate net revenue and taxes $tot_net = $gbook[0]['total'] - (float)$gbook[0]['tot_taxes'] - (float)$gbook[0]['tot_city_taxes'] - (float)$gbook[0]['tot_fees'] - (float)$gbook[0]['cmms']; $tot_net = $tot_net / $gbook[0]['days'] * $booking_nights; $revenue += $tot_net; if (!empty($gbook[0]['idorderota']) && !empty($gbook[0]['channel'])) { $ota_revenue += $tot_net; } else { $ibe_revenue += $tot_net; } $tot_taxes = ((float)$gbook[0]['tot_taxes'] + (float)$gbook[0]['tot_city_taxes'] + (float)$gbook[0]['tot_fees'] + (float)$gbook[0]['cmms']) / $gbook[0]['days'] * $booking_nights; $taxes += $tot_taxes; } } $occupancy = round(($nights_booked * 100 / $range_max_occupancy), 2); $adr = $rooms_sold > 0 ? $revenue / $rooms_sold : 0; $revpar = $total_rooms_units > 0 ? ($revenue / $total_rooms_units) : 0; // push fields in the rows array as a new row array_push($this->rows, array( array( 'key' => 'day', 'callback' => function ($val) use ($range_ts_to, $df, $datesep, $curwday_from, $curwday_to, $pperiod, $months_map, $range_same_day) { if ($pperiod == 'day' || $range_same_day) { return $curwday_from . ', ' . date(str_replace("/", $datesep, $df), $val); } if (($pperiod == 'month' || $pperiod == 'full') && date('d', $val) == '1' && date('t', $range_ts_to) == date('d', $range_ts_to) && date('m', $val) == date('m', $range_ts_to)) { // full month return $months_map[((int)date('m', $val) - 1)] . ' ' . date('Y', $val); } return $curwday_from . ', ' . date(str_replace("/", $datesep, $df), $val) . ' - ' . $curwday_to . ', ' . date(str_replace("/", $datesep, $df), $range_ts_to); }, 'value' => $range_ts_from ), array( 'key' => 'rooms_sold', 'attr' => array( 'class="center"' ), 'value' => $rooms_sold ), array( 'key' => 'nights_booked', 'attr' => array( 'class="center"' ), 'callback' => function ($val) use ($range_max_occupancy) { return $val . ' / ' . $range_max_occupancy; }, 'value' => $nights_booked ), array( 'key' => 'tot_bookings', 'attr' => array( 'class="center"' ), 'value' => $tot_bookings ), array( 'key' => 'occupancy', 'attr' => array( 'class="center"' ), 'value' => $occupancy ), array( 'key' => 'ibe_revenue', 'attr' => array( 'class="center vbo-report-col-hideable"' ), 'callback' => function ($val) use ($currency_symb) { return $currency_symb.' '.VikBooking::numberFormat($val); }, 'value' => $ibe_revenue ), array( 'key' => 'ota_revenue', 'attr' => array( 'class="center vbo-report-col-hideable"' ), 'callback' => function ($val) use ($currency_symb) { return $currency_symb.' '.VikBooking::numberFormat($val); }, 'value' => $ota_revenue ), array( 'key' => 'adr', 'attr' => array( 'class="center vbo-report-col-hideable"' ), 'callback' => function ($val) use ($currency_symb) { return $currency_symb.' '.VikBooking::numberFormat($val); }, 'value' => $adr ), array( 'key' => 'revpar', 'attr' => array( 'class="center vbo-report-col-hideable"' ), 'callback' => function ($val) use ($currency_symb) { return $currency_symb.' '.VikBooking::numberFormat($val); }, 'value' => $revpar ), array( 'key' => 'taxes', 'attr' => array( 'class="center vbo-report-col-hideable"' ), 'callback' => function ($val) use ($currency_symb) { return $currency_symb.' '.VikBooking::numberFormat($val); }, 'value' => $taxes ), array( 'key' => 'revenue', 'attr' => array( 'class="center"' ), 'callback' => function ($val) use ($currency_symb) { return $currency_symb.' '.VikBooking::numberFormat($val); }, 'value' => $revenue ) )); } // sort rows $this->sortRows($pkrsort, $pkrorder); // update sorting and ordering key $this->defaultKeySort = $pkrsort; $this->defaultKeyOrder = $pkrorder; // loop over the rows to build the footer row with the totals $foot_rooms_sold = 0; $foot_nights_booked = 0; $foot_tot_bookings = 0; $foot_ibe_revenue = 0; $foot_ota_revenue = 0; $foot_taxes = 0; $foot_revenue = 0; foreach ($this->rows as $row) { $foot_rooms_sold += $row[1]['value']; $foot_nights_booked += $row[2]['value']; $foot_tot_bookings += $row[3]['value']; $foot_ibe_revenue += $row[5]['value']; $foot_ota_revenue += $row[6]['value']; $foot_taxes += $row[9]['value']; $foot_revenue += $row[10]['value']; } array_push($this->footerRow, array( array( 'attr' => array( 'class="vbo-report-total"' ), 'value' => '
' . print_r($bookings, true) . '
'.print_r($bookings, true).'