<?php
/**
 * Akeeba Engine
 * The modular PHP5 site backup engine
 * @copyright Copyright (c)2009-2014 Nicholas K. Dionysopoulos
 * @license GNU GPL version 3 or, at your option, any later version
 * @package ALICE
 *
 */

// Protection against direct access
defined('AKEEBAENGINE') or die();

/**
 * Checks that every page load is not hitting the timeout limit.
 * Time diff is performed against the "Start step" and "Saving Kettenrad" timestamps.
 */
class AliceCoreDomainChecksRuntimeerrorsTimeout extends AliceCoreDomainChecksAbstract
{
    public function __construct($logFile = null)
    {
        parent::__construct(20, JText::_('ALICE_ANALYZE_RUNTIME_ERRORS_TIMEOUT'), $logFile);
    }

	public function check()
	{
        $handle = @fopen($this->logFile, 'r');

        if($handle === false)
        {
            AliceUtilLogger::WriteLog(_AE_LOG_ERROR, $this->checkName.' Test error, could not open backup log file.');
            return false;
        }

        $prev_data = '';
        $buffer    = 65536;
        $starting  = array();
        $saving    = array();

        while (!feof($handle))
        {
            $data = $prev_data.fread($handle, $buffer);

            // Let's find the last occurrence of a new line
            $newLine = strrpos($data, "\n");

            // I didn't hit any EOL char, let's keep reading
            if($newLine === false)
            {
                $prev_data = $data;
                continue;
            }
            else
            {
                // Gotcha! Let's roll back to its position
                $prev_data = '';
                $rollback  = strlen($data) - $newLine + 1;
                $len       = strlen($data);

                $data      = substr($data, 0, $newLine);

                // I have to rollback only if I read the whole buffer (ie I'm not at the end of the file)
                // Using this trick should be much more faster than calling ftell to know where we are
                if($len == $buffer)
                {
                    fseek($handle, -$rollback, SEEK_CUR);
                }
            }

            preg_match_all('#(\d{6}\s\d{2}:\d{2}:\d{2})\|.*?Starting Step number#i', $data, $tmp_matches);

            if(isset($tmp_matches[1]))
            {
                $starting = array_merge($starting, $tmp_matches[1]);
            }

            preg_match_all('#(\d{6}\s\d{2}:\d{2}:\d{2})\|.*?Saving Kettenrad instance#i', $data, $tmp_matches);

            if(isset($tmp_matches[1]))
            {
                $saving = array_merge($saving, $tmp_matches[1]);
            }
        }

        fclose($handle);

        // If there is an issue with starting and saving instances, I can't go on, first of all fix that
        if(count($saving) != count($starting))
        {
            AliceUtilLogger::WriteLog(_AE_LOG_INFO, $this->checkName.' Could not proceed, starting and saving steps are different.');

            $this->setResult(-1);
            throw new Exception(JText::_('ALICE_ANALYZE_RUNTIME_ERRORS_TIMEOUT_KETTENRAD_BROKEN'));
        }

        $temp = array();

        // Let's expand the date part so I can safely work with that strings
        foreach($starting as $item)
        {
            $temp[] = '20'.substr($item, 0, 2).'-'.substr($item, 2, 2).'-'.substr($item, 4, 2).substr($item, 6);
        }

        $starting = $temp;
        $temp     = array();

        // Let's expand the date part so I can safely work with that strings
        foreach($saving as $item)
        {
            $temp[] = '20'.substr($item, 0, 2).'-'.substr($item, 2, 2).'-'.substr($item, 4, 2).substr($item, 6);
        }

        $saving = $temp;

        $maxExcution = $this->detectMaxExec();

        // Ok, did I had any timeout between the start and saving step (ie page loads)?
        for($i = 0; $i < count($starting); $i++)
        {
            $duration = strtotime($saving[$i]) - strtotime($starting[$i]);

            AliceUtilLogger::WriteLog(_AE_LOG_INFO, $this->checkName.' Detected page running time: '.$duration.' seconds');

            if($duration > $maxExcution)
            {
                $this->setResult(-1);
                throw new Exception(JText::sprintf('ALICE_ANALYZE_RUNTIME_ERRORS_TIMEOUT_MAX_EXECUTION', $duration));
            }
        }

        AliceUtilLogger::WriteLog(_AE_LOG_INFO, $this->checkName.' Test passed : '.count($starting).' starting vs '.count($saving).' savings');

        return true;
	}

	public function getSolution()
	{
		return JText::_('ALICE_ANALYZE_RUNTIME_ERRORS_TIMEOUT_SOLUTION');
	}

    /**
     * Detects max execution time, reading backup log. If the maximum execution time is set to 0 or it's bigger
     * than 100, it gets the default value of 100.
     *
     * @return int|string
     */
    private function detectMaxExec()
    {
        $handle = @fopen($this->logFile, 'r');
        $time   = 0;

        while(($line = fgets($handle)) !== false)
        {
            $pos = stripos($line, '|Max. exec. time');

            if($pos !== false)
            {
                $time = trim(substr($line, strpos($line, ':', $pos) + 1));
                break;
            }
        }

        fclose($handle);

        if(!$time || $time > 100)
        {
            $time = 100;
        }

        return $time;
    }
}
