<?php
/**
 *----------------------------------------------------------------------------
 * iCagenda     Events Management Extension for Joomla!
 *----------------------------------------------------------------------------
 * @version     4.0.9 2026-06-24
 *
 * @package     iCagenda
 * @link        https://www.joomlic.com
 *
 * @author      Cyril Reze
 * @copyright   (c) 2012-2026 Cyril Reze / JoomliC - All rights reserved.
 * @license     GNU General Public License version 3 or later; see LICENSE.txt
 *
 * @since       3.7.18
 *----------------------------------------------------------------------------
*/

use Joomla\CMS\Cache\Cache;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Factory;
use Joomla\CMS\Installer\Installer;
use Joomla\CMS\Installer\InstallerAdapter;
use Joomla\CMS\Installer\InstallerScriptInterface;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Log\Log;
use Joomla\CMS\Object\CMSObject;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\Database\DatabaseInterface;
use Joomla\Filesystem\File;
use Joomla\Filesystem\Folder;

// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects

return new class () implements InstallerScriptInterface
{
	private string $ictype = 'core';

	/**
	 * The name of our package, e.g. pkg_example. Used for dependency tracking.
	 *
	 * @var  string
	 */
	private string $packageName = 'pkg_icagenda';

	/**
	 * The name of our component, e.g. com_example. Used for dependency tracking.
	 *
	 * @var  string
	 */
	private string $componentName = 'com_icagenda';

	/**
	 * The minimum PHP version required to install this extension
	 *
	 * @var   string
	 */
	private string $minimumPHPVersion = '7.2.5';

	/**
	 * The minimum Joomla! version required to install this extension
	 *
	 * @var   string
	 */
	private string $minimumJoomlaVersion = '4.2.0';

	/**
	 * The maximum Joomla! version this extension can be installed on
	 *
	 * @var   string
	 */
	private string $maximumJoomlaVersion = '6.2.99';

	/**
	 * A list of extensions (modules, plugins) to enable after installation. Each item has four values, in this order:
	 * type (plugin, module, ...), name (of the extension), client (0=site, 1=admin), group (for plugins).
	 *
	 * @var   array
	 */
	protected $extensionsToEnable = [
		// Actionlog plugins
		['plugin', 'icagenda', 1, 'actionlog'],

		// Privacy plugins
		['plugin', 'icagenda', 1, 'privacy'],

		// Quick Icon plugins
		['plugin', 'icagendaupdate', 1, 'quickicon'],

		// Smart Search plugin
		['plugin', 'icagenda', 1, 'finder'],

		// System plugins
		['plugin', 'ic_autologin', 1, 'system'],
		['plugin', 'ic_library', 1, 'system'],
		['plugin', 'icagenda', 1, 'system'],
	];

	/**
	 * Like above, but enable these extensions on installation OR update. Use this sparingly. It overrides the
	 * preferences of the user. Ideally, this should only be used for installer plugins.
	 *
	 * @var array
	 */
	protected $extensionsToAlwaysEnable = [
	];

	/**
	 * A list of extensions (library, modules, plugins) installed in this package. Each item has five values, in this order:
	 * type (plugin, module, ...), element (of the extension), client (0=site, 1=admin), group (for plugins), name (of the extension).
	 *
	 * @var array
	 */
	protected $packageExtensions = [
		// Component
		['component', 'com_icagenda', 1, '', 'iCagenda'],

		// Library
		['library', 'ic_library', 0, '', 'iC Library'],

		// Modules
		['module', 'mod_icagenda_calendar', 0, '', 'iCagenda - Calendar'],

		// Plugins
		['plugin', 'icagenda', 1, 'actionlog', 'Action Log - iCagenda'],
		['plugin', 'icagenda', 1, 'finder', 'Smart Search - iCagenda'],
		['plugin', 'icagenda', 1, 'privacy', 'Privacy - iCagenda'],
		['plugin', 'icagendaupdate', 1, 'quickicon', 'Quick Icon - iCagenda :: Update Notification'],
		['plugin', 'ic_autologin', 1, 'system', 'System - iCagenda :: Autologin'],
		['plugin', 'ic_library', 1, 'system', 'System - iC Library'],
		['plugin', 'icagenda', 1, 'system', 'System - iCagenda'],
	];

	/**
	 * A list of deprecated extensions (library, modules, plugins) updated with this package (but not installed on fresh install).
	 * Each item has five values, in this order:
	 * type (plugin, module, ...), element (of the extension), client (0=site, 1=admin), group (for plugins), name (of the extension).
	 *
	 * @var    array
	 *
	 * @since  4.0.0
	 */
	protected $legacyExtensions = [
		// Modules
		['module', 'mod_iccalendar', 0, '', 'iCagenda - Calendar (deprecated)'],

		// Plugins
		['plugin', 'icagenda', 1, 'search', 'Search - iCagenda'],
	];

	/**
	 * The list of extra modules and plugins to install on component installation / update and remove on component
	 * uninstallation.
	 *
	 * // modules => { (folder) => { (module) => { (position), (published) } }* }*
	 *
	 * @var   array
	 *
	 * @since  4.0.0
	 */
	protected $legacyQueue = [
		'modules' => [
			'site' => [
				'mod_iccalendar' => ['sidebar-right', 0],
			],
		],
		'plugins' => [
			'search' => [
				'icagenda' => 0,
			],
		],
	];

	/**
	 * The list of obsolete extensions to uninstall on component update.
	 *
	 * // plugins => { (folder) => (element) }*
	 *
	 * @var   array
	 *
	 * @since  4.0.1
	 */
	protected $obsoleteQueue = [
		'plugins' => [
			'installer' => 'icagenda',
		],
	];

	protected $installedExtensions = [];

	/**
	 * Function called before extension installation/update/removal procedure commences.
	 *
	 * @param   string            $type     The type of change (install or discover_install, update, uninstall)
	 * @param   InstallerAdapter  $adapter  The adapter calling this method
	 *
	 * @return  boolean  True on success
	 */
	public function preflight(string $type, InstallerAdapter $adapter): bool
	{
		// Do not run on uninstall.
		if ($type === 'uninstall') {
			return true;
		}

		// Check the minimum PHP version
		if ( ! version_compare(PHP_VERSION, $this->minimumPHPVersion, 'ge')) {
			$msg = '<p><strong>' . Text::sprintf('PKG_ICAGENDA_WARNING_MINIMUM_PHP', $this->minimumPHPVersion) . '</strong></p>';
			Log::add($msg, Log::WARNING, 'jerror');

			return false;
		}

		// Check the minimum Joomla! version
		if ( ! version_compare(JVERSION, $this->minimumJoomlaVersion, 'ge')) {
			$msg = '<p><strong>' . Text::sprintf('PKG_ICAGENDA_WARNING_MINIMUM_JOOMLA', $this->minimumJoomlaVersion) . '</strong></p>';
			Log::add($msg, Log::WARNING, 'jerror');

			return false;
		}

		// Check the maximum Joomla! version
		if ( ! version_compare(JVERSION, $this->maximumJoomlaVersion, 'le')) {
			$msg = '<p><strong>' . Text::sprintf('PKG_ICAGENDA_WARNING_MAXIMUM_JOOMLA', $this->maximumJoomlaVersion) . '</strong></p>';
			Log::add($msg, Log::WARNING, 'jerror');

			return false;
		}

		// HHVM made sense in 2013, now PHP 7 is a way better solution than an hybrid PHP interpreter
		if (\defined('HHVM_VERSION')) {
			$minPHP = '7';
			$msg = '<p><strong>' . Text::sprintf('PKG_ICAGENDA_WARNING_HHVM', $minPHP) . '</strong></p>';
			Log::add($msg, Log::WARNING, 'jerror');

			return false;
		}

		if ($type === 'update') {
			$db = Factory::getContainer()->get(DatabaseInterface::class);

			$query = (method_exists($db, 'createQuery') ? $db->createQuery() : $db->getQuery(true))
				->select('id')
				->from($db->qn('#__icagenda'))
				->where($db->qn('id') . ' IN (1,2,3)');
			$db->setQuery($query);

			$eids = $db->loadObjectList();

			if (\count($eids) > 0) {
				$this->installLegacyExtensions($adapter);
//			} else {
//				$this->checkLegacyExtensions($adapter);
			}
			$this->checkLegacyExtensions($adapter);
		}

		$this->checkInstalled();

		return true;
	}

	/**
	 * Function called after extension installation/update/removal procedure commences.
	 *
	 * @param   string            $type     The type of change (install or discover_install, update, uninstall)
	 * @param   InstallerAdapter  $adapter  The adapter calling this method
	 *
	 * @return  boolean  True on success
	 */
	public function postflight(string $type, InstallerAdapter $adapter): bool
	{
		if ($type === 'uninstall') {
			$this->uninstallLegacyExtensions($adapter);

			return true;
		}

		// Always enable these extensions
		if (isset($this->extensionsToAlwaysEnable) && !empty($this->extensionsToAlwaysEnable)) {
			$this->enableExtensions($this->extensionsToAlwaysEnable);
		}

		// Get manifest file version
		$release = $adapter->getManifest()->version;

		echo '<div style="background: var(--body-bg); padding: 2rem; border-radius: .2rem;">';

		echo '<p>';
		echo '<img src="../media/com_icagenda/images/iCagenda-brand.svg" height="48" />';

		if ($this->ictype != 'core') echo '<span class="text-body-secondary" style="font-weight: bold; font-size: 1.25rem;"> ' . strtoupper($this->ictype) . '</span>';

		echo '<br />';
		echo '<span style="font-size: 16px; color: #555; margin-left: 70px;">' . Text::_('PKG_ICAGENDA_EXTENSION_DESCRIPTION') . '</span>';
		echo '</p>';

		echo '<hr />';

		echo '<p>';
		echo '<div style="float: left; margin-right: 30px;">';
		echo '<img src="../media/com_icagenda/images/iCagenda-logo-brand.svg" alt="iCagenda logo" width="120" height="120" />';
		echo '</div>';

		echo '<span style="letter-spacing: 1px; font-size: 1rem">'
			. Text::_('COM_ICAGENDA_WELCOME')
			. '</span>'
			. '<br />';

		if ($type == 'install') {
			echo '<span style="text-transform:uppercase; font-size: .875rem; font-weight: bold;">'
				. Text::sprintf('COM_ICAGENDA_WELCOME_1', '<strong>iCagenda</strong>') . ' ' . $release
				. ' ' . Text::_('COM_ICAGENDA_WELCOME_2') . '</span>'
				. '<br /><br />';
		}

		// Extension Update
		if ($type == 'update') {
			echo '<span style="font-size: 1rem; font-weight: bold;">'
				. Text::sprintf('PKG_ICAGENDA_UPDATED_TO_VERSION', 'iCagenda', $release)
				. '</span>'
				. '<br /><br />';
		}

		echo '<div class="small">';
		echo Text::_('COM_ICAGENDA_FEATURES_BACKEND') . '<br />';
		echo Text::_('COM_ICAGENDA_FEATURES_FRONTEND');
		echo '</div>';

		echo '</p>';

		echo '<div style="clear: both"></div>';

		echo '<hr />';

		$translationPacks =  [
			'af'    => 'Afrikaans (South Africa)',
			'ar'    => 'Arabic (Unitag)',
			'eu_es' => 'Basque (Spain)',
			'bg'    => 'Bulgarian (Bulgaria)',
			'ca'    => 'Catalan (Spain)',
			'zh'    => 'Chinese (China)',
			'tw'    => 'Chinese (Taiwan)',
			'hr'    => 'Croatian (Croatia)',
			'cz'    => 'Czech (Czech Republic)',
			'dk'    => 'Danish (Denmark)',
			'nl'    => 'Dutch (Netherlands)',
			'en'    => 'English (United Kingdom)',
			'us'    => 'English (United States)',
			'eo'    => 'Esperanto',
			'et'    => 'Estonian (Estonia)',
			'fi'    => 'Finnish (Finland)',
			'fr'    => 'French (France)',
			'gl'    => 'Galician (Spain)',
			'de'    => 'German (Germany)',
			'el'    => 'Greek (Greece)',
			'hu'    => 'Hungarian (Hungary)',
			'it'    => 'Italian (Italy)',
			'ja'    => 'Japanese (Japan)',
			'lv'    => 'Latvian (Latvia)',
			'lt'    => 'Lithuanian (Lithuania)',
			'none'  => 'Luxembourgish (Luxembourg)',
			'mk'    => 'Macedonian (Macedonia)',
			'no'    => 'Norwegian Bokmål (Norway)',
			'fa_ir' => 'Persian (Iran)',
			'pl'    => 'Polish (Poland)',
			'pt_br' => 'Portuguese (Brazil)',
			'pt'    => 'Portuguese (Portugal)',
			'ro'    => 'Romanian (Romania)',
			'ru'    => 'Russian (Russia)',
			'sr'    => 'Serbian (latin)',
			'sk'    => 'Slovak (Slovakia)',
			'sl'    => 'Slovenian (Slovenia)',
			'es'    => 'Spanish (Spain)',
			'sv'    => 'Swedish (Sweden)',
			'th'    => 'Thai (Thailand)',
			'tr'    => 'Turkish (Turkey)',
			'uk'    => 'Ukrainian (Ukraine)',
		];

		echo '<div class="text-body-secondary" style="font-size: 1.125rem; font-weight: bold; margin-bottom: 10px;">'
			. Text::sprintf('COM_ICAGENDA_FEATURES_TRANSLATION_PACKS', \count($translationPacks))
			. '</div>';

		echo '<p>';

		foreach ($translationPacks as $code => $lang) {
			$flagIcon = ($code == 'none') ? 'icon-16-language.png' : $code . '.gif';

			echo '<span rel="tooltip" data-placement="top" class="editlinktip hasTip" style="margin: 2px;" title="' . $lang . '">'
				. '<img src="../media/mod_languages/images/' . $flagIcon . '" border="0" alt="Tooltip"/>'
				. '</span>';
		}

		echo '<br /><br />';
		echo '<a href="https://www.icagenda.com/resources/translations" target="_blank" class="btn" style="color: #2a69b8;">'
			. Text::_('COM_ICAGENDA_TRANSLATION_PACKS_DONWLOAD')
			. '</a>';

		echo '</p>';

		echo '<hr />';

		echo '<div class="text-body-secondary" style="font-size: 1.125rem; font-weight: bold; margin-bottom: 10px;">'
			. Text::_('COM_ICAGENDA_INSTALL_LABEL')
			. '</div>';

		// Load language
		Factory::getLanguage()->load('com_installer', JPATH_ADMINISTRATOR);

		if ($type == 'install') {
			echo '<div><i>' . Text::_('JTOOLBAR_INSTALL') . '</i></div>';
		} elseif ($type == 'update') {
			echo '<div><i>' . Text::_('COM_INSTALLER_TOOLBAR_UPDATE') . '</i></div>';
		}

		$this->postMessages();

		$this->createFolders();

		$icagendaParams = ComponentHelper::getParams('com_icagenda');
		$currentRelease = $icagendaParams->get('release', '');
		$priorRelease   = $icagendaParams->get('prior_release', '');

		if ($type == 'update') {
			$suspiciousData  = $this->checkDataEntries();
			$suspiciousFiles = $this->checkMediaFiles();

			if (version_compare($priorRelease, '4.0.7', 'le')) {
				$alertTitle = Text::_('PKG_ICAGENDA_WARNING_SECURITY_UPDATE_ANALYSIS');
				$alertNote  = 'A vulnerability has been discovered in the front-end submission form, even if it is not enabled on your site.';
			} else {
				$alertTitle = Text::_('PKG_ICAGENDA_SECURITY_CHECKER_ANALYSIS');
				$alertNote  = Text::_('PKG_ICAGENDA_SECURITY_CHECKER_RESULT_NOTE');
			}

			if ($suspiciousData || $suspiciousFiles) {
				$securityMessage = '<strong class="fs-3">' . $alertTitle . '</strong>';
				$securityMessage.= '<p>';
				if (\count($suspiciousFiles) > 0) {
					$securityMessage.= '<strong>' . Text::_('PKG_ICAGENDA_WARNING_INSTALLATION_COMPROMISED') . '</strong><br>';
				}
				$securityMessage.= $alertNote;
				$securityMessage.= '</p>';
				$securityMessage.= '<strong>' . Text::_('PKG_ICAGENDA_WARNING_ACTION_REQUIRED') . '</strong><ul>';
				if ($suspiciousData && \count($suspiciousData) > 0) {
					$securityMessage.= '<li>' . Text::_('PKG_ICAGENDA_WARNING_ITEMS_MOVE_TO_TRASH');
					$securityMessage.= '<ul>';
					foreach ($suspiciousData as $itemType => $itemsData) {
						foreach ($itemsData as $itemData) {
							$securityMessage.= '<li>' . Text::sprintf('PKG_ICAGENDA_WARNING_' . $itemType . '_MOVE_TO_TRASH_INFO', $itemData->title, $itemData->id) . '</li>';
						}
					}
					$securityMessage.= '</ul>';
					$securityMessage.= '<strong>' . Text::_('PKG_ICAGENDA_WARNING_ITEMS_MOVE_TO_TRASH_ACTIONS_TO_TAKE') . '</strong>';
					$securityMessage.= '</li>';
				}
				if (\count($suspiciousFiles) > 0) {
					$securityMessage.= '<li>' . Text::plural('PKG_ICAGENDA_WARNING_SUSPICIOUS_FILES_DETECTED', \count($suspiciousFiles)) . '';
					$securityMessage.= '<ul>';
					foreach ($suspiciousFiles as $sf) {
						$securityMessage.= '<li>' . $sf . '</li>';
					}
					$securityMessage.= '</ul>';
					$securityMessage.= '<strong>' . Text::_('PKG_ICAGENDA_WARNING_SUSPICIOUS_FILES_ACTIONS_TO_TAKE') . '</strong>';
					$securityMessage.= '</li>';
				}
				$securityMessage.= '</ul>';
				if (version_compare($priorRelease, '4.0.7', 'le')) {
					$securityMessage.= '<p>' . Text::sprintf('PKG_ICAGENDA_WARNING_SECURITY_UPDATE_READ_MORE', ' 4.0.8', '<a href="https://www.joomlic.com/news/icagenda-security-update" target="_blank" rel="noopener noreferrer">https://www.joomlic.com/news/icagenda-security-update</a>') . '</p>';
				} else {
					// @TODO Replace this text with a link to the documentation when it becomes available.
					$securityMessage.= '<p>' . Text::sprintf('PKG_ICAGENDA_WARNING_SECURITY_UPDATE_READ_MORE', ' 4.0.8', '<a href="https://www.joomlic.com/news/icagenda-security-update" target="_blank" rel="noopener noreferrer">https://www.joomlic.com/news/icagenda-security-update</a>') . '</p>';
				}

				Factory::getApplication()->enqueueMessage($securityMessage, 'warning');
			}
		}

		// Remove Obsolete Update Sites
		// When switching from/to Core/Pro
		// If current version installed before update older than 3.9.4 (new update site)
		if ($icagendaParams->get('icsys') !== $this->ictype || version_compare($currentRelease, '3.9.3', 'le')) {
			// Get com_icagenda extension_id
			$db = Factory::getContainer()->get(DatabaseInterface::class);

			$query = (method_exists($db, 'createQuery') ? $db->createQuery() : $db->getQuery(true))
				->select('extension_id')
				->from($db->qn('#__extensions'))
				->where($db->qn('element') . ' = "pkg_icagenda"');
			$db->setQuery($query);

			$eid = $db->loadResult();

			if ($eid) {
				$this->removeObsoleteUpdateSites($eid);
			}
		}

		if ($type === 'update') {
			$this->uninstallObsoleteExtensions();
		}

		if ($this->ictype != 'core'
			&& (version_compare($priorRelease, '4.0.0', 'le') || version_compare($currentRelease, '4.0.0', 'le'))
		) {
			// Get pkg_icagenda extension id
			$db = Factory::getContainer()->get(DatabaseInterface::class);

			$query = (method_exists($db, 'createQuery') ? $db->createQuery() : $db->getQuery(true))
				->select('extension_id')
				->from($db->qn('#__extensions'))
				->where($db->qn('element') . ' = "pkg_icagenda"');
			$db->setQuery($query);

			$eid = $db->loadResult();

			if ($eid) {
				$this->setDlidUpdateSite($eid);
			}
		}

		echo '<span style="font-size: 11px; font-style: italic; font-weight: bold;">iCagenda &#8226; <a href="https://www.joomlic.com/extensions/icagenda" rel="noopener noreferrer" target="_blank">www.joomlic.com</a></span>';

		echo '<hr /></div>';

		return true;
	}

	/**
	 * Function called after the extension is installed.
	 *
	 * @param   InstallerAdapter  $adapter  The adapter calling this method
	 *
	 * @return  boolean  True on success
	 */
	public function install(InstallerAdapter $adapter): bool
	{
		// Enable the extensions we need to install
		$this->enableExtensions();

		return true;
	}

	/**
	 * Function called after the extension is updated.
	 *
	 * @param   InstallerAdapter  $adapter  The adapter calling this method
	 *
	 * @return  boolean  True on success
	 */
	public function update(InstallerAdapter $adapter): bool
	{
		return true;
	}

	/**
	 * Function called after the extension is uninstalled.
	 *
	 * @param   InstallerAdapter  $adapter  The adapter calling this method
	 *
	 * @return  boolean  True on success
	 */
	public function uninstall(InstallerAdapter $adapter): bool
	{
		return true;
	}

	/**
	 * Enable modules and plugins after installing them
	 */
	private function enableExtensions($extensions = [])
	{
		if (empty($extensions)) {
			$extensions = $this->extensionsToEnable;
		}

		foreach ($extensions as $ext) {
			$this->enableExtension($ext[0], $ext[1], $ext[2], $ext[3]);
		}
	}

	/**
	 * Enable an extension
	 *
	 * @param   string   $type    The extension type.
	 * @param   string   $name    The name of the extension (the element field).
	 * @param   integer  $client  The application id (0: Joomla CMS site; 1: Joomla CMS administrator).
	 * @param   string   $group   The extension group (for plugins).
	 */
	private function enableExtension($type, $name, $client = 1, $group = null)
	{
		try {
			$db = Factory::getContainer()->get(DatabaseInterface::class);

			$query = (method_exists($db, 'createQuery') ? $db->createQuery() : $db->getQuery(true))
				->update('#__extensions')
				->set($db->qn('enabled') . ' = ' . $db->q(1))
				->where('type = ' . $db->quote($type))
				->where('element = ' . $db->quote($name));
		} catch (\Exception $e) {
			return;
		}

		switch ($type) {
			case 'plugin':
				// Plugins have a folder but not a client
				$query->where('folder = ' . $db->quote($group));
				break;

			case 'language':
			case 'module':
			case 'template':
				// Languages, modules and templates have a client but not a folder
				$query->where('client_id = ' . (int) $client);
				break;

			default:
			case 'library':
			case 'package':
			case 'component':
				break;
		}

		try {
			$db->setQuery($query);
			$db->execute();
		} catch (\Exception $e) {
			// Nothing
		}
	}

	/**
	 * Check if extension installed and set extension_id to packageExtensions array.
	 */
	private function checkInstalled()
	{
		foreach ($this->packageExtensions as $k => $ext) {
			$extension_id = $this->extensionIsInstalled($ext[0], $ext[1], $ext[3]);

			$this->packageExtensions[$k][5] = $extension_id;

			if ( ! $extension_id) {
				$this->extensionsToAlwaysEnable[] = [$ext[0], $ext[1], $ext[2], $ext[3]];
			}
		}
	}

	/**
	 * Check if extension is installed
	 *
	 * @param   string   $type          The extension type.
	 * @param   string   $element       The element field.
	 * @param   string   $group         The extension group (for plugins).
	 */
	private function extensionIsInstalled($type, $element, $group = null)
	{
		try {
			$db = Factory::getContainer()->get(DatabaseInterface::class);

			$query = (method_exists($db, 'createQuery') ? $db->createQuery() : $db->getQuery(true))
				->select('e.extension_id')
				->from('`#__extensions` AS e')
				->where('type = ' . $db->quote($type))
				->where('element = ' . $db->quote($element));
		} catch (\Exception $e) {
			return;
		}

		switch ($type) {
			case 'component':
				break;

			case 'language':
			case 'library':
				break;

			case 'module':
				break;

			case 'plugin':
				// Plugins have a folder but not a client
				$query->where('folder = ' . $db->quote($group));
				break;

			default:
			case 'template':
			case 'package':
				break;
		}

		try {
			$db->setQuery($query);
			$extension_id = $db->loadResult();
		} catch (\Exception $e) {
			// Nothing
		}

		return $extension_id;
	}

	/**
	 * Check and set extension message
	 */
	private function postMessages($extensions = [])
	{
		if (empty($extensions)) {
			$extensions = $this->packageExtensions;
		}

		foreach ($extensions as $ext) {
			$this->postMessage($ext[0], $ext[1], $ext[2], $ext[3], $ext[4], $ext[5]);
		}

		echo '<br><br>';
	}

	/**
	 * Set extension message
	 *
	 * @param   string   $type          The extension type.
	 * @param   string   $element       The element field.
	 * @param   string   $client        0=site, 1=admin.
	 * @param   string   $group         The extension group (for plugins).
	 * @param   string   $name          The name of the extension (the title).
	 * @param   boolean  $extension_id  The extension id (if already installed).
	 */
	private function postMessage($type, $element, $client, $group, $name, $extension_id = null)
	{
		$labelClass = '';

		switch ($type) {
			case 'component':
				// Components, packages and libraries don't have a folder or client.
				// Included for completeness.
				$labelClass = 'badge bg-success';
				break;

			case 'file':
				$labelClass = 'badge bg-dark text-white';
				break;

			case 'language':
			case 'library':
				$labelClass = 'badge bg-warning text-dark';
				break;

			case 'module':
				$labelClass = 'badge bg-danger';
				break;

			case 'plugin':
				// Plugins have a folder but not a client
				$labelClass = 'badge bg-primary';
				break;

			case 'template':

			default:
			case 'package':
				break;
		}

		$extensionLabel = '<span class="' . $labelClass . '" style="text-transform: capitalize;">&nbsp;' . Text::_('COM_INSTALLER_TYPE_TYPE_' . strtoupper($type)) . '&nbsp;</span>';
		$extensionName  = '<strong>' . $name . '</strong>';

		if ($extension_id) {
			echo '<div>' . $extensionLabel . '&nbsp;&nbsp;<small>' . Text::sprintf('COM_INSTALLER_MSG_UPDATE_SUCCESS', $extensionName) . '</small></div>';
		} else {
			echo '<div>' . $extensionLabel . '&nbsp;&nbsp;<small>' . Text::sprintf('COM_INSTALLER_INSTALL_SUCCESS', $extensionName) . '</small>'
				. ' &#8680; <span class="text-success"><strong>' . Text::_('JPUBLISHED') . '</strong></span></div>';
		}
	}

	/**
	 * Files Security Check
	 *
	 * @since   4.0.8
	 */
	private function checkMediaFiles()
	{
		// Security check 4.0.8
		$image_path = ComponentHelper::getParams('com_media')->get('image_path');

		$icagenda_root_path = JPATH_ROOT . '/' . $image_path . '/icagenda';

		$deniedExtensions = [
			'.php', '.php3', '.php4', '.php5', '.php6', '.php7', '.php8',
			'.phar', '.phps', '.pl', '.py', '.pyc', '.pyo', '.jsp', '.asp',
			'.shtml', '.pht', '.phtml', '.sh', '.cgi'
		];

		$suspiciousFiles = [];

		$fileInfos = new \RecursiveIteratorIterator(
			new \RecursiveDirectoryIterator($icagenda_root_path)
		);

		foreach ($fileInfos as $pathName => $fileInfo) {
			if (!$fileInfo->isFile()) continue;
			$fileName = $fileInfo->getFilename();
			if (strtolower($fileName) !== str_ireplace($deniedExtensions, '', strtolower($fileName))) {
				$suspiciousFiles[] = '<small>[ROOT]</small> ' . str_replace(JPATH_ROOT, '', $pathName);
			}
		}

		return $suspiciousFiles;
	}

	/**
	 * DB Entries Security Check
	 *
	 * @since   4.0.8
	 */
	private function checkDataEntries()
	{
		$suspiciousEntries = [];

		$search = '%' . str_replace(' ', '%', trim('iCagenda CMD')) . '%';
		$email  = '%' . str_replace(' ', '%', trim('guest-probe')) . '%';

		$db = Factory::getContainer()->get(DatabaseInterface::class);

		$query = $db->createQuery();

		$query
			->select(['id', 'title'])
			->from($db->qn('#__icagenda_events'))
			->where($db->qn('state') . ' <> -2')
			->where($db->qn('title') . ' LIKE :search')
			->extendWhere(
				'OR',
				[
					$db->qn('created_by_email') . ' LIKE :email',
				]
			)
			->bind(':state', $state)
			->bind(':search', $search)
			->bind(':email', $email);

		$db->setQuery($query);
		$results = $db->loadObjectList();

		if ($results) {
			$query = $db->createQuery();

			// Fields to update.
			$fields = [
				$db->qn('state') . ' = -2',
			];

			// Conditions for which records should be updated.
			$conditions = [
				$db->qn('title') . ' LIKE :search OR ' . $db->qn('created_by_email') . ' LIKE :email',
			];

			$query->update($db->qn('#__icagenda_events'))->set($fields)->where($conditions);

			$query
				->bind(':search', $search)
				->bind(':email', $email);

			$db->setQuery($query);

			$result = $db->execute();

			$suspiciousEntries['EVENT'] = $results;

			return $suspiciousEntries;
		}

		return false;
	}

	/**
	 * Create folders used by iCagenda
	 */
	private function createFolders()
	{
		// Get Joomla Images PATH setting
		$image_path = ComponentHelper::getParams('com_media')->get('image_path');

		$icagenda_image_path = $image_path . '/icagenda';

		// Create Folder iCagenda in ROOT/IMAGES_PATH/icagenda
		$folders = [
			$icagenda_image_path,
			$icagenda_image_path . '/files',
			$icagenda_image_path . '/thumbs',
			$icagenda_image_path . '/thumbs/system',
			$icagenda_image_path . '/thumbs/themes',
			$icagenda_image_path . '/thumbs/copy',
			$icagenda_image_path . '/feature_icons',
			$icagenda_image_path . '/feature_icons/16_bit',
			$icagenda_image_path . '/feature_icons/24_bit',
			$icagenda_image_path . '/feature_icons/32_bit',
			$icagenda_image_path . '/feature_icons/48_bit',
			$icagenda_image_path . '/feature_icons/64_bit',
		];

		$message = '<div><em>' . Text::_('COM_ICAGENDA_FOLDER_CREATION') . '</em></div>';

		$labelClass = 'badge bg-secondary';

		foreach ($folders as $folder) {
			$message.= '<div><span class="' . $labelClass . '">&nbsp;' . Text::_('COM_ICAGENDA_FOLDER') . '&nbsp;</span>&nbsp;&nbsp;<small>';

			if (!is_dir(JPATH_ROOT . '/' . $folder)) {
				// Create Folder
				if (Folder::create(JPATH_ROOT . '/' . $folder, 0755)) {
					if ($folder == $icagenda_image_path) {
						$this->secureWebAccess($folder, true);
					} else {
						$this->secureWebAccess($folder);
					}

					$message.= '<strong><span class="text-success">' . $folder . ' ' . Text::_('COM_ICAGENDA_CREATED') . '</span></strong>';
				} else {
					$message.= '<strong><span style="text-danger">' . $folder . ' ' . Text::_('COM_ICAGENDA_CREATION_FAILED') . '</span></strong> ' . Text::_('COM_ICAGENDA_PLEASE_CREATE_MANUALLY');
				}
			} else {
				// Folder exists
				if ($folder == $icagenda_image_path) {
					$this->secureWebAccess($folder, true);
				} else {
					$this->secureWebAccess($folder);
				}

				$message.= '<strong>' . $folder . '</strong> <span>' . Text::_('COM_ICAGENDA_EXISTS') . '</span>';
			}

			$message.= '</small></div>';
		}

		$message.= '<br><br>';

		echo $message;
	}

	/**
	 * Secure web access to the iCagenda images folder.
	 * Generates a set of files that prevent direct web access, remote execution of executable files,
	 * and directory listing.
	 *
	 * This method generates:
	 * - An .htaccess file for Apache, Lighttpd, and LiteSpeed, and a web.config file for IIS 7 or later.
	 * - index.html and index.htm files to prevent directory listing.
	 *
	 * @param   string   $dir    The output directory to secure against web access
	 * @param   boolean  $root   Generate the file only at the root of the icagenda folder.
	 * @param   boolean  $force  Force replacement of existing files
	 *
	 * @return  void
	 *
	 * @since   4.0.9
	 */
	private function secureWebAccess($folder, $root = false, $force = false)
	{
		$dir = JPATH_ROOT . '/' . $folder;

		if (!is_dir($dir)) {
			return;
		}

		$deniedExtensions = 'php|php3|php4|php5|php6|php7|php8|phar|phps|pl|py|pyc|pyo|jsp|asp|shtml|pht|phtml|sh|cgi';

		if ($root) {
			// Create a .htaccess file to prevent web access
			if (!is_file($dir . '/.htaccess') || $force) {
				$htaccess = <<< APACHE
## This file was generated automatically by iCagenda
##
<FilesMatch "\.($deniedExtensions)">
  Order Deny,Allow
  Deny from all
</FilesMatch>
APACHE;

				File::write($dir . "/.htaccess", $htaccess);
			}

			// Create a web.config to prevent web access
			if (!is_file($dir . '/web.config') || $force) {
				$exExtensions = explode('|', $deniedExtensions);
				$webConfig = <<< XML
<?xml version="1.0" encoding="UTF-8"?>
<!--
This file was generated automatically by iCagenda
-->
<configuration>
  <system.webServer>
    <security>
      <requestFiltering>
        <fileExtensions>

XML;

			foreach ($exExtensions as $ext) {
				$webConfig.= <<< XML
          <add fileExtension=".$ext" allowed="false" />

XML;
			}

			$webConfig.= <<< XML
        </fileExtensions>
      </requestFiltering>
    </security>
  </system.webServer>
</configuration>
XML;

				File::write($dir . "/web.config", $webConfig);
			}
		}

		// Create a blank index.html or index.htm to prevent directory listings (all servers)
		$blankHtml = <<< HTML
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <title>Access Denied</title>
  </head>
  <body>
    <h1>Access Denied</h1>
  </body>
</html>
HTML;

		if (!is_file($dir . '/index.html') || $force) {
			File::write($dir . "/index.html", $blankHtml);
		}

		if (!is_file($dir . '/index.htm') || $force) {
			File::write($dir . "/index.htm", $blankHtml);
		}
	}

	/*
	 * Delete unused update site
	 *
	 * $eid  int  extension_id
	 */
	private function removeObsoleteUpdateSites($eid)
	{
		if ($eid) {
			$db = Factory::getContainer()->get(DatabaseInterface::class);

			$query = (method_exists($db, 'createQuery') ? $db->createQuery() : $db->getQuery(true))
				->delete('#__update_sites_extensions')
				->where('extension_id = ' . $eid);
			$db->setQuery($query);
			$db->execute();

			// Delete any unused update sites
			$query->clear()
				->select('update_site_id')
				->from('#__update_sites_extensions');
			$db->setQuery($query);
			$results = $db->loadColumn();

			if (\is_array($results)) {
				// So we need to delete the update sites and their associated updates
				$updatesite_delete = (method_exists($db, 'createQuery') ? $db->createQuery() : $db->getQuery(true));
				$updatesite_delete->delete('#__update_sites');
				$updatesite_query = (method_exists($db, 'createQuery') ? $db->createQuery() : $db->getQuery(true));
				$updatesite_query->select('update_site_id')
					->from('#__update_sites');

				// If we get results back then we can exclude them
				if (\count($results)) {
					$updatesite_query->where('update_site_id NOT IN (' . implode(',', $results) . ')');
					$updatesite_delete->where('update_site_id NOT IN (' . implode(',', $results) . ')');
				}

				// So let's find what update sites we're about to nuke and remove their associated extensions
				$db->setQuery($updatesite_query);
				$update_sites_pending_delete = $db->loadColumn();

				if (\is_array($update_sites_pending_delete) && \count($update_sites_pending_delete)) {
					// Nuke any pending updates with this site before we delete it
					// TODO: investigate alternative of using a query after the delete below with a query and not in like above
					$query->clear()
						->delete('#__updates')
						->where('update_site_id IN (' . implode(',', $update_sites_pending_delete) . ')');
					$db->setQuery($query);
					$db->execute();
				}

				// Note: this might wipe out the entire table if there are no extensions linked
				$db->setQuery($updatesite_delete);
				$db->execute();
			}

			// Last but not least we wipe out any pending updates for the extension
			$query->clear()
				->delete('#__updates')
				->where('extension_id = ' . $eid);
			$db->setQuery($query);
			$db->execute();
		}
	}

	/*
	 * Set Dlid to the extension update site.
	 *
	 * $eid  int  extension_id
	 */
	private function setDlidUpdateSite($eid)
	{
		if ($this->ictype != 'core') {
			$downloadid = ComponentHelper::getParams('com_icagenda')->get('downloadid', '');

			if ($downloadid) {
				$db = Factory::getContainer()->get(DatabaseInterface::class);

				$query = (method_exists($db, 'createQuery') ? $db->createQuery() : $db->getQuery(true))
					->select('update_site_id')
					->from($db->qn('#__update_sites_extensions'))
					->where($db->qn('extension_id') . ' = ' . (int) $eid);
				$db->setQuery($query);

				$update_site_id = $db->loadResult();

				$extraQuery = 'dlid=' . trim($downloadid);

				$query = (method_exists($db, 'createQuery') ? $db->createQuery() : $db->getQuery(true))
					->update($db->qn('#__update_sites'))
					->set($db->qn('extra_query') . ' = ' . $db->q($extraQuery))
					->where($db->qn('update_site_id') . ' = ' . $db->q($update_site_id));
				$db->setQuery($query);

				try {
					$db->execute();
				} catch (Exception $exc) {
					// Nothing
				}
			}
		}
	}

	/*
	 * Uninstall obsolete extensions
	 *
	 * @since   4.0.1
	 */
	private function uninstallObsoleteExtensions()
	{
		$db = Factory::getContainer()->get(DatabaseInterface::class);

		// Plugins uninstallation
		if (isset($this->obsoleteQueue['plugins']) && \count($this->obsoleteQueue['plugins'])) {
			foreach ($this->obsoleteQueue['plugins'] as $folder => $element) {

				$plugin = PluginHelper::getPlugin($folder, $element);

				if ($plugin) {
					// Was the plugin already installed?
					$query = (method_exists($db, 'createQuery') ? $db->createQuery() : $db->getQuery(true))
						->select('COUNT(*)')
						->from($db->qn('#__extensions'))
						->where($db->qn('element') . ' = ' . $db->q($element))
						->where($db->qn('folder') . ' = ' . $db->q($folder));
					$db->setQuery($query);

					try {
						$count = $db->loadResult();
					} catch (Exception $exc) {
						$count = 0;
					}

					if ($count) {
						$query = (method_exists($db, 'createQuery') ? $db->createQuery() : $db->getQuery(true))
							->update($db->qn('#__extensions'))
							->set($db->qn('enabled') . ' = ' . $db->q('0'))
							->set($db->qn('package_id') . ' = ' . $db->q('0'))
							->where($db->qn('element') . ' = ' . $db->q($element))
							->where($db->qn('folder') . ' = ' . $db->q($folder));
						$db->setQuery($query);

						$result = $db->execute();

						if ($result) {
							$installer = new Installer();
							$installer->setDatabase($db);
							$installer->uninstall('plugin', $plugin->id);
						}
					}
				}
			}
		}
	}

	/**
	 * Check legacy extensions (modules, plugins) bundled with the main extension
	 *
	 * @param  Installer  $adapter
	 *
	 * @return void
	 *
	 * @since  4.0.0
	 */
	private function checkLegacyExtensions(InstallerAdapter $adapter): void
	{
		$db = Factory::getContainer()->get(DatabaseInterface::class);

		if (isset($this->legacyQueue['modules']) && \count($this->legacyQueue['modules']) > 0) {
			foreach ($this->legacyQueue['modules'] as $folder => $modules) {
				if (\count($modules)) {
					foreach ($modules as $module => $modulePreferences) {

						// Was the module already installed?
						$sql = (method_exists($db, 'createQuery') ? $db->createQuery() : $db->getQuery(true))
							->select('COUNT(*)')
							->from('#__extensions')
							->where($db->qn('element') . ' = ' . $db->q($module));
						$db->setQuery($sql);

						try {
							$count = $db->loadResult();
						} catch (Exception $exc) {
							$count = 0;
						}

						if ($count) {
							// Make sure legacy modules are removed from parent package.
							$query = (method_exists($db, 'createQuery') ? $db->createQuery() : $db->getQuery(true))
								->update($db->qn('#__extensions'))
								->set($db->qn('package_id') . ' = ' . $db->q('0'))
								->where($db->qn('element') . ' = ' . $db->q($module));
							$db->setQuery($query);

							try {
								$db->execute();
							} catch (Exception $exc) {
								// Nothing
							}

//							$legacyInstall = [
//								'modules' => [
//									$folder => [
//										$module => ['sidebar-right', 0],
//									],
//								],
//							];

//							$this->installLegacyExtensions($adapter, $legacyInstall);
						}
					}
				}
			}
		}
	}

	/**
	 * Installs legacy extensions (modules, plugins) bundled with the main extension
	 *
	 * @param  Installer  $adapter
	 *
	 * @return CMSObject  The legacy extension installation status
	 *
	 * @since  4.0.0
	 */
	private function installLegacyExtensions(InstallerAdapter $adapter)
	{
		$src = $adapter->getParent()->getPath('source');

		$db = Factory::getContainer()->get(DatabaseInterface::class);

		$status = new CMSObject;
		$status->modules = [];
		$status->plugins = [];

		// Modules installation
		if (isset($this->legacyQueue['modules']) && \count($this->legacyQueue['modules']) > 0) {
			foreach ($this->legacyQueue['modules'] as $folder => $modules) {
				if (\count($modules)) {
					foreach ($modules as $module => $modulePreferences) {
						// Install the module
						if (empty($folder)) {
							$folder = 'site';
						}

						$path = $src . '/legacy/modules/' . $folder . '/' . $module;

						if (!is_dir($path)) {
							continue;
						}

						// Was the module already installed?
						$sql = (method_exists($db, 'createQuery') ? $db->createQuery() : $db->getQuery(true))
							->select('COUNT(*)')
							->from('#__modules')
							->where($db->qn('module') . ' = ' . $db->q($module));
						$db->setQuery($sql);

						try {
							$count = $db->loadResult();
						} catch (Exception $exc) {
							$count = 0;
						}

						$installer = new Installer();
						$installer->setDatabase($db);
						$result    = $installer->install($path);

						$status->modules[] = [
							'name'   => $module,
							'client' => $folder,
							'result' => $result
						];

						// Modify where it's published and its published state
						if (!$count) {
							// A. Position and state
							list($modulePosition, $modulePublished) = $modulePreferences;

							$sql = (method_exists($db, 'createQuery') ? $db->createQuery() : $db->getQuery(true))
								->update($db->qn('#__modules'))
								->set($db->qn('position') . ' = ' . $db->q($modulePosition))
								->where($db->qn('module') . ' = ' . $db->q($module));

							if ($modulePublished) {
								$sql->set($db->qn('published') . ' = ' . $db->q('1'));
							}

							$db->setQuery($sql);

							try {
								$db->execute();
							} catch (Exception $exc) {
								// Nothing
							}

							// B. Change the ordering of back-end modules to 1 + max ordering
							if ($folder == 'admin') {
								try {
									$query = (method_exists($db, 'createQuery') ? $db->createQuery() : $db->getQuery(true));
									$query->select('MAX(' . $db->qn('ordering') . ')')
										->from($db->qn('#__modules'))
										->where($db->qn('position') . '=' . $db->q($modulePosition));
									$db->setQuery($query);
									$position = $db->loadResult();
									$position++;

									$query = (method_exists($db, 'createQuery') ? $db->createQuery() : $db->getQuery(true));
									$query->update($db->qn('#__modules'))
										->set($db->qn('ordering') . ' = ' . $db->q($position))
										->where($db->qn('module') . ' = ' . $db->q($module));
									$db->setQuery($query);
									$db->execute();
								} catch (Exception $exc) {
									// Nothing
								}
							}

							// C. Link to all pages
							try {
								$query = (method_exists($db, 'createQuery') ? $db->createQuery() : $db->getQuery(true));
								$query->select('id')->from($db->qn('#__modules'))
									->where($db->qn('module') . ' = ' . $db->q($module));
								$db->setQuery($query);
								$moduleid = $db->loadResult();

								$query = (method_exists($db, 'createQuery') ? $db->createQuery() : $db->getQuery(true));
								$query->select('*')->from($db->qn('#__modules_menu'))
									->where($db->qn('moduleid') . ' = ' . $db->q($moduleid));
								$db->setQuery($query);
								$assignments = $db->loadObjectList();
								$isAssigned = !empty($assignments);

								if (!$isAssigned) {
									$o = (object) [
										'moduleid' => $moduleid,
										'menuid'   => 0
									];

									$db->insertObject('#__modules_menu', $o);
								}
							} catch (Exception $exc) {
								// Nothing
							}
						} else {
							$query = (method_exists($db, 'createQuery') ? $db->createQuery() : $db->getQuery(true))
								->update($db->qn('#__extensions'))
								->set($db->qn('package_id') . ' = ' . $db->q('0'))
								->where($db->qn('element') . ' = ' . $db->q($module));
							$db->setQuery($query);

							try {
								$db->execute();
							} catch (Exception $exc) {
								// Nothing
							}
						}
					}
				}
			}
		}

		// Plugins installation
		if (isset($this->legacyQueue['plugins']) && \count($this->legacyQueue['plugins'])) {
			foreach ($this->legacyQueue['plugins'] as $folder => $plugins) {
				if (\count($plugins)) {
					foreach ($plugins as $plugin => $published) {
						$path = $src . '/legacy/plugins/' . $folder . '/plg_' . $folder . '_' . $plugin;

						if (!is_dir($path)) {
							continue;
						}

						// Was the plugin already installed?
						$query = (method_exists($db, 'createQuery') ? $db->createQuery() : $db->getQuery(true))
							->select('COUNT(*)')
							->from($db->qn('#__extensions'))
							->where($db->qn('element') . ' = ' . $db->q($plugin))
							->where($db->qn('folder') . ' = ' . $db->q($folder));
						$db->setQuery($query);

						try {
							$count = $db->loadResult();
						} catch (Exception $exc) {
							$count = 0;
						}

						$installer = new Installer();
						$installer->setDatabase($db);
						$result = $installer->install($path);

						$status->plugins[] = [
							'name'   => 'plg_' . $folder . '_' . $plugin,
							'group'  => $folder,
							'result' => $result
						];

						if ($published && !$count) {
							$query = (method_exists($db, 'createQuery') ? $db->createQuery() : $db->getQuery(true))
								->update($db->qn('#__extensions'))
								->set($db->qn('enabled') . ' = ' . $db->q('0'))
								->set($db->qn('package_id') . ' = ' . $db->q('0'))
								->where($db->qn('element') . ' = ' . $db->q($plugin))
								->where($db->qn('folder') . ' = ' . $db->q($folder));
							$db->setQuery($query);

							try {
								$db->execute();
							} catch (Exception $exc) {
								// Nothing
							}
						} else {
							$query = (method_exists($db, 'createQuery') ? $db->createQuery() : $db->getQuery(true))
								->update($db->qn('#__extensions'))
								->set($db->qn('package_id') . ' = ' . $db->q('0'))
								->where($db->qn('element') . ' = ' . $db->q($plugin))
								->where($db->qn('folder') . ' = ' . $db->q($folder));
							$db->setQuery($query);

							try {
								$db->execute();
							} catch (Exception $exc) {
								// Nothing
							}
						}
					}
				}
			}
		}

		// Clear com_modules and com_plugins cache (needed when we alter module/plugin state)
		$this->clearCacheGroups(['com_modules', 'com_plugins']);

		return $status;
	}

	/**
	 * Uninstalls legacy extensions (modules, plugins) bundled with the main extension
	 *
	 * @param   Installer  $adapter  The parent object
	 *
	 * @return  stdClass  The legacy extensions uninstallation status
	 *
	 * @since  4.0.0
	 */
	private function uninstallLegacyExtensions(InstallerAdapter $adapter)
	{
		$db = Factory::getContainer()->get(DatabaseInterface::class);

		$status = new \stdClass();
		$status->modules = [];
		$status->plugins = [];

		$src = $adapter->getParent()->getPath('source');

		// Modules uninstallation
		if (isset($this->legacyQueue['modules']) && \count($this->legacyQueue['modules'])) {
			foreach ($this->legacyQueue['modules'] as $folder => $modules) {
				if (\count($modules)) {
					foreach ($modules as $module => $modulePreferences) {
						// Find the module ID
						$sql = (method_exists($db, 'createQuery') ? $db->createQuery() : $db->getQuery(true))
							->select($db->qn('extension_id'))
							->from($db->qn('#__extensions'))
							->where($db->qn('element') . ' = ' . $db->q($module))
							->where($db->qn('type') . ' = ' . $db->q('module'));
						$db->setQuery($sql);

						try {
							$id = $db->loadResult();
						} catch (Exception $exc) {
							$id = 0;
						}

						// Uninstall the module
						if ($id) {
							$installer = new Installer();
							$installer->setDatabase($db);
							$result = $installer->uninstall('module', $id, 1);
							$status->modules[] = [
								'name'   => $module,
								'client' => $folder,
								'result' => $result
							];
						}
					}
				}
			}
		}

		// Plugins uninstallation
		if (isset($this->legacyQueue['plugins']) && \count($this->legacyQueue['plugins'])) {
			foreach ($this->legacyQueue['plugins'] as $folder => $plugins) {
				if (\count($plugins)) {
					foreach ($plugins as $plugin => $published) {
						$sql = (method_exists($db, 'createQuery') ? $db->createQuery() : $db->getQuery(true))
							->select($db->qn('extension_id'))
							->from($db->qn('#__extensions'))
							->where($db->qn('type') . ' = ' . $db->q('plugin'))
							->where($db->qn('element') . ' = ' . $db->q($plugin))
							->where($db->qn('folder') . ' = ' . $db->q($folder));
						$db->setQuery($sql);

						try {
							$id = $db->loadResult();
						} catch (Exception $exc) {
							$id = 0;
						}

						if ($id) {
							$installer = new Installer();
							$installer->setDatabase($db);
							$result = $installer->uninstall('plugin', $id, 1);
							$status->plugins[] = [
								'name'   => 'plg_' . $folder . '_' . $plugin,
								'group'  => $folder,
								'result' => $result
							];
						}
					}
				}
			}
		}

		// Clear com_modules and com_plugins cache (needed when we alter module/plugin state)
		$this->clearCacheGroups(['com_modules', 'com_plugins']);

		return $status;
	}

	/**
	 * Clears the specified cache groups.
	 *
	 * @param   array  $clearGroups   Which cache groups to clear. Usually this is com_yourcomponent to clear your
	 *                                component's cache.
	 * @param   array  $cacheClients  Which cache clients to clear. 0 is the back-end, 1 is the front-end. If you do not
	 *                                specify anything, both cache clients will be cleared.
	 *
	 * @return  void
	 *
	 * @since  4.0.0
	 */
	public static 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' : Factory::getApplication()->get('cache_path', JPATH_CACHE),
					];

					$cache = Cache::getInstance('callback', $options);
					$cache->clean();
				} catch (\Exception $e) {
					// Ignore it
				}
			}
		}
	}
};
