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

namespace Akeeba\Component\AdminTools\Administrator\Scanner;

defined('_JEXEC') || die;

use Akeeba\Component\AdminTools\Administrator\Scanner\Logger\Logger;
use Akeeba\Component\AdminTools\Administrator\Scanner\Util\Configuration;
use Akeeba\Component\AdminTools\Administrator\Scanner\Util\Session;
use Akeeba\Component\AdminTools\Administrator\Table\ScanTable;
use Exception;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text as JText;
use Joomla\CMS\Mail\MailerFactoryInterface;
use Joomla\Database\DatabaseDriver;
use Joomla\Database\DatabaseInterface;

/**
 * Handles sending a notification email after the PHP File Change Scanner has finished executing
 */
class Email
{
	/**
	 * Scanner configuration
	 *
	 * @var   Configuration
	 */
	private $configuration;

	/**
	 * Logger
	 *
	 * @var   Logger
	 */
	private $logger;

	/**
	 * Temporary session storage
	 *
	 * @var   Session
	 */
	private $session;

	/**
	 * Email constructor.
	 *
	 * @param   Configuration  $configuration  Scanner configuration
	 * @param   Session        $session        Temporary session storage
	 * @param   Logger         $logger         Logger
	 */
	public function __construct(Configuration $configuration, Session $session, Logger $logger)
	{
		$this->configuration = $configuration;
		$this->session       = $session;
		$this->logger        = $logger;
	}

	public function sendEmail()
	{
		// If no email is set, quit
		$email = trim($this->configuration->get('scanemail'));

		if (empty($email))
		{
			$this->logger->debug("No email is set. Scan results will not sent by email.");

			return;
		}

		$this->logger->debug(sprintf("%s: Email address set to %s", __CLASS__, $email));

		// Get the ID of the scan
		$scanID = $this->session->get('scanID');
		$this->logger->debug(sprintf("%s: Latest scan ID is %s", __CLASS__, $scanID));

		// Get scan statistics
		$this->logger->debug(sprintf("%s: Getting scan statistics", __CLASS__));

		/** @var DatabaseDriver $db */
		$db = Factory::getContainer()->get(DatabaseInterface::class);
		/** @var ScanTable $scanTable */
		$scanTable = new ScanTable($db);
		$scanTable->load($scanID);

		// Populate table data for new, modified and suspicious files
		$this->logger->debug(sprintf("%s: Populating table", __CLASS__));

		$bodyNewFiles      = '';
		$bodyAttentionFiles = '';

		$query      = (method_exists($db, 'createQuery') ? $db->createQuery() : $db->getQuery(true))
			->select('COUNT(*)')
			->from($db->quoteName('#__admintools_scanalerts'))
			->where($db->quoteName('scan_id') . ' = :scan_id')
			->where($db->quoteName('acknowledged') . ' = 0')
			->bind(':scan_id', $scanID);
		$totalFiles = $db->setQuery($query)->loadResult() ?: 0;

		$segments = (int) ($totalFiles / 100) + 1;
		$this->logger->debug(sprintf("%s: Processing file list in $segments segment(s)", __CLASS__));

		$modified   = 0;
		$new        = 0;
		$suspicious = 0;

		$query->clear('select')
			->select('*');

		for ($i = 0; $i < $segments; $i++)
		{
			$limitStart = 100 * $i;
			$query->setLimit(100, $limitStart);
			$files = $db->setQuery($query)->loadObjectList() ?: [];

			if (!count($files))
			{
				continue;
			}

			foreach ($files as $file)
			{
				if ($file->threat_score > 0)
				{
					$suspicious++;
				}

				$fileRow = "<tr><td>{$file->path}</td><td>{$file->threat_score}</td></tr>\n";

				if (empty($file->diff))
				{
					$bodyNewFiles .= $fileRow;
					$new++;
				}
				else
				{
					$bodyAttentionFiles .= $fileRow;
					$modified++;
				}
			}
		}

		// Conditional email sending only when actionable items are found
		if ($this->configuration->get('scan_conditional_email'))
		{
			$this->logger->info("You have enabled conditional email sending. Calculating number of actionable items (number of added, modified and suspicious files)");

			$numActionableFiles = $modified + $new + $suspicious;

			if ($numActionableFiles < 1)
			{
				$this->logger->info("No actionable items were detected. An email will NOT be sent this time.");

				return;
			}
		}

		$this->logger->debug(sprintf("%s: Preparing email text", __CLASS__));

		// Prepare the email body
		$body = '<html lang="en"><head>' . JText::_('COM_ADMINTOOLS_LBL_SCANS_EMAIL_HEADING') . '<title></title></head><body>';
		$body .= '<h1>' . JText::_('COM_ADMINTOOLS_LBL_SCANS_EMAIL_HEADING') . "</h1><hr/>\n";
		$body .= '<h2>' . JText::_('COM_ADMINTOOLS_LBL_SCANS_EMAIL_OVERVIEW') . "</h2>\n";
		$body .= "<p>\n";
		$body .= '<strong>' . JText::_('COM_ADMINTOOLS_SCAN_LBL_TOTAL') . "</strong>: " . $totalFiles . "<br/>\n";
		$body .= '<strong>' . JText::_('COM_ADMINTOOLS_SCAN_LBL_MODIFIED') . "</strong>: " . $modified . "<br/>\n";

		$body .= '<strong>' . JText::_('COM_ADMINTOOLS_SCAN_LBL_ADDED') . "</strong>: " . $new . "<br/>\n";
		$body .= '<strong>' . JText::_('COM_ADMINTOOLS_SCAN_LBL_SUSPICIOUS') . "</strong>: " . $suspicious . "<br/>\n";
		$body .= "</p>\n";

		// Add the new files report only if we really have some files
		if ($bodyNewFiles)
		{
			$body .= '<hr/><h2>' . JText::_('COM_ADMINTOOLS_SCAN_LBL_ADDED') . "</h2>\n";
			$body .= "<table width=\"100%\">\n";
			$body .= "\t<thead>\n";
			$body .= "\t<tr>\n";
			$body .= "\t\t<th>" . JText::_('COM_ADMINTOOLS_SCANALERTS_LBL_PATH') . "</th>\n";
			$body .= "\t\t<th width=\"50\">" . JText::_('COM_ADMINTOOLS_SCANALERTS_LBL_THREAT_SCORE') . "</th>\n";
			$body .= "\t</tr>\n";
			$body .= "\t</thead>\n";
			$body .= "\t<tbody>\n";
			$body .= $bodyNewFiles;
			$body .= "\t</tbody>\n";
			$body .= '</table>';
		}

		// Add the modified files report only if we really have some files
		if ($bodyAttentionFiles)
		{
			$body .= '<hr/><h2>' . JText::_('COM_ADMINTOOLS_SCAN_LBL_MODIFIED_OR_SUSPICIOUS') . "</h2>\n";
			$body .= "<table width=\"100%\">\n";
			$body .= "\t<thead>\n";
			$body .= "\t<tr>\n";
			$body .= "\t\t<th>" . JText::_('COM_ADMINTOOLS_SCANALERTS_LBL_PATH') . "</th>\n";
			$body .= "\t\t<th width=\"50\">" . JText::_('COM_ADMINTOOLS_SCANALERTS_LBL_THREAT_SCORE') . "</th>\n";
			$body .= "\t</tr>\n";
			$body .= "\t</thead>\n";
			$body .= "\t<tbody>\n";
			$body .= $bodyAttentionFiles;
			$body .= "\t</tbody>\n";
			$body .= '</table>';
		}

		// No added or modified files? Let's print a message for the user
		if (!$bodyNewFiles && !$bodyAttentionFiles)
		{
			$body .= '<p>' . JText::_('COM_ADMINTOOLS_LBL_SCANS_EMAIL_NOTHING_TO_REPORT') . '</p>';
		}

		unset($bodyNewFiles);
		unset($bodyAttentionFiles);

		$body .= '</body></html>';

		// Prepare the email subject
		$app      = Factory::getApplication();
		$sitename = $app->get('sitename', 'Unknown Site');
		$subject  = JText::sprintf('COM_ADMINTOOLS_LBL_SCANS_EMAIL_SUBJECT', $sitename);

		// Send the email
		$this->logger->debug(sprintf("%s: Ready to send out emails", __CLASS__));

		try
		{
			/** @noinspection PhpDeprecationInspection */
			$mailer = clone (class_exists(MailerFactoryInterface::class)
				? Factory::getContainer()->get(MailerFactoryInterface::class)->createMailer()
				: Factory::getMailer());
			$mailer->isHTML(true);
			$mailer->addRecipient($email);
			$mailer->setSubject($subject);
			$mailer->setBody($body);
			$mailer->Send();
		}
		catch (Exception $e)
		{
			$this->logger->warning(sprintf("Could not set email. Received error from Joomla's Mailer: %s", $e->getMessage()));
		}
	}

}

© 2025 Cubjrnet7