first commit
This commit is contained in:
262
plugins/system/debug/debug.xml
Normal file
262
plugins/system/debug/debug.xml
Normal file
@ -0,0 +1,262 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<extension type="plugin" group="system" method="upgrade">
|
||||
<name>plg_system_debug</name>
|
||||
<author>Joomla! Project</author>
|
||||
<creationDate>2006-12</creationDate>
|
||||
<copyright>(C) 2006 Open Source Matters, Inc.</copyright>
|
||||
<license>GNU General Public License version 2 or later; see LICENSE.txt</license>
|
||||
<authorEmail>admin@joomla.org</authorEmail>
|
||||
<authorUrl>www.joomla.org</authorUrl>
|
||||
<version>3.0.0</version>
|
||||
<description>PLG_DEBUG_XML_DESCRIPTION</description>
|
||||
<namespace path="src">Joomla\Plugin\System\Debug</namespace>
|
||||
<files>
|
||||
<folder plugin="debug">services</folder>
|
||||
<folder>src</folder>
|
||||
</files>
|
||||
<languages>
|
||||
<language tag="en-GB">language/en-GB/plg_system_debug.ini</language>
|
||||
<language tag="en-GB">language/en-GB/plg_system_debug.sys.ini</language>
|
||||
</languages>
|
||||
<config>
|
||||
<fields name="params">
|
||||
<fieldset name="basic">
|
||||
<field
|
||||
name="refresh_assets"
|
||||
type="radio"
|
||||
label="PLG_DEBUG_FIELD_REFRESH_ASSETS_LABEL"
|
||||
description="PLG_DEBUG_FIELD_REFRESH_ASSETS_DESC"
|
||||
layout="joomla.form.field.radio.switcher"
|
||||
default="1"
|
||||
filter="integer"
|
||||
>
|
||||
<option value="0">JNO</option>
|
||||
<option value="1">JYES</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="filter_groups"
|
||||
type="usergrouplist"
|
||||
label="PLG_DEBUG_FIELD_ALLOWED_GROUPS_LABEL"
|
||||
multiple="true"
|
||||
layout="joomla.form.field.list-fancy-select"
|
||||
filter="intarray"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="memory"
|
||||
type="radio"
|
||||
label="PLG_DEBUG_FIELD_MEMORY_LABEL"
|
||||
layout="joomla.form.field.radio.switcher"
|
||||
default="1"
|
||||
filter="integer"
|
||||
>
|
||||
<option value="0">JHIDE</option>
|
||||
<option value="1">JSHOW</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="request"
|
||||
type="radio"
|
||||
label="PLG_DEBUG_FIELD_REQUEST_LABEL"
|
||||
layout="joomla.form.field.radio.switcher"
|
||||
default="1"
|
||||
filter="integer"
|
||||
>
|
||||
<option value="0">JHIDE</option>
|
||||
<option value="1">JSHOW</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="session"
|
||||
type="radio"
|
||||
label="PLG_DEBUG_FIELD_SESSION_LABEL"
|
||||
layout="joomla.form.field.radio.switcher"
|
||||
default="1"
|
||||
filter="integer"
|
||||
>
|
||||
<option value="0">JHIDE</option>
|
||||
<option value="1">JSHOW</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="profile"
|
||||
type="radio"
|
||||
label="PLG_DEBUG_FIELD_PROFILING_LABEL"
|
||||
layout="joomla.form.field.radio.switcher"
|
||||
default="1"
|
||||
filter="integer"
|
||||
>
|
||||
<option value="0">JHIDE</option>
|
||||
<option value="1">JSHOW</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="queries"
|
||||
type="radio"
|
||||
label="PLG_DEBUG_FIELD_QUERIES_LABEL"
|
||||
layout="joomla.form.field.radio.switcher"
|
||||
default="1"
|
||||
filter="integer"
|
||||
>
|
||||
<option value="0">JHIDE</option>
|
||||
<option value="1">JSHOW</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="query_traces"
|
||||
type="radio"
|
||||
label="PLG_DEBUG_FIELD_QUERY_TRACES_LABEL"
|
||||
layout="joomla.form.field.radio.switcher"
|
||||
default="0"
|
||||
showon="queries:1"
|
||||
filter="integer"
|
||||
>
|
||||
<option value="0">JHIDE</option>
|
||||
<option value="1">JSHOW</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="query_profiles"
|
||||
type="radio"
|
||||
label="PLG_DEBUG_FIELD_QUERY_PROFILES_LABEL"
|
||||
layout="joomla.form.field.radio.switcher"
|
||||
default="0"
|
||||
showon="queries:1"
|
||||
filter="integer"
|
||||
>
|
||||
<option value="0">JHIDE</option>
|
||||
<option value="1">JSHOW</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="query_explains"
|
||||
type="radio"
|
||||
label="PLG_DEBUG_FIELD_QUERY_EXPLAINS_LABEL"
|
||||
layout="joomla.form.field.radio.switcher"
|
||||
default="0"
|
||||
showon="queries:1"
|
||||
filter="integer"
|
||||
>
|
||||
<option value="0">JHIDE</option>
|
||||
<option value="1">JSHOW</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="track_request_history"
|
||||
type="radio"
|
||||
label="PLG_DEBUG_FIELD_TRACK_REQUEST_HISTORY_LABEL"
|
||||
description="PLG_DEBUG_FIELD_TRACK_REQUEST_HISTORY_DESC"
|
||||
layout="joomla.form.field.radio.switcher"
|
||||
default="0"
|
||||
filter="integer"
|
||||
>
|
||||
<option value="0">JDISABLED</option>
|
||||
<option value="1">JENABLED</option>
|
||||
</field>
|
||||
</fieldset>
|
||||
|
||||
<fieldset
|
||||
name="language"
|
||||
label="PLG_DEBUG_LANGUAGE_FIELDSET_LABEL"
|
||||
>
|
||||
|
||||
<field
|
||||
name="language_errorfiles"
|
||||
type="radio"
|
||||
label="PLG_DEBUG_FIELD_LANGUAGE_ERRORFILES_LABEL"
|
||||
layout="joomla.form.field.radio.switcher"
|
||||
default="1"
|
||||
filter="integer"
|
||||
>
|
||||
<option value="0">JHIDE</option>
|
||||
<option value="1">JSHOW</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="language_files"
|
||||
type="radio"
|
||||
label="PLG_DEBUG_FIELD_LANGUAGE_FILES_LABEL"
|
||||
layout="joomla.form.field.radio.switcher"
|
||||
default="1"
|
||||
filter="integer"
|
||||
>
|
||||
<option value="0">JHIDE</option>
|
||||
<option value="1">JSHOW</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="language_strings"
|
||||
type="radio"
|
||||
label="PLG_DEBUG_FIELD_LANGUAGE_STRING_LABEL"
|
||||
layout="joomla.form.field.radio.switcher"
|
||||
default="1"
|
||||
filter="integer"
|
||||
>
|
||||
<option value="0">JHIDE</option>
|
||||
<option value="1">JSHOW</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="strip-first"
|
||||
type="radio"
|
||||
label="PLG_DEBUG_FIELD_STRIP_FIRST_LABEL"
|
||||
layout="joomla.form.field.radio.switcher"
|
||||
default="1"
|
||||
filter="integer"
|
||||
>
|
||||
<option value="0">JNO</option>
|
||||
<option value="1">JYES</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="strip-prefix"
|
||||
type="textarea"
|
||||
label="PLG_DEBUG_FIELD_STRIP_PREFIX_LABEL"
|
||||
description="PLG_DEBUG_FIELD_STRIP_PREFIX_DESC"
|
||||
cols="30"
|
||||
rows="4"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="strip-suffix"
|
||||
type="textarea"
|
||||
label="PLG_DEBUG_FIELD_STRIP_SUFFIX_LABEL"
|
||||
description="PLG_DEBUG_FIELD_STRIP_SUFFIX_DESC"
|
||||
cols="30"
|
||||
rows="4"
|
||||
/>
|
||||
</fieldset>
|
||||
|
||||
<fieldset
|
||||
name="logging"
|
||||
label="PLG_DEBUG_LOGGING_FIELDSET_LABEL"
|
||||
>
|
||||
<field
|
||||
name="logs"
|
||||
type="radio"
|
||||
label="PLG_DEBUG_FIELD_LOGS_LABEL"
|
||||
layout="joomla.form.field.radio.switcher"
|
||||
default="1"
|
||||
>
|
||||
<option value="0">JHIDE</option>
|
||||
<option value="1">JSHOW</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="log-deprecated-core"
|
||||
type="radio"
|
||||
label="PLG_DEBUG_FIELD_LOG_DEPRECATED_CORE_LABEL"
|
||||
layout="joomla.form.field.radio.switcher"
|
||||
default="0"
|
||||
filter="integer"
|
||||
showon="logs:1"
|
||||
>
|
||||
<option value="0">JNO</option>
|
||||
<option value="1">JYES</option>
|
||||
</field>
|
||||
|
||||
</fieldset>
|
||||
</fields>
|
||||
</config>
|
||||
</extension>
|
||||
46
plugins/system/debug/services/provider.php
Normal file
46
plugins/system/debug/services/provider.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage System.debug
|
||||
*
|
||||
* @copyright (C) 2023 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
\defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Extension\PluginInterface;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Plugin\PluginHelper;
|
||||
use Joomla\Database\DatabaseInterface;
|
||||
use Joomla\DI\Container;
|
||||
use Joomla\DI\ServiceProviderInterface;
|
||||
use Joomla\Event\DispatcherInterface;
|
||||
use Joomla\Plugin\System\Debug\Extension\Debug;
|
||||
|
||||
return new class () implements ServiceProviderInterface {
|
||||
/**
|
||||
* Registers the service provider with a DI container.
|
||||
*
|
||||
* @param Container $container The DI container.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.4.0
|
||||
*/
|
||||
public function register(Container $container): void
|
||||
{
|
||||
$container->set(
|
||||
PluginInterface::class,
|
||||
function (Container $container) {
|
||||
return new Debug(
|
||||
$container->get(DispatcherInterface::class),
|
||||
(array) PluginHelper::getPlugin('system', 'debug'),
|
||||
Factory::getApplication(),
|
||||
$container->get(DatabaseInterface::class)
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
113
plugins/system/debug/src/AbstractDataCollector.php
Normal file
113
plugins/system/debug/src/AbstractDataCollector.php
Normal file
@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage System.Debug
|
||||
*
|
||||
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Plugin\System\Debug;
|
||||
|
||||
use DebugBar\DataCollector\DataCollector;
|
||||
use DebugBar\DataCollector\Renderable;
|
||||
use Joomla\Registry\Registry;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* AbstractDataCollector
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
abstract class AbstractDataCollector extends DataCollector implements Renderable
|
||||
{
|
||||
/**
|
||||
* Parameters.
|
||||
*
|
||||
* @var Registry
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected $params;
|
||||
|
||||
/**
|
||||
* The default formatter.
|
||||
*
|
||||
* @var DataFormatter
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private static $defaultDataFormatter;
|
||||
|
||||
/**
|
||||
* AbstractDataCollector constructor.
|
||||
*
|
||||
* @param Registry $params Parameters.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function __construct(Registry $params)
|
||||
{
|
||||
$this->params = $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a data formatter.
|
||||
*
|
||||
* @since 4.0.0
|
||||
* @return DataFormatter
|
||||
*/
|
||||
public function getDataFormatter(): DataFormatter
|
||||
{
|
||||
if ($this->dataFormater === null) {
|
||||
$this->dataFormater = self::getDefaultDataFormatter();
|
||||
}
|
||||
|
||||
return $this->dataFormater;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default data formatter
|
||||
*
|
||||
* @since 4.0.0
|
||||
* @return DataFormatter
|
||||
*/
|
||||
public static function getDefaultDataFormatter(): DataFormatter
|
||||
{
|
||||
if (self::$defaultDataFormatter === null) {
|
||||
self::$defaultDataFormatter = new DataFormatter();
|
||||
}
|
||||
|
||||
return self::$defaultDataFormatter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip the Joomla! root path.
|
||||
*
|
||||
* @param string $path The path.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function formatPath($path): string
|
||||
{
|
||||
return $this->getDataFormatter()->formatPath($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a string from back trace.
|
||||
*
|
||||
* @param array $call The array to format
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function formatCallerInfo(array $call): string
|
||||
{
|
||||
return $this->getDataFormatter()->formatCallerInfo($call);
|
||||
}
|
||||
}
|
||||
217
plugins/system/debug/src/DataCollector/InfoCollector.php
Normal file
217
plugins/system/debug/src/DataCollector/InfoCollector.php
Normal file
@ -0,0 +1,217 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage System.Debug
|
||||
*
|
||||
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Plugin\System\Debug\DataCollector;
|
||||
|
||||
use DebugBar\DataCollector\AssetProvider;
|
||||
use Joomla\CMS\Application\AdministratorApplication;
|
||||
use Joomla\CMS\Application\SiteApplication;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Uri\Uri;
|
||||
use Joomla\CMS\User\User;
|
||||
use Joomla\Plugin\System\Debug\AbstractDataCollector;
|
||||
use Joomla\Registry\Registry;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* InfoDataCollector
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
class InfoCollector extends AbstractDataCollector implements AssetProvider
|
||||
{
|
||||
/**
|
||||
* Collector name.
|
||||
*
|
||||
* @var string
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private $name = 'info';
|
||||
|
||||
/**
|
||||
* Request ID.
|
||||
*
|
||||
* @var string
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private $requestId;
|
||||
|
||||
/**
|
||||
* InfoDataCollector constructor.
|
||||
*
|
||||
* @param Registry $params Parameters
|
||||
* @param string $requestId Request ID
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function __construct(Registry $params, $requestId)
|
||||
{
|
||||
$this->requestId = $requestId;
|
||||
|
||||
parent::__construct($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the unique name of the collector
|
||||
*
|
||||
* @since 4.0.0
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a hash where keys are control names and their values
|
||||
* an array of options as defined in {@see \DebugBar\JavascriptRenderer::addControl()}
|
||||
*
|
||||
* @since 4.0.0
|
||||
* @return array
|
||||
*/
|
||||
public function getWidgets(): array
|
||||
{
|
||||
return [
|
||||
'info' => [
|
||||
'icon' => 'info-circle',
|
||||
'title' => 'J! Info',
|
||||
'widget' => 'PhpDebugBar.Widgets.InfoWidget',
|
||||
'map' => $this->name,
|
||||
'default' => '{}',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array with the following keys:
|
||||
* - base_path
|
||||
* - base_url
|
||||
* - css: an array of filenames
|
||||
* - js: an array of filenames
|
||||
*
|
||||
* @since 4.0.0
|
||||
* @return array
|
||||
*/
|
||||
public function getAssets(): array
|
||||
{
|
||||
return [
|
||||
'js' => Uri::root(true) . '/media/plg_system_debug/widgets/info/widget.min.js',
|
||||
'css' => Uri::root(true) . '/media/plg_system_debug/widgets/info/widget.min.css',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the DebugBar when data needs to be collected
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @return array Collected data
|
||||
*/
|
||||
public function collect(): array
|
||||
{
|
||||
/** @type SiteApplication|AdministratorApplication $application */
|
||||
$application = Factory::getApplication();
|
||||
|
||||
$model = $application->bootComponent('com_admin')
|
||||
->getMVCFactory()->createModel('Sysinfo', 'Administrator');
|
||||
|
||||
return [
|
||||
'phpVersion' => PHP_VERSION,
|
||||
'joomlaVersion' => JVERSION,
|
||||
'requestId' => $this->requestId,
|
||||
'identity' => $this->getIdentityInfo($application->getIdentity()),
|
||||
'response' => $this->getResponseInfo($application->getResponse()),
|
||||
'template' => $this->getTemplateInfo($application->getTemplate(true)),
|
||||
'database' => $this->getDatabaseInfo($model->getInfo()),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Identity info.
|
||||
*
|
||||
* @param User $identity The identity.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getIdentityInfo(User $identity): array
|
||||
{
|
||||
if (!$identity->id) {
|
||||
return ['type' => 'guest'];
|
||||
}
|
||||
|
||||
return [
|
||||
'type' => 'user',
|
||||
'id' => $identity->id,
|
||||
'name' => $identity->name,
|
||||
'username' => $identity->username,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get response info.
|
||||
*
|
||||
* @param ResponseInterface $response The response.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getResponseInfo(ResponseInterface $response): array
|
||||
{
|
||||
return [
|
||||
'status_code' => $response->getStatusCode(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get template info.
|
||||
*
|
||||
* @param object $template The template.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getTemplateInfo($template): array
|
||||
{
|
||||
return [
|
||||
'template' => $template->template ?? '',
|
||||
'home' => $template->home ?? '',
|
||||
'id' => $template->id ?? '',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get database info.
|
||||
*
|
||||
* @param array $info General information.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getDatabaseInfo(array $info): array
|
||||
{
|
||||
return [
|
||||
'dbserver' => $info['dbserver'] ?? '',
|
||||
'dbversion' => $info['dbversion'] ?? '',
|
||||
'dbcollation' => $info['dbcollation'] ?? '',
|
||||
'dbconnectioncollation' => $info['dbconnectioncollation'] ?? '',
|
||||
'dbconnectionencryption' => $info['dbconnectionencryption'] ?? '',
|
||||
'dbconnencryptsupported' => $info['dbconnencryptsupported'] ?? '',
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage System.Debug
|
||||
*
|
||||
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Plugin\System\Debug\DataCollector;
|
||||
|
||||
use DebugBar\DataCollector\AssetProvider;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Uri\Uri;
|
||||
use Joomla\Plugin\System\Debug\AbstractDataCollector;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* LanguageErrorsDataCollector
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
class LanguageErrorsCollector extends AbstractDataCollector implements AssetProvider
|
||||
{
|
||||
/**
|
||||
* Collector name.
|
||||
*
|
||||
* @var string
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private $name = 'languageErrors';
|
||||
|
||||
/**
|
||||
* The count.
|
||||
*
|
||||
* @var integer
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private $count = 0;
|
||||
|
||||
/**
|
||||
* Called by the DebugBar when data needs to be collected
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @return array Collected data
|
||||
*/
|
||||
public function collect(): array
|
||||
{
|
||||
return [
|
||||
'data' => [
|
||||
'files' => $this->getData(),
|
||||
'jroot' => JPATH_ROOT,
|
||||
'xdebugLink' => $this->getXdebugLinkTemplate(),
|
||||
],
|
||||
'count' => $this->getCount(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the unique name of the collector
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a hash where keys are control names and their values
|
||||
* an array of options as defined in {@see \DebugBar\JavascriptRenderer::addControl()}
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getWidgets(): array
|
||||
{
|
||||
return [
|
||||
'errors' => [
|
||||
'icon' => 'warning',
|
||||
'widget' => 'PhpDebugBar.Widgets.languageErrorsWidget',
|
||||
'map' => $this->name . '.data',
|
||||
'default' => '',
|
||||
],
|
||||
'errors:badge' => [
|
||||
'map' => $this->name . '.count',
|
||||
'default' => 'null',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array with the following keys:
|
||||
* - base_path
|
||||
* - base_url
|
||||
* - css: an array of filenames
|
||||
* - js: an array of filenames
|
||||
*
|
||||
* @since 4.0.0
|
||||
* @return array
|
||||
*/
|
||||
public function getAssets()
|
||||
{
|
||||
return [
|
||||
'js' => Uri::root(true) . '/media/plg_system_debug/widgets/languageErrors/widget.min.js',
|
||||
'css' => Uri::root(true) . '/media/plg_system_debug/widgets/languageErrors/widget.min.css',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect data.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private function getData(): array
|
||||
{
|
||||
$errorFiles = Factory::getLanguage()->getErrorFiles();
|
||||
$errors = [];
|
||||
|
||||
if (\count($errorFiles)) {
|
||||
foreach ($errorFiles as $file => $lines) {
|
||||
foreach ($lines as $line) {
|
||||
$errors[] = [$file, $line];
|
||||
$this->count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a count value.
|
||||
*
|
||||
* @return int
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private function getCount(): int
|
||||
{
|
||||
return $this->count;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,130 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage System.Debug
|
||||
*
|
||||
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Plugin\System\Debug\DataCollector;
|
||||
|
||||
use DebugBar\DataCollector\AssetProvider;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Uri\Uri;
|
||||
use Joomla\Plugin\System\Debug\AbstractDataCollector;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* LanguageFilesDataCollector
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
class LanguageFilesCollector extends AbstractDataCollector implements AssetProvider
|
||||
{
|
||||
/**
|
||||
* Collector name.
|
||||
*
|
||||
* @var string
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private $name = 'languageFiles';
|
||||
|
||||
/**
|
||||
* The count.
|
||||
*
|
||||
* @var integer
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private $count = 0;
|
||||
|
||||
/**
|
||||
* Called by the DebugBar when data needs to be collected
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @return array Collected data
|
||||
*/
|
||||
public function collect(): array
|
||||
{
|
||||
$paths = Factory::getLanguage()->getPaths();
|
||||
$loaded = [];
|
||||
|
||||
foreach ($paths as $extension => $files) {
|
||||
$loaded[$extension] = [];
|
||||
|
||||
foreach ($files as $file => $status) {
|
||||
$loaded[$extension][$file] = $status;
|
||||
|
||||
if ($status) {
|
||||
$this->count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'loaded' => $loaded,
|
||||
'xdebugLink' => $this->getXdebugLinkTemplate(),
|
||||
'jroot' => JPATH_ROOT,
|
||||
'count' => $this->count,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the unique name of the collector
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a hash where keys are control names and their values
|
||||
* an array of options as defined in {@see \DebugBar\JavascriptRenderer::addControl()}
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getWidgets(): array
|
||||
{
|
||||
return [
|
||||
'loaded' => [
|
||||
'icon' => 'language',
|
||||
'widget' => 'PhpDebugBar.Widgets.languageFilesWidget',
|
||||
'map' => $this->name,
|
||||
'default' => '[]',
|
||||
],
|
||||
'loaded:badge' => [
|
||||
'map' => $this->name . '.count',
|
||||
'default' => 'null',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array with the following keys:
|
||||
* - base_path
|
||||
* - base_url
|
||||
* - css: an array of filenames
|
||||
* - js: an array of filenames
|
||||
*
|
||||
* @since 4.0.0
|
||||
* @return array
|
||||
*/
|
||||
public function getAssets(): array
|
||||
{
|
||||
return [
|
||||
'js' => Uri::root(true) . '/media/plg_system_debug/widgets/languageFiles/widget.min.js',
|
||||
'css' => Uri::root(true) . '/media/plg_system_debug/widgets/languageFiles/widget.min.css',
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,189 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage System.Debug
|
||||
*
|
||||
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Plugin\System\Debug\DataCollector;
|
||||
|
||||
use DebugBar\DataCollector\AssetProvider;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Language\Language;
|
||||
use Joomla\CMS\Uri\Uri;
|
||||
use Joomla\Plugin\System\Debug\AbstractDataCollector;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* LanguageStringsDataCollector
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
class LanguageStringsCollector extends AbstractDataCollector implements AssetProvider
|
||||
{
|
||||
/**
|
||||
* Collector name.
|
||||
*
|
||||
* @var string
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private $name = 'languageStrings';
|
||||
|
||||
/**
|
||||
* Called by the DebugBar when data needs to be collected
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @return array Collected data
|
||||
*/
|
||||
public function collect(): array
|
||||
{
|
||||
return [
|
||||
'data' => $this->getData(),
|
||||
'count' => $this->getCount(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the unique name of the collector
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a hash where keys are control names and their values
|
||||
* an array of options as defined in {@see \DebugBar\JavascriptRenderer::addControl()}
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getWidgets(): array
|
||||
{
|
||||
return [
|
||||
'untranslated' => [
|
||||
'icon' => 'question-circle',
|
||||
'widget' => 'PhpDebugBar.Widgets.languageStringsWidget',
|
||||
'map' => $this->name . '.data',
|
||||
'default' => '',
|
||||
],
|
||||
'untranslated:badge' => [
|
||||
'map' => $this->name . '.count',
|
||||
'default' => 'null',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array with the following keys:
|
||||
* - base_path
|
||||
* - base_url
|
||||
* - css: an array of filenames
|
||||
* - js: an array of filenames
|
||||
*
|
||||
* @since 4.0.0
|
||||
* @return array
|
||||
*/
|
||||
public function getAssets(): array
|
||||
{
|
||||
return [
|
||||
'js' => Uri::root(true) . '/media/plg_system_debug/widgets/languageStrings/widget.min.js',
|
||||
'css' => Uri::root(true) . '/media/plg_system_debug/widgets/languageStrings/widget.min.css',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect data.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private function getData(): array
|
||||
{
|
||||
$orphans = Factory::getLanguage()->getOrphans();
|
||||
|
||||
$data = [];
|
||||
|
||||
foreach ($orphans as $orphan => $occurrences) {
|
||||
$data[$orphan] = [];
|
||||
|
||||
foreach ($occurrences as $occurrence) {
|
||||
$item = [];
|
||||
|
||||
$item['string'] = $occurrence['string'] ?? 'n/a';
|
||||
$item['trace'] = [];
|
||||
$item['caller'] = '';
|
||||
|
||||
if (isset($occurrence['trace'])) {
|
||||
$cnt = 0;
|
||||
$trace = [];
|
||||
$callerLocation = '';
|
||||
|
||||
array_shift($occurrence['trace']);
|
||||
|
||||
foreach ($occurrence['trace'] as $i => $stack) {
|
||||
$class = $stack['class'] ?? '';
|
||||
$file = $stack['file'] ?? '';
|
||||
$line = $stack['line'] ?? '';
|
||||
|
||||
$caller = $this->formatCallerInfo($stack);
|
||||
$location = $file && $line ? "$file:$line" : 'same';
|
||||
|
||||
$isCaller = 0;
|
||||
|
||||
if (!$callerLocation && $class !== Language::class && !strpos($file, 'Text.php')) {
|
||||
$callerLocation = $location;
|
||||
$isCaller = 1;
|
||||
}
|
||||
|
||||
$trace[] = [
|
||||
\count($occurrence['trace']) - $cnt,
|
||||
$isCaller,
|
||||
$caller,
|
||||
$file,
|
||||
$line,
|
||||
];
|
||||
|
||||
$cnt++;
|
||||
}
|
||||
|
||||
$item['trace'] = $trace;
|
||||
$item['caller'] = $callerLocation;
|
||||
}
|
||||
|
||||
$data[$orphan][] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'orphans' => $data,
|
||||
'jroot' => JPATH_ROOT,
|
||||
'xdebugLink' => $this->getXdebugLinkTemplate(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a count value.
|
||||
*
|
||||
* @return integer
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private function getCount(): int
|
||||
{
|
||||
return \count(Factory::getLanguage()->getOrphans());
|
||||
}
|
||||
}
|
||||
151
plugins/system/debug/src/DataCollector/MemoryCollector.php
Normal file
151
plugins/system/debug/src/DataCollector/MemoryCollector.php
Normal file
@ -0,0 +1,151 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage System.Debug
|
||||
*
|
||||
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Plugin\System\Debug\DataCollector;
|
||||
|
||||
use Joomla\Plugin\System\Debug\AbstractDataCollector;
|
||||
use Joomla\Registry\Registry;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Collects info about the request duration as well as providing
|
||||
* a way to log duration of any operations
|
||||
*
|
||||
* @since 4.4.0
|
||||
*/
|
||||
class MemoryCollector extends AbstractDataCollector
|
||||
{
|
||||
/**
|
||||
* @var boolean
|
||||
* @since 4.4.0
|
||||
*/
|
||||
protected $realUsage = false;
|
||||
|
||||
/**
|
||||
* @var float
|
||||
* @since 4.4.0
|
||||
*/
|
||||
protected $peakUsage = 0;
|
||||
|
||||
/**
|
||||
* @param Registry $params Parameters.
|
||||
* @param float $peakUsage
|
||||
* @param boolean $realUsage
|
||||
*
|
||||
* @since 4.4.0
|
||||
*/
|
||||
public function __construct(Registry $params, $peakUsage = null, $realUsage = null)
|
||||
{
|
||||
parent::__construct($params);
|
||||
|
||||
if ($peakUsage !== null) {
|
||||
$this->peakUsage = $peakUsage;
|
||||
}
|
||||
|
||||
if ($realUsage !== null) {
|
||||
$this->realUsage = $realUsage;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether total allocated memory page size is used instead of actual used memory size
|
||||
* by the application. See $real_usage parameter on memory_get_peak_usage for details.
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 4.4.0
|
||||
*/
|
||||
public function getRealUsage()
|
||||
{
|
||||
return $this->realUsage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether total allocated memory page size is used instead of actual used memory size
|
||||
* by the application. See $real_usage parameter on memory_get_peak_usage for details.
|
||||
*
|
||||
* @param boolean $realUsage
|
||||
*
|
||||
* @since 4.4.0
|
||||
*/
|
||||
public function setRealUsage($realUsage)
|
||||
{
|
||||
$this->realUsage = $realUsage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the peak memory usage
|
||||
*
|
||||
* @return integer
|
||||
*
|
||||
* @since 4.4.0
|
||||
*/
|
||||
public function getPeakUsage()
|
||||
{
|
||||
return $this->peakUsage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the peak memory usage value
|
||||
*
|
||||
* @since 4.4.0
|
||||
*/
|
||||
public function updatePeakUsage()
|
||||
{
|
||||
if ($this->peakUsage === null) {
|
||||
$this->peakUsage = memory_get_peak_usage($this->realUsage);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*
|
||||
* @since 4.4.0
|
||||
*/
|
||||
public function collect()
|
||||
{
|
||||
$this->updatePeakUsage();
|
||||
|
||||
return [
|
||||
'peak_usage' => $this->peakUsage,
|
||||
'peak_usage_str' => $this->getDataFormatter()->formatBytes($this->peakUsage, 3),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*
|
||||
* @since 4.4.0
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return 'memory';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*
|
||||
* @since 4.4.0
|
||||
*/
|
||||
public function getWidgets()
|
||||
{
|
||||
return [
|
||||
'memory' => [
|
||||
'icon' => 'cogs',
|
||||
'tooltip' => 'Memory Usage',
|
||||
'map' => 'memory.peak_usage_str',
|
||||
'default' => "'0B'",
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
342
plugins/system/debug/src/DataCollector/ProfileCollector.php
Normal file
342
plugins/system/debug/src/DataCollector/ProfileCollector.php
Normal file
@ -0,0 +1,342 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the DebugBar package.
|
||||
*
|
||||
* @copyright (c) 2013 Maxime Bouroumeau-Fuseau
|
||||
* @license For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Joomla\Plugin\System\Debug\DataCollector;
|
||||
|
||||
use DebugBar\DebugBarException;
|
||||
use Joomla\CMS\Profiler\Profiler;
|
||||
use Joomla\Plugin\System\Debug\AbstractDataCollector;
|
||||
use Joomla\Registry\Registry;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Collects info about the request duration as well as providing
|
||||
* a way to log duration of any operations
|
||||
*
|
||||
* @since version
|
||||
*/
|
||||
class ProfileCollector extends AbstractDataCollector
|
||||
{
|
||||
/**
|
||||
* Request start time.
|
||||
*
|
||||
* @var float
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected $requestStartTime;
|
||||
|
||||
/**
|
||||
* Request end time.
|
||||
*
|
||||
* @var float
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected $requestEndTime;
|
||||
|
||||
/**
|
||||
* Started measures.
|
||||
*
|
||||
* @var array
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected $startedMeasures = [];
|
||||
|
||||
/**
|
||||
* Measures.
|
||||
*
|
||||
* @var array
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected $measures = [];
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param Registry $params Parameters.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function __construct(Registry $params)
|
||||
{
|
||||
if (isset($_SERVER['REQUEST_TIME_FLOAT'])) {
|
||||
$this->requestStartTime = $_SERVER['REQUEST_TIME_FLOAT'];
|
||||
} else {
|
||||
$this->requestStartTime = microtime(true);
|
||||
}
|
||||
|
||||
parent::__construct($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a measure.
|
||||
*
|
||||
* @param string $name Internal name, used to stop the measure
|
||||
* @param string|null $label Public name
|
||||
* @param string|null $collector The source of the collector
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function startMeasure($name, $label = null, $collector = null)
|
||||
{
|
||||
$start = microtime(true);
|
||||
|
||||
$this->startedMeasures[$name] = [
|
||||
'label' => $label ?: $name,
|
||||
'start' => $start,
|
||||
'collector' => $collector,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check a measure exists
|
||||
*
|
||||
* @param string $name Group name.
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function hasStartedMeasure($name): bool
|
||||
{
|
||||
return isset($this->startedMeasures[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops a measure.
|
||||
*
|
||||
* @param string $name Measurement name.
|
||||
* @param array $params Parameters
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @throws DebugBarException
|
||||
*/
|
||||
public function stopMeasure($name, array $params = [])
|
||||
{
|
||||
$end = microtime(true);
|
||||
|
||||
if (!$this->hasStartedMeasure($name)) {
|
||||
throw new DebugBarException("Failed stopping measure '$name' because it hasn't been started");
|
||||
}
|
||||
|
||||
$this->addMeasure($this->startedMeasures[$name]['label'], $this->startedMeasures[$name]['start'], $end, $params, $this->startedMeasures[$name]['collector']);
|
||||
|
||||
unset($this->startedMeasures[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a measure
|
||||
*
|
||||
* @param string $label A label.
|
||||
* @param float $start Start of request.
|
||||
* @param float $end End of request.
|
||||
* @param array $params Parameters.
|
||||
* @param string|null $collector A collector.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function addMeasure($label, $start, $end, array $params = [], $collector = null)
|
||||
{
|
||||
$this->measures[] = [
|
||||
'label' => $label,
|
||||
'start' => $start,
|
||||
'relative_start' => $start - $this->requestStartTime,
|
||||
'end' => $end,
|
||||
'relative_end' => $end - $this->requestEndTime,
|
||||
'duration' => $end - $start,
|
||||
'duration_str' => $this->getDataFormatter()->formatDuration($end - $start),
|
||||
'params' => $params,
|
||||
'collector' => $collector,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function to measure the execution of a Closure
|
||||
*
|
||||
* @param string $label A label.
|
||||
* @param \Closure $closure A closure.
|
||||
* @param string|null $collector A collector.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function measure($label, \Closure $closure, $collector = null)
|
||||
{
|
||||
$name = spl_object_hash($closure);
|
||||
$this->startMeasure($name, $label, $collector);
|
||||
$result = $closure();
|
||||
$params = \is_array($result) ? $result : [];
|
||||
$this->stopMeasure($name, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of all measures
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getMeasures(): array
|
||||
{
|
||||
return $this->measures;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the request start time
|
||||
*
|
||||
* @return float
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getRequestStartTime(): float
|
||||
{
|
||||
return $this->requestStartTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the request end time
|
||||
*
|
||||
* @return float
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getRequestEndTime(): float
|
||||
{
|
||||
return $this->requestEndTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the duration of a request
|
||||
*
|
||||
* @return float
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getRequestDuration(): float
|
||||
{
|
||||
if ($this->requestEndTime !== null) {
|
||||
return $this->requestEndTime - $this->requestStartTime;
|
||||
}
|
||||
|
||||
return microtime(true) - $this->requestStartTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets request end time.
|
||||
*
|
||||
* @param float $time Request end time.
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @since 4.4.0
|
||||
*/
|
||||
public function setRequestEndTime($time): self
|
||||
{
|
||||
$this->requestEndTime = $time;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the DebugBar when data needs to be collected
|
||||
*
|
||||
* @return array Collected data
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function collect(): array
|
||||
{
|
||||
$this->requestEndTime = $this->requestEndTime ?? microtime(true);
|
||||
|
||||
$start = $this->requestStartTime;
|
||||
|
||||
$marks = Profiler::getInstance('Application')->getMarks();
|
||||
|
||||
foreach ($marks as $mark) {
|
||||
$mem = $this->getDataFormatter()->formatBytes(abs($mark->memory) * 1048576);
|
||||
$label = $mark->label . " ($mem)";
|
||||
$end = $start + $mark->time / 1000;
|
||||
$this->addMeasure($label, $start, $end);
|
||||
$start = $end;
|
||||
}
|
||||
|
||||
foreach (array_keys($this->startedMeasures) as $name) {
|
||||
$this->stopMeasure($name);
|
||||
}
|
||||
|
||||
usort(
|
||||
$this->measures,
|
||||
function ($a, $b) {
|
||||
if ($a['start'] === $b['start']) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return $a['start'] < $b['start'] ? -1 : 1;
|
||||
}
|
||||
);
|
||||
|
||||
return [
|
||||
'start' => $this->requestStartTime,
|
||||
'end' => $this->requestEndTime,
|
||||
'duration' => $this->getRequestDuration(),
|
||||
'duration_str' => $this->getDataFormatter()->formatDuration($this->getRequestDuration()),
|
||||
'measures' => array_values($this->measures),
|
||||
'rawMarks' => $marks,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the unique name of the collector
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return 'profile';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a hash where keys are control names and their values
|
||||
* an array of options as defined in {@see \DebugBar\JavascriptRenderer::addControl()}
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getWidgets(): array
|
||||
{
|
||||
return [
|
||||
'profileTime' => [
|
||||
'icon' => 'clock-o',
|
||||
'tooltip' => 'Request Duration',
|
||||
'map' => 'profile.duration_str',
|
||||
'default' => "'0ms'",
|
||||
],
|
||||
'profile' => [
|
||||
'icon' => 'clock-o',
|
||||
'widget' => 'PhpDebugBar.Widgets.TimelineWidget',
|
||||
'map' => 'profile',
|
||||
'default' => '{}',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
258
plugins/system/debug/src/DataCollector/QueryCollector.php
Normal file
258
plugins/system/debug/src/DataCollector/QueryCollector.php
Normal file
@ -0,0 +1,258 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage System.Debug
|
||||
*
|
||||
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Plugin\System\Debug\DataCollector;
|
||||
|
||||
use DebugBar\DataCollector\AssetProvider;
|
||||
use Joomla\CMS\Uri\Uri;
|
||||
use Joomla\Database\Monitor\DebugMonitor;
|
||||
use Joomla\Plugin\System\Debug\AbstractDataCollector;
|
||||
use Joomla\Registry\Registry;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* QueryDataCollector
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
class QueryCollector extends AbstractDataCollector implements AssetProvider
|
||||
{
|
||||
/**
|
||||
* Collector name.
|
||||
*
|
||||
* @var string
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private $name = 'queries';
|
||||
|
||||
/**
|
||||
* The query monitor.
|
||||
*
|
||||
* @var DebugMonitor
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private $queryMonitor;
|
||||
|
||||
/**
|
||||
* Profile data.
|
||||
*
|
||||
* @var array
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private $profiles;
|
||||
|
||||
/**
|
||||
* Explain data.
|
||||
*
|
||||
* @var array
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private $explains;
|
||||
|
||||
/**
|
||||
* Accumulated Duration.
|
||||
*
|
||||
* @var integer
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private $accumulatedDuration = 0;
|
||||
|
||||
/**
|
||||
* Accumulated Memory.
|
||||
*
|
||||
* @var integer
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private $accumulatedMemory = 0;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param Registry $params Parameters.
|
||||
* @param DebugMonitor $queryMonitor Query monitor.
|
||||
* @param array $profiles Profile data.
|
||||
* @param array $explains Explain data
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function __construct(Registry $params, DebugMonitor $queryMonitor, array $profiles, array $explains)
|
||||
{
|
||||
$this->queryMonitor = $queryMonitor;
|
||||
|
||||
parent::__construct($params);
|
||||
|
||||
$this->profiles = $profiles;
|
||||
$this->explains = $explains;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the DebugBar when data needs to be collected
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @return array Collected data
|
||||
*/
|
||||
public function collect(): array
|
||||
{
|
||||
$statements = $this->getStatements();
|
||||
|
||||
return [
|
||||
'data' => [
|
||||
'statements' => $statements,
|
||||
'nb_statements' => \count($statements),
|
||||
'accumulated_duration_str' => $this->getDataFormatter()->formatDuration($this->accumulatedDuration),
|
||||
'memory_usage_str' => $this->getDataFormatter()->formatBytes($this->accumulatedMemory),
|
||||
'xdebug_link' => $this->getXdebugLinkTemplate(),
|
||||
'root_path' => JPATH_ROOT,
|
||||
],
|
||||
'count' => \count($this->queryMonitor->getLogs()),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the unique name of the collector
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a hash where keys are control names and their values
|
||||
* an array of options as defined in {@see \DebugBar\JavascriptRenderer::addControl()}
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getWidgets(): array
|
||||
{
|
||||
return [
|
||||
'queries' => [
|
||||
'icon' => 'database',
|
||||
'widget' => 'PhpDebugBar.Widgets.SQLQueriesWidget',
|
||||
'map' => $this->name . '.data',
|
||||
'default' => '[]',
|
||||
],
|
||||
'queries:badge' => [
|
||||
'map' => $this->name . '.count',
|
||||
'default' => 'null',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Assets for the collector.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getAssets(): array
|
||||
{
|
||||
return [
|
||||
'css' => Uri::root(true) . '/media/plg_system_debug/widgets/sqlqueries/widget.min.css',
|
||||
'js' => Uri::root(true) . '/media/plg_system_debug/widgets/sqlqueries/widget.min.js',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the executed statements data.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getStatements(): array
|
||||
{
|
||||
$statements = [];
|
||||
$logs = $this->queryMonitor->getLogs();
|
||||
$boundParams = $this->queryMonitor->getBoundParams();
|
||||
$timings = $this->queryMonitor->getTimings();
|
||||
$memoryLogs = $this->queryMonitor->getMemoryLogs();
|
||||
$stacks = $this->queryMonitor->getCallStacks();
|
||||
$collectStacks = $this->params->get('query_traces');
|
||||
|
||||
foreach ($logs as $id => $item) {
|
||||
$queryTime = 0;
|
||||
$queryMemory = 0;
|
||||
|
||||
if ($timings && isset($timings[$id * 2 + 1])) {
|
||||
// Compute the query time.
|
||||
$queryTime = ($timings[$id * 2 + 1] - $timings[$id * 2]);
|
||||
$this->accumulatedDuration += $queryTime;
|
||||
}
|
||||
|
||||
if ($memoryLogs && isset($memoryLogs[$id * 2 + 1])) {
|
||||
// Compute the query memory usage.
|
||||
$queryMemory = ($memoryLogs[$id * 2 + 1] - $memoryLogs[$id * 2]);
|
||||
$this->accumulatedMemory += $queryMemory;
|
||||
}
|
||||
|
||||
$trace = [];
|
||||
$callerLocation = '';
|
||||
|
||||
if (isset($stacks[$id])) {
|
||||
$cnt = 0;
|
||||
|
||||
foreach ($stacks[$id] as $i => $stack) {
|
||||
$class = $stack['class'] ?? '';
|
||||
$file = $stack['file'] ?? '';
|
||||
$line = $stack['line'] ?? '';
|
||||
|
||||
$caller = $this->formatCallerInfo($stack);
|
||||
$location = $file && $line ? "$file:$line" : 'same';
|
||||
|
||||
$isCaller = 0;
|
||||
|
||||
if (\Joomla\Database\DatabaseDriver::class === $class && false === strpos($file, 'DatabaseDriver.php')) {
|
||||
$callerLocation = $location;
|
||||
$isCaller = 1;
|
||||
}
|
||||
|
||||
if ($collectStacks) {
|
||||
$trace[] = [\count($stacks[$id]) - $cnt, $isCaller, $caller, $file, $line];
|
||||
}
|
||||
|
||||
$cnt++;
|
||||
}
|
||||
}
|
||||
|
||||
$explain = $this->explains[$id] ?? [];
|
||||
$explainColumns = [];
|
||||
|
||||
// Extract column labels for Explain table
|
||||
if ($explain) {
|
||||
$explainColumns = array_keys(reset($explain));
|
||||
}
|
||||
|
||||
$statements[] = [
|
||||
'sql' => $item,
|
||||
'params' => $boundParams[$id] ?? [],
|
||||
'duration_str' => $this->getDataFormatter()->formatDuration($queryTime),
|
||||
'memory_str' => $this->getDataFormatter()->formatBytes($queryMemory),
|
||||
'caller' => $callerLocation,
|
||||
'callstack' => $trace,
|
||||
'explain' => $explain,
|
||||
'explain_col' => $explainColumns,
|
||||
'profile' => $this->profiles[$id] ?? [],
|
||||
];
|
||||
}
|
||||
|
||||
return $statements;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage System.Debug
|
||||
*
|
||||
* @copyright (C) 2022 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Plugin\System\Debug\DataCollector;
|
||||
|
||||
use Joomla\Plugin\System\Debug\Extension\Debug;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Collects info about the request content while redacting potentially secret content
|
||||
*
|
||||
* @since 4.2.4
|
||||
*/
|
||||
class RequestDataCollector extends \DebugBar\DataCollector\RequestDataCollector
|
||||
{
|
||||
/**
|
||||
* Called by the DebugBar when data needs to be collected
|
||||
*
|
||||
* @since 4.2.4
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function collect()
|
||||
{
|
||||
$vars = ['_GET', '_POST', '_SESSION', '_COOKIE', '_SERVER'];
|
||||
$returnData = [];
|
||||
|
||||
foreach ($vars as $var) {
|
||||
if (isset($GLOBALS[$var])) {
|
||||
$key = "$" . $var;
|
||||
|
||||
$data = $GLOBALS[$var];
|
||||
|
||||
// Replace Joomla session data from session data, it will be collected by SessionCollector
|
||||
if ($var === '_SESSION' && !empty($data['joomla'])) {
|
||||
$data['joomla'] = '***redacted***';
|
||||
}
|
||||
|
||||
array_walk_recursive($data, static function (&$value, $key) {
|
||||
if (!preg_match(Debug::PROTECTED_COLLECTOR_KEYS, $key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$value = '***redacted***';
|
||||
});
|
||||
|
||||
if ($this->isHtmlVarDumperUsed()) {
|
||||
$returnData[$key] = $this->getVarDumper()->renderVar($data);
|
||||
} else {
|
||||
$returnData[$key] = $this->getDataFormatter()->formatVar($data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $returnData;
|
||||
}
|
||||
}
|
||||
125
plugins/system/debug/src/DataCollector/SessionCollector.php
Normal file
125
plugins/system/debug/src/DataCollector/SessionCollector.php
Normal file
@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage System.Debug
|
||||
*
|
||||
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Plugin\System\Debug\DataCollector;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\Plugin\System\Debug\AbstractDataCollector;
|
||||
use Joomla\Plugin\System\Debug\Extension\Debug;
|
||||
use Joomla\Registry\Registry;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* SessionDataCollector
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
class SessionCollector extends AbstractDataCollector
|
||||
{
|
||||
/**
|
||||
* Collector name.
|
||||
*
|
||||
* @var string
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private $name = 'session';
|
||||
|
||||
/**
|
||||
* Collected data.
|
||||
*
|
||||
* @var array
|
||||
* @since 4.4.0
|
||||
*/
|
||||
protected $sessionData;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param Registry $params Parameters.
|
||||
* @param bool $collect Collect the session data.
|
||||
*
|
||||
* @since 4.4.0
|
||||
*/
|
||||
public function __construct($params, $collect = false)
|
||||
{
|
||||
parent::__construct($params);
|
||||
|
||||
if ($collect) {
|
||||
$this->collect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the DebugBar when data needs to be collected
|
||||
*
|
||||
* @param bool $overwrite Overwrite the previously collected session data.
|
||||
*
|
||||
* @return array Collected data
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function collect($overwrite = false)
|
||||
{
|
||||
if ($this->sessionData === null || $overwrite) {
|
||||
$this->sessionData = [];
|
||||
$data = Factory::getApplication()->getSession()->all();
|
||||
|
||||
// redact value of potentially secret keys
|
||||
array_walk_recursive($data, static function (&$value, $key) {
|
||||
if (!preg_match(Debug::PROTECTED_COLLECTOR_KEYS, $key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$value = '***redacted***';
|
||||
});
|
||||
|
||||
foreach ($data as $key => $value) {
|
||||
$this->sessionData[$key] = $this->getDataFormatter()->formatVar($value);
|
||||
}
|
||||
}
|
||||
|
||||
return ['data' => $this->sessionData];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the unique name of the collector
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a hash where keys are control names and their values
|
||||
* an array of options as defined in {@see \DebugBar\JavascriptRenderer::addControl()}
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getWidgets()
|
||||
{
|
||||
return [
|
||||
'session' => [
|
||||
'icon' => 'key',
|
||||
'widget' => 'PhpDebugBar.Widgets.VariableListWidget',
|
||||
'map' => $this->name . '.data',
|
||||
'default' => '[]',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
62
plugins/system/debug/src/DataCollector/UserCollector.php
Normal file
62
plugins/system/debug/src/DataCollector/UserCollector.php
Normal file
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage System.Debug
|
||||
*
|
||||
* @copyright (C) 2022 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Plugin\System\Debug\DataCollector;
|
||||
|
||||
use DebugBar\DataCollector\DataCollectorInterface;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\User\UserFactoryInterface;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* User collector that stores the user id of the person making the request allowing us to filter on it after storage
|
||||
*
|
||||
* @since 4.2.4
|
||||
*/
|
||||
class UserCollector implements DataCollectorInterface
|
||||
{
|
||||
/**
|
||||
* Collector name.
|
||||
*
|
||||
* @var string
|
||||
* @since 4.2.4
|
||||
*/
|
||||
private $name = 'juser';
|
||||
|
||||
/**
|
||||
* Called by the DebugBar when data needs to be collected
|
||||
*
|
||||
* @since 4.2.4
|
||||
*
|
||||
* @return array Collected data
|
||||
*/
|
||||
public function collect()
|
||||
{
|
||||
$user = Factory::getApplication()->getIdentity()
|
||||
?: Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById(0);
|
||||
|
||||
return ['user_id' => $user->id];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the unique name of the collector
|
||||
*
|
||||
* @since 4.2.4
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
}
|
||||
93
plugins/system/debug/src/DataFormatter.php
Normal file
93
plugins/system/debug/src/DataFormatter.php
Normal file
@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage System.Debug
|
||||
*
|
||||
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Plugin\System\Debug;
|
||||
|
||||
use DebugBar\DataFormatter\DataFormatter as DebugBarDataFormatter;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* DataFormatter
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
class DataFormatter extends DebugBarDataFormatter
|
||||
{
|
||||
/**
|
||||
* Strip the root path.
|
||||
*
|
||||
* @param string $path The path.
|
||||
* @param string $replacement The replacement
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function formatPath($path, $replacement = ''): string
|
||||
{
|
||||
return str_replace(JPATH_ROOT, $replacement, $path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a string from back trace.
|
||||
*
|
||||
* @param array $call The array to format
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function formatCallerInfo(array $call): string
|
||||
{
|
||||
$string = '';
|
||||
|
||||
if (isset($call['class'])) {
|
||||
// If entry has Class/Method print it.
|
||||
$string .= htmlspecialchars($call['class'] . $call['type'] . $call['function']) . '()';
|
||||
} elseif (isset($call['args'][0]) && \is_array($call['args'][0])) {
|
||||
$string .= htmlspecialchars($call['function']) . ' (';
|
||||
|
||||
foreach ($call['args'][0] as $arg) {
|
||||
// Check if the arguments can be used as string
|
||||
if (\is_object($arg) && !method_exists($arg, '__toString')) {
|
||||
$arg = \get_class($arg);
|
||||
}
|
||||
|
||||
// Keep only the size of array
|
||||
if (\is_array($arg)) {
|
||||
$arg = 'Array(count=' . \count($arg) . ')';
|
||||
}
|
||||
|
||||
$string .= htmlspecialchars($arg) . ', ';
|
||||
}
|
||||
|
||||
$string = rtrim($string, ', ') . ')';
|
||||
} elseif (isset($call['args'][0])) {
|
||||
$string .= htmlspecialchars($call['function']) . '(';
|
||||
|
||||
if (is_scalar($call['args'][0])) {
|
||||
$string .= $call['args'][0];
|
||||
} elseif (\is_object($call['args'][0])) {
|
||||
$string .= \get_class($call['args'][0]);
|
||||
} else {
|
||||
$string .= \gettype($call['args'][0]);
|
||||
}
|
||||
$string .= ')';
|
||||
} else {
|
||||
// It's a function.
|
||||
$string .= htmlspecialchars($call['function']) . '()';
|
||||
}
|
||||
|
||||
return $string;
|
||||
}
|
||||
}
|
||||
712
plugins/system/debug/src/Extension/Debug.php
Normal file
712
plugins/system/debug/src/Extension/Debug.php
Normal file
@ -0,0 +1,712 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage System.debug
|
||||
*
|
||||
* @copyright (C) 2006 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Plugin\System\Debug\Extension;
|
||||
|
||||
use DebugBar\DataCollector\MessagesCollector;
|
||||
use DebugBar\DebugBar;
|
||||
use DebugBar\OpenHandler;
|
||||
use Joomla\Application\ApplicationEvents;
|
||||
use Joomla\CMS\Application\CMSApplicationInterface;
|
||||
use Joomla\CMS\Document\HtmlDocument;
|
||||
use Joomla\CMS\Event\Plugin\AjaxEvent;
|
||||
use Joomla\CMS\Log\Log;
|
||||
use Joomla\CMS\Log\LogEntry;
|
||||
use Joomla\CMS\Log\Logger\InMemoryLogger;
|
||||
use Joomla\CMS\Plugin\CMSPlugin;
|
||||
use Joomla\CMS\Profiler\Profiler;
|
||||
use Joomla\CMS\Session\Session;
|
||||
use Joomla\CMS\Uri\Uri;
|
||||
use Joomla\Database\DatabaseAwareTrait;
|
||||
use Joomla\Database\DatabaseInterface;
|
||||
use Joomla\Database\Event\ConnectionEvent;
|
||||
use Joomla\Event\DispatcherInterface;
|
||||
use Joomla\Event\Event;
|
||||
use Joomla\Event\Priority;
|
||||
use Joomla\Event\SubscriberInterface;
|
||||
use Joomla\Plugin\System\Debug\DataCollector\InfoCollector;
|
||||
use Joomla\Plugin\System\Debug\DataCollector\LanguageErrorsCollector;
|
||||
use Joomla\Plugin\System\Debug\DataCollector\LanguageFilesCollector;
|
||||
use Joomla\Plugin\System\Debug\DataCollector\LanguageStringsCollector;
|
||||
use Joomla\Plugin\System\Debug\DataCollector\MemoryCollector;
|
||||
use Joomla\Plugin\System\Debug\DataCollector\ProfileCollector;
|
||||
use Joomla\Plugin\System\Debug\DataCollector\QueryCollector;
|
||||
use Joomla\Plugin\System\Debug\DataCollector\RequestDataCollector;
|
||||
use Joomla\Plugin\System\Debug\DataCollector\SessionCollector;
|
||||
use Joomla\Plugin\System\Debug\DataCollector\UserCollector;
|
||||
use Joomla\Plugin\System\Debug\JavascriptRenderer;
|
||||
use Joomla\Plugin\System\Debug\JoomlaHttpDriver;
|
||||
use Joomla\Plugin\System\Debug\Storage\FileStorage;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Joomla! Debug plugin.
|
||||
*
|
||||
* @since 1.5
|
||||
*/
|
||||
final class Debug extends CMSPlugin implements SubscriberInterface
|
||||
{
|
||||
use DatabaseAwareTrait;
|
||||
|
||||
/**
|
||||
* List of protected keys that will be redacted in multiple data collected
|
||||
*
|
||||
* @since 4.2.4
|
||||
*/
|
||||
public const PROTECTED_COLLECTOR_KEYS = "/password|passwd|pwd|secret|token|server_auth|_pass|smtppass|otpKey|otep/i";
|
||||
|
||||
/**
|
||||
* True if debug lang is on.
|
||||
*
|
||||
* @var boolean
|
||||
* @since 3.0
|
||||
*/
|
||||
private $debugLang;
|
||||
|
||||
/**
|
||||
* Holds log entries handled by the plugin.
|
||||
*
|
||||
* @var LogEntry[]
|
||||
* @since 3.1
|
||||
*/
|
||||
private $logEntries = [];
|
||||
|
||||
/**
|
||||
* Holds all SHOW PROFILE FOR QUERY n, indexed by n-1.
|
||||
*
|
||||
* @var array
|
||||
* @since 3.1.2
|
||||
*/
|
||||
private $sqlShowProfileEach = [];
|
||||
|
||||
/**
|
||||
* Holds all EXPLAIN EXTENDED for all queries.
|
||||
*
|
||||
* @var array
|
||||
* @since 3.1.2
|
||||
*/
|
||||
private $explains = [];
|
||||
|
||||
/**
|
||||
* @var DebugBar
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private $debugBar;
|
||||
|
||||
/**
|
||||
* The query monitor.
|
||||
*
|
||||
* @var \Joomla\Database\Monitor\DebugMonitor
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private $queryMonitor;
|
||||
|
||||
/**
|
||||
* AJAX marker
|
||||
*
|
||||
* @var bool
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected $isAjax = false;
|
||||
|
||||
/**
|
||||
* Whether displaying a logs is enabled
|
||||
*
|
||||
* @var bool
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected $showLogs = false;
|
||||
|
||||
/**
|
||||
* The time spent in onAfterDisconnect()
|
||||
*
|
||||
* @var float
|
||||
* @since 4.4.0
|
||||
*/
|
||||
protected $timeInOnAfterDisconnect = 0;
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*
|
||||
* @since 4.1.3
|
||||
*/
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
'onBeforeCompileHead' => 'onBeforeCompileHead',
|
||||
'onAjaxDebug' => 'onAjaxDebug',
|
||||
'onBeforeRespond' => 'onBeforeRespond',
|
||||
'onAfterRespond' => [
|
||||
'onAfterRespond',
|
||||
Priority::MIN,
|
||||
],
|
||||
ApplicationEvents::AFTER_RESPOND => [
|
||||
'onAfterRespond',
|
||||
Priority::MIN,
|
||||
],
|
||||
'onAfterDisconnect' => 'onAfterDisconnect',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DispatcherInterface $dispatcher The object to observe -- event dispatcher.
|
||||
* @param array $config An optional associative array of configuration settings.
|
||||
* @param CMSApplicationInterface $app The app
|
||||
* @param DatabaseInterface $db The db
|
||||
*
|
||||
* @since 1.5
|
||||
*/
|
||||
public function __construct(DispatcherInterface $dispatcher, array $config, CMSApplicationInterface $app, DatabaseInterface $db)
|
||||
{
|
||||
parent::__construct($dispatcher, $config);
|
||||
|
||||
$this->setApplication($app);
|
||||
$this->setDatabase($db);
|
||||
|
||||
$this->debugLang = $this->getApplication()->get('debug_lang');
|
||||
|
||||
// Skip the plugin if debug is off
|
||||
if (!$this->debugLang && !$this->getApplication()->get('debug')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->getApplication()->set('gzip', false);
|
||||
ob_start();
|
||||
ob_implicit_flush(false);
|
||||
|
||||
/** @var \Joomla\Database\Monitor\DebugMonitor */
|
||||
$this->queryMonitor = $this->getDatabase()->getMonitor();
|
||||
|
||||
if (!$this->params->get('queries', 1)) {
|
||||
// Remove the database driver monitor
|
||||
$this->getDatabase()->setMonitor(null);
|
||||
}
|
||||
|
||||
$this->debugBar = new DebugBar();
|
||||
|
||||
// Check whether we want to track the request history for future use.
|
||||
if ($this->params->get('track_request_history', false)) {
|
||||
$storagePath = JPATH_CACHE . '/plg_system_debug_' . $this->getApplication()->getName();
|
||||
$this->debugBar->setStorage(new FileStorage($storagePath));
|
||||
}
|
||||
|
||||
$this->debugBar->setHttpDriver(new JoomlaHttpDriver($this->getApplication()));
|
||||
|
||||
$this->isAjax = $this->getApplication()->getInput()->get('option') === 'com_ajax'
|
||||
&& $this->getApplication()->getInput()->get('plugin') === 'debug' && $this->getApplication()->getInput()->get('group') === 'system';
|
||||
|
||||
$this->showLogs = (bool) $this->params->get('logs', true);
|
||||
|
||||
// Log deprecated class aliases
|
||||
if ($this->showLogs && $this->getApplication()->get('log_deprecated')) {
|
||||
foreach (\JLoader::getDeprecatedAliases() as $deprecation) {
|
||||
Log::add(
|
||||
sprintf(
|
||||
'%1$s has been aliased to %2$s and the former class name is deprecated. The alias will be removed in %3$s.',
|
||||
$deprecation['old'],
|
||||
$deprecation['new'],
|
||||
$deprecation['version']
|
||||
),
|
||||
Log::WARNING,
|
||||
'deprecation-notes'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an assets for debugger.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function onBeforeCompileHead()
|
||||
{
|
||||
// Only if debugging or language debug is enabled.
|
||||
if ((JDEBUG || $this->debugLang) && $this->isAuthorisedDisplayDebug() && $this->getApplication()->getDocument() instanceof HtmlDocument) {
|
||||
// Use our own jQuery and fontawesome instead of the debug bar shipped version
|
||||
$assetManager = $this->getApplication()->getDocument()->getWebAssetManager();
|
||||
$assetManager->registerAndUseStyle(
|
||||
'plg.system.debug',
|
||||
'plg_system_debug/debug.css',
|
||||
[],
|
||||
[],
|
||||
['fontawesome']
|
||||
);
|
||||
$assetManager->registerAndUseScript(
|
||||
'plg.system.debug',
|
||||
'plg_system_debug/debug.min.js',
|
||||
[],
|
||||
['defer' => true],
|
||||
['jquery']
|
||||
);
|
||||
}
|
||||
|
||||
// Disable asset media version if needed.
|
||||
if (JDEBUG && (int) $this->params->get('refresh_assets', 1) === 0) {
|
||||
$this->getApplication()->getDocument()->setMediaVersion('');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the debug info.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public function onAfterRespond()
|
||||
{
|
||||
$endTime = microtime(true) - $this->timeInOnAfterDisconnect;
|
||||
$endMemory = memory_get_peak_usage(false);
|
||||
|
||||
// Do not collect data if debugging or language debug is not enabled.
|
||||
if ((!JDEBUG && !$this->debugLang) || $this->isAjax) {
|
||||
return;
|
||||
}
|
||||
|
||||
// User has to be authorised to see the debug information.
|
||||
if (!$this->isAuthorisedDisplayDebug()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Load language.
|
||||
$this->loadLanguage();
|
||||
|
||||
$this->debugBar->addCollector(new InfoCollector($this->params, $this->debugBar->getCurrentRequestId()));
|
||||
$this->debugBar->addCollector(new UserCollector());
|
||||
|
||||
if (JDEBUG) {
|
||||
if ($this->params->get('memory', 1)) {
|
||||
$this->debugBar->addCollector(new MemoryCollector($this->params, $endMemory));
|
||||
}
|
||||
|
||||
if ($this->params->get('request', 1)) {
|
||||
$this->debugBar->addCollector(new RequestDataCollector());
|
||||
}
|
||||
|
||||
if ($this->params->get('session', 1)) {
|
||||
$this->debugBar->addCollector(new SessionCollector($this->params, true));
|
||||
}
|
||||
|
||||
if ($this->params->get('profile', 1)) {
|
||||
$this->debugBar->addCollector((new ProfileCollector($this->params))->setRequestEndTime($endTime));
|
||||
}
|
||||
|
||||
if ($this->params->get('queries', 1)) {
|
||||
// Remember session form token for possible future usage.
|
||||
$formToken = Session::getFormToken();
|
||||
|
||||
// Close session to collect possible session-related queries.
|
||||
$this->getApplication()->getSession()->close();
|
||||
|
||||
// Call $db->disconnect() here to trigger the onAfterDisconnect() method here in this class!
|
||||
$this->getDatabase()->disconnect();
|
||||
$this->debugBar->addCollector(new QueryCollector($this->params, $this->queryMonitor, $this->sqlShowProfileEach, $this->explains));
|
||||
}
|
||||
|
||||
if ($this->showLogs) {
|
||||
$this->collectLogs();
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->debugLang) {
|
||||
$this->debugBar->addCollector(new LanguageFilesCollector($this->params));
|
||||
$this->debugBar->addCollector(new LanguageStringsCollector($this->params));
|
||||
$this->debugBar->addCollector(new LanguageErrorsCollector($this->params));
|
||||
}
|
||||
|
||||
// Only render for HTML output.
|
||||
if (!($this->getApplication()->getDocument() instanceof HtmlDocument)) {
|
||||
$this->debugBar->stackData();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$debugBarRenderer = new JavascriptRenderer($this->debugBar, Uri::root(true) . '/media/vendor/debugbar/');
|
||||
$openHandlerUrl = Uri::base(true) . '/index.php?option=com_ajax&plugin=debug&group=system&format=raw&action=openhandler';
|
||||
$openHandlerUrl .= '&' . ($formToken ?? Session::getFormToken()) . '=1';
|
||||
|
||||
$debugBarRenderer->setOpenHandlerUrl($openHandlerUrl);
|
||||
|
||||
/**
|
||||
* @todo disable highlightjs from the DebugBar, import it through NPM
|
||||
* and deliver it through Joomla's API
|
||||
* Also every DebugBar script and stylesheet needs to use Joomla's API
|
||||
* $debugBarRenderer->disableVendor('highlightjs');
|
||||
*/
|
||||
|
||||
// Capture output.
|
||||
$contents = ob_get_contents();
|
||||
|
||||
if ($contents) {
|
||||
ob_end_clean();
|
||||
}
|
||||
|
||||
// No debug for Safari and Chrome redirection.
|
||||
if (
|
||||
strpos($contents, '<html><head><meta http-equiv="refresh" content="0;') === 0
|
||||
&& strpos(strtolower($_SERVER['HTTP_USER_AGENT'] ?? ''), 'webkit') !== false
|
||||
) {
|
||||
$this->debugBar->stackData();
|
||||
|
||||
echo $contents;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
echo str_replace('</body>', $debugBarRenderer->renderHead() . $debugBarRenderer->render() . '</body>', $contents);
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX handler
|
||||
*
|
||||
* @param AjaxEvent $event
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function onAjaxDebug(AjaxEvent $event)
|
||||
{
|
||||
// Do not render if debugging or language debug is not enabled.
|
||||
if (!JDEBUG && !$this->debugLang) {
|
||||
return;
|
||||
}
|
||||
|
||||
// User has to be authorised to see the debug information.
|
||||
if (!$this->isAuthorisedDisplayDebug() || !Session::checkToken('request')) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch ($this->getApplication()->getInput()->get('action')) {
|
||||
case 'openhandler':
|
||||
$handler = new OpenHandler($this->debugBar);
|
||||
$result = $handler->handle($this->getApplication()->getInput()->request->getArray(), false, false);
|
||||
|
||||
$event->addResult($result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to check if the current user is allowed to see the debug information or not.
|
||||
*
|
||||
* @return boolean True if access is allowed.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
private function isAuthorisedDisplayDebug(): bool
|
||||
{
|
||||
static $result;
|
||||
|
||||
if ($result !== null) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
// If the user is not allowed to view the output then end here.
|
||||
$filterGroups = (array) $this->params->get('filter_groups', []);
|
||||
|
||||
if (!empty($filterGroups)) {
|
||||
$userGroups = $this->getApplication()->getIdentity()->get('groups');
|
||||
|
||||
if (!array_intersect($filterGroups, $userGroups)) {
|
||||
$result = false;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$result = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect handler for database to collect profiling and explain information.
|
||||
*
|
||||
* @param ConnectionEvent $event Event object
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function onAfterDisconnect(ConnectionEvent $event)
|
||||
{
|
||||
if (!JDEBUG) {
|
||||
return;
|
||||
}
|
||||
|
||||
$startTime = microtime(true);
|
||||
|
||||
$db = $event->getDriver();
|
||||
|
||||
// Remove the monitor to avoid monitoring the following queries
|
||||
$db->setMonitor(null);
|
||||
|
||||
if ($this->params->get('query_profiles') && $db->getServerType() === 'mysql') {
|
||||
try {
|
||||
// Check if profiling is enabled.
|
||||
$db->setQuery("SHOW VARIABLES LIKE 'have_profiling'");
|
||||
$hasProfiling = $db->loadResult();
|
||||
|
||||
if ($hasProfiling) {
|
||||
// Run a SHOW PROFILE query.
|
||||
$db->setQuery('SHOW PROFILES');
|
||||
$sqlShowProfiles = $db->loadAssocList();
|
||||
|
||||
if ($sqlShowProfiles) {
|
||||
foreach ($sqlShowProfiles as $qn) {
|
||||
// Run SHOW PROFILE FOR QUERY for each query where a profile is available (max 100).
|
||||
$db->setQuery('SHOW PROFILE FOR QUERY ' . (int) $qn['Query_ID']);
|
||||
$this->sqlShowProfileEach[$qn['Query_ID'] - 1] = $db->loadAssocList();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$this->sqlShowProfileEach[0] = [['Error' => 'MySql have_profiling = off']];
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$this->sqlShowProfileEach[0] = [['Error' => $e->getMessage()]];
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->params->get('query_explains') && \in_array($db->getServerType(), ['mysql', 'postgresql'], true)) {
|
||||
$logs = $this->queryMonitor->getLogs();
|
||||
$boundParams = $this->queryMonitor->getBoundParams();
|
||||
|
||||
foreach ($logs as $k => $query) {
|
||||
$dbVersion56 = $db->getServerType() === 'mysql' && version_compare($db->getVersion(), '5.6', '>=');
|
||||
$dbVersion80 = $db->getServerType() === 'mysql' && version_compare($db->getVersion(), '8.0', '>=');
|
||||
|
||||
if ($dbVersion80) {
|
||||
$dbVersion56 = false;
|
||||
}
|
||||
|
||||
if ((stripos($query, 'select') === 0) || ($dbVersion56 && ((stripos($query, 'delete') === 0) || (stripos($query, 'update') === 0)))) {
|
||||
try {
|
||||
$queryInstance = $db->getQuery(true);
|
||||
$queryInstance->setQuery('EXPLAIN ' . ($dbVersion56 ? 'EXTENDED ' : '') . $query);
|
||||
|
||||
if ($boundParams[$k]) {
|
||||
foreach ($boundParams[$k] as $key => $obj) {
|
||||
$queryInstance->bind($key, $obj->value, $obj->dataType, $obj->length, $obj->driverOptions);
|
||||
}
|
||||
}
|
||||
|
||||
$this->explains[$k] = $db->setQuery($queryInstance)->loadAssocList();
|
||||
} catch (\Exception $e) {
|
||||
$this->explains[$k] = [['error' => $e->getMessage()]];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->timeInOnAfterDisconnect = microtime(true) - $startTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store log messages so they can be displayed later.
|
||||
* This function is passed log entries by JLogLoggerCallback.
|
||||
*
|
||||
* @param LogEntry $entry A log entry.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
* @deprecated 4.3 will be removed in 6.0
|
||||
* Use \Joomla\CMS\Log\Log::add(LogEntry $entry) instead
|
||||
*/
|
||||
public function logger(LogEntry $entry)
|
||||
{
|
||||
if (!$this->showLogs) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->logEntries[] = $entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect log messages.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private function collectLogs()
|
||||
{
|
||||
$loggerOptions = ['group' => 'default'];
|
||||
$logger = new InMemoryLogger($loggerOptions);
|
||||
$logEntries = $logger->getCollectedEntries();
|
||||
|
||||
if (!$this->logEntries && !$logEntries) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->logEntries) {
|
||||
$logEntries = array_merge($logEntries, $this->logEntries);
|
||||
}
|
||||
|
||||
$logDeprecated = $this->getApplication()->get('log_deprecated', 0);
|
||||
$logDeprecatedCore = $this->params->get('log-deprecated-core', 0);
|
||||
|
||||
$this->debugBar->addCollector(new MessagesCollector('log'));
|
||||
|
||||
if ($logDeprecated) {
|
||||
$this->debugBar->addCollector(new MessagesCollector('deprecated'));
|
||||
$this->debugBar->addCollector(new MessagesCollector('deprecation-notes'));
|
||||
}
|
||||
|
||||
if ($logDeprecatedCore) {
|
||||
$this->debugBar->addCollector(new MessagesCollector('deprecated-core'));
|
||||
}
|
||||
|
||||
foreach ($logEntries as $entry) {
|
||||
switch ($entry->category) {
|
||||
case 'deprecation-notes':
|
||||
if ($logDeprecated) {
|
||||
$this->debugBar[$entry->category]->addMessage($entry->message);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'deprecated':
|
||||
if (!$logDeprecated && !$logDeprecatedCore) {
|
||||
break;
|
||||
}
|
||||
|
||||
$file = '';
|
||||
$line = '';
|
||||
|
||||
// Find the caller, skip Log methods and trigger_error function
|
||||
foreach ($entry->callStack as $stackEntry) {
|
||||
if (
|
||||
!empty($stackEntry['class'])
|
||||
&& ($stackEntry['class'] === 'Joomla\CMS\Log\LogEntry' || $stackEntry['class'] === 'Joomla\CMS\Log\Log')
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
empty($stackEntry['class']) && !empty($stackEntry['function'])
|
||||
&& $stackEntry['function'] === 'trigger_error'
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$file = $stackEntry['file'] ?? '';
|
||||
$line = $stackEntry['line'] ?? '';
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
$category = $entry->category;
|
||||
$relative = $file ? str_replace(JPATH_ROOT, '', $file) : '';
|
||||
|
||||
if ($relative && 0 === strpos($relative, '/libraries/src')) {
|
||||
if (!$logDeprecatedCore) {
|
||||
break;
|
||||
}
|
||||
|
||||
$category .= '-core';
|
||||
} elseif (!$logDeprecated) {
|
||||
break;
|
||||
}
|
||||
|
||||
$message = [
|
||||
'message' => $entry->message,
|
||||
'caller' => $file . ':' . $line,
|
||||
// @todo 'stack' => $entry->callStack;
|
||||
];
|
||||
$this->debugBar[$category]->addMessage($message, 'warning');
|
||||
break;
|
||||
|
||||
case 'databasequery':
|
||||
// Should be collected by its own collector
|
||||
break;
|
||||
|
||||
default:
|
||||
switch ($entry->priority) {
|
||||
case Log::EMERGENCY:
|
||||
case Log::ALERT:
|
||||
case Log::CRITICAL:
|
||||
case Log::ERROR:
|
||||
$level = 'error';
|
||||
break;
|
||||
case Log::WARNING:
|
||||
$level = 'warning';
|
||||
break;
|
||||
default:
|
||||
$level = 'info';
|
||||
}
|
||||
|
||||
$this->debugBar['log']->addMessage($entry->category . ' - ' . $entry->message, $level);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add server timing headers when profile is activated.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.1.0
|
||||
*/
|
||||
public function onBeforeRespond(): void
|
||||
{
|
||||
if (!JDEBUG || !$this->params->get('profile', 1)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$metrics = '';
|
||||
$moduleTime = 0;
|
||||
$accessTime = 0;
|
||||
|
||||
foreach (Profiler::getInstance('Application')->getMarks() as $index => $mark) {
|
||||
// Ignore the before mark as the after one contains the timing of the action
|
||||
if (stripos($mark->label, 'before') !== false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Collect the module render time
|
||||
if (strpos($mark->label, 'mod_') !== false) {
|
||||
$moduleTime += $mark->time;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Collect the access render time
|
||||
if (strpos($mark->label, 'Access:') !== false) {
|
||||
$accessTime += $mark->time;
|
||||
continue;
|
||||
}
|
||||
|
||||
$desc = str_ireplace('after', '', $mark->label);
|
||||
$name = preg_replace('/[^\da-z]/i', '', $desc);
|
||||
$metrics .= sprintf('%s;dur=%f;desc="%s", ', $index . $name, $mark->time, $desc);
|
||||
|
||||
// Do not create too large headers, some web servers don't love them
|
||||
if (\strlen($metrics) > 3000) {
|
||||
$metrics .= 'System;dur=0;desc="Data truncated to 3000 characters", ';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Add the module entry
|
||||
$metrics .= 'Modules;dur=' . $moduleTime . ';desc="Modules", ';
|
||||
|
||||
// Add the access entry
|
||||
$metrics .= 'Access;dur=' . $accessTime . ';desc="Access"';
|
||||
|
||||
$this->getApplication()->setHeader('Server-Timing', $metrics);
|
||||
}
|
||||
}
|
||||
133
plugins/system/debug/src/JavascriptRenderer.php
Normal file
133
plugins/system/debug/src/JavascriptRenderer.php
Normal file
@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage System.Debug
|
||||
*
|
||||
* @copyright (C) 2019 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Plugin\System\Debug;
|
||||
|
||||
use DebugBar\DebugBar;
|
||||
use DebugBar\JavascriptRenderer as DebugBarJavascriptRenderer;
|
||||
use Joomla\CMS\Factory;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Custom JavascriptRenderer for DebugBar
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
class JavascriptRenderer extends DebugBarJavascriptRenderer
|
||||
{
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param \DebugBar\DebugBar $debugBar DebugBar instance
|
||||
* @param string $baseUrl The base URL from which assets will be served
|
||||
* @param string $basePath The path which assets are relative to
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function __construct(DebugBar $debugBar, $baseUrl = null, $basePath = null)
|
||||
{
|
||||
parent::__construct($debugBar, $baseUrl, $basePath);
|
||||
|
||||
// Disable features that loaded by Joomla! API, or not in use
|
||||
$this->setEnableJqueryNoConflict(false);
|
||||
$this->disableVendor('jquery');
|
||||
$this->disableVendor('fontawesome');
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the html to include needed assets
|
||||
*
|
||||
* Only useful if Assetic is not used
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function renderHead()
|
||||
{
|
||||
list($cssFiles, $jsFiles, $inlineCss, $inlineJs, $inlineHead) = $this->getAssets(null, self::RELATIVE_URL);
|
||||
$html = '';
|
||||
$doc = Factory::getApplication()->getDocument();
|
||||
|
||||
foreach ($cssFiles as $file) {
|
||||
$html .= sprintf('<link rel="stylesheet" type="text/css" href="%s">' . "\n", $file);
|
||||
}
|
||||
|
||||
foreach ($inlineCss as $content) {
|
||||
$html .= sprintf('<style>%s</style>' . "\n", $content);
|
||||
}
|
||||
|
||||
foreach ($jsFiles as $file) {
|
||||
$html .= sprintf('<script type="text/javascript" src="%s" defer></script>' . "\n", $file);
|
||||
}
|
||||
|
||||
$nonce = '';
|
||||
|
||||
if ($doc->cspNonce) {
|
||||
$nonce = ' nonce="' . $doc->cspNonce . '"';
|
||||
}
|
||||
|
||||
foreach ($inlineJs as $content) {
|
||||
$html .= sprintf('<script type="module"%s>%s</script>' . "\n", $nonce, $content);
|
||||
}
|
||||
|
||||
foreach ($inlineHead as $content) {
|
||||
$html .= $content . "\n";
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the code needed to display the debug bar
|
||||
*
|
||||
* AJAX request should not render the initialization code.
|
||||
*
|
||||
* @param boolean $initialize Whether or not to render the debug bar initialization code
|
||||
* @param boolean $renderStackedData Whether or not to render the stacked data
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function render($initialize = true, $renderStackedData = true)
|
||||
{
|
||||
$js = '';
|
||||
$doc = Factory::getApplication()->getDocument();
|
||||
|
||||
if ($initialize) {
|
||||
$js = $this->getJsInitializationCode();
|
||||
}
|
||||
|
||||
if ($renderStackedData && $this->debugBar->hasStackedData()) {
|
||||
foreach ($this->debugBar->getStackedData() as $id => $data) {
|
||||
$js .= $this->getAddDatasetCode($id, $data, '(stacked)');
|
||||
}
|
||||
}
|
||||
|
||||
$suffix = !$initialize ? '(ajax)' : null;
|
||||
$js .= $this->getAddDatasetCode($this->debugBar->getCurrentRequestId(), $this->debugBar->getData(), $suffix);
|
||||
|
||||
$nonce = '';
|
||||
|
||||
if ($doc->cspNonce) {
|
||||
$nonce = ' nonce="' . $doc->cspNonce . '"';
|
||||
}
|
||||
|
||||
if ($this->useRequireJs) {
|
||||
return "<script type=\"module\"$nonce>\nrequire(['debugbar'], function(PhpDebugBar){ $js });\n</script>\n";
|
||||
}
|
||||
|
||||
return "<script type=\"module\"$nonce>\n$js\n</script>\n";
|
||||
}
|
||||
}
|
||||
134
plugins/system/debug/src/JoomlaHttpDriver.php
Normal file
134
plugins/system/debug/src/JoomlaHttpDriver.php
Normal file
@ -0,0 +1,134 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage System.Debug
|
||||
*
|
||||
* @copyright (C) 2022 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Plugin\System\Debug;
|
||||
|
||||
use DebugBar\HttpDriverInterface;
|
||||
use Joomla\Application\WebApplicationInterface;
|
||||
use Joomla\CMS\Application\CMSApplicationInterface;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Joomla HTTP driver for DebugBar
|
||||
*
|
||||
* @since 4.1.5
|
||||
*/
|
||||
final class JoomlaHttpDriver implements HttpDriverInterface
|
||||
{
|
||||
/**
|
||||
* @var CMSApplicationInterface
|
||||
*
|
||||
* @since 4.1.5
|
||||
*/
|
||||
private $app;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*
|
||||
* @since 4.1.5
|
||||
*/
|
||||
private $dummySession = [];
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param CMSApplicationInterface $app
|
||||
*
|
||||
* @since 4.1.5
|
||||
*/
|
||||
public function __construct(CMSApplicationInterface $app)
|
||||
{
|
||||
$this->app = $app;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets HTTP headers
|
||||
*
|
||||
* @param array $headers
|
||||
*
|
||||
* @since 4.1.5
|
||||
*/
|
||||
public function setHeaders(array $headers)
|
||||
{
|
||||
if ($this->app instanceof WebApplicationInterface) {
|
||||
foreach ($headers as $name => $value) {
|
||||
$this->app->setHeader($name, $value, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the session is started
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 4.1.5
|
||||
*/
|
||||
public function isSessionStarted()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a value in the session
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $value
|
||||
*
|
||||
* @since 4.1.5
|
||||
*/
|
||||
public function setSessionValue($name, $value)
|
||||
{
|
||||
$this->dummySession[$name] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a value is in the session
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 4.1.5
|
||||
*/
|
||||
public function hasSessionValue($name)
|
||||
{
|
||||
return \array_key_exists($name, $this->dummySession);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a value from the session
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @since 4.1.5
|
||||
*/
|
||||
public function getSessionValue($name)
|
||||
{
|
||||
return $this->dummySession[$name] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a value from the session
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @since 4.1.5
|
||||
*/
|
||||
public function deleteSessionValue($name)
|
||||
{
|
||||
unset($this->dummySession[$name]);
|
||||
}
|
||||
}
|
||||
192
plugins/system/debug/src/Storage/FileStorage.php
Normal file
192
plugins/system/debug/src/Storage/FileStorage.php
Normal file
@ -0,0 +1,192 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage System.Debug
|
||||
*
|
||||
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Plugin\System\Debug\Storage;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Filesystem\Folder;
|
||||
use Joomla\CMS\User\UserFactoryInterface;
|
||||
use Joomla\Filesystem\File;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Stores collected data into files
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
class FileStorage extends \DebugBar\Storage\FileStorage
|
||||
{
|
||||
/**
|
||||
* Saves collected data
|
||||
*
|
||||
* @param string $id The log id
|
||||
* @param string $data The log data
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function save($id, $data)
|
||||
{
|
||||
if (!file_exists($this->dirname)) {
|
||||
Folder::create($this->dirname);
|
||||
}
|
||||
|
||||
$dataStr = '<?php die(); ?>#(^-^)#' . json_encode($data);
|
||||
|
||||
File::write($this->makeFilename($id), $dataStr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns collected data with the specified id
|
||||
*
|
||||
* @param string $id The log id
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function get($id)
|
||||
{
|
||||
$dataStr = file_get_contents($this->makeFilename($id));
|
||||
$dataStr = str_replace('<?php die(); ?>#(^-^)#', '', $dataStr);
|
||||
|
||||
return json_decode($dataStr, true) ?: [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a metadata about collected data
|
||||
*
|
||||
* @param array $filters Filtering options
|
||||
* @param integer $max The limit, items per page
|
||||
* @param integer $offset The offset
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function find(array $filters = [], $max = 20, $offset = 0)
|
||||
{
|
||||
// Loop through all .php files and remember the modified time and id.
|
||||
$files = [];
|
||||
|
||||
foreach (new \DirectoryIterator($this->dirname) as $file) {
|
||||
if ($file->getExtension() == 'php') {
|
||||
$files[] = [
|
||||
'time' => $file->getMTime(),
|
||||
'id' => $file->getBasename('.php'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Sort the files, newest first
|
||||
usort(
|
||||
$files,
|
||||
function ($a, $b) {
|
||||
if ($a['time'] === $b['time']) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return $a['time'] < $b['time'] ? 1 : -1;
|
||||
}
|
||||
);
|
||||
|
||||
// Load the metadata and filter the results.
|
||||
$results = [];
|
||||
$i = 0;
|
||||
|
||||
foreach ($files as $file) {
|
||||
// When filter is empty, skip loading the offset
|
||||
if ($i++ < $offset && empty($filters)) {
|
||||
$results[] = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
$data = $this->get($file['id']);
|
||||
|
||||
if (!$this->isSecureToReturnData($data)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$meta = $data['__meta'];
|
||||
unset($data);
|
||||
|
||||
if ($this->filter($meta, $filters)) {
|
||||
$results[] = $meta;
|
||||
}
|
||||
|
||||
if (\count($results) >= ($max + $offset)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return \array_slice($results, $offset, $max);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a full path to the file
|
||||
*
|
||||
* @param string $id The log id
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function makeFilename($id)
|
||||
{
|
||||
return $this->dirname . basename($id) . '.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the user is allowed to view the request. Users can only see their own requests.
|
||||
*
|
||||
* @param array $data The data item to process
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 4.2.4
|
||||
*/
|
||||
private function isSecureToReturnData($data): bool
|
||||
{
|
||||
/**
|
||||
* We only started this collector in Joomla 4.2.4 - any older files we have to assume are insecure.
|
||||
*/
|
||||
if (!\array_key_exists('juser', $data)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$currentUser = Factory::getUser();
|
||||
$currentUserId = $currentUser->id;
|
||||
$currentUserSuperAdmin = $currentUser->authorise('core.admin');
|
||||
|
||||
/**
|
||||
* Guests aren't allowed to look at other requests because there's no guarantee it's the same guest. Potentially
|
||||
* in the future this could be refined to check the session ID to show some requests. But it's unlikely we want
|
||||
* guests to be using the debug bar anyhow
|
||||
*/
|
||||
if ($currentUserId === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @var \Joomla\CMS\User\User $user */
|
||||
$user = Factory::getContainer()->get(UserFactoryInterface::class)
|
||||
->loadUserById($data['juser']['user_id']);
|
||||
|
||||
// Super users are allowed to look at other users requests. Otherwise users can only see their own requests.
|
||||
if ($currentUserSuperAdmin || $user->id === $currentUserId) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user