<?php
/*
 * @package 	RSFirewall!
 * @copyright 	(c) 2009 - 2024 RSJoomla!
 * @link 		https://www.rsjoomla.com/joomla-extensions/joomla-security.html
 * @license 	GNU General Public License https://www.gnu.org/licenses/gpl-3.0.en.html
 */

namespace Rsjoomla\Plugin\System\Rsfirewallconsole;

use Joomla\Console\Command\AbstractCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Console\Input\InputArgument;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;

defined('_JEXEC') or die;

class CheckMalwareCommand extends AbstractCommand
{
	use HelperFunctions;

	protected $symfonyStyle;
	protected $num_files;
	protected static $defaultName = 'rsfirewall:check-malware';

	protected function doExecute(InputInterface $input, OutputInterface $output): int
	{
		require_once JPATH_ADMINISTRATOR . '/components/com_rsfirewall/models/check.php';

		$this->symfonyStyle = new SymfonyStyle($input, $output);
		
		// load the Joomla library language file
		Factory::getApplication()->getLanguage()->load('lib_joomla', JPATH_ADMINISTRATOR);
		Factory::getApplication()->getLanguage()->load('com_rsfirewall', JPATH_ADMINISTRATOR);

		$useLog  = boolval($input->getOption('log'));
		
		$scriptStart = microtime(true);

		try
		{
			
			$this->symfonyStyle->title(Text::_('PLG_SYSTEM_RSFIREWALLCONSOLE_COMMAND_CHECKMALWARE'));
			$this->symfonyStyle->info(Text::sprintf('PLG_SYSTEM_RSFIREWALLCONSOLE_PHP_INFO', ini_get('memory_limit'), ini_get('max_execution_time')));

			// Check if Xdebug is enabled and try to disable it
			if (extension_loaded('xdebug'))
			{
				$this->symfonyStyle->warning(Text::_('PLG_SYSTEM_RSFIREWALLCONSOLE_XDEBUG_ENABLED'));
				$this->symfonyStyle->writeln(Text::_('PLG_SYSTEM_RSFIREWALLCONSOLE_XDEBUG_TRY_DISABLE'));
				// try and disable it, if no stop the script
				if (function_exists('xdebug_disable'))
				{
					xdebug_disable();
					$this->symfonyStyle->success(Text::_('PLG_SYSTEM_RSFIREWALLCONSOLE_XDEBUG_DISABLE_SUCCESS'));
				}
				else 
				{
					throw new \Exception(Text::_('PLG_SYSTEM_RSFIREWALLCONSOLE_XDEBUG_DISABLE_FAIL'));
				}
			}

			
			$model = new \RsfirewallModelCheck();

			// output a message that states we are scanning the instalation for all the files
			$this->symfonyStyle->writeln(Text::_('PLG_SYSTEM_RSFIREWALLCONSOLE_COMMAND_CHECKMALWARE_SCANNING'));

			// count all files so that we can create the progress bar
			$this->num_files = $this->numAllFiles($model);

			// output the number of files we found
			$this->symfonyStyle->writeln(Text::sprintf('PLG_SYSTEM_RSFIREWALLCONSOLE_COMMAND_CHECKMALWARE_FOUND_NR_FILES', $this->num_files));

			// create the progress bar
			$this->symfonyStyle->progressStart($this->num_files); 

			$output = $this->checkSignatures($model);

			$this->symfonyStyle->progressFinish();

			if (!empty($output))
			{
				$this->parseOutput($output, $useLog);
			}
			else
			{
				$this->symfonyStyle->success(Text::_('PLG_SYSTEM_RSFIREWALLCONSOLE_COMMAND_CHECKMALWARE_NOT_FOUND_FILES'));
			}
		}
		catch (\Exception $e)
		{
			$err = $e->getMessage();
			// remove the tags from the error message
			$err = strip_tags($err);

			$this->symfonyStyle->error($err);
			return $e->getCode();
		}

		$time = number_format(microtime(true) - $scriptStart, 2, '.', '');

		$this->symfonyStyle->writeln(Text::sprintf('PLG_SYSTEM_RSFIREWALLCONSOLE_FINISHED_IN_SECONDS', $time));
		$this->symfonyStyle->success(Text::_('PLG_SYSTEM_RSFIREWALLCONSOLE_COMMAND_CHECKMALWARE_SUCCESS'));

		
		return 0;
	}

	protected function numAllFiles($model, $start = 'none', $limit = 0)
	{
		static $last_file;
		$num_files = 0;

		$root = JPATH_SITE;
		// workaround to grab the correct root
		if ($root == '') {
			$root = '/';
		}

		// if the limit is not set get
		if (empty($limit))
		{
			$limit  = $model->getConfig()->get('offset', 300);
		}

		$model->setOffsetLimit($limit);

		// if the start is none use the root
		$start = $start == 'none' ? $root : $start;

		// this is so we know where to stop
		if (is_null($last_file))
		{
			$last_file = $model->getLastFile($root);
		}

		if (is_file($start) && $last_file === $start) {
			return $num_files;
		}

		$files = $model->getFilesRecursive($start);

		if ($files === false)
		{
			throw new \Exception($model->getError());
		}

		// add to the count
		$num_files = count($files);

		$next_file = end($files);

		if ($next_file != $last_file) {
			$rest = $this->numAllFiles($model, $next_file , $limit);
			$num_files += $rest;
		}
		

		return $num_files;
	}

	protected function checkSignatures($model, $start = 'none', $limit = 0)
	{
		static $last_file;
		static $set = 0;
		$output = [];

		// increment the set of files that are scanned
		$set++;

		$root = JPATH_SITE;
		// workaround to grab the correct root
		if ($root == '') {
			$root = '/';
		}

		// if the limit is not set get
		if (empty($limit))
		{
			$limit  = $model->getConfig()->get('offset', 300);
		}

		$model->setOffsetLimit($limit);

		// if the start is none use the root
		$start = $start == 'none' ? $root : $start;

		// this is so we know where to stop
		if (is_null($last_file))
		{
			$last_file = $model->getLastFile($root);
		}

		if (is_file($start) && $last_file === $start) {
			return $output;
		}

		$files = $model->getFilesRecursive($start);

		if ($files === false)
		{
			throw new \Exception($model->getError());
		}

		
		foreach ($files as $file)
		{
			if ($result = $model->checkSignatures($file))
			{
				$result['path'] = substr_replace($file, '', 0, strlen(JPATH_SITE.DIRECTORY_SEPARATOR));

				$output[] = $result; 
			}
		}

		$progress = count($files);

		$this->symfonyStyle->progressAdvance($progress);

		$next_file = end($files);

		if ($next_file != $last_file) {
			$rest = $this->checkSignatures($model, $next_file , $limit);
			$output = array_merge($output, $rest);
		}
		

		return $output;
	}

	protected function parseOutput($output = [], $useLog = false, $restore = false, $confirm = false)
	{
		$problems = [];
		
		$this->symfonyStyle->warning(Text::sprintf('PLG_SYSTEM_RSFIREWALLCONSOLE_COMMAND_CHECKMALWARE_FOUND_FILES', count($output)));

		foreach ($output as $file)
		{
			$log = [];
			// match
			$log[] = Text::sprintf('PLG_SYSTEM_RSFIREWALLCONSOLE_COMMAND_CHECKMALWARE_FOUND', $file['match']);
			// reason
			$log[] = Text::sprintf('PLG_SYSTEM_RSFIREWALLCONSOLE_COMMAND_CHECKMALWARE_REASON', $file['reason']);
			// path
			$log[] = Text::sprintf('PLG_SYSTEM_RSFIREWALLCONSOLE_COMMAND_CHECKMALWARE_PATH', $file['path']);
			
			$date_log = '';
			$file_time = $this->getFileTime($file['path']);

			if (strlen($file_time['relative']))
			{
				$log[] 		= Text::sprintf('PLG_SYSTEM_RSFIREWALLCONSOLE_COMMAND_CHECKMALWARE_FILE_AGO', $file_time['relative']);
				$date_log 	= Text::sprintf('PLG_SYSTEM_RSFIREWALLCONSOLE_COMMAND_CHECKMALWARE_FILE_DATE', $file_time['time']);
			}

			$cli_message = implode("\n", $log);

			// show the output of the file
			$this->symfonyStyle->writeln($cli_message);
			$this->symfonyStyle->writeln('');

			// remove the relative from the log
			array_pop($log);

			// add the modified date
			if (strlen($date_log))
			{
				$log[] = $date_log;
			}
			
			$problems[] = implode("\n", $log); 
		}

		if ($useLog && !empty($problems))
		{
			require_once JPATH_ADMINISTRATOR . '/components/com_rsfirewall/helpers/log.php';
			$logger = \RSFirewallLogger::getInstance();
			// use the server timezone
			$logger->setTimezone(false);

			if (!empty($problems))
			{
				// save them with new lines
				$problems = implode("\n\n", $problems);
							
				$logger->add('critical','MALWARE_FILES_FOUND', $problems)->save();
			}
			
			$this->symfonyStyle->newLine();
			$this->symfonyStyle->writeln(Text::_('PLG_SYSTEM_RSFIREWALLCONSOLE_COMMAND_CHECKMALWARE_FILES_ADDED_TO_LOG'));
		}
		
	}

	protected function configure(): void
	{
		$this->addOption('log', 'l', InputArgument::OPTIONAL, Text::_('PLG_SYSTEM_RSFIREWALLCONSOLE_COMMAND_USE_LOG_OPTION'), '');
		$this->setDescription(Text::_('PLG_SYSTEM_RSFIREWALLCONSOLE_COMMAND_CHECKMALWARE_DESCRIPTION'));

		$help_1 = Text::_('PLG_SYSTEM_RSFIREWALLCONSOLE_COMMAND_CHECKMALWARE_HELP_1');
		$this->setHelp(
			<<<EOF
RSFirewall!
###########

{$help_1}

        php joomla.php rsfirewall:check-malware --log=1

EOF
		);
	}
}