<?php
/**
* @package Regular Labs Extension Manager
* @version 9.1.0
*
* @author Peter van Westen <[email protected]>
* @link https://regularlabs.com
* @copyright Copyright © 2025 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Component\RegularLabsExtensionsManager\Administrator\Model;
use Joomla\CMS\Factory as JFactory;
use Joomla\CMS\Installer\Installer as JInstaller;
use Joomla\CMS\Installer\InstallerHelper as JInstallerHelper;
use Joomla\CMS\Language\Text as JText;
use Joomla\CMS\MVC\Model\ListModel as JListModel;
use Joomla\CMS\Plugin\PluginHelper as JPluginHelper;
use Joomla\CMS\Updater\Update as JUpdate;
use Joomla\CMS\Uri\Uri as JUri;
use RegularLabs\Component\RegularLabsExtensionsManager\Administrator\Helper\ExtensionsHelper;
use RegularLabs\Library\DB as RL_DB;
use RegularLabs\Library\Input as RL_Input;
use RegularLabs\Library\Language as RL_Language;
use RegularLabs\Library\Parameters as RL_Parameters;
defined('_JEXEC') or die;
class ProcessModel extends JListModel
{
protected $config;
/**
* @var string The prefix to use with controller messages.
*/
protected $text_prefix = 'RL';
/**
* @param array An optional associative array of configuration settings.
*/
public function __construct($config = [])
{
parent::__construct($config);
$this->config = RL_Parameters::getComponent('regularlabsmanager');
}
public function getItems()
{
$view = RL_Input::getCmd('view');
$extensions = ExtensionsHelper::getFromUrl();
if ( ! empty($extensions))
{
return $extensions;
}
return match ($view)
{
'reinstall' => ExtensionsHelper::getBroken(),
'update' => ExtensionsHelper::getUpdates(),
default => [],
};
}
/**
* @return boolean
*/
public function install()
{
RL_Language::load('com_installer', JPATH_ADMINISTRATOR);
$app = JFactory::getApplication();
// Load installer plugins for assistance if required:
JPluginHelper::importPlugin('installer');
$package = $this->getPackageFromUrl();
// This event allows a custom installation of the package or a customization of the package:
$results = $app->triggerEvent('onInstallerBeforeInstaller', [$this, &$package]);
if (in_array(true, $results, true))
{
return true;
}
if (in_array(false, $results, true))
{
$this->cleanupInstall($package);
return false;
}
// Check if package was uploaded successfully.
if ( ! is_array($package))
{
$app->enqueueMessage(JText::_('COM_INSTALLER_UNABLE_TO_FIND_INSTALL_PACKAGE'), 'error');
return false;
}
// Was the package unpacked?
if (empty($package['type']))
{
JInstallerHelper::cleanupInstall($package['packagefile'], $package['extractdir']);
$app->enqueueMessage(JText::_('JLIB_INSTALLER_ABORT_DETECTMANIFEST'), 'error');
return false;
}
$installer = JInstaller::getInstance();
$success = $installer->install($package['dir']) ? true : false;
$msg = '';
$messages = $app->getMessageQueue();
foreach ($messages as $message)
{
if ($message['type'] == 'error')
{
$this->cleanupInstall($package);
return false;
}
}
if ( ! $success)
{
$msg = JText::sprintf('COM_INSTALLER_INSTALL_ERROR', JText::_('COM_INSTALLER_TYPE_TYPE_' . strtoupper($package['type'])));
}
// This event allows a custom a post-flight:
$app->triggerEvent('onInstallerAfterInstaller', [$this, &$package, $installer, &$success, &$msg]);
if ( ! $success)
{
$app->enqueueMessage($msg, 'error');
}
$this->cleanupInstall($package);
// Clear the cached extension data and menu cache
$this->cleanCache('_system');
$this->cleanCache('com_modules');
$this->cleanCache('com_plugins');
$this->cleanCache('mod_menu');
return $success;
}
/**
* @return boolean
*/
public function uninstall()
{
RL_Language::load('com_installer', JPATH_ADMINISTRATOR);
$app = JFactory::getApplication();
if ( ! $app->getIdentity()->authorise('core.delete', 'com_installer'))
{
$app->enqueueMessage(JText::_('JERROR_CORE_DELETE_NOT_PERMITTED'), 'error');
return false;
}
$extension = $this->getExtensionFromUrl();
if ( ! $extension || ! $extension->type)
{
$app->enqueueMessage('Could not find an extension to uninstall.', 'error');
return false;
}
$extension_type = JText::_('COM_INSTALLER_TYPE_TYPE_' . strtoupper($extension->type));
$installer = JInstaller::getInstance();
$result = $installer->uninstall($extension->type, $extension->id);
// Clear the cached extension data and menu cache
$this->cleanCache('_system');
$this->cleanCache('com_modules');
$this->cleanCache('com_plugins');
$this->cleanCache('mod_menu');
if ($result === false)
{
$app->enqueueMessage(JText::sprintf('COM_INSTALLER_UNINSTALL_ERROR', $extension_type), 'error');
return false;
}
$app->enqueueMessage(JText::sprintf('COM_INSTALLER_UNINSTALL_SUCCESS', $extension_type), 'success');
return true;
}
/**
* @return object|false
*/
protected function getExtensionFromUrl()
{
$extensions = ExtensionsHelper::getFromUrl();
if (empty($extensions))
{
return false;
}
$extension = $extensions[0];
$query = RL_DB::getQuery()
->select([
RL_DB::quoteName('extension_id', 'id'),
'type',
])
->from('#__extensions');
[$type, $folder] = explode('_', $extension->types[0]->type . '_');
switch ($type)
{
case 'pkg':
$query->where(RL_DB::is('type', 'package'))
->where(RL_DB::is('element', 'pkg_' . $extension->extname));
break;
case 'com':
$query->where(RL_DB::is('type', 'component'))
->where(RL_DB::is('element', 'com_' . $extension->extname));
break;
case 'mod':
$query->where(RL_DB::is('type', 'module'))
->where(RL_DB::is('element', 'mod_' . $extension->extname));
break;
case 'plg':
$query->where(RL_DB::is('type', 'plugin'))
->where(RL_DB::is('folder', $folder))
->where(RL_DB::is('element', $extension->extname));
break;
default:
return false;
}
return RL_DB::get()->setQuery($query)->loadObject();
}
/**
* @return bool|array Package details or false on failure.
*/
protected function getPackageFromUrl()
{
$url = base64_decode(RL_Input::getBase64('url'));
// Did you give us a URL?
if ( ! $url)
{
JFactory::getApplication()->enqueueMessage(JText::_('COM_INSTALLER_MSG_INSTALL_ENTER_A_URL'), 'error');
return false;
}
// We only allow http & https here
$uri = new JUri($url);
if ( ! in_array($uri->getScheme(), ['http', 'https']))
{
JFactory::getApplication()->enqueueMessage(JText::_('COM_INSTALLER_MSG_INSTALL_INVALID_URL_SCHEME'), 'error');
return false;
}
// Handle updater XML file case:
if (preg_match('/\.xml\s*$/', $url))
{
$update = new JUpdate;
$update->loadFromXml($url);
$package_url = trim($update->get('downloadurl', false)->_data);
if ($package_url)
{
$url = $package_url;
}
unset($update);
}
// Download the package at the URL given.
$p_file = JInstallerHelper::downloadPackage($url);
// Was the package downloaded?
if ( ! $p_file)
{
JFactory::getApplication()->enqueueMessage(JText::_('COM_INSTALLER_MSG_INSTALL_INVALID_URL'), 'error');
return false;
}
$tmp_dest = JFactory::getApplication()->get('tmp_path');
// Unpack the downloaded package file.
$package = JInstallerHelper::unpack($tmp_dest . '/' . $p_file, true);
return $package;
}
private function cleanupInstall($package)
{
// Cleanup the install files.
if ( ! is_file($package['packagefile']))
{
$package['packagefile'] = JFactory::getApplication()->get('tmp_path') . '/' . $package['packagefile'];
}
JInstallerHelper::cleanupInstall($package['packagefile'], $package['extractdir']);
}
}