1304 lines
		
	
	
		
			34 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			1304 lines
		
	
	
		
			34 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | ||
| /**
 | ||
|  * @package   FOF
 | ||
|  * @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
 | ||
|  * @license   GNU General Public License version 2, or later
 | ||
|  */
 | ||
| 
 | ||
| namespace FOF30\Utils\InstallScript;
 | ||
| 
 | ||
| defined('_JEXEC') || die;
 | ||
| 
 | ||
| use Exception;
 | ||
| use FOF30\Database\Installer as DatabaseInstaller;
 | ||
| use InvalidArgumentException;
 | ||
| use JDatabaseDriver;
 | ||
| use JError;
 | ||
| use JLoader;
 | ||
| use Joomla\CMS\Factory;
 | ||
| use Joomla\CMS\Filesystem\File;
 | ||
| use Joomla\CMS\Filesystem\Folder;
 | ||
| use Joomla\CMS\Installer\Adapter\ComponentAdapter;
 | ||
| use Joomla\CMS\Installer\Installer;
 | ||
| use Joomla\CMS\Log\Log;
 | ||
| use Joomla\CMS\Table\Menu;
 | ||
| use Joomla\CMS\Table\Table;
 | ||
| use SimpleXMLElement;
 | ||
| use stdClass;
 | ||
| 
 | ||
| // In case FOF's autoloader is not present yet, e.g. new installation
 | ||
| if (!class_exists('FOF30\\Utils\\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 FOF30\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
 | ||
| 	 */
 | ||
| 	protected $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;
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * 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
 | ||
| 	 */
 | ||
| 	public function preflight($type, $parent)
 | ||
| 	{
 | ||
| 		// 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 JInstaller issues.
 | ||
| 		if (in_array($type, ['install', 'discover_install']))
 | ||
| 		{
 | ||
| 			// Bug fix for "Database function returned no error"
 | ||
| 			$this->bugfixDBFunctionReturnedNoError();
 | ||
| 		}
 | ||
| 		else
 | ||
| 		{
 | ||
| 			// Bug fix 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($type, $parent)
 | ||
| 	{
 | ||
| 		// Add ourselves to the list of extensions depending on FOF30
 | ||
| 		$this->addDependency('fof30', $this->componentName);
 | ||
| 
 | ||
| 		// Install or update database
 | ||
| 		$dbInstaller = new DatabaseInstaller(Factory::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 sub-extensions
 | ||
| 		$this->uninstallObsoleteSubextensions($parent);
 | ||
| 
 | ||
| 		// Clear the FOF cache
 | ||
| 		$false = false;
 | ||
| 		$cache = Factory::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();
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * Runs on uninstallation
 | ||
| 	 *
 | ||
| 	 * @param   ComponentAdapter  $parent  The parent object
 | ||
| 	 */
 | ||
| 	public function uninstall($parent)
 | ||
| 	{
 | ||
| 		// Uninstall database
 | ||
| 		$dbInstaller = new DatabaseInstaller(Factory::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 on FOF30
 | ||
| 		$this->removeDependency('fof30', $this->componentName);
 | ||
| 
 | ||
| 		// Show the post-uninstallation page
 | ||
| 		$this->renderPostUninstallation($parent);
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * Copies the CLI scripts into Joomla!'s cli directory
 | ||
| 	 *
 | ||
| 	 * @param   ComponentAdapter  $parent
 | ||
| 	 */
 | ||
| 	protected function copyCliFiles($parent)
 | ||
| 	{
 | ||
| 		$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($parent)
 | ||
| 	{
 | ||
| 		Log::add("Joomla! extension update workaround for component $this->componentName", Log::INFO, 'fof3_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, 'fof3_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
 | ||
| 	 */
 | ||
| 	protected function renderPostInstallation($parent)
 | ||
| 	{
 | ||
| 		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
 | ||
| 	 */
 | ||
| 	protected function renderPostUninstallation($parent)
 | ||
| 	{
 | ||
| 		echo "<h3>$this->componentName has been uninstalled</h3>";
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * Bug fix for "DB function returned no error"
 | ||
| 	 */
 | ||
| 	protected function bugfixDBFunctionReturnedNoError()
 | ||
| 	{
 | ||
| 		$db = Factory::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.
 | ||
| 			 */
 | ||
| 			if (version_compare(JVERSION, '3.6.9999', 'le'))
 | ||
| 			{
 | ||
| 				$this->deleteComponentMenuRecord($db);
 | ||
| 			}
 | ||
| 		}
 | ||
| 		catch (Exception $exc)
 | ||
| 		{
 | ||
| 			return;
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * Joomla! 1.6+ bug fix for "Can not build admin menus"
 | ||
| 	 */
 | ||
| 	protected function bugfixCantBuildAdminMenus()
 | ||
| 	{
 | ||
| 		$db = Factory::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($removeList)
 | ||
| 	{
 | ||
| 		// 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']) && !empty($removeList['folders']))
 | ||
| 		{
 | ||
| 			foreach ($removeList['folders'] as $folder)
 | ||
| 			{
 | ||
| 				$f = JPATH_ROOT . '/' . $folder;
 | ||
| 
 | ||
| 				if (!is_dir($f))
 | ||
| 				{
 | ||
| 					continue;
 | ||
| 				}
 | ||
| 
 | ||
| 				Folder::delete($f);
 | ||
| 			}
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * Uninstalls obsolete sub-extensions (modules, plugins) bundled with the main extension
 | ||
| 	 *
 | ||
| 	 * @param   ComponentAdapter  $parent  The parent object
 | ||
| 	 *
 | ||
| 	 * @return  stdClass The sub-extension uninstallation status
 | ||
| 	 */
 | ||
| 	protected function uninstallObsoleteSubextensions($parent)
 | ||
| 	{
 | ||
| 		$db = Factory::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)
 | ||
| 				{
 | ||
| 					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 Installer;
 | ||
| 							$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)
 | ||
| 				{
 | ||
| 					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 Installer;
 | ||
| 							$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($parent)
 | ||
| 	{
 | ||
| 		$db = $parent->getParent()->getDbo();
 | ||
| 		/** @var Menu $table */
 | ||
| 		$table  = Table::getInstance('menu');
 | ||
| 		$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 ?: '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))
 | ||
| 			{
 | ||
| 				// Oops! Could not get the menu ID. Go back and rollback changes.
 | ||
| 				JError::raiseWarning(1, $table->getError());
 | ||
| 
 | ||
| 				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
 | ||
| 					JError::raiseWarning(1, $table->getError());
 | ||
| 
 | ||
| 					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 ?: '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      = (count($request)) ? '&' . implode('&', $request) : '';
 | ||
| 				$data['link'] = 'index.php?option=' . $option . $qstring;
 | ||
| 			}
 | ||
| 
 | ||
| 			$table = Table::getInstance('menu');
 | ||
| 
 | ||
| 			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
 | ||
| 	 *
 | ||
| 	 * @return bool
 | ||
| 	 */
 | ||
| 	private function _reallyPublishAdminMenuItems($parent)
 | ||
| 	{
 | ||
| 		$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));
 | ||
| 
 | ||
| 		$db->setQuery($query);
 | ||
| 
 | ||
| 		try
 | ||
| 		{
 | ||
| 			$db->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()
 | ||
| 	{
 | ||
| 		/** @var Menu $table */
 | ||
| 		$table = Table::getInstance('menu');
 | ||
| 		$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))
 | ||
| 		{
 | ||
| 			// Woah! 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 false;
 | ||
| 		}
 | ||
| 
 | ||
| 		$table->rebuild($rootItemId);
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * Deletes the assets table records for the component
 | ||
| 	 *
 | ||
| 	 * @param   JDatabaseDriver  $db
 | ||
| 	 *
 | ||
| 	 * @return  void
 | ||
| 	 *
 | ||
| 	 * @since   3.0.18
 | ||
| 	 */
 | ||
| 	private function deleteComponentAssetRecords($db)
 | ||
| 	{
 | ||
| 		$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  $db
 | ||
| 	 *
 | ||
| 	 * @return  void
 | ||
| 	 *
 | ||
| 	 * @since   3.0.18
 | ||
| 	 */
 | ||
| 	private function deleteComponentExtensionRecord($db)
 | ||
| 	{
 | ||
| 		$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  $db
 | ||
| 	 *
 | ||
| 	 * @return  void
 | ||
| 	 *
 | ||
| 	 * @since   3.0.18
 | ||
| 	 */
 | ||
| 	private function deleteComponentMenuRecord($db)
 | ||
| 	{
 | ||
| 		$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
 | ||
| 			}
 | ||
| 		}
 | ||
| 	}
 | ||
| }
 |