<?php

/**
 * @desc This is the master report class. All other reports should be childen of this parent class
 * it is assumed that this is included somewhere where common.inc.php is also included
 */
if (!defined('APP_INCLUDE')) {
    die('invalid inclusion');
}

include_once(logaholic_dir() . "includes/datavisualization.php");

class Report extends DataVisualization
{
    var $from;                    // This contains the from timestamp
    var $to;                        // This contains the to timestamp
    var $labels;                    // This contains the report name string or constant from the url - don't use this use $this->label or $this->label_constant instead
    var $paginationPanel;            // This is a toggle to show the pagination panel under a report or not.
    var $sortTable;                    // If this is set to false, no javascript datatable plugin will be activated
    var $help;                        // This contains the help text for a report
    var string $addlabel;                    // this can be used to add custom text to the report options list in the header
    var $customHeaderContent;        // replaces the list of report options in the header with a custom string (default empty)
    var $graphcolors;                // contains the colors of graphs and pies.
    var $template;                    // Globaling the template
    var $allowDateFormat;            // Allows the use of dateFormat from the profile. ( default true )
    var string $DefaultDisplay;
    var string $period;
    var $roadto;
    var string $sparktype;
    var bool $hidelegenda;                // Removes the legend from a report ( default false )

    var $dbMinimumDate;                // minimum timestamp in the database
    var $dbMaximumDate;                // maximum timestamp in the database

    var bool $regenerateDatafiles;        // Force overwrite of datafiles
    var string $dataCollectType;            // Define how to collect the datafiles : from months, days or both? (default:auto = both)

    var bool $ignoreDayFileCreation;        // Does exactly what it says. With this set to TRUE , createDataFiles will ignore creating files per day.
    var string $fileNameExpension;        // !MUST START WITH '.'! Add an extension name to the exported & imported data files. Example: (See top cities report) returns string.
    var $columnDefinitions;
    var int $key_col;
    var bool $sort;
    var int $sort_order;
    var int $sort_key;


    var $label;                        // this is the report label STRING
    var $label_constant;            // this is the report CONSTANT
    var bool $datafiles;

    var bool $available;                    // there is something wrong with the report so add an notice to tell everyone if it is false
    var $deleteOutdatedFiles;        // if this is set to false the update won't re-create the datafile ( defeault true )
    var array $remove_outputmodes;        // array containing any outpout modes that are not available, like xml, or csv (default empty array)

    function __construct()
    {
        global $from, $to, $profile, $template, $db, $reports, $get_label, $cnames;

        $this->from = $from;
        $this->to = $to;
        $this->profile = $profile;
        $this->addlabel = "";
        $this->datafiles = true;
        $this->displayHeader = true;
        $this->displayReportLabel = false;
        $this->displayReportButtons = true;
        $this->displayTotalRow = true;
        $this->template = $template;
        $this->allowDateFormat = true;
        $this->DefaultDisplay = "table";
        $this->period = 'auto';
        $this->fileNameExpension = "";
        $this->ignoreDayFileCreation = false;
        $this->available = true;
        $this->hidelegenda = false;
        $this->sparktype = "";
        $this->roadto = "";

        $this->regenerateDatafiles = false;
        $this->dataCollectType = "days";

        $this->columnDefinitions = [];
        $this->sort = true;
        $this->sort_order = SORT_ASC;
        $this->sort_key = 0;
        $this->key_col = 0;
        $this->stacked = false;
        $this->chartoptions = [
            "stacked_graph_style" => "expand",
            "stacked_showcontrols" => false,
            "showlegend" => true
        ];
        $this->remove_outputmodes = [];

        if ($this->profile !== null) {
            if (!empty($this->profile->min_db_timestamp) && !empty($this->profile->max_db_timestamp)) {
                $this->dbMinimumDate = $this->profile->min_db_timestamp;
                $this->dbMaximumDate = $this->profile->max_db_timestamp;
            } else {
                $q = $db->Execute(
                    "SELECT MIN(timestamp) as mindate, MAX(timestamp) as maxdate FROM {$this->profile->tablename}"
                );
                if ($da = $q->FetchRow()) {
                    if ($da["mindate"] == 0 && $da["maxdate"] != 0) {
                        $this->dbMinimumDate = $da["maxdate"];
                        echoWarning(
                            "Entries for 1 Jan 1970 detected, you may have corrupted data. Overriding from date."
                        );
                    } else {
                        $this->dbMinimumDate = $da["mindate"];
                    }

                    $this->dbMaximumDate = $da["maxdate"];
                } else {
                    $this->dbMinimumDate = 0;
                    $this->dbMaximumDate = 0;
                }
            }

            if ($this->profile->animate == 1) {
                $this->animate_graph = 'false';
            } else {
                $this->animate_graph = 'true';
            }
        }

        #Create a ignore total row array for labels.
        $this->noTotalRow = [
            _DATE,
            _BROWSER_VERSION,
            _BROWSER . " version",
            _OS_VERSION,
            _PAGES_PER_USER,
            _HOUR,
            _IP_NUMBER,
            _CRAWLED_PERC,
            _PAGES_PER_IP,
            _STATUS,
            _CONVERSION_PERC,
            _CONVERSION_RATE,
            _BOUNCE_RATE,
            _RETENTION_PERC,
            _SEARCHES_PER_USER,
            _AVERAGE_DURATION_IN_MINUTES,
            _TIME_SPENT,
            _VISIT_SHARE,
            _CONVERSION,
            _INTERNAL_KEYWORD,
            _KEYWORDS,
            _UNIQUE_VISITORS
        ];


        # check if HTTP_USER_AGENT is set. if not we are running from command line.
        if (isset($_SERVER['HTTP_USER_AGENT'])) {
            $useragent = $_SERVER['HTTP_USER_AGENT'];
            if (strpos($useragent, "MSIE") > 0) {
                $ie_agent = substr($useragent, strpos($useragent, "MSIE"), 10);
                $ie_agent = explode(" ", $ie_agent);
                $ie_version = explode(".", $ie_agent[1]);
                $ie_version = $ie_version[0];
            }
        }

        if (!empty($ie_version) && $ie_version <= 8) {
            $this->graphcolors = [
                "rgb(0,120,174)",
                "rgb(255,0,0)",
                "rgb(0,111,0)",
                "rgb(0,221,34)",
                "rgb(200,66,57)",
                "rgb(249,113,247)",
                "rgb(60,31,110)",
                "rgb(46,136,10)",
                "rgb(221,24,64)",
                "rgb(252,218,0)",
                "rgb(25,65,165)",
                "rgb(175,216,248)",
                "rgb(246,189,15)",
                "rgb(139,186,0)",
                "rgb(166,110,221)",
                "rgb(249,132,161)",
                "rgb(204,204,0)",
                "rgb(153,153,153)",
                "rgb(0,153,225)",
                "rgb(255,102,204)",
                "rgb(102,153,102)",
                "rgb(124,124,180)",
                "rgb(255,153,51)",
                "rgb(152,0,255)",
                "rgb(153,0,204)",
                "rgb(204,204,0)",
                "rgb(102,153,0)",
                "#FF0000",
                "#00F000",
                "#0000F0",
                "#FF00FF",
                "#00FFFF",
                "#FFFF00",
                "#1970B4",
                "#19B470",
                "#7019B4",
                "#70B419",
                "#B41970",
                "#B47019",
                "#FB9619",
                "#FB1996",
                "#96FB19",
                "#9619FB",
                "#1996FB",
                "#19FB96",
                "#CC0000",
                "#00C000",
                "#0000C0",
                "#CC00CC",
                "#00CCCC",
                "#CCCC00",
                "#414D91",
                "#41914D",
                "#4D4191",
                "#4D9141",
                "#91414D",
                "#914D41",
                "#E78741",
                "#E74187",
                "#87E741",
                "#8741E7",
                "#4187E7",
                "#41E787",
                "#990000",
                "#009000",
                "#000090",
                "#990099",
                "#009999",
                "#999900",
                "#692A6E",
                "#696E2A",
                "#2A696E",
                "#2A6E69",
                "#6E692A",
                "#6E2A69",
                "#D37869",
                "#D36978",
                "#78D369",
                "#7869D3",
                "#6978D3",
                "#69D378",
                "#660000",
                "#006000",
                "#000060",
                "#660066",
                "#006666",
                "#666600",
                "#91074B",
                "#914B07",
                "#07914B",
                "#074B91",
                "#4B9107",
                "#4B0791",
                "#BF6991",
                "#BF9169",
                "#69BF91",
                "#6991BF",
                "#9169BF",
                "#91BF69"
            ];
        } else {
            $this->graphcolors = [
                "#1F77B4",
                "#AEC7E8",
                "#FF7F0E",
                "#FFBB78",
                "#2CA02C",
                "#98DF8A",
                "#D62728",
                "#FF9896",
                "#9467BD",
                "#C5B0D5",
                "#C5B0D5",
                "rgba(226,82,29,1)",
                "rgba(119,189,57,1)",
                "rgba(44,181,233,1)",
                "rgba(233,165,38,1)",
                "rgba(0,120,174,1)",
                "rgba(255,0,0,1)",
                "rgba(0,111,0,1)",
                "rgba(0,221,34,1)",
                "rgba(200,66,57,1)",
                "rgba(249,113,247,1)",
                "rgba(60,31,110,1)",
                "rgba(46,136,10,1)",
                "rgba(221,24,64,1)",
                "rgba(252,218,0,1)",
                "rgba(25,65,165,1)",
                "rgba(175,216,248,1)",
                "rgba(246,189,15,1)",
                "rgba(139,186,0,1)",
                "rgba(166,110,221,1)",
                "rgba(249,132,161,1)",
                "rgba(204,204,0,1)",
                "rgba(153,153,153,1)",
                "rgba(0,153,225,1)",
                "rgba(255,102,204,1)",
                "rgba(102,153,102,1)",
                "rgba(124,124,180,1)",
                "rgba(255,153,51,1)",
                "rgba(152,0,255,1)",
                "rgba(153,0,204,1)",
                "rgba(204,204,0,1)",
                "rgba(102,153,0,1)",
                "#FF0000",
                "#00F000",
                "#0000F0",
                "#FF00FF",
                "#00FFFF",
                "#FFFF00",
                "#1970B4",
                "#19B470",
                "#7019B4",
                "#70B419",
                "#B41970",
                "#B47019",
                "#FB9619",
                "#FB1996",
                "#96FB19",
                "#9619FB",
                "#1996FB",
                "#19FB96",
                "#CC0000",
                "#00C000",
                "#0000C0",
                "#CC00CC",
                "#00CCCC",
                "#CCCC00",
                "#414D91",
                "#41914D",
                "#4D4191",
                "#4D9141",
                "#91414D",
                "#914D41",
                "#E78741",
                "#E74187",
                "#87E741",
                "#8741E7",
                "#4187E7",
                "#41E787",
                "#990000",
                "#009000",
                "#000090",
                "#990099",
                "#009999",
                "#999900",
                "#692A6E",
                "#696E2A",
                "#2A696E",
                "#2A6E69",
                "#6E692A",
                "#6E2A69",
                "#D37869",
                "#D36978",
                "#78D369",
                "#7869D3",
                "#6978D3",
                "#69D378",
                "#660000",
                "#006000",
                "#000060",
                "#660066",
                "#006666",
                "#666600",
                "#91074B",
                "#914B07",
                "#07914B",
                "#074B91",
                "#4B9107",
                "#4B0791",
                "#BF6991",
                "#BF9169",
                "#69BF91",
                "#6991BF",
                "#9169BF",
                "#91BF69"
            ];
        }

        # colors for bars in html tables
        $this->barcolors = [
            "#9ecae1",
            "#c6dbef",
            "#fdae6b",
            "#fdd0a2",
            "#a1d99b",
            "#c7e9c0",
            "#fb6a4a",
            "#fcae91"
        ];

        # set $_GET to vars
        foreach ($_GET as $key => $value) {
            if (isset($value)) {
                if (is_array($value)) {
                    $this->$key = $value;
                    $this->options[$key] = $value;
                } else {
                    $this->$key = addslashes(urldecode($value));
                    $this->options[$key] = addslashes(urldecode($value));
                }
            }
        }

        # set $_POST to vars
        foreach ($_POST as $key => $value) {
            if (isset($value)) {
                if (is_array($value)) {
                    $this->$key = $value;
                    $this->options[$key] = $value;
                } else {
                    $this->$key = addslashes(urldecode($value));
                    $this->options[$key] = addslashes(urldecode($value));
                }
            }
        }

        if (empty($this->source)) {
            $this->source = "";
        }

        if ($this->period == 'auto') {
            $this->period = $this->getPeriod($this->from, $this->to);
        }

        if (!isset($this->trafficsource)) {
            if (isset($_SESSION["trafficsource"])) {
                unset($_SESSION["trafficsource"]);
            }
            $this->trafficsource = null;
            $this->applytrafficsource = false;
        } else {
            $this->applytrafficsource = true;
        }


        if (empty($this->current_plotting_graph)) {
            $this->current_plotting_graph = 1;
        }

        $this->sortTable = true;

        if (empty($this->label)) {
            if (isset($get_label[get_class($this)])) {
                $this->label = constant($get_label[get_class($this)]);
                $this->label_constant = $get_label[get_class($this)];
            } else {
                $this->label = _UNKNOWN;
                $this->label_constant = "_UNKNOWN";
                //echo get_class($this);
            }
        }

        if (!empty($this->label) && empty($this->roadto) && $this->label != _UNKNOWN) {
            $opts = $reports[$this->label_constant]['Options'];

            if (strpos($opts, 'roadto') !== false) {
                $targetfiles = explode(",", str_replace(" ", "", $this->profile->targetfiles));
                if (!empty($targetfiles[0])) {
                    $this->roadto = $targetfiles[0];
                } else {
                    $this->roadto = '';
                }
            }
        }
        if (!empty($this->country_list)) {
            $cflip = array_flip($cnames);
            $selected = explode(";", $this->country_list);
            foreach ($selected as $cname) {
                $this->countrycodelist[] = $cflip[$cname];
            }
        }

        if (isset($this->human) && $this->human == 1) {
            $this->fileNameExpension .= ".C1";
            $this->addlabel .= _ALL_HUMANS_AND_BOTS;
        }

        if (method_exists($this, "Settings") == true) {
            $this->Settings();
        }

        # add the default settings to the existing columnDefinitions
        $this->setupColumnDefinitions();

        if (empty($this->limit)) {
            $this->limit = 10;
        } else {
            $this->limit = intval($this->limit);
        }

        if (!isset($this->paginationPanel)) {
            if ($this->limit <= 10) {
                $this->paginationPanel = false;
            } else {
                $this->paginationPanel = true;
            }
        }

        if (empty($this->help)) {
            $this->help = $this->CreateHelpText();
        }

        if (empty($this->displaymode)) {
            $this->displaymode = $this->DefaultDisplay;
        }

        if ($this->regenerateDatafiles == "false") {
            $this->regenerateDatafiles = false;
        }
    }

    # do this for PHP 4 compatibility

    function getPeriod($from, $to): string
    {
        if ((($to - $from) / 86400) >= 217) {
            $period = '_MONTHS';
        } elseif ((($to - $from) / 86400) >= 32) {
            $period = '_WEEKS';
        } else {
            $period = '_DAYS';
        }
        return $period;
    }

    function CreateHelpText()
    {
        $preset_label = "{$this->label_constant}_DESC";
        if (defined($preset_label)) { # is there a desc made in the translation files ?
            $help = constant($preset_label);
        } else { # If not make one dynamic from viewing the column definitions
            $help = _DEFINITIONS_FOR_THIS_REPORT . ':<ul>';
            foreach ($this->columnDefinitions as $definition) {
                if ($definition["display"] !== false) {
                    if (defined("{$definition["Label"]}_DEFINITION")) {
                        $help .= "<li>" . constant("{$definition["Label"]}_DEFINITION") . "</li>";
                    }
                }
            }
            $help .= '</ul>';
        }
        return $help;
    }

    function Report()
    {
        __construct();
    }


    # this function displays any custom form fields needed to run this report

    function DisplayCustomForm()
    {
        return false;
    }

    # this function returns either a database query or false

    function DataGroupBy($data, $col)
    {
        $group = [];
        $new = [];

        foreach ($data as $v) {
            $group[$v[$col]] = $v;
        }

        $i = 0;
        foreach ($group as $nv) {
            $new[$i] = $nv;
            $i++;
        }
        return $new;
    }

    # this function creates a data array that we will use to display results

    function echoAdmin($str)
    {
        global $session;
        if ($session->isAdmin()) {
            echoNotice($str);
        }
    }

    function getTrendDataFromFiles($files)
    {
        global $get_constant;
        $data = [];
        if (DATAFILE_METHOD == "ziparchive") {
            $path = "zip://{$this->profile->datamanagerDir}{$this->profile->profilename}/reports";
        } else {
            $path = "{$this->profile->datamanagerDir}{$this->profile->profilename}/reports/";
        }

        foreach ($files as $tstamp => $file) {
            if (empty($file)) {
                continue;
            }

            $filetime = str_replace($path, '', $file);
            if (DATAFILE_METHOD == "ziparchive") {
                $filetime = explode("{$get_constant[$this->label]}.zip#/", $filetime);
                $filetime = $filetime[1];
            }
            $t = explode(".", $filetime);

            $data[$t[1]] = $this->getDataFromFile($file);
        }
        return $data;
    }

    function getDataFromFile($filename)
    {
        $fp = @gzopen($filename, "rb");

        $i = 0;
        $data = [];

        if (!$fp) {
            @error_log("Problem openening $filename");
            return $data;
        }

        while ($line = gzgets($fp)) {
            $line = $this->SearchDataFromLine($line);
            if ($line === false) {
                continue;
            }

            if ($this->limit < 50 && $i > ($this->limit * 5)) {
                break;
            } else {
                if ($this->limit >= 50 && $i > ($this->limit * 2)) {
                    break;
                }
            }

            $line = json_decode($line, true);
            if ($line === false) {
                break;
            } else {
                $data[] = $line;
                $i++;
            }
        }

        gzclose($fp);

        return $data;
    }

    function SearchDataFromLine($line)
    {
        //echo "1";
        if (!empty($this->status)) {
            if (strpos(strtolower($line), strtolower($this->status)) !== false) {
                // keep going
            } else {
                return false;
            }
        }

        if (!empty($this->countrycodelist)) {
            $dline = json_decode(($line), true);
            $item = $dline[$this->ColumnDefSearch("_COUNTRY")];
            foreach ($this->countrycodelist as $country) {
                if ($country == $item) {
                    return $line;
                }
            }
            return false;
        }

        if (empty($this->search)) {
            # nothing to do here move on
            return $line;
        }
        $dline = json_decode(($line), true);
        $c = $this->ColumnDefSearch();
        $check = explode("##", $dline[$c]);
        $item = $check[0];

        if (!empty($this->searchmode) && $this->searchmode == "not like") {
            if (strpos(strtolower($item), strtolower($this->search)) === false) {
                return $line;
            }
        } elseif (!empty($this->search) && (empty($this->searchmode) || $this->searchmode == "like")) {
            if (strpos(strtolower($item), strtolower($this->search)) !== false) {
                return $line;
            }
        }

        if (!empty($this->searchmode) && $this->searchmode == "equals") {
            if ($item === $this->search) {
                return $line;
            }
        }
        return false;
    }

    function ColumnDefSearch($label = "")
    {
        if (empty($label)) {
            $col = 0;
            foreach ($this->columnDefinitions as $k => $v) {
                if (isset($v['search']) && $v['search'] == true) {
                    $col = $k;
                    break;
                }
            }
            return $col;
        } else {
            $col = 0;
            foreach ($this->columnDefinitions as $k => $v) {
                if (isset($v['Label']) && $v['Label'] == $label) {
                    $col = $k;
                    break;
                }
            }
            return $col;
        }
    }

    # creates data files if they don't already exist

    function DisplayReport()
    {
        $data = $this->GetReportData();
        $this->DefaultDisplayReport($data);
    }

    function GetReportData()
    {
        if ($this->datafiles === false) {
            $data = $this->GetDataFromDb();
            if (is_object($data)) {
                $returndata = [];
                while ($row = $data->FetchRow()) {
                    $returndata[] = $row;
                }
                $data = $returndata;
            }
            return $data;
        }

        $this->createDataFiles($this->from, $this->to);

        return $this->getDataFromFiles($this->dataFiles($this->from, $this->to));
    }

    function GetDataFromDb()
    {
        global $db;
        $q = $this->DefineQuery();
        if ($q !== false && !empty($q)) {
            if ($this->applytrafficsource == true) {
                $q = subsetDataToSourceID($q, $this->trafficsource, $this->from);
            }
            $db->SetFetchMode(ADODB_FETCH_NUM);
            $result = $db->Execute($q);
            $db->SetFetchMode(ADODB_FETCH_BOTH);
            return $result;
        } else {
            # when the report is not based on a single database query this is where the data will be created
            return false;
        }
    }

    function DefineQuery()
    {
        return false;
    }

    # Read the data in the date range from the data files

    function createDataFiles($from, $to, $print = 0)
    {
        global $get_constant, $import;

        $this->Waitifwriting();

        if ($from < $this->dbMinimumDate) {
            $from = $this->dbMinimumDate;
        }

        if ($to > $this->dbMaximumDate) {
            $to = $this->dbMaximumDate;
        }

        $this->SetFilenameExpansion();
        if ($this->ignoreDayFileCreation == true) {
            $this->createMonthDataFiles($from, $to);
            $this->ConvertDataFilesToZip();
            return false;
        }

        if ($this->dataCollectType == "months") {
            // echoWarning("Gonna create month files for $this->label");
            if (!$this->createMonthDataFiles($from, $to)) {
                if ($print == 1) {
                    echoWarning("Failed to create month files!");
                }
                if ($print == 2) {
                    $import->LogProcess("Failed to create month files!", false);
                }
            }
        }

        $time = mktime(00, 00, 00, date("m", $from), date("d", $from), date("Y", $from));

        while ($time <= $to) {
            if ($print !== 0) {
                $start_time = time();
            }

            $dir_2 = $this->profile->datamanagerDir . $this->profile->profilename . "/reports";
            $dir_3 = $dir_2 . "/" . date("Y", $time);
            $dir_4 = $dir_3 . "/{$get_constant[$this->label]}";
            $zip_file = $dir_4 . ".zip";

            $end_of_day = mktime(23, 59, 59, date("m", $time), date("d", $time), date("Y", $time));


            $this->MakeDataDir($dir_3);
            $this->MakeDataDir($dir_4);

            $path = $dir_4;
            $daterange = date("Ymd", $time);
            $filename = "$path/{$get_constant[$this->label]}.{$daterange}{$this->fileNameExpension}.json.gz";

            if (DATAFILE_METHOD == "ziparchive") {
                $filename_check = "{$get_constant[$this->label]}.{$daterange}{$this->fileNameExpension}.json.gz";
                # if file already exists skip it
                if (file_exists($filename) && (!$this->regenerateDatafiles)) {
                    $time = $end_of_day + 1;
                    continue;
                } else {
                    if ($this->report_file_exists($filename_check, $zip_file) === true) {
                        if (!$this->regenerateDatafiles) {
                            $time = $end_of_day + 1;
                            continue;
                        } else {
                            # remove file from zip.
                            $zip = new ZipArchive;
                            if ($zip->open($zip_file) === true) {
                                # If the zip has 1 file then remove it.
                                if ($zip->numFiles === 1) {
                                    $zip->close();
                                    unlink($zip_file);
                                } else {
                                    $zip->deleteName("/$filename_check");
                                    $zip->close();
                                }
                            }
                        }
                    }
                }
            } else {
                # if file already exists skip it
                if ($this->report_file_exists($filename) === true && (!$this->regenerateDatafiles)) {
                    $time = $end_of_day + 1;
                    continue;
                }
            }


            $day = clone $this;
            $day->from = $time;
            $day->to = $end_of_day;

            #setup Data to store in Files
            $data = $day->GetDataFromDb();
            if (file_exists($filename)) {
                $fp = gzopen($filename, "wb");
            } else {

                $fp = 'error';
            }


            if ($fp == 'error') {
                //echoWarning("Could Not create file: {$filename}");
                $time = $end_of_day + 1;
                continue;
            }

            set_permissions($filename);

            if ($print == 1) {
                echo "Creating Data File {$this->label}: <b>$filename</b><br/><br/>";
                @lgflush();
            }
            if ($print == 2 && !empty($import)) {
                $import->LogProcess("Creating Data File {$this->label}: <b>$filename</b>", false);
            }

            $fileFilled = false;

            if (empty($data)) {
                # there is no data so keep going and remove this file.
            } elseif (!is_array($data)) {
                while ($row = $data->FetchRow()) {
                    gzwrite($fp, (json_encode($row)) . "\n");
                    $fileFilled = true;
                }
            } else {
                foreach ($data as $row) {
                    gzwrite($fp, (json_encode($row)) . "\n");
                    $fileFilled = true;
                }
            }

            gzclose($fp);

            if ($fileFilled == false) {
                if (is_file($filename)) {
                    unlink($filename);
                }
            } else {
                if ($print == 1) {
                    $took = time() - $start_time;
                    echo "Done. This took $took secs<br/><br/>";
                    @lgflush();
                }
                if ($print == 2 && !empty($import)) {
                    $took = time() - $start_time;
                    $import->LogProcess("Done. This took $took secs", false);
                }
            }
            $time = $end_of_day + 1;
        }
        $this->ConvertDataFilesToZip($print);
        $this->regenerateDatafiles = false;
        return true;
    }

    function Waitifwriting($i = 0)
    {
        if (DATAFILE_METHOD != "ziparchive") {
            return;
        }
        $dir_2 = $this->profile->datamanagerDir . $this->profile->profilename . "/reports";
        $dir_3 = $dir_2 . "/" . date("Y", $this->from);
        $dir_4 = $dir_3 . "/{$this->label_constant}";

        if (file_exists($dir_4)) {
            if (count(scandir($dir_4)) > 2) {
                $i++;
                # this is taking long ... check if the files that are in there have been made recently
                $newest = 0;
                if ($handle = opendir($dir_4)) {
                    while (false !== ($entry = readdir($handle))) {
                        if ($entry != "." && $entry != "..") {
                            $ftime = filemtime($dir_4 . "/" . $entry);
                            if ($ftime > $newest) {
                                //echo "newest file is $entry with timestamp $ftime ".date("Y-m-d H:i:s",$ftime);
                                $newest = $ftime;
                            }
                        }
                    }
                    closedir($handle);
                    if ((time() - $newest) > 600) {
                        # the newst file was written over 10 minutes ago. We can assume something went wrong, so let's delete those files
                        return;
                    } else {
                        if ($i > 20) {
                            die('Another process is creating data for this report, please reload later');
                        }
                    }
                }
                sleep(0.5);
                $this->Waitifwriting($i);
            }
        }
    }

    # Reads the data for the report from a single file into an array

    function SetFilenameExpansion()
    {
        if (!empty($this->trafficsource)) {
            $this->fileNameExpension .= ".S-{$this->trafficsource}";
        }

        if (!empty($this->roadto)) {
            $this->fileNameExpension .= ".T-" . md5($this->roadto);
        }
    }

    function createMonthDataFiles($from, $to)
    {
        global $get_constant;

        # Get first date from the $from;
        $from = mktime(00, 00, 00, date("m", $from), 01, date("Y", $from));
        $time = $from;

        if (!isset($get_constant[$this->label])) {
            return;
        }
        $dir_1 = $this->profile->datamanagerDir . $this->profile->profilename;
        $dir_2 = $dir_1 . "/reports";

        $this->MakeDataDir($dir_1);
        $this->MakeDataDir($dir_2);
        while ($time <= $to) {
            # Get last day of current month
            $lastday = get_month_lastday(date("m", $time), date("Y", $time));
            $l_day = mktime(23, 59, 59, date("m", $time), $lastday, date("Y", $time));
            $dir_3 = $dir_2 . "/" . date("Y", $time);
            $dir_4 = $dir_3 . "/{$get_constant[$this->label]}";

            $this->MakeDataDir($dir_3);
            $this->MakeDataDir($dir_4);

            $path = $dir_4;
            $daterange = date("Ym", $time);

            $zip_file = $dir_4 . ".zip";
            $filename = "$path/{$get_constant[$this->label]}.{$daterange}{$this->fileNameExpension}.json.gz";

            if (DATAFILE_METHOD == "ziparchive") {
                $filename_check = "{$get_constant[$this->label]}.{$daterange}{$this->fileNameExpension}.json.gz";
                # if file already exists skip it
                if (file_exists($filename) && (!$this->regenerateDatafiles)) {
                    $time = $l_day + 1;
                    continue;
                } else {
                    if ($this->report_file_exists($filename_check, $zip_file) === true) {
                        if (!$this->regenerateDatafiles) {
                            $time = $l_day + 1;
                            continue;
                        } else {
                            # remove file from zip.
                            $zip = new ZipArchive;
                            if ($zip->open($zip_file) === true) {
                                # If the zip has 1 file then remove it..
                                if ($zip->numFiles === 1) {
                                    $zip->close();
                                    unlink($zip_file);
                                } else {
                                    $zip->deleteName("/$filename_check");
                                    $zip->close();
                                }
                            }
                        }
                    }
                }
            } else {
                # if file already exists skip it
                if ($this->report_file_exists($filename) === true && (!$this->regenerateDatafiles)) {
                    $time = $l_day + 1;
                    continue;
                }
            }

            # set the file pointer
            $fp = @gzopen($filename, "wb");
            if ($fp == false) {
                echoWarning("Could Not create file: {$filename}");
                $time = $l_day + 1;
                continue;
            }
            set_permissions($filename);


            $month = clone $this;
            $month->from = $time;
            $month->to = $l_day;

            $data = $month->GetDataFromDb();

            // $fileFilled = false;

            if (!is_array($data)) {
                if ($data !== false) {
                    while ($row = $data->FetchRow()) {
                        gzwrite($fp, (json_encode($row)) . "\n");
                        // $fileFilled = true;
                    }
                }
            } else {
                foreach ($data as $row) {
                    gzwrite($fp, (json_encode($row)) . "\n");
                    // $fileFilled = true;
                }
            }

            gzclose($fp);
            // if($fileFilled == false){
            // 	unlink($filename);
            // }
            $time = $l_day + 1;
        }
        $this->regenerateDatafiles = false;
        return true;
    }

    /*
    ** This functions does the data file exists check
    */

    function MakeDataDir($dir)
    {
        if (!file_exists($dir)) {
            $e = @mkdir($dir, 0777, true);
            // we do this because multiple processes can be trying to create this and we want to avoid the file exists warning
            if ($e == false) {
                $e = error_get_last();
                if (strpos($e["message"], "File exists") === false) {
                    print_r($e);
                } else {
                    return;
                }
            }
            set_permissions($dir);
        }
    }

    function report_file_exists($file, $path = "")
    {
        if (DATAFILE_METHOD == "ziparchive") {
            # Open the archive for checks
            if (!file_exists($path)) {
                return false;
            }
            $zip = new ZipArchive();
            $zip->open($path);

            set_time_limit(300);
            $index = $zip->locateName("/$file");
            if ($index !== false) {
                $stat = $zip->statIndex($index);
                # is the file corrupted ?
                if ($stat['crc'] === 0) {
                    $zip->deleteName("/$file");
                    return false;
                }
                // if ($this->isFileOutdated($file, $stat) ===true) {
                // 	//dump($file);
                // 	$zip->deleteName("/$file");
                // 	return false;
                // }
                return true;
            }
        } else {
            # Collect the json.gz
            if (file_exists($file)) {
                return true;
            }
        }
        return false;
    }

    function ConvertDataFilesToZip($print = 0)
    {
        $start = time();
        if (DATAFILE_METHOD == "gzip") {
            return;
        }
        $zip = new ZipArchive();

        $startyear = date("Y", $this->from);
        $endyear = date("Y", $this->to);
        while ($startyear <= $endyear) {
            $path = $this->profile->datamanagerDir . $this->profile->profilename . "/reports/$startyear/" . $this->label_constant . ".zip";
            $options = ['add_path' => '/', 'remove_all_path' => true];
            $dirpath = str_replace(".zip", "", $path);

            # see if we have any new files to add to the zip
            //echoWarning(count(glob($dirpath."/*")));
            if (count(glob($dirpath . "/*")) > 0) {
                $ret = $zip->open($path, ZipArchive::CREATE);
                if ($ret !== true) {
                    echoWarning('Failed with code %d', $ret);
                } else {
                    $zip->addGlob("$dirpath/*", GLOB_BRACE, $options);
                    @$zip->close();
                    set_permissions($path);
                    $this->DelReportDataFiles($dirpath);
                    //echoWarning("made new zip $path");
                }
            }
            $startyear++;
        }

        if ($print == 2 && !empty($import)) {
            $took = time() - $start;
            $import->LogProcess("Zipping datafiles for $this->label_constant took $took secs", false);
        }
    }

    function DelReportDataFiles($reportdir)
    {
        $files = glob($reportdir . "/*");
        if ($files) {
            foreach ($files as $file) {
                @unlink($file);
            }
        }
    }

    function getDataFromFiles($files)
    {
        global $get_constant, $reports;
        $newdata = [];

        # if no files return empty array. No need for more handlings
        if (empty($files)) {
            return $newdata;
        }

        # Get data from each file
        foreach ($files as $t => $file) {
            # If we have no datafile set an empty array
            if (empty($file)) {
                $data = [$this->newReportArrayRow($t)];
                # If our data is a sum set like Top pages, we can skip the empty row
                if (empty($data[0]) || empty($data[0][0])) {
                    continue;
                }
            } else {
                $data = $this->getDataFromFile($file);
            }

            if (!is_array($data)) {
                continue;
            }
            foreach ($data as $row) {
                $row_key = $row[$this->key_col];

                if (!isset($this->columnDefinitions[$this->key_col]["display"]) || $this->columnDefinitions[$this->key_col]["display"] === true) {
                    $newdata[$row_key][$this->key_col] = $row_key;
                }

                if (!is_array($row)) {
                    continue;
                }
                $c = 0;
                foreach ($row as $key => $val) {
                    if (!isset($this->columnDefinitions[$key]["display"]) || $this->columnDefinitions[$key]["display"] === true) {
                        if ($key == $this->key_col) {
                            $c++;
                            continue;
                        }
                        if (isset($this->columnDefinitions[$key]["dataType"]) && $this->columnDefinitions[$key]["dataType"] == "String") {
                            $newdata[$row_key][$c] = $val;
                            $c++;
                            continue;
                        }
                        if (empty($val)) {
                            $val = 0;
                        }
                        if (is_numeric($val)) {
                            if (!isset($newdata[$row_key][$c])) {
                                $newdata[$row_key][$c] = $val;
                            } else {
                                $newdata[$row_key][$c] = (int)$newdata[$row_key][$c] + $val;
                            }
                        } else {
                            $newdata[$row_key][$c] = $val;
                        }
                        $c++;
                    }
                }
                //sort the keys to the expected order
                ksort($newdata[$row_key]);
            }
        }

        $data = [];
        $i = 0;
        if (empty($newdata)) {
            return [];
        }
        foreach ($newdata as $row) {
            $data[$i] = $row;
            $i++;
        }
        if ($this->sort === true) {
            $newdata = @DataSort($data, $this->sort_key, $this->sort_order);
        }

        $data = [];
        $i = 0;
        foreach ($newdata as $row) {
            $data[$i] = $row;
            $i++;
            if (strpos($reports[$get_constant[$this->label]]["Options"], "limit") !== false && $i >= $this->limit) {
                break;
            }
        }

        return $data;
    }

    # Gets the data from files for trend reports.

    function newReportArrayRow($time = 0)
    {
        $data = [];

        $total_cols = ($this->key_col > count($this->columnDefinitions)) ? $this->key_col : count(
            $this->columnDefinitions
        );

        for ($i = 0; $i < $total_cols; $i++) {
            if (!empty($this->columnDefinitions[$i]) && $this->columnDefinitions[$i]['Label'] == "_HOUR") {
                $data[$i] = '00';
            } elseif (!empty($this->columnDefinitions[$i]) && $this->columnDefinitions[$i]['Label'] == "_DATE") {
                if ($this->dataCollectType == "months") {
                    $data[$i] = date("F Y", $time);
                } else {
                    $data[$i] = date("d-M-Y D", $time);
                }
            } elseif (!empty($this->columnDefinitions[$i]) && !empty($this->columnDefinitions[$i]['dataType']) && $this->columnDefinitions[$i]['dataType'] == 'String') {
                $data[$i] = '';
            } else {
                $data[$i] = 0;
            }
        }

        return $data;
    }

    # this function will print the report, by default a table or whatever the defaults are for different displaymodes

    function dataFiles($from, $to)
    {
        global $get_constant;

        $files = [];
        $missing_files = [];
        $time = $from;
        $collect_method = $this->dataCollectType;
        $base_path = "{$this->profile->datamanagerDir}{$this->profile->profilename}/reports/";
        # This variable checks if there are files available. If there are no datafiles return an empty array instead of a list with empty values
        $files_available = false;

        if ($this->dataCollectType == "auto") {
            $auto = true;
        } else {
            $auto = false;
        }

        while ($time <= $to) {
            # if the default auto setting is set get the month files before the day files
            if ($auto) {
                $dist = $to - $time;
                if ($dist > (86400 * 30) && ($to < $this->dbMaximumDate)) {
                    $collect_method = "months";
                } else {
                    $collect_method = "days";
                }
            }

            if ($collect_method == "months") {
                # Set to collect Month files
                $daterange = date("Ym", $time);
                # Set it so the while loop goes to the next month
                $lastday = get_month_lastday(date("m", $time), date("Y", $time));
                $l_day = mktime(23, 59, 59, date("m", $time), $lastday, date("Y", $time));
                $uptime = $l_day + 1;
            } else {
                if ($collect_method == "days") {
                    # Set to collect Day Files
                    $daterange = date("Ymd", $time);

                    # Set it so the while loop goes to the next day
                    $end_day = mktime(23, 59, 59, date("m", $time), date("d", $time), date("Y", $time));
                    $uptime = $end_day + 1;
                }
            }

            # Set a variable for the name of the file that should be inserted into the array
            if (DATAFILE_METHOD == "ziparchive") {
                $path = $base_path . date("Y", $time) . "/{$get_constant[$this->label]}.zip";
                $file = "{$get_constant[$this->label]}.{$daterange}{$this->fileNameExpension}.json.gz";
                $file_to_return = "zip://$path#/$file";
            } else {
                $path = $base_path . date("Y", $time) . "/{$get_constant[$this->label]}/";
                $file = "$path{$get_constant[$this->label]}.{$daterange}{$this->fileNameExpension}.json.gz";
                $file_to_return = $file;
            }

            # Check if the file exists
            $filecheck = $this->dataFilesFileCheck($path, $file);
            if ($filecheck == true) {
                $files_available = true;
                $files[$time] = $file_to_return;
            } elseif ($collect_method == "months" && $this->dataCollectType != "months") {
                $collect_method = "days";
                $auto = false;
                $uptime = $time;
            } else {
                $files[$time] = "";
                $missing_files[$time] = $file_to_return;
            }
            $time = $uptime;
        }

        # Are we missing something here ?
        if (!empty($missing_files)) {
            $import_files = [];
            # Loop through the missing files array
            $log_dir = $this->profile->datamanagerDir . $this->profile->profilename . "/log/";
            foreach ($missing_files as $time => $file) {
                $time = (int)$time;
                $d = date("Ymd", $time);
                # never reimport a date that is still in the database
                if ($d >= date("Ymd", $this->dbMinimumDate) && $d <= date("Ymd", $this->dbMaximumDate)) {
                    continue;
                }
                if ($time > 0) {
                    $f = $log_dir . date("Ymd", $time) . ".gz";
                    if (file_exists($f) && filesize($f) > 25) {
                        $import_files[$time] = $f;
                    }
                }
            }


            # Show the reimport box
            if (!empty($import_files) && empty($this->IgnReIm)) {
                $from = (int)key($import_files);
                $this->OpenReImport($import_files);
                //dump($missing_files);
            }
        }

        if ($files_available) {
            return $files;
        } else {
            return [];
        }
    }

    # this function is seperated from DisplayReport in case sonmeone wants to use it in a child class in their custom DisplayReport function

    function dataFilesFileCheck($path, $file)
    {
        # Add the files to the files array
        if (DATAFILE_METHOD == "ziparchive") {
            if ($this->report_file_exists($file, $path) === true) {
                return true;
            } else {
                return false;
            }
        } else {
            if ($this->report_file_exists($file, $path) === true) {
                return true;
            } else {
                return false;
            }
        }
    }

    function OpenReImport($files)
    {
        $files = array_flip($files);
        $modalid = "R" . md5(time() . rand(0, 5000));

        echo "<div id='{$modalid}' class='modal reimport-modal' role='dialog'>";
        echo "<div class='modal-dialog'>";
        echo "<div class='modal-content'>
	                <div class='modal-header'>
	                    <button data-dismiss='modal' class='close' type='button'>×</button>
	                    <h4 class='modal-title'>Reimport Data for {$this->label}</h4>
	                </div>";
        echo "<div class='modal-body'>";
        echo "<span class='col-sm-6 col-xs-12'>" . date(
                implode($this->profile->dateFormat),
                min($files)
            ) . " - " . date(implode($this->profile->dateFormat), max($files)) . "</span>";
        echo "<a class='col-sm-6 col-xs-12 full-list-dates-toggle'>Show/hide all missing data.</a>";
        echo "<div class='col-xs-12 full-list-dates'>";
        foreach ($files as $t) {
            echo "<span class='col-xs-4'>" . date(implode($this->profile->dateFormat), $t) . "</span>";
        }
        echo "</div>";
        echo "<div style='clear:both;'></div>";
        echo "</div>";
        echo "<div class='modal-footer'>
			                <button data-dismiss='modal' class='btn btn-default'>Cancel</button>
	                		<form action='profile.php' method='GET' target='_self' style='float:right;margin: 0 7px;'>
			                    <button class='btn btn-success re-import'>Reimport</button>
			                    <input type='hidden' name='conf' value='{$this->profile->profilename}' />      
			                    <input type='hidden' name='reimport-from' value='" . date("d-m-Y", min($files)) . "' />      
			                    <input type='hidden' name='reimport-to' value='" . date("d-m-Y", max($files)) . "' />
		                    </form>
		                </div>";
        echo "</div>";
        echo "</div>";
        echo "</div>";
        echo "<script type='text/javascript'>
			$('#{$modalid}').modal('show'); 
			$('#{$modalid} .full-list-dates-toggle').click(function(){ 
				$('#{$modalid} .full-list-dates').toggle(); 
			});
 			ui.SetCookie('last_p_action', '_DATAMANAGER_SETTINGS');
		</script>";
    }

    function DefaultDisplayReport($data)
    {
        if (isset($this->DefaultDisplay) && !isset($this->displaymode)) {
            $this->displaymode = $this->DefaultDisplay;
        }

        if (isset($this->displaymode) && !empty($data)) {
            if ($this->displaymode == "pie") {
                $this->displayReportButtons = false;

                $this->ReportHeader();
                $this->Pie($data);
            } elseif ($this->displaymode == "linechart") {
                $this->displayReportButtons = false;

                $this->ReportHeader();
                $this->LineChart($data);
            } elseif ($this->displaymode == "barchart") {
                $this->displayReportButtons = false;

                $this->ReportHeader();
                $this->BarChart($data);
            } elseif ($this->displaymode == "areachart") {
                $this->displayReportButtons = false;

                $this->ReportHeader();
                $this->StackedAreaChart2($data);
            } elseif ($this->displaymode == "bubble") {
                $this->displayReportButtons = false;

                $this->ReportHeader();
                $this->BubbleChart($data);
            } elseif ($this->displaymode == "hbar") {
                $this->displayReportButtons = false;

                $this->ReportHeader();
                $this->Hbar($this->limit, $data);
            } elseif ($this->displaymode == "barlinechart") {
                $this->displayReportButtons = false;

                $this->ReportHeader();
                $this->MultiChart($data);
            } elseif ($this->displaymode == "multichart") {
                $this->displayReportButtons = false;

                $this->ReportHeader();
                $this->MultiChart($data);
            } elseif ($this->displaymode == "horizontal_bar_chart") {
                $this->displayReportButtons = false;

                $this->ReportHeader();
                $this->HorizontalMultiBar($data);
            } else {
                $this->ReportHeader();
                $this->Table($data);
            }
        } else {
            $this->ReportHeader();
            $this->Table($data);
        }
    }

    public function ReportHeader()
    {
        global $reports, $clabel;

        if ($this->displayHeader == false) {
            return;
        }

        if (isset($this->trafficsource)) {
            if ($this->trafficsource > 0) {
                $source = getTrafficSourceByID($this->trafficsource);
                $segmentname = $source['sourcename'];
            } else {
                $segmentname = "";
            }
        }
        if (isset($this->searchmode)) {
            if ($this->searchmode == "like") {
                $nice_searchmode = "contain";
            } elseif ($this->searchmode == "not like") {
                $nice_searchmode = "do not contain";
            } else {
                $nice_searchmode = "equals";
            }
        }
        echo "<div class='report-header'>";

        if ($this->displayReportLabel == true) {
            echo "<h2>{$this->label}</h2>";
        }

        // Check date format handling
        $nicefrom = LogaDate($this->profile->dateFormat, $this->from);
        $niceto = LogaDate($this->profile->dateFormat, $this->to);

        echo "<div class='report-header-content'>";
        echo "<div class='dialog_info'>";
        if (empty($this->customHeaderContent)) {
            if (!empty($clabel)) {
                if (strpos($reports[$clabel]["Options"], "daterangeField") !== false) {
                    echo "<span>{$nicefrom} - {$niceto}</span>";
                }
            } else {
                if (strpos($reports[$this->label_constant]["Options"], "daterangeField") !== false) {
                    echo "<span>{$nicefrom} - {$niceto}</span>";
                }
            }

            # add name to the addlabel if profilename not equal to the main profilename.
            echo "<span class='addlabel-profile-selector' style='display:none;'>Profile: {$this->profile->profilename}</span> ";
            if (!empty($this->profileselector)) {
                echo "<script type='text/javascript'> 
							if(original_conf_name !== '{$this->profileselector}'){
								report_multi_profiles = true;
								$('.addlabel-profile-selector').css('display','inline');
							}
						</script>";
            }
            if (!empty($this->addlabel)) {
                echo "<span>{$this->addlabel}</span> ";
            }
            if (!empty($segmentname)) {
                echo "<span>" . _SEGMENT . ": {$segmentname}</span> ";
            }
            if (!empty($this->search)) {
                echo "<span>" . _RESULTS_THAT . " {$nice_searchmode} '{$this->search}'</span> ";
            }
            if (!empty($this->roadto)) {
                echo "<span>" . _USING_TARGET_PAGE . " '{$this->roadto}'</span> ";
            }
        } else {
            echo "{$this->customHeaderContent}";
        }
        echo "</div>";
        echo "</div>";

        echo "<div class='help_content alert alert-info'><i class=\"fa fa-info\"></i><a class=\"btn btn-flat pull-right close_help\"><i class=\"fa fa-times\"></i></a>";
        echo "{$this->help}";
        echo "</div>";
        echo '<div class="breaker"></div>';

        echo "</div>";
    }

    public function ReorderColumns($data, $neworder)
    {
        if (empty($neworder)) {
            return $data;
        }
        $fields = $this->fieldsarray();
        $cols = array_flip($fields);
        $order = explode(',', $neworder);
        $ncols = count($order);
        $newdata = [];
        $i = 0;
        foreach ($data as $row) {
            $c = 0;
            while ($c < $ncols) {
                $thiscol = $cols[$order[$c]];
                $newdata[$i][$c] = $row[$thiscol];
                $c++;
            }
            $i++;
        }
        return $newdata;
    }

    public function CompareMeter($what, $val_a, $val_b, $text_a = "", $text_b = "")
    {
        if ($val_a == 0 && $val_b == 0) {
            $a = 50;
            $b = 50;
        } else {
            $tot = ($val_a * 100) + ($val_b * 100);
            $a = round((($val_a * 100) / $tot) * 100);
            $b = round((($val_b * 100) / $tot) * 100);
        }
        if (empty($text_a)) {
            $text_a = $val_a;
            $text_b = $val_b;
        }

        echo "<div style='margin:0 auto;width:500px;font-style:italic;text-align:center;padding:3px 0 3px 0;'>$what</div>";
        echo "<div style='margin:0 auto;width:500px;border:1px solid grey;border-radius:4px;clear:both;margin-bottom:3px;'>";

        echo "<div style='position:absolute;width:250px;height:20px;border-right:1px solid white;'></div>";

        echo "<div style='font-size:11px;width:$a%;height:20px;line-height:20px;background: #de241b url(images/bg_glass_red2.png) 50% 50% repeat-x;color:white;float:left;'></div>";
        echo "<div style='font-size:11px;width:$b%;height:20px;line-height:20px;background: #49c106 url(images/bg_glass_green.png) 50% 50% repeat-x;float:right;text-align:right;'></div>";
        echo "<div style='float:left;margin-top: -20px;width:100%; line-height:20px;'>";
        echo "<p style='float:left; margin:0 0 0 5px; padding:0; color:#FFFFFF;'>" . $text_a . "</p>";
        echo "<p style='float:right; margin:0 5px 0 0; padding:0; color:#000000;'>" . $text_b . "</p>";
        echo "</div>";
        echo "<div class=clear></div>";
        echo "</div>";
    }

    function DisplayWinner($data1, $data2, $itemname = _PAGE)
    {
        $confidence = 0;
        $cfactor = 0;
        $md5_time = md5(time());
        $container_id = $md5_time . rand(1, 1000);

        echo "<div id='decide_winner'>";
        echo "<div id='$container_id' class='confidence_graph'></div>";

        if ($data1["page_total"] == 0 || $data2["page_total"] == 0) {
            echo "<b>" . _NOT_ENOUGH_DATA_TO_DETERMINE . "!</b><br/><br/>";
        } else {
            $epop = ($data1["target_indirect_total"] + $data2["target_indirect_total"]) / ($data1["page_total"] + $data2["page_total"]);
            $eerr = sqrt(
                ($epop * (1 - $epop) * ($data1["page_total"] + $data2["page_total"])) / ($data1["page_total"] * $data2["page_total"])
            );
            $cfactor = @(abs(
                    (@($data1["target_indirect_total"] / $data1["page_total"]) - @($data2["target_indirect_total"] / $data2["page_total"]))
                ) / $eerr);

            if ($cfactor > 0.01) {
                $confidence = 1;
            }
            if ($cfactor > 0.06) {
                $confidence = 5;
            }
            if ($cfactor > 0.14) {
                $confidence = 10;
            }
            if ($cfactor > 0.25) {
                $confidence = 20;
            }
            if ($cfactor > 0.52) {
                $confidence = 39;
            }
            if ($cfactor > 0.68) {
                $confidence = 50;
            }
            if ($cfactor > 1.00) {
                $confidence = 68;
            }
            if ($cfactor > 1.64) {
                $confidence = 90;
            }
            if ($cfactor > 1.96) {
                $confidence = 95;
            }
            if ($cfactor > 2.58) {
                $confidence = 99;
            }
            if ($cfactor > 2.81) {
                $confidence = 99.5;
            }

            if (($data1["target_indirect_total"] / $data1["page_total"]) > ($data2["target_indirect_total"] / $data2["page_total"])) {
                $winner = "<span class=testa>$itemname A</span>";
                $loser = "<span class=testb>$itemname B</span>";
            } else {
                $winner = "<span class=testb>$itemname B</span>";
                $loser = "<span class=testa>$itemname A</span>";
            }
            if ($confidence >= 90) {
                echo "<font size=+1>" . _SPLIT_TEST_WINNER . " <b>$winner</b>!</font><br/><br/>";
                echo _TEST_RESULTS_ARE_SIGNIFICANT . " ($confidence% " . _CONFIDENCE . "). " . _YOU_CAN_BE_ABOUT . " $confidence% " . _CONFIDENT_THAT . " $winner " . _WILL_PERFORM_BETTER_THAN . " $loser";
            } else {
                if ($confidence > 50) {
                    echo "<font size=+1>" . _SPLIT_TEST_WINNER . " <b>$winner</b></font>";
                    echo "<P><b>$winner " . _PERFORMED_BETTER_BUT . " ... </b></p>";
                    echo _TEST_RESULTS_ARE_NOT_SIGNIFICANT . " ($confidence% " . _CONFIDENCE . "). " . _WHEN_THE_LEVEL_DROPS . " $winner " . _DIDNT_JUST_GOT_LUCKY . ".";
                } else {
                    echo "<font size=+1>" . _SPLIT_TEST_WINNER . " " . _INCONCLUSIVE . "</font>";
                    echo "<P><b>$winner " . _PERFORMED_BETTER_BUT . " ... </b></p>";
                    echo _TEST_RESULTS_ARE_NOT_SIGNIFICANT . " ($confidence% " . _CONFIDENCE . "). " . _WHEN_THE_LEVEL_DROPS . " $winner " . _DID_NOT_PERFORM_BETTER;
                }
            }
            echo "<br>";
        }
        echo "</div>";
        ?>
        <script type="text/javascript" charset="utf-8">
            $(document).ready(function () {
                s1 = [<?php echo $confidence;?>];

                plots['<?php echo $container_id; ?>'] = $.jqplot('<?php echo $container_id;?>', [s1], {
                    seriesDefaults: {
                        pointLabels: {
                            show: false
                        },
                        renderer: $.jqplot.MeterGaugeRenderer,
                        rendererOptions: {
                            label: 'Confidence',
                            padding: 0,
                            min: 0,
                            max: 100,
                            intervals: [25, 50, 75, 90, 100],
                            intervalColors: ['#FCF2E5', '#FCDCB3', '#FCB85F', '#FC950F', '#0078AE']
                        }
                    },
                    highlighter: {
                        show: false
                    },
                    cursor: {
                        show: false
                    },

                });
            });
        </script>
        <?php
    }

    function UpdateStats($from = '', $to = '', $print = 2)
    {
        if (empty($from)) {
            $from = $this->from;
        }
        if (empty($to)) {
            $to = $this->to;
        }

        $this->createDataFiles(
            mktime(0, 0, 0, date('m', $from), date('d', $from), date('Y', $from)),
            mktime(23, 59, 59, date("m", $to), date("d", $to), date("Y", $to)),
            $print
        );

        return false;
    }

    function DisplayCSV()
    {
        $this->IgnReIm = 1; # Ignore reimport box
        if (in_array("csv", $this->remove_outputmodes)) {
            echo "CSV output is not available for this data";
            return;
        }
        $data = $this->GetReportData();
        $this->CSVStatsTable($data, $this->from, $this->to, $this->label);
    }

    function DisplayJSON()
    {
        $this->IgnReIm = 1; # Ignore reimport box
        if (in_array("json", $this->remove_outputmodes)) {
            $data = ["JSON output is not available for this data"];
            echo json_encode($data);
            return;
        }
        $data = $this->GetReportData();
        foreach ($this->columnDefinitions as $col) {
            if (!isset($col["display"]) || $col["display"] === true) {
                $data['headers'][] = $col['Label'];
            }
        }

        if (isset($_REQUEST['pretty']) == 'true') {
            header('Content-Type: application/json');
            echo json_encode($data, JSON_PRETTY_PRINT);
        } else {
            echo json_encode($data);
        }
    }

    function DisplayXML()
    {
        $this->IgnReIm = 1; # Ignore reimport box
        if (in_array("xml", $this->remove_outputmodes)) {
            echo "XML output is not available for this data";
            return;
        }
        $data = $this->GetReportData();
        $this->XMLStatsTable($data, $this->from, $this->to, $this->label);
    }

    function DisplaySimpleTable()
    {
        $data = $this->GetReportData();
        $this->SimpleStatsTable($data, $this->from, $this->to, $this->label);
    }

    // this creates an empty zerofilled array

    function MakeSearchString($search, $field, $searchmode)
    {
        //this function turns the search field input into valid sql
        global $db;

        if (strpos($search, " and ") != false) {
            $searchitems = explode(" and ", $search);
            $andor = "AND";
        } else {
            if (strpos($search, " or ") != false) {
                $searchitems = explode(" or ", $search);
                $andor = "OR";
            } else {
                $searchitems[0] = $search;
                $andor = "AND";
            }
        }
        $i = 0;

        if ($searchmode == "equals") {
            $searchst = "and $field = " . $db->Quote($searchitems[$i]);
        } else {
            if ($searchmode == "like" || $searchmode == "not like") {
                $searchst = "and $field $searchmode " . $db->Quote("%$searchitems[$i]%");
            } else {
                die('invalid input');
            }
        }


        $i++;
        while (isset($searchitems[$i]) != "") {
            if ($searchmode == "equals") {
                $searchst .= " $andor $field = " . $db->Quote($searchitems[$i]);
            } else {
                if ($searchmode == "like" || $searchmode == "not like") {
                    $searchst .= " $andor $field $searchmode " . $db->Quote("%$searchitems[$i]%");
                }
            }
            $i++;
        }
        // $searchst.=")"; # this screws up the query in recent visitors report... That is the only place this function is used
        return $searchst;
    }

    // this creates an empty zerofilled data array row

    function SearchMatchingIDs($search, $field, $searchmode, $tables): string
    {
        //this function makes a subquery that returns matching id's

        if (strpos($search, " and ") != false) {
            $searchitems = explode(" and ", $search);
            $andor = "AND";
        } else {
            if (strpos($search, " or ") != false) {
                $searchitems = explode(" or ", $search);
                $andor = "OR";
            } else {
                $searchitems[0] = $search;
                $andor = "AND";
            }
        }
        $i = 0;
        if ($searchmode == "equals") {
            $searchst = "($field = '$searchitems[$i]' ";
        } else {
            $searchst = "($field $searchmode '%$searchitems[$i]%' ";
        }

        $i++;
        while (@$searchitems[$i] != "") {
            if ($searchmode == "equals") {
                $searchst .= "$andor $field = '$searchitems[$i]' ";
            } else {
                $searchst .= "$andor $field $searchmode '%$searchitems[$i]%' ";
            }
            $i++;
        }
        $searchst .= ")";

        $searchst = "and $field IN (select id from $tables where $searchst)";
        return $searchst;
    }

    // this creates a full empty zerofilled data array

    function AddDateToZeroArray($data, $format = 'D, m/d/Y')
    {
        $t = $this->from;
        if (empty($this->period)) {
            $period = '_DAYS';
        } else {
            $period = $this->period;
        }

        foreach ($data as $k => $v) {
            #  Date preset as format : Thu, 11/12/2015
            $data[$k][0] = date($format, $t);

            switch ($period) {
                case '_DAYS':
                    $t = mktime(0, 0, 0, date("m", $t), date("d", $t) + 1, date("Y", $t));
                    break;
                case '_WEEKS':
                    $t = mktime(0, 0, 0, date("m", $t), date("d", $t) + 7, date("Y", $t));
                    break;
                case '_MONTHS':
                    $t = mktime(0, 0, 0, date("m", $t), date("d", $t) + 31, date("Y", $t));
                    break;
            }
        }
        return $data;
    }

    // this function takes a series based array and converts/pivots the array to columns

    function seriesToColumns($input_data, $row_key = 0, $row_label = "Date", $series_key = 1)
    {
        $data = [];

        if (empty($input_data)) {
            return false;
        }
        # create a column name array, the first element should be the row label
        $col_names = [];
        $col_names[] = $row_label;

        # now get the column names
        foreach ($input_data as $row => $serie_data) {
            if (!in_array($serie_data[$series_key], $col_names)) {
                $col_names[] = $serie_data[$series_key];
            }
        }
        $col_nums = array_flip($col_names);
        $cn = count($col_nums);

        # convert the input data
        foreach ($input_data as $row => $serie_data) {
            # use the chosen field from the serie data as the row key for the new array
            $rk = $serie_data[$row_key];

            # use the chosen field from the serie data as column key
            $ck = $col_nums[$serie_data[$series_key]];

            # loop each series array and transform to column
            foreach ($serie_data as $col_num => $val) {
                # if column is the series key, skip it
                if ($col_num == $series_key) {
                    continue;
                }
                # add a column to the array.
                if ($col_num == $row_key) {
                    $data[$rk][$row_key] = $val;
                } else {
                    $data[$rk][$ck] = $val;
                }
            }
            # fill empty data
            for ($i = 0; $i < $cn; $i++) {
                if (!isset($data[$rk][$i])) {
                    $data[$rk][$i] = 0;
                }
            }
        }
        $result['fields'] = $col_names;
        $result['data'] = $data;
        return $result;
    }

    // this function returns the number of intervals between from and now, based on the period

    function externDbConnectForm($savename)
    {
        global $cm;

        # Checking global settings if data is already stored.
        $connection = json_decode($this->profile->GetOtherSettings("externalConnection.$savename", ""), true);
        // if  not stored setup the array but empty.
        if (empty($connection)) {
            $connection["server"] = (empty($cm->external_db_server)) ? "" : $cm->external_db_server;
            $connection["username"] = "";
            $connection["password"] = "";
            $connection["database"] = "";
            $connection["tableprefix"] = "";
        }
        // if saved store data.
        if (!empty($this->action) && $this->action == "save") {
            echoNotice(_SAVED_CONNECTION, "margin:15px;");
            $connection["password"] = $_REQUEST["password"];
            $connection["tableprefix"] = $_REQUEST["tableprefix"];

            $connection["database"] = (empty($cm->external_db_prefix)) ? $_REQUEST["database"] : $cm->external_db_prefix . $_REQUEST["database"];
            $connection["username"] = (empty($cm->external_db_prefix)) ? $_REQUEST["username"] : $cm->external_db_prefix . $_REQUEST["username"];
            $connection["server"] = (empty($cm->external_db_server)) ? $_REQUEST["server"] : $cm->external_db_server;

            $save = json_encode($connection);
            $this->profile->SetOtherSettings("externalConnection.$savename", $save);
            $this->profile->SetOtherSettings("externalConnection.prefix.$savename", $connection["tableprefix"]);
        }

        # If there is a server preset in user_settings.php then just set it.
        if (empty($cm->external_db_server)) {
            $serv = "<input class='form-control' type='text' id='server' name='server' value='" . $connection["server"] . "' />";
        } else {
            $serv = $cm->external_db_server;
        }

        if (empty($cm->external_db_prefix)) {
            $username = "<input class='form-control' type='text' id='username' name='username' value='" . $connection["username"] . "' />";
            $database = "<input class='form-control' type='text' id='username' name='database' value='" . $connection["database"] . "' />";
        } else {
            $username = $cm->external_db_prefix . " <input class='form-control' type='text' size=11 id='username' name='username' value='" . $connection["username"] . "' />";
            $database = $cm->external_db_prefix . " <input class='form-control' type='text' size=11 id='database' name='database' value='" . $connection["database"] . "' />";
        }

        echo "<div class='col-sm-6' style='border-right:1px solid silver'>
			<h4>" . _SETUP_CONNECTION_DATA . "</h4>
			<form method='POST'>
				<div class='form-group'>
					<label>" . _SERVER . ":</label>
					{$serv}
				</div>
				<div class='form-group'>
					<label>" . _USERNAME . ":</label>
					{$username}
				</div>
				<div class='form-group'>
					<label>" . _PASSWORD . ":</label>
					<input class='form-control' type='password' id='password' name='password' value='" . $connection["password"] . "' />
				</div>
				<div class='form-group'>
					<label>" . _DATABASE . ":</label>
					{$database}
				</div>
				<div class='form-group'>
					<label title='" . _LEAVE_EMPTY_NOT_SET . "'>" . _TABLE_PREFIX . ":</label>
					<input class='form-control' type='text' id='tableprefix' name='tableprefix' value='" . $connection["tableprefix"] . "' />
				</div>

				<input class='btn btn-primary' type='submit' value='" . _SET_CONNECTION . "' />

				<input type='hidden' name='action' value='save' />
				
			<form>
		</div>";
        echo "<div class='col-sm-6'>";
        echo "<h4>" . _TESTING_CONNECTION . "</h4>";

        if ($con = $this->checkExternalConnection($savename)) {
            echo "<p>" . _CONNECTED_TO_SERVER . "</p>";
            echo "<p>" . _CONNECTED_TO_DATABASE . "</p>";
            echo "<p>" . _CONNECTION_READY . "</p>";
            echo "<p>" . _CLOSE_THIS_FRAME . "</p>";
            mysqli_close($con);
        } else {
            echo "<p>" . _COULD_NOT_CONNECT_SERVDB . "</p>";
            echo "<p>" . _CHECK_SETTINGS . "</p>";
        }
        echo "</div>";
    }

    // this function returns the date period to use

    function checkExternalConnection($savename)
    {
        /// From here we are testing the connection with the given data.
        $connection = json_decode($this->profile->GetOtherSettings("externalConnection.$savename", ""), true);
        if (empty($connection)) {
            $connection["server"] = "";
            $connection["username"] = "";
            $connection["password"] = "";
            $connection["database"] = "";
        }
        if (empty($connection["server"])) {
            return false;
        }
        // Check connection with server
        try {
            $con = mysqli_connect($connection["server"], $connection["username"], $connection["password"]);
            if (!$con) {
                throw new Exception('MySQL Connection Database Error: ' . mysqli_error());
            }
        } catch (Exception $e) {
            echo echoWarning($e->getMessage());
        }

        if ($con == false) {
            return false;
        }
        // Check connection with database
        $db = mysqli_select_db($connection["database"], $con);
        if ($db == false) {
            return false;
        }
        return $con;
    }

    // this function retuns the number of seconds in an interval period

    function PopulateColors($colors = [], $times = 2)
    {
        if (empty($colors)) {
            $colors = $this->graphcolors;
        }

        $newcolors = [];
        for ($c = 1; $c <= $times; $c++) {
            foreach ($colors as $color) {
                $newcolors[] = $color;
            }
        }

        return $newcolors;
    }

    // this function retuns the default date anotation of an interval period

    function GetHiddenLegends($legends = [])
    {
        # Check which legend items need to be hidden.
        # Returns and array containing the number of the corresponding legend items.
        if (!isset($this->hiddenlegends)) {
            return [];
        }

        $hiddenLegends = $this->hiddenlegends;
        if (!empty($hiddenLegends)) {
            $hiddenLegends = json_decode($hiddenLegends, true);
        } else {
            $hiddenLegends = [];
        }

        return $hiddenLegends;
    }

    # This function must be use in DefineReport()

    function ReimportLogFiles($from, $to)
    {
        include_once("includes/datamanager.php");

        $dataManager = new DataManager();
        $dataManager->Import($this->profile, $from, $to, 1);

        $f = date(implode($this->profile->dateFormat), $from);
        $t = date(implode($this->profile->dateFormat), $to);
        echoNotice("Re-Imported <b>$f - $t</b>", "margin:10px");
    }

    function DisplaySparkline()
    {
        $data = $this->GetReportData();
        $this->Sparkline($data, $this->sparktype);
    }

    # This function populated the graph color array.
    # Times is the amount of times the graph color array should multiply itself.

    function Sparkline($data, $type = "", $label = "", $postfix = "", $parent = ".inpage_container")
    {
        global $get_constant;

        if (empty($label)) {
            $label = $this->label;
        }
        $clabel = $get_constant[$this->label];
        $sourcedata = $data;

        if ($postfix == "") {
            $showfield = $this->fieldsarray();
            $showfield = $showfield[1];
            $showvalue = $data;
            $showvalue = array_pop($showvalue);
            $showvalue = $showvalue[1];
            $trend = number_format(getTrend($data, 1), 0);
            if ($trend > 0) {
                $trend = "<span class=\'uptrend\'>+$trend</span>";
            } else {
                $trend = "<span class=\'downtrend\'>$trend</span>";
            }
            $postfix = "$showvalue ($trend)";
        }

        $data = $this->ConvertToSparklineData($data);

        $div_id = md5($label . time());

        switch ($type) {
            case "bar":

                ?>
                if ($("#<?php
                echo $clabel; ?>").length == 0){
                $('
                <div id="<?php
                echo $clabel; ?>"></div>').appendTo("<?php
                echo $parent; ?>");
                }
                $('
                <div class="spark_container" name="<?php
                echo $clabel; ?>" rel="<?php
                echo $label; ?>"><span style="float:left;width:100%;text-align:center;"><span
                                style="text-align:center;float: left; font-size: 11px; width: 100%;height:18px;"><?php
                            echo $label; ?></span><span id="<?php
                        echo $div_id; ?>"><?php
                            echo $data["values"]; ?></span></span></div>').appendTo("#<?php
                echo $clabel; ?>");
                $(function() {
                $("#<?php
                echo $div_id; ?>").sparkline('html', {
                type: 'bar',
                tooltipFormat: '<span style="color: {{color}}">&#9679;</span> {{offset:names}}  ({{value}})',
                tooltipValueLookups: { names: { <?php
                echo $data['names']; ?> } }
                });
                });

                <?php
                break;

            case "pie":

                ?>
                if ($("#<?php
                echo $clabel; ?>").length == 0){
                $('
                <div id="<?php
                echo $clabel; ?>"></div>').appendTo("<?php
                echo $parent; ?>");
                }
                $('
                <div class="spark_container" name="<?php
                echo $clabel; ?>" rel="<?php
                echo $label; ?>"><span style="float:left;width:100%;text-align:center;"><span
                                style="text-align:center;float:left;font-size:11px;width:100%;height:18px;"><?php
                            echo $label; ?></span><span id="<?php
                        echo $div_id; ?>"><?php
                            echo $data["values"]; ?></span> <?php
                        echo $postfix; ?></span></div>').appendTo("#<?php
                echo $clabel; ?>");
                $(function() {
                $("#<?php
                echo $div_id; ?>").sparkline('html', {
                type: 'pie',
                tooltipFormat: '<span style="color: {{color}}">&#9679;</span> {{offset:names}} ({{percent.1}}%)',
                tooltipValueLookups: { names: { <?php
                echo $data['names']; ?> } }
                });
                });
                <?php
                break;

            default:
                ?>
                if ($("#<?php
                echo $clabel; ?>").length == 0){
                $('
                <div id="<?php
                echo $clabel; ?>"></div>').appendTo("<?php
                echo $parent; ?>");
                }
                $('
                <div class="spark_container" name="<?php
                echo $clabel; ?>" rel="<?php
                echo $label; ?>"><span style="float:left;width:100%;text-align:center;"><span
                                style="text-align:center;float:left;font-size:11px;width:100%;height:18px;"><?php
                            echo $label; ?></span><span id="<?php
                        echo $div_id; ?>"><?php
                            echo $data["values"]; ?></span> <?php
                        echo $postfix; ?></span></div>').appendTo("#<?php
                echo $clabel; ?>");
                $(function() {
                $("#<?php
                echo $div_id; ?>").sparkline('html', {
                tooltipFormat: '<span style="color: {{color}}">&#9679;</span> {{offset:names}}  ({{y}})',
                tooltipValueLookups: { names: { <?php
                echo $data['names']; ?> } }
                });
                });


                <?php
                break;
        }
        return $sourcedata;
    }

    function ConvertToSparklineData($data)
    {
        $d = [];
        $dd = [];
        foreach ($data as $key => $val) {
            $d[] = "$key: '{$val[0]}'";
            $dd[] = $val[1];
        }
        $newdata['names'] = implode(", ", $d);
        $newdata['values'] = implode(", ", $dd);
        return $newdata;
    }

    function EmptyTrendData()
    {
        $ncols = 2;
        $nrows = $this->dateNumber($this->from, $this->to, $this->period);
        $seed_data = $this->newReportArray($nrows, $ncols);

        $this->columnDefinitions = [];
        $this->columnDefinitions[] = ["Label" => _DATE];
        $this->columnDefinitions[] = ["Label" => _NO_DATA_TO_DISPLAY];

        for ($i = 0; $i < count($seed_data); $i++) {
            $seed_data[$i][0] = $this->getFormatDate(
                $this->period,
                ($this->from + (($i) * $this->getSeconds($this->period)))
            );
        }
        $this->setupColumnDefinitions();

        //dump($this->columnDefinitions);
        return $seed_data;
    }

    # a sparkline is a mini graph

    function dateNumber($from, $now, $period)
    {
        switch ($period) {
            case '_DAYS':
                return round(($now - $from) / 86400);
            case '_WEEKS':
                return round(($now - $from) / (7 * 86400));
            case '_MONTHS':
                return round(($now - $from) / (31 * 86400));
        }
    }

    # this function will take a default data array and convert it to something that
    # we can use to generate sparkline graphs

    function newReportArray($rows, $collums)
    {
        $data = [];
        $r = 0;
        while ($r < $rows) {
            $c = 0;
            while ($c < $collums) {
                $data[$r][$c] = 0;
                $c++;
            }
            $r++;
        }
        return $data;
    }

    function getFormatDate($period, $timestamp)
    {
        switch ($period) {
            // case _DAYS: return  date("D, m/d/Y",$timestamp);
            case '_DAYS':
                return LogaDate($this->profile->dateFormat, $timestamp);
            case '_WEEKS':
                return date('o-\\WW', $timestamp);
            case '_MONTHS':
                return date("M Y", $timestamp);
        }
        return false;
    }

    function getSeconds($period)
    {
        switch ($period) {
            case '_DAYS':
                return 86400;
            case '_WEEKS':
                return (7 * 86400);
            case '_MONTHS':
                return (31 * 86400);
        }
        return false;
    }

    function ReportOptionsPanel()
    {
        global $reports, $profile, $optionvalues, $from, $to, $cnames;

        echo '<div class="modal report-options" tabindex="-1" role="dialog" aria-labelledby="lgModalLabel" aria-hidden="true">
          <div class="modal-dialog">
            <div class="modal-content">
              <div class="modal-header">
                <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
                <h4 class="modal-title" id="lgModalLabel">' . $this->label . '</h4>
              </div>
              <div class="modal-body">';

        echo "<div class=''>";
        echo "<form>";
        include_once("includes/reportoptions.php");
        echo "<input type=hidden class='moved' name='moved' value='false'>";
        echo "</form>";
        echo "</div>";

        echo '</div>
              <div class="modal-footer" style="clear:both;">

				<button class="pull-left btn btn-default advanced-options-toggler" type="button"><i class="fa fa-th-large"></i><span class="hidden-xs">' . _ADVANCED_REPORT_OPTIONS . '</span></button>
                <button type="button" class="btn btn-default close-options" data-dismiss="modal">' . ucwords(_CLOSE) . '</button>
                <button type="button" class="btn btn-primary load-report">' . _SUBMIT . '</button>
              </div>
            </div>
          </div>
        </div>';
    }

    function ValidateReportEdition()
    {
        global $reports, $edition, $session, $ss;

        $valid = true;

        # if we are a cpanel edition, a free or basic edition; limit reports to the 'Standard' distribution
        if ((_LOGAHOLIC_EDITION == 0 || _LOGAHOLIC_EDITION == 7) && $ss == false) {
            if ($reports[$this->label_constant]["Distribution"] != "Standard") {
                $valid = false;
            }
        } else {
            # nothing to disable here
        }

        #check if there is a user-login required, if the user has logged in and if the user is an admin.
        if (!$session->isAdmin() && $session->logged_in) {
            if (CheckForSubscriptionsUsage() && !empty($session->userinfo['subscriptionid'])) {
                if (!in_array($this->label_constant, $session->subscription['reports'])) {
                    $valid = false;
                }
            }
        }

        return $valid;
    }

    function DisplayPromo($r)
    {
        ?>
        <div class="ss_overlay" style="position:relative; height:300px; overflow: hidden;">

            <div class="ss_overlay"
                 style="position:absolute;width:100%;height:100%;background-color:#333333;opacity:0.83;z-index:100;">

                <div style="text-align:center; position:relative;opacity:1;padding-top:60px;">

                    <h4 style="color:white">Upgrade to Logaholic Pro</h4>
                    <h2 style="color:white"><?php
                        echo _GET_ACCESS_TO_ALL_REPORT_AND_FEAT; ?></h2>
                    <a href="#" onclick="ui.LicenseUpgrade();" class="btn btn-lg btn-success"><?php
                        echo _START_WITH_TRIAL; ?></a>
                </div>

            </div>
            <div class="report_preview">
                <?php
                $r->DisplayReport(); ?>
            </div>
        </div>
        <?php
    }

    function SettingsButton(): string
    {
        return '<button type="button" class="btn btn-default report-options-toggle" data-toggle="modal"><i class="fa fa-calendar"></i> ' . _GO_TO_SETTINGS . '</button> ';
    }

    function DisplaySubscriptionPromo($r)
    {
        ?>
        <div class="ss_overlay" style="position:relative; height:300px; overflow: hidden;">

            <div class="ss_overlay mask"
                 style="position:absolute;width:100%;height:100%;background-color:#333333;opacity:0.83;z-index:100;">

                <div style="text-align:center; position:relative;opacity:1;padding:45px 5px 5px 5px;" title='<?php
                echo str_replace("%x", $this->label, _SUBSCRIPTION_UPGRADE_NOTICE); ?>'>

                    <h4 style="color:white">Logaholic Pro <?php
                        //echo _UPGRADE_NOW;
                        ?></h4>
                    <h3 style="color:white"><?php
                        echo _GET_ACCESS_TO_ALL_REPORT_AND_FEAT; ?></h3>
                    <a href="#" class="btn btn-lg btn-success btn-upgrade-subscription"><?php
                        echo _FREE_30_DAY_TEST; ?></a>
                    <!-- <div style="color:silver"><br><?php
                    echo str_replace(
                        "%x",
                        "<strong>" . $this->label . "</strong>",
                        _SUBSCRIPTION_UPGRADE_NOTICE
                    ); ?></div> -->
                </div>

            </div>
            <div class="report_preview">
                <?php
                $r->DisplayReport(); ?>
            </div>
        </div>
        <?php
    }

    function AggregateReportData($label)
    {
        global $reports, $db, $profile, $session;

        $rollupdata = [];

        $q = $this->AggregateProfiles();
        $np = 0;

        $totmem = substr(ini_get('memory_limit'), 0, -1);
        if (!$totmem) {
            $totmem = (8 * 1024 * 1024);
        } else {
            $totmem = ($totmem * 1024 * 1024);
        }
        $totmem = intval(($totmem * 0.75));
        foreach ($q as $data) {
            $mem_usage = memory_get_usage();
            //echoWarning($mem_usage/1024/1024);
            if ($mem_usage > $totmem) {
                echoWarning("memory failure imminent");
            }

            if (!$session->isAdmin() && !$session->allProfiles) {
                // check if user has access to profile
                if (!in_array($data[0], $session->user_profiles)) {
                    echoWarning("Access denied for {$data[0]}");
                    continue;
                }
            }
            $profile = new SiteProfile($data[0]);
            $r = new $reports[$label]["ClassName"]();
            $r->from = $this->from;
            $r->to = $this->to;
            if (isset($this->search)) {
                $r->search = $this->search;
            }
            $r->columnDefinitions = $this->columnDefinitions;
            //dump($this->columnDefinitions);
            //dump($r->columnDefinitions);
            //$r->setupColumnDefinitions();
            $np++; // keep track of the number of valid profiles

            $data = $r->GetReportData();


            //$mem_usage = memory_get_usage();
            //echoWarning("got data for {$profile->profilename}".$mem_usage/1024/1024);
            //die();
            if (@$r->trenddata == true) {
                //just a shortcut for traffic breakdown, won't nessecarily work on any other report
                if (!empty($data)) {
                    foreach ($data as $k => $v) {
                        // our key here is a date
                        foreach ($v as $kvalues => $values) {
                            foreach ($values as $rk => $rv) {
                                if (is_numeric(
                                        $rv
                                    ) && $rk !== $this->key_col && @$r->columnDefinitions[$rk]["dataType"] !== "String") {
                                    $rollupdata[$k][$kvalues][$rk] = @$rollupdata[$k][$kvalues][$rk] + $rv;
                                } else {
                                    $rollupdata[$k][$kvalues][$rk] = $rv;
                                }
                            }
                        }
                    }
                }
            } else {
                if (!empty($data)) {
                    //dump($data);
                    foreach ($data as $k => $v) {
                        foreach ($v as $rk => $rv) {
                            if (!isset($r->columnDefinitions[$rk]["Label"])) {
                                continue;
                            }
                            $labeled_key = $r->columnDefinitions[$rk]["Label"];
                            if (is_numeric(
                                    $rv
                                ) && $rk !== $this->key_col && @$r->columnDefinitions[$rk]["dataType"] !== "String") {
                                $rollupdata[$v[$r->key_col]][$labeled_key] = @$rollupdata[$v[$r->key_col]][$labeled_key] + $rv;
                            } else {
                                if (!isset($rollupdata[$v[$r->key_col]][$labeled_key])) {
                                    //first time
                                    $rollupdata[$v[$r->key_col]][$labeled_key] = $rv;
                                }
                                if ($rollupdata[$v[$r->key_col]][$labeled_key] !== $rv) {
                                    echo "Overwriting val from {$rollupdata[$v[$r->key_col]][$labeled_key]} to $rv<br>";
                                }
                                $rollupdata[$v[$r->key_col]][$labeled_key] = $rv;
                            }
                        }
                    }
                }
            }
        }

        // in case we are doing a trends report, we need to limit the result set per day/period
        // let's find out which ones we are going to allow into the final dataset
        if (isset($this->limit_per_day)) {
            foreach ($rollupdata as $row) {
                foreach ($row as $k => $v) {
                    $totals[$k] = $totals[$k] ?? $totals[$k] + $v;
                }
            }
            arsort($totals);
            $totals = array_slice($totals, 0, $this->limit_per_day);
            if (!isset($totals[_DATE])) {
                $totals[_DATE] = 1;
            }
        }

        //now we should get all labeled keys and make an ordered template for those - also use that as the new columndefinitions
        $metrics_map = [];
        $i = 0;
        foreach ($rollupdata as $row) {
            foreach ($row as $k => $v) {
                if (isset($totals) && !isset($totals[$k])) {
                    continue;
                }
                if (!isset($metrics_map[$k])) {
                    $metrics_map[$k] = $i;
                    $this->columnDefinitions[$i]['Label'] = $k;
                    $i++;
                }
                if (isset($this->limit_per_day)) {
                    $totals[$k] = @$totals[$k] + $v;
                }
            }
        }
        //$mem_usage = memory_get_usage();
        //echoWarning("created metrics_map ".$mem_usage/1024/1024);

        $this->setupColumnDefinitions();
        //dump($rollupdata);

        if (@$r->trenddata == true) {
            return $rollupdata;
        } else {
            $data = [];
            $i = 0;
            foreach ($rollupdata as $row) {
                foreach ($row as $k => $val) {
                    if (!isset($metrics_map[$k])) {
                        continue;
                    }
                    $k = $metrics_map[$k];
                    if (@$r->columnDefinitions[$k]['totalRow'] == "avg" && $data[$i][1] > 0) {
                        $data[$i][$k] = number_format(($data[$i][2] / $data[$i][1]), 2);
                    }
                    $data[$i][$k] = $val;
                }
                $i++;
            }

            if ($r->sort !== false) {
                $data = DataSort($data, $r->sort_key, $r->sort_order);
                //dump($this->limit);
                if (strpos($reports[$this->labels]['Options'], 'limit') !== false) {
                    $data = array_slice($data, 0, $this->limit);
                }
            }
            if (method_exists($this, 'AggregateFixData')) {
                $data = $this->AggregateFixData($data, $np);
            }
            return $data;
        }
    }

    function AggregateProfiles()
    {
        global $db, $session;

        if (!empty($this->addlabel)) {
            $this->addlabel .= " | ";
        }

        if (!empty($this->profilelist)) {
            $plist = explode(";", $this->profilelist);
            $qstr = "select profilename,stats from " . TBL_PROFILES . " where activated='1' and profilename IN (";

            if (count($plist) > 3) {
                $this->addlabel .= "<span title='{$this->profilelist}'>Aggregated " . count(
                        $plist
                    ) . " Profiles</span>";
            } else {
                $this->addlabel .= str_replace(";", ", ", $this->profilelist);
            }
            foreach ($plist as $p) {
                $qstr .= $db->Quote($p) . ",";
            }
            $qstr = substr($qstr, 0, -1) . ")";

            $query = $db->Execute($qstr);
        } else {
            if ($session->isAdmin() || $session->allProfiles) {
                $query = $db->Execute("select profilename,stats from " . TBL_PROFILES . "");
                $this->addlabel .= "Aggregated all profiles";
            } else {
                $result = [];
                foreach ($session->user_profiles as $p) {
                    $result[][0] = $p;
                }
                $this->addlabel .= "Aggregated all profiles";
            }
        }

        if (!isset($result)) {
            $result = [];
            while ($data = $query->fetchRow()) {
                $stats = json_decode($data['stats'], true);
                //skip any profiles that don't have stats...
                if ($stats['max_db_timestamp'] > $this->from) {
                    $result[][0] = $data['profilename'];
                }
            }
        }
        return $result;
    }

    function GetReportDataPP($label)
    {
        global $reports, $db, $profile;

        array_unshift($this->columnDefinitions, ["Label" => "_PROFILE", "dataType" => "String"]);
        $this->setupColumnDefinitions();

        $superdata = [];

        $q = $this->AggregateProfiles();

        foreach ($q as $data) {
            $profile = new SiteProfile($data[0]);
            $r = new $reports[$label]["ClassName"]();
            $r->from = $this->from;
            $r->to = $this->to;
            $r->search = "";

            $data = $r->GetReportData();

            if (!empty($data)) {
                //dump($data);
                foreach ($data as $k => $v) {
                    array_unshift($v, $r->profile->profilename);
                    $data[$k] = $v;
                }
                $superdata = array_merge($superdata, $data);
            }
        }
        return $superdata;
    }

    function Sigma($data, $test)
    {
        // find out how many standard deviations the test value is away from the mean
        //echo "hallo";
        //return 1;

        if (@$this->sigmadata != $data) {
            //echo "hallo";
            $count = count($data);
            $this->sigmadata = $data;
            $this->mean = array_sum($data) / $count;
            $total_diff = 0;
            //echo "hallo";
            //return;
            foreach ($data as $n) {
                $diff = $n - $this->mean; // difference from mean
                $diff = $diff * $diff; // squared
                $total_diff += $diff;
            }
            $this->variance = $total_diff / $count;
            $this->st_dev = sqrt($this->variance);
        }
        if ($this->st_dev == 0) {
            return 0;
        }
        $sigma = ($test - $this->mean) / $this->st_dev;
        return $sigma;
    }

    function sec2time($secs): string
    {
        $hours = str_pad(floor($secs / (60 * 60)), 2, '0', STR_PAD_LEFT);
        $divisor_for_minutes = $secs % (60 * 60);
        $minutes = str_pad(floor($divisor_for_minutes / 60), 2, '0', STR_PAD_LEFT);
        $divisor_for_seconds = $divisor_for_minutes % 60;
        $seconds = str_pad(ceil($divisor_for_seconds), 2, '0', STR_PAD_LEFT);
        return "{$hours}:{$minutes}:{$seconds}";
    }

    protected function __distruct()
    {
        $this->childObject = null;
    }

}


function ListIncludeReports($list = [], $dir = REPORTS_FOLDER)
{
    $ignore_report_files = [
        "test_center.php",
        "facebook_page_videos.php",
        "facebook_page_language.php",
        "facebook_all_pageviews.php",
        "facebook_page_age.php",
        "facebook_page_gender.php",
        "facebook_page_active_countries.php",
        "facebook_page_cities.php",
        "facebook_page_stories.php",
        "facebook_app_api_calls.php",
        "facebook_page_gender_and_age.php",
        "facebook_page_posts.php"
    ];

    # include report files.
    $report_dir = opendir($dir . "/");
    while ($file = readdir($report_dir)) {
        if (strpos($file, ".php") !== false) {
            if (in_array($file, $ignore_report_files) == false) {
                $location = $dir . "/" . $file;
                # only add to the list if the file is not already located
                if (!in_array($location, $list)) {
                    $list[$file] = $location;
                }
            }
        }
    }
    closedir($report_dir);

    # include the phar file if it exists also check if the php version is high enough (5.3)
    if (file_exists($dir . "/reports.phar.gz") && class_exists('Phar')) {
        $p = new Phar($dir . "/reports.phar.gz", 0);
        foreach (new RecursiveIteratorIterator($p) as $r) {
            # only add to the list if the file is not already located
            if (!in_array($r->getPathName(), $list)) {
                $list[$r->getFileName()] = $r->getPathName();
            }
        }
    }
    return $list;
}

if (file_exists("includes/adwords_core.php")) {
    include_once("includes/adwords_core.php");
}
if (file_exists(logaholic_dir() . "includes/twitter_core.php")) {
    include_once(logaholic_dir() . "includes/twitter_core.php");
}
if (file_exists(logaholic_dir() . "includes/facebook_core.php")) {
    include_once(logaholic_dir() . "includes/facebook_core.php");
}

# This is as good a place as any to include all the report child classes from the reports dir
$reports = []; # default empty

# preset array
$includes = [];

# create a list with files to inlcude
if (empty($profile) || $profile->customreportsonly == 0) {
    $includes = ListIncludeReports();
}

# Add the custom reports to the list
if (isset($profile) && is_dir($profile->customreportsdir)) {
    $includes = ListIncludeReports($includes, $profile->customreportsdir);
}

# Just include everything.. We filter the reports in reports.php by store and or edition..
foreach ($includes as $inc) {
    include_once($inc);
}

$all_reports = $reports;


# But, if we don't want to show any promo screens for subscriptions, remove invalid reports from the array
if (CheckForSubscriptionsUsage() && @!empty($session->userinfo['subscriptionid']) && getGlobalSetting(
        "no_promo",
        false
    ) == "true") {
    foreach ($reports as $key => $value) {
        if (!in_array($key, (array)isset($session->subscription['reports']))) {
            unset($reports[$key]);
        }
    }
}
# sort out the reports
$reports = sort_reports($reports);

if ($conf == "AggregateProfiles") {
    include_once "aggregator.php";
}

#Now we need to make a label/constant matcher, so can always get the constant name
$get_constant = [];
foreach ($reports as $key => $value) {
    if (defined($key)) {
        $get_constant[constant($key)] = $key;
        $get_label[$value['ClassName']] = $key;
    }
}