Spade
Mini Shell
| Directory:~$ /home/lmsyaran/public_html/joomla4/ |
| [Home] [System Details] [Kill Me] |
PKK�[uIg5`�`�Adapter/ComponentAdapter.phpnu�[���<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Installer\Adapter;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Installer\Installer;
use Joomla\CMS\Installer\InstallerAdapter;
use Joomla\CMS\Table\Asset;
use Joomla\CMS\Table\Extension;
use Joomla\CMS\Table\Table;
use Joomla\CMS\Table\Update;
jimport('joomla.filesystem.folder');
/**
* Component installer
*
* @since 3.1
*/
class ComponentAdapter extends InstallerAdapter
{
/**
* The list of current files fo the Joomla! CMS administrator that are
installed and is read
* from the manifest on disk in the update area to handle doing a diff
* and deleting files that are in the old files list and not in the new
* files list.
*
* @var array
* @since 3.1
* */
protected $oldAdminFiles = null;
/**
* The list of current files that are installed and is read
* from the manifest on disk in the update area to handle doing a diff
* and deleting files that are in the old files list and not in the new
* files list.
*
* @var array
* @since 3.1
* */
protected $oldFiles = null;
/**
* A path to the PHP file that the scriptfile declaration in
* the manifest refers to.
*
* @var string
* @since 3.1
* */
protected $manifest_script = null;
/**
* For legacy installations this is a path to the PHP file that the
scriptfile declaration in the
* manifest refers to.
*
* @var string
* @since 3.1
* */
protected $install_script = null;
/**
* Method to check if the extension is present in the filesystem
*
* @return boolean
*
* @since 3.4
* @throws \RuntimeException
*/
protected function checkExtensionInFilesystem()
{
/*
* If the component site or admin directory already exists, then we will
assume that the component is already
* installed or another component is using that directory.
*/
if (file_exists($this->parent->getPath('extension_site'))
||
file_exists($this->parent->getPath('extension_administrator')))
{
// Look for an update function or update tag
$updateElement = $this->getManifest()->update;
// Upgrade manually set or update function available or update tag
detected
if ($updateElement || $this->parent->isUpgrade()
|| ($this->parent->manifestClass &&
method_exists($this->parent->manifestClass, 'update')))
{
// If there is a matching extension mark this as an update
$this->setRoute('update');
}
elseif (!$this->parent->isOverwrite())
{
// We didn't have overwrite set, find an update function or find
an update tag so lets call it safe
if
(file_exists($this->parent->getPath('extension_site')))
{
// If the site exists say so.
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ERROR_COMP_INSTALL_DIR_SITE',
$this->parent->getPath('extension_site')
)
);
}
// If the admin exists say so
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ERROR_COMP_INSTALL_DIR_ADMIN',
$this->parent->getPath('extension_administrator')
)
);
}
}
return false;
}
/**
* Method to copy the extension's base files from the `<files>`
tag(s) and the manifest file
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function copyBaseFiles()
{
// Copy site files
if ($this->getManifest()->files)
{
if ($this->route === 'update')
{
$result =
$this->parent->parseFiles($this->getManifest()->files, 0,
$this->oldFiles);
}
else
{
$result =
$this->parent->parseFiles($this->getManifest()->files);
}
if ($result === false)
{
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_COMP_FAIL_SITE_FILES',
\JText::_('JLIB_INSTALLER_' . strtoupper($this->route))
)
);
}
}
// Copy admin files
if ($this->getManifest()->administration->files)
{
if ($this->route === 'update')
{
$result =
$this->parent->parseFiles($this->getManifest()->administration->files,
1, $this->oldAdminFiles);
}
else
{
$result =
$this->parent->parseFiles($this->getManifest()->administration->files,
1);
}
if ($result === false)
{
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_COMP_FAIL_ADMIN_FILES',
\JText::_('JLIB_INSTALLER_' . strtoupper($this->route))
)
);
}
}
// If there is a manifest script, let's copy it.
if ($this->manifest_script)
{
$path['src'] =
$this->parent->getPath('source') . '/' .
$this->manifest_script;
$path['dest'] =
$this->parent->getPath('extension_administrator') .
'/' . $this->manifest_script;
if ($this->parent->isOverwrite() ||
!file_exists($path['dest']))
{
if (!$this->parent->copyFiles(array($path)))
{
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_COMP_COPY_MANIFEST',
\JText::_('JLIB_INSTALLER_' . strtoupper($this->route))
)
);
}
}
}
}
/**
* Method to create the extension root path if necessary
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function createExtensionRoot()
{
// If the component directory does not exist, let's create it
$created = false;
if
(!file_exists($this->parent->getPath('extension_site')))
{
if (!$created =
\JFolder::create($this->parent->getPath('extension_site')))
{
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ERROR_COMP_FAILED_TO_CREATE_DIRECTORY',
\JText::_('JLIB_INSTALLER_' . strtoupper($this->route)),
$this->parent->getPath('extension_site')
)
);
}
}
/*
* Since we created the component directory and we will want to remove it
if we have to roll back
* the installation, let's add it to the installation step stack
*/
if ($created)
{
$this->parent->pushStep(
array(
'type' => 'folder',
'path' =>
$this->parent->getPath('extension_site'),
)
);
}
// If the component admin directory does not exist, let's create it
$created = false;
if
(!file_exists($this->parent->getPath('extension_administrator')))
{
if (!$created =
\JFolder::create($this->parent->getPath('extension_administrator')))
{
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ERROR_COMP_FAILED_TO_CREATE_DIRECTORY',
\JText::_('JLIB_INSTALLER_' . strtoupper($this->route)),
$this->parent->getPath('extension_site')
)
);
}
}
/*
* Since we created the component admin directory and we will want to
remove it if we have to roll
* back the installation, let's add it to the installation step
stack
*/
if ($created)
{
$this->parent->pushStep(
array(
'type' => 'folder',
'path' =>
$this->parent->getPath('extension_administrator'),
)
);
}
}
/**
* Method to finalise the installation processing
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function finaliseInstall()
{
/** @var Update $update */
$update = Table::getInstance('update');
// Clobber any possible pending updates
$uid = $update->find(
array(
'element' => $this->element,
'type' => $this->extension->type,
'client_id' => 1,
)
);
if ($uid)
{
$update->delete($uid);
}
// We will copy the manifest file to its appropriate place.
if ($this->route !== 'discover_install')
{
if (!$this->parent->copyManifest())
{
// Install failed, roll back changes
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_COMP_COPY_SETUP',
\JText::_('JLIB_INSTALLER_' . strtoupper($this->route))
)
);
}
}
// Time to build the admin menus
if (!$this->_buildAdminMenus($this->extension->extension_id))
{
\JLog::add(\JText::_('JLIB_INSTALLER_ABORT_COMP_BUILDADMINMENUS_FAILED'),
\JLog::WARNING, 'jerror');
}
// Make sure that menu items pointing to the component have correct
component id assigned to them.
// Prevents message "Component 'com_extension' does not
exist." after uninstalling / re-installing component.
if (!$this->_updateMenus($this->extension->extension_id))
{
\JLog::add(\JText::_('JLIB_INSTALLER_ABORT_COMP_UPDATESITEMENUS_FAILED'),
\JLog::WARNING, 'jerror');
}
/** @var Asset $asset */
$asset = Table::getInstance('Asset');
// Check if an asset already exists for this extension and create it if
not
if (!$asset->loadByName($this->extension->element))
{
// Register the component container just under root in the assets table.
$asset->name = $this->extension->element;
$asset->parent_id = 1;
$asset->rules = '{}';
$asset->title = $this->extension->name;
$asset->setLocation(1, 'last-child');
if (!$asset->store())
{
// Install failed, roll back changes
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_ROLLBACK',
\JText::_('JLIB_INSTALLER_' . strtoupper($this->route)),
$this->extension->getError()
)
);
}
}
}
/**
* Get the filtered extension element from the manifest
*
* @param string $element Optional element name to be converted
*
* @return string The filtered element
*
* @since 3.4
*/
public function getElement($element = null)
{
$element = parent::getElement($element);
if (strpos($element, 'com_') !== 0)
{
$element = 'com_' . $element;
}
return $element;
}
/**
* Custom loadLanguage method
*
* @param string $path The path language files are on.
*
* @return void
*
* @since 3.1
*/
public function loadLanguage($path = null)
{
$source = $this->parent->getPath('source');
$client = $this->parent->extension->client_id ?
JPATH_ADMINISTRATOR : JPATH_SITE;
if (!$source)
{
$this->parent->setPath('source', $client .
'/components/' . $this->parent->extension->element);
}
$extension = $this->getElement();
$source = $path ?: $client . '/components/' . $extension;
if ($this->getManifest()->administration->files)
{
$element = $this->getManifest()->administration->files;
}
elseif ($this->getManifest()->files)
{
$element = $this->getManifest()->files;
}
else
{
$element = null;
}
if ($element)
{
$folder = (string) $element->attributes()->folder;
if ($folder && file_exists($path . '/' . $folder))
{
$source = $path . '/' . $folder;
}
}
$this->doLoadLanguage($extension, $source);
}
/**
* Method to parse optional tags in the manifest
*
* @return void
*
* @since 3.4
*/
protected function parseOptionalTags()
{
// Parse optional tags
$this->parent->parseMedia($this->getManifest()->media);
$this->parent->parseLanguages($this->getManifest()->languages);
$this->parent->parseLanguages($this->getManifest()->administration->languages,
1);
}
/**
* Prepares the adapter for a discover_install task
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
public function prepareDiscoverInstall()
{
// Need to find to find where the XML file is since we don't store
this normally
$client =
ApplicationHelper::getClientInfo($this->extension->client_id);
$short_element = str_replace('com_', '',
$this->extension->element);
$manifestPath = $client->path . '/components/' .
$this->extension->element . '/' . $short_element .
'.xml';
$this->parent->manifest =
$this->parent->isManifest($manifestPath);
$this->parent->setPath('manifest', $manifestPath);
$this->parent->setPath('source', $client->path .
'/components/' . $this->extension->element);
$this->parent->setPath('extension_root',
$this->parent->getPath('source'));
$this->setManifest($this->parent->getManifest());
$manifest_details =
Installer::parseXMLInstallFile($this->parent->getPath('manifest'));
$this->extension->manifest_cache = json_encode($manifest_details);
$this->extension->state = 0;
$this->extension->name = $manifest_details['name'];
$this->extension->enabled = 1;
$this->extension->params = $this->parent->getParams();
$stored = false;
try
{
$this->extension->store();
$stored = true;
}
catch (\RuntimeException $e)
{
// Try to delete existing failed records before retrying
$db = $this->db;
$query = $db->getQuery(true)
->select($db->qn('extension_id'))
->from($db->qn('#__extensions'))
->where($db->qn('name') . ' = ' .
$db->q($this->extension->name))
->where($db->qn('type') . ' = ' .
$db->q($this->extension->type))
->where($db->qn('element') . ' = ' .
$db->q($this->extension->element));
$db->setQuery($query);
$extension_ids = $db->loadColumn();
if (!empty($extension_ids))
{
foreach ($extension_ids as $eid)
{
// Remove leftover admin menus for this extension ID
$this->_removeAdminMenus($eid);
// Remove the extension record itself
/** @var Extension $extensionTable */
$extensionTable = Table::getInstance('extension');
$extensionTable->delete($eid);
}
}
}
if (!$stored)
{
try
{
$this->extension->store();
}
catch (\RuntimeException $e)
{
throw new
\RuntimeException(\JText::_('JLIB_INSTALLER_ERROR_COMP_DISCOVER_STORE_DETAILS'),
$e->getCode(), $e);
}
}
}
/**
* Method to do any prechecks and setup the install paths for the
extension
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function setupInstallPaths()
{
// Set the installation target paths
$this->parent->setPath('extension_site',
\JPath::clean(JPATH_SITE . '/components/' . $this->element));
$this->parent->setPath('extension_administrator',
\JPath::clean(JPATH_ADMINISTRATOR . '/components/' .
$this->element));
// Copy the admin path as it's used as a common base
$this->parent->setPath('extension_root',
$this->parent->getPath('extension_administrator'));
// Make sure that we have an admin element
if (!$this->getManifest()->administration)
{
throw new
\RuntimeException(\JText::_('JLIB_INSTALLER_ERROR_COMP_INSTALL_ADMIN_ELEMENT'));
}
}
/**
* Method to setup the update routine for the adapter
*
* @return void
*
* @since 3.4
*/
protected function setupUpdates()
{
// Hunt for the original XML file
$old_manifest = null;
// Use a temporary instance due to side effects; start in the
administrator first
$tmpInstaller = new Installer;
$tmpInstaller->setPath('source',
$this->parent->getPath('extension_administrator'));
if (!$tmpInstaller->findManifest())
{
// Then the site
$tmpInstaller->setPath('source',
$this->parent->getPath('extension_site'));
if ($tmpInstaller->findManifest())
{
$old_manifest = $tmpInstaller->getManifest();
}
}
else
{
$old_manifest = $tmpInstaller->getManifest();
}
if ($old_manifest)
{
$this->oldAdminFiles = $old_manifest->administration->files;
$this->oldFiles = $old_manifest->files;
}
}
/**
* Method to store the extension to the database
*
* @param bool $deleteExisting Should I try to delete existing records
of the same component?
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function storeExtension($deleteExisting = false)
{
// The extension is stored during prepareDiscoverInstall for discover
installs
if ($this->route === 'discover_install')
{
return;
}
// Add or update an entry to the extension table
$this->extension->name = $this->name;
$this->extension->type = 'component';
$this->extension->element = $this->element;
// If we are told to delete existing extension entries then do so.
if ($deleteExisting)
{
$db = $this->parent->getDbo();
$query = $db->getQuery(true)
->select($db->qn('extension_id'))
->from($db->qn('#__extensions'))
->where($db->qn('name') . ' = ' .
$db->q($this->extension->name))
->where($db->qn('type') . ' = ' .
$db->q($this->extension->type))
->where($db->qn('element') . ' = ' .
$db->q($this->extension->element));
$db->setQuery($query);
$extension_ids = $db->loadColumn();
if (!empty($extension_ids))
{
foreach ($extension_ids as $eid)
{
// Remove leftover admin menus for this extension ID
$this->_removeAdminMenus($eid);
// Remove the extension record itself
/** @var Extension $extensionTable */
$extensionTable = Table::getInstance('extension');
$extensionTable->delete($eid);
}
}
}
// If there is not already a row, generate a heap of defaults
if (!$this->currentExtensionId)
{
$this->extension->folder = '';
$this->extension->enabled = 1;
$this->extension->protected = 0;
$this->extension->access = 0;
$this->extension->client_id = 1;
$this->extension->params = $this->parent->getParams();
$this->extension->custom_data = '';
$this->extension->system_data = '';
}
$this->extension->manifest_cache =
$this->parent->generateManifestCache();
$couldStore = $this->extension->store();
if (!$couldStore && $deleteExisting)
{
// Install failed, roll back changes
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_COMP_INSTALL_ROLLBACK',
$this->extension->getError()
)
);
}
if (!$couldStore && !$deleteExisting)
{
// Maybe we have a failed installation (e.g. timeout). Let's retry
after deleting old records.
$this->storeExtension(true);
}
}
/**
* Custom uninstall method for components
*
* @param integer $id The unique extension id of the component to
uninstall
*
* @return boolean True on success
*
* @since 3.1
*/
public function uninstall($id)
{
$db = $this->db;
$retval = true;
// First order of business will be to load the component object table
from the database.
// This should give us the necessary information to proceed.
if (!$this->extension->load((int) $id))
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_COMP_UNINSTALL_ERRORUNKOWNEXTENSION'),
\JLog::WARNING, 'jerror');
return false;
}
// Is the component we are trying to uninstall a core one?
// Because that is not a good idea...
if ($this->extension->protected)
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_COMP_UNINSTALL_WARNCORECOMPONENT'),
\JLog::WARNING, 'jerror');
return false;
}
/*
* Does this extension have a parent package?
* If so, check if the package disallows individual extensions being
uninstalled if the package is not being uninstalled
*/
if ($this->extension->package_id &&
!$this->parent->isPackageUninstall() &&
!$this->canUninstallPackageChild($this->extension->package_id))
{
\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_CANNOT_UNINSTALL_CHILD_OF_PACKAGE',
$this->extension->name), \JLog::WARNING, 'jerror');
return false;
}
// Get the admin and site paths for the component
$this->parent->setPath('extension_administrator',
\JPath::clean(JPATH_ADMINISTRATOR . '/components/' .
$this->extension->element));
$this->parent->setPath('extension_site',
\JPath::clean(JPATH_SITE . '/components/' .
$this->extension->element));
// Copy the admin path as it's used as a common base
$this->parent->setPath('extension_root',
$this->parent->getPath('extension_administrator'));
/**
*
---------------------------------------------------------------------------------------------
* Manifest Document Setup Section
*
---------------------------------------------------------------------------------------------
*/
// Find and load the XML install file for the component
$this->parent->setPath('source',
$this->parent->getPath('extension_administrator'));
// Get the package manifest object
// We do findManifest to avoid problem when uninstalling a list of
extension: getManifest cache its manifest file
$this->parent->findManifest();
$this->setManifest($this->parent->getManifest());
if (!$this->getManifest())
{
// Make sure we delete the folders if no manifest exists
\JFolder::delete($this->parent->getPath('extension_administrator'));
\JFolder::delete($this->parent->getPath('extension_site'));
// Remove the menu
$this->_removeAdminMenus($this->extension->extension_id);
// Raise a warning
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_COMP_UNINSTALL_ERRORREMOVEMANUALLY'),
\JLog::WARNING, 'jerror');
// Return
return false;
}
// Set the extensions name
$this->set('name', $this->getName());
$this->set('element', $this->getElement());
// Attempt to load the admin language file; might have uninstall strings
$this->loadLanguage(JPATH_ADMINISTRATOR . '/components/' .
$this->element);
/**
*
---------------------------------------------------------------------------------------------
* Installer Trigger Loading and Uninstall
*
---------------------------------------------------------------------------------------------
*/
$this->setupScriptfile();
try
{
$this->triggerManifestScript('uninstall');
}
catch (\RuntimeException $e)
{
// Ignore errors for now
}
/**
*
---------------------------------------------------------------------------------------------
* Database Processing Section
*
---------------------------------------------------------------------------------------------
*/
// Let's run the uninstall queries for the component
try
{
$this->parseQueries();
}
catch (\RuntimeException $e)
{
\JLog::add($e->getMessage(), \JLog::WARNING, 'jerror');
$retval = false;
}
$this->_removeAdminMenus($this->extension->extension_id);
/**
*
---------------------------------------------------------------------------------------------
* Filesystem Processing Section
*
---------------------------------------------------------------------------------------------
*/
// Let's remove those language files and media in the JROOT/images/
folder that are
// associated with the component we are uninstalling
$this->parent->removeFiles($this->getManifest()->media);
$this->parent->removeFiles($this->getManifest()->languages);
$this->parent->removeFiles($this->getManifest()->administration->languages,
1);
// Remove the schema version
$query = $db->getQuery(true)
->delete('#__schemas')
->where('extension_id = ' . $id);
$db->setQuery($query);
$db->execute();
// Remove the component container in the assets table.
$asset = Table::getInstance('Asset');
if ($asset->loadByName($this->element))
{
$asset->delete();
}
// Remove categories for this component
$query->clear()
->delete('#__categories')
->where('extension=' . $db->quote($this->element),
'OR')
->where('extension LIKE ' . $db->quote($this->element
. '.%'));
$db->setQuery($query);
$db->execute();
// Rebuild the categories for correct lft/rgt
$category = Table::getInstance('category');
$category->rebuild();
// Clobber any possible pending updates
$update = Table::getInstance('update');
$uid = $update->find(
array(
'element' => $this->extension->element,
'type' => 'component',
'client_id' => 1,
'folder' => '',
)
);
if ($uid)
{
$update->delete($uid);
}
// Now we need to delete the installation directories. This is the final
step in uninstalling the component.
if (trim($this->extension->element))
{
// Delete the component site directory
if (is_dir($this->parent->getPath('extension_site')))
{
if
(!\JFolder::delete($this->parent->getPath('extension_site')))
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_COMP_UNINSTALL_FAILED_REMOVE_DIRECTORY_SITE'),
\JLog::WARNING, 'jerror');
$retval = false;
}
}
// Delete the component admin directory
if
(is_dir($this->parent->getPath('extension_administrator')))
{
if
(!\JFolder::delete($this->parent->getPath('extension_administrator')))
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_COMP_UNINSTALL_FAILED_REMOVE_DIRECTORY_ADMIN'),
\JLog::WARNING, 'jerror');
$retval = false;
}
}
// Now we will no longer need the extension object, so let's delete
it
$this->extension->delete($this->extension->extension_id);
return $retval;
}
else
{
// No component option defined... cannot delete what we don't know
about
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_COMP_UNINSTALL_NO_OPTION'),
\JLog::WARNING, 'jerror');
return false;
}
}
/**
* Method to build menu database entries for a component
*
* @param int|null $componentId The component ID for which I'm
building menus
*
* @return boolean True if successful
*
* @since 3.1
*/
protected function _buildAdminMenus($componentId = null)
{
$db = $this->parent->getDbo();
$option = $this->get('element');
// If a component exists with this option in the table within the
protected menutype 'main' then we don't need to add menus
$query = $db->getQuery(true)
->select('m.id, e.extension_id')
->from('#__menu AS m')
->join('LEFT', '#__extensions AS e ON m.component_id =
e.extension_id')
->where('m.parent_id = 1')
->where('m.client_id = 1')
->where('m.menutype = ' . $db->quote('main'))
->where('e.element = ' . $db->quote($option));
$db->setQuery($query);
// In case of a failed installation (e.g. timeout error) we may have
duplicate menu item and extension records.
$componentrows = $db->loadObjectList();
// Check if menu items exist
if (!empty($componentrows))
{
// Don't do anything if overwrite has not been enabled
if (!$this->parent->isOverwrite())
{
return true;
}
// Remove all menu items
foreach ($componentrows as $componentrow)
{
// Remove existing menu items if overwrite has been enabled
if ($option)
{
// If something goes wrong, there's no way to rollback TODO:
Search for better solution
$this->_removeAdminMenus($componentrow->extension_id);
}
}
}
// Only try to detect the component ID if it's not provided
if (empty($componentId))
{
// Lets find the extension id
$query->clear()
->select('e.extension_id')
->from('#__extensions AS e')
->where('e.type = ' .
$db->quote('component'))
->where('e.element = ' . $db->quote($option));
$db->setQuery($query);
$componentId = $db->loadResult();
}
// Ok, now its time to handle the menus. Start with the component root
menu, then handle submenus.
$menuElement = $this->getManifest()->administration->menu;
// Just do not create the menu if $menuElement not exist
if (!$menuElement)
{
return true;
}
// If the menu item is hidden do nothing more, just return
if (in_array((string) $menuElement['hidden'],
array('true', 'hidden')))
{
return true;
}
// Let's figure out what the menu item data should look like
$data = array();
if ($menuElement)
{
// I have a menu element, use this information
$data['menutype'] = 'main';
$data['client_id'] = 1;
$data['title'] = (string) trim($menuElement);
$data['alias'] = (string) $menuElement;
$data['type'] = 'component';
$data['published'] = 1;
$data['parent_id'] = 1;
$data['component_id'] = $componentId;
$data['img'] = ((string)
$menuElement->attributes()->img) ?: 'class:component';
$data['home'] = 0;
$data['path'] = '';
$data['params'] = '';
// Set the menu link
$request = array();
if ((string) $menuElement->attributes()->task)
{
$request[] = 'task=' .
$menuElement->attributes()->task;
}
if ((string) $menuElement->attributes()->view)
{
$request[] = 'view=' .
$menuElement->attributes()->view;
}
$qstring = count($request) ? '&' .
implode('&', $request) : '';
$data['link'] = 'index.php?option=' . $option .
$qstring;
}
else
{
// No menu element was specified, Let's make a generic menu item
$data = array();
$data['menutype'] = 'main';
$data['client_id'] = 1;
$data['title'] = $option;
$data['alias'] = $option;
$data['link'] = 'index.php?option=' . $option;
$data['type'] = 'component';
$data['published'] = 1;
$data['parent_id'] = 1;
$data['component_id'] = $componentId;
$data['img'] = 'class:component';
$data['home'] = 0;
$data['path'] = '';
$data['params'] = '';
}
// Try to create the menu item in the database
$parent_id = $this->_createAdminMenuItem($data, 1);
if ($parent_id === false)
{
return false;
}
/*
* Process SubMenus
*/
if (!$this->getManifest()->administration->submenu)
{
// No submenu? We're done.
return true;
}
foreach ($this->getManifest()->administration->submenu->menu
as $child)
{
$data = array();
$data['menutype'] = 'main';
$data['client_id'] = 1;
$data['title'] = (string) trim($child);
$data['alias'] = (string) $child;
$data['type'] = 'component';
$data['published'] = 1;
$data['parent_id'] = $parent_id;
$data['component_id'] = $componentId;
$data['img'] = ((string) $child->attributes()->img) ?:
'class:component';
$data['home'] = 0;
// Set the sub menu link
if ((string) $child->attributes()->link)
{
$data['link'] = 'index.php?' .
$child->attributes()->link;
}
else
{
$request = array();
if ((string) $child->attributes()->act)
{
$request[] = 'act=' . $child->attributes()->act;
}
if ((string) $child->attributes()->task)
{
$request[] = 'task=' . $child->attributes()->task;
}
if ((string) $child->attributes()->controller)
{
$request[] = 'controller=' .
$child->attributes()->controller;
}
if ((string) $child->attributes()->view)
{
$request[] = 'view=' . $child->attributes()->view;
}
if ((string) $child->attributes()->layout)
{
$request[] = 'layout=' . $child->attributes()->layout;
}
if ((string) $child->attributes()->sub)
{
$request[] = 'sub=' . $child->attributes()->sub;
}
$qstring = count($request) ? '&' .
implode('&', $request) : '';
$data['link'] = 'index.php?option=' . $option .
$qstring;
}
$submenuId = $this->_createAdminMenuItem($data, $parent_id);
if ($submenuId === false)
{
return false;
}
/*
* Since we have created a menu item, we add it to the installation step
stack
* so that if we have to rollback the changes we can undo it.
*/
$this->parent->pushStep(array('type' =>
'menu', 'id' => $componentId));
}
return true;
}
/**
* Method to remove admin menu references to a component
*
* @param int $id The ID of the extension whose admin menus will be
removed
*
* @return boolean True if successful.
*
* @since 3.1
*/
protected function _removeAdminMenus($id)
{
$db = $this->parent->getDbo();
/** @var \JTableMenu $table */
$table = Table::getInstance('menu');
// Get the ids of the menu items
$query = $db->getQuery(true)
->select('id')
->from('#__menu')
->where($db->quoteName('client_id') . ' = 1')
->where($db->quoteName('menutype') . ' = ' .
$db->q('main'))
->where($db->quoteName('component_id') . ' = '
. (int) $id);
$db->setQuery($query);
$ids = $db->loadColumn();
$result = true;
// Check for error
if (!empty($ids))
{
// Iterate the items to delete each one.
foreach ($ids as $menuid)
{
if (!$table->delete((int) $menuid, false))
{
$this->setError($table->getError());
$result = false;
}
}
// Rebuild the whole tree
$table->rebuild();
}
return $result;
}
/**
* Method to update menu database entries for a component in case the
component has been uninstalled before.
* NOTE: This will not update admin menus. Use _updateMenus() instead to
update admin menus ase well.
*
* @param int|null $componentId The component ID.
*
* @return boolean True if successful
*
* @since 3.4.2
*/
protected function _updateSiteMenus($componentId = null)
{
return $this->_updateMenus($componentId, 0);
}
/**
* Method to update menu database entries for a component in case if the
component has been uninstalled before.
*
* @param int|null $componentId The component ID.
* @param int $clientId The client id
*
* @return boolean True if successful
*
* @since 3.7.0
*/
protected function _updateMenus($componentId, $clientId = null)
{
$db = $this->parent->getDbo();
$option = $this->get('element');
// Update all menu items which contain
'index.php?option=com_extension' or
'index.php?option=com_extension&...'
// to use the new component id.
$query = $db->getQuery(true)
->update('#__menu')
->set('component_id = ' . $db->quote($componentId))
->where('type = ' . $db->quote('component'))
->where('('
. 'link LIKE ' . $db->quote('index.php?option='
. $option) . ' OR '
. 'link LIKE ' .
$db->q($db->escape('index.php?option=' . $option .
'&') . '%', false)
. ')');
if (isset($clientId))
{
$query->where('client_id = ' . (int) $clientId);
}
$db->setQuery($query);
try
{
$db->execute();
}
catch (\RuntimeException $e)
{
return false;
}
return true;
}
/**
* Custom rollback method
* - Roll back the component menu item
*
* @param array $step Installation step to rollback.
*
* @return boolean True on success
*
* @since 3.1
*/
protected function _rollback_menu($step)
{
return $this->_removeAdminMenus($step['id']);
}
/**
* Discover unregistered extensions.
*
* @return array A list of extensions.
*
* @since 3.1
*/
public function discover()
{
$results = array();
$site_components = \JFolder::folders(JPATH_SITE .
'/components');
$admin_components = \JFolder::folders(JPATH_ADMINISTRATOR .
'/components');
foreach ($site_components as $component)
{
if (file_exists(JPATH_SITE . '/components/' . $component .
'/' . str_replace('com_', '', $component) .
'.xml'))
{
$manifest_details = Installer::parseXMLInstallFile(
JPATH_SITE . '/components/' . $component . '/' .
str_replace('com_', '', $component) . '.xml'
);
$extension = Table::getInstance('extension');
$extension->set('type', 'component');
$extension->set('client_id', 0);
$extension->set('element', $component);
$extension->set('folder', '');
$extension->set('name', $component);
$extension->set('state', -1);
$extension->set('manifest_cache',
json_encode($manifest_details));
$extension->set('params', '{}');
$results[] = $extension;
}
}
foreach ($admin_components as $component)
{
if (file_exists(JPATH_ADMINISTRATOR . '/components/' .
$component . '/' . str_replace('com_', '',
$component) . '.xml'))
{
$manifest_details = Installer::parseXMLInstallFile(
JPATH_ADMINISTRATOR . '/components/' . $component .
'/' . str_replace('com_', '', $component) .
'.xml'
);
$extension = Table::getInstance('extension');
$extension->set('type', 'component');
$extension->set('client_id', 1);
$extension->set('element', $component);
$extension->set('folder', '');
$extension->set('name', $component);
$extension->set('state', -1);
$extension->set('manifest_cache',
json_encode($manifest_details));
$extension->set('params', '{}');
$results[] = $extension;
}
}
return $results;
}
/**
* Refreshes the extension table cache
*
* @return boolean Result of operation, true if updated, false on
failure
*
* @since 3.1
*/
public function refreshManifestCache()
{
// Need to find to find where the XML file is since we don't store
this normally
$client =
ApplicationHelper::getClientInfo($this->parent->extension->client_id);
$short_element = str_replace('com_', '',
$this->parent->extension->element);
$manifestPath = $client->path . '/components/' .
$this->parent->extension->element . '/' . $short_element
. '.xml';
$this->parent->manifest =
$this->parent->isManifest($manifestPath);
$this->parent->setPath('manifest', $manifestPath);
$manifest_details =
Installer::parseXMLInstallFile($this->parent->getPath('manifest'));
$this->parent->extension->manifest_cache =
json_encode($manifest_details);
$this->parent->extension->name =
$manifest_details['name'];
try
{
return $this->parent->extension->store();
}
catch (\RuntimeException $e)
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_COMP_REFRESH_MANIFEST_CACHE'),
\JLog::WARNING, 'jerror');
return false;
}
}
/**
* Creates the menu item in the database. If the item already exists it
tries to remove it and create it afresh.
*
* @param array &$data The menu item data to create
* @param integer $parentId The parent menu item ID
*
* @return boolean|integer Menu item ID on success, false on failure
*/
protected function _createAdminMenuItem(array &$data, $parentId)
{
$db = $this->parent->getDbo();
/** @var \JTableMenu $table */
$table = Table::getInstance('menu');
try
{
$table->setLocation($parentId, 'last-child');
}
catch (\InvalidArgumentException $e)
{
\JLog::add($e->getMessage(), \JLog::WARNING, 'jerror');
return false;
}
if (!$table->bind($data) || !$table->check() ||
!$table->store())
{
// The menu item already exists. Delete it and retry instead of throwing
an error.
$query = $db->getQuery(true)
->select('id')
->from('#__menu')
->where('menutype = ' .
$db->q($data['menutype']))
->where('client_id = 1')
->where('link = ' . $db->q($data['link']))
->where('type = ' . $db->q($data['type']))
->where('parent_id = ' .
$db->q($data['parent_id']))
->where('home = ' . $db->q($data['home']));
$db->setQuery($query);
$menu_id = $db->loadResult();
if (!$menu_id)
{
// Oops! Could not get the menu ID. Go back and rollback changes.
\JError::raiseWarning(1, $table->getError());
return false;
}
else
{
/** @var \JTableMenu $temporaryTable */
$temporaryTable = Table::getInstance('menu');
$temporaryTable->delete($menu_id, true);
$temporaryTable->load($parentId);
$temporaryTable->rebuild($parentId, $temporaryTable->lft,
$temporaryTable->level, $temporaryTable->path);
// Retry creating the menu item
$table->setLocation($parentId, 'last-child');
if (!$table->bind($data) || !$table->check() ||
!$table->store())
{
// Install failed, warn user and rollback changes
\JError::raiseWarning(1, $table->getError());
return false;
}
}
}
return $table->id;
}
}
PKK�[.Vou@u@Adapter/FileAdapter.phpnu�[���<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Installer\Adapter;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Installer\Installer;
use Joomla\CMS\Installer\InstallerAdapter;
use Joomla\CMS\Table\Table;
\JLoader::import('joomla.filesystem.folder');
/**
* File installer
*
* @since 3.1
*/
class FileAdapter extends InstallerAdapter
{
/**
* `<scriptfile>` element of the extension manifest
*
* @var object
* @since 3.1
*/
protected $scriptElement = null;
/**
* Flag if the adapter supports discover installs
*
* Adapters should override this and set to false if discover install is
unsupported
*
* @var boolean
* @since 3.4
*/
protected $supportsDiscoverInstall = false;
/**
* Method to copy the extension's base files from the `<files>`
tag(s) and the manifest file
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function copyBaseFiles()
{
// Populate File and Folder List to copy
$this->populateFilesAndFolderList();
// Now that we have folder list, lets start creating them
foreach ($this->folderList as $folder)
{
if (!\JFolder::exists($folder))
{
if (!$created = \JFolder::create($folder))
{
throw new \RuntimeException(
\JText::sprintf('JLIB_INSTALLER_ABORT_FILE_INSTALL_FAIL_SOURCE_DIRECTORY',
$folder)
);
}
// Since we created a directory and will want to remove it if we have
to roll back.
// The installation due to some errors, let's add it to the
installation step stack.
if ($created)
{
$this->parent->pushStep(array('type' =>
'folder', 'path' => $folder));
}
}
}
// Now that we have file list, let's start copying them
$this->parent->copyFiles($this->fileList);
}
/**
* Method to finalise the installation processing
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function finaliseInstall()
{
// Clobber any possible pending updates
$update = Table::getInstance('update');
$uid = $update->find(
array(
'element' => $this->element,
'type' => $this->type,
)
);
if ($uid)
{
$update->delete($uid);
}
// Lastly, we will copy the manifest file to its appropriate place.
$manifest = array();
$manifest['src'] =
$this->parent->getPath('manifest');
$manifest['dest'] = JPATH_MANIFESTS . '/files/' .
basename($this->parent->getPath('manifest'));
if (!$this->parent->copyFiles(array($manifest), true))
{
// Install failed, rollback changes
throw new
\RuntimeException(\JText::_('JLIB_INSTALLER_ABORT_FILE_INSTALL_COPY_SETUP'));
}
// If there is a manifest script, let's copy it.
if ($this->manifest_script)
{
// First, we have to create a folder for the script if one isn't
present
if
(!file_exists($this->parent->getPath('extension_root')))
{
\JFolder::create($this->parent->getPath('extension_root'));
}
$path['src'] =
$this->parent->getPath('source') . '/' .
$this->manifest_script;
$path['dest'] =
$this->parent->getPath('extension_root') . '/' .
$this->manifest_script;
if ($this->parent->isOverwrite() ||
!file_exists($path['dest']))
{
if (!$this->parent->copyFiles(array($path)))
{
// Install failed, rollback changes
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_MANIFEST',
\JText::_('JLIB_INSTALLER_' . strtoupper($this->route))
)
);
}
}
}
}
/**
* Get the filtered extension element from the manifest
*
* @param string $element Optional element name to be converted
*
* @return string The filtered element
*
* @since 3.4
*/
public function getElement($element = null)
{
if (!$element)
{
$manifestPath =
\JPath::clean($this->parent->getPath('manifest'));
$element = preg_replace('/\.xml/', '',
basename($manifestPath));
}
return $element;
}
/**
* Custom loadLanguage method
*
* @param string $path The path on which to find language files.
*
* @return void
*
* @since 3.1
*/
public function loadLanguage($path)
{
$extension = 'files_' .
strtolower(str_replace('files_', '',
$this->getElement()));
$this->doLoadLanguage($extension, $path, JPATH_SITE);
}
/**
* Method to parse optional tags in the manifest
*
* @return void
*
* @since 3.4
*/
protected function parseOptionalTags()
{
// Parse optional tags
$this->parent->parseLanguages($this->getManifest()->languages);
}
/**
* Method to do any prechecks and setup the install paths for the
extension
*
* @return void
*
* @since 3.4
*/
protected function setupInstallPaths()
{
// Set the file root path
if ($this->name === 'files_joomla')
{
// If we are updating the Joomla core, set the root path to the root of
Joomla
$this->parent->setPath('extension_root', JPATH_ROOT);
}
else
{
$this->parent->setPath('extension_root', JPATH_MANIFESTS
. '/files/' . $this->element);
}
}
/**
* Method to store the extension to the database
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function storeExtension()
{
if ($this->currentExtensionId)
{
// Load the entry and update the manifest_cache
$this->extension->load($this->currentExtensionId);
// Update name
$this->extension->name = $this->name;
// Update manifest
$this->extension->manifest_cache =
$this->parent->generateManifestCache();
if (!$this->extension->store())
{
// Install failed, roll back changes
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_ROLLBACK',
\JText::_('JLIB_INSTALLER_' . strtoupper($this->route)),
$this->extension->getError()
)
);
}
}
else
{
// Add an entry to the extension table with a whole heap of defaults
$this->extension->name = $this->name;
$this->extension->type = 'file';
$this->extension->element = $this->element;
// There is no folder for files so leave it blank
$this->extension->folder = '';
$this->extension->enabled = 1;
$this->extension->protected = 0;
$this->extension->access = 0;
$this->extension->client_id = 0;
$this->extension->params = '';
$this->extension->system_data = '';
$this->extension->manifest_cache =
$this->parent->generateManifestCache();
$this->extension->custom_data = '';
if (!$this->extension->store())
{
// Install failed, roll back changes
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_ROLLBACK',
\JText::_('JLIB_INSTALLER_' . strtoupper($this->route)),
$this->extension->getError()
)
);
}
// Since we have created a module item, we add it to the installation
step stack
// so that if we have to rollback the changes we can undo it.
$this->parent->pushStep(array('type' =>
'extension', 'extension_id' =>
$this->extension->extension_id));
}
}
/**
* Custom uninstall method
*
* @param string $id The id of the file to uninstall
*
* @return boolean True on success
*
* @since 3.1
*/
public function uninstall($id)
{
$row = Table::getInstance('extension');
if (!$row->load($id))
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_FILE_UNINSTALL_LOAD_ENTRY'),
\JLog::WARNING, 'jerror');
return false;
}
if ($row->protected)
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_FILE_UNINSTALL_WARNCOREFILE'),
\JLog::WARNING, 'jerror');
return false;
}
/*
* Does this extension have a parent package?
* If so, check if the package disallows individual extensions being
uninstalled if the package is not being uninstalled
*/
if ($row->package_id &&
!$this->parent->isPackageUninstall() &&
!$this->canUninstallPackageChild($row->package_id))
{
\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_CANNOT_UNINSTALL_CHILD_OF_PACKAGE',
$row->name), \JLog::WARNING, 'jerror');
return false;
}
$retval = true;
$manifestFile = JPATH_MANIFESTS . '/files/' . $row->element
. '.xml';
// Because files may not have their own folders we cannot use the
standard method of finding an installation manifest
if (file_exists($manifestFile))
{
// Set the files root path
$this->parent->setPath('extension_root', JPATH_MANIFESTS
. '/files/' . $row->element);
$xml = simplexml_load_file($manifestFile);
// If we cannot load the XML file return null
if (!$xml)
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_FILE_UNINSTALL_LOAD_MANIFEST'),
\JLog::WARNING, 'jerror');
return false;
}
// Check for a valid XML root tag.
if ($xml->getName() !== 'extension')
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_FILE_UNINSTALL_INVALID_MANIFEST'),
\JLog::WARNING, 'jerror');
return false;
}
$this->setManifest($xml);
// If there is a manifest class file, let's load it
$this->scriptElement = $this->getManifest()->scriptfile;
$manifestScript = (string) $this->getManifest()->scriptfile;
if ($manifestScript)
{
$manifestScriptFile =
$this->parent->getPath('extension_root') . '/' .
$manifestScript;
// Set the class name
$classname = $row->element . 'InstallerScript';
\JLoader::register($classname, $manifestScriptFile);
if (class_exists($classname))
{
// Create a new instance
$this->parent->manifestClass = new $classname($this);
// And set this so we can copy it later
$this->set('manifest_script', $manifestScript);
}
}
ob_start();
ob_implicit_flush(false);
// Run uninstall if possible
if ($this->parent->manifestClass &&
method_exists($this->parent->manifestClass, 'uninstall'))
{
$this->parent->manifestClass->uninstall($this);
}
$msg = ob_get_contents();
ob_end_clean();
if ($msg != '')
{
$this->parent->set('extension_message', $msg);
}
$db = \JFactory::getDbo();
// Let's run the uninstall queries for the extension
$result =
$this->parent->parseSQLFiles($this->getManifest()->uninstall->sql);
if ($result === false)
{
// Install failed, rollback changes
\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_FILE_UNINSTALL_SQL_ERROR',
$db->stderr(true)), \JLog::WARNING, 'jerror');
$retval = false;
}
// Remove the schema version
$query = $db->getQuery(true)
->delete('#__schemas')
->where('extension_id = ' . $row->extension_id);
$db->setQuery($query);
$db->execute();
// Loop through all elements and get list of files and folders
foreach ($xml->fileset->files as $eFiles)
{
$target = (string) $eFiles->attributes()->target;
// Create folder path
if (empty($target))
{
$targetFolder = JPATH_ROOT;
}
else
{
$targetFolder = JPATH_ROOT . '/' . $target;
}
$folderList = array();
// Check if all children exists
if (count($eFiles->children()) > 0)
{
// Loop through all filenames elements
foreach ($eFiles->children() as $eFileName)
{
if ($eFileName->getName() === 'folder')
{
$folderList[] = $targetFolder . '/' . $eFileName;
}
else
{
$fileName = $targetFolder . '/' . $eFileName;
\JFile::delete($fileName);
}
}
}
// Delete any folders that don't have any content in them.
foreach ($folderList as $folder)
{
$files = \JFolder::files($folder);
if ($files !== false && !count($files))
{
\JFolder::delete($folder);
}
}
}
\JFile::delete($manifestFile);
// Lastly, remove the extension_root
$folder = $this->parent->getPath('extension_root');
if (\JFolder::exists($folder))
{
\JFolder::delete($folder);
}
}
else
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_FILE_UNINSTALL_INVALID_NOTFOUND_MANIFEST'),
\JLog::WARNING, 'jerror');
// Delete the row because its broken
$row->delete();
return false;
}
$this->parent->removeFiles($xml->languages);
$row->delete();
return $retval;
}
/**
* Function used to check if extension is already installed
*
* @param string $extension The element name of the extension to
install
*
* @return boolean True if extension exists
*
* @since 3.1
*/
protected function extensionExistsInSystem($extension = null)
{
// Get a database connector object
$db = $this->parent->getDbo();
$query = $db->getQuery(true)
->select($db->quoteName('extension_id'))
->from($db->quoteName('#__extensions'))
->where($db->quoteName('type') . ' = ' .
$db->quote('file'))
->where($db->quoteName('element') . ' = ' .
$db->quote($extension));
$db->setQuery($query);
try
{
$db->execute();
}
catch (\RuntimeException $e)
{
// Install failed, roll back changes
$this->parent->abort(\JText::sprintf('JLIB_INSTALLER_ABORT_FILE_ROLLBACK',
$db->stderr(true)));
return false;
}
$id = $db->loadResult();
if (empty($id))
{
return false;
}
return true;
}
/**
* Function used to populate files and folder list
*
* @return boolean none
*
* @since 3.1
*/
protected function populateFilesAndFolderList()
{
// Initialise variable
$this->folderList = array();
$this->fileList = array();
// Set root folder names
$packagePath = $this->parent->getPath('source');
$jRootPath = \JPath::clean(JPATH_ROOT);
// Loop through all elements and get list of files and folders
foreach ($this->getManifest()->fileset->files as $eFiles)
{
// Check if the element is files element
$folder = (string) $eFiles->attributes()->folder;
$target = (string) $eFiles->attributes()->target;
// Split folder names into array to get folder names. This will help in
creating folders
$arrList = preg_split("#/|\\/#", $target);
$folderName = $jRootPath;
foreach ($arrList as $dir)
{
if (empty($dir))
{
continue;
}
$folderName .= '/' . $dir;
// Check if folder exists, if not then add to the array for folder
creation
if (!\JFolder::exists($folderName))
{
$this->folderList[] = $folderName;
}
}
// Create folder path
$sourceFolder = empty($folder) ? $packagePath : $packagePath .
'/' . $folder;
$targetFolder = empty($target) ? $jRootPath : $jRootPath . '/'
. $target;
// Check if source folder exists
if (!\JFolder::exists($sourceFolder))
{
\JLog::add(\JText::sprintf('JLIB_INSTALLER_ABORT_FILE_INSTALL_FAIL_SOURCE_DIRECTORY',
$sourceFolder), \JLog::WARNING, 'jerror');
// If installation fails, rollback
$this->parent->abort();
return false;
}
// Check if all children exists
if (count($eFiles->children()))
{
// Loop through all filenames elements
foreach ($eFiles->children() as $eFileName)
{
$path['src'] = $sourceFolder . '/' . $eFileName;
$path['dest'] = $targetFolder . '/' . $eFileName;
$path['type'] = 'file';
if ($eFileName->getName() === 'folder')
{
$folderName = $targetFolder . '/' . $eFileName;
$this->folderList[] = $folderName;
$path['type'] = 'folder';
}
$this->fileList[] = $path;
}
}
else
{
$files = \JFolder::files($sourceFolder);
foreach ($files as $file)
{
$path['src'] = $sourceFolder . '/' . $file;
$path['dest'] = $targetFolder . '/' . $file;
$this->fileList[] = $path;
}
}
}
}
/**
* Refreshes the extension table cache
*
* @return boolean result of operation, true if updated, false on failure
*
* @since 3.1
*/
public function refreshManifestCache()
{
// Need to find to find where the XML file is since we don't store
this normally
$manifestPath = JPATH_MANIFESTS . '/files/' .
$this->parent->extension->element . '.xml';
$this->parent->manifest =
$this->parent->isManifest($manifestPath);
$this->parent->setPath('manifest', $manifestPath);
$manifest_details =
Installer::parseXMLInstallFile($this->parent->getPath('manifest'));
$this->parent->extension->manifest_cache =
json_encode($manifest_details);
$this->parent->extension->name =
$manifest_details['name'];
try
{
return $this->parent->extension->store();
}
catch (\RuntimeException $e)
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_PACK_REFRESH_MANIFEST_CACHE'),
\JLog::WARNING, 'jerror');
return false;
}
}
}
PKK�[��i��c�cAdapter/LanguageAdapter.phpnu�[���<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Installer\Adapter;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Installer\Installer;
use Joomla\CMS\Installer\InstallerAdapter;
use Joomla\CMS\Language\Language;
use Joomla\CMS\Language\LanguageHelper;
use Joomla\CMS\Table\Table;
use Joomla\Registry\Registry;
jimport('joomla.filesystem.folder');
/**
* Language installer
*
* @since 3.1
*/
class LanguageAdapter extends InstallerAdapter
{
/**
* Core language pack flag
*
* @var boolean
* @since 3.0.0
*/
protected $core = false;
/**
* Method to copy the extension's base files from the `<files>`
tag(s) and the manifest file
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function copyBaseFiles()
{
// TODO - Refactor adapter to use common code
}
/**
* Method to do any prechecks and setup the install paths for the
extension
*
* @return void
*
* @since 3.4
*/
protected function setupInstallPaths()
{
// TODO - Refactor adapter to use common code
}
/**
* Method to store the extension to the database
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function storeExtension()
{
// TODO - Refactor adapter to use common code
}
/**
* Custom install method
*
* Note: This behaves badly due to hacks made in the middle of 1.5.x to
add
* the ability to install multiple distinct packs in one install. The
* preferred method is to use a package to install multiple language
packs.
*
* @return boolean|integer The extension ID on success, boolean false on
failure
*
* @since 3.1
*/
public function install()
{
$source = $this->parent->getPath('source');
if (!$source)
{
$this->parent
->setPath(
'source',
($this->parent->extension->client_id ? JPATH_ADMINISTRATOR :
JPATH_SITE) . '/language/' .
$this->parent->extension->element
);
}
$this->setManifest($this->parent->getManifest());
// Get the client application target
if ($cname = (string)
$this->getManifest()->attributes()->client)
{
// Attempt to map the client to a base path
$client = ApplicationHelper::getClientInfo($cname, true);
if ($client === null)
{
$this->parent->abort(\JText::sprintf('JLIB_INSTALLER_ABORT',
\JText::sprintf('JLIB_INSTALLER_ERROR_UNKNOWN_CLIENT_TYPE',
$cname)));
return false;
}
$basePath = $client->path;
$clientId = $client->id;
$element = $this->getManifest()->files;
return $this->_install($cname, $basePath, $clientId, $element);
}
else
{
// No client attribute was found so we assume the site as the client
$cname = 'site';
$basePath = JPATH_SITE;
$clientId = 0;
$element = $this->getManifest()->files;
return $this->_install($cname, $basePath, $clientId, $element);
}
}
/**
* Install function that is designed to handle individual clients
*
* @param string $cname Cname @todo: not used
* @param string $basePath The base name.
* @param integer $clientId The client id.
* @param object &$element The XML element.
*
* @return boolean|integer The extension ID on success, boolean false on
failure
*
* @since 3.1
*/
protected function _install($cname, $basePath, $clientId, &$element)
{
$this->setManifest($this->parent->getManifest());
// Get the language name
// Set the extensions name
$name = \JFilterInput::getInstance()->clean((string)
$this->getManifest()->name, 'cmd');
$this->set('name', $name);
// Get the Language tag [ISO tag, eg. en-GB]
$tag = (string) $this->getManifest()->tag;
// Check if we found the tag - if we didn't, we may be trying to
install from an older language package
if (!$tag)
{
$this->parent->abort(\JText::sprintf('JLIB_INSTALLER_ABORT',
\JText::_('JLIB_INSTALLER_ERROR_NO_LANGUAGE_TAG')));
return false;
}
$this->set('tag', $tag);
// Set the language installation path
$this->parent->setPath('extension_site', $basePath .
'/language/' . $tag);
// Do we have a meta file in the file list? In other words... is this a
core language pack?
if ($element && count($element->children()))
{
$files = $element->children();
foreach ($files as $file)
{
if ((string) $file->attributes()->file === 'meta')
{
$this->core = true;
break;
}
}
}
// If the language directory does not exist, let's create it
$created = false;
if
(!file_exists($this->parent->getPath('extension_site')))
{
if (!$created =
\JFolder::create($this->parent->getPath('extension_site')))
{
$this->parent
->abort(
\JText::sprintf(
'JLIB_INSTALLER_ABORT',
\JText::sprintf('JLIB_INSTALLER_ERROR_CREATE_FOLDER_FAILED',
$this->parent->getPath('extension_site'))
)
);
return false;
}
}
else
{
// Look for an update function or update tag
$updateElement = $this->getManifest()->update;
// Upgrade manually set or update tag detected
if ($updateElement || $this->parent->isUpgrade())
{
// Transfer control to the update function
return $this->update();
}
elseif (!$this->parent->isOverwrite())
{
// Overwrite is set
// We didn't have overwrite set, find an update function or find
an update tag so lets call it safe
if
(file_exists($this->parent->getPath('extension_site')))
{
// If the site exists say so.
\JLog::add(
\JText::sprintf('JLIB_INSTALLER_ABORT',
\JText::sprintf('JLIB_INSTALLER_ERROR_FOLDER_IN_USE',
$this->parent->getPath('extension_site'))),
\JLog::WARNING, 'jerror'
);
}
else
{
// If the admin exists say so.
\JLog::add(
\JText::sprintf('JLIB_INSTALLER_ABORT',
\JText::sprintf('JLIB_INSTALLER_ERROR_FOLDER_IN_USE',
$this->parent->getPath('extension_administrator'))
),
\JLog::WARNING, 'jerror'
);
}
return false;
}
}
/*
* If we created the language directory we will want to remove it if we
* have to roll back the installation, so let's add it to the
installation
* step stack
*/
if ($created)
{
$this->parent->pushStep(array('type' =>
'folder', 'path' =>
$this->parent->getPath('extension_site')));
}
// Copy all the necessary files
if ($this->parent->parseFiles($element) === false)
{
// Install failed, rollback changes
$this->parent->abort();
return false;
}
// Parse optional tags
$this->parent->parseMedia($this->getManifest()->media);
/*
* Log that PDF Fonts in language packs are deprecated and will be
removed in 4.0
* Ref: https://github.com/joomla/joomla-cms/issues/31286
*/
if (is_dir($basePath . '/language/pdf_fonts'))
{
try
{
\JLog::add(
'Using the "pdf_fonts" folder to load language specific
fonts in languages is deprecated and will be removed in 4.0.',
\JLog::WARNING,
'deprecated'
);
}
catch (RuntimeException $exception)
{
// Informational log only
}
}
// Copy all the necessary font files to the common pdf_fonts directory
$this->parent->setPath('extension_site', $basePath .
'/language/pdf_fonts');
$overwrite = $this->parent->setOverwrite(true);
if ($this->parent->parseFiles($this->getManifest()->fonts)
=== false)
{
// Install failed, rollback changes
$this->parent->abort();
return false;
}
$this->parent->setOverwrite($overwrite);
// Get the language description
$description = (string) $this->getManifest()->description;
if ($description)
{
$this->parent->set('message', \JText::_($description));
}
else
{
$this->parent->set('message', '');
}
// Add an entry to the extension table with a whole heap of defaults
$row = Table::getInstance('extension');
$row->set('name', $this->get('name'));
$row->set('type', 'language');
$row->set('element', $this->get('tag'));
// There is no folder for languages
$row->set('folder', '');
$row->set('enabled', 1);
$row->set('protected', 0);
$row->set('access', 0);
$row->set('client_id', $clientId);
$row->set('params', $this->parent->getParams());
$row->set('manifest_cache',
$this->parent->generateManifestCache());
if (!$row->check() || !$row->store())
{
// Install failed, roll back changes
$this->parent->abort(\JText::sprintf('JLIB_INSTALLER_ABORT',
$row->getError()));
return false;
}
// Create an unpublished content language.
if ((int) $clientId === 0)
{
// Load the site language manifest.
$siteLanguageManifest = LanguageHelper::parseXMLLanguageFile(JPATH_SITE
. '/language/' . $this->tag . '/' . $this->tag .
'.xml');
// Set the content language title as the language metadata name.
$contentLanguageTitle = $siteLanguageManifest['name'];
// Set, as fallback, the content language native title to the language
metadata name.
$contentLanguageNativeTitle = $contentLanguageTitle;
// If exist, load the native title from the language xml metadata.
if (isset($siteLanguageManifest['nativeName']) &&
$siteLanguageManifest['nativeName'])
{
$contentLanguageNativeTitle =
$siteLanguageManifest['nativeName'];
}
// Try to load a language string from the installation language var.
Will be removed in 4.0.
if ($contentLanguageNativeTitle === $contentLanguageTitle)
{
if (file_exists(JPATH_INSTALLATION . '/language/' .
$this->tag . '/' . $this->tag . '.xml'))
{
$installationLanguage = new Language($this->tag);
$installationLanguage->load('', JPATH_INSTALLATION);
if
($installationLanguage->hasKey('INSTL_DEFAULTLANGUAGE_NATIVE_LANGUAGE_NAME'))
{
// Make sure it will not use the en-GB fallback.
$defaultLanguage = new Language('en-GB');
$defaultLanguage->load('', JPATH_INSTALLATION);
$defaultLanguageNativeTitle =
$defaultLanguage->_('INSTL_DEFAULTLANGUAGE_NATIVE_LANGUAGE_NAME');
$installationLanguageNativeTitle =
$installationLanguage->_('INSTL_DEFAULTLANGUAGE_NATIVE_LANGUAGE_NAME');
if ($defaultLanguageNativeTitle !== $installationLanguageNativeTitle)
{
$contentLanguageNativeTitle =
$installationLanguage->_('INSTL_DEFAULTLANGUAGE_NATIVE_LANGUAGE_NAME');
}
}
}
}
// Prepare language data for store.
$languageData = array(
'lang_id' => 0,
'lang_code' => $this->tag,
'title' => $contentLanguageTitle,
'title_native' => $contentLanguageNativeTitle,
'sef' => $this->getSefString($this->tag),
'image' => strtolower(str_replace('-',
'_', $this->tag)),
'published' => 0,
'ordering' => 0,
'access' => (int)
\JFactory::getConfig()->get('access', 1),
'description' => '',
'metakey' => '',
'metadesc' => '',
'sitename' => '',
);
$tableLanguage = Table::getInstance('language');
if (!$tableLanguage->bind($languageData) ||
!$tableLanguage->check() || !$tableLanguage->store() ||
!$tableLanguage->reorder())
{
\JLog::add(
\JText::sprintf('JLIB_INSTALLER_WARNING_UNABLE_TO_INSTALL_CONTENT_LANGUAGE',
$siteLanguageManifest['name'], $tableLanguage->getError()),
\JLog::NOTICE,
'jerror'
);
}
}
// Clobber any possible pending updates
$update = Table::getInstance('update');
$uid = $update->find(array('element' =>
$this->get('tag'), 'type' =>
'language', 'folder' => ''));
if ($uid)
{
$update->delete($uid);
}
// Clean installed languages cache.
\JFactory::getCache()->clean('com_languages');
return $row->get('extension_id');
}
/**
* Gets a unique language SEF string.
*
* This function checks other existing language with the same code, if
they exist provides a unique SEF name.
* For instance: en-GB, en-US and en-AU will share the same SEF code by
default: www.mywebsite.com/en/
* To avoid this conflict, this function creates an specific SEF in case
of existing conflict:
* For example: www.mywebsite.com/en-au/
*
* @param string $itemLanguageTag Language Tag.
*
* @return string
*
* @since 3.7.0
*/
protected function getSefString($itemLanguageTag)
{
$langs = explode('-', $itemLanguageTag);
$prefixToFind = $langs[0];
$numberPrefixesFound = 0;
// Get the sef value of all current content languages.
$db = \JFactory::getDbo();
$query = $db->getQuery(true)
->select($db->qn('sef'))
->from($db->qn('#__languages'));
$db->setQuery($query);
$siteLanguages = $db->loadObjectList();
foreach ($siteLanguages as $siteLang)
{
if ($siteLang->sef === $prefixToFind)
{
$numberPrefixesFound++;
}
}
return $numberPrefixesFound === 0 ? $prefixToFind :
strtolower($itemLanguageTag);
}
/**
* Custom update method
*
* @return boolean True on success, false on failure
*
* @since 3.1
*/
public function update()
{
$xml = $this->parent->getManifest();
$this->setManifest($xml);
$cname = $xml->attributes()->client;
// Attempt to map the client to a base path
$client = ApplicationHelper::getClientInfo($cname, true);
if ($client === null || (empty($cname) && $cname !== 0))
{
$this->parent->abort(\JText::sprintf('JLIB_INSTALLER_ABORT',
\JText::sprintf('JLIB_INSTALLER_ERROR_UNKNOWN_CLIENT_TYPE',
$cname)));
return false;
}
$basePath = $client->path;
$clientId = $client->id;
// Get the language name
// Set the extensions name
$name = (string) $this->getManifest()->name;
$name = \JFilterInput::getInstance()->clean($name, 'cmd');
$this->set('name', $name);
// Get the Language tag [ISO tag, eg. en-GB]
$tag = (string) $xml->tag;
// Check if we found the tag - if we didn't, we may be trying to
install from an older language package
if (!$tag)
{
$this->parent->abort(\JText::sprintf('JLIB_INSTALLER_ABORT',
\JText::_('JLIB_INSTALLER_ERROR_NO_LANGUAGE_TAG')));
return false;
}
$this->set('tag', $tag);
// Set the language installation path
$this->parent->setPath('extension_site', $basePath .
'/language/' . $tag);
// Do we have a meta file in the file list? In other words... is this a
core language pack?
if (count($xml->files->children()))
{
foreach ($xml->files->children() as $file)
{
if ((string) $file->attributes()->file === 'meta')
{
$this->core = true;
break;
}
}
}
// Copy all the necessary files
if ($this->parent->parseFiles($xml->files) === false)
{
// Install failed, rollback changes
$this->parent->abort();
return false;
}
// Parse optional tags
$this->parent->parseMedia($xml->media);
/*
* Log that PDF Fonts in language packs are deprecated and will be
removed in 4.0
* Ref: https://github.com/joomla/joomla-cms/issues/31286
*/
if (is_dir($basePath . '/language/pdf_fonts'))
{
try
{
\JLog::add(
'Using the "pdf_fonts" folder to load language specific
fonts in languages is deprecated and will be removed in 4.0.',
\JLog::WARNING,
'deprecated'
);
}
catch (RuntimeException $exception)
{
// Informational log only
}
}
// Copy all the necessary font files to the common pdf_fonts directory
$this->parent->setPath('extension_site', $basePath .
'/language/pdf_fonts');
$overwrite = $this->parent->setOverwrite(true);
if ($this->parent->parseFiles($xml->fonts) === false)
{
// Install failed, rollback changes
$this->parent->abort();
return false;
}
$this->parent->setOverwrite($overwrite);
// Get the language description and set it as message
$this->parent->set('message', (string)
$xml->description);
/**
*
---------------------------------------------------------------------------------------------
* Finalization and Cleanup Section
*
---------------------------------------------------------------------------------------------
*/
// Clobber any possible pending updates
$update = Table::getInstance('update');
$uid = $update->find(array('element' =>
$this->get('tag'), 'type' =>
'language', 'client_id' => $clientId));
if ($uid)
{
$update->delete($uid);
}
// Update an entry to the extension table
$row = Table::getInstance('extension');
$eid = $row->find(array('element' =>
$this->get('tag'), 'type' =>
'language', 'client_id' => $clientId));
if ($eid)
{
$row->load($eid);
}
else
{
// Set the defaults
// There is no folder for language
$row->set('folder', '');
$row->set('enabled', 1);
$row->set('protected', 0);
$row->set('access', 0);
$row->set('client_id', $clientId);
$row->set('params', $this->parent->getParams());
}
$row->set('name', $this->get('name'));
$row->set('type', 'language');
$row->set('element', $this->get('tag'));
$row->set('manifest_cache',
$this->parent->generateManifestCache());
// Clean installed languages cache.
\JFactory::getCache()->clean('com_languages');
if (!$row->check() || !$row->store())
{
// Install failed, roll back changes
$this->parent->abort(\JText::sprintf('JLIB_INSTALLER_ABORT',
$row->getError()));
return false;
}
return $row->get('extension_id');
}
/**
* Custom uninstall method
*
* @param string $eid The tag of the language to uninstall
*
* @return boolean True on success
*
* @since 3.1
*/
public function uninstall($eid)
{
// Load up the extension details
$extension = Table::getInstance('extension');
$extension->load($eid);
// Grab a copy of the client details
$client =
ApplicationHelper::getClientInfo($extension->get('client_id'));
// Check the element isn't blank to prevent nuking the languages
directory...just in case
$element = $extension->get('element');
if (empty($element))
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_LANG_UNINSTALL_ELEMENT_EMPTY'),
\JLog::WARNING, 'jerror');
return false;
}
// Check that the language is not protected, Normally en-GB.
$protected = $extension->get('protected');
if ($protected == 1)
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_LANG_UNINSTALL_PROTECTED'),
\JLog::WARNING, 'jerror');
return false;
}
// Verify that it's not the default language for that client
$params = ComponentHelper::getParams('com_languages');
if ($params->get($client->name) === $element)
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_LANG_UNINSTALL_DEFAULT'),
\JLog::WARNING, 'jerror');
return false;
}
/*
* Does this extension have a parent package?
* If so, check if the package disallows individual extensions being
uninstalled if the package is not being uninstalled
*/
if ($extension->package_id &&
!$this->parent->isPackageUninstall() &&
!$this->canUninstallPackageChild($extension->package_id))
{
\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_CANNOT_UNINSTALL_CHILD_OF_PACKAGE',
$extension->name), \JLog::WARNING, 'jerror');
return false;
}
// Construct the path from the client, the language and the extension
element name
$path = $client->path . '/language/' . $element;
// Get the package manifest object and remove media
$this->parent->setPath('source', $path);
// We do findManifest to avoid problem when uninstalling a list of
extension: getManifest cache its manifest file
$this->parent->findManifest();
$this->setManifest($this->parent->getManifest());
$this->parent->removeFiles($this->getManifest()->media);
// Check it exists
if (!\JFolder::exists($path))
{
// If the folder doesn't exist lets just nuke the row as well and
presume the user killed it for us
$extension->delete();
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_LANG_UNINSTALL_PATH_EMPTY'),
\JLog::WARNING, 'jerror');
return false;
}
if (!\JFolder::delete($path))
{
// If deleting failed we'll leave the extension entry in tact just
in case
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_LANG_UNINSTALL_DIRECTORY'),
\JLog::WARNING, 'jerror');
return false;
}
// Remove the extension table entry
$extension->delete();
// Setting the language of users which have this language as the default
language
$db = \JFactory::getDbo();
$query = $db->getQuery(true)
->from('#__users')
->select('*');
$db->setQuery($query);
$users = $db->loadObjectList();
if ($client->name === 'administrator')
{
$param_name = 'admin_language';
}
else
{
$param_name = 'language';
}
$count = 0;
foreach ($users as $user)
{
$registry = new Registry($user->params);
if ($registry->get($param_name) === $element)
{
$registry->set($param_name, '');
$query->clear()
->update('#__users')
->set('params=' . $db->quote($registry))
->where('id=' . (int) $user->id);
$db->setQuery($query);
$db->execute();
$count++;
}
}
// Clean installed languages cache.
\JFactory::getCache()->clean('com_languages');
if (!empty($count))
{
\JLog::add(\JText::plural('JLIB_INSTALLER_NOTICE_LANG_RESET_USERS',
$count), \JLog::NOTICE, 'jerror');
}
// All done!
return true;
}
/**
* Custom discover method
* Finds language files
*
* @return boolean True on success
*
* @since 3.1
*/
public function discover()
{
$results = array();
$site_languages = \JFolder::folders(JPATH_SITE . '/language');
$admin_languages = \JFolder::folders(JPATH_ADMINISTRATOR .
'/language');
foreach ($site_languages as $language)
{
if (file_exists(JPATH_SITE . '/language/' . $language .
'/' . $language . '.xml'))
{
$manifest_details = Installer::parseXMLInstallFile(JPATH_SITE .
'/language/' . $language . '/' . $language .
'.xml');
$extension = Table::getInstance('extension');
$extension->set('type', 'language');
$extension->set('client_id', 0);
$extension->set('element', $language);
$extension->set('folder', '');
$extension->set('name', $language);
$extension->set('state', -1);
$extension->set('manifest_cache',
json_encode($manifest_details));
$extension->set('params', '{}');
$results[] = $extension;
}
}
foreach ($admin_languages as $language)
{
if (file_exists(JPATH_ADMINISTRATOR . '/language/' . $language
. '/' . $language . '.xml'))
{
$manifest_details = Installer::parseXMLInstallFile(JPATH_ADMINISTRATOR
. '/language/' . $language . '/' . $language .
'.xml');
$extension = Table::getInstance('extension');
$extension->set('type', 'language');
$extension->set('client_id', 1);
$extension->set('element', $language);
$extension->set('folder', '');
$extension->set('name', $language);
$extension->set('state', -1);
$extension->set('manifest_cache',
json_encode($manifest_details));
$extension->set('params', '{}');
$results[] = $extension;
}
}
return $results;
}
/**
* Custom discover install method
* Basically updates the manifest cache and leaves everything alone
*
* @return integer The extension id
*
* @since 3.1
*/
public function discover_install()
{
// Need to find to find where the XML file is since we don't store
this normally
$client =
ApplicationHelper::getClientInfo($this->parent->extension->client_id);
$short_element = $this->parent->extension->element;
$manifestPath = $client->path . '/language/' .
$short_element . '/' . $short_element . '.xml';
$this->parent->manifest =
$this->parent->isManifest($manifestPath);
$this->parent->setPath('manifest', $manifestPath);
$this->parent->setPath('source', $client->path .
'/language/' . $short_element);
$this->parent->setPath('extension_root',
$this->parent->getPath('source'));
$manifest_details =
Installer::parseXMLInstallFile($this->parent->getPath('manifest'));
$this->parent->extension->manifest_cache =
json_encode($manifest_details);
$this->parent->extension->state = 0;
$this->parent->extension->name =
$manifest_details['name'];
$this->parent->extension->enabled = 1;
// @todo remove code: $this->parent->extension->params =
$this->parent->getParams();
try
{
$this->parent->extension->check();
$this->parent->extension->store();
}
catch (\RuntimeException $e)
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_LANG_DISCOVER_STORE_DETAILS'),
\JLog::WARNING, 'jerror');
return false;
}
// Clean installed languages cache.
\JFactory::getCache()->clean('com_languages');
return $this->parent->extension->get('extension_id');
}
/**
* Refreshes the extension table cache
*
* @return boolean result of operation, true if updated, false on failure
*
* @since 3.1
*/
public function refreshManifestCache()
{
$client =
ApplicationHelper::getClientInfo($this->parent->extension->client_id);
$manifestPath = $client->path . '/language/' .
$this->parent->extension->element . '/' .
$this->parent->extension->element . '.xml';
$this->parent->manifest =
$this->parent->isManifest($manifestPath);
$this->parent->setPath('manifest', $manifestPath);
$manifest_details =
Installer::parseXMLInstallFile($this->parent->getPath('manifest'));
$this->parent->extension->manifest_cache =
json_encode($manifest_details);
$this->parent->extension->name =
$manifest_details['name'];
if ($this->parent->extension->store())
{
return true;
}
else
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_MOD_REFRESH_MANIFEST_CACHE'),
\JLog::WARNING, 'jerror');
return false;
}
}
}
PKK�[�v4�+5+5Adapter/LibraryAdapter.phpnu�[���<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Installer\Adapter;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Installer\Installer;
use Joomla\CMS\Installer\InstallerAdapter;
use Joomla\CMS\Installer\Manifest\LibraryManifest;
use Joomla\CMS\Table\Table;
use Joomla\CMS\Table\Update;
\JLoader::import('joomla.filesystem.folder');
/**
* Library installer
*
* @since 3.1
*/
class LibraryAdapter extends InstallerAdapter
{
/**
* Method to check if the extension is present in the filesystem, flags
the route as update if so
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function checkExtensionInFilesystem()
{
if ($this->currentExtensionId)
{
// Already installed, can we upgrade?
if ($this->parent->isOverwrite() ||
$this->parent->isUpgrade())
{
// We can upgrade, so uninstall the old one
$installer = new Installer; // we don't want to compromise this
instance!
$installer->setPackageUninstall(true);
$installer->uninstall('library',
$this->currentExtensionId);
// Clear the cached data
$this->currentExtensionId = null;
$this->extension = Table::getInstance('Extension',
'JTable', array('dbo' => $this->db));
// From this point we'll consider this an update
$this->setRoute('update');
}
else
{
// Abort the install, no upgrade possible
throw new
\RuntimeException(\JText::_('JLIB_INSTALLER_ABORT_LIB_INSTALL_ALREADY_INSTALLED'));
}
}
}
/**
* Method to copy the extension's base files from the `<files>`
tag(s) and the manifest file
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function copyBaseFiles()
{
if ($this->parent->parseFiles($this->getManifest()->files,
-1) === false)
{
throw new
\RuntimeException(\JText::_('JLIB_INSTALLER_ABORT_LIB_COPY_FILES'));
}
}
/**
* Method to finalise the installation processing
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function finaliseInstall()
{
// Clobber any possible pending updates
/** @var Update $update */
$update = Table::getInstance('update');
$uid = $update->find(
array(
'element' => $this->element,
'type' => $this->type,
)
);
if ($uid)
{
$update->delete($uid);
}
// Lastly, we will copy the manifest file to its appropriate place.
if ($this->route !== 'discover_install')
{
$manifest = array();
$manifest['src'] =
$this->parent->getPath('manifest');
$manifest['dest'] = JPATH_MANIFESTS . '/libraries/'
. $this->element . '.xml';
$destFolder = dirname($manifest['dest']);
if (!is_dir($destFolder) && !@mkdir($destFolder))
{
// Install failed, rollback changes
throw new
\RuntimeException(\JText::_('JLIB_INSTALLER_ABORT_LIB_INSTALL_COPY_SETUP'));
}
if (!$this->parent->copyFiles(array($manifest), true))
{
// Install failed, rollback changes
throw new
\RuntimeException(\JText::_('JLIB_INSTALLER_ABORT_LIB_INSTALL_COPY_SETUP'));
}
// If there is a manifest script, let's copy it.
if ($this->manifest_script)
{
$path['src'] =
$this->parent->getPath('source') . '/' .
$this->manifest_script;
$path['dest'] =
$this->parent->getPath('extension_root') . '/' .
$this->manifest_script;
if ($this->parent->isOverwrite() ||
!file_exists($path['dest']))
{
if (!$this->parent->copyFiles(array($path)))
{
// Install failed, rollback changes
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_MANIFEST',
\JText::_('JLIB_INSTALLER_' .
strtoupper($this->route))
)
);
}
}
}
}
}
/**
* Get the filtered extension element from the manifest
*
* @param string $element Optional element name to be converted
*
* @return string The filtered element
*
* @since 3.4
*/
public function getElement($element = null)
{
if (!$element)
{
$element = (string) $this->getManifest()->libraryname;
}
return $element;
}
/**
* Custom loadLanguage method
*
* @param string $path The path where to find language files.
*
* @return void
*
* @since 3.1
*/
public function loadLanguage($path = null)
{
$source = $this->parent->getPath('source');
if (!$source)
{
$this->parent->setPath('source', JPATH_PLATFORM .
'/' . $this->getElement());
}
$extension = 'lib_' . str_replace('/', '_',
$this->getElement());
$librarypath = (string) $this->getManifest()->libraryname;
$source = $path ?: JPATH_PLATFORM . '/' . $librarypath;
$this->doLoadLanguage($extension, $source, JPATH_SITE);
}
/**
* Method to parse optional tags in the manifest
*
* @return void
*
* @since 3.4
*/
protected function parseOptionalTags()
{
$this->parent->parseLanguages($this->getManifest()->languages);
$this->parent->parseMedia($this->getManifest()->media);
}
/**
* Prepares the adapter for a discover_install task
*
* @return void
*
* @since 3.4
*/
public function prepareDiscoverInstall()
{
$manifestPath = JPATH_MANIFESTS . '/libraries/' .
$this->extension->element . '.xml';
$this->parent->manifest =
$this->parent->isManifest($manifestPath);
$this->parent->setPath('manifest', $manifestPath);
$this->setManifest($this->parent->getManifest());
}
/**
* Method to do any prechecks and setup the install paths for the
extension
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function setupInstallPaths()
{
$group = (string) $this->getManifest()->libraryname;
if (!$group)
{
throw new
\RuntimeException(\JText::_('JLIB_INSTALLER_ABORT_LIB_INSTALL_NOFILE'));
}
// Don't install libraries which would override core folders
$restrictedFolders = array('cms', 'fof',
'idna_convert', 'joomla', 'legacy',
'php-encryption', 'phpass', 'phputf8',
'src', 'vendor');
if (in_array($group, $restrictedFolders))
{
throw new
\RuntimeException(\JText::_('JLIB_INSTALLER_ABORT_LIB_INSTALL_CORE_FOLDER'));
}
$this->parent->setPath('extension_root', JPATH_PLATFORM .
'/' . implode(DIRECTORY_SEPARATOR, explode('/',
$group)));
}
/**
* Method to store the extension to the database
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function storeExtension()
{
// Discover installs are stored a little differently
if ($this->route === 'discover_install')
{
$manifest_details =
Installer::parseXMLInstallFile($this->parent->getPath('manifest'));
$this->extension->manifest_cache = json_encode($manifest_details);
$this->extension->state = 0;
$this->extension->name = $manifest_details['name'];
$this->extension->enabled = 1;
$this->extension->params = $this->parent->getParams();
if (!$this->extension->store())
{
// Install failed, roll back changes
throw new
\RuntimeException(\JText::_('JLIB_INSTALLER_ERROR_LIB_DISCOVER_STORE_DETAILS'));
}
return;
}
$this->extension->name = $this->name;
$this->extension->type = 'library';
$this->extension->element = $this->element;
// There is no folder for libraries
$this->extension->folder = '';
$this->extension->enabled = 1;
$this->extension->protected = 0;
$this->extension->access = 1;
$this->extension->client_id = 0;
$this->extension->params = $this->parent->getParams();
// Custom data
$this->extension->custom_data = '';
$this->extension->system_data = '';
// Update the manifest cache for the entry
$this->extension->manifest_cache =
$this->parent->generateManifestCache();
if (!$this->extension->store())
{
// Install failed, roll back changes
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_LIB_INSTALL_ROLLBACK',
$this->extension->getError()
)
);
}
// Since we have created a library item, we add it to the installation
step stack
// so that if we have to rollback the changes we can undo it.
$this->parent->pushStep(array('type' =>
'extension', 'id' =>
$this->extension->extension_id));
}
/**
* Custom uninstall method
*
* @param string $id The id of the library to uninstall.
*
* @return boolean True on success
*
* @since 3.1
*/
public function uninstall($id)
{
$retval = true;
// First order of business will be to load the module object table from
the database.
// This should give us the necessary information to proceed.
$row = Table::getInstance('extension');
if (!$row->load((int) $id) || $row->element === '')
{
\JLog::add(\JText::_('ERRORUNKOWNEXTENSION'), \JLog::WARNING,
'jerror');
return false;
}
// Is the library we are trying to uninstall a core one?
// Because that is not a good idea...
if ($row->protected)
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_LIB_UNINSTALL_WARNCORELIBRARY'),
\JLog::WARNING, 'jerror');
return false;
}
/*
* Does this extension have a parent package?
* If so, check if the package disallows individual extensions being
uninstalled if the package is not being uninstalled
*/
if ($row->package_id &&
!$this->parent->isPackageUninstall() &&
!$this->canUninstallPackageChild($row->package_id))
{
\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_CANNOT_UNINSTALL_CHILD_OF_PACKAGE',
$row->name), \JLog::WARNING, 'jerror');
return false;
}
$manifestFile = JPATH_MANIFESTS . '/libraries/' .
$row->element . '.xml';
// Because libraries may not have their own folders we cannot use the
standard method of finding an installation manifest
if (file_exists($manifestFile))
{
$manifest = new LibraryManifest($manifestFile);
// Set the library root path
$this->parent->setPath('extension_root', JPATH_PLATFORM
. '/' . $manifest->libraryname);
$xml = simplexml_load_file($manifestFile);
// If we cannot load the XML file return null
if (!$xml)
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_LIB_UNINSTALL_LOAD_MANIFEST'),
\JLog::WARNING, 'jerror');
return false;
}
// Check for a valid XML root tag.
if ($xml->getName() !== 'extension')
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_LIB_UNINSTALL_INVALID_MANIFEST'),
\JLog::WARNING, 'jerror');
return false;
}
$this->parent->removeFiles($xml->files, -1);
\JFile::delete($manifestFile);
}
else
{
// Remove this row entry since its invalid
$row->delete($row->extension_id);
unset($row);
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_LIB_UNINSTALL_INVALID_NOTFOUND_MANIFEST'),
\JLog::WARNING, 'jerror');
return false;
}
// TODO: Change this so it walked up the path backwards so we clobber
multiple empties
// If the folder is empty, let's delete it
if
(\JFolder::exists($this->parent->getPath('extension_root')))
{
if (is_dir($this->parent->getPath('extension_root')))
{
$files =
\JFolder::files($this->parent->getPath('extension_root'));
if (!count($files))
{
\JFolder::delete($this->parent->getPath('extension_root'));
}
}
}
$this->parent->removeFiles($xml->media);
$this->parent->removeFiles($xml->languages);
$elementParts = explode('/', $row->element);
// Delete empty vendor folders
if (2 === count($elementParts))
{
@rmdir(JPATH_MANIFESTS . '/libraries/' . $elementParts[0]);
@rmdir(JPATH_PLATFORM . '/' . $elementParts[0]);
}
$row->delete($row->extension_id);
unset($row);
return $retval;
}
/**
* Custom discover method
*
* @return array Extension list of extensions available
*
* @since 3.1
*/
public function discover()
{
$results = array();
$mainFolder = JPATH_MANIFESTS . '/libraries';
$folder = new \RecursiveDirectoryIterator($mainFolder);
$iterator = new \RegexIterator(
new \RecursiveIteratorIterator($folder),
'/\.xml$/i',
\RecursiveRegexIterator::GET_MATCH
);
foreach ($iterator as $file => $pattern)
{
$element = str_replace(array($mainFolder . DIRECTORY_SEPARATOR,
'.xml'), '', $file);
$manifestCache = Installer::parseXMLInstallFile($file);
$extension = Table::getInstance('extension');
$extension->set('type', 'library');
$extension->set('client_id', 0);
$extension->set('element', $element);
$extension->set('folder', '');
$extension->set('name', $element);
$extension->set('state', -1);
$extension->set('manifest_cache',
json_encode($manifestCache));
$extension->set('params', '{}');
$results[] = $extension;
}
return $results;
}
/**
* Refreshes the extension table cache
*
* @return boolean Result of operation, true if updated, false on
failure
*
* @since 3.1
*/
public function refreshManifestCache()
{
// Need to find to find where the XML file is since we don't store
this normally
$manifestPath = JPATH_MANIFESTS . '/libraries/' .
$this->parent->extension->element . '.xml';
$this->parent->manifest =
$this->parent->isManifest($manifestPath);
$this->parent->setPath('manifest', $manifestPath);
$manifest_details =
Installer::parseXMLInstallFile($this->parent->getPath('manifest'));
$this->parent->extension->manifest_cache =
json_encode($manifest_details);
$this->parent->extension->name =
$manifest_details['name'];
try
{
return $this->parent->extension->store();
}
catch (\RuntimeException $e)
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_LIB_REFRESH_MANIFEST_CACHE'),
\JLog::WARNING, 'jerror');
return false;
}
}
}
PKK�[Mm��QQAdapter/ModuleAdapter.phpnu�[���<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Installer\Adapter;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Installer\Installer;
use Joomla\CMS\Installer\InstallerAdapter;
use Joomla\CMS\Table\Table;
use Joomla\Utilities\ArrayHelper;
\JLoader::import('joomla.filesystem.folder');
/**
* Module installer
*
* @since 3.1
*/
class ModuleAdapter extends InstallerAdapter
{
/**
* The install client ID
*
* @var integer
* @since 3.4
*/
protected $clientId;
/**
* `<scriptfile>` element of the extension manifest
*
* @var object
* @since 3.1
*/
protected $scriptElement = null;
/**
* Method to check if the extension is already present in the database
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function checkExistingExtension()
{
try
{
$this->currentExtensionId = $this->extension->find(
array(
'element' => $this->element,
'type' => $this->type,
'client_id' => $this->clientId,
)
);
}
catch (\RuntimeException $e)
{
// Install failed, roll back changes
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_ROLLBACK',
\JText::_('JLIB_INSTALLER_' . $this->route),
$e->getMessage()
),
$e->getCode(),
$e
);
}
}
/**
* Method to copy the extension's base files from the `<files>`
tag(s) and the manifest file
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function copyBaseFiles()
{
// Copy all necessary files
if ($this->parent->parseFiles($this->getManifest()->files,
-1) === false)
{
throw new
\RuntimeException(\JText::_('JLIB_INSTALLER_ABORT_MOD_COPY_FILES'));
}
// If there is a manifest script, let's copy it.
if ($this->manifest_script)
{
$path['src'] =
$this->parent->getPath('source') . '/' .
$this->manifest_script;
$path['dest'] =
$this->parent->getPath('extension_root') . '/' .
$this->manifest_script;
if ($this->parent->isOverwrite() ||
!file_exists($path['dest']))
{
if (!$this->parent->copyFiles(array($path)))
{
// Install failed, rollback changes
throw new
\RuntimeException(\JText::_('JLIB_INSTALLER_ABORT_MOD_INSTALL_MANIFEST'));
}
}
}
}
/**
* Method to finalise the installation processing
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function finaliseInstall()
{
// Clobber any possible pending updates
$update = Table::getInstance('update');
$uid = $update->find(
array(
'element' => $this->element,
'type' => 'module',
'client_id' => $this->clientId,
)
);
if ($uid)
{
$update->delete($uid);
}
// Lastly, we will copy the manifest file to its appropriate place.
if ($this->route !== 'discover_install')
{
if (!$this->parent->copyManifest(-1))
{
// Install failed, rollback changes
throw new
\RuntimeException(\JText::_('JLIB_INSTALLER_ABORT_MOD_INSTALL_COPY_SETUP'));
}
}
}
/**
* Get the filtered extension element from the manifest
*
* @param string $element Optional element name to be converted
*
* @return string The filtered element
*
* @since 3.4
*/
public function getElement($element = null)
{
if (!$element)
{
if (count($this->getManifest()->files->children()))
{
foreach ($this->getManifest()->files->children() as $file)
{
if ((string) $file->attributes()->module)
{
$element = strtolower((string) $file->attributes()->module);
break;
}
}
}
}
return $element;
}
/**
* Custom loadLanguage method
*
* @param string $path The path where we find language files
*
* @return void
*
* @since 3.4
*/
public function loadLanguage($path = null)
{
$source = $this->parent->getPath('source');
$client = $this->parent->extension->client_id ?
JPATH_ADMINISTRATOR : JPATH_SITE;
if (!$source)
{
$this->parent->setPath('source', $client .
'/modules/' . $this->parent->extension->element);
}
$this->setManifest($this->parent->getManifest());
if ($this->getManifest()->files)
{
$extension = $this->getElement();
if ($extension)
{
$source = $path ?: ($this->parent->extension->client_id ?
JPATH_ADMINISTRATOR : JPATH_SITE) . '/modules/' . $extension;
$folder = (string)
$this->getManifest()->files->attributes()->folder;
if ($folder && file_exists($path . '/' . $folder))
{
$source = $path . '/' . $folder;
}
$client = (string) $this->getManifest()->attributes()->client;
$this->doLoadLanguage($extension, $source,
constant('JPATH_' . strtoupper($client)));
}
}
}
/**
* Method to parse optional tags in the manifest
*
* @return void
*
* @since 3.4
*/
protected function parseOptionalTags()
{
// Parse optional tags
$this->parent->parseMedia($this->getManifest()->media,
$this->clientId);
$this->parent->parseLanguages($this->getManifest()->languages,
$this->clientId);
}
/**
* Prepares the adapter for a discover_install task
*
* @return void
*
* @since 3.4
*/
public function prepareDiscoverInstall()
{
$client =
ApplicationHelper::getClientInfo($this->parent->extension->client_id);
$manifestPath = $client->path . '/modules/' .
$this->parent->extension->element . '/' .
$this->parent->extension->element . '.xml';
$this->parent->manifest =
$this->parent->isManifest($manifestPath);
$this->parent->setPath('manifest', $manifestPath);
$this->setManifest($this->parent->getManifest());
}
/**
* Method to do any prechecks and setup the install paths for the
extension
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function setupInstallPaths()
{
// Get the target application
$cname = (string) $this->getManifest()->attributes()->client;
if ($cname)
{
// Attempt to map the client to a base path
$client = ApplicationHelper::getClientInfo($cname, true);
if ($client === false)
{
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_MOD_UNKNOWN_CLIENT',
\JText::_('JLIB_INSTALLER_' . $this->route),
$client->name
)
);
}
$basePath = $client->path;
$this->clientId = $client->id;
}
else
{
// No client attribute was found so we assume the site as the client
$basePath = JPATH_SITE;
$this->clientId = 0;
}
// Set the installation path
if (empty($this->element))
{
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_MOD_INSTALL_NOFILE',
\JText::_('JLIB_INSTALLER_' . $this->route)
)
);
}
$this->parent->setPath('extension_root', $basePath .
'/modules/' . $this->element);
}
/**
* Method to store the extension to the database
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function storeExtension()
{
// Discover installs are stored a little differently
if ($this->route === 'discover_install')
{
$manifest_details =
Installer::parseXMLInstallFile($this->parent->getPath('manifest'));
$this->extension->manifest_cache = json_encode($manifest_details);
$this->extension->state = 0;
$this->extension->name = $manifest_details['name'];
$this->extension->enabled = 1;
$this->extension->params = $this->parent->getParams();
if (!$this->extension->store())
{
// Install failed, roll back changes
throw new
\RuntimeException(\JText::_('JLIB_INSTALLER_ERROR_MOD_DISCOVER_STORE_DETAILS'));
}
return;
}
// Was there a module already installed with the same name?
if ($this->currentExtensionId)
{
if (!$this->parent->isOverwrite())
{
// Install failed, roll back changes
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_MOD_INSTALL_ALLREADY_EXISTS',
\JText::_('JLIB_INSTALLER_' . $this->route),
$this->name
)
);
}
// Load the entry and update the manifest_cache
$this->extension->load($this->currentExtensionId);
// Update name
$this->extension->name = $this->name;
// Update manifest
$this->extension->manifest_cache =
$this->parent->generateManifestCache();
if (!$this->extension->store())
{
// Install failed, roll back changes
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_MOD_ROLLBACK',
\JText::_('JLIB_INSTALLER_' . $this->route),
$this->extension->getError()
)
);
}
}
else
{
$this->extension->name = $this->name;
$this->extension->type = 'module';
$this->extension->element = $this->element;
// There is no folder for modules
$this->extension->folder = '';
$this->extension->enabled = 1;
$this->extension->protected = 0;
$this->extension->access = $this->clientId == 1 ? 2 : 0;
$this->extension->client_id = $this->clientId;
$this->extension->params = $this->parent->getParams();
// Custom data
$this->extension->custom_data = '';
$this->extension->system_data = '';
$this->extension->manifest_cache =
$this->parent->generateManifestCache();
if (!$this->extension->store())
{
// Install failed, roll back changes
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_MOD_ROLLBACK',
\JText::_('JLIB_INSTALLER_' . $this->route),
$this->extension->getError()
)
);
}
// Since we have created a module item, we add it to the installation
step stack
// so that if we have to rollback the changes we can undo it.
$this->parent->pushStep(
array(
'type' => 'extension',
'extension_id' => $this->extension->extension_id,
)
);
// Create unpublished module
$name = preg_replace('#[\*?]#', '',
\JText::_($this->name));
/** @var \JTableModule $module */
$module = Table::getInstance('module');
$module->title = $name;
$module->content = '';
$module->module = $this->element;
$module->access = '1';
$module->showtitle = '1';
$module->params = '';
$module->client_id = $this->clientId;
$module->language = '*';
$module->store();
}
}
/**
* Custom discover method
*
* @return array Extension list of extensions available
*
* @since 3.1
*/
public function discover()
{
$results = array();
$site_list = \JFolder::folders(JPATH_SITE . '/modules');
$admin_list = \JFolder::folders(JPATH_ADMINISTRATOR .
'/modules');
$site_info = ApplicationHelper::getClientInfo('site', true);
$admin_info = ApplicationHelper::getClientInfo('administrator',
true);
foreach ($site_list as $module)
{
if (file_exists(JPATH_SITE . "/modules/$module/$module.xml"))
{
$manifest_details = Installer::parseXMLInstallFile(JPATH_SITE .
"/modules/$module/$module.xml");
$extension = Table::getInstance('extension');
$extension->set('type', 'module');
$extension->set('client_id', $site_info->id);
$extension->set('element', $module);
$extension->set('folder', '');
$extension->set('name', $module);
$extension->set('state', -1);
$extension->set('manifest_cache',
json_encode($manifest_details));
$extension->set('params', '{}');
$results[] = clone $extension;
}
}
foreach ($admin_list as $module)
{
if (file_exists(JPATH_ADMINISTRATOR .
"/modules/$module/$module.xml"))
{
$manifest_details = Installer::parseXMLInstallFile(JPATH_ADMINISTRATOR
. "/modules/$module/$module.xml");
$extension = Table::getInstance('extension');
$extension->set('type', 'module');
$extension->set('client_id', $admin_info->id);
$extension->set('element', $module);
$extension->set('folder', '');
$extension->set('name', $module);
$extension->set('state', -1);
$extension->set('manifest_cache',
json_encode($manifest_details));
$extension->set('params', '{}');
$results[] = clone $extension;
}
}
return $results;
}
/**
* Refreshes the extension table cache
*
* @return boolean Result of operation, true if updated, false on
failure.
*
* @since 3.1
*/
public function refreshManifestCache()
{
$client =
ApplicationHelper::getClientInfo($this->parent->extension->client_id);
$manifestPath = $client->path . '/modules/' .
$this->parent->extension->element . '/' .
$this->parent->extension->element . '.xml';
$this->parent->manifest =
$this->parent->isManifest($manifestPath);
$this->parent->setPath('manifest', $manifestPath);
$manifest_details =
Installer::parseXMLInstallFile($this->parent->getPath('manifest'));
$this->parent->extension->manifest_cache =
json_encode($manifest_details);
$this->parent->extension->name =
$manifest_details['name'];
if ($this->parent->extension->store())
{
return true;
}
else
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_MOD_REFRESH_MANIFEST_CACHE'),
\JLog::WARNING, 'jerror');
return false;
}
}
/**
* Custom uninstall method
*
* @param integer $id The id of the module to uninstall
*
* @return boolean True on success
*
* @since 3.1
*/
public function uninstall($id)
{
$retval = true;
$db = $this->db;
// First order of business will be to load the module object table from
the database.
// This should give us the necessary information to proceed.
if (!$this->extension->load((int) $id) ||
$this->extension->element === '')
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_MOD_UNINSTALL_ERRORUNKOWNEXTENSION'),
\JLog::WARNING, 'jerror');
return false;
}
// Is the module we are trying to uninstall a core one?
// Because that is not a good idea...
if ($this->extension->protected)
{
\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_MOD_UNINSTALL_WARNCOREMODULE',
$this->extension->name), \JLog::WARNING, 'jerror');
return false;
}
/*
* Does this extension have a parent package?
* If so, check if the package disallows individual extensions being
uninstalled if the package is not being uninstalled
*/
if ($this->extension->package_id &&
!$this->parent->isPackageUninstall() &&
!$this->canUninstallPackageChild($this->extension->package_id))
{
\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_CANNOT_UNINSTALL_CHILD_OF_PACKAGE',
$this->extension->name), \JLog::WARNING, 'jerror');
return false;
}
// Get the extension root path
$element = $this->extension->element;
$client =
ApplicationHelper::getClientInfo($this->extension->client_id);
if ($client === false)
{
$this->parent->abort(
\JText::sprintf(
'JLIB_INSTALLER_ERROR_MOD_UNINSTALL_UNKNOWN_CLIENT',
$this->extension->client_id
)
);
return false;
}
$this->parent->setPath('extension_root', $client->path
. '/modules/' . $element);
$this->parent->setPath('source',
$this->parent->getPath('extension_root'));
// Get the module's manifest object
// We do findManifest to avoid problem when uninstalling a list of
extensions: getManifest cache its manifest file.
$this->parent->findManifest();
$this->setManifest($this->parent->getManifest());
// Attempt to load the language file; might have uninstall strings
$this->loadLanguage(($this->extension->client_id ?
JPATH_ADMINISTRATOR : JPATH_SITE) . '/modules/' . $element);
// If there is a manifest class file, let's load it
$this->scriptElement = $this->getManifest()->scriptfile;
$manifestScript = (string) $this->getManifest()->scriptfile;
if ($manifestScript)
{
$manifestScriptFile =
$this->parent->getPath('extension_root') . '/' .
$manifestScript;
// Set the class name
$classname = $element . 'InstallerScript';
\JLoader::register($classname, $manifestScriptFile);
if (class_exists($classname))
{
// Create a new instance
$this->parent->manifestClass = new $classname($this);
// And set this so we can copy it later
$this->set('manifest_script', $manifestScript);
}
}
try
{
$this->triggerManifestScript('uninstall');
}
catch (\RuntimeException $e)
{
// Ignore errors for now
}
if (!($this->getManifest() instanceof \SimpleXMLElement))
{
// Make sure we delete the folders
\JFolder::delete($this->parent->getPath('extension_root'));
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_MOD_UNINSTALL_INVALID_NOTFOUND_MANIFEST'),
\JLog::WARNING, 'jerror');
return false;
}
// Let's run the uninstall queries for the module
try
{
$this->parseQueries();
}
catch (\RuntimeException $e)
{
// Install failed, rollback changes
\JLog::add($e->getMessage(), \JLog::WARNING, 'jerror');
$retval = false;
}
// Remove the schema version
$query = $db->getQuery(true)
->delete('#__schemas')
->where('extension_id = ' .
$this->extension->extension_id);
$db->setQuery($query);
$db->execute();
// Remove other files
$this->parent->removeFiles($this->getManifest()->media);
$this->parent->removeFiles($this->getManifest()->languages,
$this->extension->client_id);
// Let's delete all the module copies for the type we are
uninstalling
$query->clear()
->select($db->quoteName('id'))
->from($db->quoteName('#__modules'))
->where($db->quoteName('module') . ' = ' .
$db->quote($this->extension->element))
->where($db->quoteName('client_id') . ' = ' .
(int) $this->extension->client_id);
$db->setQuery($query);
try
{
$modules = $db->loadColumn();
}
catch (\RuntimeException $e)
{
$modules = array();
}
// Do we have any module copies?
if (count($modules))
{
// Ensure the list is sane
$modules = ArrayHelper::toInteger($modules);
$modID = implode(',', $modules);
// Wipe out any items assigned to menus
$query = $db->getQuery(true)
->delete($db->quoteName('#__modules_menu'))
->where($db->quoteName('moduleid') . ' IN ('
. $modID . ')');
$db->setQuery($query);
try
{
$db->execute();
}
catch (\RuntimeException $e)
{
\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_MOD_UNINSTALL_EXCEPTION',
$db->stderr(true)), \JLog::WARNING, 'jerror');
$retval = false;
}
// Wipe out any instances in the modules table
/** @var \JTableModule $module */
$module = Table::getInstance('Module');
foreach ($modules as $modInstanceId)
{
$module->load($modInstanceId);
if (!$module->delete())
{
\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_MOD_UNINSTALL_EXCEPTION',
$module->getError()), \JLog::WARNING, 'jerror');
$retval = false;
}
}
}
// Now we will no longer need the module object, so let's delete it
and free up memory
$this->extension->delete($this->extension->extension_id);
$query = $db->getQuery(true)
->delete($db->quoteName('#__modules'))
->where($db->quoteName('module') . ' = ' .
$db->quote($this->extension->element))
->where($db->quoteName('client_id') . ' = ' .
(int) $this->extension->client_id);
$db->setQuery($query);
try
{
// Clean up any other ones that might exist as well
$db->execute();
}
catch (\RuntimeException $e)
{
// Ignore the error...
}
// Remove the installation folder
if
(!\JFolder::delete($this->parent->getPath('extension_root')))
{
// \JFolder should raise an error
$retval = false;
}
return $retval;
}
/**
* Custom rollback method
* - Roll back the menu item
*
* @param array $arg Installation step to rollback
*
* @return boolean True on success
*
* @since 3.1
*/
protected function _rollback_menu($arg)
{
// Get database connector object
$db = $this->parent->getDbo();
// Remove the entry from the #__modules_menu table
$query = $db->getQuery(true)
->delete($db->quoteName('#__modules_menu'))
->where($db->quoteName('moduleid') . ' = ' .
(int) $arg['id']);
$db->setQuery($query);
try
{
return $db->execute();
}
catch (\RuntimeException $e)
{
return false;
}
}
/**
* Custom rollback method
* - Roll back the module item
*
* @param array $arg Installation step to rollback
*
* @return boolean True on success
*
* @since 3.1
*/
protected function _rollback_module($arg)
{
// Get database connector object
$db = $this->parent->getDbo();
// Remove the entry from the #__modules table
$query = $db->getQuery(true)
->delete($db->quoteName('#__modules'))
->where($db->quoteName('id') . ' = ' . (int)
$arg['id']);
$db->setQuery($query);
try
{
return $db->execute();
}
catch (\RuntimeException $e)
{
return false;
}
}
}
PKK�[Ʒ�9NNAdapter/PackageAdapter.phpnu�[���<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Installer\Adapter;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Installer\Installer;
use Joomla\CMS\Installer\InstallerAdapter;
use Joomla\CMS\Installer\InstallerHelper;
use Joomla\CMS\Installer\Manifest\PackageManifest;
use Joomla\CMS\Table\Table;
use Joomla\CMS\Table\Update;
/**
* Package installer
*
* @since 3.1
*/
class PackageAdapter extends InstallerAdapter
{
/**
* Flag if the internal event callback has been registered
*
* @var boolean
* @since 3.7.0
*/
private static $eventRegistered = false;
/**
* An array of extension IDs for each installed extension
*
* @var array
* @since 3.7.0
*/
protected $installedIds = array();
/**
* The results of each installed extensions
*
* @var array
* @since 3.1
*/
protected $results = array();
/**
* Flag if the adapter supports discover installs
*
* Adapters should override this and set to false if discover install is
unsupported
*
* @var boolean
* @since 3.4
*/
protected $supportsDiscoverInstall = false;
/**
* Method to check if the extension is present in the filesystem, flags
the route as update if so
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function checkExtensionInFilesystem()
{
// If the package manifest already exists, then we will assume that the
package is already installed.
if (file_exists(JPATH_MANIFESTS . '/packages/' .
basename($this->parent->getPath('manifest'))))
{
// Look for an update function or update tag
$updateElement = $this->manifest->update;
// Upgrade manually set or update function available or update tag
detected
if ($updateElement || $this->parent->isUpgrade()
|| ($this->parent->manifestClass &&
method_exists($this->parent->manifestClass, 'update')))
{
// Force this one
$this->parent->setOverwrite(true);
$this->parent->setUpgrade(true);
if ($this->currentExtensionId)
{
// If there is a matching extension mark this as an update
$this->setRoute('update');
}
}
elseif (!$this->parent->isOverwrite())
{
// We didn't have overwrite set, find an update function or find
an update tag so lets call it safe
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_DIRECTORY',
\JText::_('JLIB_INSTALLER_' . $this->route),
$this->type,
$this->parent->getPath('extension_root')
)
);
}
}
}
/**
* Method to copy the extension's base files from the `<files>`
tag(s) and the manifest file
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function copyBaseFiles()
{
$folder = (string)
$this->getManifest()->files->attributes()->folder;
$source = $this->parent->getPath('source');
if ($folder)
{
$source .= '/' . $folder;
}
// Install all necessary files
if (!count($this->getManifest()->files->children()))
{
throw new \RuntimeException(
\JText::sprintf('JLIB_INSTALLER_ABORT_PACK_INSTALL_NO_FILES',
\JText::_('JLIB_INSTALLER_' . strtoupper($this->route))
)
);
}
// Add a callback for the `onExtensionAfterInstall` event so we can
receive the installed extension ID
if (!self::$eventRegistered)
{
self::$eventRegistered = true;
\JEventDispatcher::getInstance()->register('onExtensionAfterInstall',
array($this, 'onExtensionAfterInstall'));
}
foreach ($this->getManifest()->files->children() as $child)
{
$file = $source . '/' . (string) $child;
if (is_dir($file))
{
// If it's actually a directory then fill it up
$package = array();
$package['dir'] = $file;
$package['type'] = InstallerHelper::detectType($file);
}
else
{
// If it's an archive
$package = InstallerHelper::unpack($file);
}
$tmpInstaller = new Installer;
$installResult = $tmpInstaller->install($package['dir']);
if (!$installResult)
{
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_PACK_INSTALL_ERROR_EXTENSION',
\JText::_('JLIB_INSTALLER_' . strtoupper($this->route)),
basename($file)
)
);
}
$this->results[] = array(
'name' => (string) $tmpInstaller->manifest->name,
'result' => $installResult,
);
}
}
/**
* Method to create the extension root path if necessary
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function createExtensionRoot()
{
/*
* For packages, we only need the extension root if copying manifest
files; this step will be handled
* at that point if necessary
*/
}
/**
* Method to finalise the installation processing
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function finaliseInstall()
{
// Clobber any possible pending updates
/** @var Update $update */
$update = Table::getInstance('update');
$uid = $update->find(
array(
'element' => $this->element,
'type' => $this->type,
)
);
if ($uid)
{
$update->delete($uid);
}
// Set the package ID for each of the installed extensions to track the
relationship
if (!empty($this->installedIds))
{
$db = $this->db;
$query = $db->getQuery(true)
->update('#__extensions')
->set($db->quoteName('package_id') . ' = ' .
(int) $this->extension->extension_id)
->where($db->quoteName('extension_id') . ' IN
(' . implode(', ', $this->installedIds) . ')');
try
{
$db->setQuery($query)->execute();
}
catch (\JDatabaseExceptionExecuting $e)
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_PACK_SETTING_PACKAGE_ID'),
\JLog::WARNING, 'jerror');
}
}
// Lastly, we will copy the manifest file to its appropriate place.
$manifest = array();
$manifest['src'] =
$this->parent->getPath('manifest');
$manifest['dest'] = JPATH_MANIFESTS . '/packages/' .
basename($this->parent->getPath('manifest'));
if (!$this->parent->copyFiles(array($manifest), true))
{
// Install failed, rollback changes
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_PACK_INSTALL_COPY_SETUP',
\JText::_('JLIB_INSTALLER_ABORT_PACK_INSTALL_NO_FILES')
)
);
}
// If there is a manifest script, let's copy it.
if ($this->manifest_script)
{
// First, we have to create a folder for the script if one isn't
present
if
(!file_exists($this->parent->getPath('extension_root')))
{
if
(!\JFolder::create($this->parent->getPath('extension_root')))
{
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_CREATE_DIRECTORY',
\JText::_('JLIB_INSTALLER_' . $this->route),
$this->parent->getPath('extension_root')
)
);
}
/*
* Since we created the extension directory and will want to remove it
if
* we have to roll back the installation, let's add it to the
* installation step stack
*/
$this->parent->pushStep(
array(
'type' => 'folder',
'path' =>
$this->parent->getPath('extension_root'),
)
);
}
$path['src'] =
$this->parent->getPath('source') . '/' .
$this->manifest_script;
$path['dest'] =
$this->parent->getPath('extension_root') . '/' .
$this->manifest_script;
if ($this->parent->isOverwrite() ||
!file_exists($path['dest']))
{
if (!$this->parent->copyFiles(array($path)))
{
// Install failed, rollback changes
throw new
\RuntimeException(\JText::_('JLIB_INSTALLER_ABORT_PACKAGE_INSTALL_MANIFEST'));
}
}
}
}
/**
* Get the filtered extension element from the manifest
*
* @param string $element Optional element name to be converted
*
* @return string The filtered element
*
* @since 3.4
*/
public function getElement($element = null)
{
if (!$element)
{
// Ensure the element is a string
$element = (string) $this->getManifest()->packagename;
// Filter the name for illegal characters
$element = 'pkg_' .
\JFilterInput::getInstance()->clean($element, 'cmd');
}
return $element;
}
/**
* Load language from a path
*
* @param string $path The path of the language.
*
* @return void
*
* @since 3.1
*/
public function loadLanguage($path)
{
$this->doLoadLanguage($this->getElement(), $path);
}
/**
* Handler for the `onExtensionAfterInstall` event
*
* @param Installer $installer Installer instance managing the
extension's installation
* @param integer|boolean $eid The extension ID of the installed
extension on success, boolean false on install failure
*
* @return void
*
* @since 3.7.0
*/
public function onExtensionAfterInstall(Installer $installer, $eid)
{
if ($eid !== false)
{
$this->installedIds[] = $eid;
}
}
/**
* Method to parse optional tags in the manifest
*
* @return void
*
* @since 3.4
*/
protected function parseOptionalTags()
{
$this->parent->parseLanguages($this->getManifest()->languages);
}
/**
* Method to do any prechecks and setup the install paths for the
extension
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function setupInstallPaths()
{
$packagepath = (string) $this->getManifest()->packagename;
if (empty($packagepath))
{
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_PACK_INSTALL_NO_PACK',
\JText::_('JLIB_INSTALLER_' . strtoupper($this->route))
)
);
}
$this->parent->setPath('extension_root', JPATH_MANIFESTS
. '/packages/' . $packagepath);
}
/**
* Method to store the extension to the database
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function storeExtension()
{
if ($this->currentExtensionId)
{
if (!$this->parent->isOverwrite())
{
// Install failed, roll back changes
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_ALREADY_EXISTS',
\JText::_('JLIB_INSTALLER_' . $this->route),
$this->name
)
);
}
$this->extension->load($this->currentExtensionId);
$this->extension->name = $this->name;
}
else
{
$this->extension->name = $this->name;
$this->extension->type = 'package';
$this->extension->element = $this->element;
// There is no folder for packages
$this->extension->folder = '';
$this->extension->enabled = 1;
$this->extension->protected = 0;
$this->extension->access = 1;
$this->extension->client_id = 0;
// Custom data
$this->extension->custom_data = '';
$this->extension->system_data = '';
$this->extension->params = $this->parent->getParams();
}
// Update the manifest cache for the entry
$this->extension->manifest_cache =
$this->parent->generateManifestCache();
if (!$this->extension->store())
{
// Install failed, roll back changes
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_PACK_INSTALL_ROLLBACK',
$this->extension->getError()
)
);
}
// Since we have created a package item, we add it to the installation
step stack
// so that if we have to rollback the changes we can undo it.
$this->parent->pushStep(array('type' =>
'extension', 'id' =>
$this->extension->extension_id));
}
/**
* Executes a custom install script method
*
* @param string $method The install method to execute
*
* @return boolean True on success
*
* @since 3.4
*/
protected function triggerManifestScript($method)
{
ob_start();
ob_implicit_flush(false);
if ($this->parent->manifestClass &&
method_exists($this->parent->manifestClass, $method))
{
switch ($method)
{
// The preflight method takes the route as a param
case 'preflight':
if ($this->parent->manifestClass->$method($this->route,
$this) === false)
{
// The script failed, rollback changes
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_INSTALL_CUSTOM_INSTALL_FAILURE',
\JText::_('JLIB_INSTALLER_' . $this->route)
)
);
}
break;
// The postflight method takes the route and a results array as params
case 'postflight':
$this->parent->manifestClass->$method($this->route, $this,
$this->results);
break;
// The install, uninstall, and update methods only pass this object as
a param
case 'install':
case 'uninstall':
case 'update':
if ($this->parent->manifestClass->$method($this) === false)
{
if ($method !== 'uninstall')
{
// The script failed, rollback changes
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_INSTALL_CUSTOM_INSTALL_FAILURE',
\JText::_('JLIB_INSTALLER_' . $this->route)
)
);
}
}
break;
}
}
// Append to the message object
$this->extensionMessage .= ob_get_clean();
// If in postflight or uninstall, set the message for display
if (($method === 'uninstall' || $method ===
'postflight') && $this->extensionMessage !==
'')
{
$this->parent->set('extension_message',
$this->extensionMessage);
}
return true;
}
/**
* Custom uninstall method
*
* @param integer $id The id of the package to uninstall.
*
* @return boolean True on success
*
* @since 3.1
*/
public function uninstall($id)
{
$row = null;
$retval = true;
$row = Table::getInstance('extension');
$row->load($id);
if ($row->protected)
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_PACK_UNINSTALL_WARNCOREPACK'),
\JLog::WARNING, 'jerror');
return false;
}
/*
* Does this extension have a parent package?
* If so, check if the package disallows individual extensions being
uninstalled if the package is not being uninstalled
*/
if ($row->package_id &&
!$this->parent->isPackageUninstall() &&
!$this->canUninstallPackageChild($row->package_id))
{
\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_CANNOT_UNINSTALL_CHILD_OF_PACKAGE',
$row->name), \JLog::WARNING, 'jerror');
return false;
}
$manifestFile = JPATH_MANIFESTS . '/packages/' .
$row->get('element') . '.xml';
$manifest = new PackageManifest($manifestFile);
// Set the package root path
$this->parent->setPath('extension_root', JPATH_MANIFESTS
. '/packages/' . $manifest->packagename);
// Because packages may not have their own folders we cannot use the
standard method of finding an installation manifest
if (!file_exists($manifestFile))
{
// TODO: Fail?
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_PACK_UNINSTALL_MISSINGMANIFEST'),
\JLog::WARNING, 'jerror');
return false;
}
$xml = simplexml_load_file($manifestFile);
// If we cannot load the XML file return false
if (!$xml)
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_PACK_UNINSTALL_LOAD_MANIFEST'),
\JLog::WARNING, 'jerror');
return false;
}
// Check for a valid XML root tag.
if ($xml->getName() !== 'extension')
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_PACK_UNINSTALL_INVALID_MANIFEST'),
\JLog::WARNING, 'jerror');
return false;
}
// If there is a manifest class file, let's load it
$manifestScript = (string) $manifest->scriptfile;
if ($manifestScript)
{
$manifestScriptFile =
$this->parent->getPath('extension_root') . '/' .
$manifestScript;
// Set the class name
$classname = $row->element . 'InstallerScript';
\JLoader::register($classname, $manifestScriptFile);
if (class_exists($classname))
{
// Create a new instance
$this->parent->manifestClass = new $classname($this);
// And set this so we can copy it later
$this->set('manifest_script', $manifestScript);
}
}
ob_start();
ob_implicit_flush(false);
// Run uninstall if possible
if ($this->parent->manifestClass &&
method_exists($this->parent->manifestClass, 'uninstall'))
{
$this->parent->manifestClass->uninstall($this);
}
$msg = ob_get_contents();
ob_end_clean();
if ($msg != '')
{
$this->parent->set('extension_message', $msg);
}
$error = false;
foreach ($manifest->filelist as $extension)
{
$tmpInstaller = new Installer;
$tmpInstaller->setPackageUninstall(true);
$id = $this->_getExtensionId($extension->type, $extension->id,
$extension->client, $extension->group);
$client = ApplicationHelper::getClientInfo($extension->client, true);
if ($id)
{
if (!$tmpInstaller->uninstall($extension->type, $id,
$client->id))
{
$error = true;
\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_PACK_UNINSTALL_NOT_PROPER',
basename($extension->filename)), \JLog::WARNING, 'jerror');
}
}
else
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_PACK_UNINSTALL_UNKNOWN_EXTENSION'),
\JLog::WARNING, 'jerror');
}
}
// Remove any language files
$this->parent->removeFiles($xml->languages);
// Clean up manifest file after we're done if there were no errors
if (!$error)
{
\JFile::delete($manifestFile);
$folder = $this->parent->getPath('extension_root');
if (\JFolder::exists($folder))
{
\JFolder::delete($folder);
}
$row->delete();
}
else
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_PACK_UNINSTALL_MANIFEST_NOT_REMOVED'),
\JLog::WARNING, 'jerror');
}
// Return the result up the line
return $retval;
}
/**
* Gets the extension id.
*
* @param string $type The extension type.
* @param string $id The name of the extension (the element
field).
* @param integer $client The application id (0: Joomla CMS site; 1:
Joomla CMS administrator).
* @param string $group The extension group (mainly for plugins).
*
* @return integer
*
* @since 3.1
*/
protected function _getExtensionId($type, $id, $client, $group)
{
$db = $this->parent->getDbo();
$query = $db->getQuery(true)
->select('extension_id')
->from('#__extensions')
->where('type = ' . $db->quote($type))
->where('element = ' . $db->quote($id));
switch ($type)
{
case 'plugin':
// Plugins have a folder but not a client
$query->where('folder = ' . $db->quote($group));
break;
case 'library':
case 'package':
case 'component':
// Components, packages and libraries don't have a folder or
client.
// Included for completeness.
break;
case 'language':
case 'module':
case 'template':
// Languages, modules and templates have a client but not a folder
$client = ApplicationHelper::getClientInfo($client, true);
$query->where('client_id = ' . (int) $client->id);
break;
}
$db->setQuery($query);
// Note: For templates, libraries and packages their unique name is their
key.
// This means they come out the same way they came in.
return $db->loadResult();
}
/**
* Refreshes the extension table cache
*
* @return boolean Result of operation, true if updated, false on
failure
*
* @since 3.1
*/
public function refreshManifestCache()
{
// Need to find to find where the XML file is since we don't store
this normally
$manifestPath = JPATH_MANIFESTS . '/packages/' .
$this->parent->extension->element . '.xml';
$this->parent->manifest =
$this->parent->isManifest($manifestPath);
$this->parent->setPath('manifest', $manifestPath);
$manifest_details =
Installer::parseXMLInstallFile($this->parent->getPath('manifest'));
$this->parent->extension->manifest_cache =
json_encode($manifest_details);
$this->parent->extension->name =
$manifest_details['name'];
try
{
return $this->parent->extension->store();
}
catch (\RuntimeException $e)
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_PACK_REFRESH_MANIFEST_CACHE'),
\JLog::WARNING, 'jerror');
return false;
}
}
}
PKK�[�Ⱦ�,M,MAdapter/PluginAdapter.phpnu�[���<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Installer\Adapter;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Installer\Installer;
use Joomla\CMS\Installer\InstallerAdapter;
use Joomla\CMS\Table\Table;
use Joomla\CMS\Table\Update;
\JLoader::import('joomla.filesystem.folder');
/**
* Plugin installer
*
* @since 3.1
*/
class PluginAdapter extends InstallerAdapter
{
/**
* `<scriptfile>` element of the extension manifest
*
* @var object
* @since 3.1
*/
protected $scriptElement = null;
/**
* `<files>` element of the old extension manifest
*
* @var object
* @since 3.1
*/
protected $oldFiles = null;
/**
* Method to check if the extension is already present in the database
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function checkExistingExtension()
{
try
{
$this->currentExtensionId = $this->extension->find(
array('type' => $this->type, 'element' =>
$this->element, 'folder' => $this->group)
);
}
catch (\RuntimeException $e)
{
// Install failed, roll back changes
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_ROLLBACK',
\JText::_('JLIB_INSTALLER_' . $this->route),
$e->getMessage()
),
$e->getCode(),
$e
);
}
}
/**
* Method to copy the extension's base files from the `<files>`
tag(s) and the manifest file
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function copyBaseFiles()
{
// Copy all necessary files
if ($this->parent->parseFiles($this->getManifest()->files,
-1, $this->oldFiles) === false)
{
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_PLG_COPY_FILES',
\JText::_('JLIB_INSTALLER_' . $this->route)
)
);
}
// If there is a manifest script, let's copy it.
if ($this->manifest_script)
{
$path['src'] =
$this->parent->getPath('source') . '/' .
$this->manifest_script;
$path['dest'] =
$this->parent->getPath('extension_root') . '/' .
$this->manifest_script;
if ($this->parent->isOverwrite() ||
!file_exists($path['dest']))
{
if (!$this->parent->copyFiles(array($path)))
{
// Install failed, rollback changes
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_PLG_INSTALL_MANIFEST',
\JText::_('JLIB_INSTALLER_' . $this->route)
)
);
}
}
}
}
/**
* Method to create the extension root path if necessary
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function createExtensionRoot()
{
// Run the common create code first
parent::createExtensionRoot();
// If we're updating at this point when there is always going to be
an extension_root find the old XML files
if ($this->route === 'update')
{
// Create a new installer because findManifest sets stuff; side effects!
$tmpInstaller = new Installer;
// Look in the extension root
$tmpInstaller->setPath('source',
$this->parent->getPath('extension_root'));
if ($tmpInstaller->findManifest())
{
$old_manifest = $tmpInstaller->getManifest();
$this->oldFiles = $old_manifest->files;
}
}
}
/**
* Method to finalise the installation processing
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function finaliseInstall()
{
// Clobber any possible pending updates
/** @var Update $update */
$update = Table::getInstance('update');
$uid = $update->find(
array(
'element' => $this->element,
'type' => $this->type,
'folder' => $this->group,
)
);
if ($uid)
{
$update->delete($uid);
}
// Lastly, we will copy the manifest file to its appropriate place.
if ($this->route !== 'discover_install')
{
if (!$this->parent->copyManifest(-1))
{
// Install failed, rollback changes
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_PLG_INSTALL_COPY_SETUP',
\JText::_('JLIB_INSTALLER_' . $this->route)
)
);
}
}
}
/**
* Get the filtered extension element from the manifest
*
* @param string $element Optional element name to be converted
*
* @return string The filtered element
*
* @since 3.4
*/
public function getElement($element = null)
{
if (!$element)
{
// Backward Compatibility
// @todo Deprecate in future version
if (count($this->getManifest()->files->children()))
{
$type = (string) $this->getManifest()->attributes()->type;
foreach ($this->getManifest()->files->children() as $file)
{
if ((string) $file->attributes()->$type)
{
$element = (string) $file->attributes()->$type;
break;
}
}
}
}
return $element;
}
/**
* Get the class name for the install adapter script.
*
* @return string The class name.
*
* @since 3.4
*/
protected function getScriptClassName()
{
return 'Plg' . str_replace('-', '',
$this->group) . $this->element . 'InstallerScript';
}
/**
* Custom loadLanguage method
*
* @param string $path The path where to find language files.
*
* @return void
*
* @since 3.1
*/
public function loadLanguage($path = null)
{
$source = $this->parent->getPath('source');
if (!$source)
{
$this->parent->setPath(
'source',
JPATH_PLUGINS . '/' .
$this->parent->extension->folder . '/' .
$this->parent->extension->element
);
}
$element = $this->getManifest()->files;
if ($element)
{
$group = strtolower((string)
$this->getManifest()->attributes()->group);
$name = '';
if (count($element->children()))
{
foreach ($element->children() as $file)
{
if ((string) $file->attributes()->plugin)
{
$name = strtolower((string) $file->attributes()->plugin);
break;
}
}
}
if ($name)
{
$extension = "plg_${group}_${name}";
$source = $path ?: JPATH_PLUGINS . "/$group/$name";
$folder = (string) $element->attributes()->folder;
if ($folder && file_exists("$path/$folder"))
{
$source = "$path/$folder";
}
$this->doLoadLanguage($extension, $source, JPATH_ADMINISTRATOR);
}
}
}
/**
* Method to parse optional tags in the manifest
*
* @return void
*
* @since 3.4
*/
protected function parseOptionalTags()
{
// Parse optional tags -- media and language files for plugins go in
admin app
$this->parent->parseMedia($this->getManifest()->media, 1);
$this->parent->parseLanguages($this->getManifest()->languages,
1);
}
/**
* Prepares the adapter for a discover_install task
*
* @return void
*
* @since 3.4
*/
public function prepareDiscoverInstall()
{
$client =
ApplicationHelper::getClientInfo($this->extension->client_id);
$basePath = $client->path . '/plugins/' .
$this->extension->folder;
if (is_dir($basePath . '/' . $this->extension->element))
{
$manifestPath = $basePath . '/' .
$this->extension->element . '/' .
$this->extension->element . '.xml';
}
else
{
// @deprecated 4.0 - This path supports Joomla! 1.5 plugin folder
layouts
$manifestPath = $basePath . '/' .
$this->extension->element . '.xml';
}
$this->parent->manifest =
$this->parent->isManifest($manifestPath);
$this->parent->setPath('manifest', $manifestPath);
$this->setManifest($this->parent->getManifest());
}
/**
* Method to do any prechecks and setup the install paths for the
extension
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function setupInstallPaths()
{
$this->group = (string)
$this->getManifest()->attributes()->group;
if (empty($this->element) && empty($this->group))
{
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_PLG_INSTALL_NO_FILE',
\JText::_('JLIB_INSTALLER_' . $this->route)
)
);
}
$this->parent->setPath('extension_root', JPATH_PLUGINS .
'/' . $this->group . '/' . $this->element);
}
/**
* Method to store the extension to the database
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function storeExtension()
{
// Discover installs are stored a little differently
if ($this->route === 'discover_install')
{
$manifest_details =
Installer::parseXMLInstallFile($this->parent->getPath('manifest'));
$this->extension->manifest_cache = json_encode($manifest_details);
$this->extension->state = 0;
$this->extension->name = $manifest_details['name'];
$this->extension->enabled = 'editors' ===
$this->extension->folder ? 1 : 0;
$this->extension->params = $this->parent->getParams();
if (!$this->extension->store())
{
// Install failed, roll back changes
throw new
\RuntimeException(\JText::_('JLIB_INSTALLER_ERROR_PLG_DISCOVER_STORE_DETAILS'));
}
return;
}
// Was there a plugin with the same name already installed?
if ($this->currentExtensionId)
{
if (!$this->parent->isOverwrite())
{
// Install failed, roll back changes
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_PLG_INSTALL_ALLREADY_EXISTS',
\JText::_('JLIB_INSTALLER_' . $this->route),
$this->name
)
);
}
$this->extension->load($this->currentExtensionId);
$this->extension->name = $this->name;
$this->extension->manifest_cache =
$this->parent->generateManifestCache();
// Update the manifest cache and name
$this->extension->store();
}
else
{
// Store in the extensions table (1.6)
$this->extension->name = $this->name;
$this->extension->type = 'plugin';
$this->extension->ordering = 0;
$this->extension->element = $this->element;
$this->extension->folder = $this->group;
$this->extension->enabled = 0;
$this->extension->protected = 0;
$this->extension->access = 1;
$this->extension->client_id = 0;
$this->extension->params = $this->parent->getParams();
// Custom data
$this->extension->custom_data = '';
// System data
$this->extension->system_data = '';
$this->extension->manifest_cache =
$this->parent->generateManifestCache();
// Editor plugins are published by default
if ($this->group === 'editors')
{
$this->extension->enabled = 1;
}
if (!$this->extension->store())
{
// Install failed, roll back changes
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_PLG_INSTALL_ROLLBACK',
\JText::_('JLIB_INSTALLER_' . $this->route),
$this->extension->getError()
)
);
}
// Since we have created a plugin item, we add it to the installation
step stack
// so that if we have to rollback the changes we can undo it.
$this->parent->pushStep(array('type' =>
'extension', 'id' =>
$this->extension->extension_id));
}
}
/**
* Custom uninstall method
*
* @param integer $id The id of the plugin to uninstall
*
* @return boolean True on success
*
* @since 3.1
*/
public function uninstall($id)
{
$this->route = 'uninstall';
$row = null;
$retval = true;
$db = $this->parent->getDbo();
// First order of business will be to load the plugin object table from
the database.
// This should give us the necessary information to proceed.
$row = Table::getInstance('extension');
if (!$row->load((int) $id))
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_PLG_UNINSTALL_ERRORUNKOWNEXTENSION'),
\JLog::WARNING, 'jerror');
return false;
}
// Is the plugin we are trying to uninstall a core one?
// Because that is not a good idea...
if ($row->protected)
{
\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_PLG_UNINSTALL_WARNCOREPLUGIN',
$row->name), \JLog::WARNING, 'jerror');
return false;
}
/*
* Does this extension have a parent package?
* If so, check if the package disallows individual extensions being
uninstalled if the package is not being uninstalled
*/
if ($row->package_id &&
!$this->parent->isPackageUninstall() &&
!$this->canUninstallPackageChild($row->package_id))
{
\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_CANNOT_UNINSTALL_CHILD_OF_PACKAGE',
$row->name), \JLog::WARNING, 'jerror');
return false;
}
// Get the plugin folder so we can properly build the plugin path
if (trim($row->folder) === '')
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_PLG_UNINSTALL_FOLDER_FIELD_EMPTY'),
\JLog::WARNING, 'jerror');
return false;
}
// Set the plugin root path
$this->parent->setPath('extension_root', JPATH_PLUGINS .
'/' . $row->folder . '/' . $row->element);
$this->parent->setPath('source',
$this->parent->getPath('extension_root'));
$this->parent->findManifest();
$this->setManifest($this->parent->getManifest());
// Attempt to load the language file; might have uninstall strings
$this->parent->setPath('source', JPATH_PLUGINS .
'/' . $row->folder . '/' . $row->element);
$this->loadLanguage(JPATH_PLUGINS . '/' . $row->folder .
'/' . $row->element);
/**
*
---------------------------------------------------------------------------------------------
* Installer Trigger Loading
*
---------------------------------------------------------------------------------------------
*/
// If there is a manifest class file, let's load it; we'll copy
it later (don't have dest yet)
$manifestScript = (string) $this->getManifest()->scriptfile;
if ($manifestScript)
{
$manifestScriptFile = $this->parent->getPath('source') .
'/' . $manifestScript;
// If a dash is present in the folder, remove it
$folderClass = str_replace('-', '',
$row->folder);
// Set the class name
$classname = 'Plg' . $folderClass . $row->element .
'InstallerScript';
\JLoader::register($classname, $manifestScriptFile);
if (class_exists($classname))
{
// Create a new instance
$this->parent->manifestClass = new $classname($this);
// And set this so we can copy it later
$this->set('manifest_script', $manifestScript);
}
}
// Run preflight if possible (since we know we're not an update)
ob_start();
ob_implicit_flush(false);
if ($this->parent->manifestClass &&
method_exists($this->parent->manifestClass, 'preflight'))
{
if ($this->parent->manifestClass->preflight($this->route,
$this) === false)
{
// Preflight failed, rollback changes
$this->parent->abort(\JText::_('JLIB_INSTALLER_ABORT_PLG_INSTALL_CUSTOM_INSTALL_FAILURE'));
return false;
}
}
// Create the $msg object and append messages from preflight
$msg = ob_get_contents();
ob_end_clean();
// Let's run the queries for the plugin
$utfresult =
$this->parent->parseSQLFiles($this->getManifest()->uninstall->sql);
if ($utfresult === false)
{
// Install failed, rollback changes
$this->parent->abort(\JText::sprintf('JLIB_INSTALLER_ABORT_PLG_UNINSTALL_SQL_ERROR',
$db->stderr(true)));
return false;
}
// Run the custom uninstall method if possible
ob_start();
ob_implicit_flush(false);
if ($this->parent->manifestClass &&
method_exists($this->parent->manifestClass, 'uninstall'))
{
$this->parent->manifestClass->uninstall($this);
}
// Append messages
$msg .= ob_get_contents();
ob_end_clean();
// Remove the plugin files
$this->parent->removeFiles($this->getManifest()->files, -1);
// Remove all media and languages as well
$this->parent->removeFiles($this->getManifest()->media);
$this->parent->removeFiles($this->getManifest()->languages,
1);
// Remove the schema version
$query = $db->getQuery(true)
->delete('#__schemas')
->where('extension_id = ' . $row->extension_id);
$db->setQuery($query);
$db->execute();
// Now we will no longer need the plugin object, so let's delete it
$row->delete($row->extension_id);
unset($row);
// Remove the plugin's folder
\JFolder::delete($this->parent->getPath('extension_root'));
if ($msg != '')
{
$this->parent->set('extension_message', $msg);
}
return $retval;
}
/**
* Custom discover method
*
* @return array Extension) list of extensions available
*
* @since 3.1
*/
public function discover()
{
$results = array();
$folder_list = \JFolder::folders(JPATH_SITE . '/plugins');
foreach ($folder_list as $folder)
{
$file_list = \JFolder::files(JPATH_SITE . '/plugins/' .
$folder, '\.xml$');
foreach ($file_list as $file)
{
$manifest_details = Installer::parseXMLInstallFile(JPATH_SITE .
'/plugins/' . $folder . '/' . $file);
$file = \JFile::stripExt($file);
// Ignore example plugins
if ($file === 'example' || $manifest_details === false)
{
continue;
}
$element = empty($manifest_details['filename']) ? $file :
$manifest_details['filename'];
$extension = Table::getInstance('extension');
$extension->set('type', 'plugin');
$extension->set('client_id', 0);
$extension->set('element', $element);
$extension->set('folder', $folder);
$extension->set('name',
$manifest_details['name']);
$extension->set('state', -1);
$extension->set('manifest_cache',
json_encode($manifest_details));
$extension->set('params', '{}');
$results[] = $extension;
}
$folder_list = \JFolder::folders(JPATH_SITE . '/plugins/' .
$folder);
foreach ($folder_list as $plugin_folder)
{
$file_list = \JFolder::files(JPATH_SITE . '/plugins/' .
$folder . '/' . $plugin_folder, '\.xml$');
foreach ($file_list as $file)
{
$manifest_details = Installer::parseXMLInstallFile(
JPATH_SITE . '/plugins/' . $folder . '/' .
$plugin_folder . '/' . $file
);
$file = \JFile::stripExt($file);
if ($file === 'example' || $manifest_details === false)
{
continue;
}
$element = empty($manifest_details['filename']) ? $file :
$manifest_details['filename'];
// Ignore example plugins
$extension = Table::getInstance('extension');
$extension->set('type', 'plugin');
$extension->set('client_id', 0);
$extension->set('element', $element);
$extension->set('folder', $folder);
$extension->set('name',
$manifest_details['name']);
$extension->set('state', -1);
$extension->set('manifest_cache',
json_encode($manifest_details));
$extension->set('params', '{}');
$results[] = $extension;
}
}
}
return $results;
}
/**
* Refreshes the extension table cache.
*
* @return boolean Result of operation, true if updated, false on
failure.
*
* @since 3.1
*/
public function refreshManifestCache()
{
/*
* Plugins use the extensions table as their primary store
* Similar to modules and templates, rather easy
* If it's not in the extensions table we just add it
*/
$client =
ApplicationHelper::getClientInfo($this->parent->extension->client_id);
$manifestPath = $client->path . '/plugins/' .
$this->parent->extension->folder . '/' .
$this->parent->extension->element . '/'
. $this->parent->extension->element . '.xml';
$this->parent->manifest =
$this->parent->isManifest($manifestPath);
$this->parent->setPath('manifest', $manifestPath);
$manifest_details =
Installer::parseXMLInstallFile($this->parent->getPath('manifest'));
$this->parent->extension->manifest_cache =
json_encode($manifest_details);
$this->parent->extension->name =
$manifest_details['name'];
if ($this->parent->extension->store())
{
return true;
}
else
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_PLG_REFRESH_MANIFEST_CACHE'),
\JLog::WARNING, 'jerror');
return false;
}
}
}
PKK�[�A�R�C�CAdapter/TemplateAdapter.phpnu�[���<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Installer\Adapter;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Installer\Installer;
use Joomla\CMS\Installer\InstallerAdapter;
use Joomla\CMS\Table\Table;
use Joomla\CMS\Table\Update;
\JLoader::import('joomla.filesystem.folder');
/**
* Template installer
*
* @since 3.1
*/
class TemplateAdapter extends InstallerAdapter
{
/**
* The install client ID
*
* @var integer
* @since 3.4
*/
protected $clientId;
/**
* Method to check if the extension is already present in the database
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function checkExistingExtension()
{
try
{
$this->currentExtensionId = $this->extension->find(
array(
'element' => $this->element,
'type' => $this->type,
'client_id' => $this->clientId,
)
);
}
catch (\RuntimeException $e)
{
// Install failed, roll back changes
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_ROLLBACK',
\JText::_('JLIB_INSTALLER_' . $this->route),
$e->getMessage()
),
$e->getCode(),
$e
);
}
}
/**
* Method to copy the extension's base files from the `<files>`
tag(s) and the manifest file
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function copyBaseFiles()
{
// Copy all the necessary files
if ($this->parent->parseFiles($this->getManifest()->files,
-1) === false)
{
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_TPL_INSTALL_COPY_FILES',
'files'
)
);
}
if ($this->parent->parseFiles($this->getManifest()->images,
-1) === false)
{
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_TPL_INSTALL_COPY_FILES',
'images'
)
);
}
if ($this->parent->parseFiles($this->getManifest()->css, -1)
=== false)
{
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_TPL_INSTALL_COPY_FILES',
'css'
)
);
}
// If there is a manifest script, let's copy it.
if ($this->manifest_script)
{
$path['src'] =
$this->parent->getPath('source') . '/' .
$this->manifest_script;
$path['dest'] =
$this->parent->getPath('extension_root') . '/' .
$this->manifest_script;
if ($this->parent->isOverwrite() ||
!file_exists($path['dest']))
{
if (!$this->parent->copyFiles(array($path)))
{
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_MANIFEST',
\JText::_('JLIB_INSTALLER_' .
strtoupper($this->getRoute()))
)
);
}
}
}
}
/**
* Method to finalise the installation processing
*
* @return void
*
* @since 3.1
* @throws \RuntimeException
*/
protected function finaliseInstall()
{
// Clobber any possible pending updates
/** @var Update $update */
$update = Table::getInstance('update');
$uid = $update->find(
array(
'element' => $this->element,
'type' => $this->type,
'client_id' => $this->clientId,
)
);
if ($uid)
{
$update->delete($uid);
}
// Lastly, we will copy the manifest file to its appropriate place.
if ($this->route !== 'discover_install')
{
if (!$this->parent->copyManifest(-1))
{
// Install failed, rollback changes
throw new
\RuntimeException(\JText::_('JLIB_INSTALLER_ABORT_TPL_INSTALL_COPY_SETUP'));
}
}
}
/**
* Custom loadLanguage method
*
* @param string $path The path where to find language files.
*
* @return InstallerTemplate
*
* @since 3.1
*/
public function loadLanguage($path = null)
{
$source = $this->parent->getPath('source');
$basePath = $this->parent->extension->client_id ?
JPATH_ADMINISTRATOR : JPATH_SITE;
if (!$source)
{
$this->parent->setPath('source', $basePath .
'/templates/' . $this->parent->extension->element);
}
$this->setManifest($this->parent->getManifest());
$client = (string) $this->getManifest()->attributes()->client;
// Load administrator language if not set.
if (!$client)
{
$client = 'ADMINISTRATOR';
}
$base = constant('JPATH_' . strtoupper($client));
$extension = 'tpl_' . $this->getName();
$source = $path ?: $base . '/templates/' .
$this->getName();
$this->doLoadLanguage($extension, $source, $base);
}
/**
* Method to parse optional tags in the manifest
*
* @return void
*
* @since 3.4
*/
protected function parseOptionalTags()
{
$this->parent->parseMedia($this->getManifest()->media);
$this->parent->parseLanguages($this->getManifest()->languages,
$this->clientId);
}
/**
* Overloaded method to parse queries for template installations
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function parseQueries()
{
if (in_array($this->route, array('install',
'discover_install')))
{
$db = $this->db;
$lang = \JFactory::getLanguage();
$debug = $lang->setDebug(false);
$columns = array(
$db->quoteName('template'),
$db->quoteName('client_id'),
$db->quoteName('home'),
$db->quoteName('title'),
$db->quoteName('params'),
);
$values = array(
$db->quote($this->extension->element),
$this->extension->client_id, $db->quote(0),
$db->quote(\JText::sprintf('JLIB_INSTALLER_DEFAULT_STYLE',
\JText::_($this->extension->name))),
$db->quote($this->extension->params),
);
$lang->setDebug($debug);
// Insert record in #__template_styles
$query = $db->getQuery(true);
$query->insert($db->quoteName('#__template_styles'))
->columns($columns)
->values(implode(',', $values));
// There is a chance this could fail but we don't care...
$db->setQuery($query)->execute();
}
}
/**
* Prepares the adapter for a discover_install task
*
* @return void
*
* @since 3.4
*/
public function prepareDiscoverInstall()
{
$client =
ApplicationHelper::getClientInfo($this->extension->client_id);
$manifestPath = $client->path . '/templates/' .
$this->extension->element . '/templateDetails.xml';
$this->parent->manifest =
$this->parent->isManifest($manifestPath);
$this->parent->setPath('manifest', $manifestPath);
$this->setManifest($this->parent->getManifest());
}
/**
* Method to do any prechecks and setup the install paths for the
extension
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function setupInstallPaths()
{
// Get the client application target
$cname = (string) $this->getManifest()->attributes()->client;
if ($cname)
{
// Attempt to map the client to a base path
$client = ApplicationHelper::getClientInfo($cname, true);
if ($client === false)
{
throw new
\RuntimeException(\JText::sprintf('JLIB_INSTALLER_ABORT_TPL_INSTALL_UNKNOWN_CLIENT',
$cname));
}
$basePath = $client->path;
$this->clientId = $client->id;
}
else
{
// No client attribute was found so we assume the site as the client
$basePath = JPATH_SITE;
$this->clientId = 0;
}
// Set the template root path
if (empty($this->element))
{
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_MOD_INSTALL_NOFILE',
\JText::_('JLIB_INSTALLER_' . strtoupper($this->route))
)
);
}
$this->parent->setPath('extension_root', $basePath .
'/templates/' . $this->element);
}
/**
* Method to store the extension to the database
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function storeExtension()
{
// Discover installs are stored a little differently
if ($this->route === 'discover_install')
{
$manifest_details =
Installer::parseXMLInstallFile($this->parent->getPath('manifest'));
$this->extension->manifest_cache = json_encode($manifest_details);
$this->extension->state = 0;
$this->extension->name = $manifest_details['name'];
$this->extension->enabled = 1;
$this->extension->params = $this->parent->getParams();
if (!$this->extension->store())
{
// Install failed, roll back changes
throw new
\RuntimeException(\JText::_('JLIB_INSTALLER_ERROR_TPL_DISCOVER_STORE_DETAILS'));
}
return;
}
// Was there a template already installed with the same name?
if ($this->currentExtensionId)
{
if (!$this->parent->isOverwrite())
{
// Install failed, roll back changes
throw new \RuntimeException(
\JText::_('JLIB_INSTALLER_ABORT_TPL_INSTALL_ALREADY_INSTALLED')
);
}
// Load the entry and update the manifest_cache
$this->extension->load($this->currentExtensionId);
}
else
{
$this->extension->type = 'template';
$this->extension->element = $this->element;
// There is no folder for templates
$this->extension->folder = '';
$this->extension->enabled = 1;
$this->extension->protected = 0;
$this->extension->access = 1;
$this->extension->client_id = $this->clientId;
$this->extension->params = $this->parent->getParams();
// Custom data
$this->extension->custom_data = '';
$this->extension->system_data = '';
}
// Name might change in an update
$this->extension->name = $this->name;
$this->extension->manifest_cache =
$this->parent->generateManifestCache();
if (!$this->extension->store())
{
// Install failed, roll back changes
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_ROLLBACK',
\JText::_('JLIB_INSTALLER_' . strtoupper($this->route)),
$this->extension->getError()
)
);
}
}
/**
* Custom uninstall method
*
* @param integer $id The extension ID
*
* @return boolean True on success
*
* @since 3.1
*/
public function uninstall($id)
{
// First order of business will be to load the template object table from
the database.
// This should give us the necessary information to proceed.
$row = Table::getInstance('extension');
if (!$row->load((int) $id) || $row->element === '')
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_TPL_UNINSTALL_ERRORUNKOWNEXTENSION'),
\JLog::WARNING, 'jerror');
return false;
}
// Is the template we are trying to uninstall a core one?
// Because that is not a good idea...
if ($row->protected)
{
\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_TPL_UNINSTALL_WARNCORETEMPLATE',
$row->name), \JLog::WARNING, 'jerror');
return false;
}
/*
* Does this extension have a parent package?
* If so, check if the package disallows individual extensions being
uninstalled if the package is not being uninstalled
*/
if ($row->package_id &&
!$this->parent->isPackageUninstall() &&
!$this->canUninstallPackageChild($row->package_id))
{
\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_CANNOT_UNINSTALL_CHILD_OF_PACKAGE',
$row->name), \JLog::WARNING, 'jerror');
return false;
}
$name = $row->element;
$clientId = $row->client_id;
// For a template the id will be the template name which represents the
subfolder of the templates folder that the template resides in.
if (!$name)
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_TPL_UNINSTALL_TEMPLATE_ID_EMPTY'),
\JLog::WARNING, 'jerror');
return false;
}
// Deny remove default template
$db = $this->parent->getDbo();
$query = $db->getQuery(true)
->select('COUNT(*)')
->from($db->qn('#__template_styles'))
->where($db->qn('home') . ' = ' .
$db->q('1'))
->where($db->qn('template') . ' = ' .
$db->q($name))
->where($db->quoteName('client_id') . ' = ' .
$clientId);
$db->setQuery($query);
if ($db->loadResult() != 0)
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_TPL_UNINSTALL_TEMPLATE_DEFAULT'),
\JLog::WARNING, 'jerror');
return false;
}
// Get the template root path
$client = ApplicationHelper::getClientInfo($clientId);
if (!$client)
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_TPL_UNINSTALL_INVALID_CLIENT'),
\JLog::WARNING, 'jerror');
return false;
}
$this->parent->setPath('extension_root', $client->path
. '/templates/' . strtolower($name));
$this->parent->setPath('source',
$this->parent->getPath('extension_root'));
// We do findManifest to avoid problem when uninstalling a list of
extensions: getManifest cache its manifest file
$this->parent->findManifest();
$manifest = $this->parent->getManifest();
if (!($manifest instanceof \SimpleXMLElement))
{
// Kill the extension entry
$row->delete($row->extension_id);
unset($row);
// Make sure we delete the folders
\JFolder::delete($this->parent->getPath('extension_root'));
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_TPL_UNINSTALL_INVALID_NOTFOUND_MANIFEST'),
\JLog::WARNING, 'jerror');
return false;
}
// Remove files
$this->parent->removeFiles($manifest->media);
$this->parent->removeFiles($manifest->languages, $clientId);
// Delete the template directory
if
(\JFolder::exists($this->parent->getPath('extension_root')))
{
$retval =
\JFolder::delete($this->parent->getPath('extension_root'));
}
else
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_TPL_UNINSTALL_TEMPLATE_DIRECTORY'),
\JLog::WARNING, 'jerror');
$retval = false;
}
// Set menu that assigned to the template back to default template
$subQuery = $db->getQuery(true)
->select('s.id')
->from($db->qn('#__template_styles', 's'))
->where($db->qn('s.template') . ' = ' .
$db->q(strtolower($name)))
->where($db->qn('s.client_id') . ' = ' .
$clientId);
$query->clear()
->update($db->qn('#__menu'))
->set($db->qn('template_style_id') . ' = 0')
->where($db->qn('template_style_id') . ' IN ('
. (string) $subQuery . ')');
$db->setQuery($query);
$db->execute();
$query = $db->getQuery(true)
->delete($db->quoteName('#__template_styles'))
->where($db->quoteName('template') . ' = ' .
$db->quote($name))
->where($db->quoteName('client_id') . ' = ' .
$clientId);
$db->setQuery($query);
$db->execute();
$row->delete($row->extension_id);
unset($row);
return $retval;
}
/**
* Discover existing but uninstalled templates
*
* @return array Extension list
*/
public function discover()
{
$results = array();
$site_list = \JFolder::folders(JPATH_SITE . '/templates');
$admin_list = \JFolder::folders(JPATH_ADMINISTRATOR .
'/templates');
$site_info = ApplicationHelper::getClientInfo('site', true);
$admin_info = ApplicationHelper::getClientInfo('administrator',
true);
foreach ($site_list as $template)
{
if (file_exists(JPATH_SITE .
"/templates/$template/templateDetails.xml"))
{
if ($template === 'system')
{
// Ignore special system template
continue;
}
$manifest_details = Installer::parseXMLInstallFile(JPATH_SITE .
"/templates/$template/templateDetails.xml");
$extension = Table::getInstance('extension');
$extension->set('type', 'template');
$extension->set('client_id', $site_info->id);
$extension->set('element', $template);
$extension->set('folder', '');
$extension->set('name', $template);
$extension->set('state', -1);
$extension->set('manifest_cache',
json_encode($manifest_details));
$extension->set('params', '{}');
$results[] = $extension;
}
}
foreach ($admin_list as $template)
{
if (file_exists(JPATH_ADMINISTRATOR .
"/templates/$template/templateDetails.xml"))
{
if ($template === 'system')
{
// Ignore special system template
continue;
}
$manifest_details = Installer::parseXMLInstallFile(JPATH_ADMINISTRATOR
. "/templates/$template/templateDetails.xml");
$extension = Table::getInstance('extension');
$extension->set('type', 'template');
$extension->set('client_id', $admin_info->id);
$extension->set('element', $template);
$extension->set('folder', '');
$extension->set('name', $template);
$extension->set('state', -1);
$extension->set('manifest_cache',
json_encode($manifest_details));
$extension->set('params', '{}');
$results[] = $extension;
}
}
return $results;
}
/**
* Refreshes the extension table cache
*
* @return boolean Result of operation, true if updated, false on
failure
*
* @since 3.1
*/
public function refreshManifestCache()
{
// Need to find to find where the XML file is since we don't store
this normally.
$client =
ApplicationHelper::getClientInfo($this->parent->extension->client_id);
$manifestPath = $client->path . '/templates/' .
$this->parent->extension->element .
'/templateDetails.xml';
$this->parent->manifest =
$this->parent->isManifest($manifestPath);
$this->parent->setPath('manifest', $manifestPath);
$manifest_details =
Installer::parseXMLInstallFile($this->parent->getPath('manifest'));
$this->parent->extension->manifest_cache =
json_encode($manifest_details);
$this->parent->extension->name =
$manifest_details['name'];
try
{
return $this->parent->extension->store();
}
catch (\RuntimeException $e)
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_TPL_REFRESH_MANIFEST_CACHE'),
\JLog::WARNING, 'jerror');
return false;
}
}
}
PKK�[�5y����
Installer.phpnu�[���<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Installer;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\CMS\Table\Extension;
use Joomla\CMS\Table\Table;
\JLoader::import('joomla.filesystem.file');
\JLoader::import('joomla.filesystem.folder');
\JLoader::import('joomla.filesystem.path');
\JLoader::import('joomla.base.adapter');
/**
* Joomla base installer class
*
* @since 3.1
*/
class Installer extends \JAdapter
{
/**
* Array of paths needed by the installer
*
* @var array
* @since 3.1
*/
protected $paths = array();
/**
* True if package is an upgrade
*
* @var boolean
* @since 3.1
*/
protected $upgrade = null;
/**
* The manifest trigger class
*
* @var object
* @since 3.1
*/
public $manifestClass = null;
/**
* True if existing files can be overwritten
*
* @var boolean
* @since 3.0.0
*/
protected $overwrite = false;
/**
* Stack of installation steps
* - Used for installation rollback
*
* @var array
* @since 3.1
*/
protected $stepStack = array();
/**
* Extension Table Entry
*
* @var Extension
* @since 3.1
*/
public $extension = null;
/**
* The output from the install/uninstall scripts
*
* @var string
* @since 3.1
* */
public $message = null;
/**
* The installation manifest XML object
*
* @var object
* @since 3.1
*/
public $manifest = null;
/**
* The extension message that appears
*
* @var string
* @since 3.1
*/
protected $extension_message = null;
/**
* The redirect URL if this extension (can be null if no redirect)
*
* @var string
* @since 3.1
*/
protected $redirect_url = null;
/**
* Flag if the uninstall process was triggered by uninstalling a package
*
* @var boolean
* @since 3.7.0
*/
protected $packageUninstall = false;
/**
* Installer instance container.
*
* @var Installer
* @since 3.1
* @deprecated 4.0
*/
protected static $instance;
/**
* Installer instances container.
*
* @var Installer[]
* @since 3.4
*/
protected static $instances;
/**
* Constructor
*
* @param string $basepath Base Path of the adapters
* @param string $classprefix Class prefix of adapters
* @param string $adapterfolder Name of folder to append to base path
*
* @since 3.1
*/
public function __construct($basepath = __DIR__, $classprefix =
'\\Joomla\\CMS\\Installer\\Adapter', $adapterfolder =
'Adapter')
{
parent::__construct($basepath, $classprefix, $adapterfolder);
$this->extension = Table::getInstance('extension');
}
/**
* Returns the global Installer object, only creating it if it
doesn't already exist.
*
* @param string $basepath Base Path of the adapters
* @param string $classprefix Class prefix of adapters
* @param string $adapterfolder Name of folder to append to base path
*
* @return Installer An installer object
*
* @since 3.1
*/
public static function getInstance($basepath = __DIR__, $classprefix =
'\\Joomla\\CMS\\Installer\\Adapter', $adapterfolder =
'Adapter')
{
if (!isset(self::$instances[$basepath]))
{
self::$instances[$basepath] = new Installer($basepath, $classprefix,
$adapterfolder);
// For B/C, we load the first instance into the static $instance
container, remove at 4.0
if (!isset(self::$instance))
{
self::$instance = self::$instances[$basepath];
}
}
return self::$instances[$basepath];
}
/**
* Get the allow overwrite switch
*
* @return boolean Allow overwrite switch
*
* @since 3.1
*/
public function isOverwrite()
{
return $this->overwrite;
}
/**
* Set the allow overwrite switch
*
* @param boolean $state Overwrite switch state
*
* @return boolean True it state is set, false if it is not
*
* @since 3.1
*/
public function setOverwrite($state = false)
{
$tmp = $this->overwrite;
if ($state)
{
$this->overwrite = true;
}
else
{
$this->overwrite = false;
}
return $tmp;
}
/**
* Get the redirect location
*
* @return string Redirect location (or null)
*
* @since 3.1
*/
public function getRedirectUrl()
{
return $this->redirect_url;
}
/**
* Set the redirect location
*
* @param string $newurl New redirect location
*
* @return void
*
* @since 3.1
*/
public function setRedirectUrl($newurl)
{
$this->redirect_url = $newurl;
}
/**
* Get whether this installer is uninstalling extensions which are part of
a package
*
* @return boolean
*
* @since 3.7.0
*/
public function isPackageUninstall()
{
return $this->packageUninstall;
}
/**
* Set whether this installer is uninstalling extensions which are part of
a package
*
* @param boolean $uninstall True if a package triggered the
uninstall, false otherwise
*
* @return void
*
* @since 3.7.0
*/
public function setPackageUninstall($uninstall)
{
$this->packageUninstall = $uninstall;
}
/**
* Get the upgrade switch
*
* @return boolean
*
* @since 3.1
*/
public function isUpgrade()
{
return $this->upgrade;
}
/**
* Set the upgrade switch
*
* @param boolean $state Upgrade switch state
*
* @return boolean True if upgrade, false otherwise
*
* @since 3.1
*/
public function setUpgrade($state = false)
{
$tmp = $this->upgrade;
if ($state)
{
$this->upgrade = true;
}
else
{
$this->upgrade = false;
}
return $tmp;
}
/**
* Get the installation manifest object
*
* @return \SimpleXMLElement Manifest object
*
* @since 3.1
*/
public function getManifest()
{
if (!is_object($this->manifest))
{
$this->findManifest();
}
return $this->manifest;
}
/**
* Get an installer path by name
*
* @param string $name Path name
* @param string $default Default value
*
* @return string Path
*
* @since 3.1
*/
public function getPath($name, $default = null)
{
return (!empty($this->paths[$name])) ? $this->paths[$name] :
$default;
}
/**
* Sets an installer path by name
*
* @param string $name Path name
* @param string $value Path
*
* @return void
*
* @since 3.1
*/
public function setPath($name, $value)
{
$this->paths[$name] = $value;
}
/**
* Pushes a step onto the installer stack for rolling back steps
*
* @param array $step Installer step
*
* @return void
*
* @since 3.1
*/
public function pushStep($step)
{
$this->stepStack[] = $step;
}
/**
* Installation abort method
*
* @param string $msg Abort message from the installer
* @param string $type Package type if defined
*
* @return boolean True if successful
*
* @since 3.1
*/
public function abort($msg = null, $type = null)
{
$retval = true;
$step = array_pop($this->stepStack);
// Raise abort warning
if ($msg)
{
\JLog::add($msg, \JLog::WARNING, 'jerror');
}
while ($step != null)
{
switch ($step['type'])
{
case 'file':
// Remove the file
$stepval = \JFile::delete($step['path']);
break;
case 'folder':
// Remove the folder
$stepval = \JFolder::delete($step['path']);
break;
case 'query':
// Execute the query.
$stepval = $this->parseSQLFiles($step['script']);
break;
case 'extension':
// Get database connector object
$db = $this->getDbo();
$query = $db->getQuery(true);
// Remove the entry from the #__extensions table
$query->delete($db->quoteName('#__extensions'))
->where($db->quoteName('extension_id') . ' =
' . (int) $step['id']);
$db->setQuery($query);
try
{
$db->execute();
$stepval = true;
}
catch (\JDatabaseExceptionExecuting $e)
{
// The database API will have already logged the error it caught, we
just need to alert the user to the issue
\JLog::add(\JText::_('JLIB_INSTALLER_ABORT_ERROR_DELETING_EXTENSIONS_RECORD'),
\JLog::WARNING, 'jerror');
$stepval = false;
}
break;
default:
if ($type && is_object($this->_adapters[$type]))
{
// Build the name of the custom rollback method for the type
$method = '_rollback_' . $step['type'];
// Custom rollback method handler
if (method_exists($this->_adapters[$type], $method))
{
$stepval = $this->_adapters[$type]->$method($step);
}
}
else
{
// Set it to false
$stepval = false;
}
break;
}
// Only set the return value if it is false
if ($stepval === false)
{
$retval = false;
}
// Get the next step and continue
$step = array_pop($this->stepStack);
}
return $retval;
}
// Adapter functions
/**
* Package installation method
*
* @param string $path Path to package source folder
*
* @return boolean True if successful
*
* @since 3.1
*/
public function install($path = null)
{
if ($path && \JFolder::exists($path))
{
$this->setPath('source', $path);
}
else
{
$this->abort(\JText::_('JLIB_INSTALLER_ABORT_NOINSTALLPATH'));
return false;
}
if (!$adapter = $this->setupInstall('install', true))
{
$this->abort(\JText::_('JLIB_INSTALLER_ABORT_DETECTMANIFEST'));
return false;
}
if (!is_object($adapter))
{
return false;
}
// Add the languages from the package itself
if (method_exists($adapter, 'loadLanguage'))
{
$adapter->loadLanguage($path);
}
// Fire the onExtensionBeforeInstall event.
PluginHelper::importPlugin('extension');
$dispatcher = \JEventDispatcher::getInstance();
$dispatcher->trigger(
'onExtensionBeforeInstall',
array(
'method' => 'install',
'type' => $this->manifest->attributes()->type,
'manifest' => $this->manifest,
'extension' => 0,
)
);
// Run the install
$result = $adapter->install();
// Fire the onExtensionAfterInstall
$dispatcher->trigger(
'onExtensionAfterInstall',
array('installer' => clone $this, 'eid' =>
$result)
);
if ($result !== false)
{
// Refresh versionable assets cache
\JFactory::getApplication()->flushAssets();
return true;
}
return false;
}
/**
* Discovered package installation method
*
* @param integer $eid Extension ID
*
* @return boolean True if successful
*
* @since 3.1
*/
public function discover_install($eid = null)
{
if (!$eid)
{
$this->abort(\JText::_('JLIB_INSTALLER_ABORT_EXTENSIONNOTVALID'));
return false;
}
if (!$this->extension->load($eid))
{
$this->abort(\JText::_('JLIB_INSTALLER_ABORT_LOAD_DETAILS'));
return false;
}
if ($this->extension->state != -1)
{
$this->abort(\JText::_('JLIB_INSTALLER_ABORT_ALREADYINSTALLED'));
return false;
}
// Load the adapter(s) for the install manifest
$type = $this->extension->type;
$params = array('extension' => $this->extension,
'route' => 'discover_install');
$adapter = $this->getAdapter($type, $params);
if (!is_object($adapter))
{
return false;
}
if (!method_exists($adapter, 'discover_install') ||
!$adapter->getDiscoverInstallSupported())
{
$this->abort(\JText::sprintf('JLIB_INSTALLER_ERROR_DISCOVER_INSTALL_UNSUPPORTED',
$type));
return false;
}
// The adapter needs to prepare itself
if (method_exists($adapter, 'prepareDiscoverInstall'))
{
try
{
$adapter->prepareDiscoverInstall();
}
catch (\RuntimeException $e)
{
$this->abort($e->getMessage());
return false;
}
}
// Add the languages from the package itself
if (method_exists($adapter, 'loadLanguage'))
{
$adapter->loadLanguage();
}
// Fire the onExtensionBeforeInstall event.
PluginHelper::importPlugin('extension');
$dispatcher = \JEventDispatcher::getInstance();
$dispatcher->trigger(
'onExtensionBeforeInstall',
array(
'method' => 'discover_install',
'type' => $this->extension->get('type'),
'manifest' => null,
'extension' =>
$this->extension->get('extension_id'),
)
);
// Run the install
$result = $adapter->discover_install();
// Fire the onExtensionAfterInstall
$dispatcher->trigger(
'onExtensionAfterInstall',
array('installer' => clone $this, 'eid' =>
$result)
);
if ($result !== false)
{
// Refresh versionable assets cache
\JFactory::getApplication()->flushAssets();
return true;
}
return false;
}
/**
* Extension discover method
*
* Asks each adapter to find extensions
*
* @return InstallerExtension[]
*
* @since 3.1
*/
public function discover()
{
$this->loadAllAdapters();
$results = array();
foreach ($this->_adapters as $adapter)
{
// Joomla! 1.5 installation adapter legacy support
if (method_exists($adapter, 'discover'))
{
$tmp = $adapter->discover();
// If its an array and has entries
if (is_array($tmp) && count($tmp))
{
// Merge it into the system
$results = array_merge($results, $tmp);
}
}
}
return $results;
}
/**
* Package update method
*
* @param string $path Path to package source folder
*
* @return boolean True if successful
*
* @since 3.1
*/
public function update($path = null)
{
if ($path && \JFolder::exists($path))
{
$this->setPath('source', $path);
}
else
{
$this->abort(\JText::_('JLIB_INSTALLER_ABORT_NOUPDATEPATH'));
return false;
}
if (!$adapter = $this->setupInstall('update', true))
{
$this->abort(\JText::_('JLIB_INSTALLER_ABORT_DETECTMANIFEST'));
return false;
}
if (!is_object($adapter))
{
return false;
}
// Add the languages from the package itself
if (method_exists($adapter, 'loadLanguage'))
{
$adapter->loadLanguage($path);
}
// Fire the onExtensionBeforeUpdate event.
PluginHelper::importPlugin('extension');
$dispatcher = \JEventDispatcher::getInstance();
$dispatcher->trigger('onExtensionBeforeUpdate',
array('type' => $this->manifest->attributes()->type,
'manifest' => $this->manifest));
// Run the update
$result = $adapter->update();
// Fire the onExtensionAfterUpdate
$dispatcher->trigger(
'onExtensionAfterUpdate',
array('installer' => clone $this, 'eid' =>
$result)
);
if ($result !== false)
{
return true;
}
return false;
}
/**
* Package uninstallation method
*
* @param string $type Package type
* @param mixed $identifier Package identifier for adapter
* @param integer $cid Application ID; deprecated in 1.6
*
* @return boolean True if successful
*
* @since 3.1
*/
public function uninstall($type, $identifier, $cid = 0)
{
$params = array('extension' => $this->extension,
'route' => 'uninstall');
$adapter = $this->getAdapter($type, $params);
if (!is_object($adapter))
{
return false;
}
// We don't load languages here, we get the extension adapter to
work it out
// Fire the onExtensionBeforeUninstall event.
PluginHelper::importPlugin('extension');
$dispatcher = \JEventDispatcher::getInstance();
$dispatcher->trigger('onExtensionBeforeUninstall',
array('eid' => $identifier));
// Run the uninstall
$result = $adapter->uninstall($identifier);
// Fire the onExtensionAfterInstall
$dispatcher->trigger(
'onExtensionAfterUninstall',
array('installer' => clone $this, 'eid' =>
$identifier, 'result' => $result)
);
// Refresh versionable assets cache
\JFactory::getApplication()->flushAssets();
return $result;
}
/**
* Refreshes the manifest cache stored in #__extensions
*
* @param integer $eid Extension ID
*
* @return boolean
*
* @since 3.1
*/
public function refreshManifestCache($eid)
{
if ($eid)
{
if (!$this->extension->load($eid))
{
$this->abort(\JText::_('JLIB_INSTALLER_ABORT_LOAD_DETAILS'));
return false;
}
if ($this->extension->state == -1)
{
$this->abort(\JText::sprintf('JLIB_INSTALLER_ABORT_REFRESH_MANIFEST_CACHE',
$this->extension->name));
return false;
}
// Fetch the adapter
$adapter = $this->getAdapter($this->extension->type);
if (!is_object($adapter))
{
return false;
}
if (!method_exists($adapter, 'refreshManifestCache'))
{
$this->abort(\JText::sprintf('JLIB_INSTALLER_ABORT_METHODNOTSUPPORTED_TYPE',
$this->extension->type));
return false;
}
$result = $adapter->refreshManifestCache();
if ($result !== false)
{
return true;
}
else
{
return false;
}
}
$this->abort(\JText::_('JLIB_INSTALLER_ABORT_REFRESH_MANIFEST_CACHE_VALID'));
return false;
}
// Utility functions
/**
* Prepare for installation: this method sets the installation directory,
finds
* and checks the installation file and verifies the installation type.
*
* @param string $route The install route being followed
* @param boolean $returnAdapter Flag to return the instantiated
adapter
*
* @return boolean|InstallerAdapter InstallerAdapter object if
explicitly requested otherwise boolean
*
* @since 3.1
*/
public function setupInstall($route = 'install', $returnAdapter
= false)
{
// We need to find the installation manifest file
if (!$this->findManifest())
{
return false;
}
// Load the adapter(s) for the install manifest
$type = (string) $this->manifest->attributes()->type;
$params = array('route' => $route, 'manifest'
=> $this->getManifest());
// Load the adapter
$adapter = $this->getAdapter($type, $params);
if ($returnAdapter)
{
return $adapter;
}
return true;
}
/**
* Backward compatible method to parse through a queries element of the
* installation manifest file and take appropriate action.
*
* @param \SimpleXMLElement $element The XML node to process
*
* @return mixed Number of queries processed or False on error
*
* @since 3.1
*/
public function parseQueries(\SimpleXMLElement $element)
{
// Get the database connector object
$db = & $this->_db;
if (!$element || !count($element->children()))
{
// Either the tag does not exist or has no children therefore we return
zero files processed.
return 0;
}
// Get the array of query nodes to process
$queries = $element->children();
if (count($queries) === 0)
{
// No queries to process
return 0;
}
$update_count = 0;
// Process each query in the $queries array (children of $tagName).
foreach ($queries as $query)
{
$db->setQuery($db->convertUtf8mb4QueryToUtf8($query));
try
{
$db->execute();
}
catch (\JDatabaseExceptionExecuting $e)
{
\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_SQL_ERROR',
$e->getMessage()), \JLog::WARNING, 'jerror');
return false;
}
$update_count++;
}
return $update_count;
}
/**
* Method to extract the name of a discreet installation sql file from the
installation manifest file.
*
* @param object $element The XML node to process
*
* @return mixed Number of queries processed or False on error
*
* @since 3.1
*/
public function parseSQLFiles($element)
{
if (!$element || !count($element->children()))
{
// The tag does not exist.
return 0;
}
$db = & $this->_db;
// TODO - At 4.0 we can change this to use `getServerType()` since SQL
Server will not be supported
$dbDriver = strtolower($db->name);
if ($db->getServerType() === 'mysql')
{
$dbDriver = 'mysql';
}
elseif ($db->getServerType() === 'postgresql')
{
$dbDriver = 'postgresql';
}
$update_count = 0;
// Get the name of the sql file to process
foreach ($element->children() as $file)
{
$fCharset = strtolower($file->attributes()->charset) ===
'utf8' ? 'utf8' : '';
$fDriver = strtolower($file->attributes()->driver);
if ($fDriver === 'mysqli' || $fDriver ===
'pdomysql')
{
$fDriver = 'mysql';
}
elseif ($fDriver === 'pgsql')
{
$fDriver = 'postgresql';
}
if ($fCharset === 'utf8' && $fDriver == $dbDriver)
{
$sqlfile = $this->getPath('extension_root') .
'/' . trim($file);
// Check that sql files exists before reading. Otherwise raise error
for rollback
if (!file_exists($sqlfile))
{
\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_SQL_FILENOTFOUND',
$sqlfile), \JLog::WARNING, 'jerror');
return false;
}
$buffer = file_get_contents($sqlfile);
// Graceful exit and rollback if read not successful
if ($buffer === false)
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_SQL_READBUFFER'),
\JLog::WARNING, 'jerror');
return false;
}
// Create an array of queries from the sql file
$queries = \JDatabaseDriver::splitSql($buffer);
if (count($queries) === 0)
{
// No queries to process
continue;
}
// Process each query in the $queries array (split out of sql file).
foreach ($queries as $query)
{
$db->setQuery($db->convertUtf8mb4QueryToUtf8($query));
try
{
$db->execute();
}
catch (\JDatabaseExceptionExecuting $e)
{
\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_SQL_ERROR',
$e->getMessage()), \JLog::WARNING, 'jerror');
return false;
}
$update_count++;
}
}
}
return $update_count;
}
/**
* Set the schema version for an extension by looking at its latest update
*
* @param \SimpleXMLElement $schema Schema Tag
* @param integer $eid Extension ID
*
* @return void
*
* @since 3.1
*/
public function setSchemaVersion(\SimpleXMLElement $schema, $eid)
{
if ($eid && $schema)
{
$db = \JFactory::getDbo();
$schemapaths = $schema->children();
if (!$schemapaths)
{
return;
}
if (count($schemapaths))
{
$dbDriver = strtolower($db->name);
if ($db->getServerType() === 'mysql')
{
$dbDriver = 'mysql';
}
elseif ($db->getServerType() === 'postgresql')
{
$dbDriver = 'postgresql';
}
$schemapath = '';
foreach ($schemapaths as $entry)
{
$attrs = $entry->attributes();
if ($attrs['type'] == $dbDriver)
{
$schemapath = $entry;
break;
}
}
if ($schemapath !== '')
{
$files = str_replace('.sql', '',
\JFolder::files($this->getPath('extension_root') .
'/' . $schemapath, '\.sql$'));
usort($files, 'version_compare');
// Update the database
$query = $db->getQuery(true)
->delete('#__schemas')
->where('extension_id = ' . $eid);
$db->setQuery($query);
if ($db->execute())
{
$query->clear()
->insert($db->quoteName('#__schemas'))
->columns(array($db->quoteName('extension_id'),
$db->quoteName('version_id')))
->values($eid . ', ' . $db->quote(end($files)));
$db->setQuery($query);
$db->execute();
}
}
}
}
}
/**
* Method to process the updates for an item
*
* @param \SimpleXMLElement $schema The XML node to process
* @param integer $eid Extension Identifier
*
* @return boolean Result of the operations
*
* @since 3.1
*/
public function parseSchemaUpdates(\SimpleXMLElement $schema, $eid)
{
$update_count = 0;
// Ensure we have an XML element and a valid extension id
if ($eid && $schema)
{
$db = \JFactory::getDbo();
$schemapaths = $schema->children();
if (count($schemapaths))
{
// TODO - At 4.0 we can change this to use `getServerType()` since SQL
Server will not be supported
$dbDriver = strtolower($db->name);
if ($db->getServerType() === 'mysql')
{
$dbDriver = 'mysql';
}
elseif ($db->getServerType() === 'postgresql')
{
$dbDriver = 'postgresql';
}
$schemapath = '';
foreach ($schemapaths as $entry)
{
$attrs = $entry->attributes();
// Assuming that the type is a mandatory attribute but if it is not
mandatory then there should be a discussion for it.
$uDriver = strtolower($attrs['type']);
if ($uDriver === 'mysqli' || $uDriver ===
'pdomysql')
{
$uDriver = 'mysql';
}
elseif ($uDriver === 'pgsql')
{
$uDriver = 'postgresql';
}
if ($uDriver == $dbDriver)
{
$schemapath = $entry;
break;
}
}
if ($schemapath !== '')
{
$files = \JFolder::files($this->getPath('extension_root')
. '/' . $schemapath, '\.sql$');
if (empty($files))
{
return $update_count;
}
$files = str_replace('.sql', '', $files);
usort($files, 'version_compare');
$query = $db->getQuery(true)
->select('version_id')
->from('#__schemas')
->where('extension_id = ' . $eid);
$db->setQuery($query);
$version = $db->loadResult();
// No version - use initial version.
if (!$version)
{
$version = '0.0.0';
}
foreach ($files as $file)
{
if (version_compare($file, $version) > 0)
{
$buffer =
file_get_contents($this->getPath('extension_root') .
'/' . $schemapath . '/' . $file . '.sql');
// Graceful exit and rollback if read not successful
if ($buffer === false)
{
\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_SQL_READBUFFER'),
\JLog::WARNING, 'jerror');
return false;
}
// Create an array of queries from the sql file
$queries = \JDatabaseDriver::splitSql($buffer);
if (count($queries) === 0)
{
// No queries to process
continue;
}
// Process each query in the $queries array (split out of sql file).
foreach ($queries as $query)
{
$db->setQuery($db->convertUtf8mb4QueryToUtf8($query));
try
{
$db->execute();
}
catch (\JDatabaseExceptionExecuting $e)
{
\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_SQL_ERROR',
$e->getMessage()), \JLog::WARNING, 'jerror');
return false;
}
$queryString = (string) $query;
$queryString = str_replace(array("\r", "\n"),
array('', ' '), substr($queryString, 0, 80));
\JLog::add(\JText::sprintf('JLIB_INSTALLER_UPDATE_LOG_QUERY',
$file, $queryString), \JLog::INFO, 'Update');
$update_count++;
}
}
}
// Update the database
$query = $db->getQuery(true)
->delete('#__schemas')
->where('extension_id = ' . $eid);
$db->setQuery($query);
if ($db->execute())
{
$query->clear()
->insert($db->quoteName('#__schemas'))
->columns(array($db->quoteName('extension_id'),
$db->quoteName('version_id')))
->values($eid . ', ' . $db->quote(end($files)));
$db->setQuery($query);
$db->execute();
}
}
}
}
return $update_count;
}
/**
* Method to parse through a files element of the installation manifest
and take appropriate
* action.
*
* @param \SimpleXMLElement $element The XML node to process
* @param integer $cid Application ID of application to
install to
* @param array $oldFiles List of old files
(SimpleXMLElement's)
* @param array $oldMD5 List of old MD5 sums (indexed by
filename with value as MD5)
*
* @return boolean True on success
*
* @since 3.1
*/
public function parseFiles(\SimpleXMLElement $element, $cid = 0, $oldFiles
= null, $oldMD5 = null)
{
// Get the array of file nodes to process; we checked whether this had
children above.
if (!$element || !count($element->children()))
{
// Either the tag does not exist or has no children (hence no files to
process) therefore we return zero files processed.
return 0;
}
$copyfiles = array();
// Get the client info
$client = ApplicationHelper::getClientInfo($cid);
/*
* Here we set the folder we are going to remove the files from.
*/
if ($client)
{
$pathname = 'extension_' . $client->name;
$destination = $this->getPath($pathname);
}
else
{
$pathname = 'extension_root';
$destination = $this->getPath($pathname);
}
/*
* Here we set the folder we are going to copy the files from.
*
* Does the element have a folder attribute?
*
* If so this indicates that the files are in a subdirectory of the
source
* folder and we should append the folder attribute to the source path
when
* copying files.
*/
$folder = (string) $element->attributes()->folder;
if ($folder && file_exists($this->getPath('source')
. '/' . $folder))
{
$source = $this->getPath('source') . '/' .
$folder;
}
else
{
$source = $this->getPath('source');
}
// Work out what files have been deleted
if ($oldFiles && ($oldFiles instanceof \SimpleXMLElement))
{
$oldEntries = $oldFiles->children();
if (count($oldEntries))
{
$deletions = $this->findDeletedFiles($oldEntries,
$element->children());
foreach ($deletions['folders'] as $deleted_folder)
{
\JFolder::delete($destination . '/' . $deleted_folder);
}
foreach ($deletions['files'] as $deleted_file)
{
\JFile::delete($destination . '/' . $deleted_file);
}
}
}
$path = array();
// Copy the MD5SUMS file if it exists
if (file_exists($source . '/MD5SUMS'))
{
$path['src'] = $source . '/MD5SUMS';
$path['dest'] = $destination . '/MD5SUMS';
$path['type'] = 'file';
$copyfiles[] = $path;
}
// Process each file in the $files array (children of $tagName).
foreach ($element->children() as $file)
{
$path['src'] = $source . '/' . $file;
$path['dest'] = $destination . '/' . $file;
// Is this path a file or folder?
$path['type'] = $file->getName() === 'folder' ?
'folder' : 'file';
/*
* Before we can add a file to the copyfiles array we need to ensure
* that the folder we are copying our file to exits and if it
doesn't,
* we need to create it.
*/
if (basename($path['dest']) !== $path['dest'])
{
$newdir = dirname($path['dest']);
if (!\JFolder::create($newdir))
{
\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_CREATE_DIRECTORY',
$newdir), \JLog::WARNING, 'jerror');
return false;
}
}
// Add the file to the copyfiles array
$copyfiles[] = $path;
}
return $this->copyFiles($copyfiles);
}
/**
* Method to parse through a languages element of the installation
manifest and take appropriate
* action.
*
* @param \SimpleXMLElement $element The XML node to process
* @param integer $cid Application ID of application to
install to
*
* @return boolean True on success
*
* @since 3.1
*/
public function parseLanguages(\SimpleXMLElement $element, $cid = 0)
{
// TODO: work out why the below line triggers 'node no longer
exists' errors with files
if (!$element || !count($element->children()))
{
// Either the tag does not exist or has no children therefore we return
zero files processed.
return 0;
}
$copyfiles = array();
// Get the client info
$client = ApplicationHelper::getClientInfo($cid);
// Here we set the folder we are going to copy the files to.
// 'languages' Files are copied to JPATH_BASE/language/ folder
$destination = $client->path . '/language';
/*
* Here we set the folder we are going to copy the files from.
*
* Does the element have a folder attribute?
*
* If so this indicates that the files are in a subdirectory of the
source
* folder and we should append the folder attribute to the source path
when
* copying files.
*/
$folder = (string) $element->attributes()->folder;
if ($folder && file_exists($this->getPath('source')
. '/' . $folder))
{
$source = $this->getPath('source') . '/' .
$folder;
}
else
{
$source = $this->getPath('source');
}
// Process each file in the $files array (children of $tagName).
foreach ($element->children() as $file)
{
/*
* Language files go in a subfolder based on the language code, ie.
* <language
tag="en-US">en-US.mycomponent.ini</language>
* would go in the en-US subdirectory of the language folder.
*/
// We will only install language files where a core language pack
// already exists.
if ((string) $file->attributes()->tag !== '')
{
$path['src'] = $source . '/' . $file;
if ((string) $file->attributes()->client !== '')
{
// Override the client
$langclient = ApplicationHelper::getClientInfo((string)
$file->attributes()->client, true);
$path['dest'] = $langclient->path .
'/language/' . $file->attributes()->tag . '/' .
basename((string) $file);
}
else
{
// Use the default client
$path['dest'] = $destination . '/' .
$file->attributes()->tag . '/' . basename((string) $file);
}
// If the language folder is not present, then the core pack
hasn't been installed... ignore
if (!\JFolder::exists(dirname($path['dest'])))
{
continue;
}
}
else
{
$path['src'] = $source . '/' . $file;
$path['dest'] = $destination . '/' . $file;
}
/*
* Before we can add a file to the copyfiles array we need to ensure
* that the folder we are copying our file to exits and if it
doesn't,
* we need to create it.
*/
if (basename($path['dest']) !== $path['dest'])
{
$newdir = dirname($path['dest']);
if (!\JFolder::create($newdir))
{
\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_CREATE_DIRECTORY',
$newdir), \JLog::WARNING, 'jerror');
return false;
}
}
// Add the file to the copyfiles array
$copyfiles[] = $path;
}
return $this->copyFiles($copyfiles);
}
/**
* Method to parse through a media element of the installation manifest
and take appropriate
* action.
*
* @param \SimpleXMLElement $element The XML node to process
* @param integer $cid Application ID of application to
install to
*
* @return boolean True on success
*
* @since 3.1
*/
public function parseMedia(\SimpleXMLElement $element, $cid = 0)
{
if (!$element || !count($element->children()))
{
// Either the tag does not exist or has no children therefore we return
zero files processed.
return 0;
}
$copyfiles = array();
// Here we set the folder we are going to copy the files to.
// Default 'media' Files are copied to the JPATH_BASE/media
folder
$folder = ((string) $element->attributes()->destination) ?
'/' . $element->attributes()->destination : null;
$destination = \JPath::clean(JPATH_ROOT . '/media' . $folder);
// Here we set the folder we are going to copy the files from.
/*
* Does the element have a folder attribute?
* If so this indicates that the files are in a subdirectory of the
source
* folder and we should append the folder attribute to the source path
when
* copying files.
*/
$folder = (string) $element->attributes()->folder;
if ($folder && file_exists($this->getPath('source')
. '/' . $folder))
{
$source = $this->getPath('source') . '/' .
$folder;
}
else
{
$source = $this->getPath('source');
}
// Process each file in the $files array (children of $tagName).
foreach ($element->children() as $file)
{
$path['src'] = $source . '/' . $file;
$path['dest'] = $destination . '/' . $file;
// Is this path a file or folder?
$path['type'] = $file->getName() === 'folder' ?
'folder' : 'file';
/*
* Before we can add a file to the copyfiles array we need to ensure
* that the folder we are copying our file to exits and if it
doesn't,
* we need to create it.
*/
if (basename($path['dest']) !== $path['dest'])
{
$newdir = dirname($path['dest']);
if (!\JFolder::create($newdir))
{
\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_CREATE_DIRECTORY',
$newdir), \JLog::WARNING, 'jerror');
return false;
}
}
// Add the file to the copyfiles array
$copyfiles[] = $path;
}
return $this->copyFiles($copyfiles);
}
/**
* Method to parse the parameters of an extension, build the JSON string
for its default parameters, and return the JSON string.
*
* @return string JSON string of parameter values
*
* @since 3.1
* @note This method must always return a JSON compliant string
*/
public function getParams()
{
// Validate that we have a fieldset to use
if (!isset($this->manifest->config->fields->fieldset))
{
return '{}';
}
// Getting the fieldset tags
$fieldsets = $this->manifest->config->fields->fieldset;
// Creating the data collection variable:
$ini = array();
// Iterating through the fieldsets:
foreach ($fieldsets as $fieldset)
{
if (!count($fieldset->children()))
{
// Either the tag does not exist or has no children therefore we return
zero files processed.
return '{}';
}
// Iterating through the fields and collecting the name/default values:
foreach ($fieldset as $field)
{
// Check against the null value since otherwise default values like
"0"
// cause entire parameters to be skipped.
if (($name = $field->attributes()->name) === null)
{
continue;
}
if (($value = $field->attributes()->default) === null)
{
continue;
}
$ini[(string) $name] = (string) $value;
}
}
return json_encode($ini);
}
/**
* Copyfiles
*
* Copy files from source directory to the target directory
*
* @param array $files Array with filenames
* @param boolean $overwrite True if existing files can be replaced
*
* @return boolean True on success
*
* @since 3.1
*/
public function copyFiles($files, $overwrite = null)
{
/*
* To allow for manual override on the overwriting flag, we check to see
if
* the $overwrite flag was set and is a boolean value. If not, use the
object
* allowOverwrite flag.
*/
if ($overwrite === null || !is_bool($overwrite))
{
$overwrite = $this->overwrite;
}
/*
* $files must be an array of filenames. Verify that it is an array with
* at least one file to copy.
*/
if (is_array($files) && count($files) > 0)
{
foreach ($files as $file)
{
// Get the source and destination paths
$filesource = \JPath::clean($file['src']);
$filedest = \JPath::clean($file['dest']);
$filetype = array_key_exists('type', $file) ?
$file['type'] : 'file';
if (!file_exists($filesource))
{
/*
* The source file does not exist. Nothing to copy so set an error
* and return false.
*/
\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_NO_FILE',
$filesource), \JLog::WARNING, 'jerror');
return false;
}
elseif (($exists = file_exists($filedest)) && !$overwrite)
{
// It's okay if the manifest already exists
if ($this->getPath('manifest') === $filesource)
{
continue;
}
// The destination file already exists and the overwrite flag is
false.
// Set an error and return false.
\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_FILE_EXISTS',
$filedest), \JLog::WARNING, 'jerror');
return false;
}
else
{
// Copy the folder or file to the new location.
if ($filetype === 'folder')
{
if (!\JFolder::copy($filesource, $filedest, null, $overwrite))
{
\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_FAIL_COPY_FOLDER',
$filesource, $filedest), \JLog::WARNING, 'jerror');
return false;
}
$step = array('type' => 'folder',
'path' => $filedest);
}
else
{
if (!\JFile::copy($filesource, $filedest, null))
{
\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_FAIL_COPY_FILE',
$filesource, $filedest), \JLog::WARNING, 'jerror');
// In 3.2, TinyMCE language handling changed. Display a special
notice in case an older language pack is installed.
if (strpos($filedest,
'media/editors/tinymce/jscripts/tiny_mce/langs'))
{
\JLog::add(\JText::_('JLIB_INSTALLER_NOT_ERROR'),
\JLog::WARNING, 'jerror');
}
return false;
}
$step = array('type' => 'file',
'path' => $filedest);
}
/*
* Since we copied a file/folder, we want to add it to the
installation step stack so that
* in case we have to roll back the installation we can remove the
files copied.
*/
if (!$exists)
{
$this->stepStack[] = $step;
}
}
}
}
else
{
// The $files variable was either not an array or an empty array
return false;
}
return count($files);
}
/**
* Method to parse through a files element of the installation manifest
and remove
* the files that were installed
*
* @param object $element The XML node to process
* @param integer $cid Application ID of application to remove
from
*
* @return boolean True on success
*
* @since 3.1
*/
public function removeFiles($element, $cid = 0)
{
if (!$element || !count($element->children()))
{
// Either the tag does not exist or has no children therefore we return
zero files processed.
return true;
}
$retval = true;
// Get the client info if we're using a specific client
if ($cid > -1)
{
$client = ApplicationHelper::getClientInfo($cid);
}
else
{
$client = null;
}
// Get the array of file nodes to process
$files = $element->children();
if (count($files) === 0)
{
// No files to process
return true;
}
$folder = '';
/*
* Here we set the folder we are going to remove the files from. There
are a few
* special cases that need to be considered for certain reserved tags.
*/
switch ($element->getName())
{
case 'media':
if ((string) $element->attributes()->destination)
{
$folder = (string) $element->attributes()->destination;
}
else
{
$folder = '';
}
$source = $client->path . '/media/' . $folder;
break;
case 'languages':
$lang_client = (string) $element->attributes()->client;
if ($lang_client)
{
$client = ApplicationHelper::getClientInfo($lang_client, true);
$source = $client->path . '/language';
}
else
{
if ($client)
{
$source = $client->path . '/language';
}
else
{
$source = '';
}
}
break;
default:
if ($client)
{
$pathname = 'extension_' . $client->name;
$source = $this->getPath($pathname);
}
else
{
$pathname = 'extension_root';
$source = $this->getPath($pathname);
}
break;
}
// Process each file in the $files array (children of $tagName).
foreach ($files as $file)
{
/*
* If the file is a language, we must handle it differently. Language
files
* go in a subdirectory based on the language code, ie.
* <language
tag="en_US">en_US.mycomponent.ini</language>
* would go in the en_US subdirectory of the languages directory.
*/
if ($file->getName() === 'language' && (string)
$file->attributes()->tag !== '')
{
if ($source)
{
$path = $source . '/' . $file->attributes()->tag .
'/' . basename((string) $file);
}
else
{
$target_client = ApplicationHelper::getClientInfo((string)
$file->attributes()->client, true);
$path = $target_client->path . '/language/' .
$file->attributes()->tag . '/' . basename((string) $file);
}
// If the language folder is not present, then the core pack
hasn't been installed... ignore
if (!\JFolder::exists(dirname($path)))
{
continue;
}
}
else
{
$path = $source . '/' . $file;
}
// Actually delete the files/folders
if (is_dir($path))
{
$val = \JFolder::delete($path);
}
else
{
$val = \JFile::delete($path);
}
if ($val === false)
{
\JLog::add('Failed to delete ' . $path, \JLog::WARNING,
'jerror');
$retval = false;
}
}
if (!empty($folder))
{
\JFolder::delete($source);
}
return $retval;
}
/**
* Copies the installation manifest file to the extension folder in the
given client
*
* @param integer $cid Where to copy the installfile [optional:
defaults to 1 (admin)]
*
* @return boolean True on success, False on error
*
* @since 3.1
*/
public function copyManifest($cid = 1)
{
// Get the client info
$client = ApplicationHelper::getClientInfo($cid);
$path['src'] = $this->getPath('manifest');
if ($client)
{
$pathname = 'extension_' . $client->name;
$path['dest'] = $this->getPath($pathname) . '/' .
basename($this->getPath('manifest'));
}
else
{
$pathname = 'extension_root';
$path['dest'] = $this->getPath($pathname) . '/' .
basename($this->getPath('manifest'));
}
return $this->copyFiles(array($path), true);
}
/**
* Tries to find the package manifest file
*
* @return boolean True on success, False on error
*
* @since 3.1
*/
public function findManifest()
{
// Do nothing if folder does not exist for some reason
if (!\JFolder::exists($this->getPath('source')))
{
return false;
}
// Main folder manifests (higher priority)
$parentXmlfiles = \JFolder::files($this->getPath('source'),
'.xml$', false, true);
// Search for children manifests (lower priority)
$allXmlFiles = \JFolder::files($this->getPath('source'),
'.xml$', 1, true);
// Create an unique array of files ordered by priority
$xmlfiles = array_unique(array_merge($parentXmlfiles, $allXmlFiles));
// If at least one XML file exists
if (!empty($xmlfiles))
{
foreach ($xmlfiles as $file)
{
// Is it a valid Joomla installation manifest file?
$manifest = $this->isManifest($file);
if ($manifest !== null)
{
// If the root method attribute is set to upgrade, allow file
overwrite
if ((string) $manifest->attributes()->method ===
'upgrade')
{
$this->upgrade = true;
$this->overwrite = true;
}
// If the overwrite option is set, allow file overwriting
if ((string) $manifest->attributes()->overwrite ===
'true')
{
$this->overwrite = true;
}
// Set the manifest object and path
$this->manifest = $manifest;
$this->setPath('manifest', $file);
// Set the installation source path to that of the manifest file
$this->setPath('source', dirname($file));
return true;
}
}
// None of the XML files found were valid install files
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_NOTFINDJOOMLAXMLSETUPFILE'),
\JLog::WARNING, 'jerror');
return false;
}
else
{
// No XML files were found in the install folder
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_NOTFINDXMLSETUPFILE'),
\JLog::WARNING, 'jerror');
return false;
}
}
/**
* Is the XML file a valid Joomla installation manifest file.
*
* @param string $file An xmlfile path to check
*
* @return \SimpleXMLElement|null A \SimpleXMLElement, or null if the
file failed to parse
*
* @since 3.1
*/
public function isManifest($file)
{
$xml = simplexml_load_file($file);
// If we cannot load the XML file return null
if (!$xml)
{
return;
}
// Check for a valid XML root tag.
if ($xml->getName() !== 'extension')
{
return;
}
// Valid manifest file return the object
return $xml;
}
/**
* Generates a manifest cache
*
* @return string serialised manifest data
*
* @since 3.1
*/
public function generateManifestCache()
{
return
json_encode(self::parseXMLInstallFile($this->getPath('manifest')));
}
/**
* Cleans up discovered extensions if they're being installed some
other way
*
* @param string $type The type of extension (component, etc)
* @param string $element Unique element identifier (e.g.
com_content)
* @param string $folder The folder of the extension (plugins; e.g.
system)
* @param integer $client The client application (administrator or
site)
*
* @return object Result of query
*
* @since 3.1
*/
public function cleanDiscoveredExtension($type, $element, $folder =
'', $client = 0)
{
$db = \JFactory::getDbo();
$query = $db->getQuery(true)
->delete($db->quoteName('#__extensions'))
->where('type = ' . $db->quote($type))
->where('element = ' . $db->quote($element))
->where('folder = ' . $db->quote($folder))
->where('client_id = ' . (int) $client)
->where('state = -1');
$db->setQuery($query);
return $db->execute();
}
/**
* Compares two "files" entries to find deleted files/folders
*
* @param array $oldFiles An array of \SimpleXMLElement objects that
are the old files
* @param array $newFiles An array of \SimpleXMLElement objects that
are the new files
*
* @return array An array with the delete files and folders in
findDeletedFiles[files] and findDeletedFiles[folders] respectively
*
* @since 3.1
*/
public function findDeletedFiles($oldFiles, $newFiles)
{
// The magic find deleted files function!
// The files that are new
$files = array();
// The folders that are new
$folders = array();
// The folders of the files that are new
$containers = array();
// A list of files to delete
$files_deleted = array();
// A list of folders to delete
$folders_deleted = array();
foreach ($newFiles as $file)
{
switch ($file->getName())
{
case 'folder':
// Add any folders to the list
$folders[] = (string) $file; // add any folders to the list
break;
case 'file':
default:
// Add any files to the list
$files[] = (string) $file;
// Now handle the folder part of the file to ensure we get any
containers
// Break up the parts of the directory
$container_parts = explode('/', dirname((string) $file));
// Make sure this is clean and empty
$container = '';
foreach ($container_parts as $part)
{
// Iterate through each part
// Add a slash if its not empty
if (!empty($container))
{
$container .= '/';
}
// Aappend the folder part
$container .= $part;
if (!in_array($container, $containers))
{
// Add the container if it doesn't already exist
$containers[] = $container;
}
}
break;
}
}
foreach ($oldFiles as $file)
{
switch ($file->getName())
{
case 'folder':
if (!in_array((string) $file, $folders))
{
// See whether the folder exists in the new list
if (!in_array((string) $file, $containers))
{
// Check if the folder exists as a container in the new list
// If it's not in the new list or a container then delete it
$folders_deleted[] = (string) $file;
}
}
break;
case 'file':
default:
if (!in_array((string) $file, $files))
{
// Look if the file exists in the new list
if (!in_array(dirname((string) $file), $folders))
{
// Look if the file is now potentially in a folder
$files_deleted[] = (string) $file; // not in a folder, doesn't
exist, wipe it out!
}
}
break;
}
}
return array('files' => $files_deleted, 'folders'
=> $folders_deleted);
}
/**
* Loads an MD5SUMS file into an associative array
*
* @param string $filename Filename to load
*
* @return array Associative array with filenames as the index and the
MD5 as the value
*
* @since 3.1
*/
public function loadMD5Sum($filename)
{
if (!file_exists($filename))
{
// Bail if the file doesn't exist
return false;
}
$data = file($filename, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
$retval = array();
foreach ($data as $row)
{
// Split up the data
$results = explode(' ', $row);
// Cull any potential prefix
$results[1] = str_replace('./', '', $results[1]);
// Throw into the array
$retval[$results[1]] = $results[0];
}
return $retval;
}
/**
* Parse a XML install manifest file.
*
* XML Root tag should be 'install' except for languages which
use meta file.
*
* @param string $path Full path to XML file.
*
* @return array XML metadata.
*
* @since 3.0.0
*/
public static function parseXMLInstallFile($path)
{
// Check if xml file exists.
if (!file_exists($path))
{
return false;
}
// Read the file to see if it's a valid component XML file
$xml = simplexml_load_file($path);
if (!$xml)
{
return false;
}
// Check for a valid XML root tag.
// Extensions use 'extension' as the root tag. Languages use
'metafile' instead
$name = $xml->getName();
if ($name !== 'extension' && $name !==
'metafile')
{
unset($xml);
return false;
}
$data = array();
$data['name'] = (string) $xml->name;
// Check if we're a language. If so use metafile.
$data['type'] = $xml->getName() === 'metafile' ?
'language' : (string) $xml->attributes()->type;
$data['creationDate'] = ((string) $xml->creationDate) ?:
\JText::_('JLIB_UNKNOWN');
$data['author'] = ((string) $xml->author) ?:
\JText::_('JLIB_UNKNOWN');
$data['copyright'] = (string) $xml->copyright;
$data['authorEmail'] = (string) $xml->authorEmail;
$data['authorUrl'] = (string) $xml->authorUrl;
$data['version'] = (string) $xml->version;
$data['description'] = (string) $xml->description;
$data['group'] = (string) $xml->group;
if ($xml->files && count($xml->files->children()))
{
$filename = \JFile::getName($path);
$data['filename'] = \JFile::stripExt($filename);
foreach ($xml->files->children() as $oneFile)
{
if ((string) $oneFile->attributes()->plugin)
{
$data['filename'] = (string)
$oneFile->attributes()->plugin;
break;
}
}
}
return $data;
}
/**
* Fetches an adapter and adds it to the internal storage if an instance
is not set
* while also ensuring its a valid adapter name
*
* @param string $name Name of adapter to return
* @param array $options Adapter options
*
* @return InstallerAdapter
*
* @since 3.4
* @deprecated 4.0 The internal adapter cache will no longer be
supported,
* use loadAdapter() to fetch an adapter instance
*/
public function getAdapter($name, $options = array())
{
$this->getAdapters($options);
if (!$this->setAdapter($name, $this->_adapters[$name]))
{
return false;
}
return $this->_adapters[$name];
}
/**
* Gets a list of available install adapters.
*
* @param array $options An array of options to inject into the
adapter
* @param array $custom Array of custom install adapters
*
* @return array An array of available install adapters.
*
* @since 3.4
* @note As of 4.0, this method will only return the names of available
adapters and will not
* instantiate them and store to the $_adapters class var.
*/
public function getAdapters($options = array(), array $custom = array())
{
$files = new \DirectoryIterator($this->_basepath . '/' .
$this->_adapterfolder);
// Process the core adapters
foreach ($files as $file)
{
$fileName = $file->getFilename();
// Only load for php files.
if (!$file->isFile() || $file->getExtension() !== 'php')
{
continue;
}
// Derive the class name from the filename.
$name = str_ireplace('.php', '', trim($fileName));
$name = str_ireplace('adapter', '', trim($name));
$class = rtrim($this->_classprefix, '\\') . '\\'
. ucfirst($name) . 'Adapter';
if (!class_exists($class))
{
// Not namespaced
$class = $this->_classprefix . ucfirst($name);
}
// Core adapters should autoload based on classname, keep this fallback
just in case
if (!class_exists($class))
{
// Try to load the adapter object
\JLoader::register($class, $this->_basepath . '/' .
$this->_adapterfolder . '/' . $fileName);
if (!class_exists($class))
{
// Skip to next one
continue;
}
}
$this->_adapters[strtolower($name)] = $this->loadAdapter($name,
$options);
}
// Add any custom adapters if specified
if (count($custom) >= 1)
{
foreach ($custom as $adapter)
{
// Setup the class name
// TODO - Can we abstract this to not depend on the Joomla class
namespace without PHP namespaces?
$class = $this->_classprefix . ucfirst(trim($adapter));
// If the class doesn't exist we have nothing left to do but look
at the next type. We did our best.
if (!class_exists($class))
{
continue;
}
$this->_adapters[$name] = $this->loadAdapter($name, $options);
}
}
return $this->_adapters;
}
/**
* Method to load an adapter instance
*
* @param string $adapter Adapter name
* @param array $options Adapter options
*
* @return InstallerAdapter
*
* @since 3.4
* @throws \InvalidArgumentException
*/
public function loadAdapter($adapter, $options = array())
{
$class = rtrim($this->_classprefix, '\\') . '\\' .
ucfirst($adapter) . 'Adapter';
if (!class_exists($class))
{
// Not namespaced
$class = $this->_classprefix . ucfirst($adapter);
}
if (!class_exists($class))
{
// @deprecated 4.0 - The adapter should be autoloaded or manually
included by the caller
$path = $this->_basepath . '/' . $this->_adapterfolder .
'/' . $adapter . '.php';
// Try to load the adapter object
if (!file_exists($path))
{
throw new \InvalidArgumentException(sprintf('The %s install
adapter does not exist.', $adapter));
}
// Try once more to find the class
\JLoader::register($class, $path);
if (!class_exists($class))
{
throw new \InvalidArgumentException(sprintf('The %s install
adapter does not exist.', $adapter));
}
}
// Ensure the adapter type is part of the options array
$options['type'] = $adapter;
return new $class($this, $this->getDbo(), $options);
}
/**
* Loads all adapters.
*
* @param array $options Adapter options
*
* @return void
*
* @since 3.4
* @deprecated 4.0 Individual adapters should be instantiated as needed
* @note This method is serving as a proxy of the legacy \JAdapter
API into the preferred API
*/
public function loadAllAdapters($options = array())
{
$this->getAdapters($options);
}
}
PKK�[��o�a�aInstallerAdapter.phpnu�[���<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Installer;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Installer\Manifest\PackageManifest;
use Joomla\CMS\Table\Extension;
use Joomla\CMS\Table\Table;
use Joomla\CMS\Table\TableInterface;
\JLoader::import('joomla.base.adapterinstance');
/**
* Abstract adapter for the installer.
*
* @method Installer getParent() Retrieves the parent object.
* @property-read Installer $parent Parent object
*
* @since 3.4
* @note As of 4.0, this class will no longer extend from
JAdapterInstance
*/
abstract class InstallerAdapter extends \JAdapterInstance
{
/**
* ID for the currently installed extension if present
*
* @var integer
* @since 3.4
*/
protected $currentExtensionId = null;
/**
* The unique identifier for the extension (e.g. mod_login)
*
* @var string
* @since 3.4
* */
protected $element = null;
/**
* Extension object.
*
* @var Extension
* @since 3.4
* */
protected $extension = null;
/**
* Messages rendered by custom scripts
*
* @var string
* @since 3.4
*/
protected $extensionMessage = '';
/**
* Copy of the XML manifest file.
*
* Making this object public allows extensions to customize the manifest
in custom scripts.
*
* @var string
* @since 3.4
*/
public $manifest = null;
/**
* A path to the PHP file that the scriptfile declaration in the manifest
refers to.
*
* @var string
* @since 3.4
*/
protected $manifest_script = null;
/**
* Name of the extension
*
* @var string
* @since 3.4
*/
protected $name = null;
/**
* Install function routing
*
* @var string
* @since 3.4
*/
protected $route = 'install';
/**
* Flag if the adapter supports discover installs
*
* Adapters should override this and set to false if discover install is
unsupported
*
* @var boolean
* @since 3.4
*/
protected $supportsDiscoverInstall = true;
/**
* The type of adapter in use
*
* @var string
* @since 3.4
*/
protected $type;
/**
* Constructor
*
* @param Installer $parent Parent object
* @param \JDatabaseDriver $db Database object
* @param array $options Configuration Options
*
* @since 3.4
*/
public function __construct(Installer $parent, \JDatabaseDriver $db, array
$options = array())
{
parent::__construct($parent, $db, $options);
// Get a generic TableExtension instance for use if not already loaded
if (!($this->extension instanceof TableInterface))
{
$this->extension = Table::getInstance('extension');
}
// Sanity check, make sure the type is set by taking the adapter name
from the class name
if (!$this->type)
{
// This assumes the adapter short class name in its namespace is
`<foo>Adapter`, replace this logic in subclasses if needed
$reflection = new \ReflectionClass(get_called_class());
$this->type = str_replace('Adapter', '',
$reflection->getShortName());
}
// Extension type is stored as lowercase in the database
$this->type = strtolower($this->type);
}
/**
* Check if a package extension allows its child extensions to be
uninstalled individually
*
* @param integer $packageId The extension ID of the package to check
*
* @return boolean
*
* @since 3.7.0
* @note This method defaults to true to emulate the behavior of 3.6
and earlier which did not support this lookup
*/
protected function canUninstallPackageChild($packageId)
{
$package = Table::getInstance('extension');
// If we can't load this package ID, we have a corrupt database
if (!$package->load((int) $packageId))
{
return true;
}
$manifestFile = JPATH_MANIFESTS . '/packages/' .
$package->element . '.xml';
$xml = $this->parent->isManifest($manifestFile);
// If the manifest doesn't exist, we've got some major issues
if (!$xml)
{
return true;
}
$manifest = new PackageManifest($manifestFile);
return $manifest->blockChildUninstall === false;
}
/**
* Method to check if the extension is already present in the database
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function checkExistingExtension()
{
try
{
$this->currentExtensionId = $this->extension->find(
array('element' => $this->element, 'type'
=> $this->type)
);
// If it does exist, load it
if ($this->currentExtensionId)
{
$this->extension->load(array('element' =>
$this->element, 'type' => $this->type));
}
}
catch (\RuntimeException $e)
{
// Install failed, roll back changes
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_ROLLBACK',
\JText::_('JLIB_INSTALLER_' . $this->route),
$e->getMessage()
),
$e->getCode(),
$e
);
}
}
/**
* Method to check if the extension is present in the filesystem, flags
the route as update if so
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function checkExtensionInFilesystem()
{
if (file_exists($this->parent->getPath('extension_root'))
&& (!$this->parent->isOverwrite() ||
$this->parent->isUpgrade()))
{
// Look for an update function or update tag
$updateElement = $this->getManifest()->update;
// Upgrade manually set or update function available or update tag
detected
if ($updateElement || $this->parent->isUpgrade()
|| ($this->parent->manifestClass &&
method_exists($this->parent->manifestClass, 'update')))
{
// Force this one
$this->parent->setOverwrite(true);
$this->parent->setUpgrade(true);
if ($this->currentExtensionId)
{
// If there is a matching extension mark this as an update
$this->setRoute('update');
}
}
elseif (!$this->parent->isOverwrite())
{
// We didn't have overwrite set, find an update function or find
an update tag so lets call it safe
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_DIRECTORY',
\JText::_('JLIB_INSTALLER_' . $this->route),
$this->type,
$this->parent->getPath('extension_root')
)
);
}
}
}
/**
* Method to copy the extension's base files from the `<files>`
tag(s) and the manifest file
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
abstract protected function copyBaseFiles();
/**
* Method to create the extension root path if necessary
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function createExtensionRoot()
{
// If the extension directory does not exist, lets create it
$created = false;
if
(!file_exists($this->parent->getPath('extension_root')))
{
if (!$created =
\JFolder::create($this->parent->getPath('extension_root')))
{
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_CREATE_DIRECTORY',
\JText::_('JLIB_INSTALLER_' . $this->route),
$this->parent->getPath('extension_root')
)
);
}
}
/*
* Since we created the extension directory and will want to remove it if
* we have to roll back the installation, let's add it to the
* installation step stack
*/
if ($created)
{
$this->parent->pushStep(
array(
'type' => 'folder',
'path' =>
$this->parent->getPath('extension_root'),
)
);
}
}
/**
* Generic discover_install method for extensions
*
* @return boolean True on success
*
* @since 3.4
*/
public function discover_install()
{
// Get the extension's description
$description = (string) $this->getManifest()->description;
if ($description)
{
$this->parent->message = \JText::_($description);
}
else
{
$this->parent->message = '';
}
// Set the extension's name and element
$this->name = $this->getName();
$this->element = $this->getElement();
/*
*
---------------------------------------------------------------------------------------------
* Extension Precheck and Setup Section
*
---------------------------------------------------------------------------------------------
*/
// Setup the install paths and perform other prechecks as necessary
try
{
$this->setupInstallPaths();
}
catch (\RuntimeException $e)
{
// Install failed, roll back changes
$this->parent->abort($e->getMessage());
return false;
}
/*
*
---------------------------------------------------------------------------------------------
* Installer Trigger Loading
*
---------------------------------------------------------------------------------------------
*/
$this->setupScriptfile();
try
{
$this->triggerManifestScript('preflight');
}
catch (\RuntimeException $e)
{
// Install failed, roll back changes
$this->parent->abort($e->getMessage());
return false;
}
/*
*
---------------------------------------------------------------------------------------------
* Database Processing Section
*
---------------------------------------------------------------------------------------------
*/
try
{
$this->storeExtension();
}
catch (\RuntimeException $e)
{
// Install failed, roll back changes
$this->parent->abort($e->getMessage());
return false;
}
try
{
$this->parseQueries();
}
catch (\RuntimeException $e)
{
// Install failed, roll back changes
$this->parent->abort($e->getMessage());
return false;
}
// Run the custom install method
try
{
$this->triggerManifestScript('install');
}
catch (\RuntimeException $e)
{
// Install failed, roll back changes
$this->parent->abort($e->getMessage());
return false;
}
/*
*
---------------------------------------------------------------------------------------------
* Finalization and Cleanup Section
*
---------------------------------------------------------------------------------------------
*/
try
{
$this->finaliseInstall();
}
catch (\RuntimeException $e)
{
// Install failed, roll back changes
$this->parent->abort($e->getMessage());
return false;
}
// And now we run the postflight
try
{
$this->triggerManifestScript('postflight');
}
catch (\RuntimeException $e)
{
// Install failed, roll back changes
$this->parent->abort($e->getMessage());
return false;
}
return $this->extension->extension_id;
}
/**
* Method to handle database transactions for the installer
*
* @return boolean True on success
*
* @since 3.4
* @throws \RuntimeException
*/
protected function doDatabaseTransactions()
{
$route = $this->route === 'discover_install' ?
'install' : $this->route;
// Let's run the install queries for the component
if (isset($this->getManifest()->{$route}->sql))
{
$result =
$this->parent->parseSQLFiles($this->getManifest()->{$route}->sql);
if ($result === false)
{
// Only rollback if installing
if ($route === 'install')
{
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_SQL_ERROR',
\JText::_('JLIB_INSTALLER_' .
strtoupper($this->route)),
$this->parent->getDbo()->stderr(true)
)
);
}
return false;
}
// If installing with success and there is an uninstall script, add an
installer rollback step to rollback if needed
if ($route === 'install' &&
isset($this->getManifest()->uninstall->sql))
{
$this->parent->pushStep(array('type' =>
'query', 'script' =>
$this->getManifest()->uninstall->sql));
}
}
return true;
}
/**
* Load language files
*
* @param string $extension The name of the extension
* @param string $source Path to the extension
* @param string $base Base path for the extension language
*
* @return void
*
* @since 3.4
*/
protected function doLoadLanguage($extension, $source, $base =
JPATH_ADMINISTRATOR)
{
$lang = \JFactory::getLanguage();
$lang->load($extension . '.sys', $source, null, false, true)
|| $lang->load($extension . '.sys', $base, null, false, true);
}
/**
* Checks if the adapter supports discover_install
*
* @return boolean
*
* @since 3.4
*/
public function getDiscoverInstallSupported()
{
return $this->supportsDiscoverInstall;
}
/**
* Get the filtered extension element from the manifest
*
* @param string $element Optional element name to be converted
*
* @return string The filtered element
*
* @since 3.4
*/
public function getElement($element = null)
{
if (!$element)
{
// Ensure the element is a string
$element = (string) $this->getManifest()->element;
}
if (!$element)
{
$element = $this->getName();
}
// Filter the name for illegal characters
return strtolower(\JFilterInput::getInstance()->clean($element,
'cmd'));
}
/**
* Get the manifest object.
*
* @return \SimpleXMLElement Manifest object
*
* @since 3.4
*/
public function getManifest()
{
return $this->manifest;
}
/**
* Get the filtered component name from the manifest
*
* @return string The filtered name
*
* @since 3.4
*/
public function getName()
{
// Ensure the name is a string
$name = (string) $this->getManifest()->name;
// Filter the name for illegal characters
$name = \JFilterInput::getInstance()->clean($name,
'string');
return $name;
}
/**
* Get the install route being followed
*
* @return string The install route
*
* @since 3.4
*/
public function getRoute()
{
return $this->route;
}
/**
* Get the class name for the install adapter script.
*
* @return string The class name.
*
* @since 3.4
*/
protected function getScriptClassName()
{
// Support element names like 'en-GB'
$className = \JFilterInput::getInstance()->clean($this->element,
'cmd') . 'InstallerScript';
// Cannot have - in class names
$className = str_replace('-', '', $className);
return $className;
}
/**
* Generic install method for extensions
*
* @return boolean|integer The extension ID on success, boolean false on
failure
*
* @since 3.4
*/
public function install()
{
// Get the extension's description
$description = (string) $this->getManifest()->description;
if ($description)
{
$this->parent->message = \JText::_($description);
}
else
{
$this->parent->message = '';
}
// Set the extension's name and element
$this->name = $this->getName();
$this->element = $this->getElement();
/*
*
---------------------------------------------------------------------------------------------
* Extension Precheck and Setup Section
*
---------------------------------------------------------------------------------------------
*/
// Setup the install paths and perform other prechecks as necessary
try
{
$this->setupInstallPaths();
}
catch (\RuntimeException $e)
{
// Install failed, roll back changes
$this->parent->abort($e->getMessage());
return false;
}
// Check to see if an extension by the same name is already installed.
try
{
$this->checkExistingExtension();
}
catch (\RuntimeException $e)
{
// Install failed, roll back changes
$this->parent->abort($e->getMessage());
return false;
}
// Check if the extension is present in the filesystem
try
{
$this->checkExtensionInFilesystem();
}
catch (\RuntimeException $e)
{
// Install failed, roll back changes
$this->parent->abort($e->getMessage());
return false;
}
// If we are on the update route, run any custom setup routines
if ($this->route === 'update')
{
try
{
$this->setupUpdates();
}
catch (\RuntimeException $e)
{
// Install failed, roll back changes
$this->parent->abort($e->getMessage());
return false;
}
}
/*
*
---------------------------------------------------------------------------------------------
* Installer Trigger Loading
*
---------------------------------------------------------------------------------------------
*/
$this->setupScriptfile();
try
{
$this->triggerManifestScript('preflight');
}
catch (\RuntimeException $e)
{
// Install failed, roll back changes
$this->parent->abort($e->getMessage());
return false;
}
/*
*
---------------------------------------------------------------------------------------------
* Filesystem Processing Section
*
---------------------------------------------------------------------------------------------
*/
// If the extension directory does not exist, lets create it
try
{
$this->createExtensionRoot();
}
catch (\RuntimeException $e)
{
// Install failed, roll back changes
$this->parent->abort($e->getMessage());
return false;
}
// Copy all necessary files
try
{
$this->copyBaseFiles();
}
catch (\RuntimeException $e)
{
// Install failed, roll back changes
$this->parent->abort($e->getMessage());
return false;
}
// Parse optional tags
$this->parseOptionalTags();
/*
*
---------------------------------------------------------------------------------------------
* Database Processing Section
*
---------------------------------------------------------------------------------------------
*/
try
{
$this->storeExtension();
}
catch (\RuntimeException $e)
{
// Install failed, roll back changes
$this->parent->abort($e->getMessage());
return false;
}
try
{
$this->parseQueries();
}
catch (\RuntimeException $e)
{
// Install failed, roll back changes
$this->parent->abort($e->getMessage());
return false;
}
// Run the custom method based on the route
try
{
$this->triggerManifestScript($this->route);
}
catch (\RuntimeException $e)
{
// Install failed, roll back changes
$this->parent->abort($e->getMessage());
return false;
}
/*
*
---------------------------------------------------------------------------------------------
* Finalization and Cleanup Section
*
---------------------------------------------------------------------------------------------
*/
try
{
$this->finaliseInstall();
}
catch (\RuntimeException $e)
{
// Install failed, roll back changes
$this->parent->abort($e->getMessage());
return false;
}
// And now we run the postflight
try
{
$this->triggerManifestScript('postflight');
}
catch (\RuntimeException $e)
{
// Install failed, roll back changes
$this->parent->abort($e->getMessage());
return false;
}
return $this->extension->extension_id;
}
/**
* Method to parse the queries specified in the `<sql>` tags
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
protected function parseQueries()
{
// Let's run the queries for the extension
if (in_array($this->route, array('install',
'discover_install', 'uninstall')))
{
// This method may throw an exception, but it is caught by the parent
caller
if (!$this->doDatabaseTransactions())
{
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_SQL_ERROR',
\JText::_('JLIB_INSTALLER_' . strtoupper($this->route)),
$this->db->stderr(true)
)
);
}
// Set the schema version to be the latest update version
if ($this->getManifest()->update)
{
$this->parent->setSchemaVersion($this->getManifest()->update->schemas,
$this->extension->extension_id);
}
}
elseif ($this->route === 'update')
{
if ($this->getManifest()->update)
{
$result =
$this->parent->parseSchemaUpdates($this->getManifest()->update->schemas,
$this->extension->extension_id);
if ($result === false)
{
// Install failed, rollback changes
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_SQL_ERROR',
\JText::_('JLIB_INSTALLER_' .
strtoupper($this->route)),
$this->db->stderr(true)
)
);
}
}
}
}
/**
* Method to parse optional tags in the manifest
*
* @return void
*
* @since 3.1
*/
protected function parseOptionalTags()
{
// Some extensions may not have optional tags
}
/**
* Prepares the adapter for a discover_install task
*
* @return void
*
* @since 3.4
*/
public function prepareDiscoverInstall()
{
// Adapters may not support discover install or may have overridden the
default task and aren't using this
}
/**
* Set the manifest object.
*
* @param object $manifest The manifest object
*
* @return InstallerAdapter Instance of this class to support chaining
*
* @since 3.4
*/
public function setManifest($manifest)
{
$this->manifest = $manifest;
return $this;
}
/**
* Set the install route being followed
*
* @param string $route The install route being followed
*
* @return InstallerAdapter Instance of this class to support chaining
*
* @since 3.4
*/
public function setRoute($route)
{
$this->route = $route;
return $this;
}
/**
* Method to do any prechecks and setup the install paths for the
extension
*
* @return void
*
* @since 3.4
*/
abstract protected function setupInstallPaths();
/**
* Setup the manifest script file for those adapters that use it.
*
* @return void
*
* @since 3.4
*/
protected function setupScriptfile()
{
// If there is a manifest class file, lets load it; we'll copy it
later (don't have dest yet)
$manifestScript = (string) $this->getManifest()->scriptfile;
if ($manifestScript)
{
$manifestScriptFile = $this->parent->getPath('source') .
'/' . $manifestScript;
$classname = $this->getScriptClassName();
\JLoader::register($classname, $manifestScriptFile);
if (class_exists($classname))
{
// Create a new instance
$this->parent->manifestClass = new $classname($this);
// And set this so we can copy it later
$this->manifest_script = $manifestScript;
}
}
}
/**
* Method to setup the update routine for the adapter
*
* @return void
*
* @since 3.4
*/
protected function setupUpdates()
{
// Some extensions may not have custom setup routines for updates
}
/**
* Method to store the extension to the database
*
* @return void
*
* @since 3.4
* @throws \RuntimeException
*/
abstract protected function storeExtension();
/**
* Executes a custom install script method
*
* @param string $method The install method to execute
*
* @return boolean True on success
*
* @since 3.4
* @throws \RuntimeException
*/
protected function triggerManifestScript($method)
{
ob_start();
ob_implicit_flush(false);
if ($this->parent->manifestClass &&
method_exists($this->parent->manifestClass, $method))
{
switch ($method)
{
// The preflight and postflight take the route as a param
case 'preflight' :
case 'postflight' :
if ($this->parent->manifestClass->$method($this->route,
$this) === false)
{
if ($method !== 'postflight')
{
// Clean and close the output buffer
ob_end_clean();
// The script failed, rollback changes
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_INSTALL_CUSTOM_INSTALL_FAILURE',
\JText::_('JLIB_INSTALLER_' . $this->route)
)
);
}
}
break;
// The install, uninstall, and update methods only pass this object as
a param
case 'install' :
case 'uninstall' :
case 'update' :
if ($this->parent->manifestClass->$method($this) === false)
{
if ($method !== 'uninstall')
{
// Clean and close the output buffer
ob_end_clean();
// The script failed, rollback changes
throw new \RuntimeException(
\JText::sprintf(
'JLIB_INSTALLER_ABORT_INSTALL_CUSTOM_INSTALL_FAILURE',
\JText::_('JLIB_INSTALLER_' . $this->route)
)
);
}
}
break;
}
}
// Append to the message object
$this->extensionMessage .= ob_get_clean();
// If in postflight or uninstall, set the message for display
if (($method === 'uninstall' || $method ===
'postflight') && $this->extensionMessage !==
'')
{
$this->parent->set('extension_message',
$this->extensionMessage);
}
return true;
}
/**
* Generic update method for extensions
*
* @return boolean|integer The extension ID on success, boolean false on
failure
*
* @since 3.4
*/
public function update()
{
// Set the overwrite setting
$this->parent->setOverwrite(true);
$this->parent->setUpgrade(true);
// And make sure the route is set correctly
$this->setRoute('update');
// Now jump into the install method to run the update
return $this->install();
}
}
PKK�[xˢaaInstallerExtension.phpnu�[���<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Installer;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Application\ApplicationHelper;
/**
* Extension object
*
* @since 3.1
*/
class InstallerExtension extends \JObject
{
/**
* Filename of the extension
*
* @var string
* @since 3.1
*/
public $filename = '';
/**
* Type of the extension
*
* @var string
* @since 3.1
*/
public $type = '';
/**
* Unique Identifier for the extension
*
* @var string
* @since 3.1
*/
public $id = '';
/**
* The status of the extension
*
* @var boolean
* @since 3.1
*/
public $published = false;
/**
* String representation of client. Valid for modules, templates and
languages.
* Set by default to site.
*
* @var string
* @since 3.1
*/
public $client = 'site';
/**
* The group name of the plugin. Not used for other known extension types
(only plugins)
*
* @var string
* @since 3.1
*/
public $group = '';
/**
* An object representation of the manifest file stored metadata
*
* @var object
* @since 3.1
*/
public $manifest_cache = null;
/**
* An object representation of the extension params
*
* @var object
* @since 3.1
*/
public $params = null;
/**
* Constructor
*
* @param \SimpleXMLElement $element A SimpleXMLElement from which to
load data from
*
* @since 3.1
*/
public function __construct(\SimpleXMLElement $element = null)
{
if ($element)
{
$this->type = (string) $element->attributes()->type;
$this->id = (string) $element->attributes()->id;
switch ($this->type)
{
case 'component':
// By default a component doesn't have anything
break;
case 'module':
case 'template':
case 'language':
$this->client = (string) $element->attributes()->client;
$tmp_client_id = ApplicationHelper::getClientInfo($this->client,
1);
if ($tmp_client_id == null)
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_EXTENSION_INVALID_CLIENT_IDENTIFIER'),
\JLog::WARNING, 'jerror');
}
else
{
$this->client_id = $tmp_client_id->id;
}
break;
case 'plugin':
$this->group = (string) $element->attributes()->group;
break;
default:
// Catch all
// Get and set client and group if we don't recognise the
extension
if ($element->attributes()->client)
{
$this->client_id =
ApplicationHelper::getClientInfo($this->client, 1);
$this->client_id = $this->client_id->id;
}
if ($element->attributes()->group)
{
$this->group = (string) $element->attributes()->group;
}
break;
}
$this->filename = (string) $element;
}
}
}
PKK�[�EN'('(InstallerHelper.phpnu�[���<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Installer;
defined('JPATH_PLATFORM') or die;
use Joomla\Archive\Archive;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\CMS\Version;
\JLoader::import('joomla.filesystem.file');
\JLoader::import('joomla.filesystem.folder');
\JLoader::import('joomla.filesystem.path');
/**
* Installer helper class
*
* @since 3.1
*/
abstract class InstallerHelper
{
/**
* Hash not validated identifier.
*
* @var integer
* @since 3.9.0
*/
const HASH_NOT_VALIDATED = 0;
/**
* Hash validated identifier.
*
* @var integer
* @since 3.9.0
*/
const HASH_VALIDATED = 1;
/**
* Hash not provided identifier.
*
* @var integer
* @since 3.9.0
*/
const HASH_NOT_PROVIDED = 2;
/**
* Downloads a package
*
* @param string $url URL of file to download
* @param mixed $target Download target filename or false to get the
filename from the URL
*
* @return string|boolean Path to downloaded package or boolean false on
failure
*
* @since 3.1
*/
public static function downloadPackage($url, $target = false)
{
// Capture PHP errors
$track_errors = ini_get('track_errors');
ini_set('track_errors', true);
// Set user agent
$version = new Version;
ini_set('user_agent',
$version->getUserAgent('Installer'));
// Load installer plugins, and allow URL and headers modification
$headers = array();
PluginHelper::importPlugin('installer');
$dispatcher = \JEventDispatcher::getInstance();
$dispatcher->trigger('onInstallerBeforePackageDownload',
array(&$url, &$headers));
try
{
$response = \JHttpFactory::getHttp()->get($url, $headers);
}
catch (\RuntimeException $exception)
{
\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_DOWNLOAD_SERVER_CONNECT',
$exception->getMessage()), \JLog::WARNING, 'jerror');
return false;
}
// Convert keys of headers to lowercase, to accommodate for case
variations
$headers = array_change_key_case($response->headers);
if (302 == $response->code &&
!empty($headers['location']))
{
return self::downloadPackage($headers['location']);
}
elseif (200 != $response->code)
{
\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_DOWNLOAD_SERVER_CONNECT',
$response->code), \JLog::WARNING, 'jerror');
return false;
}
// Parse the Content-Disposition header to get the file name
if (!empty($headers['content-disposition'])
&& preg_match("/\s*filename\s?=\s?(.*)/",
$headers['content-disposition'], $parts))
{
$flds = explode(';', $parts[1]);
$target = trim($flds[0], '"');
}
$tmpPath = \JFactory::getConfig()->get('tmp_path');
// Set the target path if not given
if (!$target)
{
$target = $tmpPath . '/' . self::getFilenameFromUrl($url);
}
else
{
$target = $tmpPath . '/' . basename($target);
}
// Write buffer to file
\JFile::write($target, $response->body);
// Restore error tracking to what it was before
ini_set('track_errors', $track_errors);
// Bump the max execution time because not using built in php zip libs
are slow
@set_time_limit(ini_get('max_execution_time'));
// Return the name of the downloaded package
return basename($target);
}
/**
* Unpacks a file and verifies it as a Joomla element package
* Supports .gz .tar .tar.gz and .zip
*
* @param string $packageFilename The uploaded package filename or
install directory
* @param boolean $alwaysReturnArray If should return false (and leave
garbage behind) or return $retval['type']=false
*
* @return array|boolean Array on success or boolean false on failure
*
* @since 3.1
*/
public static function unpack($packageFilename, $alwaysReturnArray =
false)
{
// Path to the archive
$archivename = $packageFilename;
// Temporary folder to extract the archive into
$tmpdir = uniqid('install_');
// Clean the paths to use for archive extraction
$extractdir = \JPath::clean(dirname($packageFilename) . '/' .
$tmpdir);
$archivename = \JPath::clean($archivename);
// Do the unpacking of the archive
try
{
$archive = new Archive(array('tmp_path' =>
\JFactory::getConfig()->get('tmp_path')));
$extract = $archive->extract($archivename, $extractdir);
}
catch (\Exception $e)
{
if ($alwaysReturnArray)
{
return array(
'extractdir' => null,
'packagefile' => $archivename,
'type' => false,
);
}
return false;
}
if (!$extract)
{
if ($alwaysReturnArray)
{
return array(
'extractdir' => null,
'packagefile' => $archivename,
'type' => false,
);
}
return false;
}
/*
* Let's set the extraction directory and package file in the result
array so we can
* cleanup everything properly later on.
*/
$retval['extractdir'] = $extractdir;
$retval['packagefile'] = $archivename;
/*
* Try to find the correct install directory. In case the package is
inside a
* subdirectory detect this and set the install directory to the correct
path.
*
* List all the items in the installation directory. If there is only
one, and
* it is a folder, then we will set that folder to be the installation
folder.
*/
$dirList = array_merge((array) \JFolder::files($extractdir,
''), (array) \JFolder::folders($extractdir, ''));
if (count($dirList) === 1)
{
if (\JFolder::exists($extractdir . '/' . $dirList[0]))
{
$extractdir = \JPath::clean($extractdir . '/' . $dirList[0]);
}
}
/*
* We have found the install directory so lets set it and then move on
* to detecting the extension type.
*/
$retval['dir'] = $extractdir;
/*
* Get the extension type and return the directory/type array on success
or
* false on fail.
*/
$retval['type'] = self::detectType($extractdir);
if ($alwaysReturnArray || $retval['type'])
{
return $retval;
}
else
{
return false;
}
}
/**
* Method to detect the extension type from a package directory
*
* @param string $packageDirectory Path to package directory
*
* @return mixed Extension type string or boolean false on fail
*
* @since 3.1
*/
public static function detectType($packageDirectory)
{
// Search the install dir for an XML file
$files = \JFolder::files($packageDirectory, '\.xml$', 1, true);
if (!$files || !count($files))
{
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_NOTFINDXMLSETUPFILE'),
\JLog::WARNING, 'jerror');
return false;
}
foreach ($files as $file)
{
$xml = simplexml_load_file($file);
if (!$xml)
{
continue;
}
if ($xml->getName() !== 'extension')
{
unset($xml);
continue;
}
$type = (string) $xml->attributes()->type;
// Free up memory
unset($xml);
return $type;
}
\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_NOTFINDJOOMLAXMLSETUPFILE'),
\JLog::WARNING, 'jerror');
// Free up memory.
unset($xml);
return false;
}
/**
* Gets a file name out of a url
*
* @param string $url URL to get name from
*
* @return string Clean version of the filename or a unique id
*
* @since 3.1
*/
public static function getFilenameFromUrl($url)
{
$default = uniqid();
if (!is_string($url) || strpos($url, '/') === false)
{
return $default;
}
// Get last part of the url (after the last slash).
$parts = explode('/', $url);
$filename = array_pop($parts);
// Replace special characters with underscores.
$filename = preg_replace('/[^a-z0-9\_\-\.]/i', '_',
$filename);
// Replace multiple underscores with just one.
$filename = preg_replace('/__+/', '_',
trim($filename, '_'));
// Return the cleaned filename or, if it is empty, a unique id.
return $filename ?: $default;
}
/**
* Clean up temporary uploaded package and unpacked extension
*
* @param string $package Path to the uploaded package file
* @param string $resultdir Path to the unpacked extension
*
* @return boolean True on success
*
* @since 3.1
*/
public static function cleanupInstall($package, $resultdir)
{
$config = \JFactory::getConfig();
// Does the unpacked extension directory exist?
if ($resultdir && is_dir($resultdir))
{
\JFolder::delete($resultdir);
}
// Is the package file a valid file?
if (is_file($package))
{
\JFile::delete($package);
}
elseif (is_file(\JPath::clean($config->get('tmp_path') .
'/' . $package)))
{
// It might also be just a base filename
\JFile::delete(\JPath::clean($config->get('tmp_path') .
'/' . $package));
}
}
/**
* Splits contents of a sql file into array of discreet queries.
* Queries need to be delimited with end of statement marker ';'
*
* @param string $query The SQL statement.
*
* @return array Array of queries
*
* @since 3.1
* @deprecated 4.0 Use \JDatabaseDriver::splitSql() directly
* @codeCoverageIgnore
*/
public static function splitSql($query)
{
\JLog::add('JInstallerHelper::splitSql() is deprecated. Use
JDatabaseDriver::splitSql() instead.', \JLog::WARNING,
'deprecated');
$db = \JFactory::getDbo();
return $db->splitSql($query);
}
/**
* Return the result of the checksum of a package with the
SHA256/SHA384/SHA512 tags in the update server manifest
*
* @param string $packagefile Location of the package to be
installed
* @param JUpdate $updateObject The Update Object
*
* @return integer one if the hashes match, zero if hashes doesn't
match, two if hashes not found
*
* @since 3.9.0
*/
public static function isChecksumValid($packagefile, $updateObject)
{
$hashes = array('sha256', 'sha384',
'sha512');
$hashOnFile = false;
foreach ($hashes as $hash)
{
if ($updateObject->get($hash, false))
{
$hashPackage = hash_file($hash, $packagefile);
$hashRemote = $updateObject->$hash->_data;
$hashOnFile = true;
if ($hashPackage !== strtolower($hashRemote))
{
return self::HASH_NOT_VALIDATED;
}
}
}
if ($hashOnFile)
{
return self::HASH_VALIDATED;
}
return self::HASH_NOT_PROVIDED;
}
}
PKK�[�5��$$InstallerScript.phpnu�[���<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Installer;
defined('_JEXEC') or die;
\JLoader::import('joomla.filesystem.file');
\JLoader::import('joomla.filesystem.folder');
/**
* Base install script for use by extensions providing helper methods for
common behaviours.
*
* @since 3.6
*/
class InstallerScript
{
/**
* The version number of the extension.
*
* @var string
* @since 3.6
*/
protected $release;
/**
* The table the parameters are stored in.
*
* @var string
* @since 3.6
*/
protected $paramTable;
/**
* The extension name. This should be set in the installer script.
*
* @var string
* @since 3.6
*/
protected $extension;
/**
* A list of files to be deleted
*
* @var array
* @since 3.6
*/
protected $deleteFiles = array();
/**
* A list of folders to be deleted
*
* @var array
* @since 3.6
*/
protected $deleteFolders = array();
/**
* A list of CLI script files to be copied to the cli directory
*
* @var array
* @since 3.6
*/
protected $cliScriptFiles = array();
/**
* Minimum PHP version required to install the extension
*
* @var string
* @since 3.6
*/
protected $minimumPhp;
/**
* Minimum Joomla! version required to install the extension
*
* @var string
* @since 3.6
*/
protected $minimumJoomla;
/**
* Allow downgrades of your extension
*
* Use at your own risk as if there is a change in functionality people
may wish to downgrade.
*
* @var boolean
* @since 3.6
*/
protected $allowDowngrades = false;
/**
* Function called before extension installation/update/removal procedure
commences
*
* @param string $type The type of change (install, update
or discover_install, not uninstall)
* @param InstallerAdapter $parent The class calling this method
*
* @return boolean True on success
*
* @since 3.6
*/
public function preflight($type, $parent)
{
// Check for the minimum PHP version before continuing
if (!empty($this->minimumPhp) && version_compare(PHP_VERSION,
$this->minimumPhp, '<'))
{
\JLog::add(\JText::sprintf('JLIB_INSTALLER_MINIMUM_PHP',
$this->minimumPhp), \JLog::WARNING, 'jerror');
return false;
}
// Check for the minimum Joomla version before continuing
if (!empty($this->minimumJoomla) && version_compare(JVERSION,
$this->minimumJoomla, '<'))
{
\JLog::add(\JText::sprintf('JLIB_INSTALLER_MINIMUM_JOOMLA',
$this->minimumJoomla), \JLog::WARNING, 'jerror');
return false;
}
// Extension manifest file version
$this->extension = $parent->getName();
$this->release = $parent->get('manifest')->version;
$extensionType = substr($this->extension, 0, 3);
// Modules parameters are located in the module table - else in the
extension table
if ($extensionType === 'mod')
{
$this->paramTable = '#__modules';
}
else
{
$this->paramTable = '#__extensions';
}
// Abort if the extension being installed is not newer than the currently
installed version
if (!$this->allowDowngrades && strtolower($type) ===
'update')
{
$manifest = $this->getItemArray('manifest_cache',
'#__extensions', 'element',
\JFactory::getDbo()->quote($this->extension));
// Check whether we have an old release installed and skip this check
when this here is the initial install.
if (!isset($manifest['version']))
{
return true;
}
$oldRelease = $manifest['version'];
if (version_compare($this->release, $oldRelease, '<'))
{
\JFactory::getApplication()->enqueueMessage(\JText::sprintf('JLIB_INSTALLER_INCORRECT_SEQUENCE',
$oldRelease, $this->release), 'error');
return false;
}
}
return true;
}
/**
* Gets each instance of a module in the #__modules table
*
* @param boolean $isModule True if the extension is a module as this
can have multiple instances
*
* @return array An array of ID's of the extension
*
* @since 3.6
*/
public function getInstances($isModule)
{
$db = \JFactory::getDbo();
$query = $db->getQuery(true);
// Select the item(s) and retrieve the id
$query->select($db->quoteName('id'));
if ($isModule)
{
$query->from($db->quoteName('#__modules'))
->where($db->quoteName('module') . ' = ' .
$db->quote($this->extension));
}
else
{
$query->from($db->quoteName('#__extensions'))
->where($db->quoteName('element') . ' = ' .
$db->quote($this->extension));
}
// Set the query and obtain an array of id's
return $db->setQuery($query)->loadColumn();
}
/**
* Gets parameter value in the extensions row of the extension table
*
* @param string $name The name of the parameter to be retrieved
* @param integer $id The id of the item in the Param Table
*
* @return string The parameter desired
*
* @since 3.6
*/
public function getParam($name, $id = 0)
{
if (!is_int($id) || $id == 0)
{
// Return false if there is no item given
return false;
}
$params = $this->getItemArray('params',
$this->paramTable, 'id', $id);
return $params[$name];
}
/**
* Sets parameter values in the extensions row of the extension table.
Note that the
* this must be called separately for deleting and editing. Note if edit
is called as a
* type then if the param doesn't exist it will be created
*
* @param array $paramArray The array of parameters to be
added/edited/removed
* @param string $type The type of change to be made to the
param (edit/remove)
* @param integer $id The id of the item in the relevant table
*
* @return boolean True on success
*
* @since 3.6
*/
public function setParams($paramArray = null, $type = 'edit',
$id = 0)
{
if (!is_int($id) || $id == 0)
{
// Return false if there is no valid item given
return false;
}
$params = $this->getItemArray('params',
$this->paramTable, 'id', $id);
if ($paramArray)
{
foreach ($paramArray as $name => $value)
{
if ($type === 'edit')
{
// Add or edit the new variable(s) to the existing params
if (is_array($value))
{
// Convert an array into a json encoded string
$params[(string) $name] = array_values($value);
}
else
{
$params[(string) $name] = (string) $value;
}
}
elseif ($type === 'remove')
{
// Unset the parameter from the array
unset($params[(string) $name]);
}
}
}
// Store the combined new and existing values back as a JSON string
$paramsString = json_encode($params);
$db = \JFactory::getDbo();
$query = $db->getQuery(true)
->update($db->quoteName($this->paramTable))
->set('params = ' . $db->quote($paramsString))
->where('id = ' . $id);
// Update table
$db->setQuery($query)->execute();
return true;
}
/**
* Builds a standard select query to produce better DRY code in this
script.
* This should produce a single unique cell which is json encoded - it
will then
* return an associated array with this data in.
*
* @param string $element The element to get from the query
* @param string $table The table to search for the data in
* @param string $column The column of the database to search from
* @param mixed $identifier The integer id or the already quoted
string
*
* @return array Associated array containing data from the cell
*
* @since 3.6
*/
public function getItemArray($element, $table, $column, $identifier)
{
// Get the DB and query objects
$db = \JFactory::getDbo();
// Build the query
$query = $db->getQuery(true)
->select($db->quoteName($element))
->from($db->quoteName($table))
->where($db->quoteName($column) . ' = ' . $identifier);
$db->setQuery($query);
// Load the single cell and json_decode data
return json_decode($db->loadResult(), true);
}
/**
* Remove the files and folders in the given array from
*
* @return void
*
* @since 3.6
*/
public function removeFiles()
{
if (!empty($this->deleteFiles))
{
foreach ($this->deleteFiles as $file)
{
if (file_exists(JPATH_ROOT . $file) &&
!\JFile::delete(JPATH_ROOT . $file))
{
echo \JText::sprintf('JLIB_INSTALLER_ERROR_FILE_FOLDER',
$file) . '<br />';
}
}
}
if (!empty($this->deleteFolders))
{
foreach ($this->deleteFolders as $folder)
{
if (\JFolder::exists(JPATH_ROOT . $folder) &&
!\JFolder::delete(JPATH_ROOT . $folder))
{
echo \JText::sprintf('JLIB_INSTALLER_ERROR_FILE_FOLDER',
$folder) . '<br />';
}
}
}
}
/**
* Moves the CLI scripts into the CLI folder in the CMS
*
* @return void
*
* @since 3.6
*/
public function moveCliFiles()
{
if (!empty($this->cliScriptFiles))
{
foreach ($this->cliScriptFiles as $file)
{
$name = basename($file);
if (file_exists(JPATH_ROOT . $file) && !\JFile::move(JPATH_ROOT
. $file, JPATH_ROOT . '/cli/' . $name))
{
echo \JText::sprintf('JLIB_INSTALLER_FILE_ERROR_MOVE',
$name);
}
}
}
}
}
PKK�[��ooManifest/LibraryManifest.phpnu�[���<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Installer\Manifest;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Installer\Manifest;
/**
* Joomla! Library Manifest File
*
* @since 3.1
*/
class LibraryManifest extends Manifest
{
/**
* File system name of the library
*
* @var string
* @since 3.1
*/
public $libraryname = '';
/**
* Creation Date of the library
*
* @var string
* @since 3.1
*/
public $creationDate = '';
/**
* Copyright notice for the library
*
* @var string
* @since 3.1
*/
public $copyright = '';
/**
* License for the library
*
* @var string
* @since 3.1
*/
public $license = '';
/**
* Author for the library
*
* @var string
* @since 3.1
*/
public $author = '';
/**
* Author email for the library
*
* @var string
* @since 3.1
*/
public $authoremail = '';
/**
* Author URL for the library
*
* @var string
* @since 3.1
*/
public $authorurl = '';
/**
* Apply manifest data from a \SimpleXMLElement to the object.
*
* @param \SimpleXMLElement $xml Data to load
*
* @return void
*
* @since 3.1
*/
protected function loadManifestFromData(\SimpleXMLElement $xml)
{
$this->name = (string) $xml->name;
$this->libraryname = (string) $xml->libraryname;
$this->version = (string) $xml->version;
$this->description = (string) $xml->description;
$this->creationdate = (string) $xml->creationDate;
$this->author = (string) $xml->author;
$this->authoremail = (string) $xml->authorEmail;
$this->authorurl = (string) $xml->authorUrl;
$this->packager = (string) $xml->packager;
$this->packagerurl = (string) $xml->packagerurl;
$this->update = (string) $xml->update;
if (isset($xml->files) && isset($xml->files->file)
&& count($xml->files->file))
{
foreach ($xml->files->file as $file)
{
$this->filelist[] = (string) $file;
}
}
}
}
PKK�[D��� � Manifest/PackageManifest.phpnu�[���<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Installer\Manifest;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Installer\InstallerExtension;
use Joomla\CMS\Installer\Manifest;
/**
* Joomla! Package Manifest File
*
* @since 3.1
*/
class PackageManifest extends Manifest
{
/**
* Unique name of the package
*
* @var string
* @since 3.1
*/
public $packagename = '';
/**
* Website for the package
*
* @var string
* @since 3.1
*/
public $url = '';
/**
* Scriptfile for the package
*
* @var string
* @since 3.1
*/
public $scriptfile = '';
/**
* Flag if the package blocks individual child extensions from being
uninstalled
*
* @var boolean
* @since 3.7.0
*/
public $blockChildUninstall = false;
/**
* Apply manifest data from a \SimpleXMLElement to the object.
*
* @param \SimpleXMLElement $xml Data to load
*
* @return void
*
* @since 3.1
*/
protected function loadManifestFromData(\SimpleXMLElement $xml)
{
$this->name = (string) $xml->name;
$this->packagename = (string) $xml->packagename;
$this->update = (string) $xml->update;
$this->authorurl = (string) $xml->authorUrl;
$this->author = (string) $xml->author;
$this->authoremail = (string) $xml->authorEmail;
$this->description = (string) $xml->description;
$this->packager = (string) $xml->packager;
$this->packagerurl = (string) $xml->packagerurl;
$this->scriptfile = (string) $xml->scriptfile;
$this->version = (string) $xml->version;
if (isset($xml->blockChildUninstall))
{
$value = (string) $xml->blockChildUninstall;
if ($value === '1' || $value === 'true')
{
$this->blockChildUninstall = true;
}
}
if (isset($xml->files->file) &&
count($xml->files->file))
{
foreach ($xml->files->file as $file)
{
// NOTE: JInstallerExtension doesn't expect a string.
// DO NOT CAST $file
$this->filelist[] = new InstallerExtension($file);
}
}
// Handle cases where package contains folders
if (isset($xml->files->folder) &&
count($xml->files->folder))
{
foreach ($xml->files->folder as $folder)
{
// NOTE: JInstallerExtension doesn't expect a string.
// DO NOT CAST $folder
$this->filelist[] = new InstallerExtension($folder);
}
}
}
}
PKK�[�xl��Manifest.phpnu�[���<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Installer;
defined('JPATH_PLATFORM') or die;
\JLoader::import('joomla.filesystem.file');
/**
* Joomla! Package Manifest File
*
* @since 3.1
*/
abstract class Manifest
{
/**
* Path to the manifest file
*
* @var string
* @since 3.1
*/
public $manifest_file = '';
/**
* Name of the extension
*
* @var string
* @since 3.1
*/
public $name = '';
/**
* Version of the extension
*
* @var string
* @since 3.1
*/
public $version = '';
/**
* Description of the extension
*
* @var string
* @since 3.1
*/
public $description = '';
/**
* Packager of the extension
*
* @var string
* @since 3.1
*/
public $packager = '';
/**
* Packager's URL of the extension
*
* @var string
* @since 3.1
*/
public $packagerurl = '';
/**
* Update site for the extension
*
* @var string
* @since 3.1
*/
public $update = '';
/**
* List of files in the extension
*
* @var array
* @since 3.1
*/
public $filelist = array();
/**
* Constructor
*
* @param string $xmlpath Path to XML manifest file.
*
* @since 3.1
*/
public function __construct($xmlpath = '')
{
if ($xmlpath !== '')
{
$this->loadManifestFromXml($xmlpath);
}
}
/**
* Load a manifest from a file
*
* @param string $xmlfile Path to file to load
*
* @return boolean
*
* @since 3.1
*/
public function loadManifestFromXml($xmlfile)
{
$this->manifest_file = basename($xmlfile, '.xml');
$xml = simplexml_load_file($xmlfile);
if (!$xml)
{
$this->_errors[] =
\JText::sprintf('JLIB_INSTALLER_ERROR_LOAD_XML', $xmlfile);
return false;
}
else
{
$this->loadManifestFromData($xml);
return true;
}
}
/**
* Apply manifest data from a \SimpleXMLElement to the object.
*
* @param \SimpleXMLElement $xml Data to load
*
* @return void
*
* @since 3.1
*/
abstract protected function loadManifestFromData(\SimpleXmlElement $xml);
}
PKK�[uIg5`�`�Adapter/ComponentAdapter.phpnu�[���PKK�[.Vou@u@��Adapter/FileAdapter.phpnu�[���PKK�[��i��c�ch�Adapter/LanguageAdapter.phpnu�[���PKK�[�v4�+5+5�<Adapter/LibraryAdapter.phpnu�[���PKK�[Mm��QQ rAdapter/ModuleAdapter.phpnu�[���PKK�[Ʒ�9NNb�Adapter/PackageAdapter.phpnu�[���PKK�[�Ⱦ�,M,M�Adapter/PluginAdapter.phpnu�[���PKK�[�A�R�C�C0_Adapter/TemplateAdapter.phpnu�[���PKK�[�5y����
�Installer.phpnu�[���PKK�[��o�a�aӇInstallerAdapter.phpnu�[���PKK�[xˢaa��InstallerExtension.phpnu�[���PKK�[�EN'('(��InstallerHelper.phpnu�[���PKK�[�5��$$�InstallerScript.phpnu�[���PKK�[��ooHBManifest/LibraryManifest.phpnu�[���PKK�[D��� � KManifest/PackageManifest.phpnu�[���PKK�[�xl��UManifest.phpnu�[���PKp^