Spade

Mini Shell

Directory:~$ /home/lmsyaran/public_html/joomla4/
Upload File

[Home] [System Details] [Kill Me]
Current File:~$ /home/lmsyaran/public_html/joomla4/Installer.zip

PKK�[uIg5`�`�Adapter/ComponentAdapter.phpnu�[���<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
 * @license    GNU General Public License version 2 or later; see
LICENSE.txt
 */

namespace Joomla\CMS\Installer\Adapter;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Installer\Installer;
use Joomla\CMS\Installer\InstallerAdapter;
use Joomla\CMS\Table\Asset;
use Joomla\CMS\Table\Extension;
use Joomla\CMS\Table\Table;
use Joomla\CMS\Table\Update;

jimport('joomla.filesystem.folder');

/**
 * Component installer
 *
 * @since  3.1
 */
class ComponentAdapter extends InstallerAdapter
{
	/**
	 * The list of current files fo the Joomla! CMS administrator that are
installed and is read
	 * from the manifest on disk in the update area to handle doing a diff
	 * and deleting files that are in the old files list and not in the new
	 * files list.
	 *
	 * @var    array
	 * @since  3.1
	 * */
	protected $oldAdminFiles = null;

	/**
	 * The list of current files that are installed and is read
	 * from the manifest on disk in the update area to handle doing a diff
	 * and deleting files that are in the old files list and not in the new
	 * files list.
	 *
	 * @var    array
	 * @since  3.1
	 * */
	protected $oldFiles = null;

	/**
	 * A path to the PHP file that the scriptfile declaration in
	 * the manifest refers to.
	 *
	 * @var    string
	 * @since  3.1
	 * */
	protected $manifest_script = null;

	/**
	 * For legacy installations this is a path to the PHP file that the
scriptfile declaration in the
	 * manifest refers to.
	 *
	 * @var    string
	 * @since  3.1
	 * */
	protected $install_script = null;

	/**
	 * Method to check if the extension is present in the filesystem
	 *
	 * @return  boolean
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function checkExtensionInFilesystem()
	{
		/*
		 * If the component site or admin directory already exists, then we will
assume that the component is already
		 * installed or another component is using that directory.
		 */
		if (file_exists($this->parent->getPath('extension_site'))
||
file_exists($this->parent->getPath('extension_administrator')))
		{
			// Look for an update function or update tag
			$updateElement = $this->getManifest()->update;

			// Upgrade manually set or update function available or update tag
detected
			if ($updateElement || $this->parent->isUpgrade()
				|| ($this->parent->manifestClass &&
method_exists($this->parent->manifestClass, 'update')))
			{
				// If there is a matching extension mark this as an update
				$this->setRoute('update');
			}
			elseif (!$this->parent->isOverwrite())
			{
				// We didn't have overwrite set, find an update function or find
an update tag so lets call it safe
				if
(file_exists($this->parent->getPath('extension_site')))
				{
					// If the site exists say so.
					throw new \RuntimeException(
						\JText::sprintf(
							'JLIB_INSTALLER_ERROR_COMP_INSTALL_DIR_SITE',
							$this->parent->getPath('extension_site')
						)
					);
				}

				// If the admin exists say so
				throw new \RuntimeException(
					\JText::sprintf(
						'JLIB_INSTALLER_ERROR_COMP_INSTALL_DIR_ADMIN',
						$this->parent->getPath('extension_administrator')
					)
				);
			}
		}

		return false;
	}

	/**
	 * Method to copy the extension's base files from the `<files>`
tag(s) and the manifest file
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function copyBaseFiles()
	{
		// Copy site files
		if ($this->getManifest()->files)
		{
			if ($this->route === 'update')
			{
				$result =
$this->parent->parseFiles($this->getManifest()->files, 0,
$this->oldFiles);
			}
			else
			{
				$result =
$this->parent->parseFiles($this->getManifest()->files);
			}

			if ($result === false)
			{
				throw new \RuntimeException(
					\JText::sprintf(
						'JLIB_INSTALLER_ABORT_COMP_FAIL_SITE_FILES',
						\JText::_('JLIB_INSTALLER_' . strtoupper($this->route))
					)
				);
			}
		}

		// Copy admin files
		if ($this->getManifest()->administration->files)
		{
			if ($this->route === 'update')
			{
				$result =
$this->parent->parseFiles($this->getManifest()->administration->files,
1, $this->oldAdminFiles);
			}
			else
			{
				$result =
$this->parent->parseFiles($this->getManifest()->administration->files,
1);
			}

			if ($result === false)
			{
				throw new \RuntimeException(
					\JText::sprintf(
						'JLIB_INSTALLER_ABORT_COMP_FAIL_ADMIN_FILES',
						\JText::_('JLIB_INSTALLER_' . strtoupper($this->route))
					)
				);
			}
		}

		// If there is a manifest script, let's copy it.
		if ($this->manifest_script)
		{
			$path['src']  =
$this->parent->getPath('source') . '/' .
$this->manifest_script;
			$path['dest'] =
$this->parent->getPath('extension_administrator') .
'/' . $this->manifest_script;

			if ($this->parent->isOverwrite() ||
!file_exists($path['dest']))
			{
				if (!$this->parent->copyFiles(array($path)))
				{
					throw new \RuntimeException(
						\JText::sprintf(
							'JLIB_INSTALLER_ABORT_COMP_COPY_MANIFEST',
							\JText::_('JLIB_INSTALLER_' . strtoupper($this->route))
						)
					);
				}
			}
		}
	}

	/**
	 * Method to create the extension root path if necessary
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function createExtensionRoot()
	{
		// If the component directory does not exist, let's create it
		$created = false;

		if
(!file_exists($this->parent->getPath('extension_site')))
		{
			if (!$created =
\JFolder::create($this->parent->getPath('extension_site')))
			{
				throw new \RuntimeException(
					\JText::sprintf(
						'JLIB_INSTALLER_ERROR_COMP_FAILED_TO_CREATE_DIRECTORY',
						\JText::_('JLIB_INSTALLER_' . strtoupper($this->route)),
						$this->parent->getPath('extension_site')
					)
				);
			}
		}

		/*
		 * Since we created the component directory and we will want to remove it
if we have to roll back
		 * the installation, let's add it to the installation step stack
		 */
		if ($created)
		{
			$this->parent->pushStep(
				array(
					'type' => 'folder',
					'path' =>
$this->parent->getPath('extension_site'),
				)
			);
		}

		// If the component admin directory does not exist, let's create it
		$created = false;

		if
(!file_exists($this->parent->getPath('extension_administrator')))
		{
			if (!$created =
\JFolder::create($this->parent->getPath('extension_administrator')))
			{
				throw new \RuntimeException(
					\JText::sprintf(
						'JLIB_INSTALLER_ERROR_COMP_FAILED_TO_CREATE_DIRECTORY',
						\JText::_('JLIB_INSTALLER_' . strtoupper($this->route)),
						$this->parent->getPath('extension_site')
					)
				);
			}
		}

		/*
		 * Since we created the component admin directory and we will want to
remove it if we have to roll
		 * back the installation, let's add it to the installation step
stack
		 */
		if ($created)
		{
			$this->parent->pushStep(
				array(
					'type' => 'folder',
					'path' =>
$this->parent->getPath('extension_administrator'),
				)
			);
		}
	}

	/**
	 * Method to finalise the installation processing
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function finaliseInstall()
	{
		/** @var Update $update */
		$update = Table::getInstance('update');

		// Clobber any possible pending updates
		$uid = $update->find(
			array(
				'element'   => $this->element,
				'type'      => $this->extension->type,
				'client_id' => 1,
			)
		);

		if ($uid)
		{
			$update->delete($uid);
		}

		// We will copy the manifest file to its appropriate place.
		if ($this->route !== 'discover_install')
		{
			if (!$this->parent->copyManifest())
			{
				// Install failed, roll back changes
				throw new \RuntimeException(
					\JText::sprintf(
						'JLIB_INSTALLER_ABORT_COMP_COPY_SETUP',
						\JText::_('JLIB_INSTALLER_' . strtoupper($this->route))
					)
				);
			}
		}

		// Time to build the admin menus
		if (!$this->_buildAdminMenus($this->extension->extension_id))
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ABORT_COMP_BUILDADMINMENUS_FAILED'),
\JLog::WARNING, 'jerror');
		}

		// Make sure that menu items pointing to the component have correct
component id assigned to them.
		// Prevents message "Component 'com_extension' does not
exist." after uninstalling / re-installing component.
		if (!$this->_updateMenus($this->extension->extension_id))
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ABORT_COMP_UPDATESITEMENUS_FAILED'),
\JLog::WARNING, 'jerror');
		}

		/** @var Asset $asset */
		$asset = Table::getInstance('Asset');

		// Check if an asset already exists for this extension and create it if
not
		if (!$asset->loadByName($this->extension->element))
		{
			// Register the component container just under root in the assets table.
			$asset->name      = $this->extension->element;
			$asset->parent_id = 1;
			$asset->rules     = '{}';
			$asset->title     = $this->extension->name;
			$asset->setLocation(1, 'last-child');

			if (!$asset->store())
			{
				// Install failed, roll back changes
				throw new \RuntimeException(
					\JText::sprintf(
						'JLIB_INSTALLER_ABORT_ROLLBACK',
						\JText::_('JLIB_INSTALLER_' . strtoupper($this->route)),
						$this->extension->getError()
					)
				);
			}
		}
	}

	/**
	 * Get the filtered extension element from the manifest
	 *
	 * @param   string  $element  Optional element name to be converted
	 *
	 * @return  string  The filtered element
	 *
	 * @since   3.4
	 */
	public function getElement($element = null)
	{
		$element = parent::getElement($element);

		if (strpos($element, 'com_') !== 0)
		{
			$element = 'com_' . $element;
		}

		return $element;
	}

	/**
	 * Custom loadLanguage method
	 *
	 * @param   string  $path  The path language files are on.
	 *
	 * @return  void
	 *
	 * @since   3.1
	 */
	public function loadLanguage($path = null)
	{
		$source = $this->parent->getPath('source');
		$client = $this->parent->extension->client_id ?
JPATH_ADMINISTRATOR : JPATH_SITE;

		if (!$source)
		{
			$this->parent->setPath('source', $client .
'/components/' . $this->parent->extension->element);
		}

		$extension = $this->getElement();
		$source    = $path ?: $client . '/components/' . $extension;

		if ($this->getManifest()->administration->files)
		{
			$element = $this->getManifest()->administration->files;
		}
		elseif ($this->getManifest()->files)
		{
			$element = $this->getManifest()->files;
		}
		else
		{
			$element = null;
		}

		if ($element)
		{
			$folder = (string) $element->attributes()->folder;

			if ($folder && file_exists($path . '/' . $folder))
			{
				$source = $path . '/' . $folder;
			}
		}

		$this->doLoadLanguage($extension, $source);
	}

	/**
	 * Method to parse optional tags in the manifest
	 *
	 * @return  void
	 *
	 * @since   3.4
	 */
	protected function parseOptionalTags()
	{
		// Parse optional tags
		$this->parent->parseMedia($this->getManifest()->media);
		$this->parent->parseLanguages($this->getManifest()->languages);
		$this->parent->parseLanguages($this->getManifest()->administration->languages,
1);
	}

	/**
	 * Prepares the adapter for a discover_install task
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	public function prepareDiscoverInstall()
	{
		// Need to find to find where the XML file is since we don't store
this normally
		$client =
ApplicationHelper::getClientInfo($this->extension->client_id);
		$short_element = str_replace('com_', '',
$this->extension->element);
		$manifestPath = $client->path . '/components/' .
$this->extension->element . '/' . $short_element .
'.xml';
		$this->parent->manifest =
$this->parent->isManifest($manifestPath);
		$this->parent->setPath('manifest', $manifestPath);
		$this->parent->setPath('source', $client->path .
'/components/' . $this->extension->element);
		$this->parent->setPath('extension_root',
$this->parent->getPath('source'));
		$this->setManifest($this->parent->getManifest());

		$manifest_details =
Installer::parseXMLInstallFile($this->parent->getPath('manifest'));
		$this->extension->manifest_cache = json_encode($manifest_details);
		$this->extension->state = 0;
		$this->extension->name = $manifest_details['name'];
		$this->extension->enabled = 1;
		$this->extension->params = $this->parent->getParams();

		$stored = false;

		try
		{
			$this->extension->store();
			$stored = true;
		}
		catch (\RuntimeException $e)
		{
			// Try to delete existing failed records before retrying
			$db = $this->db;

			$query = $db->getQuery(true)
				->select($db->qn('extension_id'))
				->from($db->qn('#__extensions'))
				->where($db->qn('name') . ' = ' .
$db->q($this->extension->name))
				->where($db->qn('type') . ' = ' .
$db->q($this->extension->type))
				->where($db->qn('element') . ' = ' .
$db->q($this->extension->element));

			$db->setQuery($query);

			$extension_ids = $db->loadColumn();

			if (!empty($extension_ids))
			{
				foreach ($extension_ids as $eid)
				{
					// Remove leftover admin menus for this extension ID
					$this->_removeAdminMenus($eid);

					// Remove the extension record itself
					/** @var Extension $extensionTable */
					$extensionTable = Table::getInstance('extension');
					$extensionTable->delete($eid);
				}
			}
		}

		if (!$stored)
		{
			try
			{
				$this->extension->store();
			}
			catch (\RuntimeException $e)
			{
				throw new
\RuntimeException(\JText::_('JLIB_INSTALLER_ERROR_COMP_DISCOVER_STORE_DETAILS'),
$e->getCode(), $e);
			}
		}
	}

	/**
	 * Method to do any prechecks and setup the install paths for the
extension
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function setupInstallPaths()
	{
		// Set the installation target paths
		$this->parent->setPath('extension_site',
\JPath::clean(JPATH_SITE . '/components/' . $this->element));
		$this->parent->setPath('extension_administrator',
\JPath::clean(JPATH_ADMINISTRATOR . '/components/' .
$this->element));

		// Copy the admin path as it's used as a common base
		$this->parent->setPath('extension_root',
$this->parent->getPath('extension_administrator'));

		// Make sure that we have an admin element
		if (!$this->getManifest()->administration)
		{
			throw new
\RuntimeException(\JText::_('JLIB_INSTALLER_ERROR_COMP_INSTALL_ADMIN_ELEMENT'));
		}
	}

	/**
	 * Method to setup the update routine for the adapter
	 *
	 * @return  void
	 *
	 * @since   3.4
	 */
	protected function setupUpdates()
	{
		// Hunt for the original XML file
		$old_manifest = null;

		// Use a temporary instance due to side effects; start in the
administrator first
		$tmpInstaller = new Installer;
		$tmpInstaller->setPath('source',
$this->parent->getPath('extension_administrator'));

		if (!$tmpInstaller->findManifest())
		{
			// Then the site
			$tmpInstaller->setPath('source',
$this->parent->getPath('extension_site'));

			if ($tmpInstaller->findManifest())
			{
				$old_manifest = $tmpInstaller->getManifest();
			}
		}
		else
		{
			$old_manifest = $tmpInstaller->getManifest();
		}

		if ($old_manifest)
		{
			$this->oldAdminFiles = $old_manifest->administration->files;
			$this->oldFiles = $old_manifest->files;
		}
	}

	/**
	 * Method to store the extension to the database
	 *
	 * @param   bool  $deleteExisting  Should I try to delete existing records
of the same component?
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function storeExtension($deleteExisting = false)
	{
		// The extension is stored during prepareDiscoverInstall for discover
installs
		if ($this->route === 'discover_install')
		{
			return;
		}

		// Add or update an entry to the extension table
		$this->extension->name    = $this->name;
		$this->extension->type    = 'component';
		$this->extension->element = $this->element;

		// If we are told to delete existing extension entries then do so.
		if ($deleteExisting)
		{
			$db = $this->parent->getDbo();

			$query = $db->getQuery(true)
				->select($db->qn('extension_id'))
				->from($db->qn('#__extensions'))
				->where($db->qn('name') . ' = ' .
$db->q($this->extension->name))
				->where($db->qn('type') . ' = ' .
$db->q($this->extension->type))
				->where($db->qn('element') . ' = ' .
$db->q($this->extension->element));

			$db->setQuery($query);

			$extension_ids = $db->loadColumn();

			if (!empty($extension_ids))
			{
				foreach ($extension_ids as $eid)
				{
					// Remove leftover admin menus for this extension ID
					$this->_removeAdminMenus($eid);

					// Remove the extension record itself
					/** @var Extension $extensionTable */
					$extensionTable = Table::getInstance('extension');
					$extensionTable->delete($eid);
				}
			}
		}

		// If there is not already a row, generate a heap of defaults
		if (!$this->currentExtensionId)
		{
			$this->extension->folder    = '';
			$this->extension->enabled   = 1;
			$this->extension->protected = 0;
			$this->extension->access    = 0;
			$this->extension->client_id = 1;
			$this->extension->params    = $this->parent->getParams();
			$this->extension->custom_data = '';
			$this->extension->system_data = '';
		}

		$this->extension->manifest_cache =
$this->parent->generateManifestCache();

		$couldStore = $this->extension->store();

		if (!$couldStore && $deleteExisting)
		{
			// Install failed, roll back changes
			throw new \RuntimeException(
				\JText::sprintf(
					'JLIB_INSTALLER_ABORT_COMP_INSTALL_ROLLBACK',
					$this->extension->getError()
				)
			);
		}

		if (!$couldStore && !$deleteExisting)
		{
			// Maybe we have a failed installation (e.g. timeout). Let's retry
after deleting old records.
			$this->storeExtension(true);
		}
	}

	/**
	 * Custom uninstall method for components
	 *
	 * @param   integer  $id  The unique extension id of the component to
uninstall
	 *
	 * @return  boolean  True on success
	 *
	 * @since   3.1
	 */
	public function uninstall($id)
	{
		$db     = $this->db;
		$retval = true;

		// First order of business will be to load the component object table
from the database.
		// This should give us the necessary information to proceed.
		if (!$this->extension->load((int) $id))
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_COMP_UNINSTALL_ERRORUNKOWNEXTENSION'),
\JLog::WARNING, 'jerror');

			return false;
		}

		// Is the component we are trying to uninstall a core one?
		// Because that is not a good idea...
		if ($this->extension->protected)
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_COMP_UNINSTALL_WARNCORECOMPONENT'),
\JLog::WARNING, 'jerror');

			return false;
		}

		/*
		 * Does this extension have a parent package?
		 * If so, check if the package disallows individual extensions being
uninstalled if the package is not being uninstalled
		 */
		if ($this->extension->package_id &&
!$this->parent->isPackageUninstall() &&
!$this->canUninstallPackageChild($this->extension->package_id))
		{
			\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_CANNOT_UNINSTALL_CHILD_OF_PACKAGE',
$this->extension->name), \JLog::WARNING, 'jerror');

			return false;
		}

		// Get the admin and site paths for the component
		$this->parent->setPath('extension_administrator',
\JPath::clean(JPATH_ADMINISTRATOR . '/components/' .
$this->extension->element));
		$this->parent->setPath('extension_site',
\JPath::clean(JPATH_SITE . '/components/' .
$this->extension->element));

		// Copy the admin path as it's used as a common base
		$this->parent->setPath('extension_root',
$this->parent->getPath('extension_administrator'));

		/**
		 *
---------------------------------------------------------------------------------------------
		 * Manifest Document Setup Section
		 *
---------------------------------------------------------------------------------------------
		 */

		// Find and load the XML install file for the component
		$this->parent->setPath('source',
$this->parent->getPath('extension_administrator'));

		// Get the package manifest object
		// We do findManifest to avoid problem when uninstalling a list of
extension: getManifest cache its manifest file
		$this->parent->findManifest();
		$this->setManifest($this->parent->getManifest());

		if (!$this->getManifest())
		{
			// Make sure we delete the folders if no manifest exists
			\JFolder::delete($this->parent->getPath('extension_administrator'));
			\JFolder::delete($this->parent->getPath('extension_site'));

			// Remove the menu
			$this->_removeAdminMenus($this->extension->extension_id);

			// Raise a warning
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_COMP_UNINSTALL_ERRORREMOVEMANUALLY'),
\JLog::WARNING, 'jerror');

			// Return
			return false;
		}

		// Set the extensions name
		$this->set('name', $this->getName());
		$this->set('element', $this->getElement());

		// Attempt to load the admin language file; might have uninstall strings
		$this->loadLanguage(JPATH_ADMINISTRATOR . '/components/' .
$this->element);

		/**
		 *
---------------------------------------------------------------------------------------------
		 * Installer Trigger Loading and Uninstall
		 *
---------------------------------------------------------------------------------------------
		 */

		$this->setupScriptfile();

		try
		{
			$this->triggerManifestScript('uninstall');
		}
		catch (\RuntimeException $e)
		{
			// Ignore errors for now
		}

		/**
		 *
---------------------------------------------------------------------------------------------
		 * Database Processing Section
		 *
---------------------------------------------------------------------------------------------
		 */

		// Let's run the uninstall queries for the component
		try
		{
			$this->parseQueries();
		}
		catch (\RuntimeException $e)
		{
			\JLog::add($e->getMessage(), \JLog::WARNING, 'jerror');

			$retval = false;
		}

		$this->_removeAdminMenus($this->extension->extension_id);

		/**
		 *
---------------------------------------------------------------------------------------------
		 * Filesystem Processing Section
		 *
---------------------------------------------------------------------------------------------
		 */

		// Let's remove those language files and media in the JROOT/images/
folder that are
		// associated with the component we are uninstalling
		$this->parent->removeFiles($this->getManifest()->media);
		$this->parent->removeFiles($this->getManifest()->languages);
		$this->parent->removeFiles($this->getManifest()->administration->languages,
1);

		// Remove the schema version
		$query = $db->getQuery(true)
			->delete('#__schemas')
			->where('extension_id = ' . $id);
		$db->setQuery($query);
		$db->execute();

		// Remove the component container in the assets table.
		$asset = Table::getInstance('Asset');

		if ($asset->loadByName($this->element))
		{
			$asset->delete();
		}

		// Remove categories for this component
		$query->clear()
			->delete('#__categories')
			->where('extension=' . $db->quote($this->element),
'OR')
			->where('extension LIKE ' . $db->quote($this->element
. '.%'));
		$db->setQuery($query);
		$db->execute();

		// Rebuild the categories for correct lft/rgt
		$category = Table::getInstance('category');
		$category->rebuild();

		// Clobber any possible pending updates
		$update = Table::getInstance('update');
		$uid = $update->find(
			array(
				'element'   => $this->extension->element,
				'type'      => 'component',
				'client_id' => 1,
				'folder'    => '',
			)
		);

		if ($uid)
		{
			$update->delete($uid);
		}

		// Now we need to delete the installation directories. This is the final
step in uninstalling the component.
		if (trim($this->extension->element))
		{
			// Delete the component site directory
			if (is_dir($this->parent->getPath('extension_site')))
			{
				if
(!\JFolder::delete($this->parent->getPath('extension_site')))
				{
					\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_COMP_UNINSTALL_FAILED_REMOVE_DIRECTORY_SITE'),
\JLog::WARNING, 'jerror');
					$retval = false;
				}
			}

			// Delete the component admin directory
			if
(is_dir($this->parent->getPath('extension_administrator')))
			{
				if
(!\JFolder::delete($this->parent->getPath('extension_administrator')))
				{
					\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_COMP_UNINSTALL_FAILED_REMOVE_DIRECTORY_ADMIN'),
\JLog::WARNING, 'jerror');
					$retval = false;
				}
			}

			// Now we will no longer need the extension object, so let's delete
it
			$this->extension->delete($this->extension->extension_id);

			return $retval;
		}
		else
		{
			// No component option defined... cannot delete what we don't know
about
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_COMP_UNINSTALL_NO_OPTION'),
\JLog::WARNING, 'jerror');

			return false;
		}
	}

	/**
	 * Method to build menu database entries for a component
	 *
	 * @param   int|null  $componentId  The component ID for which I'm
building menus
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   3.1
	 */
	protected function _buildAdminMenus($componentId = null)
	{
		$db     = $this->parent->getDbo();

		$option = $this->get('element');

		// If a component exists with this option in the table within the
protected menutype 'main' then we don't need to add menus
		$query = $db->getQuery(true)
			->select('m.id, e.extension_id')
			->from('#__menu AS m')
			->join('LEFT', '#__extensions AS e ON m.component_id =
e.extension_id')
			->where('m.parent_id = 1')
			->where('m.client_id = 1')
			->where('m.menutype = ' . $db->quote('main'))
			->where('e.element = ' . $db->quote($option));

		$db->setQuery($query);

		// In case of a failed installation (e.g. timeout error) we may have
duplicate menu item and extension records.
		$componentrows = $db->loadObjectList();

		// Check if menu items exist
		if (!empty($componentrows))
		{
			// Don't do anything if overwrite has not been enabled
			if (!$this->parent->isOverwrite())
			{
				return true;
			}

			// Remove all menu items
			foreach ($componentrows as $componentrow)
			{
				// Remove existing menu items if overwrite has been enabled
				if ($option)
				{
					// If something goes wrong, there's no way to rollback TODO:
Search for better solution
					$this->_removeAdminMenus($componentrow->extension_id);
				}
			}
		}

		// Only try to detect the component ID if it's not provided
		if (empty($componentId))
		{
			// Lets find the extension id
			$query->clear()
				->select('e.extension_id')
				->from('#__extensions AS e')
				->where('e.type = ' .
$db->quote('component'))
				->where('e.element = ' . $db->quote($option));

			$db->setQuery($query);
			$componentId = $db->loadResult();
		}

		// Ok, now its time to handle the menus.  Start with the component root
menu, then handle submenus.
		$menuElement = $this->getManifest()->administration->menu;

		// Just do not create the menu if $menuElement not exist
		if (!$menuElement)
		{
			return true;
		}

		// If the menu item is hidden do nothing more, just return
		if (in_array((string) $menuElement['hidden'],
array('true', 'hidden')))
		{
			return true;
		}

		// Let's figure out what the menu item data should look like
		$data = array();

		if ($menuElement)
		{
			// I have a menu element, use this information
			$data['menutype'] = 'main';
			$data['client_id'] = 1;
			$data['title'] = (string) trim($menuElement);
			$data['alias'] = (string) $menuElement;
			$data['type'] = 'component';
			$data['published'] = 1;
			$data['parent_id'] = 1;
			$data['component_id'] = $componentId;
			$data['img'] = ((string)
$menuElement->attributes()->img) ?: 'class:component';
			$data['home'] = 0;
			$data['path'] = '';
			$data['params'] = '';

			// Set the menu link
			$request = array();

			if ((string) $menuElement->attributes()->task)
			{
				$request[] = 'task=' .
$menuElement->attributes()->task;
			}

			if ((string) $menuElement->attributes()->view)
			{
				$request[] = 'view=' .
$menuElement->attributes()->view;
			}

			$qstring = count($request) ? '&' .
implode('&', $request) : '';
			$data['link'] = 'index.php?option=' . $option .
$qstring;
		}
		else
		{
			// No menu element was specified, Let's make a generic menu item
			$data = array();
			$data['menutype'] = 'main';
			$data['client_id'] = 1;
			$data['title'] = $option;
			$data['alias'] = $option;
			$data['link'] = 'index.php?option=' . $option;
			$data['type'] = 'component';
			$data['published'] = 1;
			$data['parent_id'] = 1;
			$data['component_id'] = $componentId;
			$data['img'] = 'class:component';
			$data['home'] = 0;
			$data['path'] = '';
			$data['params'] = '';
		}

		// Try to create the menu item in the database
		$parent_id = $this->_createAdminMenuItem($data, 1);

		if ($parent_id === false)
		{
			return false;
		}

		/*
		 * Process SubMenus
		 */

		if (!$this->getManifest()->administration->submenu)
		{
			// No submenu? We're done.
			return true;
		}

		foreach ($this->getManifest()->administration->submenu->menu
as $child)
		{
			$data = array();
			$data['menutype'] = 'main';
			$data['client_id'] = 1;
			$data['title'] = (string) trim($child);
			$data['alias'] = (string) $child;
			$data['type'] = 'component';
			$data['published'] = 1;
			$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 = array();

				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;
			}

			$submenuId = $this->_createAdminMenuItem($data, $parent_id);

			if ($submenuId === false)
			{
				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.
			 */
			$this->parent->pushStep(array('type' =>
'menu', 'id' => $componentId));
		}

		return true;
	}

	/**
	 * Method to remove admin menu references to a component
	 *
	 * @param   int  $id  The ID of the extension whose admin menus will be
removed
	 *
	 * @return  boolean  True if successful.
	 *
	 * @since   3.1
	 */
	protected function _removeAdminMenus($id)
	{
		$db = $this->parent->getDbo();

		/** @var  \JTableMenu  $table */
		$table = Table::getInstance('menu');

		// Get the ids of the menu items
		$query = $db->getQuery(true)
			->select('id')
			->from('#__menu')
			->where($db->quoteName('client_id') . ' = 1')
			->where($db->quoteName('menutype') . ' = ' .
$db->q('main'))
			->where($db->quoteName('component_id') . ' = '
. (int) $id);

		$db->setQuery($query);

		$ids = $db->loadColumn();

		$result = true;

		// Check for error
		if (!empty($ids))
		{
			// Iterate the items to delete each one.
			foreach ($ids as $menuid)
			{
				if (!$table->delete((int) $menuid, false))
				{
					$this->setError($table->getError());

					$result = false;
				}
			}

			// Rebuild the whole tree
			$table->rebuild();
		}

		return $result;
	}

	/**
	 * Method to update menu database entries for a component in case the
component has been uninstalled before.
	 * NOTE: This will not update admin menus. Use _updateMenus() instead to
update admin menus ase well.
	 *
	 * @param   int|null  $componentId  The component ID.
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   3.4.2
	 */
	protected function _updateSiteMenus($componentId = null)
	{
		return $this->_updateMenus($componentId, 0);
	}

	/**
	 * Method to update menu database entries for a component in case if the
component has been uninstalled before.
	 *
	 * @param   int|null  $componentId  The component ID.
	 * @param   int       $clientId     The client id
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   3.7.0
	 */
	protected function _updateMenus($componentId, $clientId = null)
	{
		$db     = $this->parent->getDbo();
		$option = $this->get('element');

		// Update all menu items which contain
'index.php?option=com_extension' or
'index.php?option=com_extension&...'
		// to use the new component id.
		$query = $db->getQuery(true)
			->update('#__menu')
			->set('component_id = ' . $db->quote($componentId))
			->where('type = ' . $db->quote('component'))
			->where('('
				. 'link LIKE ' . $db->quote('index.php?option='
. $option) . ' OR '
				. 'link LIKE ' .
$db->q($db->escape('index.php?option=' . $option .
'&') . '%', false)
				. ')');

		if (isset($clientId))
		{
			$query->where('client_id = ' . (int) $clientId);
		}

		$db->setQuery($query);

		try
		{
			$db->execute();
		}
		catch (\RuntimeException $e)
		{
			return false;
		}

		return true;
	}

	/**
	 * Custom rollback method
	 * - Roll back the component menu item
	 *
	 * @param   array  $step  Installation step to rollback.
	 *
	 * @return  boolean  True on success
	 *
	 * @since   3.1
	 */
	protected function _rollback_menu($step)
	{
		return $this->_removeAdminMenus($step['id']);
	}

	/**
	 * Discover unregistered extensions.
	 *
	 * @return  array  A list of extensions.
	 *
	 * @since   3.1
	 */
	public function discover()
	{
		$results = array();
		$site_components = \JFolder::folders(JPATH_SITE .
'/components');
		$admin_components = \JFolder::folders(JPATH_ADMINISTRATOR .
'/components');

		foreach ($site_components as $component)
		{
			if (file_exists(JPATH_SITE . '/components/' . $component .
'/' . str_replace('com_', '', $component) .
'.xml'))
			{
				$manifest_details = Installer::parseXMLInstallFile(
					JPATH_SITE . '/components/' . $component . '/' .
str_replace('com_', '', $component) . '.xml'
				);
				$extension = Table::getInstance('extension');
				$extension->set('type', 'component');
				$extension->set('client_id', 0);
				$extension->set('element', $component);
				$extension->set('folder', '');
				$extension->set('name', $component);
				$extension->set('state', -1);
				$extension->set('manifest_cache',
json_encode($manifest_details));
				$extension->set('params', '{}');
				$results[] = $extension;
			}
		}

		foreach ($admin_components as $component)
		{
			if (file_exists(JPATH_ADMINISTRATOR . '/components/' .
$component . '/' . str_replace('com_', '',
$component) . '.xml'))
			{
				$manifest_details = Installer::parseXMLInstallFile(
					JPATH_ADMINISTRATOR . '/components/' . $component .
'/' . str_replace('com_', '', $component) .
'.xml'
				);
				$extension = Table::getInstance('extension');
				$extension->set('type', 'component');
				$extension->set('client_id', 1);
				$extension->set('element', $component);
				$extension->set('folder', '');
				$extension->set('name', $component);
				$extension->set('state', -1);
				$extension->set('manifest_cache',
json_encode($manifest_details));
				$extension->set('params', '{}');
				$results[] = $extension;
			}
		}

		return $results;
	}

	/**
	 * Refreshes the extension table cache
	 *
	 * @return  boolean  Result of operation, true if updated, false on
failure
	 *
	 * @since   3.1
	 */
	public function refreshManifestCache()
	{
		// Need to find to find where the XML file is since we don't store
this normally
		$client =
ApplicationHelper::getClientInfo($this->parent->extension->client_id);
		$short_element = str_replace('com_', '',
$this->parent->extension->element);
		$manifestPath = $client->path . '/components/' .
$this->parent->extension->element . '/' . $short_element
. '.xml';
		$this->parent->manifest =
$this->parent->isManifest($manifestPath);
		$this->parent->setPath('manifest', $manifestPath);

		$manifest_details =
Installer::parseXMLInstallFile($this->parent->getPath('manifest'));
		$this->parent->extension->manifest_cache =
json_encode($manifest_details);
		$this->parent->extension->name =
$manifest_details['name'];

		try
		{
			return $this->parent->extension->store();
		}
		catch (\RuntimeException $e)
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_COMP_REFRESH_MANIFEST_CACHE'),
\JLog::WARNING, 'jerror');

			return false;
		}
	}

	/**
	 * Creates the menu item in the database. If the item already exists it
tries to remove it and create it afresh.
	 *
	 * @param   array    &$data     The menu item data to create
	 * @param   integer  $parentId  The parent menu item ID
	 *
	 * @return  boolean|integer  Menu item ID on success, false on failure
	 */
	protected function _createAdminMenuItem(array &$data, $parentId)
	{
		$db = $this->parent->getDbo();

		/** @var  \JTableMenu  $table */
		$table  = Table::getInstance('menu');

		try
		{
			$table->setLocation($parentId, 'last-child');
		}
		catch (\InvalidArgumentException $e)
		{
			\JLog::add($e->getMessage(), \JLog::WARNING, 'jerror');

			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 = $db->getQuery(true)
				->select('id')
				->from('#__menu')
				->where('menutype = ' .
$db->q($data['menutype']))
				->where('client_id = 1')
				->where('link = ' . $db->q($data['link']))
				->where('type = ' . $db->q($data['type']))
				->where('parent_id = ' .
$db->q($data['parent_id']))
				->where('home = ' . $db->q($data['home']));

			$db->setQuery($query);
			$menu_id = $db->loadResult();

			if (!$menu_id)
			{
				// Oops! Could not get the menu ID. Go back and rollback changes.
				\JError::raiseWarning(1, $table->getError());

				return false;
			}
			else
			{
				/** @var  \JTableMenu $temporaryTable */
				$temporaryTable = Table::getInstance('menu');
				$temporaryTable->delete($menu_id, true);
				$temporaryTable->load($parentId);
				$temporaryTable->rebuild($parentId, $temporaryTable->lft,
$temporaryTable->level, $temporaryTable->path);

				// Retry creating the menu item
				$table->setLocation($parentId, 'last-child');

				if (!$table->bind($data) || !$table->check() ||
!$table->store())
				{
					// Install failed, warn user and rollback changes
					\JError::raiseWarning(1, $table->getError());

					return false;
				}
			}
		}

		return $table->id;
	}
}
PKK�[.Vou@u@Adapter/FileAdapter.phpnu�[���<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
 * @license    GNU General Public License version 2 or later; see
LICENSE.txt
 */

namespace Joomla\CMS\Installer\Adapter;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Installer\Installer;
use Joomla\CMS\Installer\InstallerAdapter;
use Joomla\CMS\Table\Table;

\JLoader::import('joomla.filesystem.folder');

/**
 * File installer
 *
 * @since  3.1
 */
class FileAdapter extends InstallerAdapter
{
	/**
	 * `<scriptfile>` element of the extension manifest
	 *
	 * @var    object
	 * @since  3.1
	 */
	protected $scriptElement = null;

	/**
	 * Flag if the adapter supports discover installs
	 *
	 * Adapters should override this and set to false if discover install is
unsupported
	 *
	 * @var    boolean
	 * @since  3.4
	 */
	protected $supportsDiscoverInstall = false;

	/**
	 * Method to copy the extension's base files from the `<files>`
tag(s) and the manifest file
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function copyBaseFiles()
	{
		// Populate File and Folder List to copy
		$this->populateFilesAndFolderList();

		// Now that we have folder list, lets start creating them
		foreach ($this->folderList as $folder)
		{
			if (!\JFolder::exists($folder))
			{
				if (!$created = \JFolder::create($folder))
				{
					throw new \RuntimeException(
						\JText::sprintf('JLIB_INSTALLER_ABORT_FILE_INSTALL_FAIL_SOURCE_DIRECTORY',
$folder)
					);
				}

				// Since we created a directory and will want to remove it if we have
to roll back.
				// The installation due to some errors, let's add it to the
installation step stack.
				if ($created)
				{
					$this->parent->pushStep(array('type' =>
'folder', 'path' => $folder));
				}
			}
		}

		// Now that we have file list, let's start copying them
		$this->parent->copyFiles($this->fileList);
	}

	/**
	 * Method to finalise the installation processing
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function finaliseInstall()
	{
		// Clobber any possible pending updates
		$update = Table::getInstance('update');

		$uid = $update->find(
			array(
				'element' => $this->element,
				'type' => $this->type,
			)
		);

		if ($uid)
		{
			$update->delete($uid);
		}

		// Lastly, we will copy the manifest file to its appropriate place.
		$manifest = array();
		$manifest['src'] =
$this->parent->getPath('manifest');
		$manifest['dest'] = JPATH_MANIFESTS . '/files/' .
basename($this->parent->getPath('manifest'));

		if (!$this->parent->copyFiles(array($manifest), true))
		{
			// Install failed, rollback changes
			throw new
\RuntimeException(\JText::_('JLIB_INSTALLER_ABORT_FILE_INSTALL_COPY_SETUP'));
		}

		// If there is a manifest script, let's copy it.
		if ($this->manifest_script)
		{
			// First, we have to create a folder for the script if one isn't
present
			if
(!file_exists($this->parent->getPath('extension_root')))
			{
				\JFolder::create($this->parent->getPath('extension_root'));
			}

			$path['src'] =
$this->parent->getPath('source') . '/' .
$this->manifest_script;
			$path['dest'] =
$this->parent->getPath('extension_root') . '/' .
$this->manifest_script;

			if ($this->parent->isOverwrite() ||
!file_exists($path['dest']))
			{
				if (!$this->parent->copyFiles(array($path)))
				{
					// Install failed, rollback changes
					throw new \RuntimeException(
						\JText::sprintf(
							'JLIB_INSTALLER_ABORT_MANIFEST',
							\JText::_('JLIB_INSTALLER_' . strtoupper($this->route))
						)
					);
				}
			}
		}
	}

	/**
	 * Get the filtered extension element from the manifest
	 *
	 * @param   string  $element  Optional element name to be converted
	 *
	 * @return  string  The filtered element
	 *
	 * @since   3.4
	 */
	public function getElement($element = null)
	{
		if (!$element)
		{
			$manifestPath =
\JPath::clean($this->parent->getPath('manifest'));
			$element = preg_replace('/\.xml/', '',
basename($manifestPath));
		}

		return $element;
	}

	/**
	 * Custom loadLanguage method
	 *
	 * @param   string  $path  The path on which to find language files.
	 *
	 * @return  void
	 *
	 * @since   3.1
	 */
	public function loadLanguage($path)
	{
		$extension = 'files_' .
strtolower(str_replace('files_', '',
$this->getElement()));

		$this->doLoadLanguage($extension, $path, JPATH_SITE);
	}

	/**
	 * Method to parse optional tags in the manifest
	 *
	 * @return  void
	 *
	 * @since   3.4
	 */
	protected function parseOptionalTags()
	{
		// Parse optional tags
		$this->parent->parseLanguages($this->getManifest()->languages);
	}

	/**
	 * Method to do any prechecks and setup the install paths for the
extension
	 *
	 * @return  void
	 *
	 * @since   3.4
	 */
	protected function setupInstallPaths()
	{
		// Set the file root path
		if ($this->name === 'files_joomla')
		{
			// If we are updating the Joomla core, set the root path to the root of
Joomla
			$this->parent->setPath('extension_root', JPATH_ROOT);
		}
		else
		{
			$this->parent->setPath('extension_root', JPATH_MANIFESTS
. '/files/' . $this->element);
		}
	}

	/**
	 * Method to store the extension to the database
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function storeExtension()
	{
		if ($this->currentExtensionId)
		{
			// Load the entry and update the manifest_cache
			$this->extension->load($this->currentExtensionId);

			// Update name
			$this->extension->name = $this->name;

			// Update manifest
			$this->extension->manifest_cache =
$this->parent->generateManifestCache();

			if (!$this->extension->store())
			{
				// Install failed, roll back changes
				throw new \RuntimeException(
					\JText::sprintf(
						'JLIB_INSTALLER_ABORT_ROLLBACK',
						\JText::_('JLIB_INSTALLER_' . strtoupper($this->route)),
						$this->extension->getError()
					)
				);
			}
		}
		else
		{
			// Add an entry to the extension table with a whole heap of defaults
			$this->extension->name = $this->name;
			$this->extension->type = 'file';
			$this->extension->element = $this->element;

			// There is no folder for files so leave it blank
			$this->extension->folder = '';
			$this->extension->enabled = 1;
			$this->extension->protected = 0;
			$this->extension->access = 0;
			$this->extension->client_id = 0;
			$this->extension->params = '';
			$this->extension->system_data = '';
			$this->extension->manifest_cache =
$this->parent->generateManifestCache();
			$this->extension->custom_data = '';

			if (!$this->extension->store())
			{
				// Install failed, roll back changes
				throw new \RuntimeException(
					\JText::sprintf(
						'JLIB_INSTALLER_ABORT_ROLLBACK',
						\JText::_('JLIB_INSTALLER_' . strtoupper($this->route)),
						$this->extension->getError()
					)
				);
			}

			// Since we have created a module item, we add it to the installation
step stack
			// so that if we have to rollback the changes we can undo it.
			$this->parent->pushStep(array('type' =>
'extension', 'extension_id' =>
$this->extension->extension_id));
		}
	}

	/**
	 * Custom uninstall method
	 *
	 * @param   string  $id  The id of the file to uninstall
	 *
	 * @return  boolean  True on success
	 *
	 * @since   3.1
	 */
	public function uninstall($id)
	{
		$row = Table::getInstance('extension');

		if (!$row->load($id))
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_FILE_UNINSTALL_LOAD_ENTRY'),
\JLog::WARNING, 'jerror');

			return false;
		}

		if ($row->protected)
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_FILE_UNINSTALL_WARNCOREFILE'),
\JLog::WARNING, 'jerror');

			return false;
		}

		/*
		 * Does this extension have a parent package?
		 * If so, check if the package disallows individual extensions being
uninstalled if the package is not being uninstalled
		 */
		if ($row->package_id &&
!$this->parent->isPackageUninstall() &&
!$this->canUninstallPackageChild($row->package_id))
		{
			\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_CANNOT_UNINSTALL_CHILD_OF_PACKAGE',
$row->name), \JLog::WARNING, 'jerror');

			return false;
		}

		$retval = true;
		$manifestFile = JPATH_MANIFESTS . '/files/' . $row->element
. '.xml';

		// Because files may not have their own folders we cannot use the
standard method of finding an installation manifest
		if (file_exists($manifestFile))
		{
			// Set the files root path
			$this->parent->setPath('extension_root', JPATH_MANIFESTS
. '/files/' . $row->element);

			$xml = simplexml_load_file($manifestFile);

			// If we cannot load the XML file return null
			if (!$xml)
			{
				\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_FILE_UNINSTALL_LOAD_MANIFEST'),
\JLog::WARNING, 'jerror');

				return false;
			}

			// Check for a valid XML root tag.
			if ($xml->getName() !== 'extension')
			{
				\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_FILE_UNINSTALL_INVALID_MANIFEST'),
\JLog::WARNING, 'jerror');

				return false;
			}

			$this->setManifest($xml);

			// If there is a manifest class file, let's load it
			$this->scriptElement = $this->getManifest()->scriptfile;
			$manifestScript = (string) $this->getManifest()->scriptfile;

			if ($manifestScript)
			{
				$manifestScriptFile =
$this->parent->getPath('extension_root') . '/' .
$manifestScript;

				// Set the class name
				$classname = $row->element . 'InstallerScript';

				\JLoader::register($classname, $manifestScriptFile);

				if (class_exists($classname))
				{
					// Create a new instance
					$this->parent->manifestClass = new $classname($this);

					// And set this so we can copy it later
					$this->set('manifest_script', $manifestScript);
				}
			}

			ob_start();
			ob_implicit_flush(false);

			// Run uninstall if possible
			if ($this->parent->manifestClass &&
method_exists($this->parent->manifestClass, 'uninstall'))
			{
				$this->parent->manifestClass->uninstall($this);
			}

			$msg = ob_get_contents();
			ob_end_clean();

			if ($msg != '')
			{
				$this->parent->set('extension_message', $msg);
			}

			$db = \JFactory::getDbo();

			// Let's run the uninstall queries for the extension
			$result =
$this->parent->parseSQLFiles($this->getManifest()->uninstall->sql);

			if ($result === false)
			{
				// Install failed, rollback changes
				\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_FILE_UNINSTALL_SQL_ERROR',
$db->stderr(true)), \JLog::WARNING, 'jerror');
				$retval = false;
			}

			// Remove the schema version
			$query = $db->getQuery(true)
				->delete('#__schemas')
				->where('extension_id = ' . $row->extension_id);
			$db->setQuery($query);
			$db->execute();

			// Loop through all elements and get list of files and folders
			foreach ($xml->fileset->files as $eFiles)
			{
				$target = (string) $eFiles->attributes()->target;

				// Create folder path
				if (empty($target))
				{
					$targetFolder = JPATH_ROOT;
				}
				else
				{
					$targetFolder = JPATH_ROOT . '/' . $target;
				}

				$folderList = array();

				// Check if all children exists
				if (count($eFiles->children()) > 0)
				{
					// Loop through all filenames elements
					foreach ($eFiles->children() as $eFileName)
					{
						if ($eFileName->getName() === 'folder')
						{
							$folderList[] = $targetFolder . '/' . $eFileName;
						}
						else
						{
							$fileName = $targetFolder . '/' . $eFileName;
							\JFile::delete($fileName);
						}
					}
				}

				// Delete any folders that don't have any content in them.
				foreach ($folderList as $folder)
				{
					$files = \JFolder::files($folder);

					if ($files !== false && !count($files))
					{
						\JFolder::delete($folder);
					}
				}
			}

			\JFile::delete($manifestFile);

			// Lastly, remove the extension_root
			$folder = $this->parent->getPath('extension_root');

			if (\JFolder::exists($folder))
			{
				\JFolder::delete($folder);
			}
		}
		else
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_FILE_UNINSTALL_INVALID_NOTFOUND_MANIFEST'),
\JLog::WARNING, 'jerror');

			// Delete the row because its broken
			$row->delete();

			return false;
		}

		$this->parent->removeFiles($xml->languages);

		$row->delete();

		return $retval;
	}

	/**
	 * Function used to check if extension is already installed
	 *
	 * @param   string  $extension  The element name of the extension to
install
	 *
	 * @return  boolean  True if extension exists
	 *
	 * @since   3.1
	 */
	protected function extensionExistsInSystem($extension = null)
	{
		// Get a database connector object
		$db = $this->parent->getDbo();

		$query = $db->getQuery(true)
			->select($db->quoteName('extension_id'))
			->from($db->quoteName('#__extensions'))
			->where($db->quoteName('type') . ' = ' .
$db->quote('file'))
			->where($db->quoteName('element') . ' = ' .
$db->quote($extension));
		$db->setQuery($query);

		try
		{
			$db->execute();
		}
		catch (\RuntimeException $e)
		{
			// Install failed, roll back changes
			$this->parent->abort(\JText::sprintf('JLIB_INSTALLER_ABORT_FILE_ROLLBACK',
$db->stderr(true)));

			return false;
		}

		$id = $db->loadResult();

		if (empty($id))
		{
			return false;
		}

		return true;
	}

	/**
	 * Function used to populate files and folder list
	 *
	 * @return  boolean  none
	 *
	 * @since   3.1
	 */
	protected function populateFilesAndFolderList()
	{
		// Initialise variable
		$this->folderList = array();
		$this->fileList = array();

		// Set root folder names
		$packagePath = $this->parent->getPath('source');
		$jRootPath = \JPath::clean(JPATH_ROOT);

		// Loop through all elements and get list of files and folders
		foreach ($this->getManifest()->fileset->files as $eFiles)
		{
			// Check if the element is files element
			$folder = (string) $eFiles->attributes()->folder;
			$target = (string) $eFiles->attributes()->target;

			// Split folder names into array to get folder names. This will help in
creating folders
			$arrList = preg_split("#/|\\/#", $target);

			$folderName = $jRootPath;

			foreach ($arrList as $dir)
			{
				if (empty($dir))
				{
					continue;
				}

				$folderName .= '/' . $dir;

				// Check if folder exists, if not then add to the array for folder
creation
				if (!\JFolder::exists($folderName))
				{
					$this->folderList[] = $folderName;
				}
			}

			// Create folder path
			$sourceFolder = empty($folder) ? $packagePath : $packagePath .
'/' . $folder;
			$targetFolder = empty($target) ? $jRootPath : $jRootPath . '/'
. $target;

			// Check if source folder exists
			if (!\JFolder::exists($sourceFolder))
			{
				\JLog::add(\JText::sprintf('JLIB_INSTALLER_ABORT_FILE_INSTALL_FAIL_SOURCE_DIRECTORY',
$sourceFolder), \JLog::WARNING, 'jerror');

				// If installation fails, rollback
				$this->parent->abort();

				return false;
			}

			// Check if all children exists
			if (count($eFiles->children()))
			{
				// Loop through all filenames elements
				foreach ($eFiles->children() as $eFileName)
				{
					$path['src'] = $sourceFolder . '/' . $eFileName;
					$path['dest'] = $targetFolder . '/' . $eFileName;
					$path['type'] = 'file';

					if ($eFileName->getName() === 'folder')
					{
						$folderName         = $targetFolder . '/' . $eFileName;
						$this->folderList[] = $folderName;
						$path['type']       = 'folder';
					}

					$this->fileList[] = $path;
				}
			}
			else
			{
				$files = \JFolder::files($sourceFolder);

				foreach ($files as $file)
				{
					$path['src'] = $sourceFolder . '/' . $file;
					$path['dest'] = $targetFolder . '/' . $file;

					$this->fileList[] = $path;
				}
			}
		}
	}

	/**
	 * Refreshes the extension table cache
	 *
	 * @return  boolean result of operation, true if updated, false on failure
	 *
	 * @since   3.1
	 */
	public function refreshManifestCache()
	{
		// Need to find to find where the XML file is since we don't store
this normally
		$manifestPath = JPATH_MANIFESTS . '/files/' .
$this->parent->extension->element . '.xml';
		$this->parent->manifest =
$this->parent->isManifest($manifestPath);
		$this->parent->setPath('manifest', $manifestPath);

		$manifest_details =
Installer::parseXMLInstallFile($this->parent->getPath('manifest'));
		$this->parent->extension->manifest_cache =
json_encode($manifest_details);
		$this->parent->extension->name =
$manifest_details['name'];

		try
		{
			return $this->parent->extension->store();
		}
		catch (\RuntimeException $e)
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_PACK_REFRESH_MANIFEST_CACHE'),
\JLog::WARNING, 'jerror');

			return false;
		}
	}
}
PKK�[��i��c�cAdapter/LanguageAdapter.phpnu�[���<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
 * @license    GNU General Public License version 2 or later; see
LICENSE.txt
 */

namespace Joomla\CMS\Installer\Adapter;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Installer\Installer;
use Joomla\CMS\Installer\InstallerAdapter;
use Joomla\CMS\Language\Language;
use Joomla\CMS\Language\LanguageHelper;
use Joomla\CMS\Table\Table;
use Joomla\Registry\Registry;

jimport('joomla.filesystem.folder');

/**
 * Language installer
 *
 * @since  3.1
 */
class LanguageAdapter extends InstallerAdapter
{
	/**
	 * Core language pack flag
	 *
	 * @var    boolean
	 * @since  3.0.0
	 */
	protected $core = false;

	/**
	 * Method to copy the extension's base files from the `<files>`
tag(s) and the manifest file
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function copyBaseFiles()
	{
		// TODO - Refactor adapter to use common code
	}

	/**
	 * Method to do any prechecks and setup the install paths for the
extension
	 *
	 * @return  void
	 *
	 * @since   3.4
	 */
	protected function setupInstallPaths()
	{
		// TODO - Refactor adapter to use common code
	}

	/**
	 * Method to store the extension to the database
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function storeExtension()
	{
		// TODO - Refactor adapter to use common code
	}

	/**
	 * Custom install method
	 *
	 * Note: This behaves badly due to hacks made in the middle of 1.5.x to
add
	 * the ability to install multiple distinct packs in one install. The
	 * preferred method is to use a package to install multiple language
packs.
	 *
	 * @return  boolean|integer  The extension ID on success, boolean false on
failure
	 *
	 * @since   3.1
	 */
	public function install()
	{
		$source = $this->parent->getPath('source');

		if (!$source)
		{
			$this->parent
				->setPath(
				'source',
				($this->parent->extension->client_id ? JPATH_ADMINISTRATOR :
JPATH_SITE) . '/language/' .
$this->parent->extension->element
			);
		}

		$this->setManifest($this->parent->getManifest());

		// Get the client application target
		if ($cname = (string)
$this->getManifest()->attributes()->client)
		{
			// Attempt to map the client to a base path
			$client = ApplicationHelper::getClientInfo($cname, true);

			if ($client === null)
			{
				$this->parent->abort(\JText::sprintf('JLIB_INSTALLER_ABORT',
\JText::sprintf('JLIB_INSTALLER_ERROR_UNKNOWN_CLIENT_TYPE',
$cname)));

				return false;
			}

			$basePath = $client->path;
			$clientId = $client->id;
			$element  = $this->getManifest()->files;

			return $this->_install($cname, $basePath, $clientId, $element);
		}
		else
		{
			// No client attribute was found so we assume the site as the client
			$cname    = 'site';
			$basePath = JPATH_SITE;
			$clientId = 0;
			$element  = $this->getManifest()->files;

			return $this->_install($cname, $basePath, $clientId, $element);
		}
	}

	/**
	 * Install function that is designed to handle individual clients
	 *
	 * @param   string   $cname     Cname @todo: not used
	 * @param   string   $basePath  The base name.
	 * @param   integer  $clientId  The client id.
	 * @param   object   &$element  The XML element.
	 *
	 * @return  boolean|integer  The extension ID on success, boolean false on
failure
	 *
	 * @since   3.1
	 */
	protected function _install($cname, $basePath, $clientId, &$element)
	{
		$this->setManifest($this->parent->getManifest());

		// Get the language name
		// Set the extensions name
		$name = \JFilterInput::getInstance()->clean((string)
$this->getManifest()->name, 'cmd');
		$this->set('name', $name);

		// Get the Language tag [ISO tag, eg. en-GB]
		$tag = (string) $this->getManifest()->tag;

		// Check if we found the tag - if we didn't, we may be trying to
install from an older language package
		if (!$tag)
		{
			$this->parent->abort(\JText::sprintf('JLIB_INSTALLER_ABORT',
\JText::_('JLIB_INSTALLER_ERROR_NO_LANGUAGE_TAG')));

			return false;
		}

		$this->set('tag', $tag);

		// Set the language installation path
		$this->parent->setPath('extension_site', $basePath .
'/language/' . $tag);

		// Do we have a meta file in the file list?  In other words... is this a
core language pack?
		if ($element && count($element->children()))
		{
			$files = $element->children();

			foreach ($files as $file)
			{
				if ((string) $file->attributes()->file === 'meta')
				{
					$this->core = true;
					break;
				}
			}
		}

		// If the language directory does not exist, let's create it
		$created = false;

		if
(!file_exists($this->parent->getPath('extension_site')))
		{
			if (!$created =
\JFolder::create($this->parent->getPath('extension_site')))
			{
				$this->parent
					->abort(
					\JText::sprintf(
						'JLIB_INSTALLER_ABORT',
						\JText::sprintf('JLIB_INSTALLER_ERROR_CREATE_FOLDER_FAILED',
$this->parent->getPath('extension_site'))
					)
				);

				return false;
			}
		}
		else
		{
			// Look for an update function or update tag
			$updateElement = $this->getManifest()->update;

			// Upgrade manually set or update tag detected
			if ($updateElement || $this->parent->isUpgrade())
			{
				// Transfer control to the update function
				return $this->update();
			}
			elseif (!$this->parent->isOverwrite())
			{
				// Overwrite is set
				// We didn't have overwrite set, find an update function or find
an update tag so lets call it safe
				if
(file_exists($this->parent->getPath('extension_site')))
				{
					// If the site exists say so.
					\JLog::add(
						\JText::sprintf('JLIB_INSTALLER_ABORT',
\JText::sprintf('JLIB_INSTALLER_ERROR_FOLDER_IN_USE',
$this->parent->getPath('extension_site'))),
						\JLog::WARNING, 'jerror'
					);
				}
				else
				{
					// If the admin exists say so.
					\JLog::add(
						\JText::sprintf('JLIB_INSTALLER_ABORT',
							\JText::sprintf('JLIB_INSTALLER_ERROR_FOLDER_IN_USE',
$this->parent->getPath('extension_administrator'))
						),
						\JLog::WARNING, 'jerror'
					);
				}

				return false;
			}
		}

		/*
		 * If we created the language directory we will want to remove it if we
		 * have to roll back the installation, so let's add it to the
installation
		 * step stack
		 */
		if ($created)
		{
			$this->parent->pushStep(array('type' =>
'folder', 'path' =>
$this->parent->getPath('extension_site')));
		}

		// Copy all the necessary files
		if ($this->parent->parseFiles($element) === false)
		{
			// Install failed, rollback changes
			$this->parent->abort();

			return false;
		}

		// Parse optional tags
		$this->parent->parseMedia($this->getManifest()->media);

		/*
		 * Log that PDF Fonts in language packs are deprecated and will be
removed in 4.0
		 * Ref: https://github.com/joomla/joomla-cms/issues/31286
		 */
		if (is_dir($basePath . '/language/pdf_fonts'))
		{
			try
			{
				\JLog::add(
					'Using the "pdf_fonts" folder to load language specific
fonts in languages is deprecated and will be removed in 4.0.',
					\JLog::WARNING,
					'deprecated'
				);
			}
			catch (RuntimeException $exception)
			{
				// Informational log only
			}
		}

		// Copy all the necessary font files to the common pdf_fonts directory
		$this->parent->setPath('extension_site', $basePath .
'/language/pdf_fonts');
		$overwrite = $this->parent->setOverwrite(true);

		if ($this->parent->parseFiles($this->getManifest()->fonts)
=== false)
		{
			// Install failed, rollback changes
			$this->parent->abort();

			return false;
		}

		$this->parent->setOverwrite($overwrite);

		// Get the language description
		$description = (string) $this->getManifest()->description;

		if ($description)
		{
			$this->parent->set('message', \JText::_($description));
		}
		else
		{
			$this->parent->set('message', '');
		}

		// Add an entry to the extension table with a whole heap of defaults
		$row = Table::getInstance('extension');
		$row->set('name', $this->get('name'));
		$row->set('type', 'language');
		$row->set('element', $this->get('tag'));

		// There is no folder for languages
		$row->set('folder', '');
		$row->set('enabled', 1);
		$row->set('protected', 0);
		$row->set('access', 0);
		$row->set('client_id', $clientId);
		$row->set('params', $this->parent->getParams());
		$row->set('manifest_cache',
$this->parent->generateManifestCache());

		if (!$row->check() || !$row->store())
		{
			// Install failed, roll back changes
			$this->parent->abort(\JText::sprintf('JLIB_INSTALLER_ABORT',
$row->getError()));

			return false;
		}

		// Create an unpublished content language.
		if ((int) $clientId === 0)
		{
			// Load the site language manifest.
			$siteLanguageManifest = LanguageHelper::parseXMLLanguageFile(JPATH_SITE
. '/language/' . $this->tag . '/' . $this->tag .
'.xml');

			// Set the content language title as the language metadata name.
			$contentLanguageTitle = $siteLanguageManifest['name'];

			// Set, as fallback, the content language native title to the language
metadata name.
			$contentLanguageNativeTitle = $contentLanguageTitle;

			// If exist, load the native title from the language xml metadata.
			if (isset($siteLanguageManifest['nativeName']) &&
$siteLanguageManifest['nativeName'])
			{
				$contentLanguageNativeTitle =
$siteLanguageManifest['nativeName'];
			}

			// Try to load a language string from the installation language var.
Will be removed in 4.0.
			if ($contentLanguageNativeTitle === $contentLanguageTitle)
			{
				if (file_exists(JPATH_INSTALLATION . '/language/' .
$this->tag . '/' . $this->tag . '.xml'))
				{
					$installationLanguage = new Language($this->tag);
					$installationLanguage->load('', JPATH_INSTALLATION);

					if
($installationLanguage->hasKey('INSTL_DEFAULTLANGUAGE_NATIVE_LANGUAGE_NAME'))
					{
						// Make sure it will not use the en-GB fallback.
						$defaultLanguage = new Language('en-GB');
						$defaultLanguage->load('', JPATH_INSTALLATION);

						$defaultLanguageNativeTitle      =
$defaultLanguage->_('INSTL_DEFAULTLANGUAGE_NATIVE_LANGUAGE_NAME');
						$installationLanguageNativeTitle =
$installationLanguage->_('INSTL_DEFAULTLANGUAGE_NATIVE_LANGUAGE_NAME');

						if ($defaultLanguageNativeTitle !== $installationLanguageNativeTitle)
						{
							$contentLanguageNativeTitle =
$installationLanguage->_('INSTL_DEFAULTLANGUAGE_NATIVE_LANGUAGE_NAME');
						}
					}
				}
			}

			// Prepare language data for store.
			$languageData = array(
				'lang_id'      => 0,
				'lang_code'    => $this->tag,
				'title'        => $contentLanguageTitle,
				'title_native' => $contentLanguageNativeTitle,
				'sef'          => $this->getSefString($this->tag),
				'image'        => strtolower(str_replace('-',
'_', $this->tag)),
				'published'    => 0,
				'ordering'     => 0,
				'access'       => (int)
\JFactory::getConfig()->get('access', 1),
				'description'  => '',
				'metakey'      => '',
				'metadesc'     => '',
				'sitename'     => '',
			);

			$tableLanguage = Table::getInstance('language');

			if (!$tableLanguage->bind($languageData) ||
!$tableLanguage->check() || !$tableLanguage->store() ||
!$tableLanguage->reorder())
			{
				\JLog::add(
					\JText::sprintf('JLIB_INSTALLER_WARNING_UNABLE_TO_INSTALL_CONTENT_LANGUAGE',
$siteLanguageManifest['name'], $tableLanguage->getError()),
					\JLog::NOTICE,
					'jerror'
				);
			}
		}

		// Clobber any possible pending updates
		$update = Table::getInstance('update');
		$uid = $update->find(array('element' =>
$this->get('tag'), 'type' =>
'language', 'folder' => ''));

		if ($uid)
		{
			$update->delete($uid);
		}

		// Clean installed languages cache.
		\JFactory::getCache()->clean('com_languages');

		return $row->get('extension_id');
	}


	/**
	 * Gets a unique language SEF string.
	 *
	 * This function checks other existing language with the same code, if
they exist provides a unique SEF name.
	 * For instance: en-GB, en-US and en-AU will share the same SEF code by
default: www.mywebsite.com/en/
	 * To avoid this conflict, this function creates an specific SEF in case
of existing conflict:
	 * For example: www.mywebsite.com/en-au/
	 *
	 * @param   string  $itemLanguageTag  Language Tag.
	 *
	 * @return  string
	 *
	 * @since   3.7.0
	 */
	protected function getSefString($itemLanguageTag)
	{
		$langs               = explode('-', $itemLanguageTag);
		$prefixToFind        = $langs[0];
		$numberPrefixesFound = 0;

		// Get the sef value of all current content languages.
		$db = \JFactory::getDbo();
		$query = $db->getQuery(true)
			->select($db->qn('sef'))
			->from($db->qn('#__languages'));
		$db->setQuery($query);

		$siteLanguages = $db->loadObjectList();

		foreach ($siteLanguages as $siteLang)
		{
			if ($siteLang->sef === $prefixToFind)
			{
				$numberPrefixesFound++;
			}
		}

		return $numberPrefixesFound === 0 ? $prefixToFind :
strtolower($itemLanguageTag);
	}

	/**
	 * Custom update method
	 *
	 * @return  boolean  True on success, false on failure
	 *
	 * @since   3.1
	 */
	public function update()
	{
		$xml = $this->parent->getManifest();

		$this->setManifest($xml);

		$cname = $xml->attributes()->client;

		// Attempt to map the client to a base path
		$client = ApplicationHelper::getClientInfo($cname, true);

		if ($client === null || (empty($cname) && $cname !== 0))
		{
			$this->parent->abort(\JText::sprintf('JLIB_INSTALLER_ABORT',
\JText::sprintf('JLIB_INSTALLER_ERROR_UNKNOWN_CLIENT_TYPE',
$cname)));

			return false;
		}

		$basePath = $client->path;
		$clientId = $client->id;

		// Get the language name
		// Set the extensions name
		$name = (string) $this->getManifest()->name;
		$name = \JFilterInput::getInstance()->clean($name, 'cmd');
		$this->set('name', $name);

		// Get the Language tag [ISO tag, eg. en-GB]
		$tag = (string) $xml->tag;

		// Check if we found the tag - if we didn't, we may be trying to
install from an older language package
		if (!$tag)
		{
			$this->parent->abort(\JText::sprintf('JLIB_INSTALLER_ABORT',
\JText::_('JLIB_INSTALLER_ERROR_NO_LANGUAGE_TAG')));

			return false;
		}

		$this->set('tag', $tag);

		// Set the language installation path
		$this->parent->setPath('extension_site', $basePath .
'/language/' . $tag);

		// Do we have a meta file in the file list?  In other words... is this a
core language pack?
		if (count($xml->files->children()))
		{
			foreach ($xml->files->children() as $file)
			{
				if ((string) $file->attributes()->file === 'meta')
				{
					$this->core = true;
					break;
				}
			}
		}

		// Copy all the necessary files
		if ($this->parent->parseFiles($xml->files) === false)
		{
			// Install failed, rollback changes
			$this->parent->abort();

			return false;
		}

		// Parse optional tags
		$this->parent->parseMedia($xml->media);

		/*
		 * Log that PDF Fonts in language packs are deprecated and will be
removed in 4.0
		 * Ref: https://github.com/joomla/joomla-cms/issues/31286
		 */
		if (is_dir($basePath . '/language/pdf_fonts'))
		{
			try
			{
				\JLog::add(
					'Using the "pdf_fonts" folder to load language specific
fonts in languages is deprecated and will be removed in 4.0.',
					\JLog::WARNING,
					'deprecated'
				);
			}
			catch (RuntimeException $exception)
			{
				// Informational log only
			}
		}

		// Copy all the necessary font files to the common pdf_fonts directory
		$this->parent->setPath('extension_site', $basePath .
'/language/pdf_fonts');
		$overwrite = $this->parent->setOverwrite(true);

		if ($this->parent->parseFiles($xml->fonts) === false)
		{
			// Install failed, rollback changes
			$this->parent->abort();

			return false;
		}

		$this->parent->setOverwrite($overwrite);

		// Get the language description and set it as message
		$this->parent->set('message', (string)
$xml->description);

		/**
		 *
---------------------------------------------------------------------------------------------
		 * Finalization and Cleanup Section
		 *
---------------------------------------------------------------------------------------------
		 */

		// Clobber any possible pending updates
		$update = Table::getInstance('update');
		$uid = $update->find(array('element' =>
$this->get('tag'), 'type' =>
'language', 'client_id' => $clientId));

		if ($uid)
		{
			$update->delete($uid);
		}

		// Update an entry to the extension table
		$row = Table::getInstance('extension');
		$eid = $row->find(array('element' =>
$this->get('tag'), 'type' =>
'language', 'client_id' => $clientId));

		if ($eid)
		{
			$row->load($eid);
		}
		else
		{
			// Set the defaults

			// There is no folder for language
			$row->set('folder', '');
			$row->set('enabled', 1);
			$row->set('protected', 0);
			$row->set('access', 0);
			$row->set('client_id', $clientId);
			$row->set('params', $this->parent->getParams());
		}

		$row->set('name', $this->get('name'));
		$row->set('type', 'language');
		$row->set('element', $this->get('tag'));
		$row->set('manifest_cache',
$this->parent->generateManifestCache());

		// Clean installed languages cache.
		\JFactory::getCache()->clean('com_languages');

		if (!$row->check() || !$row->store())
		{
			// Install failed, roll back changes
			$this->parent->abort(\JText::sprintf('JLIB_INSTALLER_ABORT',
$row->getError()));

			return false;
		}

		return $row->get('extension_id');
	}

	/**
	 * Custom uninstall method
	 *
	 * @param   string  $eid  The tag of the language to uninstall
	 *
	 * @return  boolean  True on success
	 *
	 * @since   3.1
	 */
	public function uninstall($eid)
	{
		// Load up the extension details
		$extension = Table::getInstance('extension');
		$extension->load($eid);

		// Grab a copy of the client details
		$client =
ApplicationHelper::getClientInfo($extension->get('client_id'));

		// Check the element isn't blank to prevent nuking the languages
directory...just in case
		$element = $extension->get('element');

		if (empty($element))
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_LANG_UNINSTALL_ELEMENT_EMPTY'),
\JLog::WARNING, 'jerror');

			return false;
		}

		// Check that the language is not protected, Normally en-GB.
		$protected = $extension->get('protected');

		if ($protected == 1)
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_LANG_UNINSTALL_PROTECTED'),
\JLog::WARNING, 'jerror');

			return false;
		}

		// Verify that it's not the default language for that client
		$params = ComponentHelper::getParams('com_languages');

		if ($params->get($client->name) === $element)
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_LANG_UNINSTALL_DEFAULT'),
\JLog::WARNING, 'jerror');

			return false;
		}

		/*
		 * Does this extension have a parent package?
		 * If so, check if the package disallows individual extensions being
uninstalled if the package is not being uninstalled
		 */
		if ($extension->package_id &&
!$this->parent->isPackageUninstall() &&
!$this->canUninstallPackageChild($extension->package_id))
		{
			\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_CANNOT_UNINSTALL_CHILD_OF_PACKAGE',
$extension->name), \JLog::WARNING, 'jerror');

			return false;
		}

		// Construct the path from the client, the language and the extension
element name
		$path = $client->path . '/language/' . $element;

		// Get the package manifest object and remove media
		$this->parent->setPath('source', $path);

		// We do findManifest to avoid problem when uninstalling a list of
extension: getManifest cache its manifest file
		$this->parent->findManifest();
		$this->setManifest($this->parent->getManifest());
		$this->parent->removeFiles($this->getManifest()->media);

		// Check it exists
		if (!\JFolder::exists($path))
		{
			// If the folder doesn't exist lets just nuke the row as well and
presume the user killed it for us
			$extension->delete();
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_LANG_UNINSTALL_PATH_EMPTY'),
\JLog::WARNING, 'jerror');

			return false;
		}

		if (!\JFolder::delete($path))
		{
			// If deleting failed we'll leave the extension entry in tact just
in case
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_LANG_UNINSTALL_DIRECTORY'),
\JLog::WARNING, 'jerror');

			return false;
		}

		// Remove the extension table entry
		$extension->delete();

		// Setting the language of users which have this language as the default
language
		$db = \JFactory::getDbo();
		$query = $db->getQuery(true)
			->from('#__users')
			->select('*');
		$db->setQuery($query);
		$users = $db->loadObjectList();

		if ($client->name === 'administrator')
		{
			$param_name = 'admin_language';
		}
		else
		{
			$param_name = 'language';
		}

		$count = 0;

		foreach ($users as $user)
		{
			$registry = new Registry($user->params);

			if ($registry->get($param_name) === $element)
			{
				$registry->set($param_name, '');
				$query->clear()
					->update('#__users')
					->set('params=' . $db->quote($registry))
					->where('id=' . (int) $user->id);
				$db->setQuery($query);
				$db->execute();
				$count++;
			}
		}

		// Clean installed languages cache.
		\JFactory::getCache()->clean('com_languages');

		if (!empty($count))
		{
			\JLog::add(\JText::plural('JLIB_INSTALLER_NOTICE_LANG_RESET_USERS',
$count), \JLog::NOTICE, 'jerror');
		}

		// All done!
		return true;
	}

	/**
	 * Custom discover method
	 * Finds language files
	 *
	 * @return  boolean  True on success
	 *
	 * @since  3.1
	 */
	public function discover()
	{
		$results = array();
		$site_languages = \JFolder::folders(JPATH_SITE . '/language');
		$admin_languages = \JFolder::folders(JPATH_ADMINISTRATOR .
'/language');

		foreach ($site_languages as $language)
		{
			if (file_exists(JPATH_SITE . '/language/' . $language .
'/' . $language . '.xml'))
			{
				$manifest_details = Installer::parseXMLInstallFile(JPATH_SITE .
'/language/' . $language . '/' . $language .
'.xml');
				$extension = Table::getInstance('extension');
				$extension->set('type', 'language');
				$extension->set('client_id', 0);
				$extension->set('element', $language);
				$extension->set('folder', '');
				$extension->set('name', $language);
				$extension->set('state', -1);
				$extension->set('manifest_cache',
json_encode($manifest_details));
				$extension->set('params', '{}');
				$results[] = $extension;
			}
		}

		foreach ($admin_languages as $language)
		{
			if (file_exists(JPATH_ADMINISTRATOR . '/language/' . $language
. '/' . $language . '.xml'))
			{
				$manifest_details = Installer::parseXMLInstallFile(JPATH_ADMINISTRATOR
. '/language/' . $language . '/' . $language .
'.xml');
				$extension = Table::getInstance('extension');
				$extension->set('type', 'language');
				$extension->set('client_id', 1);
				$extension->set('element', $language);
				$extension->set('folder', '');
				$extension->set('name', $language);
				$extension->set('state', -1);
				$extension->set('manifest_cache',
json_encode($manifest_details));
				$extension->set('params', '{}');
				$results[] = $extension;
			}
		}

		return $results;
	}

	/**
	 * Custom discover install method
	 * Basically updates the manifest cache and leaves everything alone
	 *
	 * @return  integer  The extension id
	 *
	 * @since   3.1
	 */
	public function discover_install()
	{
		// Need to find to find where the XML file is since we don't store
this normally
		$client =
ApplicationHelper::getClientInfo($this->parent->extension->client_id);
		$short_element = $this->parent->extension->element;
		$manifestPath = $client->path . '/language/' .
$short_element . '/' . $short_element . '.xml';
		$this->parent->manifest =
$this->parent->isManifest($manifestPath);
		$this->parent->setPath('manifest', $manifestPath);
		$this->parent->setPath('source', $client->path .
'/language/' . $short_element);
		$this->parent->setPath('extension_root',
$this->parent->getPath('source'));
		$manifest_details =
Installer::parseXMLInstallFile($this->parent->getPath('manifest'));
		$this->parent->extension->manifest_cache =
json_encode($manifest_details);
		$this->parent->extension->state = 0;
		$this->parent->extension->name =
$manifest_details['name'];
		$this->parent->extension->enabled = 1;

		// @todo remove code: $this->parent->extension->params =
$this->parent->getParams();
		try
		{
			$this->parent->extension->check();
			$this->parent->extension->store();
		}
		catch (\RuntimeException $e)
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_LANG_DISCOVER_STORE_DETAILS'),
\JLog::WARNING, 'jerror');

			return false;
		}

		// Clean installed languages cache.
		\JFactory::getCache()->clean('com_languages');

		return $this->parent->extension->get('extension_id');
	}

	/**
	 * Refreshes the extension table cache
	 *
	 * @return  boolean result of operation, true if updated, false on failure
	 *
	 * @since   3.1
	 */
	public function refreshManifestCache()
	{
		$client =
ApplicationHelper::getClientInfo($this->parent->extension->client_id);
		$manifestPath = $client->path . '/language/' .
$this->parent->extension->element . '/' .
$this->parent->extension->element . '.xml';
		$this->parent->manifest =
$this->parent->isManifest($manifestPath);
		$this->parent->setPath('manifest', $manifestPath);
		$manifest_details =
Installer::parseXMLInstallFile($this->parent->getPath('manifest'));
		$this->parent->extension->manifest_cache =
json_encode($manifest_details);
		$this->parent->extension->name =
$manifest_details['name'];

		if ($this->parent->extension->store())
		{
			return true;
		}
		else
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_MOD_REFRESH_MANIFEST_CACHE'),
\JLog::WARNING, 'jerror');

			return false;
		}
	}
}
PKK�[�v4�+5+5Adapter/LibraryAdapter.phpnu�[���<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
 * @license    GNU General Public License version 2 or later; see
LICENSE.txt
 */

namespace Joomla\CMS\Installer\Adapter;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Installer\Installer;
use Joomla\CMS\Installer\InstallerAdapter;
use Joomla\CMS\Installer\Manifest\LibraryManifest;
use Joomla\CMS\Table\Table;
use Joomla\CMS\Table\Update;

\JLoader::import('joomla.filesystem.folder');

/**
 * Library installer
 *
 * @since  3.1
 */
class LibraryAdapter extends InstallerAdapter
{
	/**
	 * Method to check if the extension is present in the filesystem, flags
the route as update if so
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function checkExtensionInFilesystem()
	{
		if ($this->currentExtensionId)
		{
			// Already installed, can we upgrade?
			if ($this->parent->isOverwrite() ||
$this->parent->isUpgrade())
			{
				// We can upgrade, so uninstall the old one
				$installer = new Installer; // we don't want to compromise this
instance!
				$installer->setPackageUninstall(true);
				$installer->uninstall('library',
$this->currentExtensionId);

				// Clear the cached data
				$this->currentExtensionId = null;
				$this->extension = Table::getInstance('Extension',
'JTable', array('dbo' => $this->db));

				// From this point we'll consider this an update
				$this->setRoute('update');
			}
			else
			{
				// Abort the install, no upgrade possible
				throw new
\RuntimeException(\JText::_('JLIB_INSTALLER_ABORT_LIB_INSTALL_ALREADY_INSTALLED'));
			}
		}
	}

	/**
	 * Method to copy the extension's base files from the `<files>`
tag(s) and the manifest file
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function copyBaseFiles()
	{
		if ($this->parent->parseFiles($this->getManifest()->files,
-1) === false)
		{
			throw new
\RuntimeException(\JText::_('JLIB_INSTALLER_ABORT_LIB_COPY_FILES'));
		}
	}

	/**
	 * Method to finalise the installation processing
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function finaliseInstall()
	{
		// Clobber any possible pending updates
		/** @var Update $update */
		$update = Table::getInstance('update');
		$uid = $update->find(
			array(
				'element' => $this->element,
				'type' => $this->type,
			)
		);

		if ($uid)
		{
			$update->delete($uid);
		}

		// Lastly, we will copy the manifest file to its appropriate place.
		if ($this->route !== 'discover_install')
		{
			$manifest = array();
			$manifest['src'] =
$this->parent->getPath('manifest');
			$manifest['dest'] = JPATH_MANIFESTS . '/libraries/'
. $this->element . '.xml';

			$destFolder = dirname($manifest['dest']);

			if (!is_dir($destFolder) && !@mkdir($destFolder))
			{
				// Install failed, rollback changes
				throw new
\RuntimeException(\JText::_('JLIB_INSTALLER_ABORT_LIB_INSTALL_COPY_SETUP'));
			}

			if (!$this->parent->copyFiles(array($manifest), true))
			{
				// Install failed, rollback changes
				throw new
\RuntimeException(\JText::_('JLIB_INSTALLER_ABORT_LIB_INSTALL_COPY_SETUP'));
			}

			// If there is a manifest script, let's copy it.
			if ($this->manifest_script)
			{
				$path['src'] =
$this->parent->getPath('source') . '/' .
$this->manifest_script;
				$path['dest'] =
$this->parent->getPath('extension_root') . '/' .
$this->manifest_script;

				if ($this->parent->isOverwrite() ||
!file_exists($path['dest']))
				{
					if (!$this->parent->copyFiles(array($path)))
					{
						// Install failed, rollback changes
						throw new \RuntimeException(
							\JText::sprintf(
								'JLIB_INSTALLER_ABORT_MANIFEST',
								\JText::_('JLIB_INSTALLER_' .
strtoupper($this->route))
							)
						);
					}
				}
			}
		}
	}

	/**
	 * Get the filtered extension element from the manifest
	 *
	 * @param   string  $element  Optional element name to be converted
	 *
	 * @return  string  The filtered element
	 *
	 * @since   3.4
	 */
	public function getElement($element = null)
	{
		if (!$element)
		{
			$element  = (string) $this->getManifest()->libraryname;
		}

		return $element;
	}

	/**
	 * Custom loadLanguage method
	 *
	 * @param   string  $path  The path where to find language files.
	 *
	 * @return  void
	 *
	 * @since   3.1
	 */
	public function loadLanguage($path = null)
	{
		$source = $this->parent->getPath('source');

		if (!$source)
		{
			$this->parent->setPath('source', JPATH_PLATFORM .
'/' . $this->getElement());
		}

		$extension = 'lib_' . str_replace('/', '_',
$this->getElement());
		$librarypath = (string) $this->getManifest()->libraryname;
		$source = $path ?: JPATH_PLATFORM . '/' . $librarypath;

		$this->doLoadLanguage($extension, $source, JPATH_SITE);
	}

	/**
	 * Method to parse optional tags in the manifest
	 *
	 * @return  void
	 *
	 * @since   3.4
	 */
	protected function parseOptionalTags()
	{
		$this->parent->parseLanguages($this->getManifest()->languages);
		$this->parent->parseMedia($this->getManifest()->media);
	}

	/**
	 * Prepares the adapter for a discover_install task
	 *
	 * @return  void
	 *
	 * @since   3.4
	 */
	public function prepareDiscoverInstall()
	{
		$manifestPath = JPATH_MANIFESTS . '/libraries/' .
$this->extension->element . '.xml';
		$this->parent->manifest =
$this->parent->isManifest($manifestPath);
		$this->parent->setPath('manifest', $manifestPath);
		$this->setManifest($this->parent->getManifest());
	}

	/**
	 * Method to do any prechecks and setup the install paths for the
extension
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function setupInstallPaths()
	{
		$group = (string) $this->getManifest()->libraryname;

		if (!$group)
		{
			throw new
\RuntimeException(\JText::_('JLIB_INSTALLER_ABORT_LIB_INSTALL_NOFILE'));
		}

		// Don't install libraries which would override core folders
		$restrictedFolders = array('cms', 'fof',
'idna_convert', 'joomla', 'legacy',
'php-encryption', 'phpass', 'phputf8',
'src', 'vendor');

		if (in_array($group, $restrictedFolders))
		{
			throw new
\RuntimeException(\JText::_('JLIB_INSTALLER_ABORT_LIB_INSTALL_CORE_FOLDER'));
		}

		$this->parent->setPath('extension_root', JPATH_PLATFORM .
'/' . implode(DIRECTORY_SEPARATOR, explode('/',
$group)));
	}

	/**
	 * Method to store the extension to the database
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function storeExtension()
	{
		// Discover installs are stored a little differently
		if ($this->route === 'discover_install')
		{
			$manifest_details =
Installer::parseXMLInstallFile($this->parent->getPath('manifest'));

			$this->extension->manifest_cache = json_encode($manifest_details);
			$this->extension->state = 0;
			$this->extension->name = $manifest_details['name'];
			$this->extension->enabled = 1;
			$this->extension->params = $this->parent->getParams();

			if (!$this->extension->store())
			{
				// Install failed, roll back changes
				throw new
\RuntimeException(\JText::_('JLIB_INSTALLER_ERROR_LIB_DISCOVER_STORE_DETAILS'));
			}

			return;
		}

		$this->extension->name = $this->name;
		$this->extension->type = 'library';
		$this->extension->element = $this->element;

		// There is no folder for libraries
		$this->extension->folder = '';
		$this->extension->enabled = 1;
		$this->extension->protected = 0;
		$this->extension->access = 1;
		$this->extension->client_id = 0;
		$this->extension->params = $this->parent->getParams();

		// Custom data
		$this->extension->custom_data = '';
		$this->extension->system_data = '';

		// Update the manifest cache for the entry
		$this->extension->manifest_cache =
$this->parent->generateManifestCache();

		if (!$this->extension->store())
		{
			// Install failed, roll back changes
			throw new \RuntimeException(
				\JText::sprintf(
					'JLIB_INSTALLER_ABORT_LIB_INSTALL_ROLLBACK',
					$this->extension->getError()
				)
			);
		}

		// Since we have created a library item, we add it to the installation
step stack
		// so that if we have to rollback the changes we can undo it.
		$this->parent->pushStep(array('type' =>
'extension', 'id' =>
$this->extension->extension_id));
	}

	/**
	 * Custom uninstall method
	 *
	 * @param   string  $id  The id of the library to uninstall.
	 *
	 * @return  boolean  True on success
	 *
	 * @since   3.1
	 */
	public function uninstall($id)
	{
		$retval = true;

		// First order of business will be to load the module object table from
the database.
		// This should give us the necessary information to proceed.
		$row = Table::getInstance('extension');

		if (!$row->load((int) $id) || $row->element === '')
		{
			\JLog::add(\JText::_('ERRORUNKOWNEXTENSION'), \JLog::WARNING,
'jerror');

			return false;
		}

		// Is the library we are trying to uninstall a core one?
		// Because that is not a good idea...
		if ($row->protected)
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_LIB_UNINSTALL_WARNCORELIBRARY'),
\JLog::WARNING, 'jerror');

			return false;
		}

		/*
		 * Does this extension have a parent package?
		 * If so, check if the package disallows individual extensions being
uninstalled if the package is not being uninstalled
		 */
		if ($row->package_id &&
!$this->parent->isPackageUninstall() &&
!$this->canUninstallPackageChild($row->package_id))
		{
			\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_CANNOT_UNINSTALL_CHILD_OF_PACKAGE',
$row->name), \JLog::WARNING, 'jerror');

			return false;
		}

		$manifestFile = JPATH_MANIFESTS . '/libraries/' .
$row->element . '.xml';

		// Because libraries may not have their own folders we cannot use the
standard method of finding an installation manifest
		if (file_exists($manifestFile))
		{
			$manifest = new LibraryManifest($manifestFile);

			// Set the library root path
			$this->parent->setPath('extension_root', JPATH_PLATFORM
. '/' . $manifest->libraryname);

			$xml = simplexml_load_file($manifestFile);

			// If we cannot load the XML file return null
			if (!$xml)
			{
				\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_LIB_UNINSTALL_LOAD_MANIFEST'),
\JLog::WARNING, 'jerror');

				return false;
			}

			// Check for a valid XML root tag.
			if ($xml->getName() !== 'extension')
			{
				\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_LIB_UNINSTALL_INVALID_MANIFEST'),
\JLog::WARNING, 'jerror');

				return false;
			}

			$this->parent->removeFiles($xml->files, -1);
			\JFile::delete($manifestFile);
		}
		else
		{
			// Remove this row entry since its invalid
			$row->delete($row->extension_id);
			unset($row);
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_LIB_UNINSTALL_INVALID_NOTFOUND_MANIFEST'),
\JLog::WARNING, 'jerror');

			return false;
		}

		// TODO: Change this so it walked up the path backwards so we clobber
multiple empties
		// If the folder is empty, let's delete it
		if
(\JFolder::exists($this->parent->getPath('extension_root')))
		{
			if (is_dir($this->parent->getPath('extension_root')))
			{
				$files =
\JFolder::files($this->parent->getPath('extension_root'));

				if (!count($files))
				{
					\JFolder::delete($this->parent->getPath('extension_root'));
				}
			}
		}

		$this->parent->removeFiles($xml->media);
		$this->parent->removeFiles($xml->languages);

		$elementParts = explode('/', $row->element);

		// Delete empty vendor folders
		if (2 === count($elementParts))
		{
			@rmdir(JPATH_MANIFESTS . '/libraries/' . $elementParts[0]);
			@rmdir(JPATH_PLATFORM . '/' . $elementParts[0]);
		}

		$row->delete($row->extension_id);
		unset($row);

		return $retval;
	}

	/**
	 * Custom discover method
	 *
	 * @return  array  Extension  list of extensions available
	 *
	 * @since   3.1
	 */
	public function discover()
	{
		$results = array();


		$mainFolder = JPATH_MANIFESTS . '/libraries';
		$folder = new \RecursiveDirectoryIterator($mainFolder);
		$iterator = new \RegexIterator(
			new \RecursiveIteratorIterator($folder),
			'/\.xml$/i',
			\RecursiveRegexIterator::GET_MATCH
		);

		foreach ($iterator as $file => $pattern)
		{
			$element = str_replace(array($mainFolder . DIRECTORY_SEPARATOR,
'.xml'), '', $file);
			$manifestCache = Installer::parseXMLInstallFile($file);

			$extension = Table::getInstance('extension');
			$extension->set('type', 'library');
			$extension->set('client_id', 0);
			$extension->set('element', $element);
			$extension->set('folder', '');
			$extension->set('name', $element);
			$extension->set('state', -1);
			$extension->set('manifest_cache',
json_encode($manifestCache));
			$extension->set('params', '{}');
			$results[] = $extension;
		}

		return $results;
	}

	/**
	 * Refreshes the extension table cache
	 *
	 * @return  boolean  Result of operation, true if updated, false on
failure
	 *
	 * @since   3.1
	 */
	public function refreshManifestCache()
	{
		// Need to find to find where the XML file is since we don't store
this normally
		$manifestPath = JPATH_MANIFESTS . '/libraries/' .
$this->parent->extension->element . '.xml';
		$this->parent->manifest =
$this->parent->isManifest($manifestPath);
		$this->parent->setPath('manifest', $manifestPath);

		$manifest_details =
Installer::parseXMLInstallFile($this->parent->getPath('manifest'));
		$this->parent->extension->manifest_cache =
json_encode($manifest_details);
		$this->parent->extension->name =
$manifest_details['name'];

		try
		{
			return $this->parent->extension->store();
		}
		catch (\RuntimeException $e)
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_LIB_REFRESH_MANIFEST_CACHE'),
\JLog::WARNING, 'jerror');

			return false;
		}
	}
}
PKK�[Mm��QQAdapter/ModuleAdapter.phpnu�[���<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
 * @license    GNU General Public License version 2 or later; see
LICENSE.txt
 */

namespace Joomla\CMS\Installer\Adapter;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Installer\Installer;
use Joomla\CMS\Installer\InstallerAdapter;
use Joomla\CMS\Table\Table;
use Joomla\Utilities\ArrayHelper;

\JLoader::import('joomla.filesystem.folder');

/**
 * Module installer
 *
 * @since  3.1
 */
class ModuleAdapter extends InstallerAdapter
{
	/**
	 * The install client ID
	 *
	 * @var    integer
	 * @since  3.4
	 */
	protected $clientId;

	/**
	 * `<scriptfile>` element of the extension manifest
	 *
	 * @var    object
	 * @since  3.1
	 */
	protected $scriptElement = null;

	/**
	 * Method to check if the extension is already present in the database
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function checkExistingExtension()
	{
		try
		{
			$this->currentExtensionId = $this->extension->find(
				array(
					'element'   => $this->element,
					'type'      => $this->type,
					'client_id' => $this->clientId,
				)
			);
		}
		catch (\RuntimeException $e)
		{
			// Install failed, roll back changes
			throw new \RuntimeException(
				\JText::sprintf(
					'JLIB_INSTALLER_ABORT_ROLLBACK',
					\JText::_('JLIB_INSTALLER_' . $this->route),
					$e->getMessage()
				),
				$e->getCode(),
				$e
			);
		}
	}

	/**
	 * Method to copy the extension's base files from the `<files>`
tag(s) and the manifest file
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function copyBaseFiles()
	{
		// Copy all necessary files
		if ($this->parent->parseFiles($this->getManifest()->files,
-1) === false)
		{
			throw new
\RuntimeException(\JText::_('JLIB_INSTALLER_ABORT_MOD_COPY_FILES'));
		}

		// If there is a manifest script, let's copy it.
		if ($this->manifest_script)
		{
			$path['src']  =
$this->parent->getPath('source') . '/' .
$this->manifest_script;
			$path['dest'] =
$this->parent->getPath('extension_root') . '/' .
$this->manifest_script;

			if ($this->parent->isOverwrite() ||
!file_exists($path['dest']))
			{
				if (!$this->parent->copyFiles(array($path)))
				{
					// Install failed, rollback changes
					throw new
\RuntimeException(\JText::_('JLIB_INSTALLER_ABORT_MOD_INSTALL_MANIFEST'));
				}
			}
		}
	}

	/**
	 * Method to finalise the installation processing
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function finaliseInstall()
	{
		// Clobber any possible pending updates
		$update = Table::getInstance('update');
		$uid    = $update->find(
			array(
				'element'   => $this->element,
				'type'      => 'module',
				'client_id' => $this->clientId,
			)
		);

		if ($uid)
		{
			$update->delete($uid);
		}

		// Lastly, we will copy the manifest file to its appropriate place.
		if ($this->route !== 'discover_install')
		{
			if (!$this->parent->copyManifest(-1))
			{
				// Install failed, rollback changes
				throw new
\RuntimeException(\JText::_('JLIB_INSTALLER_ABORT_MOD_INSTALL_COPY_SETUP'));
			}
		}
	}

	/**
	 * Get the filtered extension element from the manifest
	 *
	 * @param   string  $element  Optional element name to be converted
	 *
	 * @return  string  The filtered element
	 *
	 * @since   3.4
	 */
	public function getElement($element = null)
	{
		if (!$element)
		{
			if (count($this->getManifest()->files->children()))
			{
				foreach ($this->getManifest()->files->children() as $file)
				{
					if ((string) $file->attributes()->module)
					{
						$element = strtolower((string) $file->attributes()->module);

						break;
					}
				}
			}
		}

		return $element;
	}

	/**
	 * Custom loadLanguage method
	 *
	 * @param   string  $path  The path where we find language files
	 *
	 * @return  void
	 *
	 * @since   3.4
	 */
	public function loadLanguage($path = null)
	{
		$source = $this->parent->getPath('source');
		$client = $this->parent->extension->client_id ?
JPATH_ADMINISTRATOR : JPATH_SITE;

		if (!$source)
		{
			$this->parent->setPath('source', $client .
'/modules/' . $this->parent->extension->element);
		}

		$this->setManifest($this->parent->getManifest());

		if ($this->getManifest()->files)
		{
			$extension = $this->getElement();

			if ($extension)
			{
				$source = $path ?: ($this->parent->extension->client_id ?
JPATH_ADMINISTRATOR : JPATH_SITE) . '/modules/' . $extension;
				$folder = (string)
$this->getManifest()->files->attributes()->folder;

				if ($folder && file_exists($path . '/' . $folder))
				{
					$source = $path . '/' . $folder;
				}

				$client = (string) $this->getManifest()->attributes()->client;
				$this->doLoadLanguage($extension, $source,
constant('JPATH_' . strtoupper($client)));
			}
		}
	}

	/**
	 * Method to parse optional tags in the manifest
	 *
	 * @return  void
	 *
	 * @since   3.4
	 */
	protected function parseOptionalTags()
	{
		// Parse optional tags
		$this->parent->parseMedia($this->getManifest()->media,
$this->clientId);
		$this->parent->parseLanguages($this->getManifest()->languages,
$this->clientId);
	}

	/**
	 * Prepares the adapter for a discover_install task
	 *
	 * @return  void
	 *
	 * @since   3.4
	 */
	public function prepareDiscoverInstall()
	{
		$client =
ApplicationHelper::getClientInfo($this->parent->extension->client_id);
		$manifestPath = $client->path . '/modules/' .
$this->parent->extension->element . '/' .
$this->parent->extension->element . '.xml';
		$this->parent->manifest =
$this->parent->isManifest($manifestPath);
		$this->parent->setPath('manifest', $manifestPath);
		$this->setManifest($this->parent->getManifest());
	}

	/**
	 * Method to do any prechecks and setup the install paths for the
extension
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function setupInstallPaths()
	{
		// Get the target application
		$cname = (string) $this->getManifest()->attributes()->client;

		if ($cname)
		{
			// Attempt to map the client to a base path
			$client = ApplicationHelper::getClientInfo($cname, true);

			if ($client === false)
			{
				throw new \RuntimeException(
					\JText::sprintf(
						'JLIB_INSTALLER_ABORT_MOD_UNKNOWN_CLIENT',
						\JText::_('JLIB_INSTALLER_' . $this->route),
						$client->name
					)
				);
			}

			$basePath = $client->path;
			$this->clientId = $client->id;
		}
		else
		{
			// No client attribute was found so we assume the site as the client
			$basePath = JPATH_SITE;
			$this->clientId = 0;
		}

		// Set the installation path
		if (empty($this->element))
		{
			throw new \RuntimeException(
				\JText::sprintf(
					'JLIB_INSTALLER_ABORT_MOD_INSTALL_NOFILE',
					\JText::_('JLIB_INSTALLER_' . $this->route)
				)
			);
		}

		$this->parent->setPath('extension_root', $basePath .
'/modules/' . $this->element);
	}

	/**
	 * Method to store the extension to the database
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function storeExtension()
	{
		// Discover installs are stored a little differently
		if ($this->route === 'discover_install')
		{
			$manifest_details =
Installer::parseXMLInstallFile($this->parent->getPath('manifest'));

			$this->extension->manifest_cache = json_encode($manifest_details);
			$this->extension->state = 0;
			$this->extension->name = $manifest_details['name'];
			$this->extension->enabled = 1;
			$this->extension->params = $this->parent->getParams();

			if (!$this->extension->store())
			{
				// Install failed, roll back changes
				throw new
\RuntimeException(\JText::_('JLIB_INSTALLER_ERROR_MOD_DISCOVER_STORE_DETAILS'));
			}

			return;
		}

		// Was there a module already installed with the same name?
		if ($this->currentExtensionId)
		{
			if (!$this->parent->isOverwrite())
			{
				// Install failed, roll back changes
				throw new \RuntimeException(
					\JText::sprintf(
						'JLIB_INSTALLER_ABORT_MOD_INSTALL_ALLREADY_EXISTS',
						\JText::_('JLIB_INSTALLER_' . $this->route),
						$this->name
					)
				);
			}

			// Load the entry and update the manifest_cache
			$this->extension->load($this->currentExtensionId);

			// Update name
			$this->extension->name = $this->name;

			// Update manifest
			$this->extension->manifest_cache =
$this->parent->generateManifestCache();

			if (!$this->extension->store())
			{
				// Install failed, roll back changes
				throw new \RuntimeException(
					\JText::sprintf(
						'JLIB_INSTALLER_ABORT_MOD_ROLLBACK',
						\JText::_('JLIB_INSTALLER_' . $this->route),
						$this->extension->getError()
					)
				);
			}
		}
		else
		{
			$this->extension->name    = $this->name;
			$this->extension->type    = 'module';
			$this->extension->element = $this->element;

			// There is no folder for modules
			$this->extension->folder    = '';
			$this->extension->enabled   = 1;
			$this->extension->protected = 0;
			$this->extension->access    = $this->clientId == 1 ? 2 : 0;
			$this->extension->client_id = $this->clientId;
			$this->extension->params    = $this->parent->getParams();

			// Custom data
			$this->extension->custom_data    = '';
			$this->extension->system_data    = '';
			$this->extension->manifest_cache =
$this->parent->generateManifestCache();

			if (!$this->extension->store())
			{
				// Install failed, roll back changes
				throw new \RuntimeException(
					\JText::sprintf(
						'JLIB_INSTALLER_ABORT_MOD_ROLLBACK',
						\JText::_('JLIB_INSTALLER_' . $this->route),
						$this->extension->getError()
					)
				);
			}

			// Since we have created a module item, we add it to the installation
step stack
			// so that if we have to rollback the changes we can undo it.
			$this->parent->pushStep(
				array(
					'type' => 'extension',
					'extension_id' => $this->extension->extension_id,
				)
			);

			// Create unpublished module
			$name = preg_replace('#[\*?]#', '',
\JText::_($this->name));

			/** @var \JTableModule $module */
			$module            = Table::getInstance('module');
			$module->title     = $name;
			$module->content   = '';
			$module->module    = $this->element;
			$module->access    = '1';
			$module->showtitle = '1';
			$module->params    = '';
			$module->client_id = $this->clientId;
			$module->language  = '*';

			$module->store();
		}
	}

	/**
	 * Custom discover method
	 *
	 * @return  array  Extension list of extensions available
	 *
	 * @since   3.1
	 */
	public function discover()
	{
		$results = array();
		$site_list = \JFolder::folders(JPATH_SITE . '/modules');
		$admin_list = \JFolder::folders(JPATH_ADMINISTRATOR .
'/modules');
		$site_info = ApplicationHelper::getClientInfo('site', true);
		$admin_info = ApplicationHelper::getClientInfo('administrator',
true);

		foreach ($site_list as $module)
		{
			if (file_exists(JPATH_SITE . "/modules/$module/$module.xml"))
			{
				$manifest_details = Installer::parseXMLInstallFile(JPATH_SITE .
"/modules/$module/$module.xml");
				$extension = Table::getInstance('extension');
				$extension->set('type', 'module');
				$extension->set('client_id', $site_info->id);
				$extension->set('element', $module);
				$extension->set('folder', '');
				$extension->set('name', $module);
				$extension->set('state', -1);
				$extension->set('manifest_cache',
json_encode($manifest_details));
				$extension->set('params', '{}');
				$results[] = clone $extension;
			}
		}

		foreach ($admin_list as $module)
		{
			if (file_exists(JPATH_ADMINISTRATOR .
"/modules/$module/$module.xml"))
			{
				$manifest_details = Installer::parseXMLInstallFile(JPATH_ADMINISTRATOR
. "/modules/$module/$module.xml");
				$extension = Table::getInstance('extension');
				$extension->set('type', 'module');
				$extension->set('client_id', $admin_info->id);
				$extension->set('element', $module);
				$extension->set('folder', '');
				$extension->set('name', $module);
				$extension->set('state', -1);
				$extension->set('manifest_cache',
json_encode($manifest_details));
				$extension->set('params', '{}');
				$results[] = clone $extension;
			}
		}

		return $results;
	}

	/**
	 * Refreshes the extension table cache
	 *
	 * @return  boolean  Result of operation, true if updated, false on
failure.
	 *
	 * @since   3.1
	 */
	public function refreshManifestCache()
	{
		$client =
ApplicationHelper::getClientInfo($this->parent->extension->client_id);
		$manifestPath = $client->path . '/modules/' .
$this->parent->extension->element . '/' .
$this->parent->extension->element . '.xml';
		$this->parent->manifest =
$this->parent->isManifest($manifestPath);
		$this->parent->setPath('manifest', $manifestPath);
		$manifest_details =
Installer::parseXMLInstallFile($this->parent->getPath('manifest'));
		$this->parent->extension->manifest_cache =
json_encode($manifest_details);
		$this->parent->extension->name =
$manifest_details['name'];

		if ($this->parent->extension->store())
		{
			return true;
		}
		else
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_MOD_REFRESH_MANIFEST_CACHE'),
\JLog::WARNING, 'jerror');

			return false;
		}
	}

	/**
	 * Custom uninstall method
	 *
	 * @param   integer  $id  The id of the module to uninstall
	 *
	 * @return  boolean  True on success
	 *
	 * @since   3.1
	 */
	public function uninstall($id)
	{
		$retval = true;
		$db     = $this->db;

		// First order of business will be to load the module object table from
the database.
		// This should give us the necessary information to proceed.
		if (!$this->extension->load((int) $id) ||
$this->extension->element === '')
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_MOD_UNINSTALL_ERRORUNKOWNEXTENSION'),
\JLog::WARNING, 'jerror');

			return false;
		}

		// Is the module we are trying to uninstall a core one?
		// Because that is not a good idea...
		if ($this->extension->protected)
		{
			\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_MOD_UNINSTALL_WARNCOREMODULE',
$this->extension->name), \JLog::WARNING, 'jerror');

			return false;
		}

		/*
		 * Does this extension have a parent package?
		 * If so, check if the package disallows individual extensions being
uninstalled if the package is not being uninstalled
		 */
		if ($this->extension->package_id &&
!$this->parent->isPackageUninstall() &&
!$this->canUninstallPackageChild($this->extension->package_id))
		{
			\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_CANNOT_UNINSTALL_CHILD_OF_PACKAGE',
$this->extension->name), \JLog::WARNING, 'jerror');

			return false;
		}

		// Get the extension root path
		$element = $this->extension->element;
		$client  =
ApplicationHelper::getClientInfo($this->extension->client_id);

		if ($client === false)
		{
			$this->parent->abort(
				\JText::sprintf(
					'JLIB_INSTALLER_ERROR_MOD_UNINSTALL_UNKNOWN_CLIENT',
					$this->extension->client_id
				)
			);

			return false;
		}

		$this->parent->setPath('extension_root', $client->path
. '/modules/' . $element);

		$this->parent->setPath('source',
$this->parent->getPath('extension_root'));

		// Get the module's manifest object
		// We do findManifest to avoid problem when uninstalling a list of
extensions: getManifest cache its manifest file.
		$this->parent->findManifest();
		$this->setManifest($this->parent->getManifest());

		// Attempt to load the language file; might have uninstall strings
		$this->loadLanguage(($this->extension->client_id ?
JPATH_ADMINISTRATOR : JPATH_SITE) . '/modules/' . $element);

		// If there is a manifest class file, let's load it
		$this->scriptElement = $this->getManifest()->scriptfile;
		$manifestScript      = (string) $this->getManifest()->scriptfile;

		if ($manifestScript)
		{
			$manifestScriptFile =
$this->parent->getPath('extension_root') . '/' .
$manifestScript;

			// Set the class name
			$classname = $element . 'InstallerScript';

			\JLoader::register($classname, $manifestScriptFile);

			if (class_exists($classname))
			{
				// Create a new instance
				$this->parent->manifestClass = new $classname($this);

				// And set this so we can copy it later
				$this->set('manifest_script', $manifestScript);
			}
		}

		try
		{
			$this->triggerManifestScript('uninstall');
		}
		catch (\RuntimeException $e)
		{
			// Ignore errors for now
		}

		if (!($this->getManifest() instanceof \SimpleXMLElement))
		{
			// Make sure we delete the folders
			\JFolder::delete($this->parent->getPath('extension_root'));
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_MOD_UNINSTALL_INVALID_NOTFOUND_MANIFEST'),
\JLog::WARNING, 'jerror');

			return false;
		}

		// Let's run the uninstall queries for the module
		try
		{
			$this->parseQueries();
		}
		catch (\RuntimeException $e)
		{
			// Install failed, rollback changes
			\JLog::add($e->getMessage(), \JLog::WARNING, 'jerror');
			$retval = false;
		}

		// Remove the schema version
		$query = $db->getQuery(true)
			->delete('#__schemas')
			->where('extension_id = ' .
$this->extension->extension_id);
		$db->setQuery($query);
		$db->execute();

		// Remove other files
		$this->parent->removeFiles($this->getManifest()->media);
		$this->parent->removeFiles($this->getManifest()->languages,
$this->extension->client_id);

		// Let's delete all the module copies for the type we are
uninstalling
		$query->clear()
			->select($db->quoteName('id'))
			->from($db->quoteName('#__modules'))
			->where($db->quoteName('module') . ' = ' .
$db->quote($this->extension->element))
			->where($db->quoteName('client_id') . ' = ' .
(int) $this->extension->client_id);
		$db->setQuery($query);

		try
		{
			$modules = $db->loadColumn();
		}
		catch (\RuntimeException $e)
		{
			$modules = array();
		}

		// Do we have any module copies?
		if (count($modules))
		{
			// Ensure the list is sane
			$modules = ArrayHelper::toInteger($modules);
			$modID = implode(',', $modules);

			// Wipe out any items assigned to menus
			$query = $db->getQuery(true)
				->delete($db->quoteName('#__modules_menu'))
				->where($db->quoteName('moduleid') . ' IN ('
. $modID . ')');
			$db->setQuery($query);

			try
			{
				$db->execute();
			}
			catch (\RuntimeException $e)
			{
				\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_MOD_UNINSTALL_EXCEPTION',
$db->stderr(true)), \JLog::WARNING, 'jerror');
				$retval = false;
			}

			// Wipe out any instances in the modules table
			/** @var \JTableModule $module */
			$module = Table::getInstance('Module');

			foreach ($modules as $modInstanceId)
			{
				$module->load($modInstanceId);

				if (!$module->delete())
				{
					\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_MOD_UNINSTALL_EXCEPTION',
$module->getError()), \JLog::WARNING, 'jerror');
					$retval = false;
				}
			}
		}

		// Now we will no longer need the module object, so let's delete it
and free up memory
		$this->extension->delete($this->extension->extension_id);
		$query = $db->getQuery(true)
			->delete($db->quoteName('#__modules'))
			->where($db->quoteName('module') . ' = ' .
$db->quote($this->extension->element))
			->where($db->quoteName('client_id') . ' = ' .
(int) $this->extension->client_id);
		$db->setQuery($query);

		try
		{
			// Clean up any other ones that might exist as well
			$db->execute();
		}
		catch (\RuntimeException $e)
		{
			// Ignore the error...
		}

		// Remove the installation folder
		if
(!\JFolder::delete($this->parent->getPath('extension_root')))
		{
			// \JFolder should raise an error
			$retval = false;
		}

		return $retval;
	}

	/**
	 * Custom rollback method
	 * - Roll back the menu item
	 *
	 * @param   array  $arg  Installation step to rollback
	 *
	 * @return  boolean  True on success
	 *
	 * @since   3.1
	 */
	protected function _rollback_menu($arg)
	{
		// Get database connector object
		$db = $this->parent->getDbo();

		// Remove the entry from the #__modules_menu table
		$query = $db->getQuery(true)
			->delete($db->quoteName('#__modules_menu'))
			->where($db->quoteName('moduleid') . ' = ' .
(int) $arg['id']);
		$db->setQuery($query);

		try
		{
			return $db->execute();
		}
		catch (\RuntimeException $e)
		{
			return false;
		}
	}

	/**
	 * Custom rollback method
	 * - Roll back the module item
	 *
	 * @param   array  $arg  Installation step to rollback
	 *
	 * @return  boolean  True on success
	 *
	 * @since   3.1
	 */
	protected function _rollback_module($arg)
	{
		// Get database connector object
		$db = $this->parent->getDbo();

		// Remove the entry from the #__modules table
		$query = $db->getQuery(true)
			->delete($db->quoteName('#__modules'))
			->where($db->quoteName('id') . ' = ' . (int)
$arg['id']);
		$db->setQuery($query);

		try
		{
			return $db->execute();
		}
		catch (\RuntimeException $e)
		{
			return false;
		}
	}
}
PKK�[Ʒ�9NNAdapter/PackageAdapter.phpnu�[���<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
 * @license    GNU General Public License version 2 or later; see
LICENSE.txt
 */

namespace Joomla\CMS\Installer\Adapter;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Installer\Installer;
use Joomla\CMS\Installer\InstallerAdapter;
use Joomla\CMS\Installer\InstallerHelper;
use Joomla\CMS\Installer\Manifest\PackageManifest;
use Joomla\CMS\Table\Table;
use Joomla\CMS\Table\Update;

/**
 * Package installer
 *
 * @since  3.1
 */
class PackageAdapter extends InstallerAdapter
{
	/**
	 * Flag if the internal event callback has been registered
	 *
	 * @var    boolean
	 * @since  3.7.0
	 */
	private static $eventRegistered = false;

	/**
	 * An array of extension IDs for each installed extension
	 *
	 * @var    array
	 * @since  3.7.0
	 */
	protected $installedIds = array();

	/**
	 * The results of each installed extensions
	 *
	 * @var    array
	 * @since  3.1
	 */
	protected $results = array();

	/**
	 * Flag if the adapter supports discover installs
	 *
	 * Adapters should override this and set to false if discover install is
unsupported
	 *
	 * @var    boolean
	 * @since  3.4
	 */
	protected $supportsDiscoverInstall = false;

	/**
	 * Method to check if the extension is present in the filesystem, flags
the route as update if so
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function checkExtensionInFilesystem()
	{
		// If the package manifest already exists, then we will assume that the
package is already installed.
		if (file_exists(JPATH_MANIFESTS . '/packages/' .
basename($this->parent->getPath('manifest'))))
		{
			// Look for an update function or update tag
			$updateElement = $this->manifest->update;

			// Upgrade manually set or update function available or update tag
detected
			if ($updateElement || $this->parent->isUpgrade()
				|| ($this->parent->manifestClass &&
method_exists($this->parent->manifestClass, 'update')))
			{
				// Force this one
				$this->parent->setOverwrite(true);
				$this->parent->setUpgrade(true);

				if ($this->currentExtensionId)
				{
					// If there is a matching extension mark this as an update
					$this->setRoute('update');
				}
			}
			elseif (!$this->parent->isOverwrite())
			{
				// We didn't have overwrite set, find an update function or find
an update tag so lets call it safe
				throw new \RuntimeException(
					\JText::sprintf(
						'JLIB_INSTALLER_ABORT_DIRECTORY',
						\JText::_('JLIB_INSTALLER_' . $this->route),
						$this->type,
						$this->parent->getPath('extension_root')
					)
				);
			}
		}
	}

	/**
	 * Method to copy the extension's base files from the `<files>`
tag(s) and the manifest file
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function copyBaseFiles()
	{
		$folder = (string)
$this->getManifest()->files->attributes()->folder;
		$source = $this->parent->getPath('source');

		if ($folder)
		{
			$source .= '/' . $folder;
		}

		// Install all necessary files
		if (!count($this->getManifest()->files->children()))
		{
			throw new \RuntimeException(
				\JText::sprintf('JLIB_INSTALLER_ABORT_PACK_INSTALL_NO_FILES',
					\JText::_('JLIB_INSTALLER_' . strtoupper($this->route))
				)
			);
		}

		// Add a callback for the `onExtensionAfterInstall` event so we can
receive the installed extension ID
		if (!self::$eventRegistered)
		{
			self::$eventRegistered = true;
			\JEventDispatcher::getInstance()->register('onExtensionAfterInstall',
array($this, 'onExtensionAfterInstall'));
		}

		foreach ($this->getManifest()->files->children() as $child)
		{
			$file = $source . '/' . (string) $child;

			if (is_dir($file))
			{
				// If it's actually a directory then fill it up
				$package = array();
				$package['dir'] = $file;
				$package['type'] = InstallerHelper::detectType($file);
			}
			else
			{
				// If it's an archive
				$package = InstallerHelper::unpack($file);
			}

			$tmpInstaller  = new Installer;
			$installResult = $tmpInstaller->install($package['dir']);

			if (!$installResult)
			{
				throw new \RuntimeException(
					\JText::sprintf(
						'JLIB_INSTALLER_ABORT_PACK_INSTALL_ERROR_EXTENSION',
						\JText::_('JLIB_INSTALLER_' . strtoupper($this->route)),
						basename($file)
					)
				);
			}

			$this->results[] = array(
				'name'   => (string) $tmpInstaller->manifest->name,
				'result' => $installResult,
			);
		}
	}

	/**
	 * Method to create the extension root path if necessary
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function createExtensionRoot()
	{
		/*
		 * For packages, we only need the extension root if copying manifest
files; this step will be handled
		 * at that point if necessary
		 */
	}

	/**
	 * Method to finalise the installation processing
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function finaliseInstall()
	{
		// Clobber any possible pending updates
		/** @var Update $update */
		$update = Table::getInstance('update');
		$uid = $update->find(
			array(
				'element' => $this->element,
				'type' => $this->type,
			)
		);

		if ($uid)
		{
			$update->delete($uid);
		}

		// Set the package ID for each of the installed extensions to track the
relationship
		if (!empty($this->installedIds))
		{
			$db = $this->db;
			$query = $db->getQuery(true)
				->update('#__extensions')
				->set($db->quoteName('package_id') . ' = ' .
(int) $this->extension->extension_id)
				->where($db->quoteName('extension_id') . ' IN
(' . implode(', ', $this->installedIds) . ')');

			try
			{
				$db->setQuery($query)->execute();
			}
			catch (\JDatabaseExceptionExecuting $e)
			{
				\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_PACK_SETTING_PACKAGE_ID'),
\JLog::WARNING, 'jerror');
			}
		}

		// Lastly, we will copy the manifest file to its appropriate place.
		$manifest = array();
		$manifest['src'] =
$this->parent->getPath('manifest');
		$manifest['dest'] = JPATH_MANIFESTS . '/packages/' .
basename($this->parent->getPath('manifest'));

		if (!$this->parent->copyFiles(array($manifest), true))
		{
			// Install failed, rollback changes
			throw new \RuntimeException(
				\JText::sprintf(
					'JLIB_INSTALLER_ABORT_PACK_INSTALL_COPY_SETUP',
					\JText::_('JLIB_INSTALLER_ABORT_PACK_INSTALL_NO_FILES')
				)
			);
		}

		// If there is a manifest script, let's copy it.
		if ($this->manifest_script)
		{
			// First, we have to create a folder for the script if one isn't
present
			if
(!file_exists($this->parent->getPath('extension_root')))
			{
				if
(!\JFolder::create($this->parent->getPath('extension_root')))
				{
					throw new \RuntimeException(
						\JText::sprintf(
							'JLIB_INSTALLER_ABORT_CREATE_DIRECTORY',
							\JText::_('JLIB_INSTALLER_' . $this->route),
							$this->parent->getPath('extension_root')
						)
					);
				}

				/*
				 * Since we created the extension directory and will want to remove it
if
				 * we have to roll back the installation, let's add it to the
				 * installation step stack
				 */

				$this->parent->pushStep(
					array(
						'type' => 'folder',
						'path' =>
$this->parent->getPath('extension_root'),
					)
				);
			}

			$path['src'] =
$this->parent->getPath('source') . '/' .
$this->manifest_script;
			$path['dest'] =
$this->parent->getPath('extension_root') . '/' .
$this->manifest_script;

			if ($this->parent->isOverwrite() ||
!file_exists($path['dest']))
			{
				if (!$this->parent->copyFiles(array($path)))
				{
					// Install failed, rollback changes
					throw new
\RuntimeException(\JText::_('JLIB_INSTALLER_ABORT_PACKAGE_INSTALL_MANIFEST'));
				}
			}
		}
	}

	/**
	 * Get the filtered extension element from the manifest
	 *
	 * @param   string  $element  Optional element name to be converted
	 *
	 * @return  string  The filtered element
	 *
	 * @since   3.4
	 */
	public function getElement($element = null)
	{
		if (!$element)
		{
			// Ensure the element is a string
			$element = (string) $this->getManifest()->packagename;

			// Filter the name for illegal characters
			$element = 'pkg_' .
\JFilterInput::getInstance()->clean($element, 'cmd');
		}

		return $element;
	}

	/**
	 * Load language from a path
	 *
	 * @param   string  $path  The path of the language.
	 *
	 * @return  void
	 *
	 * @since   3.1
	 */
	public function loadLanguage($path)
	{
		$this->doLoadLanguage($this->getElement(), $path);
	}

	/**
	 * Handler for the `onExtensionAfterInstall` event
	 *
	 * @param   Installer        $installer  Installer instance managing the
extension's installation
	 * @param   integer|boolean  $eid        The extension ID of the installed
extension on success, boolean false on install failure
	 *
	 * @return  void
	 *
	 * @since   3.7.0
	 */
	public function onExtensionAfterInstall(Installer $installer, $eid)
	{
		if ($eid !== false)
		{
			$this->installedIds[] = $eid;
		}
	}

	/**
	 * Method to parse optional tags in the manifest
	 *
	 * @return  void
	 *
	 * @since   3.4
	 */
	protected function parseOptionalTags()
	{
		$this->parent->parseLanguages($this->getManifest()->languages);
	}

	/**
	 * Method to do any prechecks and setup the install paths for the
extension
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function setupInstallPaths()
	{
		$packagepath = (string) $this->getManifest()->packagename;

		if (empty($packagepath))
		{
			throw new \RuntimeException(
				\JText::sprintf(
					'JLIB_INSTALLER_ABORT_PACK_INSTALL_NO_PACK',
					\JText::_('JLIB_INSTALLER_' . strtoupper($this->route))
				)
			);
		}

		$this->parent->setPath('extension_root', JPATH_MANIFESTS
. '/packages/' . $packagepath);
	}

	/**
	 * Method to store the extension to the database
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function storeExtension()
	{
		if ($this->currentExtensionId)
		{
			if (!$this->parent->isOverwrite())
			{
				// Install failed, roll back changes
				throw new \RuntimeException(
					\JText::sprintf(
						'JLIB_INSTALLER_ABORT_ALREADY_EXISTS',
						\JText::_('JLIB_INSTALLER_' . $this->route),
						$this->name
					)
				);
			}

			$this->extension->load($this->currentExtensionId);
			$this->extension->name = $this->name;
		}
		else
		{
			$this->extension->name = $this->name;
			$this->extension->type = 'package';
			$this->extension->element = $this->element;

			// There is no folder for packages
			$this->extension->folder = '';
			$this->extension->enabled = 1;
			$this->extension->protected = 0;
			$this->extension->access = 1;
			$this->extension->client_id = 0;

			// Custom data
			$this->extension->custom_data = '';
			$this->extension->system_data = '';
			$this->extension->params = $this->parent->getParams();
		}

		// Update the manifest cache for the entry
		$this->extension->manifest_cache =
$this->parent->generateManifestCache();

		if (!$this->extension->store())
		{
			// Install failed, roll back changes
			throw new \RuntimeException(
				\JText::sprintf(
					'JLIB_INSTALLER_ABORT_PACK_INSTALL_ROLLBACK',
					$this->extension->getError()
				)
			);
		}

		// Since we have created a package item, we add it to the installation
step stack
		// so that if we have to rollback the changes we can undo it.
		$this->parent->pushStep(array('type' =>
'extension', 'id' =>
$this->extension->extension_id));
	}

	/**
	 * Executes a custom install script method
	 *
	 * @param   string  $method  The install method to execute
	 *
	 * @return  boolean  True on success
	 *
	 * @since   3.4
	 */
	protected function triggerManifestScript($method)
	{
		ob_start();
		ob_implicit_flush(false);

		if ($this->parent->manifestClass &&
method_exists($this->parent->manifestClass, $method))
		{
			switch ($method)
			{
				// The preflight method takes the route as a param
				case 'preflight':
					if ($this->parent->manifestClass->$method($this->route,
$this) === false)
					{
						// The script failed, rollback changes
						throw new \RuntimeException(
							\JText::sprintf(
								'JLIB_INSTALLER_ABORT_INSTALL_CUSTOM_INSTALL_FAILURE',
								\JText::_('JLIB_INSTALLER_' . $this->route)
							)
						);
					}

					break;

				// The postflight method takes the route and a results array as params
				case 'postflight':
					$this->parent->manifestClass->$method($this->route, $this,
$this->results);

					break;

				// The install, uninstall, and update methods only pass this object as
a param
				case 'install':
				case 'uninstall':
				case 'update':
					if ($this->parent->manifestClass->$method($this) === false)
					{
						if ($method !== 'uninstall')
						{
							// The script failed, rollback changes
							throw new \RuntimeException(
								\JText::sprintf(
									'JLIB_INSTALLER_ABORT_INSTALL_CUSTOM_INSTALL_FAILURE',
									\JText::_('JLIB_INSTALLER_' . $this->route)
								)
							);
						}
					}

					break;
			}
		}

		// Append to the message object
		$this->extensionMessage .= ob_get_clean();

		// If in postflight or uninstall, set the message for display
		if (($method === 'uninstall' || $method ===
'postflight') && $this->extensionMessage !==
'')
		{
			$this->parent->set('extension_message',
$this->extensionMessage);
		}

		return true;
	}

	/**
	 * Custom uninstall method
	 *
	 * @param   integer  $id  The id of the package to uninstall.
	 *
	 * @return  boolean  True on success
	 *
	 * @since   3.1
	 */
	public function uninstall($id)
	{
		$row = null;
		$retval = true;

		$row = Table::getInstance('extension');
		$row->load($id);

		if ($row->protected)
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_PACK_UNINSTALL_WARNCOREPACK'),
\JLog::WARNING, 'jerror');

			return false;
		}

		/*
		 * Does this extension have a parent package?
		 * If so, check if the package disallows individual extensions being
uninstalled if the package is not being uninstalled
		 */
		if ($row->package_id &&
!$this->parent->isPackageUninstall() &&
!$this->canUninstallPackageChild($row->package_id))
		{
			\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_CANNOT_UNINSTALL_CHILD_OF_PACKAGE',
$row->name), \JLog::WARNING, 'jerror');

			return false;
		}

		$manifestFile = JPATH_MANIFESTS . '/packages/' .
$row->get('element') . '.xml';
		$manifest = new PackageManifest($manifestFile);

		// Set the package root path
		$this->parent->setPath('extension_root', JPATH_MANIFESTS
. '/packages/' . $manifest->packagename);

		// Because packages may not have their own folders we cannot use the
standard method of finding an installation manifest
		if (!file_exists($manifestFile))
		{
			// TODO: Fail?
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_PACK_UNINSTALL_MISSINGMANIFEST'),
\JLog::WARNING, 'jerror');

			return false;
		}

		$xml = simplexml_load_file($manifestFile);

		// If we cannot load the XML file return false
		if (!$xml)
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_PACK_UNINSTALL_LOAD_MANIFEST'),
\JLog::WARNING, 'jerror');

			return false;
		}

		// Check for a valid XML root tag.
		if ($xml->getName() !== 'extension')
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_PACK_UNINSTALL_INVALID_MANIFEST'),
\JLog::WARNING, 'jerror');

			return false;
		}

		// If there is a manifest class file, let's load it
		$manifestScript = (string) $manifest->scriptfile;

		if ($manifestScript)
		{
			$manifestScriptFile =
$this->parent->getPath('extension_root') . '/' .
$manifestScript;

			// Set the class name
			$classname = $row->element . 'InstallerScript';

			\JLoader::register($classname, $manifestScriptFile);

			if (class_exists($classname))
			{
				// Create a new instance
				$this->parent->manifestClass = new $classname($this);

				// And set this so we can copy it later
				$this->set('manifest_script', $manifestScript);
			}
		}

		ob_start();
		ob_implicit_flush(false);

		// Run uninstall if possible
		if ($this->parent->manifestClass &&
method_exists($this->parent->manifestClass, 'uninstall'))
		{
			$this->parent->manifestClass->uninstall($this);
		}

		$msg = ob_get_contents();
		ob_end_clean();

		if ($msg != '')
		{
			$this->parent->set('extension_message', $msg);
		}

		$error = false;

		foreach ($manifest->filelist as $extension)
		{
			$tmpInstaller = new Installer;
			$tmpInstaller->setPackageUninstall(true);

			$id = $this->_getExtensionId($extension->type, $extension->id,
$extension->client, $extension->group);
			$client = ApplicationHelper::getClientInfo($extension->client, true);

			if ($id)
			{
				if (!$tmpInstaller->uninstall($extension->type, $id,
$client->id))
				{
					$error = true;
					\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_PACK_UNINSTALL_NOT_PROPER',
basename($extension->filename)), \JLog::WARNING, 'jerror');
				}
			}
			else
			{
				\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_PACK_UNINSTALL_UNKNOWN_EXTENSION'),
\JLog::WARNING, 'jerror');
			}
		}

		// Remove any language files
		$this->parent->removeFiles($xml->languages);

		// Clean up manifest file after we're done if there were no errors
		if (!$error)
		{
			\JFile::delete($manifestFile);
			$folder = $this->parent->getPath('extension_root');

			if (\JFolder::exists($folder))
			{
				\JFolder::delete($folder);
			}

			$row->delete();
		}
		else
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_PACK_UNINSTALL_MANIFEST_NOT_REMOVED'),
\JLog::WARNING, 'jerror');
		}

		// Return the result up the line
		return $retval;
	}

	/**
	 * Gets the extension id.
	 *
	 * @param   string   $type    The extension type.
	 * @param   string   $id      The name of the extension (the element
field).
	 * @param   integer  $client  The application id (0: Joomla CMS site; 1:
Joomla CMS administrator).
	 * @param   string   $group   The extension group (mainly for plugins).
	 *
	 * @return  integer
	 *
	 * @since   3.1
	 */
	protected function _getExtensionId($type, $id, $client, $group)
	{
		$db = $this->parent->getDbo();

		$query = $db->getQuery(true)
			->select('extension_id')
			->from('#__extensions')
			->where('type = ' . $db->quote($type))
			->where('element = ' . $db->quote($id));

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

			case 'library':
			case 'package':
			case 'component':
				// Components, packages and libraries don't have a folder or
client.
				// Included for completeness.
				break;

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

		$db->setQuery($query);

		// Note: For templates, libraries and packages their unique name is their
key.
		// This means they come out the same way they came in.

		return $db->loadResult();
	}

	/**
	 * Refreshes the extension table cache
	 *
	 * @return  boolean  Result of operation, true if updated, false on
failure
	 *
	 * @since   3.1
	 */
	public function refreshManifestCache()
	{
		// Need to find to find where the XML file is since we don't store
this normally
		$manifestPath = JPATH_MANIFESTS . '/packages/' .
$this->parent->extension->element . '.xml';
		$this->parent->manifest =
$this->parent->isManifest($manifestPath);
		$this->parent->setPath('manifest', $manifestPath);

		$manifest_details =
Installer::parseXMLInstallFile($this->parent->getPath('manifest'));
		$this->parent->extension->manifest_cache =
json_encode($manifest_details);
		$this->parent->extension->name =
$manifest_details['name'];

		try
		{
			return $this->parent->extension->store();
		}
		catch (\RuntimeException $e)
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_PACK_REFRESH_MANIFEST_CACHE'),
\JLog::WARNING, 'jerror');

			return false;
		}
	}
}
PKK�[�Ⱦ�,M,MAdapter/PluginAdapter.phpnu�[���<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
 * @license    GNU General Public License version 2 or later; see
LICENSE.txt
 */

namespace Joomla\CMS\Installer\Adapter;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Installer\Installer;
use Joomla\CMS\Installer\InstallerAdapter;
use Joomla\CMS\Table\Table;
use Joomla\CMS\Table\Update;

\JLoader::import('joomla.filesystem.folder');

/**
 * Plugin installer
 *
 * @since  3.1
 */
class PluginAdapter extends InstallerAdapter
{
	/**
	 * `<scriptfile>` element of the extension manifest
	 *
	 * @var    object
	 * @since  3.1
	 */
	protected $scriptElement = null;

	/**
	 * `<files>` element of the old extension manifest
	 *
	 * @var    object
	 * @since  3.1
	 */
	protected $oldFiles = null;

	/**
	 * Method to check if the extension is already present in the database
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function checkExistingExtension()
	{
		try
		{
			$this->currentExtensionId = $this->extension->find(
				array('type' => $this->type, 'element' =>
$this->element, 'folder' => $this->group)
			);
		}
		catch (\RuntimeException $e)
		{
			// Install failed, roll back changes
			throw new \RuntimeException(
				\JText::sprintf(
					'JLIB_INSTALLER_ABORT_ROLLBACK',
					\JText::_('JLIB_INSTALLER_' . $this->route),
					$e->getMessage()
				),
				$e->getCode(),
				$e
			);
		}
	}

	/**
	 * Method to copy the extension's base files from the `<files>`
tag(s) and the manifest file
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function copyBaseFiles()
	{
		// Copy all necessary files
		if ($this->parent->parseFiles($this->getManifest()->files,
-1, $this->oldFiles) === false)
		{
			throw new \RuntimeException(
				\JText::sprintf(
					'JLIB_INSTALLER_ABORT_PLG_COPY_FILES',
					\JText::_('JLIB_INSTALLER_' . $this->route)
				)
			);
		}

		// If there is a manifest script, let's copy it.
		if ($this->manifest_script)
		{
			$path['src']  =
$this->parent->getPath('source') . '/' .
$this->manifest_script;
			$path['dest'] =
$this->parent->getPath('extension_root') . '/' .
$this->manifest_script;

			if ($this->parent->isOverwrite() ||
!file_exists($path['dest']))
			{
				if (!$this->parent->copyFiles(array($path)))
				{
					// Install failed, rollback changes
					throw new \RuntimeException(
						\JText::sprintf(
							'JLIB_INSTALLER_ABORT_PLG_INSTALL_MANIFEST',
							\JText::_('JLIB_INSTALLER_' . $this->route)
						)
					);
				}
			}
		}
	}

	/**
	 * Method to create the extension root path if necessary
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function createExtensionRoot()
	{
		// Run the common create code first
		parent::createExtensionRoot();

		// If we're updating at this point when there is always going to be
an extension_root find the old XML files
		if ($this->route === 'update')
		{
			// Create a new installer because findManifest sets stuff; side effects!
			$tmpInstaller = new Installer;

			// Look in the extension root
			$tmpInstaller->setPath('source',
$this->parent->getPath('extension_root'));

			if ($tmpInstaller->findManifest())
			{
				$old_manifest   = $tmpInstaller->getManifest();
				$this->oldFiles = $old_manifest->files;
			}
		}
	}

	/**
	 * Method to finalise the installation processing
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function finaliseInstall()
	{
		// Clobber any possible pending updates
		/** @var Update $update */
		$update = Table::getInstance('update');
		$uid = $update->find(
			array(
				'element' => $this->element,
				'type'    => $this->type,
				'folder'  => $this->group,
			)
		);

		if ($uid)
		{
			$update->delete($uid);
		}

		// Lastly, we will copy the manifest file to its appropriate place.
		if ($this->route !== 'discover_install')
		{
			if (!$this->parent->copyManifest(-1))
			{
				// Install failed, rollback changes
				throw new \RuntimeException(
					\JText::sprintf(
						'JLIB_INSTALLER_ABORT_PLG_INSTALL_COPY_SETUP',
						\JText::_('JLIB_INSTALLER_' . $this->route)
					)
				);
			}
		}
	}

	/**
	 * Get the filtered extension element from the manifest
	 *
	 * @param   string  $element  Optional element name to be converted
	 *
	 * @return  string  The filtered element
	 *
	 * @since   3.4
	 */
	public function getElement($element = null)
	{
		if (!$element)
		{
			// Backward Compatibility
			// @todo Deprecate in future version
			if (count($this->getManifest()->files->children()))
			{
				$type = (string) $this->getManifest()->attributes()->type;

				foreach ($this->getManifest()->files->children() as $file)
				{
					if ((string) $file->attributes()->$type)
					{
						$element = (string) $file->attributes()->$type;

						break;
					}
				}
			}
		}

		return $element;
	}

	/**
	 * Get the class name for the install adapter script.
	 *
	 * @return  string  The class name.
	 *
	 * @since   3.4
	 */
	protected function getScriptClassName()
	{
		return 'Plg' . str_replace('-', '',
$this->group) . $this->element . 'InstallerScript';
	}

	/**
	 * Custom loadLanguage method
	 *
	 * @param   string  $path  The path where to find language files.
	 *
	 * @return  void
	 *
	 * @since   3.1
	 */
	public function loadLanguage($path = null)
	{
		$source = $this->parent->getPath('source');

		if (!$source)
		{
			$this->parent->setPath(
				'source',
				JPATH_PLUGINS . '/' .
$this->parent->extension->folder . '/' .
$this->parent->extension->element
			);
		}

		$element = $this->getManifest()->files;

		if ($element)
		{
			$group = strtolower((string)
$this->getManifest()->attributes()->group);
			$name = '';

			if (count($element->children()))
			{
				foreach ($element->children() as $file)
				{
					if ((string) $file->attributes()->plugin)
					{
						$name = strtolower((string) $file->attributes()->plugin);
						break;
					}
				}
			}

			if ($name)
			{
				$extension = "plg_${group}_${name}";
				$source = $path ?: JPATH_PLUGINS . "/$group/$name";
				$folder = (string) $element->attributes()->folder;

				if ($folder && file_exists("$path/$folder"))
				{
					$source = "$path/$folder";
				}

				$this->doLoadLanguage($extension, $source, JPATH_ADMINISTRATOR);
			}
		}
	}

	/**
	 * Method to parse optional tags in the manifest
	 *
	 * @return  void
	 *
	 * @since   3.4
	 */
	protected function parseOptionalTags()
	{
		// Parse optional tags -- media and language files for plugins go in
admin app
		$this->parent->parseMedia($this->getManifest()->media, 1);
		$this->parent->parseLanguages($this->getManifest()->languages,
1);
	}

	/**
	 * Prepares the adapter for a discover_install task
	 *
	 * @return  void
	 *
	 * @since   3.4
	 */
	public function prepareDiscoverInstall()
	{
		$client   =
ApplicationHelper::getClientInfo($this->extension->client_id);
		$basePath = $client->path . '/plugins/' .
$this->extension->folder;

		if (is_dir($basePath . '/' . $this->extension->element))
		{
			$manifestPath = $basePath . '/' .
$this->extension->element . '/' .
$this->extension->element . '.xml';
		}
		else
		{
			// @deprecated 4.0 - This path supports Joomla! 1.5 plugin folder
layouts
			$manifestPath = $basePath . '/' .
$this->extension->element . '.xml';
		}

		$this->parent->manifest =
$this->parent->isManifest($manifestPath);
		$this->parent->setPath('manifest', $manifestPath);
		$this->setManifest($this->parent->getManifest());
	}

	/**
	 * Method to do any prechecks and setup the install paths for the
extension
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function setupInstallPaths()
	{
		$this->group = (string)
$this->getManifest()->attributes()->group;

		if (empty($this->element) && empty($this->group))
		{
			throw new \RuntimeException(
				\JText::sprintf(
					'JLIB_INSTALLER_ABORT_PLG_INSTALL_NO_FILE',
					\JText::_('JLIB_INSTALLER_' . $this->route)
				)
			);
		}

		$this->parent->setPath('extension_root', JPATH_PLUGINS .
'/' . $this->group . '/' . $this->element);
	}

	/**
	 * Method to store the extension to the database
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function storeExtension()
	{
		// Discover installs are stored a little differently
		if ($this->route === 'discover_install')
		{
			$manifest_details =
Installer::parseXMLInstallFile($this->parent->getPath('manifest'));

			$this->extension->manifest_cache = json_encode($manifest_details);
			$this->extension->state = 0;
			$this->extension->name = $manifest_details['name'];
			$this->extension->enabled = 'editors' ===
$this->extension->folder ? 1 : 0;
			$this->extension->params = $this->parent->getParams();

			if (!$this->extension->store())
			{
				// Install failed, roll back changes
				throw new
\RuntimeException(\JText::_('JLIB_INSTALLER_ERROR_PLG_DISCOVER_STORE_DETAILS'));
			}

			return;
		}

		// Was there a plugin with the same name already installed?
		if ($this->currentExtensionId)
		{
			if (!$this->parent->isOverwrite())
			{
				// Install failed, roll back changes
				throw new \RuntimeException(
					\JText::sprintf(
						'JLIB_INSTALLER_ABORT_PLG_INSTALL_ALLREADY_EXISTS',
						\JText::_('JLIB_INSTALLER_' . $this->route),
						$this->name
					)
				);
			}

			$this->extension->load($this->currentExtensionId);
			$this->extension->name = $this->name;
			$this->extension->manifest_cache =
$this->parent->generateManifestCache();

			// Update the manifest cache and name
			$this->extension->store();
		}
		else
		{
			// Store in the extensions table (1.6)
			$this->extension->name = $this->name;
			$this->extension->type = 'plugin';
			$this->extension->ordering = 0;
			$this->extension->element = $this->element;
			$this->extension->folder = $this->group;
			$this->extension->enabled = 0;
			$this->extension->protected = 0;
			$this->extension->access = 1;
			$this->extension->client_id = 0;
			$this->extension->params = $this->parent->getParams();

			// Custom data
			$this->extension->custom_data = '';

			// System data
			$this->extension->system_data = '';
			$this->extension->manifest_cache =
$this->parent->generateManifestCache();

			// Editor plugins are published by default
			if ($this->group === 'editors')
			{
				$this->extension->enabled = 1;
			}

			if (!$this->extension->store())
			{
				// Install failed, roll back changes
				throw new \RuntimeException(
					\JText::sprintf(
						'JLIB_INSTALLER_ABORT_PLG_INSTALL_ROLLBACK',
						\JText::_('JLIB_INSTALLER_' . $this->route),
						$this->extension->getError()
					)
				);
			}

			// Since we have created a plugin item, we add it to the installation
step stack
			// so that if we have to rollback the changes we can undo it.
			$this->parent->pushStep(array('type' =>
'extension', 'id' =>
$this->extension->extension_id));
		}
	}

	/**
	 * Custom uninstall method
	 *
	 * @param   integer  $id  The id of the plugin to uninstall
	 *
	 * @return  boolean  True on success
	 *
	 * @since   3.1
	 */
	public function uninstall($id)
	{
		$this->route = 'uninstall';

		$row = null;
		$retval = true;
		$db = $this->parent->getDbo();

		// First order of business will be to load the plugin object table from
the database.
		// This should give us the necessary information to proceed.
		$row = Table::getInstance('extension');

		if (!$row->load((int) $id))
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_PLG_UNINSTALL_ERRORUNKOWNEXTENSION'),
\JLog::WARNING, 'jerror');

			return false;
		}

		// Is the plugin we are trying to uninstall a core one?
		// Because that is not a good idea...
		if ($row->protected)
		{
			\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_PLG_UNINSTALL_WARNCOREPLUGIN',
$row->name), \JLog::WARNING, 'jerror');

			return false;
		}

		/*
		 * Does this extension have a parent package?
		 * If so, check if the package disallows individual extensions being
uninstalled if the package is not being uninstalled
		 */
		if ($row->package_id &&
!$this->parent->isPackageUninstall() &&
!$this->canUninstallPackageChild($row->package_id))
		{
			\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_CANNOT_UNINSTALL_CHILD_OF_PACKAGE',
$row->name), \JLog::WARNING, 'jerror');

			return false;
		}

		// Get the plugin folder so we can properly build the plugin path
		if (trim($row->folder) === '')
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_PLG_UNINSTALL_FOLDER_FIELD_EMPTY'),
\JLog::WARNING, 'jerror');

			return false;
		}

		// Set the plugin root path
		$this->parent->setPath('extension_root', JPATH_PLUGINS .
'/' . $row->folder . '/' . $row->element);

		$this->parent->setPath('source',
$this->parent->getPath('extension_root'));

		$this->parent->findManifest();
		$this->setManifest($this->parent->getManifest());

		// Attempt to load the language file; might have uninstall strings
		$this->parent->setPath('source', JPATH_PLUGINS .
'/' . $row->folder . '/' . $row->element);
		$this->loadLanguage(JPATH_PLUGINS . '/' . $row->folder .
'/' . $row->element);

		/**
		 *
---------------------------------------------------------------------------------------------
		 * Installer Trigger Loading
		 *
---------------------------------------------------------------------------------------------
		 */

		// If there is a manifest class file, let's load it; we'll copy
it later (don't have dest yet)
		$manifestScript = (string) $this->getManifest()->scriptfile;

		if ($manifestScript)
		{
			$manifestScriptFile = $this->parent->getPath('source') .
'/' . $manifestScript;

			// If a dash is present in the folder, remove it
			$folderClass = str_replace('-', '',
$row->folder);

			// Set the class name
			$classname = 'Plg' . $folderClass . $row->element .
'InstallerScript';

			\JLoader::register($classname, $manifestScriptFile);

			if (class_exists($classname))
			{
				// Create a new instance
				$this->parent->manifestClass = new $classname($this);

				// And set this so we can copy it later
				$this->set('manifest_script', $manifestScript);
			}
		}

		// Run preflight if possible (since we know we're not an update)
		ob_start();
		ob_implicit_flush(false);

		if ($this->parent->manifestClass &&
method_exists($this->parent->manifestClass, 'preflight'))
		{
			if ($this->parent->manifestClass->preflight($this->route,
$this) === false)
			{
				// Preflight failed, rollback changes
				$this->parent->abort(\JText::_('JLIB_INSTALLER_ABORT_PLG_INSTALL_CUSTOM_INSTALL_FAILURE'));

				return false;
			}
		}

		// Create the $msg object and append messages from preflight
		$msg = ob_get_contents();
		ob_end_clean();

		// Let's run the queries for the plugin
		$utfresult =
$this->parent->parseSQLFiles($this->getManifest()->uninstall->sql);

		if ($utfresult === false)
		{
			// Install failed, rollback changes
			$this->parent->abort(\JText::sprintf('JLIB_INSTALLER_ABORT_PLG_UNINSTALL_SQL_ERROR',
$db->stderr(true)));

			return false;
		}

		// Run the custom uninstall method if possible
		ob_start();
		ob_implicit_flush(false);

		if ($this->parent->manifestClass &&
method_exists($this->parent->manifestClass, 'uninstall'))
		{
			$this->parent->manifestClass->uninstall($this);
		}

		// Append messages
		$msg .= ob_get_contents();
		ob_end_clean();

		// Remove the plugin files
		$this->parent->removeFiles($this->getManifest()->files, -1);

		// Remove all media and languages as well
		$this->parent->removeFiles($this->getManifest()->media);
		$this->parent->removeFiles($this->getManifest()->languages,
1);

		// Remove the schema version
		$query = $db->getQuery(true)
			->delete('#__schemas')
			->where('extension_id = ' . $row->extension_id);
		$db->setQuery($query);
		$db->execute();

		// Now we will no longer need the plugin object, so let's delete it
		$row->delete($row->extension_id);
		unset($row);

		// Remove the plugin's folder
		\JFolder::delete($this->parent->getPath('extension_root'));

		if ($msg != '')
		{
			$this->parent->set('extension_message', $msg);
		}

		return $retval;
	}

	/**
	 * Custom discover method
	 *
	 * @return  array  Extension) list of extensions available
	 *
	 * @since   3.1
	 */
	public function discover()
	{
		$results = array();
		$folder_list = \JFolder::folders(JPATH_SITE . '/plugins');

		foreach ($folder_list as $folder)
		{
			$file_list = \JFolder::files(JPATH_SITE . '/plugins/' .
$folder, '\.xml$');

			foreach ($file_list as $file)
			{
				$manifest_details = Installer::parseXMLInstallFile(JPATH_SITE .
'/plugins/' . $folder . '/' . $file);
				$file = \JFile::stripExt($file);

				// Ignore example plugins
				if ($file === 'example' || $manifest_details === false)
				{
					continue;
				}

				$element = empty($manifest_details['filename']) ? $file :
$manifest_details['filename'];

				$extension = Table::getInstance('extension');
				$extension->set('type', 'plugin');
				$extension->set('client_id', 0);
				$extension->set('element', $element);
				$extension->set('folder', $folder);
				$extension->set('name',
$manifest_details['name']);
				$extension->set('state', -1);
				$extension->set('manifest_cache',
json_encode($manifest_details));
				$extension->set('params', '{}');
				$results[] = $extension;
			}

			$folder_list = \JFolder::folders(JPATH_SITE . '/plugins/' .
$folder);

			foreach ($folder_list as $plugin_folder)
			{
				$file_list = \JFolder::files(JPATH_SITE . '/plugins/' .
$folder . '/' . $plugin_folder, '\.xml$');

				foreach ($file_list as $file)
				{
					$manifest_details = Installer::parseXMLInstallFile(
						JPATH_SITE . '/plugins/' . $folder . '/' .
$plugin_folder . '/' . $file
					);
					$file = \JFile::stripExt($file);

					if ($file === 'example' || $manifest_details === false)
					{
						continue;
					}

					$element = empty($manifest_details['filename']) ? $file :
$manifest_details['filename'];

					// Ignore example plugins
					$extension = Table::getInstance('extension');
					$extension->set('type', 'plugin');
					$extension->set('client_id', 0);
					$extension->set('element', $element);
					$extension->set('folder', $folder);
					$extension->set('name',
$manifest_details['name']);
					$extension->set('state', -1);
					$extension->set('manifest_cache',
json_encode($manifest_details));
					$extension->set('params', '{}');
					$results[] = $extension;
				}
			}
		}

		return $results;
	}

	/**
	 * Refreshes the extension table cache.
	 *
	 * @return  boolean  Result of operation, true if updated, false on
failure.
	 *
	 * @since   3.1
	 */
	public function refreshManifestCache()
	{
		/*
		 * Plugins use the extensions table as their primary store
		 * Similar to modules and templates, rather easy
		 * If it's not in the extensions table we just add it
		 */
		$client =
ApplicationHelper::getClientInfo($this->parent->extension->client_id);
		$manifestPath = $client->path . '/plugins/' .
$this->parent->extension->folder . '/' .
$this->parent->extension->element . '/'
			. $this->parent->extension->element . '.xml';
		$this->parent->manifest =
$this->parent->isManifest($manifestPath);
		$this->parent->setPath('manifest', $manifestPath);
		$manifest_details =
Installer::parseXMLInstallFile($this->parent->getPath('manifest'));
		$this->parent->extension->manifest_cache =
json_encode($manifest_details);

		$this->parent->extension->name =
$manifest_details['name'];

		if ($this->parent->extension->store())
		{
			return true;
		}
		else
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_PLG_REFRESH_MANIFEST_CACHE'),
\JLog::WARNING, 'jerror');

			return false;
		}
	}
}
PKK�[�A�R�C�CAdapter/TemplateAdapter.phpnu�[���<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
 * @license    GNU General Public License version 2 or later; see
LICENSE.txt
 */

namespace Joomla\CMS\Installer\Adapter;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Installer\Installer;
use Joomla\CMS\Installer\InstallerAdapter;
use Joomla\CMS\Table\Table;
use Joomla\CMS\Table\Update;

\JLoader::import('joomla.filesystem.folder');

/**
 * Template installer
 *
 * @since  3.1
 */
class TemplateAdapter extends InstallerAdapter
{
	/**
	 * The install client ID
	 *
	 * @var    integer
	 * @since  3.4
	 */
	protected $clientId;

	/**
	 * Method to check if the extension is already present in the database
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function checkExistingExtension()
	{
		try
		{
			$this->currentExtensionId = $this->extension->find(
				array(
					'element'   => $this->element,
					'type'      => $this->type,
					'client_id' => $this->clientId,
				)
			);
		}
		catch (\RuntimeException $e)
		{
			// Install failed, roll back changes
			throw new \RuntimeException(
				\JText::sprintf(
					'JLIB_INSTALLER_ABORT_ROLLBACK',
					\JText::_('JLIB_INSTALLER_' . $this->route),
					$e->getMessage()
				),
				$e->getCode(),
				$e
			);
		}
	}

	/**
	 * Method to copy the extension's base files from the `<files>`
tag(s) and the manifest file
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function copyBaseFiles()
	{
		// Copy all the necessary files
		if ($this->parent->parseFiles($this->getManifest()->files,
-1) === false)
		{
			throw new \RuntimeException(
				\JText::sprintf(
					'JLIB_INSTALLER_ABORT_TPL_INSTALL_COPY_FILES',
					'files'
				)
			);
		}

		if ($this->parent->parseFiles($this->getManifest()->images,
-1) === false)
		{
			throw new \RuntimeException(
				\JText::sprintf(
					'JLIB_INSTALLER_ABORT_TPL_INSTALL_COPY_FILES',
					'images'
				)
			);
		}

		if ($this->parent->parseFiles($this->getManifest()->css, -1)
=== false)
		{
			throw new \RuntimeException(
				\JText::sprintf(
					'JLIB_INSTALLER_ABORT_TPL_INSTALL_COPY_FILES',
					'css'
				)
			);
		}

		// If there is a manifest script, let's copy it.
		if ($this->manifest_script)
		{
			$path['src']  =
$this->parent->getPath('source') . '/' .
$this->manifest_script;
			$path['dest'] =
$this->parent->getPath('extension_root') . '/' .
$this->manifest_script;

			if ($this->parent->isOverwrite() ||
!file_exists($path['dest']))
			{
				if (!$this->parent->copyFiles(array($path)))
				{
					throw new \RuntimeException(
						\JText::sprintf(
							'JLIB_INSTALLER_ABORT_MANIFEST',
							\JText::_('JLIB_INSTALLER_' .
strtoupper($this->getRoute()))
						)
					);
				}
			}
		}
	}

	/**
	 * Method to finalise the installation processing
	 *
	 * @return  void
	 *
	 * @since   3.1
	 * @throws  \RuntimeException
	 */
	protected function finaliseInstall()
	{
		// Clobber any possible pending updates
		/** @var Update $update */
		$update = Table::getInstance('update');

		$uid = $update->find(
			array(
				'element'   => $this->element,
				'type'      => $this->type,
				'client_id' => $this->clientId,
			)
		);

		if ($uid)
		{
			$update->delete($uid);
		}

		// Lastly, we will copy the manifest file to its appropriate place.
		if ($this->route !== 'discover_install')
		{
			if (!$this->parent->copyManifest(-1))
			{
				// Install failed, rollback changes
				throw new
\RuntimeException(\JText::_('JLIB_INSTALLER_ABORT_TPL_INSTALL_COPY_SETUP'));
			}
		}
	}

	/**
	 * Custom loadLanguage method
	 *
	 * @param   string  $path  The path where to find language files.
	 *
	 * @return  InstallerTemplate
	 *
	 * @since   3.1
	 */
	public function loadLanguage($path = null)
	{
		$source   = $this->parent->getPath('source');
		$basePath = $this->parent->extension->client_id ?
JPATH_ADMINISTRATOR : JPATH_SITE;

		if (!$source)
		{
			$this->parent->setPath('source', $basePath .
'/templates/' . $this->parent->extension->element);
		}

		$this->setManifest($this->parent->getManifest());

		$client = (string) $this->getManifest()->attributes()->client;

		// Load administrator language if not set.
		if (!$client)
		{
			$client = 'ADMINISTRATOR';
		}

		$base = constant('JPATH_' . strtoupper($client));
		$extension = 'tpl_' . $this->getName();
		$source    = $path ?: $base . '/templates/' .
$this->getName();

		$this->doLoadLanguage($extension, $source, $base);
	}

	/**
	 * Method to parse optional tags in the manifest
	 *
	 * @return  void
	 *
	 * @since   3.4
	 */
	protected function parseOptionalTags()
	{
		$this->parent->parseMedia($this->getManifest()->media);
		$this->parent->parseLanguages($this->getManifest()->languages,
$this->clientId);
	}

	/**
	 * Overloaded method to parse queries for template installations
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function parseQueries()
	{
		if (in_array($this->route, array('install',
'discover_install')))
		{
			$db    = $this->db;
			$lang  = \JFactory::getLanguage();
			$debug = $lang->setDebug(false);

			$columns = array(
				$db->quoteName('template'),
				$db->quoteName('client_id'),
				$db->quoteName('home'),
				$db->quoteName('title'),
				$db->quoteName('params'),
			);

			$values = array(
				$db->quote($this->extension->element),
$this->extension->client_id, $db->quote(0),
				$db->quote(\JText::sprintf('JLIB_INSTALLER_DEFAULT_STYLE',
\JText::_($this->extension->name))),
				$db->quote($this->extension->params),
			);

			$lang->setDebug($debug);

			// Insert record in #__template_styles
			$query = $db->getQuery(true);
			$query->insert($db->quoteName('#__template_styles'))
				->columns($columns)
				->values(implode(',', $values));

			// There is a chance this could fail but we don't care...
			$db->setQuery($query)->execute();
		}
	}

	/**
	 * Prepares the adapter for a discover_install task
	 *
	 * @return  void
	 *
	 * @since   3.4
	 */
	public function prepareDiscoverInstall()
	{
		$client =
ApplicationHelper::getClientInfo($this->extension->client_id);
		$manifestPath = $client->path . '/templates/' .
$this->extension->element . '/templateDetails.xml';
		$this->parent->manifest =
$this->parent->isManifest($manifestPath);
		$this->parent->setPath('manifest', $manifestPath);
		$this->setManifest($this->parent->getManifest());
	}

	/**
	 * Method to do any prechecks and setup the install paths for the
extension
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function setupInstallPaths()
	{
		// Get the client application target
		$cname = (string) $this->getManifest()->attributes()->client;

		if ($cname)
		{
			// Attempt to map the client to a base path
			$client = ApplicationHelper::getClientInfo($cname, true);

			if ($client === false)
			{
				throw new
\RuntimeException(\JText::sprintf('JLIB_INSTALLER_ABORT_TPL_INSTALL_UNKNOWN_CLIENT',
$cname));
			}

			$basePath = $client->path;
			$this->clientId = $client->id;
		}
		else
		{
			// No client attribute was found so we assume the site as the client
			$basePath = JPATH_SITE;
			$this->clientId = 0;
		}

		// Set the template root path
		if (empty($this->element))
		{
			throw new \RuntimeException(
				\JText::sprintf(
					'JLIB_INSTALLER_ABORT_MOD_INSTALL_NOFILE',
					\JText::_('JLIB_INSTALLER_' . strtoupper($this->route))
				)
			);
		}

		$this->parent->setPath('extension_root', $basePath .
'/templates/' . $this->element);
	}

	/**
	 * Method to store the extension to the database
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function storeExtension()
	{
		// Discover installs are stored a little differently
		if ($this->route === 'discover_install')
		{
			$manifest_details =
Installer::parseXMLInstallFile($this->parent->getPath('manifest'));

			$this->extension->manifest_cache = json_encode($manifest_details);
			$this->extension->state = 0;
			$this->extension->name = $manifest_details['name'];
			$this->extension->enabled = 1;
			$this->extension->params = $this->parent->getParams();

			if (!$this->extension->store())
			{
				// Install failed, roll back changes
				throw new
\RuntimeException(\JText::_('JLIB_INSTALLER_ERROR_TPL_DISCOVER_STORE_DETAILS'));
			}

			return;
		}

		// Was there a template already installed with the same name?
		if ($this->currentExtensionId)
		{
			if (!$this->parent->isOverwrite())
			{
				// Install failed, roll back changes
				throw new \RuntimeException(
					\JText::_('JLIB_INSTALLER_ABORT_TPL_INSTALL_ALREADY_INSTALLED')
				);
			}

			// Load the entry and update the manifest_cache
			$this->extension->load($this->currentExtensionId);
		}
		else
		{
			$this->extension->type = 'template';
			$this->extension->element = $this->element;

			// There is no folder for templates
			$this->extension->folder = '';
			$this->extension->enabled = 1;
			$this->extension->protected = 0;
			$this->extension->access = 1;
			$this->extension->client_id = $this->clientId;
			$this->extension->params = $this->parent->getParams();

			// Custom data
			$this->extension->custom_data = '';
			$this->extension->system_data = '';
		}

		// Name might change in an update
		$this->extension->name = $this->name;
		$this->extension->manifest_cache =
$this->parent->generateManifestCache();

		if (!$this->extension->store())
		{
			// Install failed, roll back changes
			throw new \RuntimeException(
				\JText::sprintf(
					'JLIB_INSTALLER_ABORT_ROLLBACK',
					\JText::_('JLIB_INSTALLER_' . strtoupper($this->route)),
					$this->extension->getError()
				)
			);
		}
	}

	/**
	 * Custom uninstall method
	 *
	 * @param   integer  $id  The extension ID
	 *
	 * @return  boolean  True on success
	 *
	 * @since   3.1
	 */
	public function uninstall($id)
	{
		// First order of business will be to load the template object table from
the database.
		// This should give us the necessary information to proceed.
		$row = Table::getInstance('extension');

		if (!$row->load((int) $id) || $row->element === '')
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_TPL_UNINSTALL_ERRORUNKOWNEXTENSION'),
\JLog::WARNING, 'jerror');

			return false;
		}

		// Is the template we are trying to uninstall a core one?
		// Because that is not a good idea...
		if ($row->protected)
		{
			\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_TPL_UNINSTALL_WARNCORETEMPLATE',
$row->name), \JLog::WARNING, 'jerror');

			return false;
		}

		/*
		 * Does this extension have a parent package?
		 * If so, check if the package disallows individual extensions being
uninstalled if the package is not being uninstalled
		 */
		if ($row->package_id &&
!$this->parent->isPackageUninstall() &&
!$this->canUninstallPackageChild($row->package_id))
		{
			\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_CANNOT_UNINSTALL_CHILD_OF_PACKAGE',
$row->name), \JLog::WARNING, 'jerror');

			return false;
		}

		$name = $row->element;
		$clientId = $row->client_id;

		// For a template the id will be the template name which represents the
subfolder of the templates folder that the template resides in.
		if (!$name)
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_TPL_UNINSTALL_TEMPLATE_ID_EMPTY'),
\JLog::WARNING, 'jerror');

			return false;
		}

		// Deny remove default template
		$db = $this->parent->getDbo();
		$query = $db->getQuery(true)
			->select('COUNT(*)')
			->from($db->qn('#__template_styles'))
			->where($db->qn('home') . ' = ' .
$db->q('1'))
			->where($db->qn('template') . ' = ' .
$db->q($name))
			->where($db->quoteName('client_id') . ' = ' .
$clientId);
		$db->setQuery($query);

		if ($db->loadResult() != 0)
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_TPL_UNINSTALL_TEMPLATE_DEFAULT'),
\JLog::WARNING, 'jerror');

			return false;
		}

		// Get the template root path
		$client = ApplicationHelper::getClientInfo($clientId);

		if (!$client)
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_TPL_UNINSTALL_INVALID_CLIENT'),
\JLog::WARNING, 'jerror');

			return false;
		}

		$this->parent->setPath('extension_root', $client->path
. '/templates/' . strtolower($name));
		$this->parent->setPath('source',
$this->parent->getPath('extension_root'));

		// We do findManifest to avoid problem when uninstalling a list of
extensions: getManifest cache its manifest file
		$this->parent->findManifest();
		$manifest = $this->parent->getManifest();

		if (!($manifest instanceof \SimpleXMLElement))
		{
			// Kill the extension entry
			$row->delete($row->extension_id);
			unset($row);

			// Make sure we delete the folders
			\JFolder::delete($this->parent->getPath('extension_root'));
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_TPL_UNINSTALL_INVALID_NOTFOUND_MANIFEST'),
\JLog::WARNING, 'jerror');

			return false;
		}

		// Remove files
		$this->parent->removeFiles($manifest->media);
		$this->parent->removeFiles($manifest->languages, $clientId);

		// Delete the template directory
		if
(\JFolder::exists($this->parent->getPath('extension_root')))
		{
			$retval =
\JFolder::delete($this->parent->getPath('extension_root'));
		}
		else
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_TPL_UNINSTALL_TEMPLATE_DIRECTORY'),
\JLog::WARNING, 'jerror');
			$retval = false;
		}

		// Set menu that assigned to the template back to default template
		$subQuery = $db->getQuery(true)
			->select('s.id')
			->from($db->qn('#__template_styles', 's'))
			->where($db->qn('s.template') . ' = ' .
$db->q(strtolower($name)))
			->where($db->qn('s.client_id') . ' = ' .
$clientId);
		$query->clear()
			->update($db->qn('#__menu'))
			->set($db->qn('template_style_id') . ' = 0')
			->where($db->qn('template_style_id') . ' IN ('
. (string) $subQuery . ')');
		$db->setQuery($query);
		$db->execute();

		$query = $db->getQuery(true)
			->delete($db->quoteName('#__template_styles'))
			->where($db->quoteName('template') . ' = ' .
$db->quote($name))
			->where($db->quoteName('client_id') . ' = ' .
$clientId);
		$db->setQuery($query);
		$db->execute();

		$row->delete($row->extension_id);
		unset($row);

		return $retval;
	}

	/**
	 * Discover existing but uninstalled templates
	 *
	 * @return  array  Extension list
	 */
	public function discover()
	{
		$results = array();
		$site_list = \JFolder::folders(JPATH_SITE . '/templates');
		$admin_list = \JFolder::folders(JPATH_ADMINISTRATOR .
'/templates');
		$site_info = ApplicationHelper::getClientInfo('site', true);
		$admin_info = ApplicationHelper::getClientInfo('administrator',
true);

		foreach ($site_list as $template)
		{
			if (file_exists(JPATH_SITE .
"/templates/$template/templateDetails.xml"))
			{
				if ($template === 'system')
				{
					// Ignore special system template
					continue;
				}

				$manifest_details = Installer::parseXMLInstallFile(JPATH_SITE .
"/templates/$template/templateDetails.xml");
				$extension = Table::getInstance('extension');
				$extension->set('type', 'template');
				$extension->set('client_id', $site_info->id);
				$extension->set('element', $template);
				$extension->set('folder', '');
				$extension->set('name', $template);
				$extension->set('state', -1);
				$extension->set('manifest_cache',
json_encode($manifest_details));
				$extension->set('params', '{}');
				$results[] = $extension;
			}
		}

		foreach ($admin_list as $template)
		{
			if (file_exists(JPATH_ADMINISTRATOR .
"/templates/$template/templateDetails.xml"))
			{
				if ($template === 'system')
				{
					// Ignore special system template
					continue;
				}

				$manifest_details = Installer::parseXMLInstallFile(JPATH_ADMINISTRATOR
. "/templates/$template/templateDetails.xml");
				$extension = Table::getInstance('extension');
				$extension->set('type', 'template');
				$extension->set('client_id', $admin_info->id);
				$extension->set('element', $template);
				$extension->set('folder', '');
				$extension->set('name', $template);
				$extension->set('state', -1);
				$extension->set('manifest_cache',
json_encode($manifest_details));
				$extension->set('params', '{}');
				$results[] = $extension;
			}
		}

		return $results;
	}

	/**
	 * Refreshes the extension table cache
	 *
	 * @return  boolean  Result of operation, true if updated, false on
failure
	 *
	 * @since   3.1
	 */
	public function refreshManifestCache()
	{
		// Need to find to find where the XML file is since we don't store
this normally.
		$client =
ApplicationHelper::getClientInfo($this->parent->extension->client_id);
		$manifestPath = $client->path . '/templates/' .
$this->parent->extension->element .
'/templateDetails.xml';
		$this->parent->manifest =
$this->parent->isManifest($manifestPath);
		$this->parent->setPath('manifest', $manifestPath);

		$manifest_details =
Installer::parseXMLInstallFile($this->parent->getPath('manifest'));
		$this->parent->extension->manifest_cache =
json_encode($manifest_details);
		$this->parent->extension->name =
$manifest_details['name'];

		try
		{
			return $this->parent->extension->store();
		}
		catch (\RuntimeException $e)
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_TPL_REFRESH_MANIFEST_CACHE'),
\JLog::WARNING, 'jerror');

			return false;
		}
	}
}
PKK�[�5y����
Installer.phpnu�[���<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
 * @license    GNU General Public License version 2 or later; see
LICENSE.txt
 */

namespace Joomla\CMS\Installer;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\CMS\Table\Extension;
use Joomla\CMS\Table\Table;

\JLoader::import('joomla.filesystem.file');
\JLoader::import('joomla.filesystem.folder');
\JLoader::import('joomla.filesystem.path');
\JLoader::import('joomla.base.adapter');

/**
 * Joomla base installer class
 *
 * @since  3.1
 */
class Installer extends \JAdapter
{
	/**
	 * Array of paths needed by the installer
	 *
	 * @var    array
	 * @since  3.1
	 */
	protected $paths = array();

	/**
	 * True if package is an upgrade
	 *
	 * @var    boolean
	 * @since  3.1
	 */
	protected $upgrade = null;

	/**
	 * The manifest trigger class
	 *
	 * @var    object
	 * @since  3.1
	 */
	public $manifestClass = null;

	/**
	 * True if existing files can be overwritten
	 *
	 * @var    boolean
	 * @since  3.0.0
	 */
	protected $overwrite = false;

	/**
	 * Stack of installation steps
	 * - Used for installation rollback
	 *
	 * @var    array
	 * @since  3.1
	 */
	protected $stepStack = array();

	/**
	 * Extension Table Entry
	 *
	 * @var    Extension
	 * @since  3.1
	 */
	public $extension = null;

	/**
	 * The output from the install/uninstall scripts
	 *
	 * @var    string
	 * @since  3.1
	 * */
	public $message = null;

	/**
	 * The installation manifest XML object
	 *
	 * @var    object
	 * @since  3.1
	 */
	public $manifest = null;

	/**
	 * The extension message that appears
	 *
	 * @var    string
	 * @since  3.1
	 */
	protected $extension_message = null;

	/**
	 * The redirect URL if this extension (can be null if no redirect)
	 *
	 * @var    string
	 * @since  3.1
	 */
	protected $redirect_url = null;

	/**
	 * Flag if the uninstall process was triggered by uninstalling a package
	 *
	 * @var    boolean
	 * @since  3.7.0
	 */
	protected $packageUninstall = false;

	/**
	 * Installer instance container.
	 *
	 * @var    Installer
	 * @since  3.1
	 * @deprecated  4.0
	 */
	protected static $instance;

	/**
	 * Installer instances container.
	 *
	 * @var    Installer[]
	 * @since  3.4
	 */
	protected static $instances;

	/**
	 * Constructor
	 *
	 * @param   string  $basepath       Base Path of the adapters
	 * @param   string  $classprefix    Class prefix of adapters
	 * @param   string  $adapterfolder  Name of folder to append to base path
	 *
	 * @since   3.1
	 */
	public function __construct($basepath = __DIR__, $classprefix =
'\\Joomla\\CMS\\Installer\\Adapter', $adapterfolder =
'Adapter')
	{
		parent::__construct($basepath, $classprefix, $adapterfolder);

		$this->extension = Table::getInstance('extension');
	}

	/**
	 * Returns the global Installer object, only creating it if it
doesn't already exist.
	 *
	 * @param   string  $basepath       Base Path of the adapters
	 * @param   string  $classprefix    Class prefix of adapters
	 * @param   string  $adapterfolder  Name of folder to append to base path
	 *
	 * @return  Installer  An installer object
	 *
	 * @since   3.1
	 */
	public static function getInstance($basepath = __DIR__, $classprefix =
'\\Joomla\\CMS\\Installer\\Adapter', $adapterfolder =
'Adapter')
	{
		if (!isset(self::$instances[$basepath]))
		{
			self::$instances[$basepath] = new Installer($basepath, $classprefix,
$adapterfolder);

			// For B/C, we load the first instance into the static $instance
container, remove at 4.0
			if (!isset(self::$instance))
			{
				self::$instance = self::$instances[$basepath];
			}
		}

		return self::$instances[$basepath];
	}

	/**
	 * Get the allow overwrite switch
	 *
	 * @return  boolean  Allow overwrite switch
	 *
	 * @since   3.1
	 */
	public function isOverwrite()
	{
		return $this->overwrite;
	}

	/**
	 * Set the allow overwrite switch
	 *
	 * @param   boolean  $state  Overwrite switch state
	 *
	 * @return  boolean  True it state is set, false if it is not
	 *
	 * @since   3.1
	 */
	public function setOverwrite($state = false)
	{
		$tmp = $this->overwrite;

		if ($state)
		{
			$this->overwrite = true;
		}
		else
		{
			$this->overwrite = false;
		}

		return $tmp;
	}

	/**
	 * Get the redirect location
	 *
	 * @return  string  Redirect location (or null)
	 *
	 * @since   3.1
	 */
	public function getRedirectUrl()
	{
		return $this->redirect_url;
	}

	/**
	 * Set the redirect location
	 *
	 * @param   string  $newurl  New redirect location
	 *
	 * @return  void
	 *
	 * @since   3.1
	 */
	public function setRedirectUrl($newurl)
	{
		$this->redirect_url = $newurl;
	}

	/**
	 * Get whether this installer is uninstalling extensions which are part of
a package
	 *
	 * @return  boolean
	 *
	 * @since   3.7.0
	 */
	public function isPackageUninstall()
	{
		return $this->packageUninstall;
	}

	/**
	 * Set whether this installer is uninstalling extensions which are part of
a package
	 *
	 * @param   boolean  $uninstall  True if a package triggered the
uninstall, false otherwise
	 *
	 * @return  void
	 *
	 * @since   3.7.0
	 */
	public function setPackageUninstall($uninstall)
	{
		$this->packageUninstall = $uninstall;
	}

	/**
	 * Get the upgrade switch
	 *
	 * @return  boolean
	 *
	 * @since   3.1
	 */
	public function isUpgrade()
	{
		return $this->upgrade;
	}

	/**
	 * Set the upgrade switch
	 *
	 * @param   boolean  $state  Upgrade switch state
	 *
	 * @return  boolean  True if upgrade, false otherwise
	 *
	 * @since   3.1
	 */
	public function setUpgrade($state = false)
	{
		$tmp = $this->upgrade;

		if ($state)
		{
			$this->upgrade = true;
		}
		else
		{
			$this->upgrade = false;
		}

		return $tmp;
	}

	/**
	 * Get the installation manifest object
	 *
	 * @return  \SimpleXMLElement  Manifest object
	 *
	 * @since   3.1
	 */
	public function getManifest()
	{
		if (!is_object($this->manifest))
		{
			$this->findManifest();
		}

		return $this->manifest;
	}

	/**
	 * Get an installer path by name
	 *
	 * @param   string  $name     Path name
	 * @param   string  $default  Default value
	 *
	 * @return  string  Path
	 *
	 * @since   3.1
	 */
	public function getPath($name, $default = null)
	{
		return (!empty($this->paths[$name])) ? $this->paths[$name] :
$default;
	}

	/**
	 * Sets an installer path by name
	 *
	 * @param   string  $name   Path name
	 * @param   string  $value  Path
	 *
	 * @return  void
	 *
	 * @since   3.1
	 */
	public function setPath($name, $value)
	{
		$this->paths[$name] = $value;
	}

	/**
	 * Pushes a step onto the installer stack for rolling back steps
	 *
	 * @param   array  $step  Installer step
	 *
	 * @return  void
	 *
	 * @since   3.1
	 */
	public function pushStep($step)
	{
		$this->stepStack[] = $step;
	}

	/**
	 * Installation abort method
	 *
	 * @param   string  $msg   Abort message from the installer
	 * @param   string  $type  Package type if defined
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   3.1
	 */
	public function abort($msg = null, $type = null)
	{
		$retval = true;
		$step = array_pop($this->stepStack);

		// Raise abort warning
		if ($msg)
		{
			\JLog::add($msg, \JLog::WARNING, 'jerror');
		}

		while ($step != null)
		{
			switch ($step['type'])
			{
				case 'file':
					// Remove the file
					$stepval = \JFile::delete($step['path']);
					break;

				case 'folder':
					// Remove the folder
					$stepval = \JFolder::delete($step['path']);
					break;

				case 'query':
					// Execute the query.
					$stepval = $this->parseSQLFiles($step['script']);
					break;

				case 'extension':
					// Get database connector object
					$db = $this->getDbo();
					$query = $db->getQuery(true);

					// Remove the entry from the #__extensions table
					$query->delete($db->quoteName('#__extensions'))
						->where($db->quoteName('extension_id') . ' =
' . (int) $step['id']);
					$db->setQuery($query);

					try
					{
						$db->execute();

						$stepval = true;
					}
					catch (\JDatabaseExceptionExecuting $e)
					{
						// The database API will have already logged the error it caught, we
just need to alert the user to the issue
						\JLog::add(\JText::_('JLIB_INSTALLER_ABORT_ERROR_DELETING_EXTENSIONS_RECORD'),
\JLog::WARNING, 'jerror');

						$stepval = false;
					}

					break;

				default:
					if ($type && is_object($this->_adapters[$type]))
					{
						// Build the name of the custom rollback method for the type
						$method = '_rollback_' . $step['type'];

						// Custom rollback method handler
						if (method_exists($this->_adapters[$type], $method))
						{
							$stepval = $this->_adapters[$type]->$method($step);
						}
					}
					else
					{
						// Set it to false
						$stepval = false;
					}
					break;
			}

			// Only set the return value if it is false
			if ($stepval === false)
			{
				$retval = false;
			}

			// Get the next step and continue
			$step = array_pop($this->stepStack);
		}

		return $retval;
	}

	// Adapter functions

	/**
	 * Package installation method
	 *
	 * @param   string  $path  Path to package source folder
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   3.1
	 */
	public function install($path = null)
	{
		if ($path && \JFolder::exists($path))
		{
			$this->setPath('source', $path);
		}
		else
		{
			$this->abort(\JText::_('JLIB_INSTALLER_ABORT_NOINSTALLPATH'));

			return false;
		}

		if (!$adapter = $this->setupInstall('install', true))
		{
			$this->abort(\JText::_('JLIB_INSTALLER_ABORT_DETECTMANIFEST'));

			return false;
		}

		if (!is_object($adapter))
		{
			return false;
		}

		// Add the languages from the package itself
		if (method_exists($adapter, 'loadLanguage'))
		{
			$adapter->loadLanguage($path);
		}

		// Fire the onExtensionBeforeInstall event.
		PluginHelper::importPlugin('extension');
		$dispatcher = \JEventDispatcher::getInstance();
		$dispatcher->trigger(
			'onExtensionBeforeInstall',
			array(
				'method' => 'install',
				'type' => $this->manifest->attributes()->type,
				'manifest' => $this->manifest,
				'extension' => 0,
			)
		);

		// Run the install
		$result = $adapter->install();

		// Fire the onExtensionAfterInstall
		$dispatcher->trigger(
			'onExtensionAfterInstall',
			array('installer' => clone $this, 'eid' =>
$result)
		);

		if ($result !== false)
		{
			// Refresh versionable assets cache
			\JFactory::getApplication()->flushAssets();

			return true;
		}

		return false;
	}

	/**
	 * Discovered package installation method
	 *
	 * @param   integer  $eid  Extension ID
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   3.1
	 */
	public function discover_install($eid = null)
	{
		if (!$eid)
		{
			$this->abort(\JText::_('JLIB_INSTALLER_ABORT_EXTENSIONNOTVALID'));

			return false;
		}

		if (!$this->extension->load($eid))
		{
			$this->abort(\JText::_('JLIB_INSTALLER_ABORT_LOAD_DETAILS'));

			return false;
		}

		if ($this->extension->state != -1)
		{
			$this->abort(\JText::_('JLIB_INSTALLER_ABORT_ALREADYINSTALLED'));

			return false;
		}

		// Load the adapter(s) for the install manifest
		$type   = $this->extension->type;
		$params = array('extension' => $this->extension,
'route' => 'discover_install');

		$adapter = $this->getAdapter($type, $params);

		if (!is_object($adapter))
		{
			return false;
		}

		if (!method_exists($adapter, 'discover_install') ||
!$adapter->getDiscoverInstallSupported())
		{
			$this->abort(\JText::sprintf('JLIB_INSTALLER_ERROR_DISCOVER_INSTALL_UNSUPPORTED',
$type));

			return false;
		}

		// The adapter needs to prepare itself
		if (method_exists($adapter, 'prepareDiscoverInstall'))
		{
			try
			{
				$adapter->prepareDiscoverInstall();
			}
			catch (\RuntimeException $e)
			{
				$this->abort($e->getMessage());

				return false;
			}
		}

		// Add the languages from the package itself
		if (method_exists($adapter, 'loadLanguage'))
		{
			$adapter->loadLanguage();
		}

		// Fire the onExtensionBeforeInstall event.
		PluginHelper::importPlugin('extension');
		$dispatcher = \JEventDispatcher::getInstance();
		$dispatcher->trigger(
			'onExtensionBeforeInstall',
			array(
				'method' => 'discover_install',
				'type' => $this->extension->get('type'),
				'manifest' => null,
				'extension' =>
$this->extension->get('extension_id'),
			)
		);

		// Run the install
		$result = $adapter->discover_install();

		// Fire the onExtensionAfterInstall
		$dispatcher->trigger(
			'onExtensionAfterInstall',
			array('installer' => clone $this, 'eid' =>
$result)
		);

		if ($result !== false)
		{
			// Refresh versionable assets cache
			\JFactory::getApplication()->flushAssets();

			return true;
		}

		return false;
	}

	/**
	 * Extension discover method
	 *
	 * Asks each adapter to find extensions
	 *
	 * @return  InstallerExtension[]
	 *
	 * @since   3.1
	 */
	public function discover()
	{
		$this->loadAllAdapters();
		$results = array();

		foreach ($this->_adapters as $adapter)
		{
			// Joomla! 1.5 installation adapter legacy support
			if (method_exists($adapter, 'discover'))
			{
				$tmp = $adapter->discover();

				// If its an array and has entries
				if (is_array($tmp) && count($tmp))
				{
					// Merge it into the system
					$results = array_merge($results, $tmp);
				}
			}
		}

		return $results;
	}

	/**
	 * Package update method
	 *
	 * @param   string  $path  Path to package source folder
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   3.1
	 */
	public function update($path = null)
	{
		if ($path && \JFolder::exists($path))
		{
			$this->setPath('source', $path);
		}
		else
		{
			$this->abort(\JText::_('JLIB_INSTALLER_ABORT_NOUPDATEPATH'));

			return false;
		}

		if (!$adapter = $this->setupInstall('update', true))
		{
			$this->abort(\JText::_('JLIB_INSTALLER_ABORT_DETECTMANIFEST'));

			return false;
		}

		if (!is_object($adapter))
		{
			return false;
		}

		// Add the languages from the package itself
		if (method_exists($adapter, 'loadLanguage'))
		{
			$adapter->loadLanguage($path);
		}

		// Fire the onExtensionBeforeUpdate event.
		PluginHelper::importPlugin('extension');
		$dispatcher = \JEventDispatcher::getInstance();
		$dispatcher->trigger('onExtensionBeforeUpdate',
array('type' => $this->manifest->attributes()->type,
'manifest' => $this->manifest));

		// Run the update
		$result = $adapter->update();

		// Fire the onExtensionAfterUpdate
		$dispatcher->trigger(
			'onExtensionAfterUpdate',
			array('installer' => clone $this, 'eid' =>
$result)
		);

		if ($result !== false)
		{
			return true;
		}

		return false;
	}

	/**
	 * Package uninstallation method
	 *
	 * @param   string   $type        Package type
	 * @param   mixed    $identifier  Package identifier for adapter
	 * @param   integer  $cid         Application ID; deprecated in 1.6
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   3.1
	 */
	public function uninstall($type, $identifier, $cid = 0)
	{
		$params = array('extension' => $this->extension,
'route' => 'uninstall');

		$adapter = $this->getAdapter($type, $params);

		if (!is_object($adapter))
		{
			return false;
		}

		// We don't load languages here, we get the extension adapter to
work it out
		// Fire the onExtensionBeforeUninstall event.
		PluginHelper::importPlugin('extension');
		$dispatcher = \JEventDispatcher::getInstance();
		$dispatcher->trigger('onExtensionBeforeUninstall',
array('eid' => $identifier));

		// Run the uninstall
		$result = $adapter->uninstall($identifier);

		// Fire the onExtensionAfterInstall
		$dispatcher->trigger(
			'onExtensionAfterUninstall',
			array('installer' => clone $this, 'eid' =>
$identifier, 'result' => $result)
		);

		// Refresh versionable assets cache
		\JFactory::getApplication()->flushAssets();

		return $result;
	}

	/**
	 * Refreshes the manifest cache stored in #__extensions
	 *
	 * @param   integer  $eid  Extension ID
	 *
	 * @return  boolean
	 *
	 * @since   3.1
	 */
	public function refreshManifestCache($eid)
	{
		if ($eid)
		{
			if (!$this->extension->load($eid))
			{
				$this->abort(\JText::_('JLIB_INSTALLER_ABORT_LOAD_DETAILS'));

				return false;
			}

			if ($this->extension->state == -1)
			{
				$this->abort(\JText::sprintf('JLIB_INSTALLER_ABORT_REFRESH_MANIFEST_CACHE',
$this->extension->name));

				return false;
			}

			// Fetch the adapter
			$adapter = $this->getAdapter($this->extension->type);

			if (!is_object($adapter))
			{
				return false;
			}

			if (!method_exists($adapter, 'refreshManifestCache'))
			{
				$this->abort(\JText::sprintf('JLIB_INSTALLER_ABORT_METHODNOTSUPPORTED_TYPE',
$this->extension->type));

				return false;
			}

			$result = $adapter->refreshManifestCache();

			if ($result !== false)
			{
				return true;
			}
			else
			{
				return false;
			}
		}

		$this->abort(\JText::_('JLIB_INSTALLER_ABORT_REFRESH_MANIFEST_CACHE_VALID'));

		return false;
	}

	// Utility functions

	/**
	 * Prepare for installation: this method sets the installation directory,
finds
	 * and checks the installation file and verifies the installation type.
	 *
	 * @param   string   $route          The install route being followed
	 * @param   boolean  $returnAdapter  Flag to return the instantiated
adapter
	 *
	 * @return  boolean|InstallerAdapter  InstallerAdapter object if
explicitly requested otherwise boolean
	 *
	 * @since   3.1
	 */
	public function setupInstall($route = 'install', $returnAdapter
= false)
	{
		// We need to find the installation manifest file
		if (!$this->findManifest())
		{
			return false;
		}

		// Load the adapter(s) for the install manifest
		$type   = (string) $this->manifest->attributes()->type;
		$params = array('route' => $route, 'manifest'
=> $this->getManifest());

		// Load the adapter
		$adapter = $this->getAdapter($type, $params);

		if ($returnAdapter)
		{
			return $adapter;
		}

		return true;
	}

	/**
	 * Backward compatible method to parse through a queries element of the
	 * installation manifest file and take appropriate action.
	 *
	 * @param   \SimpleXMLElement  $element  The XML node to process
	 *
	 * @return  mixed  Number of queries processed or False on error
	 *
	 * @since   3.1
	 */
	public function parseQueries(\SimpleXMLElement $element)
	{
		// Get the database connector object
		$db = & $this->_db;

		if (!$element || !count($element->children()))
		{
			// Either the tag does not exist or has no children therefore we return
zero files processed.
			return 0;
		}

		// Get the array of query nodes to process
		$queries = $element->children();

		if (count($queries) === 0)
		{
			// No queries to process
			return 0;
		}

		$update_count = 0;

		// Process each query in the $queries array (children of $tagName).
		foreach ($queries as $query)
		{
			$db->setQuery($db->convertUtf8mb4QueryToUtf8($query));

			try
			{
				$db->execute();
			}
			catch (\JDatabaseExceptionExecuting $e)
			{
				\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_SQL_ERROR',
$e->getMessage()), \JLog::WARNING, 'jerror');

				return false;
			}

			$update_count++;
		}

		return $update_count;
	}

	/**
	 * Method to extract the name of a discreet installation sql file from the
installation manifest file.
	 *
	 * @param   object  $element  The XML node to process
	 *
	 * @return  mixed  Number of queries processed or False on error
	 *
	 * @since   3.1
	 */
	public function parseSQLFiles($element)
	{
		if (!$element || !count($element->children()))
		{
			// The tag does not exist.
			return 0;
		}

		$db = & $this->_db;

		// TODO - At 4.0 we can change this to use `getServerType()` since SQL
Server will not be supported
		$dbDriver = strtolower($db->name);

		if ($db->getServerType() === 'mysql')
		{
			$dbDriver = 'mysql';
		}
		elseif ($db->getServerType() === 'postgresql')
		{
			$dbDriver = 'postgresql';
		}

		$update_count = 0;

		// Get the name of the sql file to process
		foreach ($element->children() as $file)
		{
			$fCharset = strtolower($file->attributes()->charset) ===
'utf8' ? 'utf8' : '';
			$fDriver  = strtolower($file->attributes()->driver);

			if ($fDriver === 'mysqli' || $fDriver ===
'pdomysql')
			{
				$fDriver = 'mysql';
			}
			elseif ($fDriver === 'pgsql')
			{
				$fDriver = 'postgresql';
			}

			if ($fCharset === 'utf8' && $fDriver == $dbDriver)
			{
				$sqlfile = $this->getPath('extension_root') .
'/' . trim($file);

				// Check that sql files exists before reading. Otherwise raise error
for rollback
				if (!file_exists($sqlfile))
				{
					\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_SQL_FILENOTFOUND',
$sqlfile), \JLog::WARNING, 'jerror');

					return false;
				}

				$buffer = file_get_contents($sqlfile);

				// Graceful exit and rollback if read not successful
				if ($buffer === false)
				{
					\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_SQL_READBUFFER'),
\JLog::WARNING, 'jerror');

					return false;
				}

				// Create an array of queries from the sql file
				$queries = \JDatabaseDriver::splitSql($buffer);

				if (count($queries) === 0)
				{
					// No queries to process
					continue;
				}

				// Process each query in the $queries array (split out of sql file).
				foreach ($queries as $query)
				{
					$db->setQuery($db->convertUtf8mb4QueryToUtf8($query));

					try
					{
						$db->execute();
					}
					catch (\JDatabaseExceptionExecuting $e)
					{
						\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_SQL_ERROR',
$e->getMessage()), \JLog::WARNING, 'jerror');

						return false;
					}

					$update_count++;
				}
			}
		}

		return $update_count;
	}

	/**
	 * Set the schema version for an extension by looking at its latest update
	 *
	 * @param   \SimpleXMLElement  $schema  Schema Tag
	 * @param   integer            $eid     Extension ID
	 *
	 * @return  void
	 *
	 * @since   3.1
	 */
	public function setSchemaVersion(\SimpleXMLElement $schema, $eid)
	{
		if ($eid && $schema)
		{
			$db = \JFactory::getDbo();
			$schemapaths = $schema->children();

			if (!$schemapaths)
			{
				return;
			}

			if (count($schemapaths))
			{
				$dbDriver = strtolower($db->name);

				if ($db->getServerType() === 'mysql')
				{
					$dbDriver = 'mysql';
				}
				elseif ($db->getServerType() === 'postgresql')
				{
					$dbDriver = 'postgresql';
				}

				$schemapath = '';

				foreach ($schemapaths as $entry)
				{
					$attrs = $entry->attributes();

					if ($attrs['type'] == $dbDriver)
					{
						$schemapath = $entry;
						break;
					}
				}

				if ($schemapath !== '')
				{
					$files = str_replace('.sql', '',
\JFolder::files($this->getPath('extension_root') .
'/' . $schemapath, '\.sql$'));
					usort($files, 'version_compare');

					// Update the database
					$query = $db->getQuery(true)
						->delete('#__schemas')
						->where('extension_id = ' . $eid);
					$db->setQuery($query);

					if ($db->execute())
					{
						$query->clear()
							->insert($db->quoteName('#__schemas'))
							->columns(array($db->quoteName('extension_id'),
$db->quoteName('version_id')))
							->values($eid . ', ' . $db->quote(end($files)));
						$db->setQuery($query);
						$db->execute();
					}
				}
			}
		}
	}

	/**
	 * Method to process the updates for an item
	 *
	 * @param   \SimpleXMLElement  $schema  The XML node to process
	 * @param   integer            $eid     Extension Identifier
	 *
	 * @return  boolean           Result of the operations
	 *
	 * @since   3.1
	 */
	public function parseSchemaUpdates(\SimpleXMLElement $schema, $eid)
	{
		$update_count = 0;

		// Ensure we have an XML element and a valid extension id
		if ($eid && $schema)
		{
			$db = \JFactory::getDbo();
			$schemapaths = $schema->children();

			if (count($schemapaths))
			{
				// TODO - At 4.0 we can change this to use `getServerType()` since SQL
Server will not be supported
				$dbDriver = strtolower($db->name);

				if ($db->getServerType() === 'mysql')
				{
					$dbDriver = 'mysql';
				}
				elseif ($db->getServerType() === 'postgresql')
				{
					$dbDriver = 'postgresql';
				}

				$schemapath = '';

				foreach ($schemapaths as $entry)
				{
					$attrs = $entry->attributes();

					// Assuming that the type is a mandatory attribute but if it is not
mandatory then there should be a discussion for it.
					$uDriver = strtolower($attrs['type']);

					if ($uDriver === 'mysqli' || $uDriver ===
'pdomysql')
					{
						$uDriver = 'mysql';
					}
					elseif ($uDriver === 'pgsql')
					{
						$uDriver = 'postgresql';
					}

					if ($uDriver == $dbDriver)
					{
						$schemapath = $entry;
						break;
					}
				}

				if ($schemapath !== '')
				{
					$files = \JFolder::files($this->getPath('extension_root')
. '/' . $schemapath, '\.sql$');

					if (empty($files))
					{
						return $update_count;
					}

					$files = str_replace('.sql', '', $files);
					usort($files, 'version_compare');

					$query = $db->getQuery(true)
						->select('version_id')
						->from('#__schemas')
						->where('extension_id = ' . $eid);
					$db->setQuery($query);
					$version = $db->loadResult();

					// No version - use initial version.
					if (!$version)
					{
						$version = '0.0.0';
					}

					foreach ($files as $file)
					{
						if (version_compare($file, $version) > 0)
						{
							$buffer =
file_get_contents($this->getPath('extension_root') .
'/' . $schemapath . '/' . $file . '.sql');

							// Graceful exit and rollback if read not successful
							if ($buffer === false)
							{
								\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_SQL_READBUFFER'),
\JLog::WARNING, 'jerror');

								return false;
							}

							// Create an array of queries from the sql file
							$queries = \JDatabaseDriver::splitSql($buffer);

							if (count($queries) === 0)
							{
								// No queries to process
								continue;
							}

							// Process each query in the $queries array (split out of sql file).
							foreach ($queries as $query)
							{
								$db->setQuery($db->convertUtf8mb4QueryToUtf8($query));

								try
								{
									$db->execute();
								}
								catch (\JDatabaseExceptionExecuting $e)
								{
									\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_SQL_ERROR',
$e->getMessage()), \JLog::WARNING, 'jerror');

									return false;
								}

								$queryString = (string) $query;
								$queryString = str_replace(array("\r", "\n"),
array('', ' '), substr($queryString, 0, 80));
								\JLog::add(\JText::sprintf('JLIB_INSTALLER_UPDATE_LOG_QUERY',
$file, $queryString), \JLog::INFO, 'Update');

								$update_count++;
							}
						}
					}

					// Update the database
					$query = $db->getQuery(true)
						->delete('#__schemas')
						->where('extension_id = ' . $eid);
					$db->setQuery($query);

					if ($db->execute())
					{
						$query->clear()
							->insert($db->quoteName('#__schemas'))
							->columns(array($db->quoteName('extension_id'),
$db->quoteName('version_id')))
							->values($eid . ', ' . $db->quote(end($files)));
						$db->setQuery($query);
						$db->execute();
					}
				}
			}
		}

		return $update_count;
	}

	/**
	 * Method to parse through a files element of the installation manifest
and take appropriate
	 * action.
	 *
	 * @param   \SimpleXMLElement  $element   The XML node to process
	 * @param   integer            $cid       Application ID of application to
install to
	 * @param   array              $oldFiles  List of old files
(SimpleXMLElement's)
	 * @param   array              $oldMD5    List of old MD5 sums (indexed by
filename with value as MD5)
	 *
	 * @return  boolean      True on success
	 *
	 * @since   3.1
	 */
	public function parseFiles(\SimpleXMLElement $element, $cid = 0, $oldFiles
= null, $oldMD5 = null)
	{
		// Get the array of file nodes to process; we checked whether this had
children above.
		if (!$element || !count($element->children()))
		{
			// Either the tag does not exist or has no children (hence no files to
process) therefore we return zero files processed.
			return 0;
		}

		$copyfiles = array();

		// Get the client info
		$client = ApplicationHelper::getClientInfo($cid);

		/*
		 * Here we set the folder we are going to remove the files from.
		 */
		if ($client)
		{
			$pathname = 'extension_' . $client->name;
			$destination = $this->getPath($pathname);
		}
		else
		{
			$pathname = 'extension_root';
			$destination = $this->getPath($pathname);
		}

		/*
		 * Here we set the folder we are going to copy the files from.
		 *
		 * Does the element have a folder attribute?
		 *
		 * If so this indicates that the files are in a subdirectory of the
source
		 * folder and we should append the folder attribute to the source path
when
		 * copying files.
		 */

		$folder = (string) $element->attributes()->folder;

		if ($folder && file_exists($this->getPath('source')
. '/' . $folder))
		{
			$source = $this->getPath('source') . '/' .
$folder;
		}
		else
		{
			$source = $this->getPath('source');
		}

		// Work out what files have been deleted
		if ($oldFiles && ($oldFiles instanceof \SimpleXMLElement))
		{
			$oldEntries = $oldFiles->children();

			if (count($oldEntries))
			{
				$deletions = $this->findDeletedFiles($oldEntries,
$element->children());

				foreach ($deletions['folders'] as $deleted_folder)
				{
					\JFolder::delete($destination . '/' . $deleted_folder);
				}

				foreach ($deletions['files'] as $deleted_file)
				{
					\JFile::delete($destination . '/' . $deleted_file);
				}
			}
		}

		$path = array();

		// Copy the MD5SUMS file if it exists
		if (file_exists($source . '/MD5SUMS'))
		{
			$path['src'] = $source . '/MD5SUMS';
			$path['dest'] = $destination . '/MD5SUMS';
			$path['type'] = 'file';
			$copyfiles[] = $path;
		}

		// Process each file in the $files array (children of $tagName).
		foreach ($element->children() as $file)
		{
			$path['src'] = $source . '/' . $file;
			$path['dest'] = $destination . '/' . $file;

			// Is this path a file or folder?
			$path['type'] = $file->getName() === 'folder' ?
'folder' : 'file';

			/*
			 * Before we can add a file to the copyfiles array we need to ensure
			 * that the folder we are copying our file to exits and if it
doesn't,
			 * we need to create it.
			 */

			if (basename($path['dest']) !== $path['dest'])
			{
				$newdir = dirname($path['dest']);

				if (!\JFolder::create($newdir))
				{
					\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_CREATE_DIRECTORY',
$newdir), \JLog::WARNING, 'jerror');

					return false;
				}
			}

			// Add the file to the copyfiles array
			$copyfiles[] = $path;
		}

		return $this->copyFiles($copyfiles);
	}

	/**
	 * Method to parse through a languages element of the installation
manifest and take appropriate
	 * action.
	 *
	 * @param   \SimpleXMLElement  $element  The XML node to process
	 * @param   integer            $cid      Application ID of application to
install to
	 *
	 * @return  boolean  True on success
	 *
	 * @since   3.1
	 */
	public function parseLanguages(\SimpleXMLElement $element, $cid = 0)
	{
		// TODO: work out why the below line triggers 'node no longer
exists' errors with files
		if (!$element || !count($element->children()))
		{
			// Either the tag does not exist or has no children therefore we return
zero files processed.
			return 0;
		}

		$copyfiles = array();

		// Get the client info
		$client = ApplicationHelper::getClientInfo($cid);

		// Here we set the folder we are going to copy the files to.
		// 'languages' Files are copied to JPATH_BASE/language/ folder

		$destination = $client->path . '/language';

		/*
		 * Here we set the folder we are going to copy the files from.
		 *
		 * Does the element have a folder attribute?
		 *
		 * If so this indicates that the files are in a subdirectory of the
source
		 * folder and we should append the folder attribute to the source path
when
		 * copying files.
		 */

		$folder = (string) $element->attributes()->folder;

		if ($folder && file_exists($this->getPath('source')
. '/' . $folder))
		{
			$source = $this->getPath('source') . '/' .
$folder;
		}
		else
		{
			$source = $this->getPath('source');
		}

		// Process each file in the $files array (children of $tagName).
		foreach ($element->children() as $file)
		{
			/*
			 * Language files go in a subfolder based on the language code, ie.
			 * <language
tag="en-US">en-US.mycomponent.ini</language>
			 * would go in the en-US subdirectory of the language folder.
			 */

			// We will only install language files where a core language pack
			// already exists.

			if ((string) $file->attributes()->tag !== '')
			{
				$path['src'] = $source . '/' . $file;

				if ((string) $file->attributes()->client !== '')
				{
					// Override the client
					$langclient = ApplicationHelper::getClientInfo((string)
$file->attributes()->client, true);
					$path['dest'] = $langclient->path .
'/language/' . $file->attributes()->tag . '/' .
basename((string) $file);
				}
				else
				{
					// Use the default client
					$path['dest'] = $destination . '/' .
$file->attributes()->tag . '/' . basename((string) $file);
				}

				// If the language folder is not present, then the core pack
hasn't been installed... ignore
				if (!\JFolder::exists(dirname($path['dest'])))
				{
					continue;
				}
			}
			else
			{
				$path['src'] = $source . '/' . $file;
				$path['dest'] = $destination . '/' . $file;
			}

			/*
			 * Before we can add a file to the copyfiles array we need to ensure
			 * that the folder we are copying our file to exits and if it
doesn't,
			 * we need to create it.
			 */

			if (basename($path['dest']) !== $path['dest'])
			{
				$newdir = dirname($path['dest']);

				if (!\JFolder::create($newdir))
				{
					\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_CREATE_DIRECTORY',
$newdir), \JLog::WARNING, 'jerror');

					return false;
				}
			}

			// Add the file to the copyfiles array
			$copyfiles[] = $path;
		}

		return $this->copyFiles($copyfiles);
	}

	/**
	 * Method to parse through a media element of the installation manifest
and take appropriate
	 * action.
	 *
	 * @param   \SimpleXMLElement  $element  The XML node to process
	 * @param   integer            $cid      Application ID of application to
install to
	 *
	 * @return  boolean     True on success
	 *
	 * @since   3.1
	 */
	public function parseMedia(\SimpleXMLElement $element, $cid = 0)
	{
		if (!$element || !count($element->children()))
		{
			// Either the tag does not exist or has no children therefore we return
zero files processed.
			return 0;
		}

		$copyfiles = array();

		// Here we set the folder we are going to copy the files to.
		// Default 'media' Files are copied to the JPATH_BASE/media
folder

		$folder = ((string) $element->attributes()->destination) ?
'/' . $element->attributes()->destination : null;
		$destination = \JPath::clean(JPATH_ROOT . '/media' . $folder);

		// Here we set the folder we are going to copy the files from.

		/*
		 * Does the element have a folder attribute?
		 * If so this indicates that the files are in a subdirectory of the
source
		 * folder and we should append the folder attribute to the source path
when
		 * copying files.
		 */

		$folder = (string) $element->attributes()->folder;

		if ($folder && file_exists($this->getPath('source')
. '/' . $folder))
		{
			$source = $this->getPath('source') . '/' .
$folder;
		}
		else
		{
			$source = $this->getPath('source');
		}

		// Process each file in the $files array (children of $tagName).
		foreach ($element->children() as $file)
		{
			$path['src'] = $source . '/' . $file;
			$path['dest'] = $destination . '/' . $file;

			// Is this path a file or folder?
			$path['type'] = $file->getName() === 'folder' ?
'folder' : 'file';

			/*
			 * Before we can add a file to the copyfiles array we need to ensure
			 * that the folder we are copying our file to exits and if it
doesn't,
			 * we need to create it.
			 */

			if (basename($path['dest']) !== $path['dest'])
			{
				$newdir = dirname($path['dest']);

				if (!\JFolder::create($newdir))
				{
					\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_CREATE_DIRECTORY',
$newdir), \JLog::WARNING, 'jerror');

					return false;
				}
			}

			// Add the file to the copyfiles array
			$copyfiles[] = $path;
		}

		return $this->copyFiles($copyfiles);
	}

	/**
	 * Method to parse the parameters of an extension, build the JSON string
for its default parameters, and return the JSON string.
	 *
	 * @return  string  JSON string of parameter values
	 *
	 * @since   3.1
	 * @note    This method must always return a JSON compliant string
	 */
	public function getParams()
	{
		// Validate that we have a fieldset to use
		if (!isset($this->manifest->config->fields->fieldset))
		{
			return '{}';
		}

		// Getting the fieldset tags
		$fieldsets = $this->manifest->config->fields->fieldset;

		// Creating the data collection variable:
		$ini = array();

		// Iterating through the fieldsets:
		foreach ($fieldsets as $fieldset)
		{
			if (!count($fieldset->children()))
			{
				// Either the tag does not exist or has no children therefore we return
zero files processed.
				return '{}';
			}

			// Iterating through the fields and collecting the name/default values:
			foreach ($fieldset as $field)
			{
				// Check against the null value since otherwise default values like
"0"
				// cause entire parameters to be skipped.

				if (($name = $field->attributes()->name) === null)
				{
					continue;
				}

				if (($value = $field->attributes()->default) === null)
				{
					continue;
				}

				$ini[(string) $name] = (string) $value;
			}
		}

		return json_encode($ini);
	}

	/**
	 * Copyfiles
	 *
	 * Copy files from source directory to the target directory
	 *
	 * @param   array    $files      Array with filenames
	 * @param   boolean  $overwrite  True if existing files can be replaced
	 *
	 * @return  boolean  True on success
	 *
	 * @since   3.1
	 */
	public function copyFiles($files, $overwrite = null)
	{
		/*
		 * To allow for manual override on the overwriting flag, we check to see
if
		 * the $overwrite flag was set and is a boolean value.  If not, use the
object
		 * allowOverwrite flag.
		 */

		if ($overwrite === null || !is_bool($overwrite))
		{
			$overwrite = $this->overwrite;
		}

		/*
		 * $files must be an array of filenames.  Verify that it is an array with
		 * at least one file to copy.
		 */
		if (is_array($files) && count($files) > 0)
		{
			foreach ($files as $file)
			{
				// Get the source and destination paths
				$filesource = \JPath::clean($file['src']);
				$filedest = \JPath::clean($file['dest']);
				$filetype = array_key_exists('type', $file) ?
$file['type'] : 'file';

				if (!file_exists($filesource))
				{
					/*
					 * The source file does not exist.  Nothing to copy so set an error
					 * and return false.
					 */
					\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_NO_FILE',
$filesource), \JLog::WARNING, 'jerror');

					return false;
				}
				elseif (($exists = file_exists($filedest)) && !$overwrite)
				{
					// It's okay if the manifest already exists
					if ($this->getPath('manifest') === $filesource)
					{
						continue;
					}

					// The destination file already exists and the overwrite flag is
false.
					// Set an error and return false.
					\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_FILE_EXISTS',
$filedest), \JLog::WARNING, 'jerror');

					return false;
				}
				else
				{
					// Copy the folder or file to the new location.
					if ($filetype === 'folder')
					{
						if (!\JFolder::copy($filesource, $filedest, null, $overwrite))
						{
							\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_FAIL_COPY_FOLDER',
$filesource, $filedest), \JLog::WARNING, 'jerror');

							return false;
						}

						$step = array('type' => 'folder',
'path' => $filedest);
					}
					else
					{
						if (!\JFile::copy($filesource, $filedest, null))
						{
							\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_FAIL_COPY_FILE',
$filesource, $filedest), \JLog::WARNING, 'jerror');

							// In 3.2, TinyMCE language handling changed.  Display a special
notice in case an older language pack is installed.
							if (strpos($filedest,
'media/editors/tinymce/jscripts/tiny_mce/langs'))
							{
								\JLog::add(\JText::_('JLIB_INSTALLER_NOT_ERROR'),
\JLog::WARNING, 'jerror');
							}

							return false;
						}

						$step = array('type' => 'file',
'path' => $filedest);
					}

					/*
					 * Since we copied a file/folder, we want to add it to the
installation step stack so that
					 * in case we have to roll back the installation we can remove the
files copied.
					 */
					if (!$exists)
					{
						$this->stepStack[] = $step;
					}
				}
			}
		}
		else
		{
			// The $files variable was either not an array or an empty array
			return false;
		}

		return count($files);
	}

	/**
	 * Method to parse through a files element of the installation manifest
and remove
	 * the files that were installed
	 *
	 * @param   object   $element  The XML node to process
	 * @param   integer  $cid      Application ID of application to remove
from
	 *
	 * @return  boolean  True on success
	 *
	 * @since   3.1
	 */
	public function removeFiles($element, $cid = 0)
	{
		if (!$element || !count($element->children()))
		{
			// Either the tag does not exist or has no children therefore we return
zero files processed.
			return true;
		}

		$retval = true;

		// Get the client info if we're using a specific client
		if ($cid > -1)
		{
			$client = ApplicationHelper::getClientInfo($cid);
		}
		else
		{
			$client = null;
		}

		// Get the array of file nodes to process
		$files = $element->children();

		if (count($files) === 0)
		{
			// No files to process
			return true;
		}

		$folder = '';

		/*
		 * Here we set the folder we are going to remove the files from.  There
are a few
		 * special cases that need to be considered for certain reserved tags.
		 */
		switch ($element->getName())
		{
			case 'media':
				if ((string) $element->attributes()->destination)
				{
					$folder = (string) $element->attributes()->destination;
				}
				else
				{
					$folder = '';
				}

				$source = $client->path . '/media/' . $folder;

				break;

			case 'languages':
				$lang_client = (string) $element->attributes()->client;

				if ($lang_client)
				{
					$client = ApplicationHelper::getClientInfo($lang_client, true);
					$source = $client->path . '/language';
				}
				else
				{
					if ($client)
					{
						$source = $client->path . '/language';
					}
					else
					{
						$source = '';
					}
				}

				break;

			default:
				if ($client)
				{
					$pathname = 'extension_' . $client->name;
					$source = $this->getPath($pathname);
				}
				else
				{
					$pathname = 'extension_root';
					$source = $this->getPath($pathname);
				}

				break;
		}

		// Process each file in the $files array (children of $tagName).
		foreach ($files as $file)
		{
			/*
			 * If the file is a language, we must handle it differently.  Language
files
			 * go in a subdirectory based on the language code, ie.
			 * <language
tag="en_US">en_US.mycomponent.ini</language>
			 * would go in the en_US subdirectory of the languages directory.
			 */

			if ($file->getName() === 'language' && (string)
$file->attributes()->tag !== '')
			{
				if ($source)
				{
					$path = $source . '/' . $file->attributes()->tag .
'/' . basename((string) $file);
				}
				else
				{
					$target_client = ApplicationHelper::getClientInfo((string)
$file->attributes()->client, true);
					$path = $target_client->path . '/language/' .
$file->attributes()->tag . '/' . basename((string) $file);
				}

				// If the language folder is not present, then the core pack
hasn't been installed... ignore
				if (!\JFolder::exists(dirname($path)))
				{
					continue;
				}
			}
			else
			{
				$path = $source . '/' . $file;
			}

			// Actually delete the files/folders

			if (is_dir($path))
			{
				$val = \JFolder::delete($path);
			}
			else
			{
				$val = \JFile::delete($path);
			}

			if ($val === false)
			{
				\JLog::add('Failed to delete ' . $path, \JLog::WARNING,
'jerror');
				$retval = false;
			}
		}

		if (!empty($folder))
		{
			\JFolder::delete($source);
		}

		return $retval;
	}

	/**
	 * Copies the installation manifest file to the extension folder in the
given client
	 *
	 * @param   integer  $cid  Where to copy the installfile [optional:
defaults to 1 (admin)]
	 *
	 * @return  boolean  True on success, False on error
	 *
	 * @since   3.1
	 */
	public function copyManifest($cid = 1)
	{
		// Get the client info
		$client = ApplicationHelper::getClientInfo($cid);

		$path['src'] = $this->getPath('manifest');

		if ($client)
		{
			$pathname = 'extension_' . $client->name;
			$path['dest'] = $this->getPath($pathname) . '/' .
basename($this->getPath('manifest'));
		}
		else
		{
			$pathname = 'extension_root';
			$path['dest'] = $this->getPath($pathname) . '/' .
basename($this->getPath('manifest'));
		}

		return $this->copyFiles(array($path), true);
	}

	/**
	 * Tries to find the package manifest file
	 *
	 * @return  boolean  True on success, False on error
	 *
	 * @since   3.1
	 */
	public function findManifest()
	{
		// Do nothing if folder does not exist for some reason
		if (!\JFolder::exists($this->getPath('source')))
		{
			return false;
		}

		// Main folder manifests (higher priority)
		$parentXmlfiles = \JFolder::files($this->getPath('source'),
'.xml$', false, true);

		// Search for children manifests (lower priority)
		$allXmlFiles    = \JFolder::files($this->getPath('source'),
'.xml$', 1, true);

		// Create an unique array of files ordered by priority
		$xmlfiles = array_unique(array_merge($parentXmlfiles, $allXmlFiles));

		// If at least one XML file exists
		if (!empty($xmlfiles))
		{
			foreach ($xmlfiles as $file)
			{
				// Is it a valid Joomla installation manifest file?
				$manifest = $this->isManifest($file);

				if ($manifest !== null)
				{
					// If the root method attribute is set to upgrade, allow file
overwrite
					if ((string) $manifest->attributes()->method ===
'upgrade')
					{
						$this->upgrade = true;
						$this->overwrite = true;
					}

					// If the overwrite option is set, allow file overwriting
					if ((string) $manifest->attributes()->overwrite ===
'true')
					{
						$this->overwrite = true;
					}

					// Set the manifest object and path
					$this->manifest = $manifest;
					$this->setPath('manifest', $file);

					// Set the installation source path to that of the manifest file
					$this->setPath('source', dirname($file));

					return true;
				}
			}

			// None of the XML files found were valid install files
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_NOTFINDJOOMLAXMLSETUPFILE'),
\JLog::WARNING, 'jerror');

			return false;
		}
		else
		{
			// No XML files were found in the install folder
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_NOTFINDXMLSETUPFILE'),
\JLog::WARNING, 'jerror');

			return false;
		}
	}

	/**
	 * Is the XML file a valid Joomla installation manifest file.
	 *
	 * @param   string  $file  An xmlfile path to check
	 *
	 * @return  \SimpleXMLElement|null  A \SimpleXMLElement, or null if the
file failed to parse
	 *
	 * @since   3.1
	 */
	public function isManifest($file)
	{
		$xml = simplexml_load_file($file);

		// If we cannot load the XML file return null
		if (!$xml)
		{
			return;
		}

		// Check for a valid XML root tag.
		if ($xml->getName() !== 'extension')
		{
			return;
		}

		// Valid manifest file return the object
		return $xml;
	}

	/**
	 * Generates a manifest cache
	 *
	 * @return string serialised manifest data
	 *
	 * @since   3.1
	 */
	public function generateManifestCache()
	{
		return
json_encode(self::parseXMLInstallFile($this->getPath('manifest')));
	}

	/**
	 * Cleans up discovered extensions if they're being installed some
other way
	 *
	 * @param   string   $type     The type of extension (component, etc)
	 * @param   string   $element  Unique element identifier (e.g.
com_content)
	 * @param   string   $folder   The folder of the extension (plugins; e.g.
system)
	 * @param   integer  $client   The client application (administrator or
site)
	 *
	 * @return  object    Result of query
	 *
	 * @since   3.1
	 */
	public function cleanDiscoveredExtension($type, $element, $folder =
'', $client = 0)
	{
		$db = \JFactory::getDbo();
		$query = $db->getQuery(true)
			->delete($db->quoteName('#__extensions'))
			->where('type = ' . $db->quote($type))
			->where('element = ' . $db->quote($element))
			->where('folder = ' . $db->quote($folder))
			->where('client_id = ' . (int) $client)
			->where('state = -1');
		$db->setQuery($query);

		return $db->execute();
	}

	/**
	 * Compares two "files" entries to find deleted files/folders
	 *
	 * @param   array  $oldFiles  An array of \SimpleXMLElement objects that
are the old files
	 * @param   array  $newFiles  An array of \SimpleXMLElement objects that
are the new files
	 *
	 * @return  array  An array with the delete files and folders in
findDeletedFiles[files] and findDeletedFiles[folders] respectively
	 *
	 * @since   3.1
	 */
	public function findDeletedFiles($oldFiles, $newFiles)
	{
		// The magic find deleted files function!
		// The files that are new
		$files = array();

		// The folders that are new
		$folders = array();

		// The folders of the files that are new
		$containers = array();

		// A list of files to delete
		$files_deleted = array();

		// A list of folders to delete
		$folders_deleted = array();

		foreach ($newFiles as $file)
		{
			switch ($file->getName())
			{
				case 'folder':
					// Add any folders to the list
					$folders[] = (string) $file; // add any folders to the list
					break;

				case 'file':
				default:
					// Add any files to the list
					$files[] = (string) $file;

					// Now handle the folder part of the file to ensure we get any
containers
					// Break up the parts of the directory
					$container_parts = explode('/', dirname((string) $file));

					// Make sure this is clean and empty
					$container = '';

					foreach ($container_parts as $part)
					{
						// Iterate through each part
						// Add a slash if its not empty
						if (!empty($container))
						{
							$container .= '/';
						}

						// Aappend the folder part
						$container .= $part;

						if (!in_array($container, $containers))
						{
							// Add the container if it doesn't already exist
							$containers[] = $container;
						}
					}
					break;
			}
		}

		foreach ($oldFiles as $file)
		{
			switch ($file->getName())
			{
				case 'folder':
					if (!in_array((string) $file, $folders))
					{
						// See whether the folder exists in the new list
						if (!in_array((string) $file, $containers))
						{
							// Check if the folder exists as a container in the new list
							// If it's not in the new list or a container then delete it
							$folders_deleted[] = (string) $file;
						}
					}
					break;

				case 'file':
				default:
					if (!in_array((string) $file, $files))
					{
						// Look if the file exists in the new list
						if (!in_array(dirname((string) $file), $folders))
						{
							// Look if the file is now potentially in a folder
							$files_deleted[] = (string) $file; // not in a folder, doesn't
exist, wipe it out!
						}
					}
					break;
			}
		}

		return array('files' => $files_deleted, 'folders'
=> $folders_deleted);
	}

	/**
	 * Loads an MD5SUMS file into an associative array
	 *
	 * @param   string  $filename  Filename to load
	 *
	 * @return  array  Associative array with filenames as the index and the
MD5 as the value
	 *
	 * @since   3.1
	 */
	public function loadMD5Sum($filename)
	{
		if (!file_exists($filename))
		{
			// Bail if the file doesn't exist
			return false;
		}

		$data = file($filename, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
		$retval = array();

		foreach ($data as $row)
		{
			// Split up the data
			$results = explode('  ', $row);

			// Cull any potential prefix
			$results[1] = str_replace('./', '', $results[1]);

			// Throw into the array
			$retval[$results[1]] = $results[0];
		}

		return $retval;
	}

	/**
	 * Parse a XML install manifest file.
	 *
	 * XML Root tag should be 'install' except for languages which
use meta file.
	 *
	 * @param   string  $path  Full path to XML file.
	 *
	 * @return  array  XML metadata.
	 *
	 * @since   3.0.0
	 */
	public static function parseXMLInstallFile($path)
	{
		// Check if xml file exists.
		if (!file_exists($path))
		{
			return false;
		}

		// Read the file to see if it's a valid component XML file
		$xml = simplexml_load_file($path);

		if (!$xml)
		{
			return false;
		}

		// Check for a valid XML root tag.

		// Extensions use 'extension' as the root tag.  Languages use
'metafile' instead

		$name = $xml->getName();

		if ($name !== 'extension' && $name !==
'metafile')
		{
			unset($xml);

			return false;
		}

		$data = array();

		$data['name'] = (string) $xml->name;

		// Check if we're a language. If so use metafile.
		$data['type'] = $xml->getName() === 'metafile' ?
'language' : (string) $xml->attributes()->type;

		$data['creationDate'] = ((string) $xml->creationDate) ?:
\JText::_('JLIB_UNKNOWN');
		$data['author'] = ((string) $xml->author) ?:
\JText::_('JLIB_UNKNOWN');

		$data['copyright'] = (string) $xml->copyright;
		$data['authorEmail'] = (string) $xml->authorEmail;
		$data['authorUrl'] = (string) $xml->authorUrl;
		$data['version'] = (string) $xml->version;
		$data['description'] = (string) $xml->description;
		$data['group'] = (string) $xml->group;

		if ($xml->files && count($xml->files->children()))
		{
			$filename = \JFile::getName($path);
			$data['filename'] = \JFile::stripExt($filename);

			foreach ($xml->files->children() as $oneFile)
			{
				if ((string) $oneFile->attributes()->plugin)
				{
					$data['filename'] = (string)
$oneFile->attributes()->plugin;
					break;
				}
			}
		}

		return $data;
	}

	/**
	 * Fetches an adapter and adds it to the internal storage if an instance
is not set
	 * while also ensuring its a valid adapter name
	 *
	 * @param   string  $name     Name of adapter to return
	 * @param   array   $options  Adapter options
	 *
	 * @return  InstallerAdapter
	 *
	 * @since       3.4
	 * @deprecated  4.0  The internal adapter cache will no longer be
supported,
	 *                   use loadAdapter() to fetch an adapter instance
	 */
	public function getAdapter($name, $options = array())
	{
		$this->getAdapters($options);

		if (!$this->setAdapter($name, $this->_adapters[$name]))
		{
			return false;
		}

		return $this->_adapters[$name];
	}

	/**
	 * Gets a list of available install adapters.
	 *
	 * @param   array  $options  An array of options to inject into the
adapter
	 * @param   array  $custom   Array of custom install adapters
	 *
	 * @return  array  An array of available install adapters.
	 *
	 * @since   3.4
	 * @note    As of 4.0, this method will only return the names of available
adapters and will not
	 *          instantiate them and store to the $_adapters class var.
	 */
	public function getAdapters($options = array(), array $custom = array())
	{
		$files = new \DirectoryIterator($this->_basepath . '/' .
$this->_adapterfolder);

		// Process the core adapters
		foreach ($files as $file)
		{
			$fileName = $file->getFilename();

			// Only load for php files.
			if (!$file->isFile() || $file->getExtension() !== 'php')
			{
				continue;
			}

			// Derive the class name from the filename.
			$name  = str_ireplace('.php', '', trim($fileName));
			$name  = str_ireplace('adapter', '', trim($name));
			$class = rtrim($this->_classprefix, '\\') . '\\'
. ucfirst($name) . 'Adapter';

			if (!class_exists($class))
			{
				// Not namespaced
				$class = $this->_classprefix . ucfirst($name);
			}

			// Core adapters should autoload based on classname, keep this fallback
just in case
			if (!class_exists($class))
			{
				// Try to load the adapter object
				\JLoader::register($class, $this->_basepath . '/' .
$this->_adapterfolder . '/' . $fileName);

				if (!class_exists($class))
				{
					// Skip to next one
					continue;
				}
			}

			$this->_adapters[strtolower($name)] = $this->loadAdapter($name,
$options);
		}

		// Add any custom adapters if specified
		if (count($custom) >= 1)
		{
			foreach ($custom as $adapter)
			{
				// Setup the class name
				// TODO - Can we abstract this to not depend on the Joomla class
namespace without PHP namespaces?
				$class = $this->_classprefix . ucfirst(trim($adapter));

				// If the class doesn't exist we have nothing left to do but look
at the next type. We did our best.
				if (!class_exists($class))
				{
					continue;
				}

				$this->_adapters[$name] = $this->loadAdapter($name, $options);
			}
		}

		return $this->_adapters;
	}

	/**
	 * Method to load an adapter instance
	 *
	 * @param   string  $adapter  Adapter name
	 * @param   array   $options  Adapter options
	 *
	 * @return  InstallerAdapter
	 *
	 * @since   3.4
	 * @throws  \InvalidArgumentException
	 */
	public function loadAdapter($adapter, $options = array())
	{
		$class = rtrim($this->_classprefix, '\\') . '\\' .
ucfirst($adapter) . 'Adapter';

		if (!class_exists($class))
		{
			// Not namespaced
			$class = $this->_classprefix . ucfirst($adapter);
		}

		if (!class_exists($class))
		{
			// @deprecated 4.0 - The adapter should be autoloaded or manually
included by the caller
			$path = $this->_basepath . '/' . $this->_adapterfolder .
'/' . $adapter . '.php';

			// Try to load the adapter object
			if (!file_exists($path))
			{
				throw new \InvalidArgumentException(sprintf('The %s install
adapter does not exist.', $adapter));
			}

			// Try once more to find the class
			\JLoader::register($class, $path);

			if (!class_exists($class))
			{
				throw new \InvalidArgumentException(sprintf('The %s install
adapter does not exist.', $adapter));
			}
		}

		// Ensure the adapter type is part of the options array
		$options['type'] = $adapter;

		return new $class($this, $this->getDbo(), $options);
	}

	/**
	 * Loads all adapters.
	 *
	 * @param   array  $options  Adapter options
	 *
	 * @return  void
	 *
	 * @since       3.4
	 * @deprecated  4.0  Individual adapters should be instantiated as needed
	 * @note        This method is serving as a proxy of the legacy \JAdapter
API into the preferred API
	 */
	public function loadAllAdapters($options = array())
	{
		$this->getAdapters($options);
	}
}
PKK�[��o�a�aInstallerAdapter.phpnu�[���<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
 * @license    GNU General Public License version 2 or later; see
LICENSE.txt
 */

namespace Joomla\CMS\Installer;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Installer\Manifest\PackageManifest;
use Joomla\CMS\Table\Extension;
use Joomla\CMS\Table\Table;
use Joomla\CMS\Table\TableInterface;

\JLoader::import('joomla.base.adapterinstance');

/**
 * Abstract adapter for the installer.
 *
 * @method         Installer  getParent()  Retrieves the parent object.
 * @property-read  Installer  $parent      Parent object
 *
 * @since  3.4
 * @note   As of 4.0, this class will no longer extend from
JAdapterInstance
 */
abstract class InstallerAdapter extends \JAdapterInstance
{
	/**
	 * ID for the currently installed extension if present
	 *
	 * @var    integer
	 * @since  3.4
	 */
	protected $currentExtensionId = null;

	/**
	 * The unique identifier for the extension (e.g. mod_login)
	 *
	 * @var    string
	 * @since  3.4
	 * */
	protected $element = null;

	/**
	 * Extension object.
	 *
	 * @var    Extension
	 * @since  3.4
	 * */
	protected $extension = null;

	/**
	 * Messages rendered by custom scripts
	 *
	 * @var    string
	 * @since  3.4
	 */
	protected $extensionMessage = '';

	/**
	 * Copy of the XML manifest file.
	 *
	 * Making this object public allows extensions to customize the manifest
in custom scripts.
	 *
	 * @var    string
	 * @since  3.4
	 */
	public $manifest = null;

	/**
	 * A path to the PHP file that the scriptfile declaration in the manifest
refers to.
	 *
	 * @var    string
	 * @since  3.4
	 */
	protected $manifest_script = null;

	/**
	 * Name of the extension
	 *
	 * @var    string
	 * @since  3.4
	 */
	protected $name = null;

	/**
	 * Install function routing
	 *
	 * @var    string
	 * @since  3.4
	 */
	protected $route = 'install';

	/**
	 * Flag if the adapter supports discover installs
	 *
	 * Adapters should override this and set to false if discover install is
unsupported
	 *
	 * @var    boolean
	 * @since  3.4
	 */
	protected $supportsDiscoverInstall = true;

	/**
	 * The type of adapter in use
	 *
	 * @var    string
	 * @since  3.4
	 */
	protected $type;

	/**
	 * Constructor
	 *
	 * @param   Installer         $parent   Parent object
	 * @param   \JDatabaseDriver  $db       Database object
	 * @param   array             $options  Configuration Options
	 *
	 * @since   3.4
	 */
	public function __construct(Installer $parent, \JDatabaseDriver $db, array
$options = array())
	{
		parent::__construct($parent, $db, $options);

		// Get a generic TableExtension instance for use if not already loaded
		if (!($this->extension instanceof TableInterface))
		{
			$this->extension = Table::getInstance('extension');
		}

		// Sanity check, make sure the type is set by taking the adapter name
from the class name
		if (!$this->type)
		{
			// This assumes the adapter short class name in its namespace is
`<foo>Adapter`, replace this logic in subclasses if needed
			$reflection = new \ReflectionClass(get_called_class());
			$this->type = str_replace('Adapter', '',
$reflection->getShortName());
		}

		// Extension type is stored as lowercase in the database
		$this->type = strtolower($this->type);
	}

	/**
	 * Check if a package extension allows its child extensions to be
uninstalled individually
	 *
	 * @param   integer  $packageId  The extension ID of the package to check
	 *
	 * @return  boolean
	 *
	 * @since   3.7.0
	 * @note    This method defaults to true to emulate the behavior of 3.6
and earlier which did not support this lookup
	 */
	protected function canUninstallPackageChild($packageId)
	{
		$package = Table::getInstance('extension');

		// If we can't load this package ID, we have a corrupt database
		if (!$package->load((int) $packageId))
		{
			return true;
		}

		$manifestFile = JPATH_MANIFESTS . '/packages/' .
$package->element . '.xml';

		$xml = $this->parent->isManifest($manifestFile);

		// If the manifest doesn't exist, we've got some major issues
		if (!$xml)
		{
			return true;
		}

		$manifest = new PackageManifest($manifestFile);

		return $manifest->blockChildUninstall === false;
	}

	/**
	 * Method to check if the extension is already present in the database
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function checkExistingExtension()
	{
		try
		{
			$this->currentExtensionId = $this->extension->find(
				array('element' => $this->element, 'type'
=> $this->type)
			);

			// If it does exist, load it
			if ($this->currentExtensionId)
			{
				$this->extension->load(array('element' =>
$this->element, 'type' => $this->type));
			}
		}
		catch (\RuntimeException $e)
		{
			// Install failed, roll back changes
			throw new \RuntimeException(
				\JText::sprintf(
					'JLIB_INSTALLER_ABORT_ROLLBACK',
					\JText::_('JLIB_INSTALLER_' . $this->route),
					$e->getMessage()
				),
				$e->getCode(),
				$e
			);
		}
	}

	/**
	 * Method to check if the extension is present in the filesystem, flags
the route as update if so
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function checkExtensionInFilesystem()
	{
		if (file_exists($this->parent->getPath('extension_root'))
&& (!$this->parent->isOverwrite() ||
$this->parent->isUpgrade()))
		{
			// Look for an update function or update tag
			$updateElement = $this->getManifest()->update;

			// Upgrade manually set or update function available or update tag
detected
			if ($updateElement || $this->parent->isUpgrade()
				|| ($this->parent->manifestClass &&
method_exists($this->parent->manifestClass, 'update')))
			{
				// Force this one
				$this->parent->setOverwrite(true);
				$this->parent->setUpgrade(true);

				if ($this->currentExtensionId)
				{
					// If there is a matching extension mark this as an update
					$this->setRoute('update');
				}
			}
			elseif (!$this->parent->isOverwrite())
			{
				// We didn't have overwrite set, find an update function or find
an update tag so lets call it safe
				throw new \RuntimeException(
					\JText::sprintf(
						'JLIB_INSTALLER_ABORT_DIRECTORY',
						\JText::_('JLIB_INSTALLER_' . $this->route),
						$this->type,
						$this->parent->getPath('extension_root')
					)
				);
			}
		}
	}

	/**
	 * Method to copy the extension's base files from the `<files>`
tag(s) and the manifest file
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	abstract protected function copyBaseFiles();

	/**
	 * Method to create the extension root path if necessary
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function createExtensionRoot()
	{
		// If the extension directory does not exist, lets create it
		$created = false;

		if
(!file_exists($this->parent->getPath('extension_root')))
		{
			if (!$created =
\JFolder::create($this->parent->getPath('extension_root')))
			{
				throw new \RuntimeException(
					\JText::sprintf(
						'JLIB_INSTALLER_ABORT_CREATE_DIRECTORY',
						\JText::_('JLIB_INSTALLER_' . $this->route),
						$this->parent->getPath('extension_root')
					)
				);
			}
		}

		/*
		 * Since we created the extension directory and will want to remove it if
		 * we have to roll back the installation, let's add it to the
		 * installation step stack
		 */

		if ($created)
		{
			$this->parent->pushStep(
				array(
					'type' => 'folder',
					'path' =>
$this->parent->getPath('extension_root'),
				)
			);
		}
	}

	/**
	 * Generic discover_install method for extensions
	 *
	 * @return  boolean  True on success
	 *
	 * @since   3.4
	 */
	public function discover_install()
	{
		// Get the extension's description
		$description = (string) $this->getManifest()->description;

		if ($description)
		{
			$this->parent->message = \JText::_($description);
		}
		else
		{
			$this->parent->message = '';
		}

		// Set the extension's name and element
		$this->name    = $this->getName();
		$this->element = $this->getElement();

		/*
		 *
---------------------------------------------------------------------------------------------
		 * Extension Precheck and Setup Section
		 *
---------------------------------------------------------------------------------------------
		 */

		// Setup the install paths and perform other prechecks as necessary
		try
		{
			$this->setupInstallPaths();
		}
		catch (\RuntimeException $e)
		{
			// Install failed, roll back changes
			$this->parent->abort($e->getMessage());

			return false;
		}

		/*
		 *
---------------------------------------------------------------------------------------------
		 * Installer Trigger Loading
		 *
---------------------------------------------------------------------------------------------
		 */

		$this->setupScriptfile();

		try
		{
			$this->triggerManifestScript('preflight');
		}
		catch (\RuntimeException $e)
		{
			// Install failed, roll back changes
			$this->parent->abort($e->getMessage());

			return false;
		}

		/*
		 *
---------------------------------------------------------------------------------------------
		 * Database Processing Section
		 *
---------------------------------------------------------------------------------------------
		 */

		try
		{
			$this->storeExtension();
		}
		catch (\RuntimeException $e)
		{
			// Install failed, roll back changes
			$this->parent->abort($e->getMessage());

			return false;
		}

		try
		{
			$this->parseQueries();
		}
		catch (\RuntimeException $e)
		{
			// Install failed, roll back changes
			$this->parent->abort($e->getMessage());

			return false;
		}

		// Run the custom install method
		try
		{
			$this->triggerManifestScript('install');
		}
		catch (\RuntimeException $e)
		{
			// Install failed, roll back changes
			$this->parent->abort($e->getMessage());

			return false;
		}

		/*
		 *
---------------------------------------------------------------------------------------------
		 * Finalization and Cleanup Section
		 *
---------------------------------------------------------------------------------------------
		 */

		try
		{
			$this->finaliseInstall();
		}
		catch (\RuntimeException $e)
		{
			// Install failed, roll back changes
			$this->parent->abort($e->getMessage());

			return false;
		}

		// And now we run the postflight
		try
		{
			$this->triggerManifestScript('postflight');
		}
		catch (\RuntimeException $e)
		{
			// Install failed, roll back changes
			$this->parent->abort($e->getMessage());

			return false;
		}

		return $this->extension->extension_id;
	}

	/**
	 * Method to handle database transactions for the installer
	 *
	 * @return  boolean  True on success
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function doDatabaseTransactions()
	{
		$route = $this->route === 'discover_install' ?
'install' : $this->route;

		// Let's run the install queries for the component
		if (isset($this->getManifest()->{$route}->sql))
		{
			$result =
$this->parent->parseSQLFiles($this->getManifest()->{$route}->sql);

			if ($result === false)
			{
				// Only rollback if installing
				if ($route === 'install')
				{
					throw new \RuntimeException(
						\JText::sprintf(
							'JLIB_INSTALLER_ABORT_SQL_ERROR',
							\JText::_('JLIB_INSTALLER_' .
strtoupper($this->route)),
							$this->parent->getDbo()->stderr(true)
						)
					);
				}

				return false;
			}

			// If installing with success and there is an uninstall script, add an
installer rollback step to rollback if needed
			if ($route === 'install' &&
isset($this->getManifest()->uninstall->sql))
			{
				$this->parent->pushStep(array('type' =>
'query', 'script' =>
$this->getManifest()->uninstall->sql));
			}
		}

		return true;
	}

	/**
	 * Load language files
	 *
	 * @param   string  $extension  The name of the extension
	 * @param   string  $source     Path to the extension
	 * @param   string  $base       Base path for the extension language
	 *
	 * @return  void
	 *
	 * @since   3.4
	 */
	protected function doLoadLanguage($extension, $source, $base =
JPATH_ADMINISTRATOR)
	{
		$lang = \JFactory::getLanguage();
		$lang->load($extension . '.sys', $source, null, false, true)
|| $lang->load($extension . '.sys', $base, null, false, true);
	}

	/**
	 * Checks if the adapter supports discover_install
	 *
	 * @return  boolean
	 *
	 * @since   3.4
	 */
	public function getDiscoverInstallSupported()
	{
		return $this->supportsDiscoverInstall;
	}

	/**
	 * Get the filtered extension element from the manifest
	 *
	 * @param   string  $element  Optional element name to be converted
	 *
	 * @return  string  The filtered element
	 *
	 * @since   3.4
	 */
	public function getElement($element = null)
	{
		if (!$element)
		{
			// Ensure the element is a string
			$element = (string) $this->getManifest()->element;
		}

		if (!$element)
		{
			$element = $this->getName();
		}

		// Filter the name for illegal characters
		return strtolower(\JFilterInput::getInstance()->clean($element,
'cmd'));
	}

	/**
	 * Get the manifest object.
	 *
	 * @return  \SimpleXMLElement  Manifest object
	 *
	 * @since   3.4
	 */
	public function getManifest()
	{
		return $this->manifest;
	}

	/**
	 * Get the filtered component name from the manifest
	 *
	 * @return  string  The filtered name
	 *
	 * @since   3.4
	 */
	public function getName()
	{
		// Ensure the name is a string
		$name = (string) $this->getManifest()->name;

		// Filter the name for illegal characters
		$name = \JFilterInput::getInstance()->clean($name,
'string');

		return $name;
	}

	/**
	 * Get the install route being followed
	 *
	 * @return  string  The install route
	 *
	 * @since   3.4
	 */
	public function getRoute()
	{
		return $this->route;
	}

	/**
	 * Get the class name for the install adapter script.
	 *
	 * @return  string  The class name.
	 *
	 * @since   3.4
	 */
	protected function getScriptClassName()
	{
		// Support element names like 'en-GB'
		$className = \JFilterInput::getInstance()->clean($this->element,
'cmd') . 'InstallerScript';

		// Cannot have - in class names
		$className = str_replace('-', '', $className);

		return $className;
	}

	/**
	 * Generic install method for extensions
	 *
	 * @return  boolean|integer  The extension ID on success, boolean false on
failure
	 *
	 * @since   3.4
	 */
	public function install()
	{
		// Get the extension's description
		$description = (string) $this->getManifest()->description;

		if ($description)
		{
			$this->parent->message = \JText::_($description);
		}
		else
		{
			$this->parent->message = '';
		}

		// Set the extension's name and element
		$this->name    = $this->getName();
		$this->element = $this->getElement();

		/*
		 *
---------------------------------------------------------------------------------------------
		 * Extension Precheck and Setup Section
		 *
---------------------------------------------------------------------------------------------
		 */

		// Setup the install paths and perform other prechecks as necessary
		try
		{
			$this->setupInstallPaths();
		}
		catch (\RuntimeException $e)
		{
			// Install failed, roll back changes
			$this->parent->abort($e->getMessage());

			return false;
		}

		// Check to see if an extension by the same name is already installed.
		try
		{
			$this->checkExistingExtension();
		}
		catch (\RuntimeException $e)
		{
			// Install failed, roll back changes
			$this->parent->abort($e->getMessage());

			return false;
		}

		// Check if the extension is present in the filesystem
		try
		{
			$this->checkExtensionInFilesystem();
		}
		catch (\RuntimeException $e)
		{
			// Install failed, roll back changes
			$this->parent->abort($e->getMessage());

			return false;
		}

		// If we are on the update route, run any custom setup routines
		if ($this->route === 'update')
		{
			try
			{
				$this->setupUpdates();
			}
			catch (\RuntimeException $e)
			{
				// Install failed, roll back changes
				$this->parent->abort($e->getMessage());

				return false;
			}
		}

		/*
		 *
---------------------------------------------------------------------------------------------
		 * Installer Trigger Loading
		 *
---------------------------------------------------------------------------------------------
		 */

		$this->setupScriptfile();

		try
		{
			$this->triggerManifestScript('preflight');
		}
		catch (\RuntimeException $e)
		{
			// Install failed, roll back changes
			$this->parent->abort($e->getMessage());

			return false;
		}

		/*
		 *
---------------------------------------------------------------------------------------------
		 * Filesystem Processing Section
		 *
---------------------------------------------------------------------------------------------
		 */

		// If the extension directory does not exist, lets create it
		try
		{
			$this->createExtensionRoot();
		}
		catch (\RuntimeException $e)
		{
			// Install failed, roll back changes
			$this->parent->abort($e->getMessage());

			return false;
		}

		// Copy all necessary files
		try
		{
			$this->copyBaseFiles();
		}
		catch (\RuntimeException $e)
		{
			// Install failed, roll back changes
			$this->parent->abort($e->getMessage());

			return false;
		}

		// Parse optional tags
		$this->parseOptionalTags();

		/*
		 *
---------------------------------------------------------------------------------------------
		 * Database Processing Section
		 *
---------------------------------------------------------------------------------------------
		 */

		try
		{
			$this->storeExtension();
		}
		catch (\RuntimeException $e)
		{
			// Install failed, roll back changes
			$this->parent->abort($e->getMessage());

			return false;
		}

		try
		{
			$this->parseQueries();
		}
		catch (\RuntimeException $e)
		{
			// Install failed, roll back changes
			$this->parent->abort($e->getMessage());

			return false;
		}

		// Run the custom method based on the route
		try
		{
			$this->triggerManifestScript($this->route);
		}
		catch (\RuntimeException $e)
		{
			// Install failed, roll back changes
			$this->parent->abort($e->getMessage());

			return false;
		}

		/*
		 *
---------------------------------------------------------------------------------------------
		 * Finalization and Cleanup Section
		 *
---------------------------------------------------------------------------------------------
		 */

		try
		{
			$this->finaliseInstall();
		}
		catch (\RuntimeException $e)
		{
			// Install failed, roll back changes
			$this->parent->abort($e->getMessage());

			return false;
		}

		// And now we run the postflight
		try
		{
			$this->triggerManifestScript('postflight');
		}
		catch (\RuntimeException $e)
		{
			// Install failed, roll back changes
			$this->parent->abort($e->getMessage());

			return false;
		}

		return $this->extension->extension_id;
	}

	/**
	 * Method to parse the queries specified in the `<sql>` tags
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function parseQueries()
	{
		// Let's run the queries for the extension
		if (in_array($this->route, array('install',
'discover_install', 'uninstall')))
		{
			// This method may throw an exception, but it is caught by the parent
caller
			if (!$this->doDatabaseTransactions())
			{
				throw new \RuntimeException(
					\JText::sprintf(
						'JLIB_INSTALLER_ABORT_SQL_ERROR',
						\JText::_('JLIB_INSTALLER_' . strtoupper($this->route)),
						$this->db->stderr(true)
					)
				);
			}

			// Set the schema version to be the latest update version
			if ($this->getManifest()->update)
			{
				$this->parent->setSchemaVersion($this->getManifest()->update->schemas,
$this->extension->extension_id);
			}
		}
		elseif ($this->route === 'update')
		{
			if ($this->getManifest()->update)
			{
				$result =
$this->parent->parseSchemaUpdates($this->getManifest()->update->schemas,
$this->extension->extension_id);

				if ($result === false)
				{
					// Install failed, rollback changes
					throw new \RuntimeException(
						\JText::sprintf(
							'JLIB_INSTALLER_ABORT_SQL_ERROR',
							\JText::_('JLIB_INSTALLER_' .
strtoupper($this->route)),
							$this->db->stderr(true)
						)
					);
				}
			}
		}
	}

	/**
	 * Method to parse optional tags in the manifest
	 *
	 * @return  void
	 *
	 * @since   3.1
	 */
	protected function parseOptionalTags()
	{
		// Some extensions may not have optional tags
	}

	/**
	 * Prepares the adapter for a discover_install task
	 *
	 * @return  void
	 *
	 * @since   3.4
	 */
	public function prepareDiscoverInstall()
	{
		// Adapters may not support discover install or may have overridden the
default task and aren't using this
	}

	/**
	 * Set the manifest object.
	 *
	 * @param   object  $manifest  The manifest object
	 *
	 * @return  InstallerAdapter  Instance of this class to support chaining
	 *
	 * @since   3.4
	 */
	public function setManifest($manifest)
	{
		$this->manifest = $manifest;

		return $this;
	}

	/**
	 * Set the install route being followed
	 *
	 * @param   string  $route  The install route being followed
	 *
	 * @return  InstallerAdapter  Instance of this class to support chaining
	 *
	 * @since   3.4
	 */
	public function setRoute($route)
	{
		$this->route = $route;

		return $this;
	}

	/**
	 * Method to do any prechecks and setup the install paths for the
extension
	 *
	 * @return  void
	 *
	 * @since   3.4
	 */
	abstract protected function setupInstallPaths();

	/**
	 * Setup the manifest script file for those adapters that use it.
	 *
	 * @return  void
	 *
	 * @since   3.4
	 */
	protected function setupScriptfile()
	{
		// If there is a manifest class file, lets load it; we'll copy it
later (don't have dest yet)
		$manifestScript = (string) $this->getManifest()->scriptfile;

		if ($manifestScript)
		{
			$manifestScriptFile = $this->parent->getPath('source') .
'/' . $manifestScript;

			$classname = $this->getScriptClassName();

			\JLoader::register($classname, $manifestScriptFile);

			if (class_exists($classname))
			{
				// Create a new instance
				$this->parent->manifestClass = new $classname($this);

				// And set this so we can copy it later
				$this->manifest_script = $manifestScript;
			}
		}
	}

	/**
	 * Method to setup the update routine for the adapter
	 *
	 * @return  void
	 *
	 * @since   3.4
	 */
	protected function setupUpdates()
	{
		// Some extensions may not have custom setup routines for updates
	}

	/**
	 * Method to store the extension to the database
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	abstract protected function storeExtension();

	/**
	 * Executes a custom install script method
	 *
	 * @param   string  $method  The install method to execute
	 *
	 * @return  boolean  True on success
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function triggerManifestScript($method)
	{
		ob_start();
		ob_implicit_flush(false);

		if ($this->parent->manifestClass &&
method_exists($this->parent->manifestClass, $method))
		{
			switch ($method)
			{
				// The preflight and postflight take the route as a param
				case 'preflight' :
				case 'postflight' :
					if ($this->parent->manifestClass->$method($this->route,
$this) === false)
					{
						if ($method !== 'postflight')
						{
							// Clean and close the output buffer
							ob_end_clean();

							// The script failed, rollback changes
							throw new \RuntimeException(
								\JText::sprintf(
									'JLIB_INSTALLER_ABORT_INSTALL_CUSTOM_INSTALL_FAILURE',
									\JText::_('JLIB_INSTALLER_' . $this->route)
								)
							);
						}
					}
					break;

				// The install, uninstall, and update methods only pass this object as
a param
				case 'install' :
				case 'uninstall' :
				case 'update' :
					if ($this->parent->manifestClass->$method($this) === false)
					{
						if ($method !== 'uninstall')
						{
							// Clean and close the output buffer
							ob_end_clean();

							// The script failed, rollback changes
							throw new \RuntimeException(
								\JText::sprintf(
									'JLIB_INSTALLER_ABORT_INSTALL_CUSTOM_INSTALL_FAILURE',
									\JText::_('JLIB_INSTALLER_' . $this->route)
								)
							);
						}
					}
					break;
			}
		}

		// Append to the message object
		$this->extensionMessage .= ob_get_clean();

		// If in postflight or uninstall, set the message for display
		if (($method === 'uninstall' || $method ===
'postflight') && $this->extensionMessage !==
'')
		{
			$this->parent->set('extension_message',
$this->extensionMessage);
		}

		return true;
	}

	/**
	 * Generic update method for extensions
	 *
	 * @return  boolean|integer  The extension ID on success, boolean false on
failure
	 *
	 * @since   3.4
	 */
	public function update()
	{
		// Set the overwrite setting
		$this->parent->setOverwrite(true);
		$this->parent->setUpgrade(true);

		// And make sure the route is set correctly
		$this->setRoute('update');

		// Now jump into the install method to run the update
		return $this->install();
	}
}
PKK�[xˢaaInstallerExtension.phpnu�[���<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
 * @license    GNU General Public License version 2 or later; see
LICENSE.txt
 */

namespace Joomla\CMS\Installer;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Application\ApplicationHelper;

/**
 * Extension object
 *
 * @since  3.1
 */
class InstallerExtension extends \JObject
{
	/**
	 * Filename of the extension
	 *
	 * @var    string
	 * @since  3.1
	 */
	public $filename = '';

	/**
	 * Type of the extension
	 *
	 * @var    string
	 * @since  3.1
	 */
	public $type = '';

	/**
	 * Unique Identifier for the extension
	 *
	 * @var    string
	 * @since  3.1
	 */
	public $id = '';

	/**
	 * The status of the extension
	 *
	 * @var    boolean
	 * @since  3.1
	 */
	public $published = false;

	/**
	 * String representation of client. Valid for modules, templates and
languages.
	 * Set by default to site.
	 *
	 * @var    string
	 * @since  3.1
	 */
	public $client = 'site';

	/**
	 * The group name of the plugin. Not used for other known extension types
(only plugins)
	 *
	 * @var string
	 * @since  3.1
	 */
	public $group = '';

	/**
	 * An object representation of the manifest file stored metadata
	 *
	 * @var object
	 * @since  3.1
	 */
	public $manifest_cache = null;

	/**
	 * An object representation of the extension params
	 *
	 * @var    object
	 * @since  3.1
	 */
	public $params = null;

	/**
	 * Constructor
	 *
	 * @param   \SimpleXMLElement  $element  A SimpleXMLElement from which to
load data from
	 *
	 * @since  3.1
	 */
	public function __construct(\SimpleXMLElement $element = null)
	{
		if ($element)
		{
			$this->type = (string) $element->attributes()->type;
			$this->id = (string) $element->attributes()->id;

			switch ($this->type)
			{
				case 'component':
					// By default a component doesn't have anything
					break;

				case 'module':
				case 'template':
				case 'language':
					$this->client = (string) $element->attributes()->client;
					$tmp_client_id = ApplicationHelper::getClientInfo($this->client,
1);

					if ($tmp_client_id == null)
					{
						\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_EXTENSION_INVALID_CLIENT_IDENTIFIER'),
\JLog::WARNING, 'jerror');
					}
					else
					{
						$this->client_id = $tmp_client_id->id;
					}
					break;

				case 'plugin':
					$this->group = (string) $element->attributes()->group;
					break;

				default:
					// Catch all
					// Get and set client and group if we don't recognise the
extension
					if ($element->attributes()->client)
					{
						$this->client_id =
ApplicationHelper::getClientInfo($this->client, 1);
						$this->client_id = $this->client_id->id;
					}

					if ($element->attributes()->group)
					{
						$this->group = (string) $element->attributes()->group;
					}
					break;
			}

			$this->filename = (string) $element;
		}
	}
}
PKK�[�EN'('(InstallerHelper.phpnu�[���<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
 * @license    GNU General Public License version 2 or later; see
LICENSE.txt
 */

namespace Joomla\CMS\Installer;

defined('JPATH_PLATFORM') or die;

use Joomla\Archive\Archive;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\CMS\Version;

\JLoader::import('joomla.filesystem.file');
\JLoader::import('joomla.filesystem.folder');
\JLoader::import('joomla.filesystem.path');

/**
 * Installer helper class
 *
 * @since  3.1
 */
abstract class InstallerHelper
{
	/**
	 * Hash not validated identifier.
	 *
	 * @var    integer
	 * @since  3.9.0
	 */
	const HASH_NOT_VALIDATED = 0;

	/**
	 * Hash validated identifier.
	 *
	 * @var    integer
	 * @since  3.9.0
	 */
	const HASH_VALIDATED = 1;

	/**
	 * Hash not provided identifier.
	 *
	 * @var    integer
	 * @since  3.9.0
	 */
	const HASH_NOT_PROVIDED = 2;

	/**
	 * Downloads a package
	 *
	 * @param   string  $url     URL of file to download
	 * @param   mixed   $target  Download target filename or false to get the
filename from the URL
	 *
	 * @return  string|boolean  Path to downloaded package or boolean false on
failure
	 *
	 * @since   3.1
	 */
	public static function downloadPackage($url, $target = false)
	{
		// Capture PHP errors
		$track_errors = ini_get('track_errors');
		ini_set('track_errors', true);

		// Set user agent
		$version = new Version;
		ini_set('user_agent',
$version->getUserAgent('Installer'));

		// Load installer plugins, and allow URL and headers modification
		$headers = array();
		PluginHelper::importPlugin('installer');
		$dispatcher = \JEventDispatcher::getInstance();
		$dispatcher->trigger('onInstallerBeforePackageDownload',
array(&$url, &$headers));

		try
		{
			$response = \JHttpFactory::getHttp()->get($url, $headers);
		}
		catch (\RuntimeException $exception)
		{
			\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_DOWNLOAD_SERVER_CONNECT',
$exception->getMessage()), \JLog::WARNING, 'jerror');

			return false;
		}

		// Convert keys of headers to lowercase, to accommodate for case
variations
		$headers = array_change_key_case($response->headers);

		if (302 == $response->code &&
!empty($headers['location']))
		{
			return self::downloadPackage($headers['location']);
		}
		elseif (200 != $response->code)
		{
			\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_DOWNLOAD_SERVER_CONNECT',
$response->code), \JLog::WARNING, 'jerror');

			return false;
		}

		// Parse the Content-Disposition header to get the file name
		if (!empty($headers['content-disposition'])
			&& preg_match("/\s*filename\s?=\s?(.*)/",
$headers['content-disposition'], $parts))
		{
			$flds = explode(';', $parts[1]);
			$target = trim($flds[0], '"');
		}

		$tmpPath = \JFactory::getConfig()->get('tmp_path');

		// Set the target path if not given
		if (!$target)
		{
			$target = $tmpPath . '/' . self::getFilenameFromUrl($url);
		}
		else
		{
			$target = $tmpPath . '/' . basename($target);
		}

		// Write buffer to file
		\JFile::write($target, $response->body);

		// Restore error tracking to what it was before
		ini_set('track_errors', $track_errors);

		// Bump the max execution time because not using built in php zip libs
are slow
		@set_time_limit(ini_get('max_execution_time'));

		// Return the name of the downloaded package
		return basename($target);
	}

	/**
	 * Unpacks a file and verifies it as a Joomla element package
	 * Supports .gz .tar .tar.gz and .zip
	 *
	 * @param   string   $packageFilename    The uploaded package filename or
install directory
	 * @param   boolean  $alwaysReturnArray  If should return false (and leave
garbage behind) or return $retval['type']=false
	 *
	 * @return  array|boolean  Array on success or boolean false on failure
	 *
	 * @since   3.1
	 */
	public static function unpack($packageFilename, $alwaysReturnArray =
false)
	{
		// Path to the archive
		$archivename = $packageFilename;

		// Temporary folder to extract the archive into
		$tmpdir = uniqid('install_');

		// Clean the paths to use for archive extraction
		$extractdir = \JPath::clean(dirname($packageFilename) . '/' .
$tmpdir);
		$archivename = \JPath::clean($archivename);

		// Do the unpacking of the archive
		try
		{
			$archive = new Archive(array('tmp_path' =>
\JFactory::getConfig()->get('tmp_path')));
			$extract = $archive->extract($archivename, $extractdir);
		}
		catch (\Exception $e)
		{
			if ($alwaysReturnArray)
			{
				return array(
					'extractdir'  => null,
					'packagefile' => $archivename,
					'type'        => false,
				);
			}

			return false;
		}

		if (!$extract)
		{
			if ($alwaysReturnArray)
			{
				return array(
					'extractdir'  => null,
					'packagefile' => $archivename,
					'type'        => false,
				);
			}

			return false;
		}

		/*
		 * Let's set the extraction directory and package file in the result
array so we can
		 * cleanup everything properly later on.
		 */
		$retval['extractdir'] = $extractdir;
		$retval['packagefile'] = $archivename;

		/*
		 * Try to find the correct install directory.  In case the package is
inside a
		 * subdirectory detect this and set the install directory to the correct
path.
		 *
		 * List all the items in the installation directory.  If there is only
one, and
		 * it is a folder, then we will set that folder to be the installation
folder.
		 */
		$dirList = array_merge((array) \JFolder::files($extractdir,
''), (array) \JFolder::folders($extractdir, ''));

		if (count($dirList) === 1)
		{
			if (\JFolder::exists($extractdir . '/' . $dirList[0]))
			{
				$extractdir = \JPath::clean($extractdir . '/' . $dirList[0]);
			}
		}

		/*
		 * We have found the install directory so lets set it and then move on
		 * to detecting the extension type.
		 */
		$retval['dir'] = $extractdir;

		/*
		 * Get the extension type and return the directory/type array on success
or
		 * false on fail.
		 */
		$retval['type'] = self::detectType($extractdir);

		if ($alwaysReturnArray || $retval['type'])
		{
			return $retval;
		}
		else
		{
			return false;
		}
	}

	/**
	 * Method to detect the extension type from a package directory
	 *
	 * @param   string  $packageDirectory  Path to package directory
	 *
	 * @return  mixed  Extension type string or boolean false on fail
	 *
	 * @since   3.1
	 */
	public static function detectType($packageDirectory)
	{
		// Search the install dir for an XML file
		$files = \JFolder::files($packageDirectory, '\.xml$', 1, true);

		if (!$files || !count($files))
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_NOTFINDXMLSETUPFILE'),
\JLog::WARNING, 'jerror');

			return false;
		}

		foreach ($files as $file)
		{
			$xml = simplexml_load_file($file);

			if (!$xml)
			{
				continue;
			}

			if ($xml->getName() !== 'extension')
			{
				unset($xml);
				continue;
			}

			$type = (string) $xml->attributes()->type;

			// Free up memory
			unset($xml);

			return $type;
		}

		\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_NOTFINDJOOMLAXMLSETUPFILE'),
\JLog::WARNING, 'jerror');

		// Free up memory.
		unset($xml);

		return false;
	}

	/**
	 * Gets a file name out of a url
	 *
	 * @param   string  $url  URL to get name from
	 *
	 * @return  string  Clean version of the filename or a unique id
	 *
	 * @since   3.1
	 */
	public static function getFilenameFromUrl($url)
	{
		$default = uniqid();

		if (!is_string($url) || strpos($url, '/') === false)
		{
			return $default;
		}

		// Get last part of the url (after the last slash).
		$parts    = explode('/', $url);
		$filename = array_pop($parts);

		// Replace special characters with underscores.
		$filename = preg_replace('/[^a-z0-9\_\-\.]/i', '_',
$filename);

		// Replace multiple underscores with just one.
		$filename = preg_replace('/__+/', '_',
trim($filename, '_'));

		// Return the cleaned filename or, if it is empty, a unique id.
		return $filename ?: $default;
	}

	/**
	 * Clean up temporary uploaded package and unpacked extension
	 *
	 * @param   string  $package    Path to the uploaded package file
	 * @param   string  $resultdir  Path to the unpacked extension
	 *
	 * @return  boolean  True on success
	 *
	 * @since   3.1
	 */
	public static function cleanupInstall($package, $resultdir)
	{
		$config = \JFactory::getConfig();

		// Does the unpacked extension directory exist?
		if ($resultdir && is_dir($resultdir))
		{
			\JFolder::delete($resultdir);
		}

		// Is the package file a valid file?
		if (is_file($package))
		{
			\JFile::delete($package);
		}
		elseif (is_file(\JPath::clean($config->get('tmp_path') .
'/' . $package)))
		{
			// It might also be just a base filename
			\JFile::delete(\JPath::clean($config->get('tmp_path') .
'/' . $package));
		}
	}

	/**
	 * Splits contents of a sql file into array of discreet queries.
	 * Queries need to be delimited with end of statement marker ';'
	 *
	 * @param   string  $query  The SQL statement.
	 *
	 * @return  array  Array of queries
	 *
	 * @since   3.1
	 * @deprecated  4.0  Use \JDatabaseDriver::splitSql() directly
	 * @codeCoverageIgnore
	 */
	public static function splitSql($query)
	{
		\JLog::add('JInstallerHelper::splitSql() is deprecated. Use
JDatabaseDriver::splitSql() instead.', \JLog::WARNING,
'deprecated');
		$db = \JFactory::getDbo();

		return $db->splitSql($query);
	}

	/**
	 * Return the result of the checksum of a package with the
SHA256/SHA384/SHA512 tags in the update server manifest
	 *
	 * @param   string   $packagefile   Location of the package to be
installed
	 * @param   JUpdate  $updateObject  The Update Object
	 *
	 * @return  integer  one if the hashes match, zero if hashes doesn't
match, two if hashes not found
	 *
	 * @since   3.9.0
	 */
	public static function isChecksumValid($packagefile, $updateObject)
	{
		$hashes     = array('sha256', 'sha384',
'sha512');
		$hashOnFile = false;

		foreach ($hashes as $hash)
		{
			if ($updateObject->get($hash, false))
			{
				$hashPackage = hash_file($hash, $packagefile);
				$hashRemote  = $updateObject->$hash->_data;
				$hashOnFile  = true;

				if ($hashPackage !== strtolower($hashRemote))	
				{
					return self::HASH_NOT_VALIDATED;
				}
			}
		}

		if ($hashOnFile)
		{
			return self::HASH_VALIDATED;
		}

		return self::HASH_NOT_PROVIDED;
	}
}
PKK�[�5��$$InstallerScript.phpnu�[���<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
 * @license    GNU General Public License version 2 or later; see
LICENSE.txt
 */

namespace Joomla\CMS\Installer;

defined('_JEXEC') or die;

\JLoader::import('joomla.filesystem.file');
\JLoader::import('joomla.filesystem.folder');

/**
 * Base install script for use by extensions providing helper methods for
common behaviours.
 *
 * @since  3.6
 */
class InstallerScript
{
	/**
	 * The version number of the extension.
	 *
	 * @var    string
	 * @since  3.6
	 */
	protected $release;

	/**
	 * The table the parameters are stored in.
	 *
	 * @var    string
	 * @since  3.6
	 */
	protected $paramTable;

	/**
	 * The extension name. This should be set in the installer script.
	 *
	 * @var    string
	 * @since  3.6
	 */
	protected $extension;

	/**
	 * A list of files to be deleted
	 *
	 * @var    array
	 * @since  3.6
	 */
	protected $deleteFiles = array();

	/**
	 * A list of folders to be deleted
	 *
	 * @var    array
	 * @since  3.6
	 */
	protected $deleteFolders = array();

	/**
	 * A list of CLI script files to be copied to the cli directory
	 *
	 * @var    array
	 * @since  3.6
	 */
	protected $cliScriptFiles = array();

	/**
	 * Minimum PHP version required to install the extension
	 *
	 * @var    string
	 * @since  3.6
	 */
	protected $minimumPhp;

	/**
	 * Minimum Joomla! version required to install the extension
	 *
	 * @var    string
	 * @since  3.6
	 */
	protected $minimumJoomla;

	/**
	 * Allow downgrades of your extension
	 *
	 * Use at your own risk as if there is a change in functionality people
may wish to downgrade.
	 *
	 * @var    boolean
	 * @since  3.6
	 */
	protected $allowDowngrades = false;

	/**
	 * Function called before extension installation/update/removal procedure
commences
	 *
	 * @param   string            $type    The type of change (install, update
or discover_install, not uninstall)
	 * @param   InstallerAdapter  $parent  The class calling this method
	 *
	 * @return  boolean  True on success
	 *
	 * @since   3.6
	 */
	public function preflight($type, $parent)
	{
		// Check for the minimum PHP version before continuing
		if (!empty($this->minimumPhp) && version_compare(PHP_VERSION,
$this->minimumPhp, '<'))
		{
			\JLog::add(\JText::sprintf('JLIB_INSTALLER_MINIMUM_PHP',
$this->minimumPhp), \JLog::WARNING, 'jerror');

			return false;
		}

		// Check for the minimum Joomla version before continuing
		if (!empty($this->minimumJoomla) && version_compare(JVERSION,
$this->minimumJoomla, '<'))
		{
			\JLog::add(\JText::sprintf('JLIB_INSTALLER_MINIMUM_JOOMLA',
$this->minimumJoomla), \JLog::WARNING, 'jerror');

			return false;
		}

		// Extension manifest file version
		$this->extension = $parent->getName();
		$this->release   = $parent->get('manifest')->version;
		$extensionType   = substr($this->extension, 0, 3);

		// Modules parameters are located in the module table - else in the
extension table
		if ($extensionType === 'mod')
		{
			$this->paramTable = '#__modules';
		}
		else
		{
			$this->paramTable = '#__extensions';
		}

		// Abort if the extension being installed is not newer than the currently
installed version
		if (!$this->allowDowngrades && strtolower($type) ===
'update')
		{
			$manifest = $this->getItemArray('manifest_cache',
'#__extensions', 'element',
\JFactory::getDbo()->quote($this->extension));

			// Check whether we have an old release installed and skip this check
when this here is the initial install.
			if (!isset($manifest['version']))
			{
				return true;
			}

			$oldRelease = $manifest['version'];

			if (version_compare($this->release, $oldRelease, '<'))
			{
				\JFactory::getApplication()->enqueueMessage(\JText::sprintf('JLIB_INSTALLER_INCORRECT_SEQUENCE',
$oldRelease, $this->release), 'error');

				return false;
			}
		}

		return true;
	}

	/**
	 * Gets each instance of a module in the #__modules table
	 *
	 * @param   boolean  $isModule  True if the extension is a module as this
can have multiple instances
	 *
	 * @return  array  An array of ID's of the extension
	 *
	 * @since   3.6
	 */
	public function getInstances($isModule)
	{
		$db = \JFactory::getDbo();
		$query = $db->getQuery(true);

		// Select the item(s) and retrieve the id
		$query->select($db->quoteName('id'));

		if ($isModule)
		{
			$query->from($db->quoteName('#__modules'))
				->where($db->quoteName('module') . ' = ' .
$db->quote($this->extension));
		}
		else
		{
			$query->from($db->quoteName('#__extensions'))
				->where($db->quoteName('element') . ' = ' .
$db->quote($this->extension));
		}

		// Set the query and obtain an array of id's
		return $db->setQuery($query)->loadColumn();
	}

	/**
	 * Gets parameter value in the extensions row of the extension table
	 *
	 * @param   string   $name  The name of the parameter to be retrieved
	 * @param   integer  $id    The id of the item in the Param Table
	 *
	 * @return  string  The parameter desired
	 *
	 * @since   3.6
	 */
	public function getParam($name, $id = 0)
	{
		if (!is_int($id) || $id == 0)
		{
			// Return false if there is no item given
			return false;
		}

		$params = $this->getItemArray('params',
$this->paramTable, 'id', $id);

		return $params[$name];
	}

	/**
	 * Sets parameter values in the extensions row of the extension table.
Note that the
	 * this must be called separately for deleting and editing. Note if edit
is called as a
	 * type then if the param doesn't exist it will be created
	 *
	 * @param   array    $paramArray  The array of parameters to be
added/edited/removed
	 * @param   string   $type        The type of change to be made to the
param (edit/remove)
	 * @param   integer  $id          The id of the item in the relevant table
	 *
	 * @return  boolean  True on success
	 *
	 * @since   3.6
	 */
	public function setParams($paramArray = null, $type = 'edit',
$id = 0)
	{
		if (!is_int($id) || $id == 0)
		{
			// Return false if there is no valid item given
			return false;
		}

		$params = $this->getItemArray('params',
$this->paramTable, 'id', $id);

		if ($paramArray)
		{
			foreach ($paramArray as $name => $value)
			{
				if ($type === 'edit')
				{
					// Add or edit the new variable(s) to the existing params
					if (is_array($value))
					{
						// Convert an array into a json encoded string
						$params[(string) $name] = array_values($value);
					}
					else
					{
						$params[(string) $name] = (string) $value;
					}
				}
				elseif ($type === 'remove')
				{
					// Unset the parameter from the array
					unset($params[(string) $name]);
				}
			}
		}

		// Store the combined new and existing values back as a JSON string
		$paramsString = json_encode($params);

		$db = \JFactory::getDbo();
		$query = $db->getQuery(true)
			->update($db->quoteName($this->paramTable))
			->set('params = ' . $db->quote($paramsString))
			->where('id = ' . $id);

		// Update table
		$db->setQuery($query)->execute();

		return true;
	}

	/**
	 * Builds a standard select query to produce better DRY code in this
script.
	 * This should produce a single unique cell which is json encoded - it
will then
	 * return an associated array with this data in.
	 *
	 * @param   string  $element     The element to get from the query
	 * @param   string  $table       The table to search for the data in
	 * @param   string  $column      The column of the database to search from
	 * @param   mixed   $identifier  The integer id or the already quoted
string
	 *
	 * @return  array  Associated array containing data from the cell
	 *
	 * @since   3.6
	 */
	public function getItemArray($element, $table, $column, $identifier)
	{
		// Get the DB and query objects
		$db = \JFactory::getDbo();

		// Build the query
		$query = $db->getQuery(true)
			->select($db->quoteName($element))
			->from($db->quoteName($table))
			->where($db->quoteName($column) . ' = ' . $identifier);
		$db->setQuery($query);

		// Load the single cell and json_decode data
		return json_decode($db->loadResult(), true);
	}

	/**
	 * Remove the files and folders in the given array from
	 *
	 * @return  void
	 *
	 * @since   3.6
	 */
	public function removeFiles()
	{
		if (!empty($this->deleteFiles))
		{
			foreach ($this->deleteFiles as $file)
			{
				if (file_exists(JPATH_ROOT . $file) &&
!\JFile::delete(JPATH_ROOT . $file))
				{
					echo \JText::sprintf('JLIB_INSTALLER_ERROR_FILE_FOLDER',
$file) . '<br />';
				}
			}
		}

		if (!empty($this->deleteFolders))
		{
			foreach ($this->deleteFolders as $folder)
			{
				if (\JFolder::exists(JPATH_ROOT . $folder) &&
!\JFolder::delete(JPATH_ROOT . $folder))
				{
					echo \JText::sprintf('JLIB_INSTALLER_ERROR_FILE_FOLDER',
$folder) . '<br />';
				}
			}
		}
	}

	/**
	 * Moves the CLI scripts into the CLI folder in the CMS
	 *
	 * @return  void
	 *
	 * @since   3.6
	 */
	public function moveCliFiles()
	{
		if (!empty($this->cliScriptFiles))
		{
			foreach ($this->cliScriptFiles as $file)
			{
				$name = basename($file);

				if (file_exists(JPATH_ROOT . $file) && !\JFile::move(JPATH_ROOT
. $file, JPATH_ROOT . '/cli/' . $name))
				{
					echo \JText::sprintf('JLIB_INSTALLER_FILE_ERROR_MOVE',
$name);
				}
			}
		}
	}
}
PKK�[��ooManifest/LibraryManifest.phpnu�[���<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
 * @license    GNU General Public License version 2 or later; see
LICENSE.txt
 */

namespace Joomla\CMS\Installer\Manifest;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Installer\Manifest;

/**
 * Joomla! Library Manifest File
 *
 * @since  3.1
 */
class LibraryManifest extends Manifest
{
	/**
	 * File system name of the library
	 *
	 * @var    string
	 * @since  3.1
	 */
	public $libraryname = '';

	/**
	 * Creation Date of the library
	 *
	 * @var    string
	 * @since  3.1
	 */
	public $creationDate = '';

	/**
	 * Copyright notice for the library
	 *
	 * @var    string
	 * @since  3.1
	 */
	public $copyright = '';

	/**
	 * License for the library
	 *
	 * @var    string
	 * @since  3.1
	 */
	public $license = '';

	/**
	 * Author for the library
	 *
	 * @var    string
	 * @since  3.1
	 */
	public $author = '';

	/**
	 * Author email for the library
	 *
	 * @var    string
	 * @since  3.1
	 */
	public $authoremail = '';

	/**
	 * Author URL for the library
	 *
	 * @var    string
	 * @since  3.1
	 */
	public $authorurl = '';

	/**
	 * Apply manifest data from a \SimpleXMLElement to the object.
	 *
	 * @param   \SimpleXMLElement  $xml  Data to load
	 *
	 * @return  void
	 *
	 * @since   3.1
	 */
	protected function loadManifestFromData(\SimpleXMLElement $xml)
	{
		$this->name         = (string) $xml->name;
		$this->libraryname  = (string) $xml->libraryname;
		$this->version      = (string) $xml->version;
		$this->description  = (string) $xml->description;
		$this->creationdate = (string) $xml->creationDate;
		$this->author       = (string) $xml->author;
		$this->authoremail  = (string) $xml->authorEmail;
		$this->authorurl    = (string) $xml->authorUrl;
		$this->packager     = (string) $xml->packager;
		$this->packagerurl  = (string) $xml->packagerurl;
		$this->update       = (string) $xml->update;

		if (isset($xml->files) && isset($xml->files->file)
&& count($xml->files->file))
		{
			foreach ($xml->files->file as $file)
			{
				$this->filelist[] = (string) $file;
			}
		}
	}
}
PKK�[D���	�	Manifest/PackageManifest.phpnu�[���<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
 * @license    GNU General Public License version 2 or later; see
LICENSE.txt
 */

namespace Joomla\CMS\Installer\Manifest;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Installer\InstallerExtension;
use Joomla\CMS\Installer\Manifest;

/**
 * Joomla! Package Manifest File
 *
 * @since  3.1
 */
class PackageManifest extends Manifest
{
	/**
	 * Unique name of the package
	 *
	 * @var    string
	 * @since  3.1
	 */
	public $packagename = '';

	/**
	 * Website for the package
	 *
	 * @var    string
	 * @since  3.1
	 */
	public $url = '';

	/**
	 * Scriptfile for the package
	 *
	 * @var    string
	 * @since  3.1
	 */
	public $scriptfile = '';

	/**
	 * Flag if the package blocks individual child extensions from being
uninstalled
	 *
	 * @var    boolean
	 * @since  3.7.0
	 */
	public $blockChildUninstall = false;

	/**
	 * Apply manifest data from a \SimpleXMLElement to the object.
	 *
	 * @param   \SimpleXMLElement  $xml  Data to load
	 *
	 * @return  void
	 *
	 * @since   3.1
	 */
	protected function loadManifestFromData(\SimpleXMLElement $xml)
	{
		$this->name        = (string) $xml->name;
		$this->packagename = (string) $xml->packagename;
		$this->update      = (string) $xml->update;
		$this->authorurl   = (string) $xml->authorUrl;
		$this->author      = (string) $xml->author;
		$this->authoremail = (string) $xml->authorEmail;
		$this->description = (string) $xml->description;
		$this->packager    = (string) $xml->packager;
		$this->packagerurl = (string) $xml->packagerurl;
		$this->scriptfile  = (string) $xml->scriptfile;
		$this->version     = (string) $xml->version;

		if (isset($xml->blockChildUninstall))
		{
			$value = (string) $xml->blockChildUninstall;

			if ($value === '1' || $value === 'true')
			{
				$this->blockChildUninstall = true;
			}
		}

		if (isset($xml->files->file) &&
count($xml->files->file))
		{
			foreach ($xml->files->file as $file)
			{
				// NOTE: JInstallerExtension doesn't expect a string.
				// DO NOT CAST $file
				$this->filelist[] = new InstallerExtension($file);
			}
		}

		// Handle cases where package contains folders
		if (isset($xml->files->folder) &&
count($xml->files->folder))
		{
			foreach ($xml->files->folder as $folder)
			{
				// NOTE: JInstallerExtension doesn't expect a string.
				// DO NOT CAST $folder
				$this->filelist[] = new InstallerExtension($folder);
			}
		}
	}
}
PKK�[�xl��Manifest.phpnu�[���<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
 * @license    GNU General Public License version 2 or later; see
LICENSE.txt
 */

namespace Joomla\CMS\Installer;

defined('JPATH_PLATFORM') or die;

\JLoader::import('joomla.filesystem.file');

/**
 * Joomla! Package Manifest File
 *
 * @since  3.1
 */
abstract class Manifest
{
	/**
	 * Path to the manifest file
	 *
	 * @var    string
	 * @since  3.1
	 */
	public $manifest_file = '';

	/**
	 * Name of the extension
	 *
	 * @var    string
	 * @since  3.1
	 */
	public $name = '';

	/**
	 * Version of the extension
	 *
	 * @var    string
	 * @since  3.1
	 */
	public $version = '';

	/**
	 * Description of the extension
	 *
	 * @var    string
	 * @since  3.1
	 */
	public $description = '';

	/**
	 * Packager of the extension
	 *
	 * @var    string
	 * @since  3.1
	 */
	public $packager = '';

	/**
	 * Packager's URL of the extension
	 *
	 * @var    string
	 * @since  3.1
	 */
	public $packagerurl = '';

	/**
	 * Update site for the extension
	 *
	 * @var    string
	 * @since  3.1
	 */
	public $update = '';

	/**
	 * List of files in the extension
	 *
	 * @var    array
	 * @since  3.1
	 */
	public $filelist = array();

	/**
	 * Constructor
	 *
	 * @param   string  $xmlpath  Path to XML manifest file.
	 *
	 * @since   3.1
	 */
	public function __construct($xmlpath = '')
	{
		if ($xmlpath !== '')
		{
			$this->loadManifestFromXml($xmlpath);
		}
	}

	/**
	 * Load a manifest from a file
	 *
	 * @param   string  $xmlfile  Path to file to load
	 *
	 * @return  boolean
	 *
	 * @since   3.1
	 */
	public function loadManifestFromXml($xmlfile)
	{
		$this->manifest_file = basename($xmlfile, '.xml');

		$xml = simplexml_load_file($xmlfile);

		if (!$xml)
		{
			$this->_errors[] =
\JText::sprintf('JLIB_INSTALLER_ERROR_LOAD_XML', $xmlfile);

			return false;
		}
		else
		{
			$this->loadManifestFromData($xml);

			return true;
		}
	}

	/**
	 * Apply manifest data from a \SimpleXMLElement to the object.
	 *
	 * @param   \SimpleXMLElement  $xml  Data to load
	 *
	 * @return  void
	 *
	 * @since   3.1
	 */
	abstract protected function loadManifestFromData(\SimpleXmlElement $xml);
}
PKK�[uIg5`�`�Adapter/ComponentAdapter.phpnu�[���PKK�[.Vou@u@��Adapter/FileAdapter.phpnu�[���PKK�[��i��c�ch�Adapter/LanguageAdapter.phpnu�[���PKK�[�v4�+5+5�<Adapter/LibraryAdapter.phpnu�[���PKK�[Mm��QQ	rAdapter/ModuleAdapter.phpnu�[���PKK�[Ʒ�9NNb�Adapter/PackageAdapter.phpnu�[���PKK�[�Ⱦ�,M,M�Adapter/PluginAdapter.phpnu�[���PKK�[�A�R�C�C0_Adapter/TemplateAdapter.phpnu�[���PKK�[�5y����
�Installer.phpnu�[���PKK�[��o�a�aӇInstallerAdapter.phpnu�[���PKK�[xˢaa��InstallerExtension.phpnu�[���PKK�[�EN'('(��InstallerHelper.phpnu�[���PKK�[�5��$$�InstallerScript.phpnu�[���PKK�[��ooHBManifest/LibraryManifest.phpnu�[���PKK�[D���	�	KManifest/PackageManifest.phpnu�[���PKK�[�xl��UManifest.phpnu�[���PKp^