Spade

Mini Shell

Directory:~$ /home/lmsyaran/www/khsh/
Upload File

[Home] [System Details] [Kill Me]
Current File:~$ /home/lmsyaran/www/khsh/src.tar

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;
    }
}