name : QuickstartModel.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\Model;

defined('_JEXEC') or die;

use Akeeba\Component\AdminTools\Administrator\Helper\Storage;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
use Joomla\CMS\MVC\Model\BaseDatabaseModel;
use Joomla\CMS\Uri\Uri;
use Joomla\Utilities\IpHelper;

#[\AllowDynamicProperties]
class QuickstartModel extends BaseDatabaseModel
{
	/**
	 * The parameters storage model
	 *
	 * @var   Storage
	 */
	private $storageModel;

	/**
	 * Administrator password protection model
	 *
	 * @var   AdminpasswordModel
	 */
	private $adminPasswordModel;

	/**
	 * WAF Config model
	 *
	 * @var   ConfigurewafModel
	 */
	private $wafModel;

	/**
	 * WAF configuration
	 *
	 * @var   array
	 */
	private $config;

	public function __construct($config = [], MVCFactoryInterface $factory = null)
	{
		parent::__construct($config, $factory);

		$this->storageModel       = Storage::getInstance();
		$this->adminPasswordModel = $this->getMVCFactory()->createModel('Adminpassword', 'Administrator', ['ignore_request' => true]);
		$this->wafModel           = $this->getMVCFactory()->createModel('Configurewaf', 'Administrator', ['ignore_request' => true]);
		$this->config             = $this->wafModel->getConfig();
	}

	/**
	 * Applies the wizard preferences to the component's configuration
	 *
	 * @return  void
	 */
	public function applyPreferences()
	{
		// Reset all stored settings
		$this->storageModel->resetContents();

		// Apply administrator secret URL parameter
		$this->config['adminpw'] = $this->getState('adminpw', '');
		$this->config['adminpw_action'] = 'redirect';

		// Password protect administrator
		$this->applyAdministratorPassword();

		// Apply email on admin login
		$this->config['emailonadminlogin']       = $this->getState('emailonadminlogin', '');
		$this->config['emailonfailedadminlogin'] = $this->getState('emailonadminlogin', '');

		// Apply IP whitelist
		$this->applyIpWhitelist();

		// Disable editing backend users' properties
		$this->config['nonewadmins'] = $this->getState('nonewadmins', 0);

		// Forbid front-end Super Administrator login
		$this->config['nofesalogin'] = $this->getState('nofesalogin', 0);

		// Enable WAF
		$this->applyWafPreferences($this->getState('enablewaf', 0));

		// Apply IP autoban preferences
		$this->applyAutoban($this->getState('autoban', 0));

		// Apply automatic permanent blacklist
		$this->applyBlacklist($this->getState('autoblacklist', 0));

		// Apply email address to report WAF exceptions and blocks
		$this->config['emailbreaches']       = $this->getState('emailbreaches', '');
		$this->config['emailafteripautoban'] = $this->getState('emailbreaches', '');

		// Apply allowed domains
		$allowedDomains = trim($this->getState('allowed_domains', ''), " ,\n\r\t\0");
		$allowedDomains = explode(',', $allowedDomains);
		$allowedDomains = array_map('trim', $allowedDomains);
		$allowedDomains = array_filter($allowedDomains, function ($x) {
			return !empty($x) && !$this->isLocalhost(false, $x);
		});
		$this->config['allowed_domains'] = $allowedDomains;

		// Project Honeypot HTTP:BL
		$this->applyProjectHoneypot($this->getState('bbhttpblkey', ''));

		// Save the WAF configuration
		$this->wafModel->saveConfig($this->config);

		// Apply .htaccess Maker
		if ($this->getState('htmaker', 0))
		{
			$written = $this->applyHtmaker();

			if (!$written)
			{
				Factory::getApplication()->enqueueMessage(Text::_('COM_ADMINTOOLS_QUICKSTART_MSG_HTMAKERNOTAPPLIED'), 'error');
			}
		}

		// Save a flag indicating we no longer need to run the Quick Start
		$this->storageModel->load();
		$this->storageModel->setValue('quickstart', 1, 1);
	}

	/**
	 * Is it the Quick Setup Wizard's first run?
	 *
	 * @return  bool
	 */
	public function isFirstRun()
	{
		return $this->storageModel->getValue('quickstart', 0) == 0;
	}

	/**
	 * Password protect / unprotect administrator
	 *
	 * @return  void
	 */
	private function applyAdministratorPassword()
	{
		$username = $this->getState('admin_username', '');
		$password = $this->getState('admin_password', '');

		$this->adminPasswordModel->setState('mode', 'everything');
		$this->adminPasswordModel->setState('resetErrorPages', 'true');
		$this->adminPasswordModel->setState('username', $username);
		$this->adminPasswordModel->setState('password', $password);

		if (empty($username) || empty($password))
		{
			$this->adminPasswordModel->unprotect();

			return;
		}

		$this->adminPasswordModel->protect();
	}

	/**
	 * Apply administrator IP whitelist
	 *
	 * @return  void
	 */
	private function applyIpWhitelist()
	{
		$this->config['ipwl'] = $this->getState('ipwl', 0);

		if (!$this->config['ipwl'])
		{
			return;
		}

		// Remove all previously allowed IP addresses
		$db = $this->getDatabase();
		$db->truncateTable('#__admintools_adminiplist');

		// Add the current IP address as an exclusively allowed IP address
		$detectedIp = $this->getState('detectedip', '');
		$currentIP  = IpHelper::getIp();
		$ip         = (!empty($detectedIp) && ($detectedIp != $currentIP)) ? $detectedIp : $currentIP;

		$o = (object) [
			'ip' => $ip,
			'description' => Text::_('COM_ADMINTOOLS_QUICKSTART_MSG_IPADDEDBYWIZARD'),
		];

		$db->insertObject('#__admintools_adminiplist', $o);
	}

	/**
	 * Apply main WAF preference (global disable/enable)
	 *
	 * @param   bool  $enabled  Should I enable WAF?
	 *
	 * @return  void
	 */
	private function applyWafPreferences($enabled = true)
	{
		$state = $enabled ? 1 : 0;

		// Note: UploadShield is disabled on Joomla! 3.4.1 and later (it's included in Joomla! itself)
		$newValues = [
			'ipbl'              => $state,
			'sqlishield'        => $state,
			'antispam'          => 0,
			'custgenerator'     => $state,
			'generator'         => 'MYOB',
			'tmpl'              => $state,
			'template'          => $state,
			'logbreaches'       => 1,
			'muashield'         => $state,
			'rfishield'         => $state,
			'dfishield'         => $state,
			'sessionshield'     => $state,
			'tmplwhitelist'     => ['component','system','raw','koowa','cartupdate'],
			'allowsitetemplate' => 0,
			'trackfailedlogins' => $state,
			'use403view'        => 0,
			'iplookup'          => 'whatismyipaddress.com/ip/{ip}',
			'saveusersignupip'  => $state,
			'whitelist_domains' => ['.crawl.baidu.com','.crawl.baidu.jp','.google.com','.googlebot.com','.search.msn.com','.crawl.yahoo.net','.yandex.ru','.yandex.net','.yandex.com'],
			'reasons_nolog'     => [],
			'reasons_noemail'   => [],
			'resetjoomlatfa'    => 0,
			'email_throttle'    => 1,
			'selfprotect'       => 1,
			'criticalfiles'     => 1,
			'superuserslist'    => 0,
		];

		$this->config = array_merge($this->config, $newValues);
	}

	/**
	 * Apply automatic IP ban
	 *
	 * @param   bool  $enabled  Should I enable it?
	 *
	 * @return  void
	 */
	private function applyAutoban($enabled = true)
	{
		$state = $enabled ? 1 : 0;

		$newValues = [
			'tsrenable'       => $state,
			'tsrstrikes'      => 3,
			'tsrnumfreq'      => 1,
			'tsrfrequency'    => 'minute',
			'tsrbannum'       => 15,
			'tsrbanfrequency' => 'minute',
		];

		$this->config = array_merge($this->config, $newValues);
	}

	/**
	 * Apply automatic IP ban
	 *
	 * @param   bool  $enabled  Should I enable it?
	 *
	 * @return  void
	 */
	private function applyBlacklist($enabled = true)
	{
		$state = $enabled ? 1 : 0;

		$newValues = [
			'permaban'    => $state,
			'permabannum' => 3,
		];

		$this->config = array_merge($this->config, $newValues);
	}

	/**
	 * Apply Project Honeypot HTTP:BL settings
	 *
	 * @param   string  $key  The HTTP:BL key
	 *
	 * @return  void
	 */
	private function applyProjectHoneypot($key = '')
	{
		$state = empty($key) ? 0 : 1;

		$newValues = [
			'bbhttpblkey'           => $key,
			'httpblenable'          => $state,
			'httpblthreshold'       => 25,
			'httpblmaxage'          => 30,
			'httpblblocksuspicious' => 0,
		];

		$this->config = array_merge($this->config, $newValues);
	}

	private function applyHtmaker()
	{
		/** @var HtaccessmakerModel $htMakerModel */
		$htMakerModel = $this->getMVCFactory()->createModel('Htaccessmaker');

		// Get the site's hostname and base directory
		$hostname = $this->getSiteHostname();
		$baseDir  = $this->getSiteBaseDir();

		// Should I redirect non-www to www or vice versa?
		$wwwRedir = substr($hostname, 0, 4) == 'www.' ? 1 : 2;
		$wwwRedir = $this->isLocalhost() ? 0 : $wwwRedir;

		// Enable HSTS on HTTPS sites. Literal localhost will never have HSTS enabled on it.
		$isHSTS = !$this->isLocalhost(true) && Uri::getInstance()->getScheme() == 'https';

		// Create an object with fine-tuned rules for this site
		try
		{
			$defaultConfig = $htMakerModel->getDefaultConfig() ?: [];
		}
		catch (\Exception $e)
		{
			$defaultConfig = [];
		}

		$newConfig = array_merge($defaultConfig, [
			'nodirlists'          => 0,
			'symlinks'            => -1,
			'exptime'             => 2,
			'autocompress'        => 1,
			'autoroot'            => 0,
			'wwwredir'            => $wwwRedir,
			'hstsheader'          => $isHSTS ? 1 : 0,
			'nohoggers'           => 1,
			'clickjacking'        => 0,
			'reducemimetyperisks' => 0,
			'reflectedxss'        => 0,
			'notransform'         => 0,
			'httphost'            => $hostname,
			'httpshost'           => $hostname,
			'rewritebase'         => $baseDir,
            // Store any custom PHP handlers that are inside the original .htaccess file
            'custfoot'            => $htMakerModel->getPhpHandlers()
		]);

		$newConfig['httpsurls'] = array_filter($newConfig['httpsurls'] ?? [], fn($x) => !empty($x));

		// Pass everything back to the model, it will merge the new config with the default one
		$htMakerModel->saveConfiguration($newConfig);

		return $htMakerModel->writeConfigFile();
	}

	private function getSiteHostname(): string
	{
		$app       = Factory::getApplication();
		$live_site = trim($app->get('live_site') ?: '');
		$uri       = empty($live_site) ? Uri::getInstance() : Uri::getInstance($live_site);

		return strtolower($uri->getHost() ?: '');
	}

	public function getSiteBaseDir(): string
	{
		$app      = Factory::getApplication();
		$sitePath = Uri::base(true);

		if (substr($sitePath, -13) === '/installation')
		{
			$sitePath = substr($sitePath, 0, -13);
		}
		elseif (substr($sitePath, -14) === '/administrator')
		{
			$sitePath = substr($sitePath, 0, -14);
		}
		elseif (substr($sitePath, -4) === '/api')
		{
			$sitePath = substr($sitePath, -4);
		}

		return trim($sitePath, '/') ?: '/';
	}

	private function isLocalhost(bool $strict = false, ?string $hostName = null): bool
	{
		$hostName = $hostName ?? $this->getSiteHostname();

		if (empty($hostName))
		{
			return false;
		}

		if (in_array($hostName, ['localhost', 'localhost.localdomain']))
		{
			return true;
		}

		if ($strict)
		{
			return false;
		}

		$ip = gethostbyname($hostName);

		if (($ip === '127.0.0.1') || ($ip === '::1'))
		{
			return true;
		}

		return false;
	}
}

© 2025 Cubjrnet7