Your IP : 216.73.216.240
<?php
/**
*----------------------------------------------------------------------------
* iCagenda Events Management Extension for Joomla!
*----------------------------------------------------------------------------
* @version 4.0.11 2026-07-02
*
* @package iCagenda.pro
* @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 = 'pro';
/**
* 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'],
// PRO plugins
['plugin', 'pro', 1, 'icagenda'],
['plugin', 'payment_paypal', 1, 'icagenda'],
['plugin', 'tickets', 1, 'icagenda'],
];
/**
* 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'],
// PRO File
['file', 'file_icagenda', 0, '', 'iCagenda Pro Files <span class="badge bg-info">PRO</span>'],
// Library
['library', 'ic_library', 0, '', 'iC Library'],
// PRO Modules
['module', 'mod_ic_event_list', 0, '', 'iCagenda - Event List <span class="badge bg-info">PRO</span>'],
// Modules
['module', 'mod_icagenda_calendar', 0, '', 'iCagenda - Calendar'],
// PRO Plugins
['plugin', 'pro', 1, 'icagenda', 'iCagenda - Pro <span class="badge bg-info">PRO</span>'],
['plugin', 'payment_paypal', 1, 'icagenda', 'iCagenda - Payment :: PayPal <span class="badge bg-info">PRO</span>'],
['plugin', 'tickets', 1, 'icagenda', 'iCagenda - Tickets <span class="badge bg-info">PRO</span>'],
// 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 • <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;"> ' . Text::_('COM_INSTALLER_TYPE_TYPE_' . strtoupper($type)) . ' </span>';
$extensionName = '<strong>' . $name . '</strong>';
if ($extension_id) {
echo '<div>' . $extensionLabel . ' <small>' . Text::sprintf('COM_INSTALLER_MSG_UPDATE_SUCCESS', $extensionName) . '</small></div>';
} else {
echo '<div>' . $extensionLabel . ' <small>' . Text::sprintf('COM_INSTALLER_INSTALL_SUCCESS', $extensionName) . '</small>'
. ' ⇨ <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(':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 . '"> ' . Text::_('COM_ICAGENDA_FOLDER') . ' </span> <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
}
}
}
}
};