489 lines
16 KiB
PHP
489 lines
16 KiB
PHP
<?php
|
|
|
|
/**
|
|
* @package Joomla.Administrator
|
|
* @subpackage com_installer
|
|
*
|
|
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
|
|
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
|
*/
|
|
|
|
namespace Joomla\Component\Installer\Administrator\Helper;
|
|
|
|
use Joomla\CMS\Factory;
|
|
use Joomla\CMS\HTML\HTMLHelper;
|
|
use Joomla\CMS\Language\Text;
|
|
use Joomla\CMS\Object\CMSObject;
|
|
use Joomla\Database\DatabaseInterface;
|
|
use Joomla\Database\ParameterType;
|
|
|
|
// phpcs:disable PSR1.Files.SideEffects
|
|
\defined('_JEXEC') or die;
|
|
// phpcs:enable PSR1.Files.SideEffects
|
|
|
|
/**
|
|
* Installer helper.
|
|
*
|
|
* @since 1.6
|
|
*/
|
|
class InstallerHelper
|
|
{
|
|
/**
|
|
* Get a list of filter options for the extension types.
|
|
*
|
|
* @return array An array of \stdClass objects.
|
|
*
|
|
* @since 3.0
|
|
*/
|
|
public static function getExtensionTypes()
|
|
{
|
|
$db = Factory::getDbo();
|
|
$query = $db->getQuery(true)
|
|
->select('DISTINCT ' . $db->quoteName('type'))
|
|
->from($db->quoteName('#__extensions'));
|
|
$db->setQuery($query);
|
|
$types = $db->loadColumn();
|
|
|
|
$options = [];
|
|
|
|
foreach ($types as $type) {
|
|
$options[] = HTMLHelper::_('select.option', $type, Text::_('COM_INSTALLER_TYPE_' . strtoupper($type)));
|
|
}
|
|
|
|
return $options;
|
|
}
|
|
|
|
/**
|
|
* Get a list of filter options for the extension types.
|
|
*
|
|
* @return array An array of \stdClass objects.
|
|
*
|
|
* @since 3.0
|
|
*/
|
|
public static function getExtensionGroups()
|
|
{
|
|
$nofolder = '';
|
|
$db = Factory::getDbo();
|
|
$query = $db->getQuery(true)
|
|
->select('DISTINCT ' . $db->quoteName('folder'))
|
|
->from($db->quoteName('#__extensions'))
|
|
->where($db->quoteName('folder') . ' != :folder')
|
|
->bind(':folder', $nofolder)
|
|
->order($db->quoteName('folder'));
|
|
$db->setQuery($query);
|
|
$folders = $db->loadColumn();
|
|
|
|
$options = [];
|
|
|
|
foreach ($folders as $folder) {
|
|
$options[] = HTMLHelper::_('select.option', $folder, $folder);
|
|
}
|
|
|
|
return $options;
|
|
}
|
|
|
|
/**
|
|
* Get a list of filter options for the application clients.
|
|
*
|
|
* @return array An array of \JHtmlOption elements.
|
|
*
|
|
* @since 3.5
|
|
*/
|
|
public static function getClientOptions()
|
|
{
|
|
// Build the filter options.
|
|
$options = [];
|
|
$options[] = HTMLHelper::_('select.option', '0', Text::_('JSITE'));
|
|
$options[] = HTMLHelper::_('select.option', '1', Text::_('JADMINISTRATOR'));
|
|
$options[] = HTMLHelper::_('select.option', '3', Text::_('JAPI'));
|
|
|
|
return $options;
|
|
}
|
|
|
|
/**
|
|
* Get a list of filter options for the application statuses.
|
|
*
|
|
* @return array An array of \JHtmlOption elements.
|
|
*
|
|
* @since 3.5
|
|
*/
|
|
public static function getStateOptions()
|
|
{
|
|
// Build the filter options.
|
|
$options = [];
|
|
$options[] = HTMLHelper::_('select.option', '0', Text::_('JDISABLED'));
|
|
$options[] = HTMLHelper::_('select.option', '1', Text::_('JENABLED'));
|
|
$options[] = HTMLHelper::_('select.option', '2', Text::_('JPROTECTED'));
|
|
$options[] = HTMLHelper::_('select.option', '3', Text::_('JUNPROTECTED'));
|
|
|
|
return $options;
|
|
}
|
|
|
|
/**
|
|
* Get a list of filter options for extensions of the "package" type.
|
|
*
|
|
* @return array
|
|
* @since 4.2.0
|
|
*/
|
|
public static function getPackageOptions(): array
|
|
{
|
|
$options = [];
|
|
|
|
/** @var DatabaseInterface $db The application's database driver object */
|
|
$db = Factory::getContainer()->get(DatabaseInterface::class);
|
|
$query = $db->getQuery(true)
|
|
->select(
|
|
$db->quoteName(
|
|
[
|
|
'extension_id',
|
|
'name',
|
|
'element',
|
|
]
|
|
)
|
|
)
|
|
->from($db->quoteName('#__extensions'))
|
|
->where($db->quoteName('type') . ' = ' . $db->quote('package'));
|
|
$extensions = $db->setQuery($query)->loadObjectList() ?: [];
|
|
|
|
if (empty($extensions)) {
|
|
return $options;
|
|
}
|
|
|
|
$language = Factory::getApplication()->getLanguage();
|
|
$arrayKeys = array_map(
|
|
function (object $entry) use ($language): string {
|
|
$language->load($entry->element, JPATH_ADMINISTRATOR);
|
|
|
|
return Text::_($entry->name);
|
|
},
|
|
$extensions
|
|
);
|
|
$arrayValues = array_map(
|
|
function (object $entry): int {
|
|
return $entry->extension_id;
|
|
},
|
|
$extensions
|
|
);
|
|
|
|
$extensions = array_combine($arrayKeys, $arrayValues);
|
|
ksort($extensions);
|
|
|
|
foreach ($extensions as $label => $id) {
|
|
$options[] = HTMLHelper::_('select.option', $id, $label);
|
|
}
|
|
|
|
return $options;
|
|
}
|
|
|
|
/**
|
|
* Get a list of filter options for the application statuses.
|
|
*
|
|
* @param string $element element of an extension
|
|
* @param string $type type of an extension
|
|
* @param integer $clientId client_id of an extension
|
|
* @param string $folder folder of an extension
|
|
*
|
|
* @return \SimpleXMLElement
|
|
*
|
|
* @since 4.0.0
|
|
*/
|
|
public static function getInstallationXML(
|
|
string $element,
|
|
string $type,
|
|
int $clientId = 1,
|
|
?string $folder = null
|
|
): ?\SimpleXMLElement {
|
|
$path = [0 => JPATH_SITE, 1 => JPATH_ADMINISTRATOR, 3 => JPATH_API][$clientId] ?? JPATH_SITE;
|
|
|
|
switch ($type) {
|
|
case 'component':
|
|
$path .= '/components/' . $element . '/' . substr($element, 4) . '.xml';
|
|
break;
|
|
case 'plugin':
|
|
$path .= '/plugins/' . $folder . '/' . $element . '/' . $element . '.xml';
|
|
break;
|
|
case 'module':
|
|
$path .= '/modules/' . $element . '/' . $element . '.xml';
|
|
break;
|
|
case 'template':
|
|
$path .= '/templates/' . $element . '/templateDetails.xml';
|
|
break;
|
|
case 'library':
|
|
$path = JPATH_ADMINISTRATOR . '/manifests/libraries/' . $element . '.xml';
|
|
break;
|
|
case 'file':
|
|
$path = JPATH_ADMINISTRATOR . '/manifests/files/' . $element . '.xml';
|
|
break;
|
|
case 'package':
|
|
$path = JPATH_ADMINISTRATOR . '/manifests/packages/' . $element . '.xml';
|
|
break;
|
|
case 'language':
|
|
$path .= '/language/' . $element . '/install.xml';
|
|
}
|
|
|
|
if (file_exists($path) === false) {
|
|
return null;
|
|
}
|
|
|
|
$xmlElement = simplexml_load_file($path);
|
|
|
|
return ($xmlElement !== false) ? $xmlElement : null;
|
|
}
|
|
|
|
/**
|
|
* Get the download key of an extension going through their installation xml
|
|
*
|
|
* @param CMSObject $extension element of an extension
|
|
*
|
|
* @return array An array with the prefix, suffix and value of the download key
|
|
*
|
|
* @since 4.0.0
|
|
*/
|
|
public static function getDownloadKey(CMSObject $extension): array
|
|
{
|
|
$installXmlFile = self::getInstallationXML(
|
|
$extension->get('element'),
|
|
$extension->get('type'),
|
|
$extension->get('client_id'),
|
|
$extension->get('folder')
|
|
);
|
|
|
|
if (!$installXmlFile) {
|
|
return [
|
|
'supported' => false,
|
|
'valid' => false,
|
|
];
|
|
}
|
|
|
|
if (!isset($installXmlFile->dlid)) {
|
|
return [
|
|
'supported' => false,
|
|
'valid' => false,
|
|
];
|
|
}
|
|
|
|
$prefix = (string) $installXmlFile->dlid['prefix'];
|
|
$suffix = (string) $installXmlFile->dlid['suffix'];
|
|
$value = substr($extension->get('extra_query'), strlen($prefix));
|
|
|
|
if ($suffix) {
|
|
$value = substr($value, 0, -strlen($suffix));
|
|
}
|
|
|
|
$downloadKey = [
|
|
'supported' => true,
|
|
'valid' => $value ? true : false,
|
|
'prefix' => $prefix,
|
|
'suffix' => $suffix,
|
|
'value' => $value,
|
|
];
|
|
|
|
return $downloadKey;
|
|
}
|
|
|
|
/**
|
|
* Get the download key of an extension given enough information to locate it in the #__extensions table
|
|
*
|
|
* @param string $element Name of the extension, e.g. com_foo
|
|
* @param string $type The type of the extension, e.g. component
|
|
* @param int $clientId [optional] Joomla client for the extension, see the #__extensions table
|
|
* @param string|null $folder Extension folder, only applies for 'plugin' type
|
|
*
|
|
* @return array
|
|
*
|
|
* @since 4.0.0
|
|
*/
|
|
public static function getExtensionDownloadKey(
|
|
string $element,
|
|
string $type,
|
|
int $clientId = 1,
|
|
?string $folder = null
|
|
): array {
|
|
// Get the database driver. If it fails we cannot report whether the extension supports download keys.
|
|
try {
|
|
$db = Factory::getDbo();
|
|
} catch (\Exception $e) {
|
|
return [
|
|
'supported' => false,
|
|
'valid' => false,
|
|
];
|
|
}
|
|
|
|
// Try to retrieve the extension information as a CMSObject
|
|
$query = $db->getQuery(true)
|
|
->select($db->quoteName('extension_id'))
|
|
->from($db->quoteName('#__extensions'))
|
|
->where($db->quoteName('type') . ' = :type')
|
|
->where($db->quoteName('element') . ' = :element')
|
|
->where($db->quoteName('folder') . ' = :folder')
|
|
->where($db->quoteName('client_id') . ' = :client_id');
|
|
$query->bind(':type', $type, ParameterType::STRING);
|
|
$query->bind(':element', $element, ParameterType::STRING);
|
|
$query->bind(':client_id', $clientId, ParameterType::INTEGER);
|
|
$query->bind(':folder', $folder, ParameterType::STRING);
|
|
|
|
try {
|
|
$extension = new CMSObject($db->setQuery($query)->loadAssoc());
|
|
} catch (\Exception $e) {
|
|
return [
|
|
'supported' => false,
|
|
'valid' => false,
|
|
];
|
|
}
|
|
|
|
// Use the getDownloadKey() method to return the download key information
|
|
return self::getDownloadKey($extension);
|
|
}
|
|
|
|
/**
|
|
* Returns a list of update site IDs which support download keys. By default this returns all qualifying update
|
|
* sites, even if they are not enabled.
|
|
*
|
|
*
|
|
* @param bool $onlyEnabled [optional] Set true to only returned enabled update sites.
|
|
*
|
|
* @return int[]
|
|
* @since 4.0.0
|
|
*/
|
|
public static function getDownloadKeySupportedSites($onlyEnabled = false): array
|
|
{
|
|
/**
|
|
* NOTE: The closures are not inlined because in this case the Joomla Code Style standard produces two mutually
|
|
* exclusive errors, making the file impossible to commit. Using closures in variables makes the code less
|
|
* readable but works around that issue.
|
|
*/
|
|
|
|
$extensions = self::getUpdateSitesInformation($onlyEnabled);
|
|
|
|
$filterClosure = function (CMSObject $extension) {
|
|
$dlidInfo = self::getDownloadKey($extension);
|
|
|
|
return $dlidInfo['supported'];
|
|
};
|
|
$extensions = array_filter($extensions, $filterClosure);
|
|
|
|
$mapClosure = function (CMSObject $extension) {
|
|
return $extension->get('update_site_id');
|
|
};
|
|
|
|
return array_map($mapClosure, $extensions);
|
|
}
|
|
|
|
/**
|
|
* Returns a list of update site IDs which are missing download keys. By default this returns all qualifying update
|
|
* sites, even if they are not enabled.
|
|
*
|
|
* @param bool $exists [optional] If true, returns update sites with a valid download key. When false,
|
|
* returns update sites with an invalid / missing download key.
|
|
* @param bool $onlyEnabled [optional] Set true to only returned enabled update sites.
|
|
*
|
|
* @return int[]
|
|
* @since 4.0.0
|
|
*/
|
|
public static function getDownloadKeyExistsSites(bool $exists = true, $onlyEnabled = false): array
|
|
{
|
|
/**
|
|
* NOTE: The closures are not inlined because in this case the Joomla Code Style standard produces two mutually
|
|
* exclusive errors, making the file impossible to commit. Using closures in variables makes the code less
|
|
* readable but works around that issue.
|
|
*/
|
|
|
|
$extensions = self::getUpdateSitesInformation($onlyEnabled);
|
|
|
|
// Filter the extensions by what supports Download Keys
|
|
$filterClosure = function (CMSObject $extension) use ($exists) {
|
|
$dlidInfo = self::getDownloadKey($extension);
|
|
|
|
if (!$dlidInfo['supported']) {
|
|
return false;
|
|
}
|
|
|
|
return $exists ? $dlidInfo['valid'] : !$dlidInfo['valid'];
|
|
};
|
|
$extensions = array_filter($extensions, $filterClosure);
|
|
|
|
// Return only the update site IDs
|
|
$mapClosure = function (CMSObject $extension) {
|
|
return $extension->get('update_site_id');
|
|
};
|
|
|
|
return array_map($mapClosure, $extensions);
|
|
}
|
|
|
|
|
|
/**
|
|
* Get information about the update sites
|
|
*
|
|
* @param bool $onlyEnabled Only return enabled update sites
|
|
*
|
|
* @return CMSObject[] List of update site and linked extension information
|
|
* @since 4.0.0
|
|
*/
|
|
protected static function getUpdateSitesInformation(bool $onlyEnabled): array
|
|
{
|
|
try {
|
|
$db = Factory::getDbo();
|
|
} catch (\Exception $e) {
|
|
return [];
|
|
}
|
|
|
|
$query = $db->getQuery(true)
|
|
->select(
|
|
$db->quoteName(
|
|
[
|
|
's.update_site_id',
|
|
's.enabled',
|
|
's.extra_query',
|
|
'e.extension_id',
|
|
'e.type',
|
|
'e.element',
|
|
'e.folder',
|
|
'e.client_id',
|
|
'e.manifest_cache',
|
|
],
|
|
[
|
|
'update_site_id',
|
|
'enabled',
|
|
'extra_query',
|
|
'extension_id',
|
|
'type',
|
|
'element',
|
|
'folder',
|
|
'client_id',
|
|
'manifest_cache',
|
|
]
|
|
)
|
|
)
|
|
->from($db->quoteName('#__update_sites', 's'))
|
|
->innerJoin(
|
|
$db->quoteName('#__update_sites_extensions', 'se'),
|
|
$db->quoteName('se.update_site_id') . ' = ' . $db->quoteName('s.update_site_id')
|
|
)
|
|
->innerJoin(
|
|
$db->quoteName('#__extensions', 'e'),
|
|
$db->quoteName('e.extension_id') . ' = ' . $db->quoteName('se.extension_id')
|
|
)
|
|
->where($db->quoteName('state') . ' = 0');
|
|
|
|
if ($onlyEnabled) {
|
|
$enabled = 1;
|
|
$query->where($db->quoteName('s.enabled') . ' = :enabled')
|
|
->bind(':enabled', $enabled, ParameterType::INTEGER);
|
|
}
|
|
|
|
// Try to get all of the update sites, including related extension information
|
|
try {
|
|
$items = [];
|
|
$db->setQuery($query);
|
|
|
|
foreach ($db->getIterator() as $item) {
|
|
$items[] = new CMSObject($item);
|
|
}
|
|
|
|
return $items;
|
|
} catch (\Exception $e) {
|
|
return [];
|
|
}
|
|
}
|
|
}
|