* @copyright Copyright (c) 2022 Samuel Marshall / JCH Optimize
* @license GNU/GPLv3, or later. See LICENSE file
*
* If LICENSE file missing, see .
*/
use CodeAlfa\Minify\Js;
use JchOptimize\ContainerFactory;
use JchOptimize\Core\Container\Container;
use JchOptimize\Core\Helper;
use JchOptimize\Core\PageCache\PageCache;
use JchOptimize\Core\SystemUri;
use Joomla\CMS\Application\CMSApplicationInterface;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Factory;
use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\CMS\Session\Session;
use Joomla\Event\DispatcherInterface;
defined('_JEXEC') or die ('Restricted access');
include_once JPATH_ADMINISTRATOR . '/components/com_jchoptimize/autoload.php';
class plgSystemJchoptimizepagecache extends CMSPlugin
{
/**
* If plugin is enabled
*
* @var bool
*/
public bool $enabled = true;
/**
* Application object
*
* @var ?CMSApplicationInterface
*/
protected $app = null;
/**
* Container object
*
* @var Container
*/
private Container $container;
/**
* Page Cache object
*
* @var PageCache
*/
private PageCache $pageCache;
/**
* Constructor
*
* @param DispatcherInterface $subject The object to observe
* @param array $config Optional associative array of configurations
*/
public function __construct(&$subject, $config = [])
{
parent::__construct($subject, $config);
//Disable if the component is not installed or disabled
if (!ComponentHelper::isEnabled('com_jchoptimize')) {
$this->enabled = false;
return;
}
//Disable if we can't get component's container
try {
$this->container = ContainerFactory::getContainer();
} catch (Exception $e) {
$this->enabled = false;
return;
}
//Disable if client is not Site
if (!$this->app->isClient('site')) {
$this->enabled = false;
return;
}
//Disable if site offline
if ($this->app->get('offline', '0')) {
$this->enabled = false;
return;
}
//Disable if there are messages enqueued
if ($this->app->getMessageQueue()) {
$this->enabled = false;
return;
}
//Disable if jchnooptimize set
if ($this->app->input->get('jchnooptimize') == '1'
|| $this->app->input->get('jchbackend') == '1') {
$this->enabled = false;
return;
}
//Disable if we couldn't get cache object
try {
$this->pageCache = $this->container->get(PageCache::class);
} catch (Exception $e) {
//didn't work, disable
$this->enabled = false;
return;
}
}
public function onAfterInitialise()
{
//If already disabled return
if (!$this->enabled) {
return;
}
if (JDEBUG) {
$this->pageCache->disableCaptureCache();
}
$this->pageCache->initialize();
}
/**
* After route event, have to check for excluded menu items here
*/
public function onAfterRoute()
{
//If already disabled return
if (!$this->enabled) {
return;
}
//If we're forcing ssl on the front end but not serving https, disable caching
if ($this->app->get('force_ssl') === 2 && SystemUri::currentUri()->getScheme() !== 'https') {
$this->enabled = false;
return;
}
try {
$excludedMenus = $this->container->get('params')->get('cache_exclude_menu', []);
$excludedComponents = $this->container->get('params')->get('cache_exclude_component', []);
if (in_array($this->app->input->get('Itemid', '', 'int'), $excludedMenus)
|| in_array($this->app->input->get('option', ''), $excludedComponents)
) {
$this->enabled = false;
$this->pageCache->disableCaching();
return;
}
//Now may be a good time to set Caching
$this->pageCache->setCaching();
} catch (Exception $e) {
}
}
public function onAfterRender()
{
if (!$this->enabled) {
return;
}
$html = $this->app->getBody();
if (!Helper::validateHtml($html)) {
$this->pageCache->disableCaching();
return;
}
if ($this->pageCache->isCaptureCacheEnabled()) {
$html = $this->addUpdateFormTokenAjax($html);
}
$this->app->setBody($html);
//Disable gzip so the HTML can be cached later
$this->app->set('gzip', false);
}
public function onAfterRespond()
{
if ($this->enabled) {
//Still need to validate the HTMl here. We may be on a redirect.
if (Helper::validateHtml($this->app->getBody())) {
$this->pageCache->store($this->app->getBody());
}
}
}
/**
* If Page Cache plugin is already disabled then this will disable the Page Cache object when it is constructed
*
* @return bool
*/
public function onPageCacheSetCaching(): bool
{
return $this->enabled;
}
public function onPageCacheGetKey()
{
return Factory::getLanguage()->getTag();
}
private function addUpdateFormTokenAjax($html)
{
$url = SystemUri::baseFull() . 'index.php?option=com_ajax&format=json&plugin=getformtoken';
/** @see plgSystemJchoptimizepagecache::onAjaxGetformtoken() */
$script = << {
const response = await fetch('$url');
if (response.ok) {
const jsonValue = await response.json();
return Promise.resolve(jsonValue);
}
}
updateFormToken().then(data => {
const formRegex = new RegExp('[0-9a-f]{32}');
for (let formToken of document.querySelectorAll('input[type=hidden]')){
if (formToken.value == '1' && formRegex.test(formToken.name)){
formToken.name = data.data[0];
}
}
const jsonRegex = new RegExp('"csrf\.token":"[^"]+"');
for(let scriptToken of document.querySelectorAll('script[type="application/json"]')){
if(scriptToken.classList.contains('joomla-script-options')){
let json = scriptToken.textContent;
if(jsonRegex.test(json)){
scriptToken.textContent = json.replace(jsonRegex, '"csrf.token":"' + data.data[0] + '"');
}
}
}
});
JS;
$htmlScript = '';
$html = str_replace('