Spade
Mini Shell
| Directory:~$ /home/lmsyaran/public_html/joomla4/ |
| [Home] [System Details] [Kill Me] |
CollectionAdapter.php000064400000014073151160300740010653 0ustar00<?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\Updater\Adapter;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Factory;
use Joomla\CMS\Filter\InputFilter;
use Joomla\CMS\Log\Log;
use Joomla\CMS\Table\Table;
use Joomla\CMS\Updater\UpdateAdapter;
use Joomla\CMS\Version;
/**
* Collection Update Adapter Class
*
* @since 1.7.0
*/
class CollectionAdapter extends UpdateAdapter
{
/**
* Root of the tree
*
* @var object
* @since 1.7.0
*/
protected $base;
/**
* Tree of objects
*
* @var array
* @since 1.7.0
*/
protected $parent = array(0);
/**
* Used to control if an item has a child or not
*
* @var boolean
* @since 1.7.0
*/
protected $pop_parent = 0;
/**
* A list of discovered update sites
*
* @var array
*/
protected $update_sites = array();
/**
* A list of discovered updates
*
* @var array
*/
protected $updates = array();
/**
* Gets the reference to the current direct parent
*
* @return object
*
* @since 1.7.0
*/
protected function _getStackLocation()
{
return implode('->', $this->stack);
}
/**
* Get the parent tag
*
* @return string parent
*
* @since 1.7.0
*/
protected function _getParent()
{
return end($this->parent);
}
/**
* Opening an XML element
*
* @param object $parser Parser object
* @param string $name Name of element that is opened
* @param array $attrs Array of attributes for the element
*
* @return void
*
* @since 1.7.0
*/
public function _startElement($parser, $name, $attrs = array())
{
$this->stack[] = $name;
$tag = $this->_getStackLocation();
// Reset the data
if (isset($this->$tag))
{
$this->$tag->_data = '';
}
switch ($name)
{
case 'CATEGORY':
if (isset($attrs['REF']))
{
$this->update_sites[] = array('type' =>
'collection', 'location' => $attrs['REF'],
'update_site_id' => $this->updateSiteId);
}
else
{
// This item will have children, so prepare to attach them
$this->pop_parent = 1;
}
break;
case 'EXTENSION':
$update = Table::getInstance('update');
$update->set('update_site_id', $this->updateSiteId);
foreach ($this->updatecols as $col)
{
// Reset the values if it doesn't exist
if (!array_key_exists($col, $attrs))
{
$attrs[$col] = '';
if ($col == 'CLIENT')
{
$attrs[$col] = 'site';
}
}
}
$client = ApplicationHelper::getClientInfo($attrs['CLIENT'],
1);
if (isset($client->id))
{
$attrs['CLIENT_ID'] = $client->id;
}
// Lower case all of the fields
foreach ($attrs as $key => $attr)
{
$values[strtolower($key)] = $attr;
}
// Only add the update if it is on the same platform and release as we
are
$ver = new Version;
// Lower case and remove the exclamation mark
$product =
strtolower(InputFilter::getInstance()->clean($ver::PRODUCT,
'cmd'));
/*
* Set defaults, the extension file should clarify in case but it may
be only available in one version
* This allows an update site to specify a targetplatform
* targetplatformversion can be a regexp, so 1.[56] would be valid for
an extension that supports 1.5 and 1.6
* Note: Whilst the version is a regexp here, the targetplatform is not
(new extension per platform)
* Additionally, the version is a regexp here and it may also be in an
extension file if the extension is
* compatible against multiple versions of the same platform (e.g. a
library)
*/
if (!isset($values['targetplatform']))
{
$values['targetplatform'] = $product;
}
// Set this to ourself as a default
if (!isset($values['targetplatformversion']))
{
$values['targetplatformversion'] = $ver::RELEASE;
}
// Set this to ourselves as a default
// validate that we can install the extension
if ($product == $values['targetplatform'] &&
preg_match('/^' . $values['targetplatformversion'] .
'/', JVERSION))
{
$update->bind($values);
$this->updates[] = $update;
}
break;
}
}
/**
* Closing an XML element
* Note: This is a protected function though has to be exposed externally
as a callback
*
* @param object $parser Parser object
* @param string $name Name of the element closing
*
* @return void
*
* @since 1.7.0
*/
protected function _endElement($parser, $name)
{
array_pop($this->stack);
switch ($name)
{
case 'CATEGORY':
if ($this->pop_parent)
{
$this->pop_parent = 0;
array_pop($this->parent);
}
break;
}
}
// Note: we don't care about char data in collection because there
should be none
/**
* Finds an update
*
* @param array $options Options to use: update_site_id: the unique ID
of the update site to look at
*
* @return array Update_sites and updates discovered
*
* @since 1.7.0
*/
public function findUpdate($options)
{
$response = $this->getUpdateSiteResponse($options);
if ($response === false)
{
return false;
}
$this->xmlParser = xml_parser_create('');
xml_set_object($this->xmlParser, $this);
xml_set_element_handler($this->xmlParser, '_startElement',
'_endElement');
if (!xml_parse($this->xmlParser, $response->body))
{
// If the URL is missing the .xml extension, try appending it and retry
loading the update
if (!$this->appendExtension && (substr($this->_url, -4) !=
'.xml'))
{
$options['append_extension'] = true;
return $this->findUpdate($options);
}
Log::add('Error parsing url: ' . $this->_url, Log::WARNING,
'updater');
$app = Factory::getApplication();
$app->enqueueMessage(\JText::sprintf('JLIB_UPDATER_ERROR_COLLECTION_PARSE_URL',
$this->_url), 'warning');
return false;
}
// TODO: Decrement the bad counter if non-zero
return array('update_sites' => $this->update_sites,
'updates' => $this->updates);
}
}
ExtensionAdapter.php000064400000025713151160300740010537 0ustar00<?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\Updater\Adapter;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Factory;
use Joomla\CMS\Filter\InputFilter;
use Joomla\CMS\Log\Log;
use Joomla\CMS\Table\Table;
use Joomla\CMS\Updater\UpdateAdapter;
use Joomla\CMS\Updater\Updater;
use Joomla\CMS\Version;
/**
* Extension class for updater
*
* @since 1.7.0
*/
class ExtensionAdapter extends UpdateAdapter
{
/**
* Start element parser callback.
*
* @param object $parser The parser object.
* @param string $name The name of the element.
* @param array $attrs The attributes of the element.
*
* @return void
*
* @since 1.7.0
*/
protected function _startElement($parser, $name, $attrs = array())
{
$this->stack[] = $name;
$tag = $this->_getStackLocation();
// Reset the data
if (isset($this->$tag))
{
$this->$tag->_data = '';
}
switch ($name)
{
case 'UPDATE':
$this->currentUpdate = Table::getInstance('update');
$this->currentUpdate->update_site_id = $this->updateSiteId;
$this->currentUpdate->detailsurl = $this->_url;
$this->currentUpdate->folder = '';
$this->currentUpdate->client_id = 1;
break;
// Don't do anything
case 'UPDATES':
break;
default:
if (in_array($name, $this->updatecols))
{
$name = strtolower($name);
$this->currentUpdate->$name = '';
}
if ($name == 'TARGETPLATFORM')
{
$this->currentUpdate->targetplatform = $attrs;
}
if ($name == 'PHP_MINIMUM')
{
$this->currentUpdate->php_minimum = '';
}
if ($name == 'SUPPORTED_DATABASES')
{
$this->currentUpdate->supported_databases = $attrs;
}
break;
}
}
/**
* Character Parser Function
*
* @param object $parser Parser object.
* @param object $name The name of the element.
*
* @return void
*
* @since 1.7.0
*/
protected function _endElement($parser, $name)
{
array_pop($this->stack);
// @todo remove code: echo 'Closing: '. $name .'<br
/>';
switch ($name)
{
case 'UPDATE':
// Lower case and remove the exclamation mark
$product =
strtolower(InputFilter::getInstance()->clean(Version::PRODUCT,
'cmd'));
// Support for the min_dev_level and max_dev_level attributes is
deprecated, a regexp should be used instead
if (isset($this->currentUpdate->targetplatform->min_dev_level)
|| isset($this->currentUpdate->targetplatform->max_dev_level))
{
Log::add(
'Support for the min_dev_level and max_dev_level attributes of
an update\'s <targetplatform> tag is deprecated and'
. ' will be removed in 4.0. The full version should be specified
in the version attribute and may optionally be a regexp.',
Log::WARNING,
'deprecated'
);
}
/*
* Check that the product matches and that the version matches
(optionally a regexp)
*
* Check for optional min_dev_level and max_dev_level attributes to
further specify targetplatform (e.g., 3.0.1)
*/
$patchMinimumSupported =
!isset($this->currentUpdate->targetplatform->min_dev_level)
|| Version::PATCH_VERSION >=
$this->currentUpdate->targetplatform->min_dev_level;
$patchMaximumSupported =
!isset($this->currentUpdate->targetplatform->max_dev_level)
|| Version::PATCH_VERSION <=
$this->currentUpdate->targetplatform->max_dev_level;
if ($product ==
$this->currentUpdate->targetplatform['NAME']
&& preg_match('/^' .
$this->currentUpdate->targetplatform['VERSION'] .
'/', JVERSION)
&& $patchMinimumSupported
&& $patchMaximumSupported)
{
// Check if PHP version supported via <php_minimum> tag, assume
true if tag isn't present
if (!isset($this->currentUpdate->php_minimum) ||
version_compare(PHP_VERSION, $this->currentUpdate->php_minimum,
'>='))
{
$phpMatch = true;
}
else
{
// Notify the user of the potential update
$msg = \JText::sprintf(
'JLIB_INSTALLER_AVAILABLE_UPDATE_PHP_VERSION',
$this->currentUpdate->name,
$this->currentUpdate->version,
$this->currentUpdate->php_minimum,
PHP_VERSION
);
Factory::getApplication()->enqueueMessage($msg,
'warning');
$phpMatch = false;
}
$dbMatch = false;
// Check if DB & version is supported via
<supported_databases> tag, assume supported if tag isn't present
if (isset($this->currentUpdate->supported_databases))
{
$db = Factory::getDbo();
$dbType = strtoupper($db->getServerType());
$dbVersion = $db->getVersion();
$supportedDbs = $this->currentUpdate->supported_databases;
// MySQL and MariaDB use the same database driver but not the same
version numbers
if ($dbType === 'mysql')
{
// Check whether we have a MariaDB version string and extract the
proper version from it
if (stripos($dbVersion, 'mariadb') !== false)
{
// MariaDB: Strip off any leading '5.5.5-', if present
$dbVersion = preg_replace('/^5\.5\.5-/', '',
$dbVersion);
$dbType = 'mariadb';
}
}
// Do we have an entry for the database?
if (array_key_exists($dbType, $supportedDbs))
{
$minumumVersion = $supportedDbs[$dbType];
$dbMatch = version_compare($dbVersion, $minumumVersion,
'>=');
if (!$dbMatch)
{
// Notify the user of the potential update
$dbMsg = \JText::sprintf(
'JLIB_INSTALLER_AVAILABLE_UPDATE_DB_MINIMUM',
$this->currentUpdate->name,
$this->currentUpdate->version,
\JText::_($db->name),
$dbVersion,
$minumumVersion
);
Factory::getApplication()->enqueueMessage($dbMsg,
'warning');
}
}
else
{
// Notify the user of the potential update
$dbMsg = \JText::sprintf(
'JLIB_INSTALLER_AVAILABLE_UPDATE_DB_TYPE',
$this->currentUpdate->name,
$this->currentUpdate->version,
\JText::_($db->name)
);
Factory::getApplication()->enqueueMessage($dbMsg,
'warning');
}
}
else
{
// Set to true if the <supported_databases> tag is not set
$dbMatch = true;
}
// Check minimum stability
$stabilityMatch = true;
if (isset($this->currentUpdate->stability) &&
($this->currentUpdate->stability < $this->minimum_stability))
{
$stabilityMatch = false;
}
// Some properties aren't valid fields in the update table so
unset them to prevent J! from trying to store them
unset($this->currentUpdate->targetplatform);
if (isset($this->currentUpdate->php_minimum))
{
unset($this->currentUpdate->php_minimum);
}
if (isset($this->currentUpdate->supported_databases))
{
unset($this->currentUpdate->supported_databases);
}
if (isset($this->currentUpdate->stability))
{
unset($this->currentUpdate->stability);
}
// If the PHP version and minimum stability checks pass, consider this
version as a possible update
if ($phpMatch && $stabilityMatch && $dbMatch)
{
if (isset($this->latest))
{
// We already have a possible update. Check the version.
if (version_compare($this->currentUpdate->version,
$this->latest->version, '>') == 1)
{
$this->latest = $this->currentUpdate;
}
}
else
{
// We don't have any possible updates yet, assume this is an
available update.
$this->latest = $this->currentUpdate;
}
}
}
break;
case 'UPDATES':
// :D
break;
}
}
/**
* Character Parser Function
*
* @param object $parser Parser object.
* @param object $data The data.
*
* @return void
*
* @note This is public because its called externally.
* @since 1.7.0
*/
protected function _characterData($parser, $data)
{
$tag = $this->_getLastTag();
if (in_array($tag, $this->updatecols))
{
$tag = strtolower($tag);
$this->currentUpdate->$tag .= $data;
}
if ($tag == 'PHP_MINIMUM')
{
$this->currentUpdate->php_minimum = $data;
}
if ($tag == 'TAG')
{
$this->currentUpdate->stability =
$this->stabilityTagToInteger((string) $data);
}
}
/**
* Finds an update.
*
* @param array $options Update options.
*
* @return array Array containing the array of update sites and array of
updates
*
* @since 1.7.0
*/
public function findUpdate($options)
{
$response = $this->getUpdateSiteResponse($options);
if ($response === false)
{
return false;
}
if (array_key_exists('minimum_stability', $options))
{
$this->minimum_stability = $options['minimum_stability'];
}
$this->xmlParser = xml_parser_create('');
xml_set_object($this->xmlParser, $this);
xml_set_element_handler($this->xmlParser, '_startElement',
'_endElement');
xml_set_character_data_handler($this->xmlParser,
'_characterData');
if (!xml_parse($this->xmlParser, $response->body))
{
// If the URL is missing the .xml extension, try appending it and retry
loading the update
if (!$this->appendExtension && (substr($this->_url, -4) !=
'.xml'))
{
$options['append_extension'] = true;
return $this->findUpdate($options);
}
Log::add('Error parsing url: ' . $this->_url, Log::WARNING,
'updater');
$app = Factory::getApplication();
$app->enqueueMessage(\JText::sprintf('JLIB_UPDATER_ERROR_EXTENSION_PARSE_URL',
$this->_url), 'warning');
return false;
}
xml_parser_free($this->xmlParser);
if (isset($this->latest))
{
if (isset($this->latest->client) &&
strlen($this->latest->client))
{
if (is_numeric($this->latest->client))
{
$byName = false;
// <client> has to be 'administrator' or
'site', numeric values are deprecated. See
https://docs.joomla.org/Special:MyLanguage/Design_of_JUpdate
Log::add(
'Using numeric values for <client> in the updater xml is
deprecated. Use \'administrator\' or \'site\'
instead.',
Log::WARNING, 'deprecated'
);
}
else
{
$byName = true;
}
$this->latest->client_id =
ApplicationHelper::getClientInfo($this->latest->client,
$byName)->id;
unset($this->latest->client);
}
$updates = array($this->latest);
}
else
{
$updates = array();
}
return array('update_sites' => array(), 'updates'
=> $updates);
}
/**
* Converts a tag to numeric stability representation. If the tag
doesn't represent a known stability level (one of
* dev, alpha, beta, rc, stable) it is ignored.
*
* @param string $tag The tag string, e.g. dev, alpha, beta, rc,
stable
*
* @return integer
*
* @since 3.4
*/
protected function stabilityTagToInteger($tag)
{
$constant = '\\Joomla\\CMS\\Updater\\Updater::STABILITY_' .
strtoupper($tag);
if (defined($constant))
{
return constant($constant);
}
return Updater::STABILITY_STABLE;
}
}
ComponentAdapter.php000064400000113540151160361610010524 0ustar00<?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;
}
}
FileAdapter.php000064400000040165151160361630007445 0ustar00<?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;
}
}
}
LanguageAdapter.php000064400000061741151160361630010314 0ustar00<?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;
}
}
}
LibraryAdapter.php000064400000032453151160361630010173 0ustar00<?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;
}
}
}
ModuleAdapter.php000064400000050420151160361630010006 0ustar00<?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;
}
}
}
PackageAdapter.php000064400000047017151160361630010124 0ustar00<?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;
}
}
}
PluginAdapter.php000064400000046454151160361630010033 0ustar00<?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;
}
}
}
TemplateAdapter.php000064400000041626151160361640010345 0ustar00<?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;
}
}
}