first commit

This commit is contained in:
2025-06-17 11:53:18 +02:00
commit 9f0f7ba12b
8804 changed files with 1369176 additions and 0 deletions

59
plugins/system/cache/cache.xml vendored Normal file
View File

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8"?>
<extension type="plugin" group="system" method="upgrade">
<name>plg_system_cache</name>
<author>Joomla! Project</author>
<creationDate>2007-02</creationDate>
<copyright>(C) 2007 Open Source Matters, Inc.</copyright>
<license>GNU General Public License version 2 or later; see LICENSE.txt</license>
<authorEmail>admin@joomla.org</authorEmail>
<authorUrl>www.joomla.org</authorUrl>
<version>3.0.0</version>
<description>PLG_CACHE_XML_DESCRIPTION</description>
<namespace path="src">Joomla\Plugin\System\Cache</namespace>
<files>
<folder plugin="cache">services</folder>
<folder>src</folder>
</files>
<languages>
<language tag="en-GB">language/en-GB/plg_system_cache.ini</language>
<language tag="en-GB">language/en-GB/plg_system_cache.sys.ini</language>
</languages>
<config>
<fields name="params">
<fieldset name="basic">
<field
name="browsercache"
type="radio"
layout="joomla.form.field.radio.switcher"
label="PLG_CACHE_FIELD_BROWSERCACHE_LABEL"
default="0"
filter="integer"
>
<option value="0">JNO</option>
<option value="1">JYES</option>
</field>
<field
name="exclude_menu_items"
type="menuitem"
label="PLG_CACHE_FIELD_EXCLUDE_MENU_ITEMS_LABEL"
multiple="multiple"
filter="intarray"
layout="joomla.form.field.groupedlist-fancy-select"
/>
</fieldset>
<fieldset name="advanced">
<field
name="exclude"
type="textarea"
label="PLG_CACHE_FIELD_EXCLUDE_LABEL"
description="PLG_CACHE_FIELD_EXCLUDE_DESC"
rows="15"
filter="raw"
/>
</fieldset>
</fields>
</config>
</extension>

View File

@ -0,0 +1,52 @@
<?php
/**
* @package Joomla.Plugin
* @subpackage System.cache
*
* @copyright (C) 2022 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
\defined('_JEXEC') or die;
use Joomla\CMS\Cache\CacheControllerFactoryInterface;
use Joomla\CMS\Extension\PluginInterface;
use Joomla\CMS\Factory;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\CMS\Profiler\Profiler;
use Joomla\CMS\Router\SiteRouter;
use Joomla\DI\Container;
use Joomla\DI\ServiceProviderInterface;
use Joomla\Event\DispatcherInterface;
use Joomla\Plugin\System\Cache\Extension\Cache;
return new class () implements ServiceProviderInterface {
/**
* Registers the service provider with a DI container.
*
* @param Container $container The DI container.
*
* @return void
* @since 4.2.0
*/
public function register(Container $container)
{
$container->set(
PluginInterface::class,
function (Container $container) {
$plugin = PluginHelper::getPlugin('system', 'cache');
$dispatcher = $container->get(DispatcherInterface::class);
$documentFactory = $container->get('document.factory');
$cacheControllerFactory = $container->get(CacheControllerFactoryInterface::class);
$profiler = (\defined('JDEBUG') && JDEBUG) ? Profiler::getInstance('Application') : null;
$router = $container->has(SiteRouter::class) ? $container->get(SiteRouter::class) : null;
$plugin = new Cache($dispatcher, (array) $plugin, $documentFactory, $cacheControllerFactory, $profiler, $router);
$plugin->setApplication(Factory::getApplication());
return $plugin;
}
);
}
};

View File

@ -0,0 +1,382 @@
<?php
/**
* @package Joomla.Plugin
* @subpackage System.cache
*
* @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\Cache\Extension;
use Joomla\CMS\Cache\CacheController;
use Joomla\CMS\Cache\CacheControllerFactoryInterface;
use Joomla\CMS\Document\FactoryInterface as DocumentFactoryInterface;
use Joomla\CMS\Event\AbstractEvent;
use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\CMS\Profiler\Profiler;
use Joomla\CMS\Router\SiteRouter;
use Joomla\CMS\Uri\Uri;
use Joomla\Event\DispatcherInterface;
use Joomla\Event\Event;
use Joomla\Event\Priority;
use Joomla\Event\SubscriberInterface;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Joomla! Page Cache Plugin.
*
* @since 1.5
*/
final class Cache extends CMSPlugin implements SubscriberInterface
{
/**
* Cache instance.
*
* @var CacheController
* @since 1.5
*/
private $cache;
/**
* The application's document factory interface
*
* @var DocumentFactoryInterface
* @since 4.2.0
*/
private $documentFactory;
/**
* Cache controller factory interface
*
* @var CacheControllerFactoryInterface
* @since 4.2.0
*/
private $cacheControllerFactory;
/**
* The application profiler, used when Debug Site is set to Yes in Global Configuration.
*
* @var Profiler|null
* @since 4.2.0
*/
private $profiler;
/**
* The frontend router, injected by the service provider.
*
* @var SiteRouter|null
* @since 4.2.0
*/
private $router;
/**
* Constructor
*
* @param DispatcherInterface $dispatcher The object to observe
* @param array $config An optional associative
* array of configuration
* settings. Recognized key
* values include 'name',
* 'group', 'params',
* 'language'
* (this list is not meant
* to be comprehensive).
* @param DocumentFactoryInterface $documentFactory The application's
* document factory
* @param CacheControllerFactoryInterface $cacheControllerFactory Cache controller factory
* @param Profiler|null $profiler The application profiler
* @param SiteRouter|null $router The frontend router
*
* @since 4.2.0
*/
public function __construct(
DispatcherInterface $dispatcher,
array $config,
DocumentFactoryInterface $documentFactory,
CacheControllerFactoryInterface $cacheControllerFactory,
?Profiler $profiler,
?SiteRouter $router
) {
parent::__construct($dispatcher, $config);
$this->documentFactory = $documentFactory;
$this->cacheControllerFactory = $cacheControllerFactory;
$this->profiler = $profiler;
$this->router = $router;
}
/**
* Returns an array of CMS events this plugin will listen to and the respective handlers.
*
* @return array
*
* @since 4.2.0
*/
public static function getSubscribedEvents(): array
{
/**
* Note that onAfterRender and onAfterRespond must be the last handlers to run for this
* plugin to operate as expected. These handlers put pages into cache. We must make sure
* that a. the page SHOULD be cached and b. we are caching the complete page, as it's
* output to the browser.
*/
return [
'onAfterRoute' => 'onAfterRoute',
'onAfterRender' => ['onAfterRender', Priority::LOW],
'onAfterRespond' => ['onAfterRespond', Priority::LOW],
];
}
/**
* Returns a cached page if the current URL exists in the cache.
*
* @param Event $event The Joomla event being handled
*
* @return void
*
* @since 4.0.0
*/
public function onAfterRoute(Event $event)
{
if (!$this->appStateSupportsCaching()) {
return;
}
// If any `pagecache` plugins return false for onPageCacheSetCaching, do not use the cache.
PluginHelper::importPlugin('pagecache');
$results = $this->getApplication()->triggerEvent('onPageCacheSetCaching');
$this->getCacheController()->setCaching(!\in_array(false, $results, true));
$data = $this->getCacheController()->get($this->getCacheKey());
if ($data === false) {
// No cached data.
return;
}
// Set the page content from the cache and output it to the browser.
$this->getApplication()->setBody($data);
echo $this->getApplication()->toString((bool) $this->getApplication()->get('gzip'));
// Mark afterCache in debug and run debug onAfterRespond events, e.g. show Joomla Debug Console if debug is active.
if (JDEBUG) {
// Create a document instance and load it into the application.
$document = $this->documentFactory
->createDocument($this->getApplication()->getInput()->get('format', 'html'));
$this->getApplication()->loadDocument($document);
if ($this->profiler) {
$this->profiler->mark('afterCache');
}
$this->getDispatcher()->dispatch('onAfterRespond', AbstractEvent::create(
'onAfterRespond',
[
'subject' => $this->getApplication(),
]
));
}
// Closes the application.
$this->getApplication()->close();
}
/**
* Does the current application state allow for caching?
*
* The following conditions must be met:
* * This is the frontend application. This plugin does not apply to other applications.
* * This is a GET request. This plugin does not apply to POST, PUT etc.
* * There is no currently logged in user (pages might have userspecific content).
* * The message queue is empty.
*
* The first two tests are cached to make early returns possible; these conditions cannot change
* throughout the lifetime of the request.
*
* The other two tests MUST NOT be cached because autologin plugins may fire anytime within
* the application lifetime logging in a user and messages can be generated anytime within the
* application's lifetime.
*
* @return boolean
* @since 4.2.0
*/
private function appStateSupportsCaching(): bool
{
static $isSite = null;
static $isGET = null;
if ($isSite === null) {
$isSite = $this->getApplication()->isClient('site');
$isGET = $this->getApplication()->getInput()->getMethod() === 'GET';
}
// Boolean shortcircuit evaluation means this returns fast false when $isSite is false.
return $isSite
&& $isGET
&& $this->getApplication()->getIdentity()->guest
&& empty($this->getApplication()->getMessageQueue());
}
/**
* Get the cache controller
*
* @return CacheController
* @since 4.2.0
*/
private function getCacheController(): CacheController
{
if (!empty($this->cache)) {
return $this->cache;
}
// Set the cache options.
$options = [
'defaultgroup' => 'page',
'browsercache' => $this->params->get('browsercache', 0),
'caching' => false,
];
// Instantiate cache with previous options.
$this->cache = $this->cacheControllerFactory->createCacheController('page', $options);
return $this->cache;
}
/**
* Get a cache key for the current page based on the url and possible other factors.
*
* @return string
*
* @since 3.7
*/
private function getCacheKey(): string
{
static $key;
if (!$key) {
PluginHelper::importPlugin('pagecache');
$parts = $this->getApplication()->triggerEvent('onPageCacheGetKey');
$parts[] = Uri::getInstance()->toString();
$key = md5(serialize($parts));
}
return $key;
}
/**
* After Render Event. Check whether the current page is excluded from cache.
*
* @param Event $event The CMS event we are handling.
*
* @return void
*
* @since 3.9.12
*/
public function onAfterRender(Event $event)
{
if (!$this->appStateSupportsCaching() || $this->getCacheController()->getCaching() === false) {
return;
}
if ($this->isExcluded() === true) {
$this->getCacheController()->setCaching(false);
return;
}
// Disable compression before caching the page.
$this->getApplication()->set('gzip', false);
}
/**
* Check if the page is excluded from the cache or not.
*
* @return boolean True if the page is excluded else false
*
* @since 3.5
*/
private function isExcluded(): bool
{
// Check if menu items have been excluded.
$excludedMenuItems = $this->params->get('exclude_menu_items', []);
if ($excludedMenuItems) {
// Get the current menu item.
$active = $this->getApplication()->getMenu()->getActive();
if ($active && $active->id && \in_array((int) $active->id, (array) $excludedMenuItems)) {
return true;
}
}
// Check if regular expressions are being used.
$exclusions = $this->params->get('exclude', '');
if ($exclusions) {
// Convert the exclusions into a normalised array
$exclusions = str_replace(["\r\n", "\r"], "\n", $exclusions);
$exclusions = explode("\n", $exclusions);
$filterExpression = function ($x) {
return $x !== '';
};
$exclusions = array_filter($exclusions, $filterExpression);
// Gets the internal (non-SEF) and the external (possibly SEF) URIs.
$internalUrl = '/index.php?'
. Uri::getInstance()->buildQuery($this->router->getVars());
$externalUrl = Uri::getInstance()->toString();
$reduceCallback
= function (bool $carry, string $exclusion) use ($internalUrl, $externalUrl) {
// Test both external and internal URIs
return $carry && preg_match(
'#' . $exclusion . '#i',
$externalUrl . ' ' . $internalUrl,
$match
);
};
$excluded = array_reduce($exclusions, $reduceCallback, false);
if ($excluded) {
return true;
}
}
// If any pagecache plugins return true for onPageCacheIsExcluded, exclude.
PluginHelper::importPlugin('pagecache');
$results = $this->getApplication()->triggerEvent('onPageCacheIsExcluded');
return \in_array(true, $results, true);
}
/**
* After Respond Event. Stores page in cache.
*
* @param Event $event The application event we are handling.
*
* @return void
*
* @since 1.5
*/
public function onAfterRespond(Event $event)
{
if (!$this->appStateSupportsCaching() || $this->getCacheController()->getCaching() === false) {
return;
}
// Saves current page in cache.
$this->getCacheController()->store($this->getApplication()->getBody(), $this->getCacheKey());
}
}