<?php

/**
 * Class Sheep_Debug_Model_Cron
 *
 * @category Sheep
 * @package  Sheep_Debug
 * @license  Copyright: Pirate Sheep, 2016
 * @link     https://piratesheep.com
 */
class Sheep_Debug_Model_Cron
{
    const DATE_FORMAT = 'Y-m-d H:i:s';

    /**
     * Cron jobs that deletes expired request info
     */
    public function deleteExpiredRequests()
    {
        $helper = Mage::helper('sheep_debug');
        if (!$helper->isEnabled()) {
            return 'skipped: module is disabled.';
        }

        if ($helper->getPersistLifetime() == 0) {
            return 'skipped: lifetime is set to 0';
        }

        $expirationDate = $this->getExpirationDate(date(self::DATE_FORMAT));
        $table = $this->getRequestsTable();
        $deleteSql = "DELETE FROM {$table} WHERE date <= '{$expirationDate}'";

        /** @var Magento_Db_Adapter_Pdo_Mysql $connection */
        $connection = Mage::getSingleton('core/resource')->getConnection('core_write');

        /** @var Varien_Db_Statement_Pdo_Mysql $result */
        $result = $connection->query($deleteSql);
        return "{$result->rowCount()} requests deleted";
    }

    /**
     * Returns request info table
     *
     * @return string
     */
    public function getRequestsTable()
    {
        return Mage::getResourceModel('sheep_debug/requestInfo')->getMainTable();
    }

    /**
     * Removes configured number of days from specified date
     *
     * @param string $currentDate
     * @return bool|string
     */
    public function getExpirationDate($currentDate)
    {
        $numberOfDays = Mage::helper('sheep_debug')->getPersistLifetime();

        return date(self::DATE_FORMAT, strtotime("-{$numberOfDays} days {$currentDate}"));
    }

}


<?php

/**
 * Class Sheep_Debug_Block_Abstract
 *
 * @category Sheep
 * @package  Sheep_Debug
 * @license  Copyright: Pirate Sheep, 2016
 * @link     https://piratesheep.com
 *
 */
class Sheep_Debug_Block_Abstract extends Mage_Core_Block_Template
{
    /** @var Sheep_Debug_Helper_Data */
    protected $helper;

    /** @var  Sheep_Debug_Model_RequestInfo */
    protected $requestInfo;

    /**
     * Sheep_Debug_Block_Abstract constructor.
     *
     * @param array $args
     */
    public function __construct(array $args = array())
    {
        parent::__construct($args);
        $this->helper = Mage::helper('sheep_debug');
    }

    /**
     * By default we don't cache our blocks
     *
     * @return null
     */
    public function getCacheLifetime()
    {
        return null;
    }

    /**
     * Retrieve application instance
     *
     * Method added to support Magento CE 1.7
     *
     * @return Mage_Core_Model_App
     */
    protected function _getApp()
    {
        return Mage::app();
    }

    /**
     * Returns id for default configured store
     *
     * @return mixed
     * @throws Mage_Core_Exception
     */
    public function getDefaultStoreId()
    {
        return Mage::app()
            ->getWebsite()
            ->getDefaultGroup()
            ->getDefaultStoreId();
    }

    /**
     * Disable template hints for our own blocks
     *
     * @return bool
     */
    public function getShowTemplateHints()
    {
        return false;
    }

    /**
     * @param array $args
     * @return mixed
     */
    public function parentTranslate(array $args)
    {
        return call_user_func_array(array('parent', '__'), $args);
    }

    /**
     * @param array $args
     * @return mixed
     */
    public function dummyTranslate(array $args)
    {
        $text = array_shift($args);
        $result = @vsprintf($text, $args);
        return $result ?: $text;
    }

    /**
     * Don't use translation for our debug module
     *
     * @return mixed
     */
    public function __()
    {
        $args = func_get_args();
        return $this->helper->useStoreLocale() ? $this->parentTranslate($args) : $this->dummyTranslate($args);
    }

    /**
     * Returns registered request info or request profile for current request
     *
     * @return Sheep_Debug_Model_RequestInfo
     */
    public function getRequestInfo()
    {
        if ($this->requestInfo === null) {
            $this->requestInfo = Mage::registry('sheep_debug_request_info') ?: Mage::getSingleton('sheep_debug/observer')->getRequestInfo();
        }

        return $this->requestInfo;
    }

    /**
     * Returns url to request profile list
     *
     * @param array $filters
     * @return string
     */
    public function getRequestListUrl(array $filters = array())
    {
        return Mage::helper('sheep_debug/url')->getRequestListUrl($filters);
    }

    /**
     * Returns url to latest request profile page view
     *
     * @param string $panel
     * @return string
     */
    public function getLatestRequestViewUrl($panel = null)
    {
        return Mage::helper('sheep_debug/url')->getLatestRequestViewUrl($panel);
    }

    /**
     * Returns url to specified request profile page
     *
     * @param string $panel
     * @param string $token
     * @return string
     */
    public function getRequestViewUrl($panel = null, $token = null)
    {
        $token = $token ?: $this->getRequestInfo()->getToken();
        return $token ? Mage::helper('sheep_debug/url')->getRequestViewUrl($token, $panel) : '#';
    }

    /**
     * Returns number formatted based on current locale
     *
     * @param mixed $number
     * @param int   $precision
     * @return string
     */
    public function formatNumber($number, $precision = 2)
    {
        return $this->helper->useStoreLocale() ? $this->helper->formatNumber($number, $precision) : number_format($number, $precision);
    }

    /**
     * Returns an option array where every element has its value and label filled in
     * with elements from passed array
     *
     * @param array $data
     * @return array
     */
    public function getOptionArray(array $data)
    {
        $options = array();

        foreach ($data as $value) {
            $options[] = array('value' => $value, 'label' => $value);
        }

        return $options;
    }

    /**
     * Returns CSS class based on response status code
     *
     * @param int $statusCode
     * @return string
     */
    public function getStatusCodeClass($statusCode)
    {
        $cssClass = 'status-success';
        if ($statusCode > 399) {
            $cssClass = 'status-error';
        } else if ($statusCode > 299) {
            $cssClass = 'status-warning';
        }

        return $cssClass;
    }

}


<?php

/**
 * Class Sheep_Debug_ModelController
 *
 * @category Sheep
 * @package  Sheep_Debug
 * @license  Copyright: Pirate Sheep, 2016
 * @link     https://piratesheep.com
 */
class Sheep_Debug_ModelController extends Sheep_Debug_Controller_Front_Action
{

    /**
     * Enable SQL profiler
     */
    public function enableSqlProfilerAction()
    {
        try {
            $this->getService()->setSqlProfilerStatus(true);
            $this->getService()->flushCache();

            Mage::getSingleton('core/session')->addSuccess('SQL profiler was enabled.');
        } catch (Exception $e) {
            Mage::getSingleton('core/session')->addError('Unable to enable SQL profiler: ' . $e->getMessage());
        }

        $this->_redirectReferer();
    }

    /**
     * Disable SQL profiler
     */
    public function disableSqlProfilerAction()
    {
        try {
            $this->getService()->setSqlProfilerStatus(false);
            $this->getService()->flushCache();

            Mage::getSingleton('core/session')->addSuccess('SQL profiler was disabled.');
        } catch (Exception $e) {
            Mage::getSingleton('core/session')->addError('Unable to disable SQL profiler: ' . $e->getMessage());
        }

        $this->_redirectReferer();
    }

    /**
     * Runs specified SQL
     */
    public function selectSqlAction()
    {
        if ($query = $this->_initQuery()) {
            $helper = Mage::helper('sheep_debug');
            $results = $helper->runSql($query->getQuery(), $query->getQueryParams());
            $this->renderTable($results);
        }
    }

    /**
     * Runs DESCRIBE for specified SQL
     */
    public function describeSqlAction()
    {
        if ($query = $this->_initQuery()) {
            $helper = Mage::helper('sheep_debug');
            $results = $helper->runSql('EXPLAIN EXTENDED ' . $query->getQuery(), $query->getQueryParams());
            $this->renderTable($results);
        }
    }

    /**
     * Returns stack trace for specified query
     */
    public function stacktraceSqlAction()
    {
        if ($query = $this->_initQuery()) {
            $helper = Mage::helper('sheep_debug');
            $stripZendPath = $helper->canStripZendDbTrace() ? 'lib/Zend/Db/Adapter' : '';
            $trimPath = $helper->canTrimMagentoBaseDir() ? Mage::getBaseDir() . DS : '';
            $html = '<pre>' . Mage::helper('sheep_debug')->formatStacktrace($query->getStackTrace(), $stripZendPath, $trimPath) . '</pre>';
            $this->getResponse()->setBody($html);
        }
    }

    /**
     * Returns query referenced in request parameters
     *
     * @return Sheep_Debug_Model_Query
     */
    protected function _initQuery()
    {
        $token = $this->getRequest()->getParam('token');
        $index = $this->getRequest()->getParam('index');

        if ($token === null || $index === null) {
            $this->getResponse()->setHttpResponseCode(400)->setBody('Invalid parameters');
            return null;
        }

        /** @var Sheep_Debug_Model_RequestInfo $requestProfile */
        $requestProfile = Mage::getModel('sheep_debug/requestInfo')->load($token, 'token');
        if (!$requestProfile->getId()) {
            $this->getResponse()->setHttpResponseCode(404)->setBody('Request profile not found');
            return null;
        }

        $queries = $requestProfile->getQueries();
        if (!$queries || !($index < count($queries))) {
            $this->getResponse()->setHttpResponseCode(404)->setBody('Query not found');
            return null;
        }

        /** @var Zend_Db_Profiler_Query $query */
        return $queries[(int)$index];
    }

}


<?php

/**
 * Class Sheep_Debug_Model_Block
 *
 * @category Sheep
 * @package  Sheep_Debug
 * @license  Copyright: Pirate Sheep, 2016
 * @link     https://piratesheep.com
 */
class Sheep_Debug_Model_Block
{
    public static $startRenderingTime;
    public static $endRenderingTime;

    /** @var string */
    protected $parentName;
    /** @var string */
    protected $name;
    /** @var string */
    protected $class;
    /** @var string */
    protected $templateFile;
    /** @var bool */
    protected $isRendering = false;
    /** @var int */
    protected $renderedAt = 0;
    /** @var int */
    protected $renderedCount;
    protected $renderedCompletedAt;
    protected $renderedDuration = 0;
    /** @var array */
    protected $data = array();

    public function init(Mage_Core_Block_Abstract $block)
    {
        $this->parentName = $block->getParentBlock() ? $block->getParentBlock()->getNameInLayout() : '';
        $this->name = $block->getNameInLayout();
        $this->class = get_class($block);
        // TODO: make sure we copy only serializable data
//        $this->data = $block->getData();
    }

    /**
     * Captures rendering start for specified block
     *
     * @param Mage_Core_Block_Abstract $block
     * @throws Exception
     */
    public function startRendering(Mage_Core_Block_Abstract $block)
    {
        if ($this->isRendering) {
            // Recursive block instances with same name is used - we don't update render start time
            $this->renderedCount++;

            Mage::log("Recursive block rendering {$this->getName()}", Zend_Log::DEBUG);
            return;
        }

        // Re-init data from block (some extension might update dynamically block's template)
        $this->init($block);

        $this->isRendering = true;
        $this->renderedCount++;
        $this->renderedAt = microtime(true);

        if (self::$startRenderingTime===null) {
            self::$startRenderingTime = $this->renderedAt;
        }
    }

    /**
     * Captures rendering completion for specified block
     *
     * @param Mage_Core_Block_Abstract $block
     */
    public function completeRendering(Mage_Core_Block_Abstract $block)
    {
        $this->isRendering = false;
        $this->renderedCompletedAt = microtime(true);
        $this->renderedDuration += ($this->renderedCompletedAt * 1000 - $this->renderedAt * 1000);
        $this->templateFile = $block instanceof Mage_Core_Block_Template ? $block->getTemplateFile() : '';

        self::$endRenderingTime = $this->renderedCompletedAt;
    }

    /**
     * Returns block's name in layout
     *
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * Returns block's class
     *
     * @return string
     */
    public function getClass()
    {
        return $this->class;
    }

    /**
     * Returns block's template file
     *
     * @return string
     */
    public function getTemplateFile()
    {
        return $this->templateFile;
    }

    /**
     * Checks if current block is rendering
     *
     * @return boolean
     */
    public function isRendering()
    {
        return $this->isRendering;
    }

    /**
     * Return timestamp when block rendering started
     *
     * @return int
     */
    public function getRenderedAt()
    {
        return $this->renderedAt;
    }

    /**
     * Returns timestamp when block rendering completed
     *
     * @return int
     */
    public function getRenderedCompletedAt()
    {
        return $this->renderedCompletedAt;
    }

    /**
     * Returns rendering duration in microseconds
     *
     * @return mixed
     */
    public function getRenderedDuration()
    {
        return $this->renderedDuration;
    }

    /**
     * Returns how many time a block with same name was rendered
     *
     * @return int
     */
    public function getRenderedCount()
    {
        return $this->renderedCount;
    }

    /**
     * Returns blocks associated data
     *
     * @return array
     */
    public function getData()
    {
        return $this->data;
    }

    /**
     * Returns rendering time for all blocks in microseconds
     *
     * @return mixed
     */
    static public function getTotalRenderingTime()
    {
        return self::$endRenderingTime * 1000 - self::$startRenderingTime * 1000;
    }

    /**
     * @return string
     */
    public function getParentName()
    {
        return $this->parentName;
    }

}


<?php

/**
 * Class Sheep_Debug_Model_Design
 *
 * @category Sheep
 * @package  Sheep_Debug
 * @license  Copyright: Pirate Sheep, 2016
 * @link     https://piratesheep.com
 */
class Sheep_Debug_Model_Design
{
    protected $area;
    protected $packageName;
    protected $themeLayout;
    protected $themeTemplate;
    protected $themeSkin;
    protected $themeLocale;

    /** @var array */
    protected $layoutHandles = array();
    /** @var string */
    protected $layoutUpdates;
    protected $uncompressedLayoutUpdates;

    /**
     * Captures layout information
     *
     * @param Mage_Core_Model_Layout $layout
     * @param Mage_Core_Model_Design_Package $designPackage
     */
    public function init(Mage_Core_Model_Layout $layout, Mage_Core_Model_Design_Package $designPackage)
    {
        $this->area = $designPackage->getArea();
        $this->packageName = $designPackage->getPackageName();
        $this->themeLayout = $designPackage->getTheme('layout');
        $this->themeLocale = $designPackage->getTheme('locale');
        $this->themeTemplate = $designPackage->getTheme('template');
        $this->themeSkin = $designPackage->getTheme('skin');

        $this->layoutHandles = $layout->getUpdate()->getHandles();
        $this->setLayoutUpdates($layout->getUpdate()->asArray());
    }

    /**
     * Sets layout updates.
     * They are stores as compressed json.
     *
     * @param array $updates
     */
    public function setLayoutUpdates(array $updates)
    {
        $this->layoutUpdates = gzcompress(json_encode($updates));
    }

    /**
     * Returns layout handles added during the request
     *
     * @return array
     */
    public function getLayoutHandles()
    {
        return $this->layoutHandles;
    }

    /**
     * Returns layout updates processed during the request
     *
     * @return array
     */
    public function getLayoutUpdates()
    {
        if ($this->uncompressedLayoutUpdates === null) {
            $this->uncompressedLayoutUpdates = $this->layoutUpdates ?
                json_decode(gzuncompress($this->layoutUpdates), true) : array();
        }

        return $this->uncompressedLayoutUpdates;
    }

    /**
     * Returns application area used during request
     *
     * @return string
     */
    public function getArea()
    {
        return $this->area;
    }

    /**
     * Returns design package name used during request
     *
     * @return string
     */
    public function getPackageName()
    {
        return $this->packageName;
    }

    /**
     * Returns used theme name
     *
     * @return string
     */
    public function getThemeLayout()
    {
        return $this->themeLayout;
    }

    /**
     * Returns used theme template
     *
     * @return string
     */
    public function getThemeTemplate()
    {
        return $this->themeTemplate;
    }

    /**
     * Returns used theme skin
     *
     * @return string
     */
    public function getThemeSkin()
    {
        return $this->themeSkin;
    }

    /**
     * Returns used theme locale
     *
     * @return string
     */
    public function getThemeLocale()
    {
        return $this->themeLocale;
    }

    /**
     * Returns design descriptive properties
     *
     * @return array
     */
    public function getInfoAsArray()
    {
        return array(
            'design_area'    => $this->getArea(),
            'package_name'   => $this->getPackageName(),
            'layout_theme'   => $this->getThemeLayout(),
            'template_theme' => $this->getThemeTemplate(),
            'locale'         => $this->getThemeLocale(),
            'skin'           => $this->getThemeSkin()
        );
    }

}


<?php

/**
 * Class Sheep_Debug_Block_View represents main block used in request profile view page
 *
 * @category Sheep
 * @package  Sheep_Debug
 * @license  Copyright: Pirate Sheep, 2016
 * @link     https://piratesheep.com
 */
class Sheep_Debug_Block_View extends Sheep_Debug_Block_Abstract
{

    /**
     * Returns request info
     *
     * @return Sheep_Debug_Model_RequestInfo
     */
    public function getRequestInfo()
    {
        if ($this->requestInfo === null) {
            $this->requestInfo = Mage::registry('sheep_debug_request_info');
        }

        return $this->requestInfo;
    }

    /**
     * Returns an instance to our service
     *
     * @return Sheep_Debug_Model_Service
     */
    public function getService()
    {
        return Mage::getSingleton('sheep_debug/service');
    }

    /**
     * Returns url for request list with current filters
     *
     * @param array $filters
     * @return string
     */
    public function getFilteredRequestListUrl($filters = array())
    {
        // Preserver current filter
        $currentFilters = Mage::helper('sheep_debug/filter')->getRequestFilters($this->getRequest());
        $filters = array_merge($currentFilters, $filters);

        return $this->getRequestListUrl($filters);
    }

    /**
     * Renders an array as text
     *
     * @param $array
     * @return string
     */
    public function renderArrayAsText($array)
    {
        $values = array();
        foreach ($array as $key => $value) {
            $values[] = $this->escapeHtml($key) . ' = ' . $this->renderValue($value);
        }

        return implode(', ', $values);
    }

    /**
     * Iterates an array and prints its keys and values.
     *
     * @param array  $data
     * @param string $noDataLabel
     * @param null   $header
     * @return string
     */
    public function renderArray($data, $noDataLabel = 'No Data', $header = null)
    {
        /** @var Mage_Core_Block_Template $block */
        $block = $this->getLayout()->createBlock('sheep_debug/view');
        $block->setTemplate('sheep_debug/view/panel/_array.phtml');
        $block->setData('array', $data);
        $block->setData('no_data_label', $noDataLabel);
        $block->setData('header', $header);

        return $block->toHtml();
    }

    /**
     *
     * If fields parameter is omitted we're going to use keys from first array element.
     *
     * @param array  $data
     * @param array  $fields
     * @param string $noDataLabel
     * @return string
     */
    public function renderArrayFields(array $data, array $fields = array(), $noDataLabel = 'No Data')
    {
        // Empty array and fields were not specified
        if (!$data && !$fields) {
            return $this->renderArray($data, $noDataLabel);
        }

        // Non empty array and fields are not specified
        if (!$fields) {
            $fields = array_keys(reset($data));
        }

        /** @var Mage_Core_Block_Template $block */
        $block = $this->getLayout()->createBlock('sheep_debug/view');
        $block->setTemplate('sheep_debug/view/panel/_array_fields.phtml');
        $block->setData('array', $data);
        $block->setData('fields', $fields);
        $block->setData('no_data_label', $noDataLabel);

        return $block->toHtml();
    }

    /**
     * Renders an array key
     *
     * @param string $field
     * @return string
     */
    public function renderFieldLabel($field)
    {
        return $this->escapeHtml(ucwords(str_replace('_', ' ', $field)));
    }

    /**
     * Prints recursively a value. We don't test for cyclic references for compound types (e.g array)
     *
     * @param $value
     * @return string
     */
    public function renderValue($value)
    {
        $output = '';
        if ($value) {
            if (is_scalar($value)) {
                $output = $this->escapeHtml($value);
            } else if (is_array($value)) {
                $output = $this->renderArray($value);
            } else {
                return $this->escapeHtml(var_export($value, true));
            }
        }

        return $output;
    }

    /**
     * Builds a tree based on block recorded information
     *
     * @return Varien_Data_Tree_Node[]
     */
    public function getBlocksAsTree()
    {
        $blocks = $this->getRequestInfo()->getBlocks();
        $tree = new Varien_Data_Tree();
        $rootNodes = array();

        foreach ($blocks as $block) {
            $parentNode = $tree->getNodeById($block->getParentName());

            $node = new Varien_Data_Tree_Node(array(
                'name'     => $block->getName(),
                'class'    => $block->getClass(),
                'template' => $block->getTemplateFile(),
                'duration' => $block->getRenderedDuration(),
                'count' => $block->getRenderedCount()
            ), 'name', $tree, $parentNode);

            $tree->addNode($node, $parentNode);

            if (!$parentNode) {
                $rootNodes[] = $node;
            }
        }

        return $rootNodes;
    }

    /**
     * Returns a tree html representation for layout tree
     *
     * @return string
     */
    public function getBlockTreeHtml()
    {
        $content = '';
        $rootNodes = $this->getBlocksAsTree();

        // Try to iterate our non-conventional tree
        foreach ($rootNodes as $rootNode) {
            $content .= $this->renderTreeNode($rootNode);
        }

        return $content;
    }

    /**
     * Renders a rendering tree node
     *
     * @see Sheep_Debug_Block_View::getBlocksTree
     * @param Varien_Data_Tree_Node $node
     * @param int                   $indentLevel
     * @return string
     */
    public function renderTreeNode(Varien_Data_Tree_Node $node, $indentLevel=0)
    {
        $block = $this->getLayout()->createBlock('sheep_debug/view');
        $block->setRequestInfo($this->getRequestInfo());
        $block->setTemplate('sheep_debug/view/panel/_block_node.phtml');
        $block->setNode($node);
        $block->setIndent($indentLevel);

        return $block->toHtml();
    }

}


<?php

/**
 * Class Sheep_Debug_Helper_Http
 *
 * @category Sheep
 * @package  Sheep_Debug
 * @license  Copyright: Pirate Sheep, 2016
 * @link     https://piratesheep.com
 */
class Sheep_Debug_Helper_Http extends Mage_Core_Helper_Data
{

    /**
     * Returns $_SERVER
     *
     * @return array
     */
    public function getGlobalServer()
    {
        return isset($_SERVER) ? $_SERVER : array();
    }

    /**
     * Returns $_SESSION or empty array if not available
     *
     * @return array
     */
    public function getGlobalSession()
    {
        return isset($_SESSION) ? $_SESSION : array();
    }

    /**
     * Returns $_POST or empty array if not available
     *
     * @return array
     */
    public function getGlobalPost()
    {
        return isset($_POST) ? $_POST : array();
    }

    /**
     * Returns $_GET or empty array if not available
     *
     * @return array
     */
    public function getGlobalGet()
    {
        return isset($_GET) ? $_GET : array();
    }

    /**
     * Returns $_COOKIE or empty array if not available
     *
     * @return array
     */
    public function getGlobalCookie()
    {
        return isset($_COOKIE) ? $_COOKIE : array();
    }

    /**
     * Returns request http method based on REQUEST_METHOD
     *
     * @return string
     */
    public function getHttpMethod()
    {
        $server = $this->getGlobalServer();
        return array_key_exists('REQUEST_METHOD', $server) ? $server['REQUEST_METHOD'] : '';
    }

    /**
     * Returns request path based on REQUEST_URI
     *
     * @return mixed|string
     */
    public function getRequestPath()
    {
        $requestPath = '';
        $server = $this->getGlobalServer();
        if (array_key_exists('REQUEST_URI', $server)) {
            $requestPath = parse_url($server['REQUEST_URI'], PHP_URL_PATH);
        }

        return $requestPath;
    }

    /**
     * @return mixed|string
     */
    public function getRemoteAddr()
    {
        $remoteAddr = '';
        $server = $this->getGlobalServer();
        $headers = $this->getAllHeaders();

        if (array_key_exists('REMOTE_ADDR', $server)) {
            $remoteAddr = $server['REMOTE_ADDR'];
        }

        if (array_key_exists('X-Forwarded-For', $headers) && $headers['X-Forwarded-For']) {
            $remoteAddr = $headers['X-Forwarded-For'];
        }

        return $remoteAddr;
    }

    /**
     * Wrapper for getallheaders().
     *
     * This method is not available on CLI
     * @return array
     */
    public function getAllHeaders()
    {
        if (!function_exists('getallheaders')) {
            $libRelativePath = 'lib' . DS . 'getallheaders' . DS . 'getallheaders.php';
            $polyfillFilepath = Mage::helper('sheep_debug')->getModuleDirectory() . DS . $libRelativePath;
            require_once($polyfillFilepath);
        }

        return function_exists('getallheaders') ? getallheaders() : array();
    }
}


<?php

/**
 * Class Sheep_Debug_Helper_Url
 *
 * @category Sheep
 * @package  Sheep_Debug
 * @license  Copyright: Pirate Sheep, 2016
 * @link     https://piratesheep.com
 */
class Sheep_Debug_Helper_Url extends Mage_Core_Helper_Data
{
    const MODULE_ROUTE = 'sheep_debug/';

    /**
     * Returns store id that is used for debug route
     *
     * @return int
     */
    public function getRouteStoreId()
    {
        return Mage::app()->getDefaultStoreView()->getId();
    }

    /**
     * Returns an url for our module route and default store
     *
     * @param string $path Contains controller and action. Route will be added.
     * @param array $params
     * @return string
     */
    public function getUrl($path, array $params = array())
    {
        $path = self::MODULE_ROUTE . $path;
        $params['_store'] = $this->getRouteStoreId();
        $params['_nosid'] = true;

        return $this->_getUrl($path, $params);
    }

    /**
     * Returns request list page url
     *
     * @see \Sheep_Debug_IndexController::searchAction
     * @param array $filters
     * @return string
     */
    public function getRequestListUrl($filters = array())
    {
        return $this->getUrl('index/search', $filters);
    }

    /**
     * Returns last request profile view page url
     *
     * @see \Sheep_Debug_IndexController::latestAction
     * @param string $panel
     * @return string
     */
    public function getLatestRequestViewUrl($panel = 'request')
    {
        return $this->getUrl('index/latest', array('panel' => $panel));
    }

    /**
     * Returns request profile view page url for request specified by token
     *
     * @see \Sheep_Debug_IndexController::viewAction
     * @param $token
     * @param string $panel
     * @return string
     */
    public function getRequestViewUrl($token, $panel = 'request')
    {
        return $this->getUrl('index/view', array('token' => $token, 'panel' => $panel));
    }

    /**
     * Returns url to enable specified module
     *
     * @see \Sheep_Debug_ModuleController::enableAction
     * @param string $moduleName
     * @return string
     */
    public function getEnableModuleUrl($moduleName)
    {
        return $this->getUrl('module/enable', array('module' => $moduleName));
    }

    /**
     * Returns url to disable specified module
     *
     * @see \Sheep_Debug_ModuleController::disableAction
     * @param string $moduleName
     * @return string
     */
    public function getDisableModuleUrl($moduleName)
    {
        return $this->getUrl('module/disable', array('module' => $moduleName));
    }

    /**
     * Returns url to enable SQL profiler
     *
     * @see \Sheep_Debug_ModelController::enableSqlProfilerAction
     * @return string
     */
    public function getEnableSqlProfilerUrl()
    {
        return $this->getUrl('model/enableSqlProfiler');
    }

    /**
     * Returns url to disable SQL profiler
     *
     * @see \Sheep_Debug_ModelController::disableSqlProfilerAction
     * @return string
     */
    public function getDisableSqlProfilerUrl()
    {
        return $this->getUrl('model/disableSqlProfiler');
    }

    /**
     * Returns url to execute SQL specified by request token and its index
     *
     * @see \Sheep_Debug_ModelController::selectSqlAction
     * @param string $token
     * @param int $index
     * @return string
     */
    public function getSelectQueryUrl($token, $index)
    {
        return $this->getUrl('model/selectSql', array('token' => $token, 'index' => $index));
    }

    /**
     * Returns url to describe SQL specified by request token and its index
     *
     * @see \Sheep_Debug_ModelController::describeSqlAction
     * @param string $token
     * @param int $index
     * @return string
     */
    public function getExplainQueryUrl($token, $index)
    {
        return $this->getUrl('model/describeSql', array('token' => $token, 'index' => $index));
    }

    /**
     * Returns url that returns SQL stack trace
     *
     * @see \Sheep_Debug_ModelController::stacktraceSqlAction
     * @param string $token
     * @param int $index
     * @return string
     */
    public function getQueryStacktraceUrl($token, $index)
    {
        return $this->getUrl('model/stacktraceSql', array('token' => $token, 'index' => $index));
    }

    /**
     * Returns url that shows definition for specified block class
     *
     * @deprecated This functionality is not currently available.
     * @param string $blockClass
     * @return string
     */
    public function getViewBlockUrl($blockClass)
    {
        return $this->getUrl('block/viewBlock', array('block' => $blockClass));
    }

    /**
     * Returns url that shows contents of specified template.
     *
     * @deprecated This functionality is not currently available.
     * @param string $template
     * @return string
     */
    public function getViewTemplateUrl($template)
    {
        return $this->getUrl('block/viewTemplate', array('template' => $this->urlEncode($template)));
    }

    /**
     * @param string $layoutHandle
     * @param int $storeId
     * @param string $area
     * @return string
     */
    public function getViewHandleUrl($layoutHandle, $storeId, $area)
    {
        return $this->getUrl('design/viewHandle', array('handle' => $layoutHandle, 'store' => $storeId, 'area' => $area));
    }

    /**
     * Returns url that fetches layout updates for processed for specified request
     *
     * @see \Sheep_Debug_DesignController::layoutUpdatesAction
     * @param string $token
     * @return string
     */
    public function getLayoutUpdatesUrl($token)
    {
        return $this->getUrl('design/layoutUpdates', array('token' => $token));
    }

    /**
     * Returns url that fetches log line added during specified request and for specified log file
     *
     * @see \Sheep_Debug_IndexController::viewLogAction
     * @param string $token
     * @param string $logfile
     * @return string
     */
    public function getViewLogUrl($token, $logfile)
    {
        return $this->getUrl('index/viewLog', array('token' => $token, 'log' => $logfile));
    }

    /**
     * Returns url that delete all recorded request profiles
     *
     * @see \Sheep_Debug_IndexController::purgeProfilesAction
     * @return string
     */
    public function getPurgeProfilesAction()
    {
        return $this->getUrl('index/purgeProfiles');
    }

    /**
     * Returns url that returns classed for different types of group classes
     *
     * @see \Sheep_Debug_UtilController::searchGroupClassAction
     * @return string
     */
    public function getSearchGroupClassUrl()
    {
        return $this->getUrl('util/searchGroupClass');
    }

    /**
     * Returns url that flushes Magento's cache storage
     *
     * @see \Sheep_Debug_UtilController::flushCacheAction
     * @return string
     */
    public function getFlushCacheUrl()
    {
        return $this->getUrl('util/flushCache');
    }

    /**
     * Returns url that enables template and block hints
     *
     * @see \Sheep_Debug_UtilController::enableTemplateHintsAction
     * @return string
     */
    public function getEnableTemplateHintsUrl()
    {
        return $this->getUrl('util/enableTemplateHints');
    }

    /**
     * Returns url that disables template and block hints
     *
     * @see \Sheep_Debug_UtilController::disableTemplateHintsAction
     * @return string
     */
    public function getDisableTemplateHintsUrl()
    {
        return $this->getUrl('util/disableTemplateHints');
    }

    /**
     * Returns url that enables Full Page Cache debug (available only for Magento Enterprise)
     *
     * @see \Sheep_Debug_UtilController::enableFPCDebugAction
     * @return string
     */
    public function getEnableFPCDebugUrl()
    {
        return $this->getUrl('util/enableFPCDebug');
    }

    /**
     * Returns url that disables Full Page Cache debug (available only for Magento Enterprise)
     *
     * @see \Sheep_Debug_UtilController::disableFPCDebugAction
     * @return string
     */
    public function getDisableFPCDebugUrl()
    {
        return $this->getUrl('util/disableFPCDebug');
    }

    /**
     * Returns url that enables inline translations
     *
     * @see \Sheep_Debug_UtilController::enableTranslateAction
     * @return string
     */
    public function getEnableTranslateUrl()
    {
        return $this->getUrl('util/enableTranslate');
    }

    /**
     * Returns url that disables inline translation
     *
     * @see \Sheep_Debug_UtilController::disableTranslateAction
     * @return string
     */
    public function getDisableTranslateUrl()
    {
        return $this->getUrl('util/disableTranslate');
    }

    /**
     * Returns url that show PHP info
     *
     * @see \Sheep_Debug_ConfigController::phpinfoAction
     * @return string
     */
    public function getPhpInfoUrl()
    {
        return $this->getUrl('config/phpinfo');
    }

    /**
     * Returns url that returns results for searched configurations
     *
     * @see \Sheep_Debug_ConfigController::searchAction
     * @deprecated Currently functionality is not available.
     * @return string
     */
    public function getSearchConfigUrl()
    {
        return $this->getUrl('config/search');
    }

    /**
     * Returns url that outputs configuration as text or as xml
     *
     * @see \Sheep_Debug_ConfigController::downloadAction
     * @param string $type
     * @return string
     */
    public function getDownloadConfig($type = 'txt')
    {
        return $this->getUrl('config/download', array('type' => $type));
    }

    /**
     * Returns url that enables to automatically start Varien Profiler
     *
     * @see \Sheep_Debug_ConfigController::enableVarienProfilerAction
     * @return string
     */
    public function getEnableVarienProfilerUrl()
    {
        return $this->getUrl('config/enableVarienProfiler');
    }

    /**
     * Returns url that disables to start automatically Varien Profiler
     *
     * @see \Sheep_Debug_ConfigController::disableVarienProfilerAction
     * @return string
     */
    public function getDisableVarienProfilerUrl()
    {
        return $this->getUrl('config/disableVarienProfiler');
    }

    /**
     * Returns url that outputs body of an e-mail sent during specified request
     *
     * @see \Sheep_Debug_EmailController::getBodyAction
     * @param string $token
     * @param int $row
     * @return string
     */
    public function getEmailBodyUrl($token, $row)
    {
        return $this->getUrl('email/getBody', array('token' => $token, 'index' => $row));
    }

}


<?php

/**
 * Class Sheep_Debug_Block_Config
 *
 * @category Sheep
 * @package  Sheep_Debug
 * @license  Copyright: Pirate Sheep, 2016
 * @link     https://piratesheep.com
 */
class Sheep_Debug_Block_Config extends Sheep_Debug_Block_Panel
{

    /**
     * Returns version for Magento
     *
     * @return string
     */
    public function getMagentoVersion()
    {
        return Mage::helper('sheep_debug/config')->getMagentoVersion();
    }

    /**
     * Checks if Magento Developer Mode is enabled
     *
     * @return bool
     */
    public function isDeveloperMode()
    {
        return $this->helper->getIsDeveloperMode();
    }

    /**
     * Returns an array with statuses for PHP extensions required by Magento
     *
     * @return array
     */
    public function getExtensionStatus()
    {
        return Mage::helper('sheep_debug/config')->getExtensionStatus();
    }

    /**
     * Returns a string representation for current store (website name and store name)
     *
     * @return string
     */
    public function getCurrentStore()
    {
        $currentStore = $this->_getApp()->getStore();
        return sprintf('%s / %s', $currentStore->getWebsite()->getName(),  $currentStore->getName());
    }

}


<?php

/**
 * Class Sheep_Debug_Model_Service
 *
 * @category Sheep
 * @package  Sheep_Debug
 * @license  Copyright: Pirate Sheep, 2016
 * @link     https://piratesheep.com
 */
class Sheep_Debug_Model_Service
{

    /**
     * Return's Magento base design directory
     *
     * @return string
     */
    public function getBaseDir()
    {
        return Mage::getBaseDir();
    }

    /**
     * Returns Magento's configuration model
     *
     * @return Mage_Core_Model_Config
     */
    protected function getConfig()
    {
        return Mage::getConfig();
    }

    /**
     * Returns Magento's cache model
     *
     * @return Mage_Core_Model_Cache
     */
    protected function getCacheInstance()
    {
        return Mage::app()->getCacheInstance();
    }

    /**
     * Loads specified XML file
     *
     * @param $filepath
     * @return SimpleXMLElement
     */
    protected function loadXmlFile($filepath)
    {
        return simplexml_load_file($filepath);
    }

    /**
     * Saves specified SimpleXMLElement
     *
     * @param SimpleXMLElement $xml
     * @param string $filepath
     * @return bool
     */
    public function saveXml($xml, $filepath)
    {
        return $xml->saveXML($filepath);
    }

    /**
     * Flushes cache instance.
     */
    public function flushCache()
    {
        $this->getCacheInstance()->flush();
    }

    /**
     * Returns module configuration file
     *
     * @param $moduleName
     * @return string
     * @throws Exception
     */
    public function getModuleConfigFilePath($moduleName)
    {
        $config = $this->getConfig();
        $moduleConfig = $config->getModuleConfig($moduleName);

        if (!$moduleConfig) {
            throw  new Exception("Unable to find module '{$moduleName}'");
        }

        return $config->getOptions()->getEtcDir() . DS . 'modules' . DS . $moduleName . '.xml';
    }

    /**
     * Sets active status for specified module
     *
     * @param string $moduleName
     * @param bool $isActive
     * @throws Exception
     */
    public function setModuleStatus($moduleName, $isActive)
    {
        $moduleConfigFile = $this->getModuleConfigFilePath($moduleName);
        $configXml = $this->loadXmlFile($moduleConfigFile);
        if ($configXml === false) {
            throw new Exception("Unable to parse module configuration file {$moduleConfigFile}");
        }

        $configXml->modules->{$moduleName}->active = $isActive ? 'true' : 'false';

        // save
        if ($this->saveXml($configXml, $moduleConfigFile) === false) {
            throw new Exception("Unable to save module configuration file {$moduleConfigFile}. Check to see if web server user has write permissions.");
        }
    }

    /**
     * Returns absolute path for local.xml
     *
     * @return string
     */
    public function getLocalXmlFilePath()
    {
        return Mage::getBaseDir('etc') . DS . 'local.xml';
    }

    /**
     * Changes status for SQL profiler
     *
     * @param bool $isEnabled
     * @throws Exception
     */
    public function setSqlProfilerStatus($isEnabled)
    {
        $filePath = $this->getLocalXmlFilePath();
        $xml = $this->loadXmlFile($filePath);
        if ($xml === false) {
            throw new Exception("Unable to parse local.xml configuration file: {$filePath}");
        }

        /** @var SimpleXMLElement $connectionNode */
        $connectionNode = $xml->global->resources->default_setup->connection;
        if ($isEnabled) {
            /** @noinspection PhpUndefinedFieldInspection */
            $connectionNode->profiler = '1';
        } else {
            unset($connectionNode->profiler);
        }

        if ($this->saveXml($xml, $filePath) === false) {
            throw new Exception("Unable to save {$filePath}: check if web server user has write permission");
        }
    }

    /**
     * Enables/Disables to automatically force varien profiler
     *
     * @param int $isEnabled
     */
    public function setVarienProfilerStatus($isEnabled)
    {
        $this->getConfig()->saveConfig(Sheep_Debug_Helper_Data::DEBUG_OPTION_FORCE_VARIEN_PROFILE_PATH, (int)$isEnabled);
    }

    /**
     * Enable/disables Full Page Cache debug
     *
     * @param int $status
     * @throws Exception
     */
    public function setFPCDebug($status)
    {
        if (!Mage::helper('sheep_debug')->isMagentoEE()) {
            throw new Exception ('Cannot enable FPC debug for this Magento version.');
        }

        $this->getConfig()->saveConfig('system/page_cache/debug', (int)$status);
    }

    /**
     * Changes status for template and block hints
     *
     * @param int $status
     */
    public function setTemplateHints($status)
    {
        $this->deleteTemplateHintsDbConfigs();

        $config = $this->getConfig();
        $config->saveConfig('dev/debug/template_hints', (int)$status);
        $config->saveConfig('dev/debug/template_hints_blocks', (int)$status);
    }

    /**
     * Changes status for inline translations
     *
     * @param int $status
     */
    public function setTranslateInline($status)
    {
        $this->getConfig()->saveConfig('dev/translate_inline/active', (int)$status);
    }

    /**
     * Searches configuration keys
     *
     * @param string $query
     * @return array
     */
    public function searchConfig($query)
    {
        $configArray = array();

        $configArray = Mage::helper('sheep_debug')->xml2array($this->getConfig()->getNode(), $configArray);

        $results = array();
        $configKeys = array_keys($configArray);
        foreach ($configKeys as $configKey) {
            if (strpos($configKey, $query) !== FALSE) {
                $results[$configKey] = $configArray[$configKey];
            }
        }

        return $results;
    }

    /**
     * Delete template_hints related configurations
     */
    public function deleteTemplateHintsDbConfigs()
    {
        $configTable = Mage::getResourceModel('core/config')->getMainTable();

        /** @var Magento_Db_Adapter_Pdo_Mysql $db */
        $db = Mage::getSingleton('core/resource')->getConnection('core_write');
        $db->delete($configTable, "path like 'dev/debug/template_hints%'");
    }

    /**
     * Deletes all saved request profiles
     *
     * @return int
     * @throws Zend_Db_Statement_Exception
     */
    public function purgeAllProfiles()
    {
        $table = Mage::getResourceModel('sheep_debug/requestInfo')->getMainTable();
        $deleteSql = "DELETE FROM {$table}";

        /** @var Magento_Db_Adapter_Pdo_Mysql $connection */
        $connection = Mage::getSingleton('core/resource')->getConnection('core_write');

        /** @var Varien_Db_Statement_Pdo_Mysql $result */
        $result = $connection->query($deleteSql);

        return $result->rowCount();
    }

    /**
     * Returns layout files that have updates for specified handle
     *
     * @param string $handle
     * @param int    $storeId
     * @param string $area
     * @return array
     */
    public function getFileUpdatesWithHandle($handle, $storeId, $area)
    {
        /** @var array $updateFiles */
        $updateFiles = Mage::helper('sheep_debug')->getLayoutUpdatesFiles($storeId, $area);

        /* @var $designPackage Mage_Core_Model_Design_Package */
        $designPackage = Mage::getModel('core/design_package');
        $designPackage->setStore($storeId);
        $designPackage->setArea($area);
        $designPackageName = $designPackage->getPackageName();
        $layoutTheme = $designPackage->getTheme('layout');

        // search handle in all layout files registered for this area, package name and theme
        $handleFiles = array();
        foreach ($updateFiles as $file) {
            $filename = $designPackage->getLayoutFilename($file, array(
                '_area'    => $area,
                '_package' => $designPackageName,
                '_theme'   => $layoutTheme
            ));

            if (!is_readable($filename)) {
                continue;
            }

            /** @var SimpleXMLElement $fileXml */
            $fileXml = $this->loadXmlFile($filename);
            if ($fileXml === false) {
                continue;
            }

            $relativeFilename = str_replace($this->getBaseDir(), '', $filename);

            /** @var SimpleXMLElement[] $result */
            $results = $fileXml->xpath("/layout/{$handle}");
            if ($results) {
                $handleFiles[$relativeFilename] = array();
                /** @var SimpleXMLElement $result */
                foreach ($results as $result) {
                    $handleFiles[$relativeFilename][] = $result->asXML();
                }
            }
        }

        return $handleFiles;
    }

    /**
     * Returns layout updates added via Admin and stored in database.
     *
     * Returned value is an array of arrays that have layout update id as key and its associated xml update as value.
     *
     * @see \Mage_Core_Model_Resource_Layout::fetchUpdatesByHandle
     *
     * @param string $handle
     * @param int    $storeId
     * @param string $area
     * @return array
     */
    public function getDatabaseUpdatesWithHandle($handle, $storeId, $area)
    {
        $databaseHandles = array();

        /* @var $designPackage Mage_Core_Model_Design_Package */
        $designPackage = Mage::getModel('core/design_package');
        $designPackage->setStore($storeId);
        $designPackage->setArea($area);

        /* @var $layoutResourceModel Mage_Core_Model_Resource_Layout */
        $layoutResourceModel = Mage::getResourceModel('core/layout');

        $bind = array(
            'store_id'             => $storeId,
            'area'                 => $area,
            'package'              => $designPackage->getPackageName(),
            'theme'                => $designPackage->getTheme('layout'),
            'layout_update_handle' => $handle
        );

        /* @var $readAdapter Varien_Db_Adapter_Pdo_Mysql */
        $readAdapter = Mage::getSingleton('core/resource')->getConnection('core_read');

        /* @var $select Varien_Db_Select */
        $select = $readAdapter->select()
            ->from(array('layout_update' => $layoutResourceModel->getMainTable()), array('layout_update_id', 'xml'))
            ->join(array('link' => $layoutResourceModel->getTable('core/layout_link')),
                'link.layout_update_id=layout_update.layout_update_id',
                '')
            ->where('link.store_id IN (0, :store_id)')
            ->where('link.area = :area')
            ->where('link.package = :package')
            ->where('link.theme = :theme')
            ->where('layout_update.handle = :layout_update_handle')
            ->order('layout_update.sort_order ' . Varien_Db_Select::SQL_ASC);

        $result = $readAdapter->fetchAssoc($select, $bind);

        if (count($result)) {
            foreach ($result as $dbLayoutUpdate) {
                $databaseHandles[$dbLayoutUpdate['layout_update_id']] = $dbLayoutUpdate['xml'];
            }
        }

        return $databaseHandles;
    }

}


<?php

/**
 * Class Sheep_Debug_Model_Collection
 *
 * @category Sheep
 * @package  Sheep_Debug
 * @license  Copyright: Pirate Sheep, 2016
 * @link     https://piratesheep.com
 */
class Sheep_Debug_Model_Collection
{
    const TYPE_FLAT = 'flat';
    const TYPE_EAV = 'eav';

    protected $type;
    protected $class;
    protected $query;
    protected $count;

    /**
     * Captures information from specified collection
     *
     * @param Varien_Data_Collection_Db $collection
     */
    public function init(Varien_Data_Collection_Db $collection)
    {
        $this->class = get_class($collection);
        $this->type = $collection instanceof Mage_Eav_Model_Entity_Collection_Abstract ? self::TYPE_EAV : self::TYPE_FLAT;
        $this->query = $collection->getSelectSql(true);
        $this->count = 0;
    }

    /**
     * Returns collection type (eav or flat)
     *
     * @return string
     */
    public function getType()
    {
        return $this->type;
    }

    /**
     * Returns collection class
     *
     * @return string
     */
    public function getClass()
    {
        return $this->class;
    }

    /**
     * Returns SQL query used by collection
     *
     * @return string
     */
    public function getQuery()
    {
        return $this->query;
    }

    /**
     * Returns number of times same collection class was loaded.
     *
     * @return int
     */
    public function getCount()
    {
        return $this->count;
    }

    /**
     * Increments collection load count
     */
    public function incrementCount()
    {
        $this->count++;
    }

}


<?php

/**
 * Class Sheep_Debug_DesignController
 *
 * @category Sheep
 * @package  Sheep_Debug
 * @license  Copyright: Pirate Sheep, 2016
 * @link     https://piratesheep.com
 */
class Sheep_Debug_DesignController extends Sheep_Debug_Controller_Front_Action
{

    /**
     * View layout handle details
     */
    public function viewHandleAction()
    {
        $area = $this->getRequest()->getParam('area');
        $storeId = (int)$this->getRequest()->getParam('store');
        $handle = $this->getRequest()->getParam('handle');

        $updatesByFile = $this->getService()->getFileUpdatesWithHandle($handle, $storeId, $area);
        $databaseUpdates = $this->getService()->getDatabaseUpdatesWithHandle($handle, $storeId, $area);

        $block = $this->getLayout()->createBlock('sheep_debug/view', '', array(
            'template' => 'sheep_debug/view/panel/_layout_updates.phtml',
            'file_updates' => $updatesByFile,
            'db_updates' => $databaseUpdates
        ));

        $this->getResponse()->setBody($block->toHtml());
    }

    /**
     * Returns layout handles for specified request profile
     */
    public function layoutUpdatesAction()
    {
        $token = $this->getRequest()->getParam('token');
        if (!$token) {
            return $this->getResponse()->setHttpResponseCode(400)->setBody('Invalid parameters');
        }

        /** @var Sheep_Debug_Model_RequestInfo $requestProfile */
        $requestProfile = Mage::getModel('sheep_debug/requestInfo')->load($token, 'token');
        if (!$requestProfile->getId()) {
            return $this->getResponse()->setHttpResponseCode(404)->setBody('Request profile not found');
        }

        $layoutUpdates = $requestProfile->getDesign()->getLayoutUpdates();
        $this->renderArray($layoutUpdates, 'No Data', array('#', 'XML'));
    }

}


<?php

/**
 * Class Sheep_Debug_Block_View_Sidebar
 *
 * @category Sheep
 * @package  Sheep_Debug
 * @license  Copyright: Pirate Sheep, 2016
 * @link     https://piratesheep.com
 */
class Sheep_Debug_Block_View_Sidebar extends Sheep_Debug_Block_View
{

    /**
     * Returns available http method filters
     *
     * @return array
     */
    public function getHttpMethodOptions()
    {
        return $this->getOptionArray(Mage::helper('sheep_debug/filter')->getHttpMethodValues());
    }

    /**
     * Returns html for http methods select
     *
     * @return string
     * @throws Exception
     */
    public function getHttpMethodsSelect()
    {
        $options = $this->getHttpMethodOptions();
        array_unshift($options, array('value' => '', 'label' => 'Any'));

        /** @var Mage_Core_Block_Html_Select $select */
        $select = $this->getLayout()->createBlock('core/html_select');

        $select->setName('method')
            ->setId('method')
            ->setValue($this->getRequest()->getParam('method'))
            ->setOptions($options);

        return $select->getHtml();
    }

    /**
     * Returns html for limit selects
     *
     * @return string
     * @throws Exception
     */
    public function getLimitOptionsSelect()
    {
        /** @var Sheep_Debug_Helper_Filter $filterHelper */
        $filterHelper = Mage::helper('sheep_debug/filter');

        /** @var Mage_Core_Block_Html_Select $select */
        $select = $this->getLayout()->createBlock('core/html_select');

        $select->setName('limit')
            ->setId('limit')
            ->setValue($this->getRequest()->getParam('limit', $filterHelper->getLimitDefaultValue()))
            ->setOptions($this->getOptionArray($filterHelper->getLimitValues()));

        return $select->getHtml();
    }

}


<?php

/**
 * Class Sheep_Debug_Model_Logging
 *
 * @category Sheep
 * @package  Sheep_Debug
 * @license  Copyright: Pirate Sheep, 2016
 * @link     https://piratesheep.com
 */
class Sheep_Debug_Model_Logging
{
    protected $files = array();
    protected $ranges = array();
    protected $logLineCount;

    /**
     * Adds log file that will be monitored during request.
     * Filename should be relative to global var/log directory.
     *
     * @param string $filename
     */
    public function addFile($filename)
    {
        $this->files[] = $filename;
    }

    /**
     * Returns monitored log files
     *
     * @return array
     */
    public function getFiles()
    {
        return $this->files;
    }

    /**
     * Adds position range for log file.
     * Position range represents a range that describe what logs were added during current requests.
     *
     * @param string $logFile
     * @param string $start
     * @param int $end
     */
    public function addRange($logFile, $start, $end = 0)
    {
        $this->ranges[$logFile] = array(
            'start' => $start,
            'end'   => $end
        );
    }

    /**
     * Returns position range for specified log file
     *
     * @param string $logFile
     * @return array
     * @throws Exception
     */
    public function getRange($logFile)
    {
        if (!array_key_exists($logFile, $this->ranges)) {
            throw new Exception('Invalid log file');
        }

        return $this->ranges[$logFile];
    }

    /**
     * Returns absolute path for specified log file.
     * Log file is considered to be relative to global log directory.
     *
     * @param string $filename
     * @return string
     */
    public function getLogFilePath($filename)
    {
        return Mage::getBaseDir('var') . DS . 'log' . DS . $filename;
    }

    /**
     * Initiates start range for all registered files
     */
    public function startRequest()
    {
        foreach ($this->files as $logFile) {
            $logFilePath = $this->getLogFilePath($logFile);
            $this->ranges[$logFile] = array(
                'start' => $this->getLastFilePosition($logFilePath),
                'end'   => 0
            );
        }
    }

    /**
     * Records end range for all registered log files
     */
    public function endRequest()
    {
        foreach ($this->files as $logFile) {
            $logFilePath = $this->getLogFilePath($logFile);
            $this->ranges[$logFile]['end'] = $this->getLastFilePosition($logFilePath);
        }
    }

    /**
     * Returns logged content for each log file
     *
     * @return array
     * @throws Exception
     */
    public function getLogging()
    {
        $logging = array();

        foreach ($this->files as $logFile) {
            $logging[$logFile] = $this->getLoggedContent($logFile);
        }

        return $logging;
    }

    /**
     * Returns current end position for specified file path
     *
     * @param string $filePath
     * @return int
     */
    public function getLastFilePosition($filePath)
    {
        if (!file_exists($filePath)) {
            return 0;
        }

        $f = fopen($filePath, 'r');
        fseek($f, -1, SEEK_END);
        return ftell($f);
    }

    /**
     * Returns content added during this request for specified log file
     *
     * @param string $logFile
     * @return string
     * @throws Exception
     */
    public function getLoggedContent($logFile)
    {
        if (!array_key_exists($logFile, $this->ranges)) {
            throw new Exception('Invalid log file');
        }

        return $this->getContent(
            $this->getLogFilePath($logFile),
            $this->ranges[$logFile]['start'],
            $this->ranges[$logFile]['end']
        );
    }

    /**
     * Returns number of lines added in specified log from start of this request
     *
     * @param string $logFile
     * @return int
     * @throws Exception
     */
    public function getLineCount($logFile)
    {
        $content = $this->getLoggedContent($logFile);
        return substr_count($content, "\n");
    }

    /**
     * Returns number of log lines added in all of registered logs
     *
     * @return int
     */
    public function getTotalLineCount()
    {
        if ($this->logLineCount === null) {
            $this->logLineCount = 0;
            foreach ($this->getFiles() as $log) {
                $this->logLineCount += $this->getLineCount($log);
            }
        }

        return $this->logLineCount;
    }

    /**
     * Returns content from specified file between specified range
     *
     * @param string $filePath
     * @param int $startPosition
     * @param int $endPosition
     * @return string
     */
    public function getContent($filePath, $startPosition, $endPosition)
    {
        if (!file_exists($filePath)) {
            return '';
        }

        // End position not saved yet
        if (!$endPosition) {
            return trim(file_get_contents($filePath, null, null, $startPosition));
        }

        // End position exists but is less then start position
        if ($endPosition <= $startPosition) {
            return '';
        }

        return trim(file_get_contents($filePath, null, null, $startPosition, $endPosition - $startPosition));
    }

}


<?php

/**
 * Class Sheep_Debug_Controller_Front_Action
 *
 * @category Sheep
 * @package  Sheep_Debug
 * @license  Copyright: Pirate Sheep, 2016
 * @link     https://piratesheep.com
 */
class Sheep_Debug_Controller_Front_Action extends Mage_Core_Controller_Front_Action
{

    /**
     * Prevent access to our access if toolbar is disabled
     *
     * @throws Zend_Controller_Response_Exception
     */
    public function preDispatch()
    {
        parent::preDispatch();

        if (!Mage::helper('sheep_debug')->isAllowed()) {
            $this->setFlag('', 'no-dispatch', true);
            $this->getResponse()->setHttpResponseCode(404);
        }
    }

    /**
     * Returns current session
     *
     * @return Mage_Core_Model_Session
     */
    public function getSession()
    {
        return Mage::getSingleton('core/session');
    }

    /**
     * Returns an instance to our all known service
     *
     * @return Sheep_Debug_Model_Service
     */
    public function getService()
    {
        return Mage::getModel('sheep_debug/service');
    }

    /**
     * Renders specified array
     *
     * @param array $data
     * @param string $noDataLabel   Label when array is empty.
     * @param null $header          An array with column label.
     * @return string
     */
    public function renderArray(array $data, $noDataLabel = 'No Data', $header = null)
    {
        /** @var Sheep_Debug_Block_View $block */
        $block = $this->getLayout()->createBlock('sheep_debug/view');
        $html = $block->renderArray($data, $noDataLabel, $header);

        $this->getResponse()->setHttpResponseCode(200)->setBody($html);
    }

    /**
     * Renders specified table (array of arrays)
     *
     * @param array $data
     * @param array $fields
     * @param string $noDataLabel
     * @return string
     */
    public function renderTable(array $data, array $fields = array(), $noDataLabel = 'No Data')
    {
        /** @var Sheep_Debug_Block_View $block */
        $block = $this->getLayout()->createBlock('sheep_debug/view');
        $html = $block->renderArrayFields($data, $fields, $noDataLabel);

        $this->getResponse()->setHttpResponseCode(200)->setBody($html);
    }

}


<?php

/**
 * Class Sheep_Debug_Helper_Config
 *
 * @category Sheep
 * @package  Sheep_Debug
 * @license  Copyright: Pirate Sheep, 2016
 * @link     https://piratesheep.com
 */
class Sheep_Debug_Helper_Config extends Mage_Core_Helper_Abstract
{

    /**
     * Returns Magento version
     *
     * @return string
     */
    public function getMagentoVersion()
    {
        return Mage::getVersion();
    }

    /**
     * Returns PHP version
     *
     * @return string
     */
    public function getPhpVersion()
    {
        return phpversion();
    }

    /**
     * Returns a list of php extensions required by current Magento version
     *
     * @return array
     */
    public function getExtensionRequirements()
    {
        return array('spl', 'dom', 'simplexml', 'mcrypt', 'hash', 'curl', 'iconv', 'ctype', 'gd', 'soap', 'mbstring');
    }

    /**
     * Returns enable state for required PHP extensions
     *
     * @return array
     */
    public function getExtensionStatus()
    {
        $status = array();

        $extensions = $this->getExtensionRequirements();
        foreach ($extensions as $extension) {
            $status [$extension] = extension_loaded($extension);
        }

        return $status;
    }

    /**
     * Returns description for installed Magento modules
     *
     * @return array
     */
    public function getModules()
    {
        $items = array();
        $items[] = array(
            'module'   => 'Magento',
            'codePool' => 'core',
            'active'   => true,
            'version'  => $this->getMagentoVersion()
        );

        $modulesConfig = Mage::getConfig()->getModuleConfig();
        foreach ($modulesConfig as $node) {
            foreach ($node as $module => $data) {
                $items[] = array(
                    'module'   => $module,
                    'codePool' => (string)$data->codePool,
                    'active'   => $data->active == 'true',
                    'version'  => (string)$data->version
                );
            }
        }

        return $items;
    }

}


<?php

/**
 * Class Sheep_Debug_Model_Model
 *
 * @category Sheep
 * @package  Sheep_Debug
 * @license  Copyright: Pirate Sheep, 2016
 * @link     https://piratesheep.com
 */
class Sheep_Debug_Model_Model
{
    protected $class;
    protected $resource;
    protected $count;

    /**
     * Captures information about specified model
     *
     * @param Mage_Core_Model_Abstract $model
     */
    public function init(Mage_Core_Model_Abstract $model)
    {
        $this->class = get_class($model);
        $this->resource = $model->getResourceName();
        $this->count = 0;
    }

    /**
     * Returns model's class
     *
     * @return string
     */
    public function getClass()
    {
        return $this->class;
    }

    /**
     * Increments number of times this model was loaded
     */
    public function incrementCount()
    {
        $this->count++;
    }

    /**
     * Returns model's resource name
     *
     * @return string
     */
    public function getResource()
    {
        return $this->resource;
    }

    /**
     * Returns model's number of loads
     *
     * @return int
     */
    public function getCount()
    {
        return $this->count;
    }

}


<?php

/**
 * Class Sheep_Debug_Helper_Filter
 *
 * @category Sheep
 * @package  Sheep_Debug
 * @license  Copyright: Pirate Sheep, 2016
 * @link     https://piratesheep.com
 */
class Sheep_Debug_Helper_Filter extends Mage_Core_Helper_Abstract
{
    const DEFAULT_LIMIT_VALUE = 10;

    protected $requestFilterValues;

    /**
     * Returns available filter names
     */
    public function getFilterParams()
    {
        return array('ip', 'method', 'path', 'token', 'start', 'limit', 'session_id');
    }

    /**
     * Returns an assoc array with filter and if its value from request.
     * Filters missing from request parameters are ignored.
     *
     * @param Mage_Core_Controller_Request_Http $request
     * @return array
     */
    public function getRequestFilters(Mage_Core_Controller_Request_Http $request)
    {
        if (!$this->requestFilterValues) {
            $filters = $this->getFilterParams();
            $this->requestFilterValues = array();

            foreach ($filters as $filter) {
                $param = $request->getParam($filter, null);
                if ($param !== null) {
                    $this->requestFilterValues[$filter] = $param;
                }
            }
        }

        return $this->requestFilterValues;
    }

    /**
     * Returns accepted values for http method filter
     *
     * @return array
     */
    public function getHttpMethodValues()
    {
        return array(
            'DELETE', 'GET', 'HEAD', 'PATCH', 'POST', 'PUT'
        );
    }

    /**
     * Returns default value for limit filter
     *
     * @return int
     */
    public function getLimitDefaultValue()
    {
        return self::DEFAULT_LIMIT_VALUE;
    }

    /**
     * Returns available values for limit filter
     *
     * @return array
     */
    public function getLimitValues()
    {
        return array(10, 50, 100);
    }

}


<?php

/**
 * Class Sheep_Debug_ModuleController
 *
 * @category Sheep
 * @package  Sheep_Debug
 * @license  Copyright: Pirate Sheep, 2016
 * @link     https://piratesheep.com
 */
class Sheep_Debug_ModuleController extends Sheep_Debug_Controller_Front_Action
{

    /**
     * Enables specified module
     */
    public function enableAction()
    {
        $moduleName = (string)$this->getRequest()->getParam('module');

        try {
            $this->getService()->setModuleStatus($moduleName, true);
            $this->getService()->flushCache();
            Mage::getSingleton('core/session')->addSuccess('Module was enabled.');
        } catch (Exception $e) {
            Mage::getSingleton('core/session')->addError('Unable to enable module: ' . $e->getMessage());
        }

        $this->_redirectReferer();
    }

    /**
     * Disables specified module
     */
    public function disableAction()
    {

        $moduleName = (string)$this->getRequest()->getParam('module');

        try {
            $this->getService()->setModuleStatus($moduleName, false);
            $this->getService()->flushCache();
            Mage::getSingleton('core/session')->addSuccess('Module was disabled.');
        } catch (Exception $e) {
            Mage::getSingleton('core/session')->addError('Unable to disable module: ' . $e->getMessage());
        }

        $this->_redirectReferer();
    }

}


<?php

/**
 * Class Sheep_Debug_Helper_Performance
 *
 * @category Sheep
 * @package  Sheep_Debug
 * @license  Copyright: Pirate Sheep, 2016
 * @link     https://piratesheep.com
 */
class Sheep_Debug_Helper_Performance extends Mage_Core_Helper_Abstract
{
    const SECTION = 'section';

    const CORE_CATEGORY = 'Core';
    const CONFIG_CATEGORY = 'Config';
    const EAV_CATEGORY = 'doctrine';
    const DB_CATEGORY = 'doctrine';
    const LAYOUT_CATEGORY = 'template';
    const EVENT_CATEGORY = 'event_listener';

    /**
     * Determines category based on timer name
     *
     * @param $timerName
     * @return string
     */
    public function getCategory($timerName)
    {
        $category = self::CORE_CATEGORY;

        if (strpos($timerName, 'mage::dispatch') === 0 || strpos($timerName, 'column.phtml') > 0) {
            $category = self::SECTION;
        } else if (strpos($timerName, 'Model_Resource') > 0) {
            $category = self::DB_CATEGORY;
        } else if (strpos($timerName, 'EAV') === 0 || strpos($timerName, '_LOAD_ATTRIBUTE_') === 0 || strpos($timerName, '__EAV_') === 0) {
            $category = self::EAV_CATEGORY;
        } else if (strpos($timerName, 'CORE::create_object_of') === 0) {
            $category = self::CORE_CATEGORY;
        } else if (strpos($timerName, 'OBSERVER') === 0 || strpos($timerName, 'DISPATCH EVENT') === 0) {
            $category = self::EVENT_CATEGORY;
        } else if (strpos($timerName, 'BLOCK') === 0) {
            $category = self::LAYOUT_CATEGORY;
        } else if (strpos($timerName, 'init_config') === 0) {
            $category = self::CONFIG_CATEGORY;
        } else if (strpos($timerName, 'layout/') === 0 || strpos($timerName, 'layout_') > 0) {
            $category = self::LAYOUT_CATEGORY;
        } else if (strpos($timerName, 'Mage_Core_Model_Design') === 0) {
            $category = self::LAYOUT_CATEGORY;
        } else if (strpos($timerName, '.phtml') > 0) {
            $category = self::LAYOUT_CATEGORY;
        }

        return $category;
    }

    /**
     * Converts timers registered by Varien Profile into structure understood by Symfony
     *
     * @param Sheep_Debug_Model_RequestInfo $request
     * @return array
     */
    public function convertTimers(Sheep_Debug_Model_RequestInfo $request)
    {
        if (!$request->getTimers()) {
            return array();
        }

        $requestData = array();
        $requestData['id'] = $request->getToken();
        $requestData['left'] = 0;
        $requestData['events'] = array();

        $currentTime = 0;
        /**  timer is an array defined by @see Varien_Profiler */
        foreach ($request->getTimers() as $name => $timer) {

            if (!$timer['sum']) {
                continue;
            }

            // convert seconds into microseconds
            $timer['sum'] *= 1000;

            $category = $this->getCategory($name);

            // we don't have start time, end time or multiple periods
            // so built an estimation of how this event might look assuming that we have only one
            // occurrence (period) and that it followed immediately after last event
            $requestData['events'][] = array(
                'name'      => $name,
                'category'  => $category,
                'origin'    => 0,
                'starttime' => $currentTime,
                'endtime'   => $currentTime + $timer['sum'],
                'duration'  => $timer['sum'],
                'memory'    => round($timer['realmem'] / pow(2014, 2), 2),
                'periods'   => array(
                    array('start' => $currentTime, 'end' => $currentTime + $timer['sum'])
                )
            );

            // we assume certain events contain the other events (most of the time it makes sense)
            if ($category != self::SECTION) {
                $currentTime += $timer['sum'];
            }
        }

        $data = array();
        $data['max'] = $currentTime;
        $data['requests'] = array($requestData);

        return $data;
    }

}


<?php

/**
 * Class Sheep_Debug_UtilController
 *
 * @category Sheep
 * @package  Sheep_Debug
 * @license  Copyright: Pirate Sheep, 2016
 * @link     https://piratesheep.com
 */
class Sheep_Debug_UtilController extends Sheep_Debug_Controller_Front_Action
{

    /**
     * Search grouped class
     */
    public function searchGroupClassAction()
    {
        if (!$this->getRequest()->isPost()) {
            $this->getResponse()->setHttpResponseCode(405);
            return;
        }

        $uri = (string)$this->getRequest()->getPost('uri');
        $groupType = $this->getRequest()->getPost('group');

        $groupTypes = array($groupType);
        if ($groupType == 'all') {
            $groupTypes = array('model', 'block', 'helper');
        }

        $items = array();

        if ($uri) {
            foreach ($groupTypes as $type) {
                $items[$type]['class'] = Mage::getConfig()->getGroupedClassName($type, $uri);
                $items[$type]['filepath'] = mageFindClassFile($items[$type]['class']);
            }

            $block = $this->getLayout()->createBlock('sheep_debug/array');
            $block->setTemplate('sheep_debug/grouped_class_search.phtml');
            $block->assign('items', $items);
            $this->getResponse()->setBody($block->toHtml());
        } else {
            $this->getResponse()->setBody($this->__('Please fill in a search query'));
        }
    }

    /**
     * Flushes cache
     */
    public function flushCacheAction()
    {
        try {
            $this->getService()->flushCache();
            $this->getSession()->addSuccess('Cache flushed.');
        } catch (Exception $e) {
            $message = $this->__('Cache cannot be flushed: %s', $e->getMessage());
            $this->getSession()->addError($message);
        }

        $this->_redirectReferer();
    }

    /**
     * Enables Full Page Cache Debug
     */
    public function enableFPCDebugAction()
    {
        try {
            $this->getService()->setFPCDebug(1);
            $this->getService()->flushCache();

            $message = $this->__('FPC debug was enabled');
            $this->getSession()->addSuccess($message);
        } catch (Exception $e) {
            $message = $this->__('FPC debug cannot be enabled: %s', $e->getMessage());
            $this->getSession()->addError($message);
        }

        $this->_redirectReferer();
    }

    /**
     * Disables Full Page Cache Debug
     */
    public function disableFPCDebugAction()
    {
        try {
            $this->getService()->setFPCDebug(0);
            $this->getService()->flushCache();

            $message = $this->__('FPC debug was disabled');
            $this->getSession()->addSuccess($message);
        } catch (Exception $e) {
            $message = $this->__('FPC debug cannot be disabled: %s', $e->getMessage());
            $this->getSession()->addError($message);
        }

        $this->_redirectReferer();
    }

    /**
     * Enables template Hints
     */
    public function enableTemplateHintsAction()
    {
        try {
            $this->getService()->setTemplateHints(1);
            $this->getService()->flushCache();
            // no need to notify customer - it's obvious if they were enabled

        } catch (Exception $e) {
            $message = $this->__('Template hints cannot be enabled: %s', $e->getMessage());
            $this->getSession()->addError($message);
        }

        $this->_redirectReferer();
    }

    /**
     * Disable template hints
     */
    public function disableTemplateHintsAction()
    {
        try {
            $this->getService()->setTemplateHints(0);
            $this->getService()->flushCache();

        } catch (Exception $e) {
            $message = $this->__('Template hints cannot be disabled: %s', $e->getMessage());
            $this->getSession()->addError($message);
        }

        $this->_redirectReferer();
    }

    /**
     * Enable inline translation
     */
    public function enableTranslateAction()
    {
        try {
            $this->getService()->setTranslateInline(1);
            $this->getService()->flushCache();
        } catch (Exception $e) {
            $message = $this->__('Translate inline cannot be enabled: %s', $e->getMessage());
            $this->getSession()->addError($message);
        }

        $this->_redirectReferer();
    }

    /**
     * Disables inline translation
     */
    public function disableTranslateAction()
    {
        try {
            $this->getService()->setTranslateInline(0);
            $this->getService()->flushCache();

        } catch (Exception $e) {
            $message = $this->__('Translate inline cannot be disabled: %s', $e->getMessage());
            $this->getSession()->addError($message);
        }

        $this->_redirectReferer();
    }

}


<?php

/**
 * Class Sheep_Debug_EmailController
 *
 * @category Sheep
 * @package  Sheep_Debug
 * @license  Copyright: Pirate Sheep, 2016
 * @link     https://piratesheep.com
 */
class Sheep_Debug_EmailController extends Sheep_Debug_Controller_Front_Action
{

    /**
     * E-mail body action
     */
    public function getBodyAction()
    {
        if ($email = $this->_initEmail()) {
            $this->getResponse()->setHeader('Content-Type', $email->getIsPlain() ? 'text/plain' : 'text/html');
            $this->getResponse()->setBody($email->getBody());
        }
    }

    /**
     * Returns query references in request parameters
     *
     * @return Sheep_Debug_Model_Email
     */
    protected function _initEmail()
    {
        $token = $this->getRequest()->getParam('token');
        $index = $this->getRequest()->getParam('index');

        if ($token === null || $index === null) {
            $this->getResponse()->setHttpResponseCode(400)->setBody('Invalid parameters');
            return null;
        }

        /** @var Sheep_Debug_Model_RequestInfo $requestProfile */
        $requestProfile = Mage::getModel('sheep_debug/requestInfo')->load($token, 'token');
        if (!$requestProfile->getId()) {
            $this->getResponse()->setHttpResponseCode(404)->setBody('Request profile not found');
            return null;
        }

        $emails = $requestProfile->getEmails();
        if (!$emails || !($index < count($emails))) {
            $this->getResponse()->setHttpResponseCode(404)->setBody('E-mail not found');
            return null;
        }

        return $emails[(int)$index];
    }

}


<?php

/**
 * Class Sheep_Debug_Model_Observer
 *
 * @category Sheep
 * @package  Sheep_Debug
 * @license  Copyright: Pirate Sheep, 2016
 * @link     https://piratesheep.com
 *
 *
 * TODO: clarify stages where request info's data is updated and when should be changed..
 * TODO: what do we generate when saving is not enabled !? Do we use persist data in cache for 30-60 minutes?
 *
 * 1. is_started is set to True only if
 *      - canCapture is True (module is enabled)
 *      - this execution context can be profiled (request not blacklisted, etc)
 * 1. request info is updated via its event observer and manually via updateProfile()
 *      - canCapture is true
 * 2. request info can be saved only if
 *      - is_started is True AND
 *      - canPersist is True
 */
class Sheep_Debug_Model_Observer
{
    // This can  proper initialised only after application config is loaded
    protected $canCapture = true;

    protected $requestInfo;

    /**
     * Returns current selected store
     *
     * @return Mage_Core_Model_Store
     */
    public function getCurrentStore()
    {
        return Mage::app()->getStore();
    }

    /**
     * Checks if we can start collection for current execution context
     *
     * @return bool
     */
    public function canCollect()
    {
        return $this->canCapture && php_sapi_name() != 'cli';
    }

    /**
     * Returns request info model associated to current request.
     *
     * @return Sheep_Debug_Model_RequestInfo
     */
    public function getRequestInfo()
    {
        if ($this->requestInfo === null) {
            $this->requestInfo = Mage::getModel('sheep_debug/requestInfo');
        }

        return $this->requestInfo;
    }

    /**
     * Called to mark that we can start profile execution of specified request
     *
     */
    public function startProfiling()
    {
        // Magento configuration is now available and we can init
        $this->canCapture = Mage::helper('sheep_debug')->canCapture();

        // Are we still allowed to collect
        if (!$this->canCapture) {
            return;
        }

        $requestInfo = $this->getRequestInfo();
        $requestInfo->setIsStarted(true);

        // Register shutdown function
        $this->registerShutdown();

        // Init profile
        $requestInfo->setStoreId($this->getCurrentStore()->getId());
        $requestInfo->setDate(date('Y-m-d H:i:s'));
        $requestInfo->initController();
        $requestInfo->initLogging();

        if (Mage::helper('sheep_debug')->canEnableVarienProfiler()) {
            Varien_Profiler::enable();
        }

        // use customer Zend Db Profiler that also records stack traces
        $stackTraceProfiler = Mage::getModel('sheep_debug/db_profiler');
        $stackTraceProfiler->setCaptureStacktraces(Mage::helper('sheep_debug')->canEnableSqlStacktrace());
        $stackTraceProfiler->replaceProfiler();
    }

    /**
     * Can be manually called to update current profile with data collected from (loggers, SQL Profiler, etC)
     * Executed after response is send to update profile with latest information
     */
    public function updateProfiling()
    {
        $requestInfo = $this->getRequestInfo();

        if (!$requestInfo->getIsStarted()) {
            return;
        }

        $helper = Mage::helper('sheep_debug');

        // update query information
        $requestInfo->initQueries();

        // capture log ranges
        $requestInfo->completeLogging();

        // update Magento session attributes
        $requestInfo->getController()->initFromSession();

        // save rendering time
        $requestInfo->setRenderingTime(Sheep_Debug_Model_Block::getTotalRenderingTime());

        $requestInfo->setPeakMemory($helper->getMemoryUsage());
        $requestInfo->setTime($helper->getCurrentScriptDuration());
        $requestInfo->setTimers(Varien_Profiler::getTimers());
        $requestInfo->setResponseCode(http_response_code());
    }

    /**
     * This represents a shutdown callback that allows us to safely save our request info
     */
    public function shutdown()
    {
        // We don't do anything during shutdown if profiling was not started
        if (!$this->getRequestInfo()->getIsStarted()) {
            return;
        }

        // Last time to update request profile information
        $this->updateProfiling();
        $this->saveProfiling();
    }

    /**
     * Saves request info model.
     *
     * @throws Exception
     */
    public function saveProfiling()
    {
        if (!$this->canCollect() || !Mage::helper('sheep_debug')->canPersist()) {
            return;
        }

        if (Mage::helper('sheep_debug')->hasDisablePersistenceCookie()) {
            return;
        }

        if (!$this->getRequestInfo()->getIsStarted()) {
            return;
        }

        $this->getRequestInfo()->save();
    }

    /**
     * Listens to controller_front_init_before event. An event that we can consider the start of HTTP request profiling.
     */
    public function onControllerFrontInitBefore()
    {
        $this->startProfiling();
    }

    /**
     * Listens to controller_action_predispatch event to capture request information
     *
     * @param Varien_Event_Observer $observer
     * @return void
     */
    public function onActionPreDispatch(Varien_Event_Observer $observer)
    {
        if (!$this->canCollect()) {
            return;
        }

        $action = $observer->getData('controller_action');

        // Record action that handled current request
        $this->getRequestInfo()->initController($action);
    }

    /**
     * Listens to controller_action_layout_generate_blocks_after and records
     * instantiated blocks
     *
     * @param Varien_Event_Observer $observer
     */
    public function onLayoutGenerate(Varien_Event_Observer $observer)
    {
        if (!$this->canCollect()) {
            return;
        }

        /** @var Mage_Core_Model_Layout $layout */
        $layout = $observer->getData('layout');
        $requestInfo = $this->getRequestInfo();

        // Adds block description for all blocks generated by layout
        $layoutBlocks = $layout->getAllBlocks();
        foreach ($layoutBlocks as $block) {
            if (!$this->canCaptureBlock($block)) {
                continue;
            }

            $requestInfo->addBlock($block);
        }

        // Update design information
        /** @var Mage_Core_Model_Design_Package $design */
        $design = Mage::getSingleton('core/design_package');
        $requestInfo->addLayout($layout, $design);

        // Save profiler information to get a generated token before rendering toolbar
        $this->saveProfiling();
    }

    /**
     * Listens to core_block_abstract_to_html_before event and records blocks that are about to be rendered.
     *
     * @param Varien_Event_Observer $observer
     */
    public function onBlockToHtml(Varien_Event_Observer $observer)
    {
        if (!$this->canCollect()) {
            return;
        }

        /* @var $block Mage_Core_Block_Abstract */
        $block = $observer->getData('block');

        // Last chance before rendering toolbar to fetch updates (queries triggered from blocks)
        if ($block->getNameInLayout() == 'debug_panels') {
            $this->updateProfiling();
        }

        if (!$this->canCaptureBlock($block)) {
            return;
        }

        $blockName = Mage::helper('sheep_debug')->getBlockName($block);

        $requestInfo = $this->getRequestInfo();

        try {
            $blockInfo = $requestInfo->getBlock($blockName);
        } catch (Exception $e) {
            // block was not found - lets add it now
            $blockInfo = $requestInfo->addBlock($block);
        }

        $blockInfo->startRendering($block);
    }

    /**
     * Listens to core_block_abstract_to_html_after event and computes time spent in block's _toHtml (rendering time).
     *
     * @param Varien_Event_Observer $observer
     */
    public function onBlockToHtmlAfter(Varien_Event_Observer $observer)
    {
        if (!$this->canCollect()) {
            return;
        }

        /* @var $block Mage_Core_Block_Abstract */
        $block = $observer->getData('block');

        // Don't list blocks from Debug module
        if (!$this->canCaptureBlock($block)) {
            return;
        }

        $blockInfo = $this->getRequestInfo()->getBlock($block->getNameInLayout());
        $blockInfo->completeRendering($block);
    }

    /**
     * Listens to controller_action_postdispatch event and captures route and controller
     * information.
     *
     * @param Varien_Event_Observer $observer
     */
    public function onActionPostDispatch(Varien_Event_Observer $observer)
    {
        if (!$this->canCollect()) {
            return;
        }

        /** @var Mage_Core_Controller_Varien_Action $action */
        $action = $observer->getData('controller_action');

        $this->getRequestInfo()->initController($action);
    }

    /**
     * Listens to core_collection_abstract_load_before and eav_collection_abstract_load_before events
     * and records loaded collections
     *
     * @param Varien_Event_Observer $observer
     */
    public function onCollectionLoad(Varien_Event_Observer $observer)
    {
        if (!$this->canCollect()) {
            return;
        }

        /** @var Mage_Core_Model_Resource_Db_Collection_Abstract */
        $collection = $observer->getData('collection');
        $this->getRequestInfo()->addCollection($collection);
    }

    /**
     * Listens to model_load_after and records loaded models
     *
     * @param Varien_Event_Observer $observer
     */
    public function onModelLoad(Varien_Event_Observer $observer)
    {
        if (!$this->canCollect()) {
            return;
        }

        $model = $observer->getData('object');
        $this->getRequestInfo()->addModel($model);
    }

    /**
     * Listens to controller_front_send_response_after. This event represents the end of a request.
     *
     * @param Varien_Event_Observer $observer
     */
    public function onControllerFrontSendResponseAfter(Varien_Event_Observer $observer)
    {
        if (!$this->canCollect()) {
            return;
        }

        /** @var Mage_Core_Controller_Varien_Front $front */
        $front = $observer->getData('front');

        $this->updateProfiling();
        $this->getRequestInfo()->getController()->addResponseInfo($front->getResponse());
    }

    /**
     * Disables website restriction module for requests handled by our module
     *
     * @param Varien_Event_Observer $observer
     */
    public function onWebsiteRestriction(Varien_Event_Observer $observer)
    {
        /** @var Mage_Core_Controller_Front_Action $controller */
        $controller = $observer->getController();
        /** @var Varien_Object $result */
        $result = $observer->getResult();

        $helper = Mage::helper('sheep_debug');
        if ($helper->canShowToolbar() && $controller instanceof Sheep_Debug_Controller_Front_Action) {
            $result->setShouldProceed(false);
        }

    }

    /**
     *
     * TODO: Make this a setting
     *
     * @return bool
     */
    public function canCaptureCoreBlocks()
    {
        return true;
    }

    /**
     * Logic that checks if we should capture specified block
     *
     * @param $block Mage_Core_Block_Abstract
     * @return bool
     */
    public function canCaptureBlock($block)
    {
        $blockClass = get_class($block);

        if (!$this->canCaptureCoreBlocks() && strpos($blockClass, 'Mage_') === 0) {
            return false;
        }

        // Don't capture debug blocks
        if (strpos($blockClass, 'Sheep_Debug_Block') > 0) {
            return false;
        }

        return true;
    }

    protected function registerShutdown()
    {
        register_shutdown_function(array($this, 'shutdown'));
    }

}


<?php

/**
 * Class Sheep_Debug_Model_Query
 *
 * @category Sheep
 * @package  Sheep_Debug
 * @license  Copyright: Pirate Sheep, 2016
 * @link     https://piratesheep.com
 */
class Sheep_Debug_Model_Query
{
    protected $queryType;
    protected $query;
    protected $queryParams;
    protected $elapsedSecs;
    protected $stacktrace;

    /**
     * Sheep_Debug_Model_Query constructor.
     *
     * @param Zend_Db_Profiler_Query $profilerQuery
     * @param string                 $stacktrace
     */
    public function init(Zend_Db_Profiler_Query $profilerQuery, $stacktrace = '')
    {
        $this->queryType = $profilerQuery->getQueryType();
        $this->query = $profilerQuery->getQuery();
        $this->queryParams = $profilerQuery->getQueryParams();
        $this->elapsedSecs = $profilerQuery->getElapsedSecs();
        $this->stacktrace = $stacktrace;
    }

    /**
     * Returns query type
     *
     * @return int
     */
    public function getQueryType()
    {
        return $this->queryType;
    }

    /**
     * Returns SQL query
     *
     * @return string
     */
    public function getQuery()
    {
        return $this->query;
    }

    /**
     * Returns SQL query parameters
     *
     * @return array
     */
    public function getQueryParams()
    {
        return $this->queryParams;
    }

    /**
     * Returns execution time in seconds
     *
     * @return false|float
     */
    public function getElapsedSecs()
    {
        return $this->elapsedSecs;
    }

    /**
     * @return string
     */
    public function getStacktrace()
    {
        return $this->stacktrace;
    }

}


<?php

trait Sheep_Debug_Model_Core_Email_Capture
{
    /**
     * Calls parent's real send method
     *
     * @return $this
     */
    public function parentSend()
    {
        return parent::send();
    }

    /**
     * Overwrites parent method to capture details for sent e-mail
     *
     * @return Sheep_Debug_Model_Core_Email
     */
    public function send()
    {
        try {
            $this->captureEmail();
        } catch (Exception $e) {
            Mage::logException($e);
        }

        return $this->parentSend();
    }

    /**
     * Adds e-mail information on current request profile info
     */
    public function captureEmail()
    {
        $email = Mage::getModel('sheep_debug/email');
        $email->setFromEmail($this->getFromEmail());
        $email->setFromName($this->getFromName());
        $email->setToEmail($this->getToEmail());
        $email->setToName($this->getToName());
        $email->setSubject($this->getSubject());
        $email->setIsPlain($this->getType() != 'html');
        $email->setBody($this->getBody());
        $email->setIsSmtpDisabled((bool)Mage::getStoreConfigFlag('system/smtp/disable'));
        $email->setIsAccepted(true);  // Assume e-mail is accepted

        $requestInfo = Mage::getSingleton('sheep_debug/observer')->getRequestInfo();
        $requestInfo->addEmail($email);
    }

}

/**
 * Class Sheep_Debug_Model_Core_Email rewrites core/email and overwrites send() to capture any e-mail information.
 *
 * @method string getType()
 * @method string getFromEmail()
 * @method string getFromName()
 * @method string getToEmail()
 * @method string getToName()
 *
 * @category Sheep
 * @package  Sheep_Debug
 * @license  Copyright: Pirate Sheep, 2016
 * @link     https://piratesheep.com
 */

if (Mage::helper('core')->isModuleEnabled('Aschroder_SMTPPro')) {
    class Sheep_Debug_Model_Core_Email extends Aschroder_SMTPPro_Model_Email
    {
        use Sheep_Debug_Model_Core_Email_Capture;
    }
} else {

    class Sheep_Debug_Model_Core_Email extends Mage_Core_Model_Email
    {
        use Sheep_Debug_Model_Core_Email_Capture;
    }
}


<?php

class Sheep_Debug_Model_Db_Profiler extends Zend_Db_Profiler
{
    protected $stackTraces = array();
    protected $captureStacktraces = false;

    /**
     * Responsible to copy queries from current profiler and set this instance sql profiler
     *
     * @throws Zend_Db_Profiler_Exception
     */
    public function replaceProfiler()
    {
        /** @var Magento_Db_Adapter_Pdo_Mysql $connection */
        $connection = Mage::getSingleton('core/resource')->getConnection('core_write');
        $currentProfile = $connection->getProfiler();

        if ($currentProfile) {
            // Copy queries
            $this->_queryProfiles = $currentProfile->_queryProfiles;
        }

        $this->setEnabled($currentProfile->getEnabled());
        $connection->setProfiler($this);
    }

    /**
     * @param $queryId
     * @return string
     * @throws Zend_Db_Profiler_Exception
     */
    public function parentQueryEnd($queryId)
    {
        return parent::queryEnd($queryId);
    }

    /**
     * Returns stack trace as array
     *
     * @return string
     */
    public function getStackTrace()
    {
        $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
        return array_slice($trace, 2);
    }

    /**
     * Calls parent implementation and saves stack trace
     *
     * @param int $queryId
     * @return string
     */
    public function queryEnd($queryId)
    {
        $result = $this->parentQueryEnd($queryId);

        if ($this->captureStacktraces) {
            $this->stackTraces[$queryId] = $this->getStackTrace();
        }

        return $result;
    }

    /**
     * Returns an array of SQL queries
     *
     * @return Sheep_Debug_Model_Query[]
     */
    public function getQueryModels()
    {
        $queries  = array();
        foreach ($this->_queryProfiles as $queryId => $queryProfile) {
            $queryModel = Mage::getModel('sheep_debug/query');
            $stacktrace = array_key_exists($queryId, $this->stackTraces) ? $this->stackTraces[$queryId] : '';
            $queryModel->init($queryProfile, $stacktrace);

            $queries[] = $queryModel;
        }

        return $queries;
    }

    /**
     * @param boolean $captureStacktraces
     */
    public function setCaptureStacktraces($captureStacktraces)
    {
        $this->captureStacktraces = $captureStacktraces;
    }

    /**
     * @return boolean
     */
    public function isCaptureStacktraces()
    {
        return $this->captureStacktraces;
    }

}