first commit

This commit is contained in:
2026-02-08 21:16:11 +01:00
commit e17b7026fd
8881 changed files with 1160453 additions and 0 deletions

View File

@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8"?>
<extension type="plugin" group="system" method="upgrade">
<name>plg_system_logrotation</name>
<author>Joomla! Project</author>
<creationDate>2018-05</creationDate>
<copyright>(C) 2018 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.9.0</version>
<description>PLG_SYSTEM_LOGROTATION_XML_DESCRIPTION</description>
<namespace path="src">Joomla\Plugin\System\LogRotation</namespace>
<files>
<folder plugin="logrotation">services</folder>
<folder>src</folder>
</files>
<languages folder="language">
<language tag="en-GB">language/en-GB/plg_system_logrotation.ini</language>
<language tag="en-GB">language/en-GB/plg_system_logrotation.sys.ini</language>
</languages>
<config>
<fields name="params">
<fieldset name="basic">
<field
name="cachetimeout"
type="integer"
label="PLG_SYSTEM_LOGROTATION_CACHETIMEOUT_LABEL"
first="0"
last="120"
step="1"
default="30"
filter="int"
validate="number"
/>
<field
name="logstokeep"
type="integer"
label="PLG_SYSTEM_LOGROTATION_LOGSTOKEEP_LABEL"
first="1"
last="10"
step="1"
default="1"
filter="int"
validate="number"
/>
<field
name="lastrun"
type="hidden"
default="0"
filter="integer"
/>
</fieldset>
</fields>
</config>
</extension>

View File

@@ -0,0 +1,49 @@
<?php
/**
* @package Joomla.Plugin
* @subpackage System.logrotation
*
* @copyright (C) 2023 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
defined('_JEXEC') or die;
use Joomla\CMS\Extension\PluginInterface;
use Joomla\CMS\Factory;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\Database\DatabaseInterface;
use Joomla\DI\Container;
use Joomla\DI\ServiceProviderInterface;
use Joomla\Event\DispatcherInterface;
use Joomla\Plugin\System\LogRotation\Extension\LogRotation;
return new class () implements ServiceProviderInterface {
/**
* Registers the service provider with a DI container.
*
* @param Container $container The DI container.
*
* @return void
*
* @since 4.4.0
*/
public function register(Container $container): void
{
$container->set(
PluginInterface::class,
function (Container $container) {
$dispatcher = $container->get(DispatcherInterface::class);
$plugin = new LogRotation(
$dispatcher,
(array) PluginHelper::getPlugin('system', 'logrotation')
);
$plugin->setApplication(Factory::getApplication());
$plugin->setDatabase($container->get(DatabaseInterface::class));
return $plugin;
}
);
}
};

View File

@@ -0,0 +1,238 @@
<?php
/**
* @package Joomla.Plugin
* @subpackage System.logrotation
*
* @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\LogRotation\Extension;
use Joomla\CMS\Cache\Cache;
use Joomla\CMS\Filesystem\Folder;
use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\Database\DatabaseAwareTrait;
use Joomla\Filesystem\File;
use Joomla\Filesystem\Path;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Joomla! Log Rotation plugin
*
* Rotate the log files created by Joomla core
*
* @since 3.9.0
*/
final class LogRotation extends CMSPlugin
{
use DatabaseAwareTrait;
/**
* Load the language file on instantiation.
*
* @var boolean
*
* @since 3.9.0
*/
protected $autoloadLanguage = true;
/**
* The log check and rotation code is triggered after the page has fully rendered.
*
* @return void
*
* @since 3.9.0
*/
public function onAfterRender()
{
// Get the timeout as configured in plugin parameters
/** @var \Joomla\Registry\Registry $params */
$cache_timeout = (int) $this->params->get('cachetimeout', 30);
$cache_timeout = 24 * 3600 * $cache_timeout;
$logsToKeep = (int) $this->params->get('logstokeep', 1);
// Do we need to run? Compare the last run timestamp stored in the plugin's options with the current
// timestamp. If the difference is greater than the cache timeout we shall not execute again.
$now = time();
$last = (int) $this->params->get('lastrun', 0);
if ((abs($now - $last) < $cache_timeout)) {
return;
}
// Update last run status
$this->params->set('lastrun', $now);
$paramsJson = $this->params->toString('JSON');
$db = $this->getDatabase();
$query = $db->getQuery(true)
->update($db->quoteName('#__extensions'))
->set($db->quoteName('params') . ' = :params')
->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
->where($db->quoteName('folder') . ' = ' . $db->quote('system'))
->where($db->quoteName('element') . ' = ' . $db->quote('logrotation'))
->bind(':params', $paramsJson);
try {
// Lock the tables to prevent multiple plugin executions causing a race condition
$db->lockTable('#__extensions');
} catch (\Exception $e) {
// If we can't lock the tables it's too risky to continue execution
return;
}
try {
// Update the plugin parameters
$result = $db->setQuery($query)->execute();
$this->clearCacheGroups(['com_plugins'], [0, 1]);
} catch (\Exception $exc) {
// If we failed to execute
$db->unlockTables();
$result = false;
}
try {
// Unlock the tables after writing
$db->unlockTables();
} catch (\Exception $e) {
// If we can't lock the tables assume we have somehow failed
$result = false;
}
// Stop on failure
if (!$result) {
return;
}
// Get the log path
$logPath = Path::clean($this->getApplication()->get('log_path'));
// Invalid path, stop processing further
if (!is_dir($logPath)) {
return;
}
$logFiles = $this->getLogFiles($logPath);
// Sort log files by version number in reserve order
krsort($logFiles, SORT_NUMERIC);
foreach ($logFiles as $version => $files) {
if ($version >= $logsToKeep) {
// Delete files which has version greater than or equals $logsToKeep
foreach ($files as $file) {
File::delete($logPath . '/' . $file);
}
} else {
// For files which has version smaller than $logsToKeep, rotate (increase version number)
foreach ($files as $file) {
$this->rotate($logPath, $file, $version);
}
}
}
}
/**
* Get log files from log folder
*
* @param string $path The folder to get log files
*
* @return array The log files in the given path grouped by version number (not rotated files has number 0)
*
* @since 3.9.0
*/
private function getLogFiles($path)
{
$logFiles = [];
$files = Folder::files($path, '\.php$');
foreach ($files as $file) {
$parts = explode('.', $file);
/*
* Rotated log file has this filename format [VERSION].[FILENAME].php. So if $parts has at least 3 elements
* and the first element is a number, we know that it's a rotated file and can get it's current version
*/
if (count($parts) >= 3 && is_numeric($parts[0])) {
$version = (int) $parts[0];
} else {
$version = 0;
}
if (!isset($logFiles[$version])) {
$logFiles[$version] = [];
}
$logFiles[$version][] = $file;
}
return $logFiles;
}
/**
* Method to rotate (increase version) of a log file
*
* @param string $path Path to file to rotate
* @param string $filename Name of file to rotate
* @param int $currentVersion The current version number
*
* @return void
*
* @since 3.9.0
*/
private function rotate($path, $filename, $currentVersion)
{
if ($currentVersion === 0) {
$rotatedFile = $path . '/1.' . $filename;
} else {
/*
* Rotated log file has this filename format [VERSION].[FILENAME].php. To rotate it, we just need to explode
* the filename into an array, increase value of first element (keep version) and implode it back to get the
* rotated file name
*/
$parts = explode('.', $filename);
$parts[0] = $currentVersion + 1;
$rotatedFile = $path . '/' . implode('.', $parts);
}
File::move($path . '/' . $filename, $rotatedFile);
}
/**
* Clears cache groups. We use it to clear the plugins cache after we update the last run timestamp.
*
* @param array $clearGroups The cache groups to clean
* @param array $cacheClients The cache clients (site, admin) to clean
*
* @return void
*
* @since 3.9.0
*/
private function clearCacheGroups(array $clearGroups, array $cacheClients = [0, 1])
{
foreach ($clearGroups as $group) {
foreach ($cacheClients as $client_id) {
try {
$options = [
'defaultgroup' => $group,
'cachebase' => $client_id ? JPATH_ADMINISTRATOR . '/cache' :
$this->getApplication()->get('cache_path', JPATH_SITE . '/cache'),
];
$cache = Cache::getInstance('callback', $options);
$cache->clean();
} catch (\Exception $e) {
// Ignore it
}
}
}
}
}