Spade
Mini Shell
Helper/BreadcrumbsHelper.php000064400000011744151170236260012077
0ustar00<?php
/**
* @package Joomla.Site
* @subpackage mod_breadcrumbs
*
* @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\Module\Breadcrumbs\Site\Helper;
use Joomla\CMS\Application\CMSApplication;
use Joomla\CMS\Application\SiteApplication;
use Joomla\CMS\Factory;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Multilanguage;
use Joomla\Registry\Registry;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Helper for mod_breadcrumbs
*
* @since 1.5
*/
class BreadcrumbsHelper
{
/**
* Retrieve breadcrumb items
*
* @param Registry $params The module parameters
* @param SiteApplication $app The application
*
* @return array
*
* @since 4.4.0
*/
public function getBreadcrumbs(Registry $params, SiteApplication $app):
array
{
// Get the PathWay object from the application
$pathway = $app->getPathway();
$items = $pathway->getPathway();
$count = \count($items);
// Don't use $items here as it references JPathway properties
directly
$crumbs = [];
for ($i = 0; $i < $count; $i++) {
$crumbs[$i] = new \stdClass();
$crumbs[$i]->name =
stripslashes(htmlspecialchars($items[$i]->name, ENT_COMPAT,
'UTF-8'));
$crumbs[$i]->link = $items[$i]->link;
}
if ($params->get('showHome', 1)) {
array_unshift($crumbs, $this->getHomeItem($params, $app));
}
return $crumbs;
}
/**
* Retrieve home item (start page)
*
* @param Registry $params The module parameters
* @param SiteApplication $app The application
*
* @return object
*
* @since 4.4.0
*/
public function getHomeItem(Registry $params, SiteApplication $app):
object
{
$menu = $app->getMenu();
if (Multilanguage::isEnabled()) {
$home =
$menu->getDefault($app->getLanguage()->getTag());
} else {
$home = $menu->getDefault();
}
$item = new \stdClass();
$item->name =
htmlspecialchars($params->get('homeText',
$app->getLanguage()->_('MOD_BREADCRUMBS_HOME')),
ENT_COMPAT, 'UTF-8');
$item->link = 'index.php?Itemid=' . $home->id;
return $item;
}
/**
* Set the breadcrumbs separator for the breadcrumbs display.
*
* @param string $custom Custom xhtml compliant string to separate
the items of the breadcrumbs
*
* @return string Separator string
*
* @since 1.5
*
* @deprecated 4.4.0 will be removed in 6.0 as this function is not
used anymore
*/
public static function setSeparator($custom = null)
{
$lang = Factory::getApplication()->getLanguage();
// If a custom separator has not been provided we try to load a
template
// specific one first, and if that is not present we load the
default separator
if ($custom === null) {
if ($lang->isRtl()) {
$_separator = HTMLHelper::_('image',
'system/arrow_rtl.png', null, null, true);
} else {
$_separator = HTMLHelper::_('image',
'system/arrow.png', null, null, true);
}
} else {
$_separator = htmlspecialchars($custom, ENT_COMPAT,
'UTF-8');
}
return $_separator;
}
/**
* Retrieve breadcrumb items
*
* @param Registry $params The module parameters
* @param CMSApplication $app The application
*
* @return array
*
* @since 1.5
*
* @deprecated 4.4.0 will be removed in 6.0
* Use the non-static method getBreadcrumbs
* Example:
Factory::getApplication()->bootModule('mod_breadcrumbs',
'site')
*
->getHelper('BreadcrumbsHelper')
* ->getBreadcrumbs($params,
Factory::getApplication())
*/
public static function getList(Registry $params, CMSApplication $app)
{
return (new self())->getBreadcrumbs($params,
Factory::getApplication());
}
/**
* Retrieve home item (start page)
*
* @param Registry $params The module parameters
* @param CMSApplication $app The application
*
* @return object
*
* @since 4.2.0
*
* @deprecated 4.4.0 will be removed in 6.0
* Use the non-static method getHomeItem
* Example:
Factory::getApplication()->bootModule('mod_breadcrumbs',
'site')
*
->getHelper('BreadcrumbsHelper')
* ->getHomeItem($params,
Factory::getApplication())
*/
public static function getHome(Registry $params, CMSApplication $app)
{
return (new self())->getHomeItem($params,
Factory::getApplication());
}
}
Dispatcher/Dispatcher.php000064400000002504151170236260011435
0ustar00<?php
/**
* @package Joomla.Site
* @subpackage mod_breadcrumbs
*
* @copyright (C) 2023 Open Source Matters, Inc.
<https://www.joomla.org>
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\Module\Breadcrumbs\Site\Dispatcher;
use Joomla\CMS\Dispatcher\AbstractModuleDispatcher;
use Joomla\CMS\Helper\HelperFactoryAwareInterface;
use Joomla\CMS\Helper\HelperFactoryAwareTrait;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Dispatcher class for mod_breadcrumbs
*
* @since 4.4.0
*/
class Dispatcher extends AbstractModuleDispatcher implements
HelperFactoryAwareInterface
{
use HelperFactoryAwareTrait;
/**
* Returns the layout data.
*
* @return array
*
* @since 4.4.0
*/
protected function getLayoutData(): array
{
$data = parent::getLayoutData();
$data['list'] =
$this->getHelperFactory()->getHelper('BreadcrumbsHelper')->getBreadcrumbs($data['params'],
$data['app']);
$data['count'] = \count($data['list']);
if (!$data['params']->get('showHome', 1)) {
$data['homeCrumb'] =
$this->getHelperFactory()->getHelper('BreadcrumbsHelper')->getHomeItem($data['params'],
$data['app']);
}
return $data;
}
}
Extension/Featuring.php000064400000036332151171330440011163
0ustar00<?php
/**
* @package Joomla.Plugin
* @subpackage Workflow.featuring
*
* @copyright (C) 2020 Open Source Matters, Inc.
<https://www.joomla.org>
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\Plugin\Workflow\Featuring\Extension;
use Joomla\CMS\Event\AbstractEvent;
use Joomla\CMS\Event\Model;
use Joomla\CMS\Event\Table\BeforeStoreEvent;
use Joomla\CMS\Event\View\DisplayEvent;
use Joomla\CMS\Event\Workflow\WorkflowFunctionalityUsedEvent;
use Joomla\CMS\Event\Workflow\WorkflowTransitionEvent;
use Joomla\CMS\Form\Form;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\Model\DatabaseModelInterface;
use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\CMS\Table\ContentHistory;
use Joomla\CMS\Table\TableInterface;
use Joomla\CMS\Workflow\WorkflowPluginTrait;
use Joomla\CMS\Workflow\WorkflowServiceInterface;
use Joomla\Component\Content\Administrator\Event\Model\FeatureEvent;
use Joomla\Event\EventInterface;
use Joomla\Event\SubscriberInterface;
use Joomla\Registry\Registry;
use Joomla\String\Inflector;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Workflow Featuring Plugin
*
* @since 4.0.0
*/
final class Featuring extends CMSPlugin implements SubscriberInterface
{
use WorkflowPluginTrait;
/**
* Load the language file on instantiation.
*
* @var boolean
* @since 4.0.0
*/
protected $autoloadLanguage = true;
/**
* The name of the supported functionality to check against
*
* @var string
* @since 4.0.0
*/
protected $supportFunctionality = 'core.featured';
/**
* Returns an array of events this subscriber will listen to.
*
* @return array
*
* @since 4.0.0
*/
public static function getSubscribedEvents(): array
{
return [
'onAfterDisplay' =>
'onAfterDisplay',
'onContentBeforeChangeFeatured' =>
'onContentBeforeChangeFeatured',
'onContentBeforeSave' =>
'onContentBeforeSave',
'onContentPrepareForm' =>
'onContentPrepareForm',
'onContentVersioningPrepareTable' =>
'onContentVersioningPrepareTable',
'onTableBeforeStore' =>
'onTableBeforeStore',
'onWorkflowAfterTransition' =>
'onWorkflowAfterTransition',
'onWorkflowBeforeTransition' =>
'onWorkflowBeforeTransition',
'onWorkflowFunctionalityUsed' =>
'onWorkflowFunctionalityUsed',
];
}
/**
* The form event.
*
* @param Model\PrepareFormEvent $event The event
*
* @since 4.0.0
*/
public function onContentPrepareForm(Model\PrepareFormEvent $event)
{
$form = $event->getForm();
$data = $event->getData();
$context = $form->getName();
// Extend the transition form
if ($context === 'com_workflow.transition') {
$this->enhanceWorkflowTransitionForm($form, $data);
return;
}
$this->enhanceItemForm($form, $data);
}
/**
* Disable certain fields in the item form view, when we want to take
over this function in the transition
* Check also for the workflow implementation and if the field exists
*
* @param Form $form The form
* @param object $data The data
*
* @return boolean
*
* @since 4.0.0
*/
protected function enhanceItemForm(Form $form, $data)
{
$context = $form->getName();
if (!$this->isSupported($context)) {
return true;
}
$parts = explode('.', $context);
$component =
$this->getApplication()->bootComponent($parts[0]);
$modelName = $component->getModelName($context);
$table = $component->getMVCFactory()->createModel($modelName,
$this->getApplication()->getName(), ['ignore_request' =>
true])
->getTable();
$fieldname = $table->getColumnAlias('featured');
$options = $form->getField($fieldname)->options;
$value = $data->$fieldname ?? $form->getValue($fieldname,
null, 0);
$text = '-';
$textclass = 'body';
switch ($value) {
case 1:
$textclass = 'success';
break;
case 0:
case -2:
$textclass = 'danger';
}
if (!empty($options)) {
foreach ($options as $option) {
if ($option->value == $value) {
$text = $option->text;
break;
}
}
}
$form->setFieldAttribute($fieldname, 'type',
'spacer');
$label = '<span class="text-' . $textclass .
'">' . htmlentities($text, ENT_COMPAT, 'UTF-8')
. '</span>';
$form->setFieldAttribute(
$fieldname,
'label',
Text::sprintf('PLG_WORKFLOW_FEATURING_FEATURED',
$label)
);
return true;
}
/**
* Manipulate the generic list view
*
* @param DisplayEvent $event
*
* @return void
*
* @since 4.0.0
*/
public function onAfterDisplay(DisplayEvent $event)
{
if
(!$this->getApplication()->isClient('administrator')) {
return;
}
$component = $event->getArgument('extensionName');
$section = $event->getArgument('section');
// We need the single model context for checking for workflow
$singularsection = Inflector::singularize($section);
if (!$this->isSupported($component . '.' .
$singularsection)) {
return;
}
// List of related batch functions we need to hide
$states = [
'featured',
'unfeatured',
];
$js = "
document.addEventListener('DOMContentLoaded', function()
{
var dropdown =
document.getElementById('toolbar-status-group');
if (!dropdown)
{
return;
}
" . json_encode($states) . ".forEach((action) => {
var button =
document.getElementById('status-group-children-' + action);
if (button)
{
button.classList.add('d-none');
}
});
});
";
$this->getApplication()->getDocument()->addScriptDeclaration($js);
}
/**
* Check if we can execute the transition
*
* @param WorkflowTransitionEvent $event
*
* @return boolean
*
* @since 4.0.0
*/
public function onWorkflowBeforeTransition(WorkflowTransitionEvent
$event)
{
$context = $event->getArgument('extension');
$transition = $event->getArgument('transition');
$pks = $event->getArgument('pks');
if (!$this->isSupported($context) ||
!is_numeric($transition->options->get('featuring'))) {
return true;
}
$value = $transition->options->get('featuring');
if (!is_numeric($value)) {
return true;
}
/**
* Here it becomes tricky. We would like to use the component
models featured method, so we will
* Execute the normal "onContentBeforeChangeFeatured"
plugins. But they could cancel the execution,
* So we have to precheck and cancel the whole transition stuff if
not allowed.
*/
$this->getApplication()->set('plgWorkflowFeaturing.' .
$context, $pks);
// Trigger the change state event.
$eventResult =
$this->getApplication()->getDispatcher()->dispatch(
'onContentBeforeChangeFeatured',
AbstractEvent::create(
'onContentBeforeChangeFeatured',
[
'eventClass' =>
'Joomla\Component\Content\Administrator\Event\Model\FeatureEvent',
'subject' => $this,
'extension' => $context,
'pks' => $pks,
'value' => $value,
'abort' => false,
'abortReason' => '',
]
)
);
// Release allowed pks, the job is done
$this->getApplication()->set('plgWorkflowFeaturing.' .
$context, []);
if ($eventResult->getArgument('abort')) {
$event->setStopTransition();
return false;
}
return true;
}
/**
* Change Feature State of an item. Used to disable feature state
change
*
* @param WorkflowTransitionEvent $event
*
* @return void
*
* @since 4.0.0
*/
public function onWorkflowAfterTransition(WorkflowTransitionEvent
$event): void
{
$context = $event->getArgument('extension');
$extensionName = $event->getArgument('extensionName');
$transition = $event->getArgument('transition');
$pks = $event->getArgument('pks');
if (!$this->isSupported($context)) {
return;
}
$component =
$this->getApplication()->bootComponent($extensionName);
$value = $transition->options->get('featuring');
if (!is_numeric($value)) {
return;
}
$options = [
'ignore_request' => true,
// We already have triggered onContentBeforeChangeFeatured, so
use our own
'event_before_change_featured' =>
'onWorkflowBeforeChangeFeatured',
];
$modelName = $component->getModelName($context);
$model = $component->getMVCFactory()->createModel($modelName,
$this->getApplication()->getName(), $options);
$model->featured($pks, $value);
}
/**
* Change Feature State of an item. Used to disable Feature state
change
*
* @param FeatureEvent $event
*
* @return boolean
*
* @throws \Exception
* @since 4.0.0
*/
public function onContentBeforeChangeFeatured(FeatureEvent $event)
{
$extension = $event->getArgument('extension');
$pks = $event->getArgument('pks');
if (!$this->isSupported($extension)) {
return true;
}
// We have allowed the pks, so we're the one who triggered
// With onWorkflowBeforeTransition => free pass
if
($this->getApplication()->get('plgWorkflowFeaturing.' .
$extension) === $pks) {
return true;
}
$event->setAbort('PLG_WORKFLOW_FEATURING_CHANGE_STATE_NOT_ALLOWED');
}
/**
* The save event.
*
* @param Model\BeforeSaveEvent $event
*
* @return boolean
*
* @since 4.0.0
*/
public function onContentBeforeSave(Model\BeforeSaveEvent $event)
{
$context = $event->getContext();
if (!$this->isSupported($context)) {
return true;
}
/** @var TableInterface $table */
$table = $event->getItem();
$data = $event->getData();
$keyName = $table->getColumnAlias('featured');
// Check for the old value
$article = clone $table;
$article->load($table->id);
/**
* We don't allow the change of the feature state when we use
the workflow
* As we're setting the field to disabled, no value should be
there at all
*/
if (isset($data[$keyName])) {
$this->getApplication()->enqueueMessage(
$this->getApplication()->getLanguage()->_('PLG_WORKFLOW_FEATURING_CHANGE_STATE_NOT_ALLOWED'),
'error'
);
return false;
}
return true;
}
/**
* We remove the featured field from the versioning
*
* @param EventInterface $event
*
* @return boolean
*
* @since 4.0.0
*/
public function onContentVersioningPrepareTable(EventInterface $event)
{
$subject = $event->getArgument('subject');
$context = $event->getArgument('extension');
if (!$this->isSupported($context)) {
return true;
}
$parts = explode('.', $context);
$component =
$this->getApplication()->bootComponent($parts[0]);
$modelName = $component->getModelName($context);
$model = $component->getMVCFactory()->createModel($modelName,
$this->getApplication()->getName(), ['ignore_request' =>
true]);
$table = $model->getTable();
$subject->ignoreChanges[] =
$table->getColumnAlias('featured');
}
/**
* Pre-processor for $table->store($updateNulls)
*
* @param BeforeStoreEvent $event The event to handle
*
* @return void
*
* @since 4.0.0
*/
public function onTableBeforeStore(BeforeStoreEvent $event)
{
$subject = $event->getArgument('subject');
if (!($subject instanceof ContentHistory)) {
return;
}
$parts = explode('.', $subject->item_id);
$typeAlias = $parts[0] . (isset($parts[1]) ? '.' .
$parts[1] : '');
if (!$this->isSupported($typeAlias)) {
return;
}
$component =
$this->getApplication()->bootComponent($parts[0]);
$modelName = $component->getModelName($typeAlias);
$model = $component->getMVCFactory()->createModel($modelName,
$this->getApplication()->getName(), ['ignore_request' =>
true]);
$table = $model->getTable();
$field = $table->getColumnAlias('featured');
$versionData = new Registry($subject->version_data);
$versionData->remove($field);
$subject->version_data = $versionData->toString();
}
/**
* Check if the current plugin should execute workflow related
activities
*
* @param string $context
*
* @return boolean
*
* @since 4.0.0
*/
protected function isSupported($context)
{
if (!$this->checkAllowedAndForbiddenlist($context) ||
!$this->checkExtensionSupport($context, $this->supportFunctionality))
{
return false;
}
$parts = explode('.', $context);
// We need at least the extension + view for loading the table
fields
if (\count($parts) < 2) {
return false;
}
$component =
$this->getApplication()->bootComponent($parts[0]);
if (
!$component instanceof WorkflowServiceInterface
|| !$component->isWorkflowActive($context)
||
!$component->supportFunctionality($this->supportFunctionality,
$context)
) {
return false;
}
$modelName = $component->getModelName($context);
$model = $component->getMVCFactory()->createModel($modelName,
$this->getApplication()->getName(), ['ignore_request' =>
true]);
if (!$model instanceof DatabaseModelInterface ||
!method_exists($model, 'featured')) {
return false;
}
$table = $model->getTable();
if (!$table instanceof TableInterface ||
!$table->hasField('featured')) {
return false;
}
return true;
}
/**
* If plugin supports the functionality we set the used variable
*
* @param WorkflowFunctionalityUsedEvent $event
*
* @since 4.0.0
*/
public function
onWorkflowFunctionalityUsed(WorkflowFunctionalityUsedEvent $event)
{
$functionality = $event->getArgument('functionality');
if ($functionality !== 'core.featured') {
return;
}
$event->setUsed();
}
}
JavascriptRenderer.php000064400000007723151171404570011070 0ustar00<?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";
}
}
JoomlaHttpDriver.php000064400000005076151171404570010527 0ustar00<?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]);
}
}
DataFormatter.php000064400000005074151171404570010025 0ustar00<?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;
}
}
Extension/Debug.php000064400000056123151171404570010273 0ustar00<?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\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);
}
}
AbstractDataCollector.php000064400000004531151171404570011471
0ustar00<?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
*/
public 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);
}
}
DataCollector/ProfileCollector.php000064400000020540151171404570013252
0ustar00<?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' => '{}',
],
];
}
}
DataCollector/LanguageErrorsCollector.php000064400000006617151171404570014603
0ustar00<?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;
}
}
DataCollector/SessionCollector.php000064400000005641151171404570013302
0ustar00<?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' => '[]',
],
];
}
}
DataCollector/LanguageStringsCollector.php000064400000011452151171404570014751
0ustar00<?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());
}
}
DataCollector/QueryCollector.php000064400000016123151171404570012761
0ustar00<?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;
}
}
DataCollector/RequestDataCollector.php000064400000003573151171404570014103
0ustar00<?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;
}
}
DataCollector/MemoryCollector.php000064400000006334151171404570013127
0ustar00<?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'",
],
];
}
}
DataCollector/LanguageFilesCollector.php000064400000006030151171404570014356
0ustar00<?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',
];
}
}
DataCollector/UserCollector.php000064400000002574151171404570012577
0ustar00<?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;
}
}
DataCollector/InfoCollector.php000064400000012647151171404570012556
0ustar00<?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'] ?? '',
];
}
}
Storage/FileStorage.php000064400000011777151171404570011107
0ustar00<?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;
}
}