1401 lines
38 KiB
PHP
1401 lines
38 KiB
PHP
<?php
|
||
/**
|
||
* @package FOF
|
||
* @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||
* @license GNU General Public License version 3, or later
|
||
*/
|
||
|
||
namespace FOF40\InstallScript;
|
||
|
||
defined('_JEXEC') || die;
|
||
|
||
use Exception;
|
||
use FOF40\Container\Container;
|
||
use FOF40\Database\Installer as DatabaseInstaller;
|
||
use FOF40\Utils\ViewManifestMigration;
|
||
use JDatabaseDriver;
|
||
use Joomla\CMS\Factory as JoomlaFactory;
|
||
use Joomla\CMS\Filesystem\File;
|
||
use Joomla\CMS\Filesystem\Folder;
|
||
use Joomla\CMS\Installer\Adapter\ComponentAdapter;
|
||
use Joomla\CMS\Installer\Installer as JoomlaInstaller;
|
||
use Joomla\CMS\Log\Log;
|
||
use Joomla\CMS\Table\Menu;
|
||
use Joomla\Database\DatabaseDriver;
|
||
|
||
// In case FOF's autoloader is not present yet, e.g. new installation
|
||
if (!class_exists('FOF40\\InstallScript\\BaseInstaller', true))
|
||
{
|
||
require_once __DIR__ . '/BaseInstaller.php';
|
||
}
|
||
|
||
/**
|
||
* A helper class which you can use to create component installation scripts.
|
||
*
|
||
* Example usage: class Com_ExampleInstallerScript extends FOF40\Utils\InstallScript\Component
|
||
*
|
||
* This namespace contains more classes for creating installation scripts for other kinds of Joomla! extensions as well.
|
||
* Do keep in mind that only components, modules and plugins could have post-installation scripts before Joomla! 3.3.
|
||
*/
|
||
class Component extends BaseInstaller
|
||
{
|
||
/**
|
||
* The component's name. Auto-filled from the class name.
|
||
*
|
||
* @var string
|
||
*/
|
||
public $componentName = '';
|
||
|
||
/**
|
||
* The title of the component (printed on installation and uninstallation messages)
|
||
*
|
||
* @var string
|
||
*/
|
||
protected $componentTitle = 'Foobar Component';
|
||
|
||
/**
|
||
* The list of obsolete extra modules and plugins to uninstall on component upgrade / installation.
|
||
*
|
||
* @var array
|
||
*/
|
||
protected $uninstallation_queue = [
|
||
// modules => { (folder) => { (module) }* }*
|
||
'modules' => [
|
||
'admin' => [],
|
||
'site' => [],
|
||
],
|
||
// plugins => { (folder) => { (element) }* }*
|
||
'plugins' => [
|
||
'system' => [],
|
||
],
|
||
];
|
||
|
||
/**
|
||
* Obsolete files and folders to remove from the free version only. This is used when you move a feature from the
|
||
* free version of your extension to its paid version. If you don't have such a distinction you can ignore this.
|
||
*
|
||
* @var array
|
||
*/
|
||
protected $removeFilesFree = [
|
||
'files' => [
|
||
// Use pathnames relative to your site's root, e.g.
|
||
// 'administrator/components/com_foobar/helpers/whatever.php'
|
||
],
|
||
'folders' => [
|
||
// Use pathnames relative to your site's root, e.g.
|
||
// 'administrator/components/com_foobar/baz'
|
||
],
|
||
];
|
||
|
||
/**
|
||
* Obsolete files and folders to remove from both paid and free releases. This is used when you refactor code and
|
||
* some files inevitably become obsolete and need to be removed.
|
||
*
|
||
* @var array
|
||
*/
|
||
protected $removeFilesAllVersions = [
|
||
'files' => [
|
||
// Use pathnames relative to your site's root, e.g.
|
||
// 'administrator/components/com_foobar/helpers/whatever.php'
|
||
],
|
||
'folders' => [
|
||
// Use pathnames relative to your site's root, e.g.
|
||
// 'administrator/components/com_foobar/baz'
|
||
],
|
||
];
|
||
|
||
/**
|
||
* A list of scripts to be copied to the "cli" directory of the site
|
||
*
|
||
* @var array
|
||
*/
|
||
protected $cliScriptFiles = [
|
||
// Use just the filename, e.g.
|
||
// 'my-cron-script.php'
|
||
];
|
||
|
||
/**
|
||
* The path inside your package where cli scripts are stored
|
||
*
|
||
* @var string
|
||
*/
|
||
protected $cliSourcePath = 'cli';
|
||
|
||
/**
|
||
* Is the schemaXmlPath class variable a relative path? If set to true the schemaXmlPath variable contains a path
|
||
* relative to the component's back-end directory. If set to false the schemaXmlPath variable contains an absolute
|
||
* filesystem path.
|
||
*
|
||
* @var boolean
|
||
*/
|
||
protected $schemaXmlPathRelative = true;
|
||
|
||
/**
|
||
* The path where the schema XML files are stored. Its contents depend on the schemaXmlPathRelative variable above
|
||
* true => schemaXmlPath contains a path relative to the component's back-end directory
|
||
* false => schemaXmlPath contains an absolute filesystem path
|
||
*
|
||
* @var string
|
||
*/
|
||
protected $schemaXmlPath = 'sql/xml';
|
||
|
||
/**
|
||
* Is this the paid version of the extension? This only determines which files / extensions will be removed.
|
||
*
|
||
* @var boolean
|
||
*/
|
||
protected $isPaid = false;
|
||
|
||
/**
|
||
* Should I copy XML manifests from the tmpl and ViewTemplates folders into the views folder on Joomla 3?
|
||
*
|
||
* This copies `tmpl/<VIEWNAME>/*.xml` and `ViewTemplates/<VIEWNAME>/*.xml` to `views/<VIEWNAME>/tmpl/*.xml` on
|
||
* Joomla 3.
|
||
*
|
||
* @var bool
|
||
*/
|
||
protected $migrateJoomla4MenuXMLFiles = true;
|
||
|
||
/**
|
||
* Should I remove the legacy `views` folder on Joomla 4?
|
||
*
|
||
* This removes both the front- and backend `views` folder. Recommended when `$migrateJoomla4MenuXMLFiles` is also
|
||
* true.
|
||
*
|
||
* @var bool
|
||
*/
|
||
protected $removeLegacyViewsFolder = true;
|
||
|
||
/**
|
||
* The path to the component's backend directory.
|
||
*
|
||
* Leave null to assume JPATH_ADMINISTRATOR . '/components/' . $this->componentName
|
||
*
|
||
* @var string|null
|
||
* @since 4.0.1
|
||
*/
|
||
protected $backendPath = null;
|
||
|
||
/**
|
||
* The path to the component's frontend directory.
|
||
*
|
||
* Leave null to assume JPATH_SITE . '/components/' . $this->componentName
|
||
*
|
||
* @var string|null
|
||
* @since 4.0.1
|
||
*/
|
||
protected $frontendPath = null;
|
||
|
||
/**
|
||
* Module installer script constructor.
|
||
*/
|
||
public function __construct()
|
||
{
|
||
// Get the plugin name and folder from the class name (it's always plgFolderPluginInstallerScript) if necessary.
|
||
if (empty($this->componentName))
|
||
{
|
||
$class = get_class($this);
|
||
$words = preg_replace('/(\s)+/', '_', $class);
|
||
$words = strtolower(preg_replace('/(?<=\\w)([A-Z])/', '_\\1', $words));
|
||
$classParts = explode('_', $words);
|
||
|
||
$this->componentName = 'com_' . $classParts[2];
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Joomla! pre-flight event. This runs before Joomla! installs or updates the component. This is our last chance to
|
||
* tell Joomla! if it should abort the installation.
|
||
*
|
||
* @param string $type Installation type (install, update, discover_install)
|
||
* @param ComponentAdapter $parent Parent object
|
||
*
|
||
* @return boolean True to let the installation proceed, false to halt the installation
|
||
*
|
||
* @noinspection PhpUnusedParameterInspection
|
||
*/
|
||
public function preflight(string $type, ComponentAdapter $parent): bool
|
||
{
|
||
// Do not run on uninstall.
|
||
if ($type === 'uninstall')
|
||
{
|
||
return true;
|
||
}
|
||
|
||
// Check the minimum PHP version
|
||
if (!$this->checkPHPVersion())
|
||
{
|
||
return false;
|
||
}
|
||
|
||
// Check the minimum Joomla! version
|
||
if (!$this->checkJoomlaVersion())
|
||
{
|
||
return false;
|
||
}
|
||
|
||
// Clear op-code caches to prevent any cached code issues
|
||
$this->clearOpcodeCaches();
|
||
|
||
// Workarounds for JoomlaInstaller issues.
|
||
if (in_array($type, ['install', 'discover_install']))
|
||
{
|
||
// Bugfix for "Database function returned no error"
|
||
$this->bugfixDBFunctionReturnedNoError();
|
||
}
|
||
else
|
||
{
|
||
// Bugfix for "Can not build admin menus"
|
||
$this->bugfixCantBuildAdminMenus();
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* Runs after install, update or discover_update. In other words, it executes after Joomla! has finished installing
|
||
* or updating your component. This is the last chance you've got to perform any additional installations, clean-up,
|
||
* database updates and similar housekeeping functions.
|
||
*
|
||
* @param string $type install, update or discover_update
|
||
* @param ComponentAdapter $parent Parent object
|
||
*
|
||
* @return void
|
||
* @throws Exception
|
||
*
|
||
*/
|
||
public function postflight(string $type, ComponentAdapter $parent): void
|
||
{
|
||
// Do not run on uninstall.
|
||
if ($type === 'uninstall')
|
||
{
|
||
return;
|
||
}
|
||
|
||
// Add ourselves to the list of extensions depending on FOF40
|
||
$this->addDependency('fof40', $this->componentName);
|
||
$this->removeDependency('fof30', $this->componentName);
|
||
|
||
// Install or update database
|
||
$dbInstaller = new DatabaseInstaller(JoomlaFactory::getDbo(),
|
||
($this->schemaXmlPathRelative ? JPATH_ADMINISTRATOR . '/components/' . $this->componentName : '') . '/' .
|
||
$this->schemaXmlPath
|
||
);
|
||
$dbInstaller->updateSchema();
|
||
|
||
// These workarounds are only needed, and only work, on Joomla! 3.x
|
||
if (strpos(JVERSION, '3.') === 0)
|
||
{
|
||
// Make sure menu items are installed
|
||
$this->_createAdminMenus($parent);
|
||
|
||
// Make sure menu items are published
|
||
$this->_reallyPublishAdminMenuItems($parent);
|
||
}
|
||
|
||
// Which files should I remove?
|
||
if ($this->isPaid)
|
||
{
|
||
// This is the paid version, only remove the removeFilesAllVersions files
|
||
$removeFiles = $this->removeFilesAllVersions;
|
||
}
|
||
else
|
||
{
|
||
// This is the free version, remove the removeFilesAllVersions and removeFilesFree files
|
||
$removeFiles = ['files' => [], 'folders' => []];
|
||
|
||
if (isset($this->removeFilesAllVersions['files']))
|
||
{
|
||
if (isset($this->removeFilesFree['files']))
|
||
{
|
||
$removeFiles['files'] = array_merge($this->removeFilesAllVersions['files'], $this->removeFilesFree['files']);
|
||
}
|
||
else
|
||
{
|
||
$removeFiles['files'] = $this->removeFilesAllVersions['files'];
|
||
}
|
||
}
|
||
elseif (isset($this->removeFilesFree['files']))
|
||
{
|
||
$removeFiles['files'] = $this->removeFilesFree['files'];
|
||
}
|
||
|
||
if (isset($this->removeFilesAllVersions['folders']))
|
||
{
|
||
if (isset($this->removeFilesFree['folders']))
|
||
{
|
||
$removeFiles['folders'] = array_merge($this->removeFilesAllVersions['folders'], $this->removeFilesFree['folders']);
|
||
}
|
||
else
|
||
{
|
||
$removeFiles['folders'] = $this->removeFilesAllVersions['folders'];
|
||
}
|
||
}
|
||
elseif (isset($this->removeFilesFree['folders']))
|
||
{
|
||
$removeFiles['folders'] = $this->removeFilesFree['folders'];
|
||
}
|
||
}
|
||
|
||
// Remove obsolete files and folders
|
||
$this->removeFilesAndFolders($removeFiles);
|
||
|
||
// Make sure everything is copied properly
|
||
$this->bugfixFilesNotCopiedOnUpdate($parent);
|
||
|
||
// Copy the CLI files (if any)
|
||
$this->copyCliFiles($parent);
|
||
|
||
// Show the post-installation page
|
||
$this->renderPostInstallation($parent);
|
||
|
||
// Uninstall obsolete subextensions
|
||
$this->uninstallObsoleteSubextensions($parent);
|
||
|
||
// Clear the FOF cache
|
||
$false = false;
|
||
$cache = JoomlaFactory::getCache('fof', '');
|
||
$cache->store($false, 'cache', 'fof');
|
||
|
||
// Make sure the Joomla! menu structure is correct
|
||
$this->_rebuildMenu();
|
||
|
||
// Add post-installation messages on Joomla! 3.2 and later
|
||
$this->_applyPostInstallationMessages();
|
||
|
||
// Clear the opcode caches again - in case someone accessed the extension while the files were being upgraded.
|
||
$this->clearOpcodeCaches();
|
||
|
||
/**
|
||
* DO NOT USE THE CONTAINER TO GET THE PATHS.
|
||
*
|
||
* There are two cases when updating from a FOF 3 version of a component may cause the container to fail to
|
||
* load:
|
||
*
|
||
* 1. If the component failed to update fully (because Joomla does that)
|
||
* 2. You are using opcache but it failed to clear, e.g. the host disabled the function to do so.
|
||
*
|
||
* Using the hardcoded paths is much safer in this context.
|
||
*
|
||
* Also note that this code carries two further defenses in cases we start using the container again in the
|
||
* future:
|
||
*
|
||
* 1. It is moved AFTER the call to bugfixFilesNotCopiedOnUpdate() to solve the problem of Joomla failing the
|
||
* update.
|
||
* 2. It is moved AFTER the calll to clearOpcodeCaches() to deal with the opcache not being cleared.
|
||
*
|
||
* However, neither solution is bulletproof. As a result it makes far more sense to NOT use the container if we
|
||
* can help it...
|
||
*/
|
||
$frontendPath = $this->frontendPath ?? (JPATH_SITE . '/components/' . $this->componentName);
|
||
$backendPath = $this->backendPath ?? (JPATH_ADMINISTRATOR . '/components/' . $this->componentName);
|
||
|
||
// Migrate view manifest XML files
|
||
if ($this->migrateJoomla4MenuXMLFiles)
|
||
{
|
||
|
||
ViewManifestMigration::migrateJoomla4MenuXMLFiles_real($frontendPath, $backendPath);
|
||
}
|
||
|
||
// Remove the legacy Joomla 3 `views` folder
|
||
if ($this->removeLegacyViewsFolder)
|
||
{
|
||
ViewManifestMigration::removeJoomla3LegacyViews_real($frontendPath, $backendPath);
|
||
}
|
||
|
||
|
||
// Finally, see if FOF 3.x is obsolete and remove it.
|
||
// $this->uninstallFOF3IfNecessary();
|
||
}
|
||
|
||
/**
|
||
* Runs on uninstallation
|
||
*
|
||
* @param ComponentAdapter $parent The parent object
|
||
*/
|
||
public function uninstall(ComponentAdapter $parent): void
|
||
{
|
||
// Uninstall database
|
||
$dbInstaller = new DatabaseInstaller(JoomlaFactory::getDbo(),
|
||
($this->schemaXmlPathRelative ? JPATH_ADMINISTRATOR . '/components/' . $this->componentName : '') . '/' .
|
||
$this->schemaXmlPath
|
||
);
|
||
|
||
$dbInstaller->removeSchema();
|
||
|
||
// Uninstall post-installation messages on Joomla! 3.2 and later
|
||
$this->uninstallPostInstallationMessages();
|
||
|
||
// Remove ourselves from the list of extensions depending of FOF 4
|
||
$this->removeDependency('fof40', $this->componentName);
|
||
|
||
// Uninstall FOF 4 if nothing else depends on it
|
||
$this->uninstallFOF4IfNecessary();
|
||
|
||
// Show the post-uninstallation page
|
||
$this->renderPostUninstallation($parent);
|
||
}
|
||
|
||
/**
|
||
* Copies the CLI scripts into Joomla!'s cli directory
|
||
*
|
||
* @param ComponentAdapter $parent
|
||
*/
|
||
protected function copyCliFiles(ComponentAdapter $parent): void
|
||
{
|
||
$src = $parent->getParent()->getPath('source');
|
||
|
||
foreach ($this->cliScriptFiles as $script)
|
||
{
|
||
if (is_file(JPATH_ROOT . '/cli/' . $script))
|
||
{
|
||
File::delete(JPATH_ROOT . '/cli/' . $script);
|
||
}
|
||
|
||
if (is_file($src . '/' . $this->cliSourcePath . '/' . $script))
|
||
{
|
||
File::copy($src . '/' . $this->cliSourcePath . '/' . $script, JPATH_ROOT . '/cli/' . $script);
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Fix for Joomla bug: sometimes files are not copied on update.
|
||
*
|
||
* We have observed that ever since Joomla! 1.5.5, when Joomla! is performing an extension update some files /
|
||
* folders are not copied properly. This seems to be a bit random and seems to be more likely to happen the more
|
||
* added / modified files and folders you have. We are trying to work around it by retrying the copy operation
|
||
* ourselves WITHOUT going through the manifest, based entirely on the conventions we follow for Akeeba Ltd's
|
||
* extensions.
|
||
*
|
||
* @param ComponentAdapter $parent
|
||
*/
|
||
protected function bugfixFilesNotCopiedOnUpdate(ComponentAdapter $parent): void
|
||
{
|
||
Log::add("Joomla! extension update workaround for component $this->componentName", Log::INFO, 'fof4_extension_installation');
|
||
|
||
$temporarySource = $parent->getParent()->getPath('source');
|
||
|
||
$copyMap = [
|
||
// Backend component files
|
||
'backend' => JPATH_ADMINISTRATOR . '/components/' . $this->componentName,
|
||
'admin' => JPATH_ADMINISTRATOR . '/components/' . $this->componentName,
|
||
// Frontend component files
|
||
'frontend' => JPATH_SITE . '/components/' . $this->componentName,
|
||
'site' => JPATH_SITE . '/components/' . $this->componentName,
|
||
// Backend language
|
||
'language/backend' => JPATH_ADMINISTRATOR . '/language',
|
||
'language/admin' => JPATH_ADMINISTRATOR . '/language',
|
||
// Frontend language
|
||
'language/frontend' => JPATH_SITE . '/language',
|
||
'language/site' => JPATH_SITE . '/language',
|
||
// Media files
|
||
'media' => JPATH_ROOT . '/media/' . $this->componentName,
|
||
];
|
||
|
||
foreach ($copyMap as $partialSource => $target)
|
||
{
|
||
$source = $temporarySource . '/' . $partialSource;
|
||
|
||
Log::add(__CLASS__ . ":: Conditional copy $source to $target", Log::DEBUG, 'fof4_extension_installation');
|
||
|
||
$this->recursiveConditionalCopy($source, $target);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Override this method to display a custom component installation message if you so wish
|
||
*
|
||
* @param ComponentAdapter $parent Parent class calling us
|
||
*
|
||
* @noinspection PhpUnusedParameterInspection
|
||
*/
|
||
protected function renderPostInstallation(ComponentAdapter $parent): void
|
||
{
|
||
echo "<h3>$this->componentName has been installed</h3>";
|
||
}
|
||
|
||
/**
|
||
* Override this method to display a custom component uninstallation message if you so wish
|
||
*
|
||
* @param ComponentAdapter $parent Parent class calling us
|
||
*
|
||
* @noinspection PhpUnusedParameterInspection
|
||
*/
|
||
protected function renderPostUninstallation(ComponentAdapter $parent): void
|
||
{
|
||
echo "<h3>$this->componentName has been uninstalled</h3>";
|
||
}
|
||
|
||
/**
|
||
* Bugfix for "DB function returned no error"
|
||
*/
|
||
protected function bugfixDBFunctionReturnedNoError(): void
|
||
{
|
||
$db = JoomlaFactory::getDbo();
|
||
|
||
try
|
||
{
|
||
// Fix broken #__assets records
|
||
$this->deleteComponentAssetRecords($db);
|
||
|
||
// Fix broken #__extensions records
|
||
$this->deleteComponentExtensionRecord($db);
|
||
|
||
/**
|
||
* Fix broken #__menu records
|
||
*
|
||
* Only run on Joomla! versions lower than 3.7. Joomla! 3.7 introduced a backend menu manager which
|
||
* lets the user create missing menu items. Moreover, it lets them create custom links to the component
|
||
* which means that our menu deleting code would break them! So we don't run this code in newer Joomla!
|
||
* versions any more.
|
||
*/
|
||
$this->deleteComponentMenuRecord($db);
|
||
}
|
||
catch (Exception $exc)
|
||
{
|
||
return;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Joomla! 1.6+ bugfix for "Can not build admin menus"
|
||
*/
|
||
protected function bugfixCantBuildAdminMenus(): void
|
||
{
|
||
$db = JoomlaFactory::getDbo();
|
||
|
||
// If there are multiple #__extensions record, keep one of them
|
||
$query = $db->getQuery(true);
|
||
$query->select('extension_id')
|
||
->from('#__extensions')
|
||
->where($db->qn('type') . ' = ' . $db->q('component'))
|
||
->where($db->qn('element') . ' = ' . $db->q($this->componentName));
|
||
$db->setQuery($query);
|
||
|
||
try
|
||
{
|
||
$ids = $db->loadColumn();
|
||
}
|
||
catch (Exception $exc)
|
||
{
|
||
return;
|
||
}
|
||
|
||
|
||
if ((is_array($ids) || $ids instanceof \Countable ? count($ids) : 0) > 1)
|
||
{
|
||
asort($ids);
|
||
$extension_id = array_shift($ids); // Keep the oldest id
|
||
|
||
foreach ($ids as $id)
|
||
{
|
||
$query = $db->getQuery(true);
|
||
$query->delete('#__extensions')
|
||
->where($db->qn('extension_id') . ' = ' . $db->q($id));
|
||
$db->setQuery($query);
|
||
|
||
try
|
||
{
|
||
$db->execute();
|
||
}
|
||
catch (Exception $exc)
|
||
{
|
||
// Nothing
|
||
}
|
||
}
|
||
}
|
||
|
||
// If there are multiple assets records, delete all except the oldest one
|
||
$query = $db->getQuery(true);
|
||
$query->select('id')
|
||
->from('#__assets')
|
||
->where($db->qn('name') . ' = ' . $db->q($this->componentName));
|
||
$db->setQuery($query);
|
||
$ids = $db->loadObjectList();
|
||
|
||
if ((is_array($ids) || $ids instanceof \Countable ? count($ids) : 0) > 1)
|
||
{
|
||
asort($ids);
|
||
$asset_id = array_shift($ids); // Keep the oldest id
|
||
|
||
foreach ($ids as $id)
|
||
{
|
||
$query = $db->getQuery(true);
|
||
$query->delete('#__assets')
|
||
->where($db->qn('id') . ' = ' . $db->q($id));
|
||
$db->setQuery($query);
|
||
|
||
try
|
||
{
|
||
$db->execute();
|
||
}
|
||
catch (Exception $exc)
|
||
{
|
||
// Nothing
|
||
}
|
||
}
|
||
}
|
||
|
||
// Remove #__menu records for good measure! –– I think this is not necessary and causes the menu item to
|
||
// disappear on extension update.
|
||
/**
|
||
* $query = $db->getQuery(true);
|
||
* $query->select('id')
|
||
* ->from('#__menu')
|
||
* ->where($db->qn('type') . ' = ' . $db->q('component'))
|
||
* ->where($db->qn('menutype') . ' = ' . $db->q('main'))
|
||
* ->where($db->qn('link') . ' LIKE ' . $db->q('index.php?option=' . $this->componentName));
|
||
* $db->setQuery($query);
|
||
*
|
||
* try
|
||
* {
|
||
* $ids1 = $db->loadColumn();
|
||
* }
|
||
* catch (Exception $exc)
|
||
* {
|
||
* $ids1 = array();
|
||
* }
|
||
*
|
||
* if (empty($ids1))
|
||
* {
|
||
* $ids1 = array();
|
||
* }
|
||
*
|
||
* $query = $db->getQuery(true);
|
||
* $query->select('id')
|
||
* ->from('#__menu')
|
||
* ->where($db->qn('type') . ' = ' . $db->q('component'))
|
||
* ->where($db->qn('menutype') . ' = ' . $db->q('main'))
|
||
* ->where($db->qn('link') . ' LIKE ' . $db->q('index.php?option=' . $this->componentName . '&%'));
|
||
* $db->setQuery($query);
|
||
*
|
||
* try
|
||
* {
|
||
* $ids2 = $db->loadColumn();
|
||
* }
|
||
* catch (Exception $exc)
|
||
* {
|
||
* $ids2 = array();
|
||
* }
|
||
*
|
||
* if (empty($ids2))
|
||
* {
|
||
* $ids2 = array();
|
||
* }
|
||
*
|
||
* $ids = array_merge($ids1, $ids2);
|
||
*
|
||
* if (!empty($ids))
|
||
* {
|
||
* foreach ($ids as $id)
|
||
* {
|
||
* $query = $db->getQuery(true);
|
||
* $query->delete('#__menu')
|
||
* ->where($db->qn('id') . ' = ' . $db->q($id));
|
||
* $db->setQuery($query);
|
||
*
|
||
* try
|
||
* {
|
||
* $db->execute();
|
||
* }
|
||
* catch (Exception $exc)
|
||
* {
|
||
* // Nothing
|
||
* }
|
||
* }
|
||
* }
|
||
* /**/
|
||
}
|
||
|
||
/**
|
||
* Removes obsolete files and folders
|
||
*
|
||
* @param array $removeList The files and directories to remove
|
||
*/
|
||
protected function removeFilesAndFolders(array $removeList): void
|
||
{
|
||
// Remove files
|
||
if (isset($removeList['files']) && !empty($removeList['files']))
|
||
{
|
||
foreach ($removeList['files'] as $file)
|
||
{
|
||
$f = JPATH_ROOT . '/' . $file;
|
||
|
||
if (!is_file($f))
|
||
{
|
||
continue;
|
||
}
|
||
|
||
File::delete($f);
|
||
}
|
||
}
|
||
// Remove folders
|
||
if (!isset($removeList['folders']))
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (empty($removeList['folders']))
|
||
{
|
||
return;
|
||
}
|
||
|
||
foreach ($removeList['folders'] as $folder)
|
||
{
|
||
$f = JPATH_ROOT . '/' . $folder;
|
||
|
||
if (!@file_exists($f) || !is_dir($f) || is_link($f))
|
||
{
|
||
continue;
|
||
}
|
||
|
||
Folder::delete($f);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Uninstalls obsolete subextensions (modules, plugins) bundled with the main extension
|
||
*
|
||
* @param ComponentAdapter $parent The parent object
|
||
*
|
||
* @return \stdClass The sub-extension uninstallation status
|
||
* @noinspection PhpUnusedParameterInspection
|
||
*/
|
||
protected function uninstallObsoleteSubextensions(ComponentAdapter $parent)
|
||
{
|
||
$db = JoomlaFactory::getDBO();
|
||
$status = new \stdClass();
|
||
$status->modules = [];
|
||
$status->plugins = [];
|
||
|
||
// Modules uninstallation
|
||
if (isset($this->uninstallation_queue['modules']) && count($this->uninstallation_queue['modules']))
|
||
{
|
||
foreach ($this->uninstallation_queue['modules'] as $folder => $modules)
|
||
{
|
||
if ((is_array($modules) || $modules instanceof \Countable ? count($modules) : 0) > 0)
|
||
{
|
||
foreach ($modules as $module)
|
||
{
|
||
// Find the module ID
|
||
$sql = $db->getQuery(true)
|
||
->select($db->qn('extension_id'))
|
||
->from($db->qn('#__extensions'))
|
||
->where($db->qn('element') . ' = ' . $db->q('mod_' . $module))
|
||
->where($db->qn('type') . ' = ' . $db->q('module'));
|
||
$db->setQuery($sql);
|
||
$id = $db->loadResult();
|
||
// Uninstall the module
|
||
if ($id)
|
||
{
|
||
$installer = new JoomlaInstaller;
|
||
$result = $installer->uninstall('module', $id, 1);
|
||
$status->modules[] = [
|
||
'name' => 'mod_' . $module,
|
||
'client' => $folder,
|
||
'result' => $result,
|
||
];
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Plugins uninstallation
|
||
if (isset($this->uninstallation_queue['plugins']) && count($this->uninstallation_queue['plugins']))
|
||
{
|
||
foreach ($this->uninstallation_queue['plugins'] as $folder => $plugins)
|
||
{
|
||
if ((is_array($plugins) || $plugins instanceof \Countable ? count($plugins) : 0) > 0)
|
||
{
|
||
foreach ($plugins as $plugin)
|
||
{
|
||
$sql = $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);
|
||
|
||
$id = $db->loadResult();
|
||
|
||
if ($id)
|
||
{
|
||
$installer = new JoomlaInstaller;
|
||
$result = $installer->uninstall('plugin', $id, 1);
|
||
$status->plugins[] = [
|
||
'name' => 'plg_' . $plugin,
|
||
'group' => $folder,
|
||
'result' => $result,
|
||
];
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return $status;
|
||
}
|
||
|
||
/**
|
||
* @param ComponentAdapter $parent
|
||
*
|
||
* @return bool
|
||
*
|
||
* @throws Exception When the Joomla! menu is FUBAR
|
||
*/
|
||
private function _createAdminMenus(ComponentAdapter $parent): bool
|
||
{
|
||
$db = $parent->getParent()->getDbo();
|
||
/** @var Menu $table */
|
||
$table = new Menu(JoomlaFactory::getDbo());
|
||
$option = $parent->get('element');
|
||
|
||
// If a component exists with this option in the table then we don't need to add menus
|
||
$query = $db->getQuery(true)
|
||
->select('COUNT(*)')
|
||
->from($db->qn('#__menu') . ' AS ' . $db->qn('m'))
|
||
->leftJoin($db->qn('#__extensions', 'e') . ' ON ' .
|
||
$db->qn('m.component_id') . ' = ' . $db->qn('e.extension_id'))
|
||
->where($db->qn('m.parent_id') . ' = ' . $db->q(1))
|
||
->where($db->qn('m.client_id') . ' = ' . $db->q(1))
|
||
->where($db->qn('e.type') . ' = ' . $db->q('component'))
|
||
->where($db->qn('e.element') . ' = ' . $db->q($option));
|
||
|
||
$db->setQuery($query);
|
||
|
||
$existingMenus = $db->loadResult();
|
||
|
||
if ($existingMenus)
|
||
{
|
||
return true;
|
||
}
|
||
|
||
// Let's find the extension id
|
||
$query->clear()
|
||
->select($db->qn('e.extension_id'))
|
||
->from($db->qn('#__extensions', 'e'))
|
||
->where($db->qn('e.type') . ' = ' . $db->q('component'))
|
||
->where($db->qn('e.element') . ' = ' . $db->q($option));
|
||
$db->setQuery($query);
|
||
$componentId = $db->loadResult();
|
||
|
||
// Ok, now its time to handle the menus. Start with the component root menu, then handle submenus.
|
||
if (method_exists($parent, 'getManifest'))
|
||
{
|
||
$menuElement = $parent->getManifest()->administration->menu;
|
||
}
|
||
else
|
||
{
|
||
$menuElement = $parent->get('manifest')->administration->menu;
|
||
}
|
||
|
||
// We need to insert the menu item as the last child of Joomla!'s menu root node. First let's make sure that
|
||
// it exists. Normally it should be the menu item with ID = 1.
|
||
$query = $db->getQuery(true)
|
||
->select($db->qn('id'))
|
||
->from($db->qn('#__menu'))
|
||
->where($db->qn('id') . ' = ' . $db->q(1));
|
||
$rootItemId = $db->setQuery($query)->loadResult();
|
||
|
||
// If we didn't find the item with ID=1 something has screwed up the menu table, e.g. a bad upgrade script. In
|
||
// this case we can try to find the root node by title.
|
||
if (is_null($rootItemId))
|
||
{
|
||
$rootItemId = null;
|
||
$query = $db->getQuery(true)
|
||
->select($db->qn('id'))
|
||
->from($db->qn('#__menu'))
|
||
->where($db->qn('title') . ' = ' . $db->q('Menu_Item_Root'));
|
||
$rootItemId = $db->setQuery($query, 0, 1)->loadResult();
|
||
}
|
||
|
||
// So, someone changed the title of the menu item too?! Let's find it by alias.
|
||
if (is_null($rootItemId))
|
||
{
|
||
$rootItemId = null;
|
||
$query = $db->getQuery(true)
|
||
->select($db->qn('id'))
|
||
->from($db->qn('#__menu'))
|
||
->where($db->qn('alias') . ' = ' . $db->q('root'));
|
||
$rootItemId = $db->setQuery($query, 0, 1)->loadResult();
|
||
}
|
||
|
||
// For crying out loud, they changed the alias too? Fine! Find it by component ID.
|
||
if (is_null($rootItemId))
|
||
{
|
||
$rootItemId = null;
|
||
$query = $db->getQuery(true)
|
||
->select($db->qn('id'))
|
||
->from($db->qn('#__menu'))
|
||
->where($db->qn('component_id') . ' = ' . $db->q('0'));
|
||
$rootItemId = $db->setQuery($query, 0, 1)->loadResult();
|
||
}
|
||
|
||
// Um, OK. Still no go. Let's try with minimum lft value.
|
||
if (is_null($rootItemId))
|
||
{
|
||
$rootItemId = null;
|
||
$query = $db->getQuery(true)
|
||
->select($db->qn('id'))
|
||
->from($db->qn('#__menu'))
|
||
->order($db->qn('lft') . ' ASC');
|
||
$rootItemId = $db->setQuery($query, 0, 1)->loadResult();
|
||
}
|
||
|
||
// I quit. Your site's menu structure is broken. I'll just throw an error.
|
||
if (is_null($rootItemId))
|
||
{
|
||
throw new Exception("Your site is broken. There is no root menu item. As a result it is impossible to create menu items. The installation of this component has failed. Please fix your database and retry!", 500);
|
||
}
|
||
|
||
/** @var \SimpleXMLElement $menuElement */
|
||
if ($menuElement)
|
||
{
|
||
$data = [];
|
||
$data['menutype'] = 'main';
|
||
$data['client_id'] = 1;
|
||
$data['title'] = (string) trim($menuElement);
|
||
$data['alias'] = (string) $menuElement;
|
||
$data['link'] = 'index.php?option=' . $option;
|
||
$data['type'] = 'component';
|
||
$data['published'] = 0;
|
||
$data['parent_id'] = 1;
|
||
$data['component_id'] = $componentId;
|
||
$data['img'] = ((string) $menuElement->attributes()->img !== '') ? (string) $menuElement->attributes()->img : 'class:component';
|
||
$data['home'] = 0;
|
||
$data['path'] = '';
|
||
$data['params'] = '';
|
||
}
|
||
// No menu element was specified, Let's make a generic menu item
|
||
else
|
||
{
|
||
$data = [];
|
||
$data['menutype'] = 'main';
|
||
$data['client_id'] = 1;
|
||
$data['title'] = $option;
|
||
$data['alias'] = $option;
|
||
$data['link'] = 'index.php?option=' . $option;
|
||
$data['type'] = 'component';
|
||
$data['published'] = 0;
|
||
$data['parent_id'] = 1;
|
||
$data['component_id'] = $componentId;
|
||
$data['img'] = 'class:component';
|
||
$data['home'] = 0;
|
||
$data['path'] = '';
|
||
$data['params'] = '';
|
||
}
|
||
|
||
try
|
||
{
|
||
$table->setLocation($rootItemId, 'last-child');
|
||
}
|
||
catch (\InvalidArgumentException $e)
|
||
{
|
||
$this->log($e->getMessage());
|
||
|
||
return false;
|
||
}
|
||
|
||
if (!$table->bind($data) || !$table->check() || !$table->store())
|
||
{
|
||
// The menu item already exists. Delete it and retry instead of throwing an error.
|
||
$query->clear()
|
||
->select('id')
|
||
->from('#__menu')
|
||
->where('menutype = ' . $db->quote('main'))
|
||
->where('client_id = 1')
|
||
->where('link = ' . $db->quote('index.php?option=' . $option))
|
||
->where('type = ' . $db->quote('component'))
|
||
->where('parent_id = 1')
|
||
->where('home = 0');
|
||
|
||
$db->setQuery($query);
|
||
$menu_ids_level1 = $db->loadColumn();
|
||
|
||
if (empty($menu_ids_level1))
|
||
{
|
||
JoomlaFactory::getApplication()->enqueueMessage($table->getError(), 'warning');
|
||
|
||
return false;
|
||
}
|
||
else
|
||
{
|
||
$ids = implode(',', $menu_ids_level1);
|
||
|
||
$query->clear()
|
||
->select('id')
|
||
->from('#__menu')
|
||
->where('menutype = ' . $db->quote('main'))
|
||
->where('client_id = 1')
|
||
->where('type = ' . $db->quote('component'))
|
||
->where('parent_id in (' . $ids . ')')
|
||
->where('level = 2')
|
||
->where('home = 0');
|
||
|
||
$db->setQuery($query);
|
||
$menu_ids_level2 = $db->loadColumn();
|
||
|
||
$ids = implode(',', array_merge($menu_ids_level1, $menu_ids_level2));
|
||
|
||
// Remove the old menu item
|
||
$query->clear()
|
||
->delete('#__menu')
|
||
->where('id in (' . $ids . ')');
|
||
|
||
$db->setQuery($query);
|
||
$db->execute();
|
||
|
||
// Retry creating the menu item
|
||
$table->setLocation($rootItemId, 'last-child');
|
||
|
||
if (!$table->bind($data) || !$table->check() || !$table->store())
|
||
{
|
||
// Install failed, warn user and rollback changes
|
||
JoomlaFactory::getApplication()->enqueueMessage($table->getError(), 'warning');
|
||
|
||
return false;
|
||
}
|
||
}
|
||
}
|
||
|
||
/*
|
||
* Since we have created a menu item, we add it to the installation step stack
|
||
* so that if we have to rollback the changes we can undo it.
|
||
*/
|
||
$parent->getParent()->pushStep(['type' => 'menu', 'id' => $componentId]);
|
||
|
||
/*
|
||
* Process SubMenus
|
||
*/
|
||
|
||
if (method_exists($parent, 'getManifest'))
|
||
{
|
||
$submenu = $parent->getManifest()->administration->submenu;
|
||
}
|
||
else
|
||
{
|
||
$submenu = $parent->get('manifest')->administration->submenu;
|
||
}
|
||
|
||
if (!$submenu)
|
||
{
|
||
return true;
|
||
}
|
||
|
||
$parent_id = $table->id;
|
||
|
||
/** @var \SimpleXMLElement $child */
|
||
foreach ($submenu->menu as $child)
|
||
{
|
||
$data = [];
|
||
$data['menutype'] = 'main';
|
||
$data['client_id'] = 1;
|
||
$data['title'] = (string) trim($child);
|
||
$data['alias'] = (string) $child;
|
||
$data['type'] = 'component';
|
||
$data['published'] = 0;
|
||
$data['parent_id'] = $parent_id;
|
||
$data['component_id'] = $componentId;
|
||
$data['img'] = ((string) $child->attributes()->img !== '') ? (string) $child->attributes()->img : 'class:component';
|
||
$data['home'] = 0;
|
||
|
||
// Set the sub menu link
|
||
if ((string) $child->attributes()->link !== '')
|
||
{
|
||
$data['link'] = 'index.php?' . $child->attributes()->link;
|
||
}
|
||
else
|
||
{
|
||
$request = [];
|
||
|
||
if ((string) $child->attributes()->act !== '')
|
||
{
|
||
$request[] = 'act=' . $child->attributes()->act;
|
||
}
|
||
|
||
if ((string) $child->attributes()->task !== '')
|
||
{
|
||
$request[] = 'task=' . $child->attributes()->task;
|
||
}
|
||
|
||
if ((string) $child->attributes()->controller !== '')
|
||
{
|
||
$request[] = 'controller=' . $child->attributes()->controller;
|
||
}
|
||
|
||
if ((string) $child->attributes()->view !== '')
|
||
{
|
||
$request[] = 'view=' . $child->attributes()->view;
|
||
}
|
||
|
||
if ((string) $child->attributes()->layout !== '')
|
||
{
|
||
$request[] = 'layout=' . $child->attributes()->layout;
|
||
}
|
||
|
||
if ((string) $child->attributes()->sub !== '')
|
||
{
|
||
$request[] = 'sub=' . $child->attributes()->sub;
|
||
}
|
||
|
||
$qstring = ((is_array($request) || $request instanceof \Countable ? count($request) : 0) > 0) ? '&' . implode('&', $request) : '';
|
||
$data['link'] = 'index.php?option=' . $option . $qstring;
|
||
}
|
||
|
||
$table = new Menu(JoomlaFactory::getDbo());
|
||
|
||
try
|
||
{
|
||
$table->setLocation($parent_id, 'last-child');
|
||
}
|
||
catch (\InvalidArgumentException $e)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
if (!$table->bind($data) || !$table->check() || !$table->store())
|
||
{
|
||
// Install failed, rollback changes
|
||
return false;
|
||
}
|
||
|
||
/*
|
||
* Since we have created a menu item, we add it to the installation step stack
|
||
* so that if we have to rollback the changes we can undo it.
|
||
*/
|
||
$parent->getParent()->pushStep(['type' => 'menu', 'id' => $componentId]);
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* Make sure the Component menu items are really published!
|
||
*
|
||
* @param ComponentAdapter $parent
|
||
*/
|
||
private function _reallyPublishAdminMenuItems(ComponentAdapter $parent): void
|
||
{
|
||
$db = $parent->getParent()->getDbo();
|
||
$option = $parent->get('element');
|
||
|
||
$query = $db->getQuery(true)
|
||
->update('#__menu AS m')
|
||
->join('LEFT', '#__extensions AS e ON m.component_id = e.extension_id')
|
||
->set($db->qn('published') . ' = ' . $db->q(1))
|
||
->where('m.parent_id = 1')
|
||
->where('m.client_id = 1')
|
||
->where('e.type = ' . $db->quote('component'))
|
||
->where('e.element = ' . $db->quote($option));
|
||
|
||
try
|
||
{
|
||
$db->setQuery($query)->execute();
|
||
}
|
||
catch (Exception $e)
|
||
{
|
||
// If it fails, it fails. Who cares.
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Tells Joomla! to rebuild its menu structure to make triple-sure that the Components menu items really do exist
|
||
* in the correct place and can really be rendered.
|
||
*/
|
||
private function _rebuildMenu(): void
|
||
{
|
||
$table = new Menu(JoomlaFactory::getDbo());
|
||
$db = $table->getDbo();
|
||
|
||
// We need to rebuild the menu based on its root item. By default this is the menu item with ID=1. However, some
|
||
// crappy upgrade scripts enjoy screwing it up. Hey, ho, the workaround way I go.
|
||
$query = $db->getQuery(true)
|
||
->select($db->qn('id'))
|
||
->from($db->qn('#__menu'))
|
||
->where($db->qn('id') . ' = ' . $db->q(1));
|
||
$rootItemId = $db->setQuery($query)->loadResult();
|
||
|
||
if (is_null($rootItemId))
|
||
{
|
||
// Guess what? The Problem has happened. Let's find the root node by title.
|
||
$rootItemId = null;
|
||
$query = $db->getQuery(true)
|
||
->select($db->qn('id'))
|
||
->from($db->qn('#__menu'))
|
||
->where($db->qn('title') . ' = ' . $db->q('Menu_Item_Root'));
|
||
$rootItemId = $db->setQuery($query, 0, 1)->loadResult();
|
||
}
|
||
|
||
if (is_null($rootItemId))
|
||
{
|
||
// Did they change the title too?! Let's find it by alias.
|
||
$rootItemId = null;
|
||
$query = $db->getQuery(true)
|
||
->select($db->qn('id'))
|
||
->from($db->qn('#__menu'))
|
||
->where($db->qn('alias') . ' = ' . $db->q('root'));
|
||
$rootItemId = $db->setQuery($query, 0, 1)->loadResult();
|
||
}
|
||
|
||
if (is_null($rootItemId))
|
||
{
|
||
// The alias is borked, too?! Find it by component ID.
|
||
$rootItemId = null;
|
||
$query = $db->getQuery(true)
|
||
->select($db->qn('id'))
|
||
->from($db->qn('#__menu'))
|
||
->where($db->qn('component_id') . ' = ' . $db->q('0'));
|
||
$rootItemId = $db->setQuery($query, 0, 1)->loadResult();
|
||
}
|
||
|
||
if (is_null($rootItemId))
|
||
{
|
||
// Your site is more of a "shite" than a "site". Let's try with minimum lft value.
|
||
$rootItemId = null;
|
||
$query = $db->getQuery(true)
|
||
->select($db->qn('id'))
|
||
->from($db->qn('#__menu'))
|
||
->order($db->qn('lft') . ' ASC');
|
||
$rootItemId = $db->setQuery($query, 0, 1)->loadResult();
|
||
}
|
||
|
||
if (is_null($rootItemId))
|
||
{
|
||
// I quit. Your site is broken.
|
||
return;
|
||
}
|
||
|
||
$table->rebuild($rootItemId);
|
||
}
|
||
|
||
/**
|
||
* Deletes the assets table records for the component
|
||
*
|
||
* @param JDatabaseDriver|DatabaseDriver $db
|
||
*
|
||
* @return void
|
||
*
|
||
* @since 3.0.18
|
||
*/
|
||
private function deleteComponentAssetRecords($db): void
|
||
{
|
||
$query = $db->getQuery(true);
|
||
$query->select('id')
|
||
->from('#__assets')
|
||
->where($db->qn('name') . ' = ' . $db->q($this->componentName));
|
||
$db->setQuery($query);
|
||
|
||
$ids = $db->loadColumn();
|
||
|
||
if (empty($ids))
|
||
{
|
||
return;
|
||
}
|
||
|
||
foreach ($ids as $id)
|
||
{
|
||
$query = $db->getQuery(true);
|
||
$query->delete('#__assets')
|
||
->where($db->qn('id') . ' = ' . $db->q($id));
|
||
$db->setQuery($query);
|
||
|
||
try
|
||
{
|
||
$db->execute();
|
||
}
|
||
catch (Exception $exc)
|
||
{
|
||
// Nothing
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Deletes the extensions table records for the component
|
||
*
|
||
* @param JDatabaseDriver|DatabaseDriver $db
|
||
*
|
||
* @return void
|
||
*
|
||
* @since 3.0.18
|
||
*/
|
||
private function deleteComponentExtensionRecord($db): void
|
||
{
|
||
$query = $db->getQuery(true);
|
||
$query->select('extension_id')
|
||
->from('#__extensions')
|
||
->where($db->qn('type') . ' = ' . $db->q('component'))
|
||
->where($db->qn('element') . ' = ' . $db->q($this->componentName));
|
||
$db->setQuery($query);
|
||
$ids = $db->loadColumn();
|
||
|
||
if (empty($ids))
|
||
{
|
||
return;
|
||
}
|
||
|
||
foreach ($ids as $id)
|
||
{
|
||
$query = $db->getQuery(true);
|
||
$query->delete('#__extensions')
|
||
->where($db->qn('extension_id') . ' = ' . $db->q($id));
|
||
$db->setQuery($query);
|
||
|
||
try
|
||
{
|
||
$db->execute();
|
||
}
|
||
catch (Exception $exc)
|
||
{
|
||
// Nothing
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Deletes the menu table records for the component
|
||
*
|
||
* @param JDatabaseDriver|DatabaseDriver $db
|
||
*
|
||
* @return void
|
||
*
|
||
* @since 3.0.18
|
||
*/
|
||
private function deleteComponentMenuRecord($db): void
|
||
{
|
||
$query = $db->getQuery(true);
|
||
$query->select('id')
|
||
->from('#__menu')
|
||
->where($db->qn('type') . ' = ' . $db->q('component'))
|
||
->where($db->qn('menutype') . ' = ' . $db->q('main'))
|
||
->where($db->qn('link') . ' LIKE ' . $db->q('index.php?option=' . $this->componentName));
|
||
$db->setQuery($query);
|
||
$ids = $db->loadColumn();
|
||
|
||
if (empty($ids))
|
||
{
|
||
return;
|
||
}
|
||
|
||
foreach ($ids as $id)
|
||
{
|
||
$query = $db->getQuery(true);
|
||
$query->delete('#__menu')
|
||
->where($db->qn('id') . ' = ' . $db->q($id));
|
||
$db->setQuery($query);
|
||
|
||
try
|
||
{
|
||
$db->execute();
|
||
}
|
||
catch (Exception $exc)
|
||
{
|
||
// Nothing
|
||
}
|
||
}
|
||
}
|
||
}
|