<?php

class LogaholicDBBatch
{

    var $exportType;
    var $supportedTypes;
    var $exportLocation;
    var $fileEntries;

    var $batch_export_timeout;

    /**
     * Default defined by Logaholic upstream is 86400
     * For cPanel, we reduce to something more reasonable, 3hrs...which is still
     * way too long
     */

    function __construct()
    {
        global $config, $running_from_command_line, $db, $databasedriver;

        $running_from_command_line = true;
        include_once $config['path'] . "/common.inc.php";

        //set some defaults
        $this->supportedTypes = array(
            'JSON' => 'Lines of JSON Array Data (DEFAULT)',
            'CSV' => 'Comma Separated Values',
        );
        $this->exportType = 'JSON';
        $this->batch_export_timeout = 10800;

        $this->Checkvars();

        return $this;
    }

    function Checkvars()
    {
        global $action, $config;

        $help = "Required variables:<br>\n"
            . " - profile (the profile name [(sub)domain] to be exported)\n"
            . " - username (the username of the account that owns the profile)\n"
            . " - exportlocation (directory to write the exported data file(s))\n"
            . "Optional variables:<br>\n";

        if (!empty($_GET['profile']) && (
                $action == 'packprofile'
                || ($action == 'unpackprofile' && !empty($_GET['username']))
            )) {
            $this->profile = $_GET['profile'];
            if (!empty($_GET['username'])) {
                $this->username = $_GET['username'];
            }
            return;
        }

        if (!empty($_GET['profile'])
            && !empty($_GET['username'])
            && !empty($_GET['exportlocation'])
        ) {
            $this->profile = $_GET['profile'];
            $this->username = $_GET['username'];

            $this->exportLocation = $_GET['exportlocation'];
            if (!is_writeable($_GET['exportlocation'])) {
                Output("Fatal error: '{$this->exportLocation}' is not writable.\n", true);
            } elseif (substr($this->exportLocation, -1) != '/') {
                $this->exportLocation .= '/';
            }
        } else {
            Output($help, true);
        }

        if (isset($_GET['exporttype'])) {
            if (!empty($_GET['exporttype'])
                && in_array($_GET['exporttype'], array_keys($this->supportedTypes))
            ) {
                $this->exportType = $_GET['exporttype'];
            } else {
                $err = "'{$_GET['exporttype']}' is not a supported export type.\n\n";
                Output($err . $help, true);
            }
        }

        if (isset($_GET['fileentries'])) {
            if (!empty($_GET['fileentries'])
                && (is_numeric($_GET['fileentries']) || (!is_numeric(
                            $_GET['fileentries']
                        ) && $_GET['fileentries'] == 'UNLIMITED'))
            ) {
                $this->fileEntries = $_GET['fileentries'];
            } else {
                $err = "'fileentries' must be numeric value or 'UNLIMITED'\n\n";
                Output($err . $help, true);
            }
        }

        if (isset($config['batch_export_timeout']) && $config['batch_export_timeout']) {
            $this->batch_export_timeout = $config['batch_export_timeout'];
        }

        if (!function_exists('json_encode')) {
            include_once $config['path'] . "/components/json/JSON.php";
            $GLOBALS['JSON_OBJECT'] = new Services_JSON();
            function json_encode($value)
            {
                return $GLOBALS['JSON_OBJECT']->encode($value);
            }

            function json_decode($value)
            {
                return $GLOBALS['JSON_OBJECT']->decode($value);
            }
        }
    }

    function LogaholicDBBatchExport()
    {
        __construct();
    }

    function LegacyExport()
    {
        global $db;
        if ($this->batch_export_timeout) {
            /**
             * The use of set_time_limit() was added in 2.9.1.  As per PHP
             * manual, this will not help govern the database operation, since
             * technically it's a different process, but it will help if the
             * db driver or other PHP code runs for an exorbitant time
             *
             */
            set_time_limit($this->batch_export_timeout);
        }
        $profile_obj = new SiteProfile($this->profile);

        // determine record count; must do this since data is extracted by offset
        $stmt = 'SELECT count(timestamp) as count FROM '
            . $profile_obj->tablename . ' as a, '
            . $profile_obj->tablename_visitorids . ' as v, '
            . $profile_obj->tablename_urls . ' as u, '
            . $profile_obj->tablename_urlparams . ' as up, '
            . $profile_obj->tablename_referrers . ' as r, '
            . $profile_obj->tablename_refparams . ' as rp, '
            . TBL_USER_AGENTS . ' as AGENTS
            WHERE (a.visitorid=v.id 
            	and a.url=u.id and a.params=up.id and a.referrer=r.id 
            	and a.refparams=rp.id and a.useragentid = AGENTS.id)';


        $recordcount = $db->GetOne($stmt);

        // prepare the SELECT statement
        $stmt = 'SELECT 
        	timestamp,
        	v.ipnumber as ipnumber,
        	u.url as url,
        	up.params as params,
        	status,
        	bytes,
        	r.referrer as referrer,
        	rp.params as refparams,
        	AGENTS.name as useragent
        	FROM ' . $profile_obj->tablename . ' as a, '
            . $profile_obj->tablename_visitorids . ' as v, '
            . $profile_obj->tablename_urls . ' as u, '
            . $profile_obj->tablename_urlparams . ' as up, '
            . $profile_obj->tablename_referrers . ' as r, '
            . $profile_obj->tablename_refparams . ' as rp, '
            . TBL_USER_AGENTS . ' as AGENTS 
            WHERE (a.visitorid=v.id 
            	and a.url=u.id and a.params=up.id and a.referrer=r.id 
            	and a.refparams=rp.id and a.useragentid = AGENTS.id)
            ORDER BY timestamp';

        $left_to_fetch = $recordcount;

        //mysql row offset for SELECTion (will grow as we read db data)
        $offset = 0;

        // internal loop counter for triggering a new file
        $count = 0;

        //filename partial
        $file_chunk = 1;

        // this controls how many records to fetch at a time; if it's too large,
        // ADO will bomb, so, don't change it unless you're decrementing it!
        $db_select_range = 10000;

        // max records to write in a give file;
        //  not honored if set lower than $db_select_range
        $range = ($this->fileEntries) ? $this->fileEntries : 40000;
        $range = (!is_numeric($range) && strtoupper($range) == 'UNLIMITED') ? $recordcount + 1 : $range;

        $success = false;
        // fetch data and write files
        while ($left_to_fetch > 0) {
            $q = $db->SelectLimit($stmt, $db_select_range, $offset);
            $data = $q->GetArray();
            ++$count;

            // write the data to file
            if (!$this->writeArrayToFile($data, $file_chunk)) {
                break;
            }

            // prepare a new file name, if necessary
            if ($count * $db_select_range >= $range) {
                ++$file_chunk;
                $count = 0;
            }

            // update the main loop counter
            $left_to_fetch -= $db_select_range;

            // increment our offset for the next record set SELECTion
            $offset += $db_select_range;
            if ($left_to_fetch <= 0) {
                $success = true;
            }
        }

        if ($success) {
            Output("Success: Batch export of $recordcount entries to '{$this->exportLocation}' complete.\n");
        } elseif ($recordcount == 0) {
            Output("Success: No entries to export for profile '{$this->profile}'.\n");
        } else {
            // we likely won't get here, as most any error would cause an exit()
            Output("Error: Batch export of profile '{$this->profile}' failed.\n", true);
        }
        return;
    }

    function writeArrayToFile($data, $name_partial)
    {
        $file_name_partial = $this->exportLocation
            . 'logaholic_'
            . $this->username
            . '_'
            . $this->profile
            . '_'
            . $name_partial;
        if ($this->exportType == 'JSON') {
            return $this->_writeJSON($data, $file_name_partial);
        } elseif ($this->exportType == 'CSV') {
            return $this->_writeCSV($data, $file_name_partial);
        }
    }

    function _writeJSON($data, $file_name_partial)
    {
        $file = $file_name_partial . '.json';

        $fh = fopen($file, 'a');
        if ($fh === false) {
            Output("Fatal error: no filehandle for '$file'\n", true);
        }

        foreach ($data as $row => $row_values) {
            // we only care about associative values
            foreach ($row_values as $col => $val) {
                if (is_numeric($col)) {
                    unset($row_values[$col]);
                }
            }
            // we'll assume it's able to write since we have a filehandle
            // checking the return probably won't get us anything by overhead
            fwrite($fh, json_encode($row_values) . "\n");
        }
        fclose($fh);
        return true;
    }

    function _writeCSV($data, $file_name_partial)
    {
        $file = $file_name_partial . '.csv';

        $fh = fopen($file, 'a');
        if ($fh === false) {
            Output("Fatal error: no filehandle for '$file'\n", true);
            return false;
        }

        foreach ($data as $row => $row_values) {
            // atm, CSV.inc.php is index based
            foreach ($row_values as $col => $val) {
                if (!is_numeric($col)) {
                    unset($row_values[$col]);
                }
            }
            //how is the params field stored?
            //$row_values[3] = urlencode($row[3]);
            fwrite($fh, implode(',', $row_values) . "\n");
        }

        fclose($fh);
    }

    function dbConnect()
    {
        global $db, $config;
        require_once($config['path'] . "/common.inc.php");
        // require_once($config['path']."/components/adodb/adodb-errorhandler.inc.php");
        // require_once($config['path']."/components/adodb/adodb.inc.php");

        // if (function_exists("mysqli_connect")) {
        //     $db = ADONewConnection("mysqli");
        // } else {
        //     $db = ADONewConnection("mysql");
        // }
        // $db->Connect($config['mysqlhost'],$config['mysqluser'],$config['mysqlpass']) or die ("Can't connect to mysql server");

    }

    function PackProfile()
    {
        # Setup profile
        $profile = new SiteProfile($this->profile);
        $dir = $profile->datamanagerDir . "/" . $profile->profilename;
        # Export all database data
        $this->Export();

        # Now dump the profile settings in a file
        $this->exportLocation = $dir . "/";
        $this->exportProfileSettings();

        # Create a zip
        $z = new ZipArchive();
        $z->open("{$profile->datamanagerDir}packed_{$profile->profilename}.zip", ZIPARCHIVE::CREATE);
        folderToZip($dir, $z);
        $z->close();
        Output("Successful Zipped {$profile->profilename}");
    }

    function Export()
    {
        global $db, $config;
        include_once($config['path'] . "/includes/datamanager.php");

        $profile = new SiteProfile($this->profile);

        $range = GetMaxDateRange($profile);

        $dataManager = new DataManager();
        $dataManager->set_time_limit = $this->batch_export_timeout;

        if (function_exists("asCpanelLogaholic")) {
            asCpanelLogaholic();
        }

        $dataManager->Export($profile, $range['from'], $range['to']);

        return "Backup logaholic data logs availabe in: " . $profile->datamanagerDir . "logs/";
    }

    function exportProfileSettings()
    {
        global $db;

        $file = $this->exportLocation
            . 'logaholic_'
            . $this->username
            . '_'
            . $this->profile
            . '_'
            . 'settings.json';

        $fh = fopen($file, 'w');

        if ($fh === false) {
            Output("Fatal error: no filehandle for '$file'\n", true);
        }

        $profile = new SiteProfile($this->profile);

        fwrite($fh, json_encode($profile) . "\n");
        fclose($fh);

        return true;
    }

    function UnPackProfile()
    {
        # Setup profile
        $profile = new SiteProfile($this->profile);
        $dir = $profile->datamanagerDir . "/";
        $zip_name = "packed_{$profile->profilename}.zip";

        $zip = new ZipArchive;
        if ($zip->open($dir . $zip_name) === true) {
            $ex = $zip->extractTo($dir . $profile->profilename . "/");
            $zip->close();
        } else {
            Output("Could not open packed file.", true);
        }

        # change permissions
        $r = exec("chmod -R 777 " . $dir . $profile->profilename);

        # import profile data and add to new user
        $this->exportLocation = $dir . $profile->profilename . "/";
        $this->importProfileSettings();

        # import data
        $result = $this->Import();
        Output($result);
    }

    function importProfileSettings()
    {
        global $db;

        $file = $this->exportLocation
            . 'logaholic_'
            . $this->username
            . '_'
            . $this->profile
            . '_'
            . 'settings.json';

        $fh = fopen($file, 'r');

        if ($fh === false) {
            Output("Fatal error: no filehandle for '$file'\n", true);
        }

        while ($line = fgets($fh)) {
            $line = json_decode($line, true);

            if (isset($line["profileid"])) {
                # handle the profile table entry
                $dbp = new LogaholicDBProfile($this->profile);

                if ($dbp->profileExists($this->profile) == true) {
                    Output("Error: Profile '$this->profile' already exists in this database\n", true);
                }

                # add the new profile to the profile list in the user table
                $dbp->username = $this->username;
                if ($dbp->AddProfiletoUser($this->profile) === false) {
                    exit();
                }

                # ok, now create the new profile based on this info
                $profile = new SiteProfile();

                foreach ($line as $k => $v) {
                    if ($k == "profileid" || $k == "tablename") {
                        # skip this to avoid conflicts on this new server
                        continue;
                    }
                    $profile->$k = $v;
                }
                $profile->Save();
                createDataTable($profile);
            } else {
                Output("Error Invalid data in settings file $file \n", true);
            }
        }
        fclose($fh);
        return "Success: Imported profile settings for $profile->profilename";
    }

    function Import()
    {
        # We need to global some variables so import can work
        global $db, $config, $profile, $ua_parser;
        include_once($config['path'] . "/includes/datamanager.php");

        $profile = new SiteProfile($this->profile);

        $limit = (int)$profile->datamanagerLimit;
        if (!empty($limit)) {
            $from = mktime(0, 0, 0, date('m') - $limit, 1, date('Y'));
        } else {
            $from = 0;
        }
        $to = time();

        $dataManager = new DataManager();
        $dataManager->set_time_limit = $this->batch_export_timeout;

        $dataManager->Import($profile, $from, $to, 0);

        return "Imported ({$profile->profilename}) data " . date("d M Y H:i:s", $from) . " - " . date(
                "d M Y H:i:s",
                $to
            );
    }
}