name : configmonitor.php
<?php
/**
 * @package   admintools
 * @copyright Copyright (c)2010-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;

defined('_JEXEC') || die;

/**
 * Monitors com_config changes and emails the user
 */
class AtsystemFeatureConfigmonitor extends AtsystemFeatureAbstract
{
	/** @var   int  The load order of each feature */
	protected $loadOrder = 220;

	/**
	 * Should we monitor changes to Global Configuration?
	 *
	 * @var   bool
	 */
	private $enabledGlobal = false;

	/**
	 * Should we monitor changes to Component Configuration?
	 *
	 * @var   bool
	 */
	private $enabledComponents = false;

	/**
	 * Which action should I take when a change is detected? 'email' for sending a warning email, 'block' for treating
	 * the request as a security exception.
	 *
	 * @var   string
	 */
	private $action = 'email';

	/**
	 * Is this feature enabled?
	 *
	 * @return bool
	 */
	public function isEnabled()
	{
		$this->enabledGlobal     = $this->cparams->getValue('configmonitor_global', 1) == 1;
		$this->enabledComponents = $this->cparams->getValue('configmonitor_components', 1) == 1;
		$this->action            = $this->cparams->getValue('configmonitor_action', 'email');

		return $this->enabledGlobal || $this->enabledComponents;
	}

	/**
	 * Disables creating new admins or updating new ones
	 */
	public function onAfterInitialise()
	{
		$input  = $this->input;
		$option = $input->getCmd('option', '');
		$task   = $input->getCmd('task', '');

		if ($option != 'com_config')
		{
			return;
		}

		$block = false;

		if ($this->enabledGlobal)
		{
			$block |= in_array($task, ['config.save.application.apply', 'config.save.application.save']);
		}

		if ($this->enabledComponents)
		{
			$block |= in_array($task, ['config.save.component.apply', 'config.save.component.save']);
		}

		if (!$block)
		{
			return;
		}

		// Get the correct reason (is this Global Configuration or component configuration)?
		$id            = $input->getInt('id', 0);
		$component     = $input->getCmd('component', '');
		$componentName = $this->getComponentName($id, $component);

		// Default reason for blocking / reporting: Global Configuration
		$jlang = Factory::getLanguage();
		$jlang->load('com_cpanel', JPATH_ADMINISTRATOR, 'en-GB', true);
		$jlang->load('com_cpanel', JPATH_ADMINISTRATOR, $jlang->getDefault(), true);
		$jlang->load('com_cpanel', JPATH_ADMINISTRATOR, null, true);
		$extraInfo = Text::_('COM_CPANEL_LINK_GLOBAL_CONFIG');

		// If, however, there is a component we need to report extension configuration monitor as the reason
		if (!empty($componentName))
		{
			$jlang = Factory::getLanguage();
			$jlang->load($componentName . '.sys', JPATH_ADMINISTRATOR, 'en-GB', true);
			$jlang->load($componentName . '.sys', JPATH_ADMINISTRATOR, $jlang->getDefault(), true);
			$jlang->load($componentName . '.sys', JPATH_ADMINISTRATOR, null, true);

			// Now set the extra information
			$extraInfo = Text::_($componentName);
		}

		// If we are set to block requests hook into Admin Tools' log and block system
		if ($this->action == 'block')
		{
			$this->exceptionsHandler->blockRequest('configmonitor', null, null, $extraInfo);

			return;
		}

		// Otherwise we need to send an email
		$this->sendEmail($extraInfo);
	}

	/**
	 * Get the component name based either on the extension ID or (preferably) the component name from the request.
	 *
	 * @param   int     $id         An extension ID passed in the request. Must belong to a component.
	 * @param   string  $component  A component name passed in the request.
	 *
	 * @return  string  The component name, or an empty string if there is no corresponding component.
	 */
	private function getComponentName($id, $component)
	{
		$component = trim(strtolower($component));

		// We have a component name
		if (!empty($component))
		{
			return $component;
		}

		// We don't have a component name or ID. Nothing to do
		if (empty($id))
		{
			return '';
		}

		// We have an ID. Try to get the component name from the #__extensions table.
		$db            = $this->container->db;
		$query         = $db->getQuery(true)
			->select($db->qn('element'))
			->from($db->qn('#__extensions'))
			->where($db->qn('extension_id') . ' = ' . $db->q((int) $id))
			->where($db->qn('type') . ' = ' . $db->q('component'));
		$componentName = $db->setQuery($query)->loadResult();

		if (empty($componentName))
		{
			return '';
		}

		return $componentName;
	}

	/**
	 * Sends a warning email to the addresses set up to receive security exception emails
	 *
	 * @param   string  $configArea  The human readable name of the configuration area being edited
	 */
	private function sendEmail($configArea)
	{
		// Load the component's administrator translation files
		$jlang = Factory::getLanguage();
		$jlang->load('com_admintools', JPATH_ADMINISTRATOR, 'en-GB', true);
		$jlang->load('com_admintools', JPATH_ADMINISTRATOR, $jlang->getDefault(), true);
		$jlang->load('com_admintools', JPATH_ADMINISTRATOR, null, true);

		// Construct the replacement table
		$substitutions = $this->exceptionsHandler->getEmailVariables(Text::_('COM_ADMINTOOLS_WAFEMAILTEMPLATE_REASON_ADMINLOGINFAIL'), [
			'[AREA]' => $configArea,
		]);

		// Let's get the most suitable email template
		$template = $this->exceptionsHandler->getEmailTemplate('configmonitor', true);

		// Got no template, the user didn't published any email template, or the template doesn't want us to
		// send a notification email. Anyway, let's stop here.
		if (!$template)
		{
			return true;
		}
		else
		{
			$subject = $template[0];
			$body    = $template[1];
		}

		foreach ($substitutions as $k => $v)
		{
			$subject = str_replace($k, $v, $subject);
			$body    = str_replace($k, $v, $body);
		}

		try
		{
			$config = $this->container->platform->getConfig();
			$mailer = Factory::getMailer();

			$mailfrom = $config->get('mailfrom');
			$fromname = $config->get('fromname');

			$recipients = explode(',', $this->cparams->getValue('emailbreaches', ''));
			$recipients = array_map('trim', $recipients);

			foreach ($recipients as $recipient)
			{
				if (empty($recipient))
				{
					continue;
				}

				// This line is required because SpamAssassin is BROKEN
				$mailer->Priority = 3;

				$mailer->isHtml(true);
				$mailer->setSender([$mailfrom, $fromname]);

				// Resets the recipients, otherwise they will pile up
				$mailer->clearAllRecipients();

				if ($mailer->addRecipient($recipient) === false)
				{
					// Failed to add a recipient?
					continue;
				}

				$mailer->setSubject($subject);
				$mailer->setBody($body);
				$mailer->Send();
			}
		}
		catch (Exception $e)
		{
			// Joomla! 3.5 and later throw an exception when crap happens instead of suppressing it and returning false
		}
	}
}

© 2025 Cubjrnet7