[
  {
    "path": "LICENSE.txt",
    "content": "\nOpen Software License (\"OSL\") v. 3.0\n\nThis Open Software License (the \"License\") applies to any original work of authorship (the \"Original Work\") whose owner (the \"Licensor\") has placed the following licensing notice adjacent to the copyright notice for the Original Work:\n\nLicensed under the Open Software License version 3.0\n\n   1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following:\n\n         1. to reproduce the Original Work in copies, either alone or as part of a collective work;\n\n         2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works (\"Derivative Works\") based upon the Original Work;\n\n         3. to distribute or communicate copies of the Original Work and Derivative Works to the public, with the proviso that copies of Original Work or Derivative Works that You distribute or communicate shall be licensed under this Open Software License;\n\n         4. to perform the Original Work publicly; and\n\n         5. to display the Original Work publicly. \n\n   2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works.\n\n   3. Grant of Source Code License. The term \"Source Code\" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work.\n\n   4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license.\n\n   5. External Deployment. The term \"External Deployment\" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c).\n\n   6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an \"Attribution Notice.\" You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work.\n\n   7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an \"AS IS\" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer.\n\n   8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation.\n\n   9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including 'fair use' or 'fair dealing'). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c).\n\n  10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware.\n\n  11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License.\n\n  12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License.\n\n  13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable.\n\n  14. Definition of \"You\" in This License. \"You\" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, \"You\" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, \"control\" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.\n\n  15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You.\n\n  16. Modification of This License. This License is Copyright (C) 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the \"Modified License\") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the \"Open Software License\" or \"OSL\" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice \"Licensed under <insert your license name here>\" or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process.\n"
  },
  {
    "path": "README.md",
    "content": "# Magento 2 Profiler Module #\n\n## Installation\n\nLog in to the Magento server, go to your Magento install dir and run these commands:\n```\ncomposer require mirasvit/module-profiler\n\nphp -f bin/magento module:enable Mirasvit_Profiler\nphp -f bin/magento setup:upgrade\nphp -f bin/magento mirasvit:profiler:enable\n```\n\n## Usage\n\n```\nphp -f bin/magento mirasvit:profiler:enable # Enable profiler\nphp -f bin/magento mirasvit:profiler:disable # Disable profiler\nphp -f bin/magento mirasvit:profiler:status # Current status\nphp -f bin/magento mirasvit:profiler:allow-ips 127.0.0.1 192.268.22.11 # Allow only specified IPs\nphp -f bin/magento mirasvit:profiler:allow-ips --none # Remove IP restriction\n```\n\n## Demo\n[http://profiler.m2.mirasvit.com/](http://profiler.m2.mirasvit.com/)\n\n[http://profiler.m2.mirasvit.com/profiler/profile/index/](http://profiler.m2.mirasvit.com/profiler/profile/index/)\n\n## Screenshots\n### Magento 2 Code Profiler and Database Profiler\n![](http://mirasvit.com/media/profiler/v2.png)\n\n\n## Licence\n[Open Software License (OSL 3.0)](http://opensource.org/licenses/osl-3.0.php)\n\n## 1.0.8\n*(2020-10-08)\n#### Improvements\n* M2.4\n\n---\n\n## 1.0.6\n*(2017-09-28)*\n\n#### Improvements\n* M2.2\n\n#### Fixed\n* Issue #20\n\n---\n\n## 1.0.5\n*(2017-09-07)* \n\n* PHP 5.6.x\n\n---\n\n## 1.0.3, 1.0.4\n*(2017-09-06)* \n\n* Issues with less compilation\n\n---\n\n## 1.0.2\n*(2017-09-05)* \n\n* Significant changes in UI\n\n---\n\n## 1.0.1\n*(2017-03-30)* \n\n* Improve styles load mechanism\n\n---\n\n## 1.0.0\n*(2017-03-30)* \n\n* Initial release\n"
  },
  {
    "path": "composer.json",
    "content": "{\n  \"name\": \"mirasvit/module-profiler\",\n  \"description\": \"Magento 2 Profiler\",\n  \"require\": {\n    \"magento/framework\": \"100.*|101.*|102.*|103.*\",\n    \"jdorn/sql-formatter\": \"^1.2\",\n    \"symfony/yaml\": \">=2.2.4\"\n  },\n  \"version\": \"1.0.8\",\n  \"type\": \"magento2-module\",\n  \"license\": [\n    \"proprietary\"\n  ],\n  \"autoload\": {\n    \"files\": [\n      \"./registration.php\"\n    ],\n    \"psr-4\": {\n      \"Mirasvit\\\\Profiler\\\\\": \"src/Profiler\"\n    }\n  }\n}\n"
  },
  {
    "path": "registration.php",
    "content": "<?php\n$_SERVER['MAGE_PROFILER_STAT'] = new \\Magento\\Framework\\Profiler\\Driver\\Standard\\Stat();\n\n$canEnable = defined('BP');\n\nif (PHP_SAPI == 'cli') {\n    global $argv;\n    if (isset($argv[1]) && substr($argv[1], 0, strlen('setup')) == 'setup') {\n        $canEnable = false;\n    }\n}\n\nif ($canEnable) {\n    \\Magento\\Framework\\Profiler::applyConfig([\n        'drivers' => [\n            [\n                'output' => 'Mirasvit\\Profiler\\Model\\Driver\\Output\\Html',\n                'stat'   => $_SERVER['MAGE_PROFILER_STAT'],\n            ],\n        ],\n    ], 'BP', false);\n}\n\n\\Magento\\Framework\\Component\\ComponentRegistrar::register(\n    \\Magento\\Framework\\Component\\ComponentRegistrar::MODULE,\n    'Mirasvit_Profiler',\n    __DIR__ . '/src/Profiler'\n);\n"
  },
  {
    "path": "src/Profiler/Api/Data/ProfileInterface.php",
    "content": "<?php\nnamespace Mirasvit\\Profiler\\Api\\Data;\n\ninterface ProfileInterface\n{\n    /**\n     * @return array\n     */\n    public function dump();\n}"
  },
  {
    "path": "src/Profiler/Block/Context.php",
    "content": "<?php\n\nnamespace Mirasvit\\Profiler\\Block;\n\nuse Magento\\Framework\\App\\RequestInterface;\nuse Magento\\Framework\\App\\ResourceConnection;\nuse Mirasvit\\Profiler\\Model\\Storage;\n\nclass Context\n{\n    private $request;\n\n    private $storage;\n\n    public function __construct(\n        RequestInterface $request,\n        Storage $storage\n    ) {\n        $this->request = $request;\n        $this->storage = $storage;\n    }\n\n    public function getProfile($id = false)\n    {\n        if (!$id) {\n            $id = $this->request->getParam('id');\n        }\n\n        return $this->storage->load($id);\n    }\n\n\n    //    /**\n    //     * @param \\Magento\\Framework\\Profiler\\Driver\\Standard\\Stat $stat\n    //     * @return $this\n    //     */\n    //    public function setProfilerStat($stat)\n    //    {\n    //        $this->profilerStat = $stat;\n    //\n    //        return $this;\n    //    }\n    //\n    //    /**\n    //     * @return \\Magento\\Framework\\Profiler\\Driver\\Standard\\Stat\n    //     */\n    //    public function getProfilerStat()\n    //    {\n    //        return $this->profilerStat;\n    //    }\n    //\n    //    /**\n    //     * @return \\Zend_Db_Profiler\n    //     */\n    //    public function getDbProfiler()\n    //    {\n    //        return $this->resourceConnection->getConnection('read')\n    //            ->getProfiler();\n    //    }\n}"
  },
  {
    "path": "src/Profiler/Block/Profile/Listing.php",
    "content": "<?php\n\nnamespace Mirasvit\\Profiler\\Block\\Profile;\n\nuse Magento\\Framework\\View\\Element\\Template\\Context as TemplateContext;\nuse Magento\\Framework\\View\\Element\\Template;\nuse Mirasvit\\Profiler\\Model\\Storage;\n\nclass Listing extends Template\n{\n    /**\n     * @var Storage\n     */\n    private $storage;\n\n    public function __construct(\n        Storage $storage,\n        Template\\Context $context,\n        array $data = []\n    ) {\n        $this->storage = $storage;\n\n        parent::__construct($context, $data);\n    }\n\n    public function getList()\n    {\n        return $this->storage->getList();\n    }\n}"
  },
  {
    "path": "src/Profiler/Block/Profile/View.php",
    "content": "<?php\n\nnamespace Mirasvit\\Profiler\\Block\\Profile;\n\nuse Magento\\Framework\\App\\RequestInterface;\nuse Magento\\Framework\\View\\Element\\Template;\nuse Mirasvit\\Profiler\\Block\\Tab\\TabInterface;\nuse Mirasvit\\Profiler\\Model\\Storage;\n\nclass View extends Template\n{\n    /**\n     * @var RequestInterface\n     */\n    private $request;\n\n    /**\n     * @var Storage\n     */\n    private $storage;\n\n    /**\n     * @var array\n     */\n    private $tabs;\n\n    public function __construct(\n        Storage $storage,\n        Template\\Context $context,\n        array $tabs = []\n    ) {\n        $this->request = $context->getRequest();\n        $this->storage = $storage;\n        $this->tabs = $tabs;\n\n        parent::__construct($context);\n    }\n\n    public function getProfile()\n    {\n        return $this->storage->load($this->getRequest()->getParam('id'));\n    }\n\n    /**\n     * @return TabInterface[]\n     */\n    public function getTabs()\n    {\n        return $this->tabs;\n    }\n}"
  },
  {
    "path": "src/Profiler/Block/Tab/Code.php",
    "content": "<?php\n\nnamespace Mirasvit\\Profiler\\Block\\Tab;\n\nuse Magento\\Framework\\View\\Element\\Template\\Context as TemplateContext;\nuse Magento\\Framework\\View\\Element\\Template;\nuse Mirasvit\\Profiler\\Block\\Context;\n\nclass Code extends Template implements TabInterface\n{\n    /**\n     * @var string\n     */\n    protected $_template = 'tab/code.phtml';\n\n    /**\n     * @var Context\n     */\n    protected $context;\n\n    public function __construct(\n        Context $context,\n        TemplateContext $templateContext,\n        array $data = []\n    ) {\n        $this->context = $context;\n\n        parent::__construct($templateContext, $data);\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function getLabel()\n    {\n        return 'Performance';\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function getIcon()\n    {\n        return 'clock-o';\n    }\n\n    /**\n     * @return array\n     */\n    public function getCodeDump()\n    {\n        return $this->context->getProfile()['code'];\n    }\n\n    public function getFlameGraphJson() {\n        $frameGraph = [\n            'name' => 'root',\n            'value' => $this->getGeneralDump()[\\Mirasvit\\Profiler\\Profile\\General::EXECUTION_TIME] / 1000,\n            'children' => [],\n        ];\n\n        foreach ($this->context->getProfile()['code'] as $path => $data) {\n            $data['value'] = $data['sum'];\n            $data['children'] = [];\n\n            $selectedNode = &$frameGraph;\n            foreach(explode('->', $path) as $node) {\n                $children = &$selectedNode['children'];\n                if (! isset($children[$node])) {\n                    $data['name'] = $node;\n                    $children[$node] = $data;\n                }\n                $selectedNode = &$children[$node];\n            }\n        }\n\n        return \\json_encode($this->_removeChildrenKeys($frameGraph));\n    }\n\n    private function _removeChildrenKeys($node) {\n        $node['children'] = array_values($node['children']);\n        foreach ($node['children'] as &$child) {\n            $child = $this->_removeChildrenKeys($child);\n        }\n        return $node;\n    }\n\n    /**\n     * @return array\n     */\n    public function getGeneralDump()\n    {\n        return $this->context->getProfile()['general'];\n    }\n\n    public function getLevel($timerId)\n    {\n        return substr_count($timerId, '->');\n    }\n\n    /**\n     * @param int $timerId\n     * @return string\n     */\n    public function renderTimerId($timerId)\n    {\n        $nestingSep = preg_quote('->', '/');\n\n        return preg_replace('/.+?' . $nestingSep . '/', '', $timerId);\n    }\n\n    /**\n     * @param int $timerId\n     * @return string\n     */\n    public function getParentTimerId($timerId)\n    {\n        $timerId = explode('->', $timerId);\n        array_pop($timerId);\n\n        return implode('->', $timerId);\n    }\n\n    /**\n     * @param int $timerId\n     * @return float\n     */\n    public function getTimerLength($timerId)\n    {\n        return 0;\n        $total = microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'];\n\n        return round($this->getStat()->fetch($timerId, 'sum') / $total * 100, 2);\n    }\n\n    /**\n     * @return float\n     */\n    public function getTotalTime()\n    {\n        return microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'];\n    }\n}"
  },
  {
    "path": "src/Profiler/Block/Tab/Database.php",
    "content": "<?php\n\nnamespace Mirasvit\\Profiler\\Block\\Tab;\n\nuse Magento\\Framework\\View\\Element\\Template\\Context as TemplateContext;\nuse Magento\\Framework\\View\\Element\\Template;\nuse Magento\\Framework\\App\\ResourceConnection;\nuse Mirasvit\\Profiler\\Block\\Context;\n\nclass Database extends Template implements TabInterface\n{\n    /**\n     * @var string\n     */\n    protected $_template = 'tab/database.phtml';\n\n    /**\n     * @var Context\n     */\n    private $context;\n\n    public function __construct(\n        Context $context,\n        TemplateContext $templateContext\n    ) {\n        $this->context = $context;\n\n        parent::__construct($templateContext);\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function getLabel()\n    {\n        return __('Database');\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function getIcon()\n    {\n        return 'database';\n    }\n\n    /**\n     * @return array\n     */\n    public function getDump()\n    {\n        return $this->context->getProfile()['database'];\n    }\n\n    /**\n     * @return array\n     */\n    public function getSlowQueries()\n    {\n        $queries = [];\n\n        /** @var  \\Zend_Db_Profiler_Query $query */\n        foreach ($this->getDbProfiler()->getQueryProfiles() as $queryId => $query) {\n            $queries[$queryId] = $query->getElapsedSecs();\n        }\n\n        arsort($queries);\n        $queries = array_slice($queries, 0, 5, true);\n\n        return $queries;\n    }\n}"
  },
  {
    "path": "src/Profiler/Block/Tab/IO.php",
    "content": "<?php\n\nnamespace Mirasvit\\Profiler\\Block\\Tab;\n\nuse Magento\\Framework\\View\\Element\\Template\\Context as TemplateContext;\nuse Magento\\Framework\\View\\Element\\Template;\nuse Mirasvit\\Profiler\\Block\\Context;\n\nclass IO extends Template implements TabInterface\n{\n    /**\n     * @var string\n     */\n    protected $_template = 'tab/io.phtml';\n\n    /**\n     * @var Context\n     */\n    protected $context;\n\n    public function __construct(\n        Context $context,\n        TemplateContext $templateContext,\n        array $data = []\n    ) {\n        $this->context = $context;\n\n        parent::__construct($templateContext, $data);\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function getLabel()\n    {\n        return 'Request / Response';\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function getIcon()\n    {\n        return 'globe';\n    }\n\n    /**\n     * @return array\n     */\n    public function getDump()\n    {\n        return $this->context->getProfile();\n    }\n}"
  },
  {
    "path": "src/Profiler/Block/Tab/TabInterface.php",
    "content": "<?php\nnamespace Mirasvit\\Profiler\\Block\\Tab;\n\ninterface TabInterface\n{\n    /**\n     * @return string\n     */\n    public function getIcon();\n\n    /**\n     * @return string\n     */\n    public function getLabel();\n}"
  },
  {
    "path": "src/Profiler/Block/Toolbar.php",
    "content": "<?php\n\nnamespace Mirasvit\\Profiler\\Block;\n\nuse Magento\\Framework\\View\\Element\\Template\\Context as TemplateContext;\nuse Magento\\Framework\\View\\Element\\Template;\nuse Magento\\Framework\\Url;\n\n/**\n * @method string getProfileId()\n */\nclass Toolbar extends Template\n{\n    /**\n     * @var string\n     */\n    protected $_template = 'toolbar.phtml';\n\n    /**\n     * @var Context\n     */\n    private $context;\n\n    /**\n     * @var Url\n     */\n    private $urlHelper;\n\n    public function __construct(\n        TemplateContext $templateContext,\n        Url $urlHelper,\n        Context $context\n    ) {\n        $this->context = $context;\n        $this->urlHelper = $urlHelper;\n\n        parent::__construct($templateContext);\n    }\n\n    /**\n     * @return array\n     */\n    public function getDump()\n    {\n        return $this->context->getProfile($this->getProfileId());\n    }\n\n    /**\n     * @return string\n     */\n    public function getProfileUrl()\n    {\n        return $this->urlHelper->getUrl('profiler/profile/view', ['id' => $this->getProfileId()]);\n    }\n}"
  },
  {
    "path": "src/Profiler/Console/Command/AbstractCommand.php",
    "content": "<?php\nnamespace Mirasvit\\Profiler\\Console\\Command;\n\nuse Magento\\Framework\\App\\DeploymentConfig;\nuse Magento\\Framework\\App\\State;\nuse Symfony\\Component\\Console\\Command\\Command;\n\nabstract class AbstractCommand extends Command\n{\n    /**\n     * App state\n     *\n     * @var \\Magento\\Framework\\App\\State\n     */\n    protected $appState;\n\n    /**\n     * {@inheritdoc}\n     *\n     * @param State $appState\n     */\n    public function __construct(\n        State $appState\n    ) {\n        $this->appState = $appState;\n\n        parent::__construct();\n    }\n}\n"
  },
  {
    "path": "src/Profiler/Console/Command/AllowIpsCommand.php",
    "content": "<?php\nnamespace Mirasvit\\Profiler\\Console\\Command;\n\nuse Magento\\Framework\\App\\State;\nuse Magento\\Framework\\Exception\\LocalizedException;\nuse Symfony\\Component\\Console\\Input\\InputInterface;\nuse Symfony\\Component\\Console\\Output\\OutputInterface;\nuse Mirasvit\\Profiler\\Model\\Config;\nuse Symfony\\Component\\Console\\Input\\InputArgument;\nuse Symfony\\Component\\Console\\Input\\InputOption;\n\nclass AllowIpsCommand extends AbstractCommand\n{\n    /**\n     * @var Config\n     */\n    protected $config;\n\n    public function __construct(\n        Config $config,\n        State $appState\n    ) {\n        $this->config = $config;\n\n        parent::__construct($appState);\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    protected function configure()\n    {\n        $arguments = [\n            new InputArgument(\n                'ip',\n                InputArgument::OPTIONAL | InputArgument::IS_ARRAY,\n                'Allowed IP addresses'\n            ),\n        ];\n        $options = [\n            new InputOption(\n                'none',\n                null,\n                InputOption::VALUE_NONE,\n                'Clear allowed IP addresses'\n            ),\n        ];\n\n        $this->setName('mirasvit:profiler:allow-ips')\n            ->setDescription('Enable profiler only for specified IPs')\n            ->setDefinition(array_merge($arguments, $options));\n\n        parent::configure();\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    protected function execute(InputInterface $input, OutputInterface $output)\n    {\n        try{\n            $this->appState->setAreaCode('empty');\n        }catch (LocalizedException $e){}\n\n        if (!$input->getOption('none')) {\n            $addresses = $input->getArgument('ip');\n\n            if (!empty($addresses)) {\n                $this->config->setAddresses(implode(',', $addresses));\n                $output->writeln(\n                    '<info>Set exempt IP-addresses: ' . implode(', ', $this->config->getAddressInfo()) .\n                    '</info>'\n                );\n            }\n        } else {\n            $this->config->setAddresses('');\n            $output->writeln('<info>Set exempt IP-addresses: none</info>');\n        }\n    }\n}\n"
  },
  {
    "path": "src/Profiler/Console/Command/DisableCommand.php",
    "content": "<?php\n\nnamespace Mirasvit\\Profiler\\Console\\Command;\n\nuse Magento\\Framework\\App\\State;\nuse Magento\\Framework\\Exception\\LocalizedException;\nuse Symfony\\Component\\Console\\Input\\InputInterface;\nuse Symfony\\Component\\Console\\Output\\OutputInterface;\nuse Mirasvit\\Profiler\\Model\\Config;\n\nclass DisableCommand extends AbstractCommand\n{\n    /**\n     * @var Config\n     */\n    private $config;\n\n    public function __construct(\n        Config $config,\n        State $appState\n    ) {\n        $this->config = $config;\n\n        parent::__construct($appState);\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    protected function configure()\n    {\n        $this->setName('mirasvit:profiler:disable')\n            ->setDescription('Disable profiler')\n            ->setDefinition([]);\n\n        parent::configure();\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    protected function execute(InputInterface $input, OutputInterface $output)\n    {\n        try {\n            $this->appState->setAreaCode('empty');\n        } catch (LocalizedException $e) {\n        }\n\n        $this->config->disableProfiler();\n\n        $output->writeln('<info>Status: ' . ($this->config->isEnabled() ? 'Enabled' : 'Disabled') . '</info>');\n    }\n}\n"
  },
  {
    "path": "src/Profiler/Console/Command/EnableCommand.php",
    "content": "<?php\n\nnamespace Mirasvit\\Profiler\\Console\\Command;\n\nuse Magento\\Framework\\App\\State;\nuse Magento\\Framework\\Exception\\LocalizedException;\nuse Symfony\\Component\\Console\\Input\\InputInterface;\nuse Symfony\\Component\\Console\\Output\\OutputInterface;\nuse Mirasvit\\Profiler\\Model\\Config;\n\nclass EnableCommand extends AbstractCommand\n{\n    /**\n     * @var Config\n     */\n    private $config;\n\n    public function __construct(\n        Config $config,\n        State $appState\n    ) {\n        $this->config = $config;\n\n        parent::__construct($appState);\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    protected function configure()\n    {\n        $this->setName('mirasvit:profiler:enable')\n            ->setDescription('Enable profiler')\n            ->setDefinition([]);\n\n        parent::configure();\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    protected function execute(InputInterface $input, OutputInterface $output)\n    {\n        try {\n            $this->appState->setAreaCode('empty');\n        } catch (LocalizedException $e) {\n        }\n\n        $this->config->enableProfiler();\n\n        $output->writeln('<info>Status: ' . ($this->config->isEnabled() ? 'Enabled' : 'Disabled') . '</info>');\n    }\n}\n"
  },
  {
    "path": "src/Profiler/Console/Command/StatusCommand.php",
    "content": "<?php\nnamespace Mirasvit\\Profiler\\Console\\Command;\n\nuse Magento\\Framework\\App\\State;\nuse Magento\\Framework\\Exception\\LocalizedException;\nuse Symfony\\Component\\Console\\Input\\InputInterface;\nuse Symfony\\Component\\Console\\Output\\OutputInterface;\nuse Mirasvit\\Profiler\\Model\\Config;\nuse Symfony\\Component\\Console\\Input\\InputArgument;\nuse Symfony\\Component\\Console\\Input\\InputOption;\n\nclass StatusCommand extends AbstractCommand\n{\n    /**\n     * @var Config\n     */\n    private $config;\n\n    public function __construct(\n        Config $config,\n        State $appState\n    ) {\n        $this->config = $config;\n\n        parent::__construct($appState);\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    protected function configure()\n    {\n        $this->setName('mirasvit:profiler:status')\n            ->setDescription('Profiler status')\n            ->setDefinition([]);\n\n        parent::configure();\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    protected function execute(InputInterface $input, OutputInterface $output)\n    {\n        try{\n            $this->appState->setAreaCode('empty');\n        }catch (LocalizedException $e){}\n\n        $output->writeln('<info>Status: ' . ($this->config->isEnabled() ? 'Enabled' : 'Disabled') . '</info>');\n        $output->writeln('<info>IPs: ' . implode(', ', $this->config->getAddressInfo()) . '</info>');\n    }\n}\n"
  },
  {
    "path": "src/Profiler/Controller/Profile/Index.php",
    "content": "<?php\nnamespace Mirasvit\\Profiler\\Controller\\Profile;\n\nuse Mirasvit\\Profiler\\Controller\\Profile;\nuse Magento\\Framework\\Controller\\ResultFactory;\n\nclass Index extends Profile\n{\n    public function execute()\n    {\n        /** @var \\Magento\\Framework\\View\\Result\\Page $resultPage */\n        $resultPage = $this->resultFactory->create(ResultFactory::TYPE_PAGE, [\n            'template' => 'Mirasvit_Profiler::root.phtml',\n        ]);\n\n        $resultPage->getConfig()->getTitle()->set(__('Profiler'));\n\n        return $resultPage;\n    }\n}"
  },
  {
    "path": "src/Profiler/Controller/Profile/View.php",
    "content": "<?php\n\nnamespace Mirasvit\\Profiler\\Controller\\Profile;\n\nuse Mirasvit\\Profiler\\Controller\\Profile;\nuse Magento\\Framework\\Controller\\ResultFactory;\n\nclass View extends Profile\n{\n    public function execute()\n    {\n        /** @var \\Magento\\Framework\\View\\Result\\Page $resultPage */\n        $resultPage = $this->resultFactory->create(ResultFactory::TYPE_PAGE, [\n            'template' => 'Mirasvit_Profiler::root.phtml',\n        ]);\n\n        $resultPage->getConfig()->getTitle()->set(__('Profiler'));\n\n        return $resultPage;\n    }\n}"
  },
  {
    "path": "src/Profiler/Controller/Profile.php",
    "content": "<?php\n\nnamespace Mirasvit\\Profiler\\Controller;\n\nuse Magento\\Framework\\App\\Action\\Action;\nuse Magento\\Framework\\App\\Action\\Context;\n\nabstract class Profile extends Action\n{\n    public function __construct(\n        Context $context\n    ) {\n\n        parent::__construct($context);\n    }\n}\n"
  },
  {
    "path": "src/Profiler/Helper/Format.php",
    "content": "<?php\n\nnamespace Mirasvit\\Profiler\\Helper;\n\nuse Magento\\Framework\\App\\Helper\\AbstractHelper;\n\nclass Format extends AbstractHelper\n{\n    /**\n     * @param float $number\n     * @param bool $isSeconds\n     * @return string\n     */\n    public function formatTime($number, $isSeconds = false)\n    {\n        if ($isSeconds) {\n            $number *= 1000;\n        }\n\n        return number_format($number, 1, '.', ' ');\n    }\n\n    /**\n     * @param array|object $any\n     * @return string\n     */\n    public function any($any)\n    {\n        if (is_array($any)) {\n            if (count($any)) {\n                return '<pre>' . print_r($any, true) . '</pre>';\n            }\n        } else {\n            return json_encode($any);\n        }\n\n        return '';\n    }\n}"
  },
  {
    "path": "src/Profiler/Model/Config.php",
    "content": "<?php\nnamespace Mirasvit\\Profiler\\Model;\n\nuse Magento\\Framework\\App\\Config\\ScopeConfigInterface;\nuse Magento\\Framework\\App\\DeploymentConfig\\Writer as DeploymentConfigWriter;\nuse Magento\\Framework\\App\\DeploymentConfig\\Reader as DeploymentConfigReader;\nuse Magento\\Framework\\Config\\File\\ConfigFilePool;\nuse Magento\\Config\\Model\\Config\\Factory as ConfigFactory;\nuse Magento\\Framework\\App\\Filesystem\\DirectoryList;\n\nclass Config\n{\n    CONST HTACCESS_ENV = 'SetEnv MAGE_PROFILER Mirasvit\\Profiler\\Model\\Driver\\Standard\\Output\\Html';\n\n    /**\n     * @var ScopeConfigInterface\n     */\n    private $scopeConfig;\n\n    /**\n     * @var DeploymentConfigWriter\n     */\n    private $deploymentConfigWriter;\n\n    /**\n     * @var DeploymentConfigWriter\n     */\n    private $deploymentConfigReader;\n\n    /**\n     * @var ConfigFactory\n     */\n    private $configFactory;\n\n    /**\n     * @var DirectoryList\n     */\n    private $directoryList;\n\n    public function __construct(\n        DeploymentConfigWriter $deploymentConfigWriter,\n        DeploymentConfigReader $deploymentConfigReader,\n        ScopeConfigInterface $scopeConfig,\n        ConfigFactory $configFactory,\n        DirectoryList $directoryList\n    ) {\n        $this->deploymentConfigWriter = $deploymentConfigWriter;\n        $this->deploymentConfigReader = $deploymentConfigReader;\n        $this->scopeConfig = $scopeConfig;\n        $this->configFactory = $configFactory;\n        $this->directoryList = $directoryList;\n    }\n\n    /**\n     * @return bool\n     */\n    public function isEnabled()\n    {\n        return (bool)$this->scopeConfig->getValue('profiler/general/enable');\n    }\n\n    /**\n     * @return bool\n     */\n    public function enableProfiler()\n    {\n        $config = $this->configFactory->create();\n        $config->setDataByPath('profiler/general/enable', true);\n        $config->save();\n\n        $this->enableDbProfiler();\n\n        return true;\n    }\n\n    /**\n     * @return bool\n     */\n    public function disableProfiler()\n    {\n        $config = $this->configFactory->create();\n        $config->setDataByPath('profiler/general/enable', false);\n        $config->save();\n\n        $this->disableDbProfiler();\n\n        return true;\n    }\n\n    /**\n     * @return bool\n     * @throws \\Exception\n     */\n    public function enableDbProfiler()\n    {\n        $env = $this->deploymentConfigReader->load(ConfigFilePool::APP_ENV);\n\n        $env['db']['connection']['default']['profiler'] = [\n            'class'   => '\\\\Magento\\\\Framework\\\\DB\\\\Profiler',\n            'enabled' => true,\n        ];\n        $this->deploymentConfigWriter->saveConfig([ConfigFilePool::APP_ENV => $env], true);\n\n        return true;\n    }\n\n    /**\n     * @return bool\n     * @throws \\Exception\n     */\n    public function disableDbProfiler()\n    {\n        $env = $this->deploymentConfigReader->load(ConfigFilePool::APP_ENV);\n\n        unset($env['db']['connection']['default']['profiler']);\n\n        $this->deploymentConfigWriter->saveConfig([ConfigFilePool::APP_ENV => $env], true);\n\n        return true;\n    }\n\n    /**\n     * @param string $addresses\n     * @return bool\n     */\n    public function setAddresses($addresses)\n    {\n        $config = $this->configFactory->create();\n        $config->setDataByPath('profiler/general/addresses', $addresses);\n        $config->save();\n\n        return true;\n    }\n\n    /**\n     * @return array\n     */\n    public function getAddressInfo()\n    {\n        $addresses = $this->scopeConfig->getValue('profiler/general/addresses');\n\n        return array_filter(explode(',', $addresses));\n    }\n\n    public function getDumpPath()\n    {\n        $path = $this->directoryList->getPath('var').'/profiler/';\n        if (!file_exists($path)) {\n            mkdir($path);\n        }\n\n        return $path;\n    }\n}"
  },
  {
    "path": "src/Profiler/Model/Driver/Output/Html.php",
    "content": "<?php\n\nnamespace Mirasvit\\Profiler\\Model\\Driver\\Output;\n\nuse Magento\\Framework\\Profiler\\Driver\\Standard\\Stat;\nuse Magento\\Framework\\Profiler\\Driver\\Standard\\OutputInterface;\nuse Magento\\Framework\\App\\ObjectManager;\nuse Mirasvit\\Profiler\\Model\\Storage;\n\nclass Html implements OutputInterface\n{\n    /**\n     * {@inheritdoc}\n     */\n    public function display(Stat $stat)\n    {\n        $objectManager = ObjectManager::getInstance();\n\n        /** @var \\Mirasvit\\Profiler\\Model\\Config $config */\n        $config = $objectManager->get('Mirasvit\\Profiler\\Model\\Config');\n\n        if (!$config->isEnabled()) {\n            return;\n        }\n\n        $addresses = $config->getAddressInfo();\n\n        if (count($addresses) && isset($_SERVER['REMOTE_ADDR']) && !in_array($_SERVER['REMOTE_ADDR'], $addresses)) {\n            return;\n        }\n\n        if (isset($_SERVER['REQUEST_URI']) && strpos($_SERVER['REQUEST_URI'], 'profiler') !== false) {\n            return;\n        }\n        \n        /** @var \\Mirasvit\\Profiler\\Model\\Storage $storage */\n        $storage = $objectManager->get('Mirasvit\\Profiler\\Model\\Storage');\n\n        $profileId = $storage->dump();\n\n        $isAjax = !empty($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest';\n\n        if (!$isAjax && PHP_SAPI != 'cli' && strpos($_SERVER['HTTP_ACCEPT'], 'text/html') !== false) {\n            /** @var \\Magento\\Framework\\View\\LayoutInterface $layout */\n            $layout = $objectManager->create('Magento\\Framework\\View\\LayoutInterface');\n\n            echo $layout->createBlock('Mirasvit\\Profiler\\Block\\Toolbar')\n                ->setProfileId($profileId)\n                ->toHtml();\n        }\n    }\n}\n"
  },
  {
    "path": "src/Profiler/Model/Storage.php",
    "content": "<?php\n\nnamespace Mirasvit\\Profiler\\Model;\n\nuse Mirasvit\\Profiler\\Profile\\Pool;\nuse Symfony\\Component\\Yaml\\Dumper as YamlDumper;\nuse Symfony\\Component\\Yaml\\Parser as YamlParser;\n\nclass Storage\n{\n    /**\n     * @var Pool\n     */\n    private $pool;\n\n    /**\n     * @var Config\n     */\n    private $config;\n\n    public function __construct(\n        Pool $profilePool,\n        Config $config\n    ) {\n        $this->pool = $profilePool;\n        $this->config = $config;\n    }\n\n    /**\n     * @return string\n     */\n    public function dump()\n    {\n        $dump = [];\n\n        foreach ($this->pool->getProfiles() as $code => $profile) {\n            $dump[$code] = $profile->dump();\n        }\n        if (!isset($dump['meta'])) {\n            return false;\n        }\n        $meta = $dump['meta'];\n\n        $dt = (\\DateTime::createFromFormat('U.u', microtime(true)));\n        $name = $dt->format('Y-m-d H:i:s.u');\n\n        $file = $this->config->getDumpPath() . $name . '.meta';\n        file_put_contents($file, (new YamlDumper())->dump($meta, 10));\n\n        $file = $this->config->getDumpPath() . $name . '.prof';\n        file_put_contents($file, (new YamlDumper())->dump($dump, 10));\n\n        $this->cleanup();\n\n        return $name;\n    }\n\n    /**\n     * @return array\n     */\n    public function load($file)\n    {\n        $content = file_get_contents($this->config->getDumpPath() . '/' . $file . '.prof');\n        $dump = (new YamlParser())->parse($content);\n\n        return $dump;\n    }\n\n    public function getList()\n    {\n        $result = [];\n        $files = scandir($this->config->getDumpPath(), 1);\n\n        foreach ($files as $file) {\n            if ($file[0] == '.') {\n                continue;\n            }\n\n            if (pathinfo($file)['extension'] != 'meta') {\n                continue;\n            }\n\n            $content = file_get_contents($this->config->getDumpPath() . '/' . $file);\n            $meta = (new YamlParser())->parse($content);\n\n            $meta['ID'] = pathinfo($file)['filename'];\n\n            $result[] = $meta;\n        }\n\n        return $result;\n    }\n\n    public function cleanup()\n    {\n        $limit = 100;\n        $files = scandir($this->config->getDumpPath(), 1);\n\n        foreach ($files as $file) {\n            if ($file[0] == '.') {\n                continue;\n            }\n\n            if (pathinfo($file)['extension'] != 'meta') {\n                continue;\n            }\n\n            $limit--;\n\n            if ($limit < 0) {\n                $name = pathinfo($file)['filename'];\n                @unlink($this->config->getDumpPath() . '/' . $name . '.meta');\n                @unlink($this->config->getDumpPath() . '/' . $name . '.prof');\n            }\n        }\n    }\n}"
  },
  {
    "path": "src/Profiler/Profile/Code.php",
    "content": "<?php\n\nnamespace Mirasvit\\Profiler\\Profile;\n\nuse Mirasvit\\Profiler\\Api\\Data\\ProfileInterface;\n\nclass Code implements ProfileInterface\n{\n    /**\n     * {@inheritdoc}\n     */\n    public function dump()\n    {\n        $dump = [];\n\n        foreach ($this->getStat()->getFilteredTimerIds() as $timerId) {\n            $dump[$timerId] = $this->getStat()->get($timerId);\n        }\n\n        return $dump;\n    }\n\n    /**\n     * @return \\Magento\\Framework\\Profiler\\Driver\\Standard\\Stat\n     */\n    private function getStat()\n    {\n        return $_SERVER['MAGE_PROFILER_STAT'];\n    }\n}"
  },
  {
    "path": "src/Profiler/Profile/Context.php",
    "content": "<?php\n\nnamespace Mirasvit\\Profiler\\Profile;\n\nclass Context\n{\n\n    /**\n     * @return float\n     */\n    public function getExecutionTime()\n    {\n        return (microtime(true) - $_SERVER['REQUEST_TIME_FLOAT']) * 1000;\n    }\n\n    /**\n     * @return string|false\n     */\n    public function getClientIP()\n    {\n        if (isset($_SERVER['REMOTE_ADDR'])) {\n            return $_SERVER['REMOTE_ADDR'];\n        }\n\n        return false;\n    }\n\n    /**\n     * @return string|false\n     */\n    public function getURI()\n    {\n        if (isset($_SERVER['REQUEST_URI'])) {\n            return $_SERVER['REQUEST_URI'];\n        }\n\n        return false;\n    }\n\n    /**\n     * @return bool\n     */\n    public function isCLI()\n    {\n        return PHP_SAPI == 'cli';\n    }\n\n    /**\n     * @return false|string\n     */\n    public function getCliArgs()\n    {\n        if ($this->isCLI()) {\n            global $argv;\n            return implode(' ', $argv);\n        }\n\n        return false;\n    }\n}"
  },
  {
    "path": "src/Profiler/Profile/Database.php",
    "content": "<?php\n\nnamespace Mirasvit\\Profiler\\Profile;\n\nuse Magento\\Framework\\App\\ResourceConnection;\nuse Mirasvit\\Profiler\\Api\\Data\\ProfileInterface;\n\nclass Database implements ProfileInterface\n{\n    const TOTAL_ELAPSED = 'totalElapsedSecs';\n    const TOTAL_QUERIES = 'totalNumQueries';\n\n    const QUERY = 'query';\n    const QUERY_TYPE = 'queryType';\n    const QUERY_PARAMS = 'queryParams';\n    const QUERY_ELAPSED = 'elapsedSecs';\n    const QUERY_STARTED = 'startedMicrotime';\n    const QUERY_COUNT = 'queryCount';\n\n    /**\n     * @var ResourceConnection\n     */\n    private $resourceConnection;\n\n    public function __construct(\n        ResourceConnection $resourceConnection\n    ) {\n        $this->resourceConnection = $resourceConnection;\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function dump()\n    {\n        $queryCountBucket = [];\n        $dump = [\n            self::TOTAL_ELAPSED => $this->getProfiler()->getTotalElapsedSecs() * 1000,\n            self::TOTAL_QUERIES => $this->getProfiler()->getTotalNumQueries(),\n            'profiles'          => [],\n        ];\n\n        $profiles = $this->getProfiler()->getQueryProfiles();\n        if (is_array($profiles)) {\n            /** @var \\Zend_Db_Profiler_Query $profile */\n            foreach ($profiles as $profile) {\n                if (!isset($queryCountBucket[$profile->getQuery()])) {\n                    $queryCountBucket[$profile->getQuery()] = 0;\n                }\n\n                $queryCountBucket[$profile->getQuery()]++;\n\n                $dump['profiles'][] = [\n                    self::QUERY         => $profile->getQuery(),\n                    self::QUERY_TYPE    => $profile->getQueryType(),\n                    self::QUERY_PARAMS  => $profile->getQueryParams(),\n                    self::QUERY_ELAPSED => $profile->getElapsedSecs() * 1000,\n                    self::QUERY_STARTED => $profile->getStartedMicrotime(),\n                    self::QUERY_COUNT   => $queryCountBucket[$profile->getQuery()]\n                ];\n            }\n        }\n\n        return $dump;\n    }\n\n    /**\n     * @return \\Zend_Db_Profiler\n     */\n    private function getProfiler()\n    {\n        return $this->resourceConnection->getConnection('read')\n            ->getProfiler();\n    }\n}"
  },
  {
    "path": "src/Profiler/Profile/General.php",
    "content": "<?php\n\nnamespace Mirasvit\\Profiler\\Profile;\n\nuse Mirasvit\\Profiler\\Api\\Data\\ProfileInterface;\nuse Magento\\Framework\\App\\ResourceConnection;\n\nclass General implements ProfileInterface\n{\n    const EXECUTION_TIME = 'EXECUTION_TIME';\n    const IP = 'IP';\n    const URI = 'URI';\n    const IS_CLI = 'IS_CLI';\n    const CLI_ARGS = 'CLI_ARGS';\n    const DB_QUERIES = 'DB_QUERIES';\n    const DB_TIME = 'DB_TIME';\n    const POST = 'POST';\n    const GET = 'GET';\n\n    /**\n     * @var ResourceConnection\n     */\n    private $resourceConnection;\n\n    /**\n     * @var Context\n     */\n    private $context;\n\n    public function __construct(\n        ResourceConnection $resourceConnection,\n        Context $context\n    ) {\n        $this->resourceConnection = $resourceConnection;\n        $this->context = $context;\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function dump()\n    {\n        /** @var  \\Zend_Db_Profiler $db */\n        $db = $this->resourceConnection->getConnection('read')\n            ->getProfiler();\n\n        $dump = [\n            self::EXECUTION_TIME => $this->context->getExecutionTime(),\n            self::IP             => $this->context->getClientIP(),\n            self::URI            => $this->context->getURI(),\n            self::IS_CLI         => $this->context->isCLI(),\n            self::CLI_ARGS       => $this->context->getCliArgs(),\n            self::DB_QUERIES     => $db->getTotalNumQueries(),\n            self::DB_TIME        => round($db->getTotalElapsedSecs() * 1000),\n            self::GET            => isset($_GET) ? $_GET : [],\n            self::POST           => isset($_POST) ? $_POST : [],\n        ];\n\n\n        return $dump;\n    }\n\n}"
  },
  {
    "path": "src/Profiler/Profile/Meta.php",
    "content": "<?php\n\nnamespace Mirasvit\\Profiler\\Profile;\n\nuse Mirasvit\\Profiler\\Api\\Data\\ProfileInterface;\n\nclass Meta implements ProfileInterface\n{\n    const RESPONSE_CODE = 'RESPONSE_CODE';\n    const IP = 'IP';\n    const METHOD = 'METHOD';\n    const TIME = 'TIME';\n    const EXECUTION_TIME = 'EXECUTION_TIME';\n    const URL = 'URL';\n\n    private $context;\n\n    public function __construct(\n        Context $context\n    ) {\n        $this->context = $context;\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function dump()\n    {\n        if ($this->context->isCLI()) {\n            $url = $this->context->getCliArgs();\n        } else {\n            $url = $this->context->getURI();\n        }\n        $dump = [\n            'RESPONSE_CODE'  => http_response_code(),\n            'METHOD'         => $this->context->isCLI() ? 'CLI' : $_SERVER['REQUEST_METHOD'],\n            'TIME'           => microtime(true),\n            'EXECUTION_TIME' => $this->context->getExecutionTime(),\n            'URL'            => $url,\n            'IP'             => $this->context->getClientIP(),\n        ];\n\n        return $dump;\n    }\n}"
  },
  {
    "path": "src/Profiler/Profile/Pool.php",
    "content": "<?php\n\nnamespace Mirasvit\\Profiler\\Profile;\n\nuse Mirasvit\\Profiler\\Api\\Data\\ProfileInterface;\n\nclass Pool\n{\n    /**\n     * @var ProfileInterface[]\n     */\n    private $profiles;\n\n    public function __construct(\n        $profiles = []\n    ) {\n        $this->profiles = $profiles;\n    }\n\n    /**\n     * @return ProfileInterface[]\n     */\n    public function getProfiles()\n    {\n        return $this->profiles;\n    }\n}"
  },
  {
    "path": "src/Profiler/etc/di.xml",
    "content": "<config xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n        xsi:noNamespaceSchemaLocation=\"urn:magento:framework:ObjectManager/etc/config.xsd\">\n    <type name=\"Magento\\Framework\\Console\\CommandList\">\n        <arguments>\n            <argument name=\"commands\" xsi:type=\"array\">\n                <item name=\"ProfilerEnableCommand\" xsi:type=\"object\">Mirasvit\\Profiler\\Console\\Command\\EnableCommand</item>\n                <item name=\"ProfilerDisableCommand\" xsi:type=\"object\">Mirasvit\\Profiler\\Console\\Command\\DisableCommand</item>\n                <item name=\"ProfilerAllowIpsCommand\" xsi:type=\"object\">Mirasvit\\Profiler\\Console\\Command\\AllowIpsCommand</item>\n                <item name=\"ProfilerStatusCommand\" xsi:type=\"object\">Mirasvit\\Profiler\\Console\\Command\\StatusCommand</item>\n            </argument>\n        </arguments>\n    </type>\n\n    <type name=\"Mirasvit\\Profiler\\Block\\Profile\\View\">\n        <arguments>\n            <argument name=\"tabs\" xsi:type=\"array\">\n                <item name=\"ioTab\" xsi:type=\"object\">Mirasvit\\Profiler\\Block\\Tab\\IO</item>\n                <item name=\"codeTab\" xsi:type=\"object\">Mirasvit\\Profiler\\Block\\Tab\\Code</item>\n                <item name=\"databaseTab\" xsi:type=\"object\">Mirasvit\\Profiler\\Block\\Tab\\Database</item>\n            </argument>\n        </arguments>\n    </type>\n\n    <type name=\"Mirasvit\\Profiler\\Profile\\Pool\">\n        <arguments>\n            <argument name=\"profiles\" xsi:type=\"array\">\n                <item name=\"general\" xsi:type=\"object\">Mirasvit\\Profiler\\Profile\\General</item>\n                <item name=\"meta\" xsi:type=\"object\">Mirasvit\\Profiler\\Profile\\Meta</item>\n                <item name=\"code\" xsi:type=\"object\">Mirasvit\\Profiler\\Profile\\Code</item>\n                <item name=\"database\" xsi:type=\"object\">Mirasvit\\Profiler\\Profile\\Database</item>\n            </argument>\n        </arguments>\n    </type>\n</config>"
  },
  {
    "path": "src/Profiler/etc/frontend/routes.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<config xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n        xsi:noNamespaceSchemaLocation=\"urn:magento:framework:App/etc/routes.xsd\">\n    <router id=\"standard\">\n        <route id=\"profiler\" frontName=\"profiler\">\n            <module name=\"Mirasvit_Profiler\"/>\n        </route>\n    </router>\n</config>\n"
  },
  {
    "path": "src/Profiler/etc/module.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<config xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"../../../../../lib/internal/Magento/Framework/Module/etc/module.xsd\">\n    <module name=\"Mirasvit_Profiler\" setup_version=\"1.0.0\"/>\n</config>\n"
  },
  {
    "path": "src/Profiler/view/base/layout/profiler_profile_index.xml",
    "content": "<?xml version=\"1.0\"?>\n<page xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" layout=\"profiler\"\n      xsi:noNamespaceSchemaLocation=\"urn:magento:framework:View/Layout/etc/page_configuration.xsd\"\n      label=\"Profiler\">\n    <body>\n        <referenceContainer name=\"profiler\">\n            <block class=\"Mirasvit\\Profiler\\Block\\Profile\\Listing\" template=\"Mirasvit_Profiler::profile/listing.phtml\"\n                   cacheable=\"false\"/>\n        </referenceContainer>\n    </body>\n</page>\n"
  },
  {
    "path": "src/Profiler/view/base/layout/profiler_profile_view.xml",
    "content": "<?xml version=\"1.0\"?>\n<page xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" layout=\"profiler\"\n      xsi:noNamespaceSchemaLocation=\"urn:magento:framework:View/Layout/etc/page_configuration.xsd\"\n      label=\"Profiler\">\n    <body>\n        <referenceContainer name=\"profiler\">\n            <block class=\"Mirasvit\\Profiler\\Block\\Profile\\View\" template=\"Mirasvit_Profiler::profile/view.phtml\"\n                   cacheable=\"false\"/>\n        </referenceContainer>\n    </body>\n</page>\n"
  },
  {
    "path": "src/Profiler/view/base/page_layout/profiler.xml",
    "content": "<?xml version=\"1.0\"?>\n<layout xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"urn:magento:framework:View/Layout/etc/page_layout.xsd\">\n    <container name=\"profiler\">\n        <!--<container name=\"main\" as=\"main\" after=\"-\" label=\"main\"/>-->\n    </container>\n</layout>\n"
  },
  {
    "path": "src/Profiler/view/base/templates/profile/listing.phtml",
    "content": "<?php\n/** @var \\Mirasvit\\Profiler\\Block\\Profile\\Listing $block */\n\nuse Mirasvit\\Profiler\\Profile\\Meta;\n\n/** @var \\Mirasvit\\Profiler\\Helper\\Format $format */\n$format = $this->helper('Mirasvit\\Profiler\\Helper\\Format');\n?>\n<table id=\"listing-table\">\n    <thead>\n    <tr>\n        <th></th>\n        <th>Status</th>\n        <th>Method</th>\n        <th>IP</th>\n        <th>Time</th>\n        <th>Execution <sup>ms</sup></th>\n        <th>URL</th>\n    </tr>\n    </thead>\n    <tbody>\n    <?php foreach ($block->getList() as $item): ?>\n        <tr>\n            <td class=\"text-center\">\n                <a href=\"<?= $block->getUrl('profiler/profile/view', ['id' => $item['ID']]) ?>\">\n                    view\n                </a>\n            </td>\n            <td class=\"text-center\">\n                <div class=\"badge code-<?= $item[Meta::RESPONSE_CODE] ?>\">\n                    <?= $item[Meta::RESPONSE_CODE] ?>\n                </div>\n            </td>\n            <td class=\"text-center\"><?= $item[Meta::METHOD] ?></td>\n            <td><?= $item[Meta::IP] ?></td>\n            <td nowrap=\"nowrap\"><?= date('M d, H:i:s', $item[Meta::TIME]) ?></td>\n            <td nowrap=\"nowrap\" class=\"text-right\"><?= $format->formatTime($item[Meta::EXECUTION_TIME]) ?></td>\n            <td><?= $item[Meta::URL] ?></td>\n        </tr>\n    <?php endforeach ?>\n    </tbody>\n</table>\n\n<script>\n    $(function () {\n        $(\"#listing-table\").tablesorter();\n    });\n</script>"
  },
  {
    "path": "src/Profiler/view/base/templates/profile/view.phtml",
    "content": "<?php\nuse Mirasvit\\Profiler\\Profile\\Meta;\nuse Mirasvit\\Profiler\\Profile\\General;\n\n/** @var \\Mirasvit\\Profiler\\Block\\Profile\\View $block */\n$profile = $block->getProfile();\n$general = $profile['general'];\n?>\n\n<div class=\"tabs-container\">\n    <ul class=\"tabs\" data-role=\"tabs\">\n        <?php foreach ($block->getTabs() as $key => $tab): ?><li data-tab=\"<?= $key ?>\">\n                <i class=\"fa fa-fw fa-<?=$tab->getIcon() ?>\"></i> <?= $tab->getLabel() ?>\n        </li><?php endforeach ?>\n        <li class=\"link\" style=\"float: right;\">\n            <i class=\"fa fa-chevron-left\"></i>&nbsp;&nbsp;\n            <a href=\"<?=$block->getUrl('profiler/profile/index') ?>\">Back to list</a>\n        </li>\n    </ul>\n    <?php foreach ($block->getTabs() as $key => $tab): ?>\n        <div class=\"tab-content\" data-tab-content=\"<?= $key ?>\">\n            <?= $tab->toHtml() ?>\n        </div>\n    <?php endforeach ?>\n</div>\n\n\n<script>\n    $(function () {\n        $('[data-role=tabs] li').on('click', function (e) {\n            if ($(e.currentTarget).attr('data-tab')) {\n                $('[data-role=tabs] li').removeClass('_active');\n                $(e.currentTarget).addClass('_active');\n                $('[data-tab-content]').hide();\n                $('[data-tab-content=' + $(e.currentTarget).attr('data-tab') + ']').show();\n            }\n        });\n\n        $('[data-role=tabs] li:first').click();\n    });\n</script>\n\n\n"
  },
  {
    "path": "src/Profiler/view/base/templates/root.phtml",
    "content": "<!doctype html>\n<html>\n<head>\n    <script src=\"//code.jquery.com/jquery-3.2.1.min.js\"></script>\n    <script src=\"//cdnjs.cloudflare.com/ajax/libs/jquery.tablesorter/2.28.15/js/jquery.tablesorter.min.js\"></script>\n    <style>\n        <?=file_get_contents(dirname(dirname(__FILE__)) . '/web/css/module.css') ?>\n    </style>\n</head>\n<body>\n<?= $layoutContent ?>\n<script>\n    $(function () {\n        setInterval(function () {\n            parent.postMessage($('body').height(), '*');\n        }, 1000);\n    })\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "src/Profiler/view/base/templates/tab/code.phtml",
    "content": "<?php\n/** @var \\Mirasvit\\Profiler\\Block\\Tab\\Code $block */\n\nuse Mirasvit\\Profiler\\Profile\\General;\n\n$general = $block->getGeneralDump();\n\n/** @var \\Mirasvit\\Profiler\\Helper\\Format $format */\n$format = $this->helper(\\Mirasvit\\Profiler\\Helper\\Format::class);\n?>\n<div class=\"metric\">\n    <strong><?= $format->formatTime($general[General::EXECUTION_TIME]) ?> ms</strong>\n    <i>Total Time</i>\n</div>\n<div class=\"metric\">\n    <strong><?= $format->formatTime($general[General::DB_TIME]) ?> ms</strong>\n    <i>Database Time</i>\n</div>\n\n\n<link rel=\"stylesheet\" type=\"text/css\" href=\"https://cdn.jsdelivr.net/gh/spiermar/d3-flame-graph@1.0.11/dist/d3.flameGraph.min.css\">\n<div id=\"chart\"></div>\n<script type=\"text/javascript\" src=\"https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js\"></script>\n<script type=\"text/javascript\" src=\"https://cdnjs.cloudflare.com/ajax/libs/d3-tip/0.7.1/d3-tip.min.js\"></script>\n<script type=\"text/javascript\" src=\"https://cdn.jsdelivr.net/gh/spiermar/d3-flame-graph@1.0.11/dist/d3.flameGraph.min.js\"></script>\n<script type=\"text/javascript\">\n  const flameGraphJson = <?= $block->getFlameGraphJson(); ?>;\n  const flamegraph = d3.flameGraph()\n    .width(1400)\n    // .height(1080)\n    // .cellHeight(18)\n    .transitionEase(d3.easeCubic)\n    .sort(false)\n    .title(\"\")\n    .reversed(true);\n\n  const tip = d3.tip()\n    .direction(\"s\")\n    .offset([8, 0])\n    .attr('class', 'd3-flame-graph-tip')\n    .html((d) => {\n      const ms = Math.round((d.data.sum/d.data.count * 1000));\n      console.log(d.data);\n      const memory = d.data.realmem ? `Memory: ${formatBytes(d.data.realmem)}` : '';\n      return `${d.data.name}<br />${d.data.count}x${ms}ms ${memory}`;\n    });\n\n  function formatBytes(bytes,decimals) {\n    if(bytes == 0) return '0 Bytes';\n    var k = 1024,\n      dm = decimals || 2,\n      sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],\n      i = Math.floor(Math.log(bytes) / Math.log(k));\n    return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];\n  }\n\n  flamegraph.tooltip(tip);\n\n  d3.select(\"#chart\")\n    .datum(flameGraphJson)\n    .call(flamegraph);\n</script>\n"
  },
  {
    "path": "src/Profiler/view/base/templates/tab/database.phtml",
    "content": "<?php\n/** @var \\Mirasvit\\Profiler\\Block\\Tab\\Database $block */\nuse Mirasvit\\Profiler\\Profile\\Database;\n\n$dump = $block->getDump();\n\n/** @var \\Mirasvit\\Profiler\\Helper\\Format $format */\n$format = $this->helper('Mirasvit\\Profiler\\Helper\\Format');\n?>\n<div class=\"metric\">\n    <strong><?= $format->formatTime($dump[Database::TOTAL_ELAPSED]) ?> ms</strong>\n    <i>Total Queries Time</i>\n</div>\n<div class=\"metric\">\n    <strong><?= $dump[Database::TOTAL_QUERIES] ?></strong>\n    <i>Total Queries</i>\n</div>\n\n\n<div class=\"mst-threshold\" id=\"sql-table-threshold\">\n    <div class=\"value\"></div>\n</div>\n\n<?php $idx = 0 ?>\n<table id=\"sql-table\" data-threshold=\"#sql-table-threshold\">\n    <thead>\n    <tr>\n        <th>#</th>\n        <th>Query Occurance</th>\n        <th>Time <sup>ms</sup></th>\n        <th>SQL Query</th>\n        <th>Query Params</th>\n    </tr>\n    </thead>\n    <tbody>\n    <?php foreach ($dump['profiles'] as $query): ?>\n        <tr data-threshold-value=\"<?= $query[Database::QUERY_ELAPSED] ?>\">\n            <td class=\"text-right\"><?= ++$idx ?></td>\n            <td><?= $query[Database::QUERY_COUNT] ?? 'N/A' ?></td>\n            <td class=\"text-right\">\n                <?= $format->formatTime($query[Database::QUERY_ELAPSED]) ?>\n            </td>\n            <td><?= \\SqlFormatter::format($query[Database::QUERY]) ?></td>\n            <td><?= $format->any($query[Database::QUERY_PARAMS]) ?></td>\n        </tr>\n    <?php endforeach ?>\n    </tbody>\n</table>\n\n<script>\n    $(function () {\n        $(\"#sql-table\").tablesorter();\n    });\n</script>"
  },
  {
    "path": "src/Profiler/view/base/templates/tab/io.phtml",
    "content": "<?php\n/** @var \\Mirasvit\\Profiler\\Block\\Tab\\IO $block */\n\nuse Magento\\Framework\\Profiler\\Driver\\Standard\\Stat;\nuse Mirasvit\\Profiler\\Profile\\General;\n\n$dump = $block->getDump();\n$general = $dump['general'];\n\n/** @var \\Mirasvit\\Profiler\\Helper\\Format $format */\n$format = $this->helper('Mirasvit\\Profiler\\Helper\\Format');\n?>\n<table>\n    <tr>\n        <td>URL</td>\n        <td><?= $general[General::URI] ?></td>\n    </tr>\n    <tr>\n        <td>GET Params</td>\n        <td><?= $format->any($general[General::GET]) ?></td>\n    </tr>\n    <tr>\n        <td>POST Params</td>\n        <td><?= $format->any($general[General::POST]) ?></td>\n    </tr>\n    <tr>\n        <td>IP</td>\n        <td><?= $general[General::IP] ?></td>\n    </tr>\n</table>\n"
  },
  {
    "path": "src/Profiler/view/base/templates/toolbar.phtml",
    "content": "<?php\n/** @var \\Mirasvit\\Profiler\\Block\\Toolbar $block */\nuse Mirasvit\\Profiler\\Profile\\Meta;\nuse Mirasvit\\Profiler\\Profile\\Database;\n\n$dump = $block->getDump();\n$meta = $dump['meta'];\n$database = $dump['database'];\n?>\n<div class=\"mst-profiler__toolbar\">\n    <section>\n        <?= $meta[Meta::RESPONSE_CODE] ?>\n    </section>\n\n    <section>\n        <i class=\"fa fa-clock-o\"></i> <?= round($meta[Meta::EXECUTION_TIME]) ?> <i>ms</i>\n    </section>\n\n    <section>\n        <i class=\"fa fa-database\"></i> <?= $database[Database::TOTAL_QUERIES] ?>\n        <i>in</i> <?= round($database[Database::TOTAL_ELAPSED]) ?> <i>ms</i>\n    </section>\n\n    <section class=\"view\">\n        <a href=\"<?= $block->getProfileUrl() ?>\" target=\"_blank\">View</a>\n    </section>\n</div>\n<style>\n    .mst-profiler__toolbar {\n        width: 100%;\n        background: #373330;\n        position: fixed;\n        bottom: 0;\n        left: 0;\n        z-index: 100000;\n        color: #fff;\n        font-size: 13px;\n        padding: 0;\n        margin: 0;\n        box-shadow: 0 -1px 1px rgba(255, 255, 255, 0.3);\n    }\n\n    .mst-profiler__toolbar section {\n        display: inline-block;\n        padding: 8px 10px;\n        margin: 0;\n        border-right: 1px solid #524e4b;\n    }\n\n    .mst-profiler__toolbar section i {\n        font-style: normal;\n        color: #bbb;\n    }\n\n    .mst-profiler__toolbar section i.fa {\n        margin-right: 5px;\n    }\n\n    .mst-profiler__toolbar section.view {\n        background: #524e4b;\n        float: right;\n    }\n    .mst-profiler__toolbar section.view a {\n        color: #fff;\n        text-decoration: underline;\n        padding-left: 15px;\n        padding-right: 15px;\n    }\n</style>"
  },
  {
    "path": "src/Profiler/view/base/web/css/less/_module/_base.less",
    "content": "html, body {\n  margin: 0;\n  font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;\n  font-style: normal;\n  font-weight: 400;\n  font-size: 13px;\n  color: @text-color;\n}\n\na {\n  color: @link-color;\n\n  &:hover {\n    text-decoration: none;\n  }\n}\n\npre {\n  background: transparent !important;\n  margin: 0;\n}\n\ntable {\n  border-collapse: collapse;\n  border-spacing: 0;\n  width: 100%;\n  border: none;\n\n  tr {\n    th, td {\n      padding: 10px;\n    }\n\n    th {\n      background-color: @bg-dark;\n      border: 1px solid @border-dark;\n      color: #ffffff;\n      font-weight: 600;\n      white-space: nowrap;\n\n      &:first-child {\n        border-left: 0;\n      }\n      &:last-child {\n        border-right: 0;\n      }\n    }\n\n    td {\n      background-color: #ffffff;\n      border-left: 1px dashed @border-light;\n      border-right: 1px dashed @border-light;\n      vertical-align: top;\n\n      &:first-child {\n        border-left: 0;\n      }\n      &:last-child {\n        border-right: 0;\n      }\n    }\n\n    &:nth-child(even) {\n      td {\n        background: @bg-light;\n      }\n    }\n\n    &:last-child {\n      td {\n        border-bottom: 1px solid @border-light;\n      }\n    }\n\n    &:hover {\n      td {\n        background: #e5f7fe;\n      }\n    }\n  }\n}\n\n.tablesorter {\n    .tablesorter-header {\n      text-decoration: underline;\n\n      &.tablesorter-headerDesc, &.tablesorter-headerAsc {\n        background: lighten(@bg-dark, 20%);\n      }\n\n      sup {\n        text-decoration: none;\n      }\n    }\n}\n\n\n.text-left {\n  text-align: left;\n}\n\n.text-right {\n  text-align: right;\n}\n\n.text-center {\n  text-align: center;\n}"
  },
  {
    "path": "src/Profiler/view/base/web/css/less/_module/_etc.less",
    "content": ".metric {\n  border: 1px solid @border-light;\n  display: inline-block;\n  margin: 10px 10px 10px 0;\n\n  strong {\n    text-align: center;\n    font-size: 25px;\n    font-weight: 300;\n    display: block;\n    padding: 10px;\n  }\n  i {\n    display: block;\n    background: @border-light;\n    font-style: normal;\n    text-align: center;\n    padding: 3px 10px;\n    font-size: 12px;\n    font-weight: 600;\n  }\n}\n\n.badge {\n  border-radius: 1px;\n  background: #eee;\n\n  &.code-200 {\n    background: #00c602;\n    color: #fff;\n  }\n\n  &.code-301, &.code-302 {\n    background: #eebe00;\n    color: #fff;\n  }\n\n  &.code-404, &.code-503, &.code-500 {\n    background: #c6000c;\n    color: #fff;\n  }\n}"
  },
  {
    "path": "src/Profiler/view/base/web/css/less/_module/_tabs.less",
    "content": ".tabs-container {\n  .tabs {\n    list-style: none;\n    padding: 0 10px 0 10px;\n    margin: 0;\n    background: @bg-dark;\n\n    li {\n      display: inline-block;\n      background: @bg-dark;\n      color: #fff;\n      font-weight: 600;\n      padding: 10px;\n      cursor: pointer;\n      border-bottom: 3px solid transparent;\n\n      a {\n        color: #fff;\n      }\n\n      &:hover {\n        border-bottom: 3px solid @orange;\n      }\n\n      &._active {\n        background: @bg-dark-hover;\n        border-bottom: 3px solid @orange;\n      }\n\n      &.link {\n        border-bottom: 3px solid transparent !important;\n      }\n    }\n  }\n\n  .tab-content {\n    width: 100%;\n    display: none;\n    padding: 10px;\n    box-sizing: border-box;\n  }\n}"
  },
  {
    "path": "src/Profiler/view/base/web/css/less/_module.less",
    "content": "@import url('//maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css');\n\n@text-color: #303030;\n@link-color: #278CBA;\n@bg-light: #f5f5f5;\n@bg-dark: #514943;\n@bg-dark-hover: lighten(#514943, 10%);\n@border-dark: #8a837f;\n@border-light: #d6d6d6;\n@orange: #EE5100;\n\n@import \"_module/_base.less\";\n@import \"_module/_tabs.less\";\n@import \"_module/_etc.less\";"
  },
  {
    "path": "src/Profiler/view/base/web/css/module.css",
    "content": "@import url('//maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css');\nhtml,\nbody {\n  margin: 0;\n  font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;\n  font-style: normal;\n  font-weight: 400;\n  font-size: 13px;\n  color: #303030;\n}\na {\n  color: #278cba;\n}\na:hover {\n  text-decoration: none;\n}\npre {\n  background: transparent !important;\n  margin: 0;\n}\ntable {\n  border-collapse: collapse;\n  border-spacing: 0;\n  width: 100%;\n  border: none;\n}\ntable tr th,\ntable tr td {\n  padding: 10px;\n}\ntable tr th {\n  background-color: #514943;\n  border: 1px solid #8a837f;\n  color: #ffffff;\n  font-weight: 600;\n  white-space: nowrap;\n}\ntable tr th:first-child {\n  border-left: 0;\n}\ntable tr th:last-child {\n  border-right: 0;\n}\ntable tr td {\n  background-color: #ffffff;\n  border-left: 1px dashed #d6d6d6;\n  border-right: 1px dashed #d6d6d6;\n  vertical-align: top;\n}\ntable tr td:first-child {\n  border-left: 0;\n}\ntable tr td:last-child {\n  border-right: 0;\n}\ntable tr:nth-child(even) td {\n  background: #f5f5f5;\n}\ntable tr:last-child td {\n  border-bottom: 1px solid #d6d6d6;\n}\ntable tr:hover td {\n  background: #e5f7fe;\n}\n.tablesorter .tablesorter-header {\n  text-decoration: underline;\n}\n.tablesorter .tablesorter-header.tablesorter-headerDesc,\n.tablesorter .tablesorter-header.tablesorter-headerAsc {\n  background: #897b71;\n}\n.tablesorter .tablesorter-header sup {\n  text-decoration: none;\n}\n.text-left {\n  text-align: left;\n}\n.text-right {\n  text-align: right;\n}\n.text-center {\n  text-align: center;\n}\n.tabs-container .tabs {\n  list-style: none;\n  padding: 0 10px 0 10px;\n  margin: 0;\n  background: #514943;\n}\n.tabs-container .tabs li {\n  display: inline-block;\n  background: #514943;\n  color: #fff;\n  font-weight: 600;\n  padding: 10px;\n  cursor: pointer;\n  border-bottom: 3px solid transparent;\n}\n.tabs-container .tabs li a {\n  color: #fff;\n}\n.tabs-container .tabs li:hover {\n  border-bottom: 3px solid #ee5100;\n}\n.tabs-container .tabs li._active {\n  background: #6d625a;\n  border-bottom: 3px solid #ee5100;\n}\n.tabs-container .tabs li.link {\n  border-bottom: 3px solid transparent !important;\n}\n.tabs-container .tab-content {\n  width: 100%;\n  display: none;\n  padding: 10px;\n  box-sizing: border-box;\n}\n.metric {\n  border: 1px solid #d6d6d6;\n  display: inline-block;\n  margin: 10px 10px 10px 0;\n}\n.metric strong {\n  text-align: center;\n  font-size: 25px;\n  font-weight: 300;\n  display: block;\n  padding: 10px;\n}\n.metric i {\n  display: block;\n  background: #d6d6d6;\n  font-style: normal;\n  text-align: center;\n  padding: 3px 10px;\n  font-size: 12px;\n  font-weight: 600;\n}\n.badge {\n  border-radius: 1px;\n  background: #eee;\n}\n.badge.code-200 {\n  background: #00c602;\n  color: #fff;\n}\n.badge.code-301,\n.badge.code-302 {\n  background: #eebe00;\n  color: #fff;\n}\n.badge.code-404,\n.badge.code-503,\n.badge.code-500 {\n  background: #c6000c;\n  color: #fff;\n}\n"
  },
  {
    "path": "src/Profiler/view/base/web/js/lib/jquery.filtertable.js",
    "content": "/**\n * jquery.filterTable\n *\n * This plugin will add a search filter to tables. When typing in the filter,\n * any rows that do not contain the filter will be hidden.\n *\n * Utilizes bindWithDelay() if available. https://github.com/bgrins/bindWithDelay\n *\n * @version v1.5.5\n * @author Sunny Walker, swalker@hawaii.edu\n * @license MIT\n */\n(function ($) {\n    var jversion = $.fn.jquery.split('.'),\n        jmajor = parseFloat(jversion[0]),\n        jminor = parseFloat(jversion[1]);\n    if (jmajor < 2 && jminor < 8) { // build the pseudo selector for jQuery < 1.8\n        $.expr[':'].filterTableFind = function (a, i, m) { // build the case insensitive filtering functionality as a pseudo-selector expression\n            return $(a).text().toUpperCase().indexOf(m[3].toUpperCase()) >= 0;\n        };\n        $.expr[':'].filterTableFindAny = function (a, i, m) { // build the case insensitive all-words filtering functionality as a pseudo-selector expression\n            // build an array of each non-falsey value passed\n            var raw_args = m[3].split(/[\\s,]/),\n                args = [];\n            $.each(raw_args, function (j, v) {\n                var t = v.replace(/^\\s+|\\s$/g, '');\n                if (t) {\n                    args.push(t);\n                }\n            });\n            // if there aren't any non-falsey values to search for, abort\n            if (!args.length) {\n                return false;\n            }\n            return function (a) {\n                var found = false;\n                $.each(args, function (j, v) {\n                    if ($(a).text().toUpperCase().indexOf(v.toUpperCase()) >= 0) {\n                        found = true;\n                        return false;\n                    }\n                });\n                return found;\n            };\n        };\n        $.expr[':'].filterTableFindAll = function (a, i, m) { // build the case insensitive all-words filtering functionality as a pseudo-selector expression\n            // build an array of each non-falsey value passed\n            var raw_args = m[3].split(/[\\s,]/),\n                args = [];\n            $.each(raw_args, function (j, v) {\n                var t = v.replace(/^\\s+|\\s$/g, '');\n                if (t) {\n                    args.push(t);\n                }\n            });\n            // if there aren't any non-falsey values to search for, abort\n            if (!args.length) {\n                return false;\n            }\n            return function (a) {\n                var found = 0; // how many terms were found?\n                $.each(args, function (j, v) {\n                    if ($(a).text().toUpperCase().indexOf(v.toUpperCase()) >= 0) {\n                        found++; // found another term\n                    }\n                });\n                return found === args.length; // did we find all of them in this cell?\n            };\n        };\n    } else { // build the pseudo selector for jQuery >= 1.8\n        $.expr[':'].filterTableFind = jQuery.expr.createPseudo(function (arg) {\n            return function (el) {\n                return $(el).text().toUpperCase().indexOf(arg.toUpperCase()) >= 0;\n            };\n        });\n        $.expr[':'].filterTableFindAny = jQuery.expr.createPseudo(function (arg) {\n            // build an array of each non-falsey value passed\n            var raw_args = arg.split(/[\\s,]/),\n                args = [];\n            $.each(raw_args, function (i, v) {\n                var t = v.replace(/^\\s+|\\s$/g, ''); // trim the string\n                if (t) {\n                    args.push(t);\n                }\n            });\n            // if there aren't any non-falsey values to search for, abort\n            if (!args.length) {\n                return false;\n            }\n            return function (el) {\n                var found = false;\n                $.each(args, function (i, v) {\n                    if ($(el).text().toUpperCase().indexOf(v.toUpperCase()) >= 0) {\n                        found = true;\n                        return false; // short-circuit the searching since this cell has one of the terms\n                    }\n                });\n                return found;\n            };\n        });\n        $.expr[':'].filterTableFindAll = jQuery.expr.createPseudo(function (arg) {\n            // build an array of each non-falsey value passed\n            var raw_args = arg.split(/[\\s,]/),\n                args = [];\n            $.each(raw_args, function (i, v) {\n                var t = v.replace(/^\\s+|\\s$/g, ''); // trim the string\n                if (t) {\n                    args.push(t);\n                }\n            });\n            // if there aren't any non-falsey values to search for, abort\n            if (!args.length) {\n                return false;\n            }\n            return function (el) {\n                var found = 0; // how many terms were found?\n                $.each(args, function (i, v) {\n                    if ($(el).text().toUpperCase().indexOf(v.toUpperCase()) >= 0) {\n                        found++; // found another term\n                    }\n                });\n                return found === args.length; // did we find all of them in this cell?\n            };\n        });\n    }\n    $.fn.filterTable = function (options) { // define the filterTable plugin\n        var defaults = { // start off with some default settings\n                autofocus: false,               // make the filter input field autofocused (not recommended for accessibility)\n                callback: null,                // callback function: function(term, table){}\n                containerClass: 'filter-table',      // class to apply to the container\n                containerTag: 'p',                 // tag name of the container\n                filterExpression: 'filterTableFind',   // jQuery expression method to use for filtering\n                hideTFootOnFilter: false,               // if true, the table's tfoot(s) will be hidden when the table is filtered\n                highlightClass: 'alt',               // class applied to cells containing the filter term\n                ignoreClass: '',                  // don't filter the contents of cells with this class\n                ignoreColumns: [],                  // don't filter the contents of these columns\n                inputSelector: null,                // use the element with this selector for the filter input field instead of creating one\n                inputName: '',                  // name of filter input field\n                inputType: 'search',            // tag name of the filter input tag\n                label: '',           // text to precede the filter input tag\n                minChars: 1,                   // filter only when at least this number of characters are in the filter input field\n                minRows: 8,                   // don't show the filter on tables with at least this number of rows\n                placeholder: 'search this table', // HTML5 placeholder text for the filter field\n                preventReturnKey: true,                // prevent the return key in the filter input field from trigger form submits\n                quickList: [],                  // list of phrases to quick fill the search\n                quickListClass: 'quick',             // class of each quick list item\n                quickListGroupTag: '',                  // tag surrounding quick list items (e.g., ul)\n                quickListTag: 'a',                 // tag type of each quick list item (e.g., a or li)\n                visibleClass: 'visible'            // class applied to visible rows\n            },\n            hsc = function (text) { // mimic PHP's htmlspecialchars() function\n                return text.replace(/&/g, '&amp;').replace(/\"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\n            },\n            settings = $.extend({}, defaults, options); // merge the user's settings into the defaults\n\n        var doFiltering = function (table, q) { // handle the actual table filtering\n            var tbody = table.find('tbody'); // cache the tbody element\n            if (q === '' || q.length < settings.minChars) { // if the filtering query is blank or the number of chars is less than the minChars option\n                tbody.find('tr').show().addClass(settings.visibleClass); // show all rows\n                tbody.find('td').removeClass(settings.highlightClass); // remove the row highlight from all cells\n                if (settings.hideTFootOnFilter) { // show footer if the setting was specified\n                    table.find('tfoot').show();\n                }\n            } else { // if the filter query is not blank\n                var all_tds = tbody.find('td');\n                tbody.find('tr').hide().removeClass(settings.visibleClass); // hide all rows, assuming none were found\n                all_tds.removeClass(settings.highlightClass); // remove previous highlights\n                if (settings.hideTFootOnFilter) { // hide footer if the setting was specified\n                    table.find('tfoot').hide();\n                }\n                if (settings.ignoreColumns.length) {\n                    var tds = [];\n                    if (settings.ignoreClass) {\n                        all_tds = all_tds.not('.' + settings.ignoreClass);\n                    }\n                    tds = all_tds.filter(':' + settings.filterExpression + '(\"' + q.replace(/(['\"])/g, '\\\\$1') + '\")');\n                    tds.each(function () {\n                        var t = $(this),\n                            col = t.parent().children().index(t);\n                        //window.console.log(t.text(), col);\n                        if ($.inArray(col, settings.ignoreColumns) === -1) {\n                            //window.console.log(t.text(), $.inArray(col, settings.ignoreColumns));\n                            t.addClass(settings.highlightClass).closest('tr').show().addClass(settings.visibleClass);\n                        }\n                    });\n                } else {\n                    if (settings.ignoreClass) {\n                        all_tds = all_tds.not('.' + settings.ignoreClass);\n                    }\n                    all_tds.filter(':' + settings.filterExpression + '(\"' + q.replace(/(['\"])/g, '\\\\$1') + '\")').addClass(settings.highlightClass).closest('tr').show().addClass(settings.visibleClass); // highlight (class=alt) only the cells that match the query and show their rows\n                }\n            }\n            if (settings.callback) { // call the callback function\n                settings.callback(q, table);\n            }\n        }; // doFiltering()\n\n        return this.each(function () {\n            var t = $(this), // cache the table\n                tbody = t.find('tbody'), // cache the tbody\n                container = null, // placeholder for the filter field container DOM node\n                quicks = null, // placeholder for the quick list items\n                filter = null, // placeholder for the field field DOM node\n                created_filter = true; // was the filter created or chosen from an existing element?\n            if (t[0].nodeName === 'TABLE' && tbody.length > 0 && (settings.minRows === 0 || (settings.minRows > 0 && tbody.find('tr').length >= settings.minRows)) && !t.prev().hasClass(settings.containerClass)) { // only if object is a table and there's a tbody and at least minRows trs and hasn't already had a filter added\n                if (settings.inputSelector && $(settings.inputSelector).length === 1) { // use a single existing field as the filter input field\n                    filter = $(settings.inputSelector);\n                    container = filter.parent(); // container to hold the quick list options\n                    created_filter = false;\n                } else { // create the filter input field (and container)\n                    container = $('<' + settings.containerTag + ' />'); // build the container tag for the filter field\n                    if (settings.containerClass !== '') { // add any classes that need to be added\n                        container.addClass(settings.containerClass);\n                    }\n                    container.prepend(settings.label + ' '); // add the label for the filter field\n                    filter = $('<input type=\"' + settings.inputType + '\" placeholder=\"' + settings.placeholder + '\" name=\"' + settings.inputName + '\" />'); // build the filter field\n                    if (settings.preventReturnKey) { // prevent return in the filter field from submitting any forms\n                        filter.on('keydown', function (ev) {\n                            if ((ev.keyCode || ev.which) === 13) {\n                                ev.preventDefault();\n                                return false;\n                            }\n                        });\n                    }\n                }\n                if (settings.autofocus) { // add the autofocus attribute if requested\n                    filter.attr('autofocus', true);\n                }\n                if ($.fn.bindWithDelay) { // does bindWithDelay() exist?\n                    filter.bindWithDelay('keyup', function () { // bind doFiltering() to keyup (delayed)\n                        doFiltering(t, $(this).val());\n                    }, 200);\n                } else { // just bind to onKeyUp\n                    filter.bind('keyup', function () { // bind doFiltering() to keyup\n                        doFiltering(t, $(this).val());\n                    });\n                } // keyup binding block\n                filter.bind('click search input paste blur', function () { // bind doFiltering() to additional events\n                    doFiltering(t, $(this).val());\n                });\n                if (created_filter) { // add the filter field to the container if it was created by the plugin\n                    container.append(filter);\n                }\n                if (settings.quickList.length > 0) { // are there any quick list items to add?\n                    quicks = settings.quickListGroupTag ? $('<' + settings.quickListGroupTag + ' />') : container;\n                    $.each(settings.quickList, function (index, value) { // for each quick list item...\n                        var q = $('<' + settings.quickListTag + ' class=\"' + settings.quickListClass + '\" />'); // build the quick list item link\n                        q.text(hsc(value)); // add the item's text\n                        if (q[0].nodeName === 'A') {\n                            q.attr('href', '#'); // add a (worthless) href to the item if it's an anchor tag so that it gets the browser's link treatment\n                        }\n                        q.bind('click', function (e) { // bind the click event to it\n                            e.preventDefault(); // stop the normal anchor tag behavior from happening\n                            filter.val(value).focus().trigger('click'); // send the quick list value over to the filter field and trigger the event\n                        });\n                        quicks.append(q); // add the quick list link to the quick list groups container\n                    }); // each quick list item\n                    if (quicks !== container) {\n                        container.append(quicks); // add the quick list groups container to the DOM if it isn't already there\n                    }\n                } // if quick list items\n                if (created_filter) { // add the filter field and quick list container to just before the table if it was created by the plugin\n                    t.before(container);\n                }\n            } // if the functionality should be added\n        }); // return this.each\n    }; // $.fn.filterTable\n})(jQuery);"
  },
  {
    "path": "src/Profiler/view/base/web/js/lib/jquery.tablesorter.js",
    "content": "/*\n *\n * TableSorter 2.0 - Client-side table sorting with ease!\n * Version 2.0.5b\n * @requires jQuery v1.2.3\n *\n * Copyright (c) 2007 Christian Bach\n * Examples and docs at: http://tablesorter.com\n * Dual licensed under the MIT and GPL licenses:\n * http://www.opensource.org/licenses/mit-license.php\n * http://www.gnu.org/licenses/gpl.html\n *\n */\n/**\n *\n * @description Create a sortable table with multi-column sorting capabilitys\n *\n * @example $('table').tablesorter();\n * @desc Create a simple tablesorter interface.\n *\n * @example $('table').tablesorter({ sortList:[[0,0],[1,0]] });\n * @desc Create a tablesorter interface and sort on the first and secound column column headers.\n *\n * @example $('table').tablesorter({ headers: { 0: { sorter: false}, 1: {sorter: false} } });\n *\n * @desc Create a tablesorter interface and disableing the first and second  column headers.\n *\n *\n * @example $('table').tablesorter({ headers: { 0: {sorter:\"integer\"}, 1: {sorter:\"currency\"} } });\n *\n * @desc Create a tablesorter interface and set a column parser for the first\n *       and second column.\n *\n *\n * @param Object\n *            settings An object literal containing key/value pairs to provide\n *            optional settings.\n *\n *\n * @option String cssHeader (optional) A string of the class name to be appended\n *         to sortable tr elements in the thead of the table. Default value:\n *         \"header\"\n *\n * @option String cssAsc (optional) A string of the class name to be appended to\n *         sortable tr elements in the thead on a ascending sort. Default value:\n *         \"headerSortUp\"\n *\n * @option String cssDesc (optional) A string of the class name to be appended\n *         to sortable tr elements in the thead on a descending sort. Default\n *         value: \"headerSortDown\"\n *\n * @option String sortInitialOrder (optional) A string of the inital sorting\n *         order can be asc or desc. Default value: \"asc\"\n *\n * @option String sortMultisortKey (optional) A string of the multi-column sort\n *         key. Default value: \"shiftKey\"\n *\n * @option String textExtraction (optional) A string of the text-extraction\n *         method to use. For complex html structures inside td cell set this\n *         option to \"complex\", on large tables the complex option can be slow.\n *         Default value: \"simple\"\n *\n * @option Object headers (optional) An object of instructions for per-column\n *         controls in the format: headers: { 0: { option: setting }, ... }. For\n *         example, to disable sorting on the first two columns of a table:\n *         headers: { 0: { sorter: false}, 1: {sorter: false} }.\n *         Default value: null.\n *\n * @option Array sortList (optional) An array of instructions for per-column sorting\n *         and direction in the format: [[columnIndex, sortDirection], ... ] where\n *         columnIndex is a zero-based index for your columns left-to-right and\n *         sortDirection is 0 for Ascending and 1 for Descending. A valid argument\n *         that sorts ascending first by column 1 and then column 2 looks like:\n *         [[0,0],[1,0]]. Default value: null.\n *\n * @option Array sortForce (optional) An array containing forced sorting rules.\n *         Use to add an additional forced sort that will be appended to the dynamic\n *         selections by the user. For example, can be used to sort people alphabetically\n *         after some other user-selected sort that results in rows with the same value\n *         like dates or money due. It can help prevent data from appearing as though it\n *         has a random secondary sort. Default value: null.\n *\n * @option Boolean sortLocaleCompare (optional) Boolean flag indicating whatever\n *         to use String.localeCampare method or not. Default set to true.\n *\n *\n * @option Array sortAppend (optional) An array containing forced sorting rules.\n *         This option let's you specify a default sorting rule, which is\n *         appended to user-selected rules. Default value: null\n *\n * @option Boolean widthFixed (optional) Boolean flag indicating if tablesorter\n *         should apply fixed widths to the table columns. This is usefull when\n *         using the pager companion plugin. This options requires the dimension\n *         jquery plugin. Default value: false\n *\n * @option Boolean cancelSelection (optional) Boolean flag indicating if\n *         tablesorter should cancel selection of the table headers text.\n *         Default value: true\n *\n * @option Boolean debug (optional) Boolean flag indicating if tablesorter\n *         should display debuging information usefull for development.\n *\n * @type jQuery\n *\n * @name tablesorter\n *\n * @cat Plugins/Tablesorter\n *\n * @author Christian Bach/christian.bach@polyester.se\n */\n\n(function ($) {\n    $.extend({\n        tablesorter: new\n            function () {\n\n                var parsers = [],\n                    widgets = [];\n\n                this.defaults = {\n                    cssHeader: \"header\",\n                    cssAsc: \"headerSortUp\",\n                    cssDesc: \"headerSortDown\",\n                    cssChildRow: \"expand-child\",\n                    sortInitialOrder: \"asc\",\n                    sortMultiSortKey: \"shiftKey\",\n                    sortForce: null,\n                    sortAppend: null,\n                    sortLocaleCompare: true,\n                    textExtraction: \"simple\",\n                    parsers: {}, widgets: [],\n                    widgetZebra: {\n                        css: [\"even\", \"odd\"]\n                    }, headers: {}, widthFixed: false,\n                    cancelSelection: true,\n                    sortList: [],\n                    headerList: [],\n                    dateFormat: \"us\",\n                    decimal: '/\\.|\\,/g',\n                    onRenderHeader: null,\n                    selectorHeaders: 'thead th',\n                    debug: false\n                };\n\n                /* debuging utils */\n\n                function benchmark(s, d) {\n                    log(s + \",\" + (new Date().getTime() - d.getTime()) + \"ms\");\n                }\n\n                this.benchmark = benchmark;\n\n                function log(s) {\n                    if (typeof console != \"undefined\" && typeof console.debug != \"undefined\") {\n                        console.log(s);\n                    } else {\n                        alert(s);\n                    }\n                }\n\n                /* parsers utils */\n\n                function buildParserCache(table, $headers) {\n\n                    if (table.config.debug) {\n                        var parsersDebug = \"\";\n                    }\n\n                    if (table.tBodies.length == 0) return; // In the case of empty tables\n                    var rows = table.tBodies[0].rows;\n\n                    if (rows[0]) {\n\n                        var list = [],\n                            cells = rows[0].cells,\n                            l = cells.length;\n\n                        for (var i = 0; i < l; i++) {\n\n                            var p = false;\n\n                            if ($.metadata && ($($headers[i]).metadata() && $($headers[i]).metadata().sorter)) {\n\n                                p = getParserById($($headers[i]).metadata().sorter);\n\n                            } else if ((table.config.headers[i] && table.config.headers[i].sorter)) {\n\n                                p = getParserById(table.config.headers[i].sorter);\n                            }\n                            if (!p) {\n\n                                p = detectParserForColumn(table, rows, -1, i);\n                            }\n\n                            if (table.config.debug) {\n                                parsersDebug += \"column:\" + i + \" parser:\" + p.id + \"\\n\";\n                            }\n\n                            list.push(p);\n                        }\n                    }\n\n                    if (table.config.debug) {\n                        log(parsersDebug);\n                    }\n\n                    return list;\n                };\n\n                function detectParserForColumn(table, rows, rowIndex, cellIndex) {\n                    var l = parsers.length,\n                        node = false,\n                        nodeValue = false,\n                        keepLooking = true;\n                    while (nodeValue == '' && keepLooking) {\n                        rowIndex++;\n                        if (rows[rowIndex]) {\n                            node = getNodeFromRowAndCellIndex(rows, rowIndex, cellIndex);\n                            nodeValue = trimAndGetNodeText(table.config, node);\n                            if (table.config.debug) {\n                                log('Checking if value was empty on row:' + rowIndex);\n                            }\n                        } else {\n                            keepLooking = false;\n                        }\n                    }\n                    for (var i = 1; i < l; i++) {\n                        if (parsers[i].is(nodeValue, table, node)) {\n                            return parsers[i];\n                        }\n                    }\n                    // 0 is always the generic parser (text)\n                    return parsers[0];\n                }\n\n                function getNodeFromRowAndCellIndex(rows, rowIndex, cellIndex) {\n                    return rows[rowIndex].cells[cellIndex];\n                }\n\n                function trimAndGetNodeText(config, node) {\n                    return $.trim(getElementText(config, node));\n                }\n\n                function getParserById(name) {\n                    var l = parsers.length;\n                    for (var i = 0; i < l; i++) {\n                        if (parsers[i].id.toLowerCase() == name.toLowerCase()) {\n                            return parsers[i];\n                        }\n                    }\n                    return false;\n                }\n\n                /* utils */\n\n                function buildCache(table) {\n\n                    if (table.config.debug) {\n                        var cacheTime = new Date();\n                    }\n\n                    var totalRows = (table.tBodies[0] && table.tBodies[0].rows.length) || 0,\n                        totalCells = (table.tBodies[0].rows[0] && table.tBodies[0].rows[0].cells.length) || 0,\n                        parsers = table.config.parsers,\n                        cache = {\n                            row: [],\n                            normalized: []\n                        };\n\n                    for (var i = 0; i < totalRows; ++i) {\n\n                        /** Add the table data to main data array */\n                        var c = $(table.tBodies[0].rows[i]),\n                            cols = [];\n\n                        // if this is a child row, add it to the last row's children and\n                        // continue to the next row\n                        if (c.hasClass(table.config.cssChildRow)) {\n                            cache.row[cache.row.length - 1] = cache.row[cache.row.length - 1].add(c);\n                            // go to the next for loop\n                            continue;\n                        }\n\n                        cache.row.push(c);\n\n                        for (var j = 0; j < totalCells; ++j) {\n                            cols.push(parsers[j].format(getElementText(table.config, c[0].cells[j]), table, c[0].cells[j]));\n                        }\n\n                        cols.push(cache.normalized.length); // add position for rowCache\n                        cache.normalized.push(cols);\n                        cols = null;\n                    };\n\n                    if (table.config.debug) {\n                        benchmark(\"Building cache for \" + totalRows + \" rows:\", cacheTime);\n                    }\n\n                    return cache;\n                };\n\n                function getElementText(config, node) {\n\n                    if (!node) return \"\";\n\n                    var $node = $(node),\n                        data = $node.attr('data-sort-value');\n                    if (data !== undefined) return data;\n\n                    var text = \"\";\n\n                    if (!config.supportsTextContent) config.supportsTextContent = node.textContent || false;\n\n                    if (config.textExtraction == \"simple\") {\n                        if (config.supportsTextContent) {\n                            text = node.textContent;\n                        } else {\n                            if (node.childNodes[0] && node.childNodes[0].hasChildNodes()) {\n                                text = node.childNodes[0].innerHTML;\n                            } else {\n                                text = node.innerHTML;\n                            }\n                        }\n                    } else {\n                        if (typeof(config.textExtraction) == \"function\") {\n                            text = config.textExtraction(node);\n                        } else {\n                            text = $(node).text();\n                        }\n                    }\n                    return text;\n                }\n\n                function appendToTable(table, cache) {\n\n                    if (table.config.debug) {\n                        var appendTime = new Date()\n                    }\n\n                    var c = cache,\n                        r = c.row,\n                        n = c.normalized,\n                        totalRows = n.length,\n                        checkCell = (n[0].length - 1),\n                        tableBody = $(table.tBodies[0]),\n                        rows = [];\n\n\n                    for (var i = 0; i < totalRows; i++) {\n                        var pos = n[i][checkCell];\n\n                        rows.push(r[pos]);\n\n                        if (!table.config.appender) {\n\n                            //var o = ;\n                            var l = r[pos].length;\n                            for (var j = 0; j < l; j++) {\n                                tableBody[0].appendChild(r[pos][j]);\n                            }\n\n                            //\n                        }\n                    }\n\n\n\n                    if (table.config.appender) {\n\n                        table.config.appender(table, rows);\n                    }\n\n                    rows = null;\n\n                    if (table.config.debug) {\n                        benchmark(\"Rebuilt table:\", appendTime);\n                    }\n\n                    // apply table widgets\n                    applyWidget(table);\n\n                    // trigger sortend\n                    setTimeout(function () {\n                        $(table).trigger(\"sortEnd\");\n                    }, 0);\n\n                };\n\n                function buildHeaders(table) {\n\n                    if (table.config.debug) {\n                        var time = new Date();\n                    }\n\n                    var meta = ($.metadata) ? true : false;\n\n                    var header_index = computeTableHeaderCellIndexes(table);\n\n                    var $tableHeaders = $(table.config.selectorHeaders, table).each(function (index) {\n\n                        this.column = header_index[this.parentNode.rowIndex + \"-\" + this.cellIndex];\n                        // this.column = index;\n                        this.order = formatSortingOrder(table.config.sortInitialOrder);\n\n\n                        this.count = this.order;\n\n                        if (checkHeaderMetadata(this) || checkHeaderOptions(table, index)) this.sortDisabled = true;\n                        if (checkHeaderOptionsSortingLocked(table, index)) this.order = this.lockedOrder = checkHeaderOptionsSortingLocked(table, index);\n\n                        if (!this.sortDisabled) {\n                            var $th = $(this).addClass(table.config.cssHeader);\n                            if (table.config.onRenderHeader) table.config.onRenderHeader.apply($th);\n                        }\n\n                        // add cell to headerList\n                        table.config.headerList[index] = this;\n                    });\n\n                    if (table.config.debug) {\n                        benchmark(\"Built headers:\", time);\n                        log($tableHeaders);\n                    }\n\n                    return $tableHeaders;\n\n                };\n\n                // from:\n                // http://www.javascripttoolbox.com/lib/table/examples.php\n                // http://www.javascripttoolbox.com/temp/table_cellindex.html\n\n\n                function computeTableHeaderCellIndexes(t) {\n                    var matrix = [];\n                    var lookup = {};\n                    var thead = t.getElementsByTagName('THEAD')[0];\n                    var trs = thead.getElementsByTagName('TR');\n\n                    for (var i = 0; i < trs.length; i++) {\n                        var cells = trs[i].cells;\n                        for (var j = 0; j < cells.length; j++) {\n                            var c = cells[j];\n\n                            var rowIndex = c.parentNode.rowIndex;\n                            var cellId = rowIndex + \"-\" + c.cellIndex;\n                            var rowSpan = c.rowSpan || 1;\n                            var colSpan = c.colSpan || 1\n                            var firstAvailCol;\n                            if (typeof(matrix[rowIndex]) == \"undefined\") {\n                                matrix[rowIndex] = [];\n                            }\n                            // Find first available column in the first row\n                            for (var k = 0; k < matrix[rowIndex].length + 1; k++) {\n                                if (typeof(matrix[rowIndex][k]) == \"undefined\") {\n                                    firstAvailCol = k;\n                                    break;\n                                }\n                            }\n                            lookup[cellId] = firstAvailCol;\n                            for (var k = rowIndex; k < rowIndex + rowSpan; k++) {\n                                if (typeof(matrix[k]) == \"undefined\") {\n                                    matrix[k] = [];\n                                }\n                                var matrixrow = matrix[k];\n                                for (var l = firstAvailCol; l < firstAvailCol + colSpan; l++) {\n                                    matrixrow[l] = \"x\";\n                                }\n                            }\n                        }\n                    }\n                    return lookup;\n                }\n\n                function checkCellColSpan(table, rows, row) {\n                    var arr = [],\n                        r = table.tHead.rows,\n                        c = r[row].cells;\n\n                    for (var i = 0; i < c.length; i++) {\n                        var cell = c[i];\n\n                        if (cell.colSpan > 1) {\n                            arr = arr.concat(checkCellColSpan(table, headerArr, row++));\n                        } else {\n                            if (table.tHead.length == 1 || (cell.rowSpan > 1 || !r[row + 1])) {\n                                arr.push(cell);\n                            }\n                            // headerArr[row] = (i+row);\n                        }\n                    }\n                    return arr;\n                };\n\n                function checkHeaderMetadata(cell) {\n                    if (($.metadata) && ($(cell).metadata().sorter === false)) {\n                        return true;\n                    };\n                    return false;\n                }\n\n                function checkHeaderOptions(table, i) {\n                    if ((table.config.headers[i]) && (table.config.headers[i].sorter === false)) {\n                        return true;\n                    };\n                    return false;\n                }\n\n                function checkHeaderOptionsSortingLocked(table, i) {\n                    if ((table.config.headers[i]) && (table.config.headers[i].lockedOrder)) return table.config.headers[i].lockedOrder;\n                    return false;\n                }\n\n                function applyWidget(table) {\n                    var c = table.config.widgets;\n                    var l = c.length;\n                    for (var i = 0; i < l; i++) {\n\n                        getWidgetById(c[i]).format(table);\n                    }\n\n                }\n\n                function getWidgetById(name) {\n                    var l = widgets.length;\n                    for (var i = 0; i < l; i++) {\n                        if (widgets[i].id.toLowerCase() == name.toLowerCase()) {\n                            return widgets[i];\n                        }\n                    }\n                };\n\n                function formatSortingOrder(v) {\n                    if (typeof(v) != \"Number\") {\n                        return (v.toLowerCase() == \"desc\") ? 1 : 0;\n                    } else {\n                        return (v == 1) ? 1 : 0;\n                    }\n                }\n\n                function isValueInArray(v, a) {\n                    var l = a.length;\n                    for (var i = 0; i < l; i++) {\n                        if (a[i][0] == v) {\n                            return true;\n                        }\n                    }\n                    return false;\n                }\n\n                function setHeadersCss(table, $headers, list, css) {\n                    // remove all header information\n                    $headers.removeClass(css[0]).removeClass(css[1]);\n\n                    var h = [];\n                    $headers.each(function (offset) {\n                        if (!this.sortDisabled) {\n                            h[this.column] = $(this);\n                        }\n                    });\n\n                    var l = list.length;\n                    for (var i = 0; i < l; i++) {\n                        h[list[i][0]].addClass(css[list[i][1]]);\n                    }\n                }\n\n                function fixColumnWidth(table, $headers) {\n                    var c = table.config;\n                    if (c.widthFixed) {\n                        var colgroup = $('<colgroup>');\n                        $(\"tr:first td\", table.tBodies[0]).each(function () {\n                            colgroup.append($('<col>').css('width', $(this).width()));\n                        });\n                        $(table).prepend(colgroup);\n                    };\n                }\n\n                function updateHeaderSortCount(table, sortList) {\n                    var c = table.config,\n                        l = sortList.length;\n                    for (var i = 0; i < l; i++) {\n                        var s = sortList[i],\n                            o = c.headerList[s[0]];\n                        o.count = s[1];\n                        o.count++;\n                    }\n                }\n\n                /* sorting methods */\n\n                var sortWrapper;\n\n                function multisort(table, sortList, cache) {\n\n                    if (table.config.debug) {\n                        var sortTime = new Date();\n                    }\n\n                    var dynamicExp = \"sortWrapper = function(a,b) {\",\n                        l = sortList.length;\n\n                    // TODO: inline functions.\n                    for (var i = 0; i < l; i++) {\n\n                        var c = sortList[i][0];\n                        var order = sortList[i][1];\n                        // var s = (getCachedSortType(table.config.parsers,c) == \"text\") ?\n                        // ((order == 0) ? \"sortText\" : \"sortTextDesc\") : ((order == 0) ?\n                        // \"sortNumeric\" : \"sortNumericDesc\");\n                        // var s = (table.config.parsers[c].type == \"text\") ? ((order == 0)\n                        // ? makeSortText(c) : makeSortTextDesc(c)) : ((order == 0) ?\n                        // makeSortNumeric(c) : makeSortNumericDesc(c));\n                        var s = (table.config.parsers[c].type == \"text\") ? ((order == 0) ? makeSortFunction(\"text\", \"asc\", c) : makeSortFunction(\"text\", \"desc\", c)) : ((order == 0) ? makeSortFunction(\"numeric\", \"asc\", c) : makeSortFunction(\"numeric\", \"desc\", c));\n                        var e = \"e\" + i;\n\n                        dynamicExp += \"var \" + e + \" = \" + s; // + \"(a[\" + c + \"],b[\" + c\n                        // + \"]); \";\n                        dynamicExp += \"if(\" + e + \") { return \" + e + \"; } \";\n                        dynamicExp += \"else { \";\n\n                    }\n\n                    // if value is the same keep orignal order\n                    var orgOrderCol = cache.normalized[0].length - 1;\n                    dynamicExp += \"return a[\" + orgOrderCol + \"]-b[\" + orgOrderCol + \"];\";\n\n                    for (var i = 0; i < l; i++) {\n                        dynamicExp += \"}; \";\n                    }\n\n                    dynamicExp += \"return 0; \";\n                    dynamicExp += \"}; \";\n\n                    if (table.config.debug) {\n                        benchmark(\"Evaling expression:\" + dynamicExp, new Date());\n                    }\n\n                    eval(dynamicExp);\n\n                    cache.normalized.sort(sortWrapper);\n\n                    if (table.config.debug) {\n                        benchmark(\"Sorting on \" + sortList.toString() + \" and dir \" + order + \" time:\", sortTime);\n                    }\n\n                    return cache;\n                };\n\n                function makeSortFunction(type, direction, index) {\n                    var a = \"a[\" + index + \"]\",\n                        b = \"b[\" + index + \"]\";\n                    if (type == 'text' && direction == 'asc') {\n                        return \"(\" + a + \" == \" + b + \" ? 0 : (\" + a + \" === null ? Number.POSITIVE_INFINITY : (\" + b + \" === null ? Number.NEGATIVE_INFINITY : (\" + a + \" < \" + b + \") ? -1 : 1 )));\";\n                    } else if (type == 'text' && direction == 'desc') {\n                        return \"(\" + a + \" == \" + b + \" ? 0 : (\" + a + \" === null ? Number.POSITIVE_INFINITY : (\" + b + \" === null ? Number.NEGATIVE_INFINITY : (\" + b + \" < \" + a + \") ? -1 : 1 )));\";\n                    } else if (type == 'numeric' && direction == 'asc') {\n                        return \"(\" + a + \" === null && \" + b + \" === null) ? 0 :(\" + a + \" === null ? Number.POSITIVE_INFINITY : (\" + b + \" === null ? Number.NEGATIVE_INFINITY : \" + a + \" - \" + b + \"));\";\n                    } else if (type == 'numeric' && direction == 'desc') {\n                        return \"(\" + a + \" === null && \" + b + \" === null) ? 0 :(\" + a + \" === null ? Number.POSITIVE_INFINITY : (\" + b + \" === null ? Number.NEGATIVE_INFINITY : \" + b + \" - \" + a + \"));\";\n                    }\n                };\n\n                function makeSortText(i) {\n                    return \"((a[\" + i + \"] < b[\" + i + \"]) ? -1 : ((a[\" + i + \"] > b[\" + i + \"]) ? 1 : 0));\";\n                };\n\n                function makeSortTextDesc(i) {\n                    return \"((b[\" + i + \"] < a[\" + i + \"]) ? -1 : ((b[\" + i + \"] > a[\" + i + \"]) ? 1 : 0));\";\n                };\n\n                function makeSortNumeric(i) {\n                    return \"a[\" + i + \"]-b[\" + i + \"];\";\n                };\n\n                function makeSortNumericDesc(i) {\n                    return \"b[\" + i + \"]-a[\" + i + \"];\";\n                };\n\n                function sortText(a, b) {\n                    if (table.config.sortLocaleCompare) return a.localeCompare(b);\n                    return ((a < b) ? -1 : ((a > b) ? 1 : 0));\n                };\n\n                function sortTextDesc(a, b) {\n                    if (table.config.sortLocaleCompare) return b.localeCompare(a);\n                    return ((b < a) ? -1 : ((b > a) ? 1 : 0));\n                };\n\n                function sortNumeric(a, b) {\n                    return a - b;\n                };\n\n                function sortNumericDesc(a, b) {\n                    return b - a;\n                };\n\n                function getCachedSortType(parsers, i) {\n                    return parsers[i].type;\n                }; /* public methods */\n                this.construct = function (settings) {\n                    return this.each(function () {\n                        // if no thead or tbody quit.\n                        if (!this.tHead || !this.tBodies) return;\n                        // declare\n                        var $this, $document, $headers, cache, config, shiftDown = 0,\n                            sortOrder;\n                        // new blank config object\n                        this.config = {};\n                        // merge and extend.\n                        config = $.extend(this.config, $.tablesorter.defaults, settings);\n                        // store common expression for speed\n                        $this = $(this);\n                        // save the settings where they read\n                        $.data(this, \"tablesorter\", config);\n                        // build headers\n                        $headers = buildHeaders(this);\n                        // try to auto detect column type, and store in tables config\n                        this.config.parsers = buildParserCache(this, $headers);\n                        // build the cache for the tbody cells\n                        cache = buildCache(this);\n                        // get the css class names, could be done else where.\n                        var sortCSS = [config.cssDesc, config.cssAsc];\n                        // fixate columns if the users supplies the fixedWidth option\n                        fixColumnWidth(this);\n                        // apply event handling to headers\n                        // this is to big, perhaps break it out?\n                        $headers.click(\n\n                            function (e) {\n                                var totalRows = ($this[0].tBodies[0] && $this[0].tBodies[0].rows.length) || 0;\n                                if (!this.sortDisabled && totalRows > 0) {\n                                    // Only call sortStart if sorting is\n                                    // enabled.\n                                    $this.trigger(\"sortStart\");\n                                    // store exp, for speed\n                                    var $cell = $(this);\n                                    // get current column index\n                                    var i = this.column;\n                                    // get current column sort order\n                                    this.order = this.count++ % 2;\n                                    // always sort on the locked order.\n                                    if(this.lockedOrder) this.order = this.lockedOrder;\n\n                                    // user only whants to sort on one\n                                    // column\n                                    if (!e[config.sortMultiSortKey]) {\n                                        // flush the sort list\n                                        config.sortList = [];\n                                        if (config.sortForce != null) {\n                                            var a = config.sortForce;\n                                            for (var j = 0; j < a.length; j++) {\n                                                if (a[j][0] != i) {\n                                                    config.sortList.push(a[j]);\n                                                }\n                                            }\n                                        }\n                                        // add column to sort list\n                                        config.sortList.push([i, this.order]);\n                                        // multi column sorting\n                                    } else {\n                                        // the user has clicked on an all\n                                        // ready sortet column.\n                                        if (isValueInArray(i, config.sortList)) {\n                                            // revers the sorting direction\n                                            // for all tables.\n                                            for (var j = 0; j < config.sortList.length; j++) {\n                                                var s = config.sortList[j],\n                                                    o = config.headerList[s[0]];\n                                                if (s[0] == i) {\n                                                    o.count = s[1];\n                                                    o.count++;\n                                                    s[1] = o.count % 2;\n                                                }\n                                            }\n                                        } else {\n                                            // add column to sort list array\n                                            config.sortList.push([i, this.order]);\n                                        }\n                                    };\n                                    setTimeout(function () {\n                                        // set css for headers\n                                        setHeadersCss($this[0], $headers, config.sortList, sortCSS);\n                                        appendToTable(\n                                            $this[0], multisort(\n                                                $this[0], config.sortList, cache)\n                                        );\n                                    }, 1);\n                                    // stop normal event by returning false\n                                    return false;\n                                }\n                                // cancel selection\n                            }).mousedown(function () {\n                            if (config.cancelSelection) {\n                                this.onselectstart = function () {\n                                    return false\n                                };\n                                return false;\n                            }\n                        });\n                        // apply easy methods that trigger binded events\n                        $this.bind(\"update\", function () {\n                            var me = this;\n                            setTimeout(function () {\n                                // rebuild parsers.\n                                me.config.parsers = buildParserCache(\n                                    me, $headers);\n                                // rebuild the cache map\n                                cache = buildCache(me);\n                            }, 1);\n                        }).bind(\"updateCell\", function (e, cell) {\n                            var config = this.config;\n                            // get position from the dom.\n                            var pos = [(cell.parentNode.rowIndex - 1), cell.cellIndex];\n                            // update cache\n                            cache.normalized[pos[0]][pos[1]] = config.parsers[pos[1]].format(\n                                getElementText(config, cell), cell);\n                        }).bind(\"sorton\", function (e, list) {\n                            $(this).trigger(\"sortStart\");\n                            config.sortList = list;\n                            // update and store the sortlist\n                            var sortList = config.sortList;\n                            // update header count index\n                            updateHeaderSortCount(this, sortList);\n                            // set css for headers\n                            setHeadersCss(this, $headers, sortList, sortCSS);\n                            // sort the table and append it to the dom\n                            appendToTable(this, multisort(this, sortList, cache));\n                        }).bind(\"appendCache\", function () {\n                            appendToTable(this, cache);\n                        }).bind(\"applyWidgetId\", function (e, id) {\n                            getWidgetById(id).format(this);\n                        }).bind(\"applyWidgets\", function () {\n                            // apply widgets\n                            applyWidget(this);\n                        });\n                        if ($.metadata && ($(this).metadata() && $(this).metadata().sortlist)) {\n                            config.sortList = $(this).metadata().sortlist;\n                        }\n                        // if user has supplied a sort list to constructor.\n                        if (config.sortList.length > 0) {\n                            $this.trigger(\"sorton\", [config.sortList]);\n                        }\n                        // apply widgets\n                        applyWidget(this);\n                    });\n                };\n                this.addParser = function (parser) {\n                    var l = parsers.length,\n                        a = true;\n                    for (var i = 0; i < l; i++) {\n                        if (parsers[i].id.toLowerCase() == parser.id.toLowerCase()) {\n                            a = false;\n                        }\n                    }\n                    if (a) {\n                        parsers.push(parser);\n                    };\n                };\n                this.addWidget = function (widget) {\n                    widgets.push(widget);\n                };\n                this.formatFloat = function (s) {\n                    var i = parseFloat(s);\n                    return (isNaN(i)) ? 0 : i;\n                };\n                this.formatInt = function (s) {\n                    var i = parseInt(s);\n                    return (isNaN(i)) ? 0 : i;\n                };\n                this.isDigit = function (s, config) {\n                    // replace all an wanted chars and match.\n                    return /^[-+]?\\d*$/.test($.trim(s.replace(/[,.']/g, '')));\n                };\n                this.clearTableBody = function (table) {\n                    if ($.browser.msie) {\n                        while (table.tBodies[0].firstChild) {\n                            table.tBodies[0].removeChild(table.tBodies[0].firstChild);\n                        }\n                    } else {\n                        table.tBodies[0].innerHTML = \"\";\n                    }\n                };\n            }\n    });\n\n    // extend plugin scope\n    $.fn.extend({\n        tablesorter: $.tablesorter.construct\n    });\n\n    // make shortcut\n    var ts = $.tablesorter;\n\n    // add default parsers\n    ts.addParser({\n        id: \"text\",\n        is: function (s) {\n            return true;\n        }, format: function (s) {\n            return $.trim(s.toLocaleLowerCase());\n        }, type: \"text\"\n    });\n\n    ts.addParser({\n        id: \"digit\",\n        is: function (s, table) {\n            var c = table.config;\n            return $.tablesorter.isDigit(s, c);\n        }, format: function (s) {\n            return $.tablesorter.formatFloat(s);\n        }, type: \"numeric\"\n    });\n\n    ts.addParser({\n        id: \"currency\",\n        is: function (s) {\n            return /^[£$€?.]/.test(s);\n        }, format: function (s) {\n            return $.tablesorter.formatFloat(s.replace(new RegExp(/[£$€]/g), \"\"));\n        }, type: \"numeric\"\n    });\n\n    ts.addParser({\n        id: \"ipAddress\",\n        is: function (s) {\n            return /^\\d{2,3}[\\.]\\d{2,3}[\\.]\\d{2,3}[\\.]\\d{2,3}$/.test(s);\n        }, format: function (s) {\n            var a = s.split(\".\"),\n                r = \"\",\n                l = a.length;\n            for (var i = 0; i < l; i++) {\n                var item = a[i];\n                if (item.length == 2) {\n                    r += \"0\" + item;\n                } else {\n                    r += item;\n                }\n            }\n            return $.tablesorter.formatFloat(r);\n        }, type: \"numeric\"\n    });\n\n    ts.addParser({\n        id: \"url\",\n        is: function (s) {\n            return /^(https?|ftp|file):\\/\\/$/.test(s);\n        }, format: function (s) {\n            return jQuery.trim(s.replace(new RegExp(/(https?|ftp|file):\\/\\//), ''));\n        }, type: \"text\"\n    });\n\n    ts.addParser({\n        id: \"isoDate\",\n        is: function (s) {\n            return /^\\d{4}[\\/-]\\d{1,2}[\\/-]\\d{1,2}$/.test(s);\n        }, format: function (s) {\n            return $.tablesorter.formatFloat((s != \"\") ? new Date(s.replace(\n                new RegExp(/-/g), \"/\")).getTime() : \"0\");\n        }, type: \"numeric\"\n    });\n\n    ts.addParser({\n        id: \"percent\",\n        is: function (s) {\n            return /\\%$/.test($.trim(s));\n        }, format: function (s) {\n            return $.tablesorter.formatFloat(s.replace(new RegExp(/%/g), \"\"));\n        }, type: \"numeric\"\n    });\n\n    ts.addParser({\n        id: \"usLongDate\",\n        is: function (s) {\n            return s.match(new RegExp(/^[A-Za-z]{3,10}\\.? [0-9]{1,2}, ([0-9]{4}|'?[0-9]{2}) (([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\\s(AM|PM)))$/));\n        }, format: function (s) {\n            return $.tablesorter.formatFloat(new Date(s).getTime());\n        }, type: \"numeric\"\n    });\n\n    ts.addParser({\n        id: \"shortDate\",\n        is: function (s) {\n            return /\\d{1,2}[\\/\\-]\\d{1,2}[\\/\\-]\\d{2,4}/.test(s);\n        }, format: function (s, table) {\n            var c = table.config;\n            s = s.replace(/\\-/g, \"/\");\n            if (c.dateFormat == \"us\") {\n                // reformat the string in ISO format\n                s = s.replace(/(\\d{1,2})[\\/\\-](\\d{1,2})[\\/\\-](\\d{4})/, \"$3/$1/$2\");\n            }\n            if (c.dateFormat == \"pt\") {\n                s = s.replace(/(\\d{1,2})[\\/\\-](\\d{1,2})[\\/\\-](\\d{4})/, \"$3/$2/$1\");\n            } else if (c.dateFormat == \"uk\") {\n                // reformat the string in ISO format\n                s = s.replace(/(\\d{1,2})[\\/\\-](\\d{1,2})[\\/\\-](\\d{4})/, \"$3/$2/$1\");\n            } else if (c.dateFormat == \"dd/mm/yy\" || c.dateFormat == \"dd-mm-yy\") {\n                s = s.replace(/(\\d{1,2})[\\/\\-](\\d{1,2})[\\/\\-](\\d{2})/, \"$1/$2/$3\");\n            }\n            return $.tablesorter.formatFloat(new Date(s).getTime());\n        }, type: \"numeric\"\n    });\n    ts.addParser({\n        id: \"time\",\n        is: function (s) {\n            return /^(([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\\s(am|pm)))$/.test(s);\n        }, format: function (s) {\n            return $.tablesorter.formatFloat(new Date(\"2000/01/01 \" + s).getTime());\n        }, type: \"numeric\"\n    });\n    ts.addParser({\n        id: \"metadata\",\n        is: function (s) {\n            return false;\n        }, format: function (s, table, cell) {\n            var c = table.config,\n                p = (!c.parserMetadataName) ? 'sortValue' : c.parserMetadataName;\n            return $(cell).metadata()[p];\n        }, type: \"numeric\"\n    });\n    // add default widgets\n    ts.addWidget({\n        id: \"zebra\",\n        format: function (table) {\n            if (table.config.debug) {\n                var time = new Date();\n            }\n            var $tr, row = -1,\n                odd;\n            // loop through the visible rows\n            $(\"tr:visible\", table.tBodies[0]).each(function (i) {\n                $tr = $(this);\n                // style children rows the same way the parent\n                // row was styled\n                if (!$tr.hasClass(table.config.cssChildRow)) row++;\n                odd = (row % 2 == 0);\n                $tr.removeClass(\n                    table.config.widgetZebra.css[odd ? 0 : 1]).addClass(\n                    table.config.widgetZebra.css[odd ? 1 : 0])\n            });\n            if (table.config.debug) {\n                $.tablesorter.benchmark(\"Applying Zebra widget\", time);\n            }\n        }\n    });\n})(jQuery);"
  },
  {
    "path": "src/Profiler/view/base/web/js/lib/jquery.treetable.js",
    "content": "/*\n * jQuery treetable Plugin 3.2.0\n * http://ludo.cubicphuse.nl/jquery-treetable\n *\n * Copyright 2013, Ludo van den Boom\n * Dual licensed under the MIT or GPL Version 2 licenses.\n */\n(function($) {\n    \"use strict\";\n\n    var Node, Tree, methods;\n\n    Node = (function() {\n        function Node(row, tree, settings) {\n            var parentId;\n\n            this.row = row;\n            this.tree = tree;\n            this.settings = settings;\n\n            // TODO Ensure id/parentId is always a string (not int)\n            this.id = this.row.data(this.settings.nodeIdAttr);\n\n            // TODO Move this to a setParentId function?\n            parentId = this.row.data(this.settings.parentIdAttr);\n            if (parentId != null && parentId !== \"\") {\n                this.parentId = parentId;\n            }\n\n            this.treeCell = $(this.row.children(this.settings.columnElType)[this.settings.column]);\n            this.expander = $(this.settings.expanderTemplate);\n            this.indenter = $(this.settings.indenterTemplate);\n            this.children = [];\n            this.initialized = false;\n            this.treeCell.prepend(this.indenter);\n        }\n\n        Node.prototype.addChild = function(child) {\n            return this.children.push(child);\n        };\n\n        Node.prototype.ancestors = function() {\n            var ancestors, node;\n            node = this;\n            ancestors = [];\n            while (node = node.parentNode()) {\n                ancestors.push(node);\n            }\n            return ancestors;\n        };\n\n        Node.prototype.collapse = function() {\n            if (this.collapsed()) {\n                return this;\n            }\n\n            this.row.removeClass(\"expanded\").addClass(\"collapsed\");\n\n            this._hideChildren();\n            this.expander.attr(\"title\", this.settings.stringExpand);\n\n            if (this.initialized && this.settings.onNodeCollapse != null) {\n                this.settings.onNodeCollapse.apply(this);\n            }\n\n            return this;\n        };\n\n        Node.prototype.collapsed = function() {\n            return this.row.hasClass(\"collapsed\");\n        };\n\n        // TODO destroy: remove event handlers, expander, indenter, etc.\n\n        Node.prototype.expand = function() {\n            if (this.expanded()) {\n                return this;\n            }\n\n            this.row.removeClass(\"collapsed\").addClass(\"expanded\");\n\n            if (this.initialized && this.settings.onNodeExpand != null) {\n                this.settings.onNodeExpand.apply(this);\n            }\n\n            if ($(this.row).is(\":visible\")) {\n                this._showChildren();\n            }\n\n            this.expander.attr(\"title\", this.settings.stringCollapse);\n\n            return this;\n        };\n\n        Node.prototype.expanded = function() {\n            return this.row.hasClass(\"expanded\");\n        };\n\n        Node.prototype.hide = function() {\n            this._hideChildren();\n            this.row.hide();\n            return this;\n        };\n\n        Node.prototype.isBranchNode = function() {\n            if(this.children.length > 0 || this.row.data(this.settings.branchAttr) === true) {\n                return true;\n            } else {\n                return false;\n            }\n        };\n\n        Node.prototype.updateBranchLeafClass = function(){\n            this.row.removeClass('branch');\n            this.row.removeClass('leaf');\n            this.row.addClass(this.isBranchNode() ? 'branch' : 'leaf');\n        };\n\n        Node.prototype.level = function() {\n            return this.ancestors().length;\n        };\n\n        Node.prototype.parentNode = function() {\n            if (this.parentId != null) {\n                return this.tree[this.parentId];\n            } else {\n                return null;\n            }\n        };\n\n        Node.prototype.removeChild = function(child) {\n            var i = $.inArray(child, this.children);\n            return this.children.splice(i, 1)\n        };\n\n        Node.prototype.render = function() {\n            var handler,\n                settings = this.settings,\n                target;\n\n            if (settings.expandable === true && this.isBranchNode()) {\n                handler = function(e) {\n                    $(this).parents(\"table\").treetable(\"node\", $(this).parents(\"tr\").data(settings.nodeIdAttr)).toggle();\n                    return e.preventDefault();\n                };\n\n                this.indenter.html(this.expander);\n                target = settings.clickableNodeNames === true ? this.treeCell : this.expander;\n\n                target.off(\"click.treetable\").on(\"click.treetable\", handler);\n                target.off(\"keydown.treetable\").on(\"keydown.treetable\", function(e) {\n                    if (e.keyCode == 13) {\n                        handler.apply(this, [e]);\n                    }\n                });\n            }\n\n            this.indenter[0].style.paddingLeft = \"\" + (this.level() * settings.indent) + \"px\";\n\n            return this;\n        };\n\n        Node.prototype.reveal = function() {\n            if (this.parentId != null) {\n                this.parentNode().reveal();\n            }\n            return this.expand();\n        };\n\n        Node.prototype.setParent = function(node) {\n            if (this.parentId != null) {\n                this.tree[this.parentId].removeChild(this);\n            }\n            this.parentId = node.id;\n            this.row.data(this.settings.parentIdAttr, node.id);\n            return node.addChild(this);\n        };\n\n        Node.prototype.show = function() {\n            if (!this.initialized) {\n                this._initialize();\n            }\n            this.row.show();\n            if (this.expanded()) {\n                this._showChildren();\n            }\n            return this;\n        };\n\n        Node.prototype.toggle = function() {\n            if (this.expanded()) {\n                this.collapse();\n            } else {\n                this.expand();\n            }\n            return this;\n        };\n\n        Node.prototype._hideChildren = function() {\n            var child, _i, _len, _ref, _results;\n            _ref = this.children;\n            _results = [];\n            for (_i = 0, _len = _ref.length; _i < _len; _i++) {\n                child = _ref[_i];\n                _results.push(child.hide());\n            }\n            return _results;\n        };\n\n        Node.prototype._initialize = function() {\n            var settings = this.settings;\n\n            this.render();\n\n            if (settings.expandable === true && settings.initialState === \"collapsed\") {\n                this.collapse();\n            } else {\n                this.expand();\n            }\n\n            if (settings.onNodeInitialized != null) {\n                settings.onNodeInitialized.apply(this);\n            }\n\n            return this.initialized = true;\n        };\n\n        Node.prototype._showChildren = function() {\n            var child, _i, _len, _ref, _results;\n            _ref = this.children;\n            _results = [];\n            for (_i = 0, _len = _ref.length; _i < _len; _i++) {\n                child = _ref[_i];\n                _results.push(child.show());\n            }\n            return _results;\n        };\n\n        return Node;\n    })();\n\n    Tree = (function() {\n        function Tree(table, settings) {\n            this.table = table;\n            this.settings = settings;\n            this.tree = {};\n\n            // Cache the nodes and roots in simple arrays for quick access/iteration\n            this.nodes = [];\n            this.roots = [];\n        }\n\n        Tree.prototype.collapseAll = function() {\n            var node, _i, _len, _ref, _results;\n            _ref = this.nodes;\n            _results = [];\n            for (_i = 0, _len = _ref.length; _i < _len; _i++) {\n                node = _ref[_i];\n                _results.push(node.collapse());\n            }\n            return _results;\n        };\n\n        Tree.prototype.expandAll = function() {\n            var node, _i, _len, _ref, _results;\n            _ref = this.nodes;\n            _results = [];\n            for (_i = 0, _len = _ref.length; _i < _len; _i++) {\n                node = _ref[_i];\n                _results.push(node.expand());\n            }\n            return _results;\n        };\n\n        Tree.prototype.findLastNode = function (node) {\n            if (node.children.length > 0) {\n                return this.findLastNode(node.children[node.children.length - 1]);\n            } else {\n                return node;\n            }\n        };\n\n        Tree.prototype.loadRows = function(rows) {\n            var node, row, i;\n\n            if (rows != null) {\n                for (i = 0; i < rows.length; i++) {\n                    row = $(rows[i]);\n\n                    if (row.data(this.settings.nodeIdAttr) != null) {\n                        node = new Node(row, this.tree, this.settings);\n                        this.nodes.push(node);\n                        this.tree[node.id] = node;\n\n                        if (node.parentId != null && this.tree[node.parentId]) {\n                            this.tree[node.parentId].addChild(node);\n                        } else {\n                            this.roots.push(node);\n                        }\n                    }\n                }\n            }\n\n            for (i = 0; i < this.nodes.length; i++) {\n                node = this.nodes[i].updateBranchLeafClass();\n            }\n\n            return this;\n        };\n\n        Tree.prototype.move = function(node, destination) {\n            // Conditions:\n            // 1: +node+ should not be inserted as a child of +node+ itself.\n            // 2: +destination+ should not be the same as +node+'s current parent (this\n            //    prevents +node+ from being moved to the same location where it already\n            //    is).\n            // 3: +node+ should not be inserted in a location in a branch if this would\n            //    result in +node+ being an ancestor of itself.\n            var nodeParent = node.parentNode();\n            if (node !== destination && destination.id !== node.parentId && $.inArray(node, destination.ancestors()) === -1) {\n                node.setParent(destination);\n                this._moveRows(node, destination);\n\n                // Re-render parentNode if this is its first child node, and therefore\n                // doesn't have the expander yet.\n                if (node.parentNode().children.length === 1) {\n                    node.parentNode().render();\n                }\n            }\n\n            if(nodeParent){\n                nodeParent.updateBranchLeafClass();\n            }\n            if(node.parentNode()){\n                node.parentNode().updateBranchLeafClass();\n            }\n            node.updateBranchLeafClass();\n            return this;\n        };\n\n        Tree.prototype.removeNode = function(node) {\n            // Recursively remove all descendants of +node+\n            this.unloadBranch(node);\n\n            // Remove node from DOM (<tr>)\n            node.row.remove();\n\n            // Remove node from parent children list\n            if (node.parentId != null) {\n                node.parentNode().removeChild(node);\n            }\n\n            // Clean up Tree object (so Node objects are GC-ed)\n            delete this.tree[node.id];\n            this.nodes.splice($.inArray(node, this.nodes), 1);\n\n            return this;\n        }\n\n        Tree.prototype.render = function() {\n            var root, _i, _len, _ref;\n            _ref = this.roots;\n            for (_i = 0, _len = _ref.length; _i < _len; _i++) {\n                root = _ref[_i];\n\n                // Naming is confusing (show/render). I do not call render on node from\n                // here.\n                root.show();\n            }\n            return this;\n        };\n\n        Tree.prototype.sortBranch = function(node, sortFun) {\n            // First sort internal array of children\n            node.children.sort(sortFun);\n\n            // Next render rows in correct order on page\n            this._sortChildRows(node);\n\n            return this;\n        };\n\n        Tree.prototype.unloadBranch = function(node) {\n            // Use a copy of the children array to not have other functions interfere\n            // with this function if they manipulate the children array\n            // (eg removeNode).\n            var children = node.children.slice(0),\n                i;\n\n            for (i = 0; i < children.length; i++) {\n                this.removeNode(children[i]);\n            }\n\n            // Reset node's collection of children\n            node.children = [];\n\n            node.updateBranchLeafClass();\n\n            return this;\n        };\n\n        Tree.prototype._moveRows = function(node, destination) {\n            var children = node.children, i;\n\n            node.row.insertAfter(destination.row);\n            node.render();\n\n            // Loop backwards through children to have them end up on UI in correct\n            // order (see #112)\n            for (i = children.length - 1; i >= 0; i--) {\n                this._moveRows(children[i], node);\n            }\n        };\n\n        // Special _moveRows case, move children to itself to force sorting\n        Tree.prototype._sortChildRows = function(parentNode) {\n            return this._moveRows(parentNode, parentNode);\n        };\n\n        return Tree;\n    })();\n\n    // jQuery Plugin\n    methods = {\n        init: function(options, force) {\n            var settings;\n\n            settings = $.extend({\n                branchAttr: \"ttBranch\",\n                clickableNodeNames: false,\n                column: 0,\n                columnElType: \"td\", // i.e. 'td', 'th' or 'td,th'\n                expandable: false,\n                expanderTemplate: \"<a href='#'>&nbsp;</a>\",\n                indent: 19,\n                indenterTemplate: \"<span class='indenter'></span>\",\n                initialState: \"collapsed\",\n                nodeIdAttr: \"ttId\", // maps to data-tt-id\n                parentIdAttr: \"ttParentId\", // maps to data-tt-parent-id\n                stringExpand: \"Expand\",\n                stringCollapse: \"Collapse\",\n\n                // Events\n                onInitialized: null,\n                onNodeCollapse: null,\n                onNodeExpand: null,\n                onNodeInitialized: null\n            }, options);\n\n            return this.each(function() {\n                var el = $(this), tree;\n\n                if (force || el.data(\"treetable\") === undefined) {\n                    tree = new Tree(this, settings);\n                    tree.loadRows(this.rows).render();\n\n                    el.addClass(\"treetable\").data(\"treetable\", tree);\n\n                    if (settings.onInitialized != null) {\n                        settings.onInitialized.apply(tree);\n                    }\n                }\n\n                return el;\n            });\n        },\n\n        destroy: function() {\n            return this.each(function() {\n                return $(this).removeData(\"treetable\").removeClass(\"treetable\");\n            });\n        },\n\n        collapseAll: function() {\n            this.data(\"treetable\").collapseAll();\n            return this;\n        },\n\n        collapseNode: function(id) {\n            var node = this.data(\"treetable\").tree[id];\n\n            if (node) {\n                node.collapse();\n            } else {\n                throw new Error(\"Unknown node '\" + id + \"'\");\n            }\n\n            return this;\n        },\n\n        expandAll: function() {\n            this.data(\"treetable\").expandAll();\n            return this;\n        },\n\n        expandNode: function(id) {\n            var node = this.data(\"treetable\").tree[id];\n\n            if (node) {\n                if (!node.initialized) {\n                    node._initialize();\n                }\n\n                node.expand();\n            } else {\n                throw new Error(\"Unknown node '\" + id + \"'\");\n            }\n\n            return this;\n        },\n\n        loadBranch: function(node, rows) {\n            var settings = this.data(\"treetable\").settings,\n                tree = this.data(\"treetable\").tree;\n\n            // TODO Switch to $.parseHTML\n            rows = $(rows);\n\n            if (node == null) { // Inserting new root nodes\n                this.append(rows);\n            } else {\n                var lastNode = this.data(\"treetable\").findLastNode(node);\n                rows.insertAfter(lastNode.row);\n            }\n\n            this.data(\"treetable\").loadRows(rows);\n\n            // Make sure nodes are properly initialized\n            rows.filter(\"tr\").each(function() {\n                tree[$(this).data(settings.nodeIdAttr)].show();\n            });\n\n            if (node != null) {\n                // Re-render parent to ensure expander icon is shown (#79)\n                node.render().expand();\n            }\n\n            return this;\n        },\n\n        move: function(nodeId, destinationId) {\n            var destination, node;\n\n            node = this.data(\"treetable\").tree[nodeId];\n            destination = this.data(\"treetable\").tree[destinationId];\n            this.data(\"treetable\").move(node, destination);\n\n            return this;\n        },\n\n        node: function(id) {\n            return this.data(\"treetable\").tree[id];\n        },\n\n        removeNode: function(id) {\n            var node = this.data(\"treetable\").tree[id];\n\n            if (node) {\n                this.data(\"treetable\").removeNode(node);\n            } else {\n                throw new Error(\"Unknown node '\" + id + \"'\");\n            }\n\n            return this;\n        },\n\n        reveal: function(id) {\n            var node = this.data(\"treetable\").tree[id];\n\n            if (node) {\n                node.reveal();\n            } else {\n                throw new Error(\"Unknown node '\" + id + \"'\");\n            }\n\n            return this;\n        },\n\n        sortBranch: function(node, columnOrFunction) {\n            var settings = this.data(\"treetable\").settings,\n                prepValue,\n                sortFun;\n\n            columnOrFunction = columnOrFunction || settings.column;\n            sortFun = columnOrFunction;\n\n            if ($.isNumeric(columnOrFunction)) {\n                sortFun = function(a, b) {\n                    var extractValue, valA, valB;\n\n                    extractValue = function(node) {\n                        var val = node.row.find(\"td:eq(\" + columnOrFunction + \")\").text();\n                        // Ignore trailing/leading whitespace and use uppercase values for\n                        // case insensitive ordering\n                        return $.trim(val).toUpperCase();\n                    }\n\n                    valA = extractValue(a);\n                    valB = extractValue(b);\n\n                    if (valA < valB) return -1;\n                    if (valA > valB) return 1;\n                    return 0;\n                };\n            }\n\n            this.data(\"treetable\").sortBranch(node, sortFun);\n            return this;\n        },\n\n        unloadBranch: function(node) {\n            this.data(\"treetable\").unloadBranch(node);\n            return this;\n        }\n    };\n\n    $.fn.treetable = function(method) {\n        if (methods[method]) {\n            return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));\n        } else if (typeof method === 'object' || !method) {\n            return methods.init.apply(this, arguments);\n        } else {\n            return $.error(\"Method \" + method + \" does not exist on jQuery.treetable\");\n        }\n    };\n\n    // Expose classes to world\n    window.TreeTable || (window.TreeTable = {});\n    window.TreeTable.Node = Node;\n    window.TreeTable.Tree = Tree;\n})(jQuery);"
  },
  {
    "path": "src/Profiler/view/base/web/js/table.js",
    "content": "define([\n    'jquery',\n    'jquery/jquery.cookie',\n    'jquery/ui',\n    'Mirasvit_Profiler/js/lib/jquery.treetable',\n    'Mirasvit_Profiler/js/lib/jquery.tablesorter',\n    'Mirasvit_Profiler/js/lib/jquery.filtertable'\n], function ($) {\n    'use strict';\n\n    var methods = {\n        init: function (options) {\n            var $table = this;\n\n            $table.treetable({\n                expandable: true\n            });\n\n            $table.tablesorter({\n                callback: function (term, table) {\n                    $table.treetable('expandAll');\n                }\n            });\n\n            $table.bind(\"sortStart\", function () {\n                $table.treetable('expandAll');\n            });\n\n            $table.filterTable({\n                callback: function (term, table) {\n                    $table.treetable('expandAll');\n                }\n            });\n\n            if ($table.attr('data-threshold')) {\n                var max = 0;\n                var min = 1000000;\n                $('[data-threshold-value]', $table).each(function (i, tr) {\n                    var value = parseInt($(tr).attr('data-threshold-value'));\n                    if (value > max) {\n                        max = value;\n                    }\n                    if (value < max) {\n                        min = value;\n                    }\n                });\n\n                var $threshold = $($table.attr('data-threshold'));\n                $threshold.slider({\n                    min:   min,\n                    max:   max,\n                    value: min,\n                    slide: function (event, ui) {\n                        $table.treetable('expandAll');\n\n                        $('[data-threshold-value]', $table).each(function (i, tr) {\n                            var value = $(tr).attr('data-threshold-value');\n                            if (value > ui.value) {\n                                $(tr).show();\n                            } else {\n                                $(tr).hide();\n                            }\n                        });\n\n                        $(\".value\", this).html(ui.value + \" ms\");\n                    }\n                });\n            }\n        }\n    };\n\n    $.fn.profilerTable = function (method) {\n        if (methods[method]) {\n            return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));\n        } else if (typeof method === 'object' || !method) {\n            return methods.init.apply(this, arguments);\n        }\n    };\n});\n"
  }
]