primo commit
This commit is contained in:
374
libraries/fof30/Update/Collection.php
Normal file
374
libraries/fof30/Update/Collection.php
Normal file
@ -0,0 +1,374 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Update;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use Exception;
|
||||
use FOF30\Container\Container;
|
||||
use FOF30\Download\Download;
|
||||
use SimpleXMLElement;
|
||||
|
||||
class Collection
|
||||
{
|
||||
/**
|
||||
* Reads a "collection" XML update source and returns the complete tree of categories
|
||||
* and extensions applicable for platform version $jVersion
|
||||
*
|
||||
* @param string $url The collection XML update source URL to read from
|
||||
* @param string $jVersion Joomla! version to fetch updates for, or null to use JVERSION
|
||||
*
|
||||
* @return array A list of update sources applicable to $jVersion
|
||||
*/
|
||||
public function getAllUpdates($url, $jVersion = null)
|
||||
{
|
||||
// Get the target platform
|
||||
if (is_null($jVersion))
|
||||
{
|
||||
$jVersion = JVERSION;
|
||||
}
|
||||
|
||||
// Initialise return value
|
||||
$updates = [
|
||||
'metadata' => [
|
||||
'name' => '',
|
||||
'description' => '',
|
||||
],
|
||||
'categories' => [],
|
||||
'extensions' => [],
|
||||
];
|
||||
|
||||
// Download and parse the XML file
|
||||
$container = Container::getInstance('com_foobar');
|
||||
$downloader = new Download($container);
|
||||
$xmlSource = $downloader->getFromURL($url);
|
||||
|
||||
try
|
||||
{
|
||||
$xml = new SimpleXMLElement($xmlSource, LIBXML_NONET);
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
return $updates;
|
||||
}
|
||||
|
||||
// Sanity check
|
||||
if (($xml->getName() != 'extensionset'))
|
||||
{
|
||||
unset($xml);
|
||||
|
||||
return $updates;
|
||||
}
|
||||
|
||||
// Initialise return value with the stream metadata (name, description)
|
||||
$rootAttributes = $xml->attributes();
|
||||
foreach ($rootAttributes as $k => $v)
|
||||
{
|
||||
$updates['metadata'][$k] = (string) $v;
|
||||
}
|
||||
|
||||
// Initialise the raw list of updates
|
||||
$rawUpdates = [
|
||||
'categories' => [],
|
||||
'extensions' => [],
|
||||
];
|
||||
|
||||
// Segregate the raw list to a hierarchy of extension and category entries
|
||||
/** @var SimpleXMLElement $extension */
|
||||
foreach ($xml->children() as $extension)
|
||||
{
|
||||
switch ($extension->getName())
|
||||
{
|
||||
case 'category':
|
||||
// These are the parameters we expect in a category
|
||||
$params = [
|
||||
'name' => '',
|
||||
'description' => '',
|
||||
'category' => '',
|
||||
'ref' => '',
|
||||
'targetplatformversion' => $jVersion,
|
||||
];
|
||||
|
||||
// These are the attributes of the element
|
||||
$attributes = $extension->attributes();
|
||||
|
||||
// Merge them all
|
||||
foreach ($attributes as $k => $v)
|
||||
{
|
||||
$params[$k] = (string) $v;
|
||||
}
|
||||
|
||||
// We can't have a category with an empty category name
|
||||
if (empty($params['category']))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// We can't have a category with an empty ref
|
||||
if (empty($params['ref']))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (empty($params['description']))
|
||||
{
|
||||
$params['description'] = $params['category'];
|
||||
}
|
||||
|
||||
if (!array_key_exists($params['category'], $rawUpdates['categories']))
|
||||
{
|
||||
$rawUpdates['categories'][$params['category']] = [];
|
||||
}
|
||||
|
||||
$rawUpdates['categories'][$params['category']][] = $params;
|
||||
|
||||
break;
|
||||
|
||||
case 'extension':
|
||||
// These are the parameters we expect in a category
|
||||
$params = [
|
||||
'element' => '',
|
||||
'type' => '',
|
||||
'version' => '',
|
||||
'name' => '',
|
||||
'detailsurl' => '',
|
||||
'targetplatformversion' => $jVersion,
|
||||
];
|
||||
|
||||
// These are the attributes of the element
|
||||
$attributes = $extension->attributes();
|
||||
|
||||
// Merge them all
|
||||
foreach ($attributes as $k => $v)
|
||||
{
|
||||
$params[$k] = (string) $v;
|
||||
}
|
||||
|
||||
// We can't have an extension with an empty element
|
||||
if (empty($params['element']))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// We can't have an extension with an empty type
|
||||
if (empty($params['type']))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// We can't have an extension with an empty version
|
||||
if (empty($params['version']))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (empty($params['name']))
|
||||
{
|
||||
$params['name'] = $params['element'] . ' ' . $params['version'];
|
||||
}
|
||||
|
||||
if (!array_key_exists($params['type'], $rawUpdates['extensions']))
|
||||
{
|
||||
$rawUpdates['extensions'][$params['type']] = [];
|
||||
}
|
||||
|
||||
if (!array_key_exists($params['element'], $rawUpdates['extensions'][$params['type']]))
|
||||
{
|
||||
$rawUpdates['extensions'][$params['type']][$params['element']] = [];
|
||||
}
|
||||
|
||||
$rawUpdates['extensions'][$params['type']][$params['element']][] = $params;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
unset($xml);
|
||||
|
||||
if (!empty($rawUpdates['categories']))
|
||||
{
|
||||
foreach ($rawUpdates['categories'] as $category => $entries)
|
||||
{
|
||||
$update = $this->filterListByPlatform($entries, $jVersion);
|
||||
$updates['categories'][$category] = $update;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($rawUpdates['extensions']))
|
||||
{
|
||||
foreach ($rawUpdates['extensions'] as $type => $extensions)
|
||||
{
|
||||
$updates['extensions'][$type] = [];
|
||||
|
||||
if (!empty($extensions))
|
||||
{
|
||||
foreach ($extensions as $element => $entries)
|
||||
{
|
||||
$update = $this->filterListByPlatform($entries, $jVersion);
|
||||
$updates['extensions'][$type][$element] = $update;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $updates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns only the category definitions of a collection
|
||||
*
|
||||
* @param string $url The URL of the collection update source
|
||||
* @param string $jVersion Joomla! version to fetch updates for, or null to use JVERSION
|
||||
*
|
||||
* @return array An array of category update definitions
|
||||
*/
|
||||
public function getCategories($url, $jVersion = null)
|
||||
{
|
||||
$allUpdates = $this->getAllUpdates($url, $jVersion);
|
||||
|
||||
return $allUpdates['categories'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the update source for a specific category
|
||||
*
|
||||
* @param string $url The URL of the collection update source
|
||||
* @param string $category The category name you want to get the update source URL of
|
||||
* @param string $jVersion Joomla! version to fetch updates for, or null to use JVERSION
|
||||
*
|
||||
* @return string|null The update stream URL, or null if it's not found
|
||||
*/
|
||||
public function getCategoryUpdateSource($url, $category, $jVersion = null)
|
||||
{
|
||||
$allUpdates = $this->getAllUpdates($url, $jVersion);
|
||||
|
||||
if (array_key_exists($category, $allUpdates['categories']))
|
||||
{
|
||||
return $allUpdates['categories'][$category]['ref'];
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of updates for extensions only, optionally of a specific type
|
||||
*
|
||||
* @param string $url The URL of the collection update source
|
||||
* @param string $type The extension type you want to get the update source URL of, empty to get all
|
||||
* extension types
|
||||
* @param string $jVersion Joomla! version to fetch updates for, or null to use JVERSION
|
||||
*
|
||||
* @return array|null An array of extension update definitions or null if none is found
|
||||
*/
|
||||
public function getExtensions($url, $type = null, $jVersion = null)
|
||||
{
|
||||
$allUpdates = $this->getAllUpdates($url, $jVersion);
|
||||
|
||||
if (empty($type))
|
||||
{
|
||||
return $allUpdates['extensions'];
|
||||
}
|
||||
elseif (array_key_exists($type, $allUpdates['extensions']))
|
||||
{
|
||||
return $allUpdates['extensions'][$type];
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the update source URL for a specific extension, based on the type and element, e.g.
|
||||
* type=file and element=joomla is Joomla! itself.
|
||||
*
|
||||
* @param string $url The URL of the collection update source
|
||||
* @param string $type The extension type you want to get the update source URL of
|
||||
* @param string $element The extension element you want to get the update source URL of
|
||||
* @param string $jVersion Joomla! version to fetch updates for, or null to use JVERSION
|
||||
*
|
||||
* @return string|null The update source URL or null if the extension is not found
|
||||
*/
|
||||
public function getExtensionUpdateSource($url, $type, $element, $jVersion = null)
|
||||
{
|
||||
$allUpdates = $this->getExtensions($url, $type, $jVersion);
|
||||
|
||||
if (empty($allUpdates))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
elseif (array_key_exists($element, $allUpdates))
|
||||
{
|
||||
return $allUpdates[$element]['detailsurl'];
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters a list of updates, returning only those available for the
|
||||
* specified platform version $jVersion
|
||||
*
|
||||
* @param array $updates An array containing update definitions (categories or extensions)
|
||||
* @param string $jVersion Joomla! version to fetch updates for, or null to use JVERSION
|
||||
*
|
||||
* @return array|null The update definition that is compatible, or null if none is compatible
|
||||
*/
|
||||
private function filterListByPlatform($updates, $jVersion = null)
|
||||
{
|
||||
// Get the target platform
|
||||
if (is_null($jVersion))
|
||||
{
|
||||
$jVersion = JVERSION;
|
||||
}
|
||||
|
||||
$versionParts = explode('.', $jVersion, 4);
|
||||
$platformVersionMajor = $versionParts[0];
|
||||
$platformVersionMinor = (count($versionParts) > 1) ? $platformVersionMajor . '.' . $versionParts[1] : $platformVersionMajor;
|
||||
$platformVersionNormal = (count($versionParts) > 2) ? $platformVersionMinor . '.' . $versionParts[2] : $platformVersionMinor;
|
||||
$platformVersionFull = (count($versionParts) > 3) ? $platformVersionNormal . '.' . $versionParts[3] : $platformVersionNormal;
|
||||
|
||||
$pickedExtension = null;
|
||||
$pickedSpecificity = -1;
|
||||
|
||||
foreach ($updates as $update)
|
||||
{
|
||||
// Test the target platform
|
||||
$targetPlatform = (string) $update['targetplatformversion'];
|
||||
|
||||
if ($targetPlatform === $platformVersionFull)
|
||||
{
|
||||
$pickedExtension = $update;
|
||||
$pickedSpecificity = 4;
|
||||
}
|
||||
elseif (($targetPlatform === $platformVersionNormal) && ($pickedSpecificity <= 3))
|
||||
{
|
||||
$pickedExtension = $update;
|
||||
$pickedSpecificity = 3;
|
||||
}
|
||||
elseif (($targetPlatform === $platformVersionMinor) && ($pickedSpecificity <= 2))
|
||||
{
|
||||
$pickedExtension = $update;
|
||||
$pickedSpecificity = 2;
|
||||
}
|
||||
elseif (($targetPlatform === $platformVersionMajor) && ($pickedSpecificity <= 1))
|
||||
{
|
||||
$pickedExtension = $update;
|
||||
$pickedSpecificity = 1;
|
||||
}
|
||||
}
|
||||
|
||||
return $pickedExtension;
|
||||
}
|
||||
}
|
||||
134
libraries/fof30/Update/Extension.php
Normal file
134
libraries/fof30/Update/Extension.php
Normal file
@ -0,0 +1,134 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Update;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use Exception;
|
||||
use FOF30\Container\Container;
|
||||
use FOF30\Download\Download;
|
||||
use SimpleXMLElement;
|
||||
|
||||
/**
|
||||
* A helper class to read and parse "extension" update XML files over the web
|
||||
*/
|
||||
class Extension
|
||||
{
|
||||
/**
|
||||
* Reads an "extension" XML update source and returns all listed update entries.
|
||||
*
|
||||
* If you have a "collection" XML update source you should do something like this:
|
||||
* $collection = new F0FUtilsUpdateCollection();
|
||||
* $extensionUpdateURL = $collection->getExtensionUpdateSource($url, 'component', 'com_foobar', JVERSION);
|
||||
* $extension = new F0FUtilsUpdateExtension();
|
||||
* $updates = $extension->getUpdatesFromExtension($extensionUpdateURL);
|
||||
*
|
||||
* @param string $url The extension XML update source URL to read from
|
||||
*
|
||||
* @return array An array of update entries
|
||||
*/
|
||||
public function getUpdatesFromExtension($url)
|
||||
{
|
||||
// Initialise
|
||||
$ret = [];
|
||||
|
||||
// Get and parse the XML source
|
||||
$container = Container::getInstance('com_FOOBAR');
|
||||
$downloader = new Download($container);
|
||||
$xmlSource = $downloader->getFromURL($url);
|
||||
|
||||
try
|
||||
{
|
||||
$xml = new SimpleXMLElement($xmlSource, LIBXML_NONET);
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
return $ret;
|
||||
}
|
||||
|
||||
// Sanity check
|
||||
if (($xml->getName() != 'updates'))
|
||||
{
|
||||
unset($xml);
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
// Let's populate the list of updates
|
||||
/** @var SimpleXMLElement $update */
|
||||
foreach ($xml->children() as $update)
|
||||
{
|
||||
// Sanity check
|
||||
if ($update->getName() != 'update')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$entry = [
|
||||
'infourl' => ['title' => '', 'url' => ''],
|
||||
'downloads' => [],
|
||||
'tags' => [],
|
||||
'targetplatform' => [],
|
||||
];
|
||||
|
||||
$properties = get_object_vars($update);
|
||||
|
||||
foreach ($properties as $nodeName => $nodeContent)
|
||||
{
|
||||
switch ($nodeName)
|
||||
{
|
||||
default:
|
||||
$entry[$nodeName] = $nodeContent;
|
||||
break;
|
||||
|
||||
case 'infourl':
|
||||
case 'downloads':
|
||||
case 'tags':
|
||||
case 'targetplatform':
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$infourlNode = $update->xpath('infourl');
|
||||
$entry['infourl']['title'] = (string) $infourlNode[0]['title'];
|
||||
$entry['infourl']['url'] = (string) $infourlNode[0];
|
||||
|
||||
$downloadNodes = $update->xpath('downloads/downloadurl');
|
||||
foreach ($downloadNodes as $downloadNode)
|
||||
{
|
||||
$entry['downloads'][] = [
|
||||
'type' => (string) $downloadNode['type'],
|
||||
'format' => (string) $downloadNode['format'],
|
||||
'url' => (string) $downloadNode,
|
||||
];
|
||||
}
|
||||
|
||||
$tagNodes = $update->xpath('tags/tag');
|
||||
foreach ($tagNodes as $tagNode)
|
||||
{
|
||||
$entry['tags'][] = (string) $tagNode;
|
||||
}
|
||||
|
||||
/** @var SimpleXMLElement[] $targetPlatformNode */
|
||||
$targetPlatformNode = $update->xpath('targetplatform');
|
||||
|
||||
$entry['targetplatform']['name'] = (string) $targetPlatformNode[0]['name'];
|
||||
$entry['targetplatform']['version'] = (string) $targetPlatformNode[0]['version'];
|
||||
$client = $targetPlatformNode[0]->xpath('client');
|
||||
$entry['targetplatform']['client'] = (is_array($client) && count($client)) ? (string) $client[0] : '';
|
||||
$folder = $targetPlatformNode[0]->xpath('folder');
|
||||
$entry['targetplatform']['folder'] = is_array($folder) && count($folder) ? (string) $folder[0] : '';
|
||||
|
||||
$ret[] = $entry;
|
||||
}
|
||||
|
||||
unset($xml);
|
||||
|
||||
return $ret;
|
||||
}
|
||||
}
|
||||
509
libraries/fof30/Update/Joomla.php
Normal file
509
libraries/fof30/Update/Joomla.php
Normal file
@ -0,0 +1,509 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Update;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
class Joomla extends Extension
|
||||
{
|
||||
/**
|
||||
* The source for LTS updates
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $lts_url = 'http://update.joomla.org/core/list.xml';
|
||||
|
||||
/**
|
||||
* The source for STS updates
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $sts_url = 'http://update.joomla.org/core/sts/list_sts.xml';
|
||||
|
||||
/**
|
||||
* The source for test release updates
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $test_url = 'http://update.joomla.org/core/test/list_test.xml';
|
||||
|
||||
/**
|
||||
* Reads a "collection" XML update source and picks the correct source URL
|
||||
* for the extension update source.
|
||||
*
|
||||
* @param string $url The collection XML update source URL to read from
|
||||
* @param string $jVersion Joomla! version to fetch updates for, or null to use JVERSION
|
||||
*
|
||||
* @return string The URL of the extension update source, or empty if no updates are provided / fetching failed
|
||||
*/
|
||||
public function getUpdateSourceFromCollection($url, $jVersion = null)
|
||||
{
|
||||
$provider = new Collection();
|
||||
|
||||
return $provider->getExtensionUpdateSource($url, 'file', 'joomla', $jVersion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the properties of a version: STS/LTS, normal or testing
|
||||
*
|
||||
* @param string $jVersion The version number to check
|
||||
* @param string $currentVersion The current Joomla! version number
|
||||
*
|
||||
* @return array The properties analysis
|
||||
*/
|
||||
public function getVersionProperties($jVersion, $currentVersion = null)
|
||||
{
|
||||
// Initialise
|
||||
$ret = [
|
||||
'lts' => true,
|
||||
// Is this an LTS release? False means STS.
|
||||
'current' => false,
|
||||
// Is this a release in the $currentVersion branch?
|
||||
'upgrade' => 'none',
|
||||
// Upgrade relation of $jVersion to $currentVersion: 'none' (can't upgrade), 'lts' (next or current LTS), 'sts' (next or current STS) or 'current' (same release, no upgrade available)
|
||||
'testing' => false,
|
||||
// Is this a testing (alpha, beta, RC) release?
|
||||
];
|
||||
|
||||
// Get the current version if none is defined
|
||||
if (is_null($currentVersion))
|
||||
{
|
||||
$currentVersion = JVERSION;
|
||||
}
|
||||
|
||||
// Sanitise version numbers
|
||||
$sameVersion = $jVersion == $currentVersion;
|
||||
$jVersion = $this->sanitiseVersion($jVersion);
|
||||
$currentVersion = $this->sanitiseVersion($currentVersion);
|
||||
$sameVersion = $sameVersion || ($jVersion == $currentVersion);
|
||||
|
||||
// Get the base version
|
||||
$baseVersion = substr($jVersion, 0, 3);
|
||||
|
||||
// Get the minimum and maximum current version numbers
|
||||
$current_minimum = substr($currentVersion, 0, 3);
|
||||
$current_maximum = $current_minimum . '.9999';
|
||||
|
||||
// Initialise STS/LTS version numbers
|
||||
$sts_minimum = false;
|
||||
$sts_maximum = false;
|
||||
$lts_minimum = false;
|
||||
|
||||
// Is it an LTS or STS release?
|
||||
switch ($baseVersion)
|
||||
{
|
||||
case '1.5':
|
||||
$ret['lts'] = true;
|
||||
break;
|
||||
|
||||
case '1.6':
|
||||
$ret['lts'] = false;
|
||||
$sts_minimum = '1.7';
|
||||
$sts_maximum = '1.7.999';
|
||||
$lts_minimum = '2.5';
|
||||
break;
|
||||
|
||||
case '1.7':
|
||||
$ret['lts'] = false;
|
||||
$sts_minimum = false;
|
||||
$lts_minimum = '2.5';
|
||||
break;
|
||||
|
||||
case '2.5':
|
||||
$ret['lts'] = true;
|
||||
$sts_minimum = false;
|
||||
$lts_minimum = '2.5';
|
||||
break;
|
||||
|
||||
default:
|
||||
$majorVersion = (int) substr($jVersion, 0, 1);
|
||||
//$minorVersion = (int) substr($jVersion, 2, 1);
|
||||
|
||||
$ret['lts'] = true;
|
||||
$sts_minimum = false;
|
||||
$lts_minimum = $majorVersion . '.0';
|
||||
break;
|
||||
}
|
||||
|
||||
// Is it a current release?
|
||||
if (version_compare($jVersion, $current_minimum, 'ge') && version_compare($jVersion, $current_maximum, 'le'))
|
||||
{
|
||||
$ret['current'] = true;
|
||||
}
|
||||
|
||||
// Is this a testing release?
|
||||
$versionParts = explode('.', $jVersion);
|
||||
$lastVersionPart = array_pop($versionParts);
|
||||
|
||||
if (in_array(substr($lastVersionPart, 0, 1), ['a', 'b']))
|
||||
{
|
||||
$ret['testing'] = true;
|
||||
}
|
||||
elseif (substr($lastVersionPart, 0, 2) == 'rc')
|
||||
{
|
||||
$ret['testing'] = true;
|
||||
}
|
||||
elseif (substr($lastVersionPart, 0, 3) == 'dev')
|
||||
{
|
||||
$ret['testing'] = true;
|
||||
}
|
||||
|
||||
// Find the upgrade relation of $jVersion to $currentVersion
|
||||
if (version_compare($jVersion, $currentVersion, 'eq'))
|
||||
{
|
||||
$ret['upgrade'] = 'current';
|
||||
}
|
||||
elseif (($sts_minimum !== false) && version_compare($jVersion, $sts_minimum, 'ge') && version_compare($jVersion, $sts_maximum, 'le'))
|
||||
{
|
||||
$ret['upgrade'] = 'sts';
|
||||
}
|
||||
elseif (($lts_minimum !== false) && version_compare($jVersion, $lts_minimum, 'ge'))
|
||||
{
|
||||
$ret['upgrade'] = 'lts';
|
||||
}
|
||||
elseif ($baseVersion == $current_minimum)
|
||||
{
|
||||
$ret['upgrade'] = $ret['lts'] ? 'lts' : 'sts';
|
||||
}
|
||||
else
|
||||
{
|
||||
$ret['upgrade'] = 'none';
|
||||
}
|
||||
|
||||
if ($sameVersion)
|
||||
{
|
||||
$ret['upgrade'] = 'none';
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Filters a list of updates, making sure they apply to the specified CMS
|
||||
* release.
|
||||
*
|
||||
* @param array $updates A list of update records returned by the getUpdatesFromExtension method
|
||||
* @param string $jVersion The current Joomla! version number
|
||||
*
|
||||
* @return array A filtered list of updates. Each update record also includes version relevance information.
|
||||
*/
|
||||
public function filterApplicableUpdates($updates, $jVersion = null)
|
||||
{
|
||||
if (empty($jVersion))
|
||||
{
|
||||
$jVersion = JVERSION;
|
||||
}
|
||||
|
||||
$versionParts = explode('.', $jVersion, 4);
|
||||
$platformVersionMajor = $versionParts[0];
|
||||
$platformVersionMinor = $platformVersionMajor . '.' . $versionParts[1];
|
||||
//$platformVersionNormal = $platformVersionMinor . '.' . $versionParts[2];
|
||||
//$platformVersionFull = (count($versionParts) > 3) ? $platformVersionNormal . '.' . $versionParts[3] : $platformVersionNormal;
|
||||
|
||||
$ret = [];
|
||||
|
||||
foreach ($updates as $update)
|
||||
{
|
||||
// Check each update for platform match
|
||||
if (strtolower($update['targetplatform']['name']) != 'joomla')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$targetPlatformVersion = $update['targetplatform']['version'];
|
||||
|
||||
if (!preg_match('/' . $targetPlatformVersion . '/', $platformVersionMinor))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get some information from the version number
|
||||
$updateVersion = $update['version'];
|
||||
$versionProperties = $this->getVersionProperties($updateVersion, $jVersion);
|
||||
|
||||
if ($versionProperties['upgrade'] == 'none')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// The XML files are ill-maintained. Maybe we already have this update?
|
||||
if (!array_key_exists($updateVersion, $ret))
|
||||
{
|
||||
$ret[$updateVersion] = array_merge($update, $versionProperties);
|
||||
}
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Joomla! has a lousy track record in naming its alpha, beta and release
|
||||
* candidate releases. The convention used seems to be "what the hell the
|
||||
* current package maintainer thinks looks better". This method tries to
|
||||
* figure out what was in the mind of the maintainer and translate the
|
||||
* funky version number to an actual PHP-format version string.
|
||||
*
|
||||
* @param string $version The whatever-format version number
|
||||
*
|
||||
* @return string A standard formatted version number
|
||||
*/
|
||||
public function sanitiseVersion($version)
|
||||
{
|
||||
$test = strtolower($version);
|
||||
$alphaQualifierPosition = strpos($test, 'alpha-');
|
||||
$betaQualifierPosition = strpos($test, 'beta-');
|
||||
$betaQualifierPosition2 = strpos($test, '-beta');
|
||||
$rcQualifierPosition = strpos($test, 'rc-');
|
||||
$rcQualifierPosition2 = strpos($test, '-rc');
|
||||
$rcQualifierPosition3 = strpos($test, 'rc');
|
||||
$devQualifiedPosition = strpos($test, 'dev');
|
||||
|
||||
if ($alphaQualifierPosition !== false)
|
||||
{
|
||||
$betaRevision = substr($test, $alphaQualifierPosition + 6);
|
||||
if (!$betaRevision)
|
||||
{
|
||||
$betaRevision = 1;
|
||||
}
|
||||
$test = substr($test, 0, $alphaQualifierPosition) . '.a' . $betaRevision;
|
||||
}
|
||||
elseif ($betaQualifierPosition !== false)
|
||||
{
|
||||
$betaRevision = substr($test, $betaQualifierPosition + 5);
|
||||
if (!$betaRevision)
|
||||
{
|
||||
$betaRevision = 1;
|
||||
}
|
||||
$test = substr($test, 0, $betaQualifierPosition) . '.b' . $betaRevision;
|
||||
}
|
||||
elseif ($betaQualifierPosition2 !== false)
|
||||
{
|
||||
$betaRevision = substr($test, $betaQualifierPosition2 + 5);
|
||||
|
||||
if (!$betaRevision)
|
||||
{
|
||||
$betaRevision = 1;
|
||||
}
|
||||
|
||||
$test = substr($test, 0, $betaQualifierPosition2) . '.b' . $betaRevision;
|
||||
}
|
||||
elseif ($rcQualifierPosition !== false)
|
||||
{
|
||||
$betaRevision = substr($test, $rcQualifierPosition + 5);
|
||||
if (!$betaRevision)
|
||||
{
|
||||
$betaRevision = 1;
|
||||
}
|
||||
$test = substr($test, 0, $rcQualifierPosition) . '.rc' . $betaRevision;
|
||||
}
|
||||
elseif ($rcQualifierPosition2 !== false)
|
||||
{
|
||||
$betaRevision = substr($test, $rcQualifierPosition2 + 3);
|
||||
|
||||
if (!$betaRevision)
|
||||
{
|
||||
$betaRevision = 1;
|
||||
}
|
||||
|
||||
$test = substr($test, 0, $rcQualifierPosition2) . '.rc' . $betaRevision;
|
||||
}
|
||||
elseif ($rcQualifierPosition3 !== false)
|
||||
{
|
||||
$betaRevision = substr($test, $rcQualifierPosition3 + 5);
|
||||
|
||||
if (!$betaRevision)
|
||||
{
|
||||
$betaRevision = 1;
|
||||
}
|
||||
|
||||
$test = substr($test, 0, $rcQualifierPosition3) . '.rc' . $betaRevision;
|
||||
}
|
||||
elseif ($devQualifiedPosition !== false)
|
||||
{
|
||||
$betaRevision = substr($test, $devQualifiedPosition + 6);
|
||||
if (!$betaRevision)
|
||||
{
|
||||
$betaRevision = '';
|
||||
}
|
||||
$test = substr($test, 0, $devQualifiedPosition) . '.dev' . $betaRevision;
|
||||
}
|
||||
|
||||
return $test;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads the list of all updates available for the specified Joomla! version
|
||||
* from the network.
|
||||
*
|
||||
* @param array $sources The enabled sources to look into
|
||||
* @param string $jVersion The Joomla! version we are checking updates for
|
||||
*
|
||||
* @return array A list of updates for the installed, current, lts and sts versions
|
||||
*/
|
||||
public function getUpdates($sources = [], $jVersion = null)
|
||||
{
|
||||
// Make sure we have a valid list of sources
|
||||
if (empty($sources) || !is_array($sources))
|
||||
{
|
||||
$sources = [];
|
||||
}
|
||||
|
||||
$defaultSources = ['lts' => true, 'sts' => true, 'test' => true, 'custom' => ''];
|
||||
|
||||
$sources = array_merge($defaultSources, $sources);
|
||||
|
||||
// Use the current JVERSION if none is specified
|
||||
if (empty($jVersion))
|
||||
{
|
||||
$jVersion = JVERSION;
|
||||
}
|
||||
|
||||
// Get the current branch' min/max versions
|
||||
$versionParts = explode('.', $jVersion, 4);
|
||||
$currentMinVersion = $versionParts[0] . '.' . $versionParts[1];
|
||||
$currentMaxVersion = $versionParts[0] . '.' . $versionParts[1] . '.9999';
|
||||
|
||||
|
||||
// Retrieve all updates
|
||||
$allUpdates = [];
|
||||
|
||||
foreach ($sources as $source => $value)
|
||||
{
|
||||
if (($value === false) || empty($value))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
switch ($source)
|
||||
{
|
||||
default:
|
||||
case 'lts':
|
||||
$url = self::$lts_url;
|
||||
break;
|
||||
|
||||
case 'sts':
|
||||
$url = self::$sts_url;
|
||||
break;
|
||||
|
||||
case 'test':
|
||||
$url = self::$test_url;
|
||||
break;
|
||||
|
||||
case 'custom':
|
||||
$url = $value;
|
||||
break;
|
||||
}
|
||||
|
||||
$url = $this->getUpdateSourceFromCollection($url, $jVersion);
|
||||
|
||||
if (!empty($url))
|
||||
{
|
||||
$updates = $this->getUpdatesFromExtension($url);
|
||||
|
||||
if (!empty($updates))
|
||||
{
|
||||
$applicableUpdates = $this->filterApplicableUpdates($updates, $jVersion);
|
||||
|
||||
if (!empty($applicableUpdates))
|
||||
{
|
||||
$allUpdates = array_merge($allUpdates, $applicableUpdates);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$ret = [
|
||||
// Currently installed version (used to reinstall, if available)
|
||||
'installed' => [
|
||||
'version' => '',
|
||||
'package' => '',
|
||||
'infourl' => '',
|
||||
],
|
||||
// Current branch
|
||||
'current' => [
|
||||
'version' => '',
|
||||
'package' => '',
|
||||
'infourl' => '',
|
||||
],
|
||||
// Upgrade to STS release
|
||||
'sts' => [
|
||||
'version' => '',
|
||||
'package' => '',
|
||||
'infourl' => '',
|
||||
],
|
||||
// Upgrade to LTS release
|
||||
'lts' => [
|
||||
'version' => '',
|
||||
'package' => '',
|
||||
'infourl' => '',
|
||||
],
|
||||
// Upgrade to LTS release
|
||||
'test' => [
|
||||
'version' => '',
|
||||
'package' => '',
|
||||
'infourl' => '',
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($allUpdates as $update)
|
||||
{
|
||||
$sections = [];
|
||||
|
||||
if ($update['upgrade'] == 'current')
|
||||
{
|
||||
$sections[0] = 'installed';
|
||||
}
|
||||
elseif (version_compare($update['version'], $currentMinVersion, 'ge') && version_compare($update['version'], $currentMaxVersion, 'le'))
|
||||
{
|
||||
$sections[0] = 'current';
|
||||
}
|
||||
else
|
||||
{
|
||||
$sections[0] = '';
|
||||
}
|
||||
|
||||
$sections[1] = $update['lts'] ? 'lts' : 'sts';
|
||||
|
||||
if ($update['testing'])
|
||||
{
|
||||
$sections = ['test'];
|
||||
}
|
||||
|
||||
foreach ($sections as $section)
|
||||
{
|
||||
if (empty($section))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$existingVersionForSection = $ret[$section]['version'];
|
||||
|
||||
if (empty($existingVersionForSection))
|
||||
{
|
||||
$existingVersionForSection = '0.0.0';
|
||||
}
|
||||
|
||||
if (version_compare($update['version'], $existingVersionForSection, 'ge'))
|
||||
{
|
||||
$ret[$section]['version'] = $update['version'];
|
||||
$ret[$section]['package'] = $update['downloads'][0]['url'];
|
||||
$ret[$section]['infourl'] = $update['infourl']['url'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Catch the case when the latest current branch version is the installed version (up to date site)
|
||||
if (empty($ret['current']['version']) && !empty($ret['installed']['version']))
|
||||
{
|
||||
$ret['current'] = $ret['installed'];
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
}
|
||||
943
libraries/fof30/Update/Update.php
Normal file
943
libraries/fof30/Update/Update.php
Normal file
@ -0,0 +1,943 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Update;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use Exception;
|
||||
use FOF30\Container\Container;
|
||||
use FOF30\Model\Model;
|
||||
use Joomla\CMS\Component\ComponentHelper;
|
||||
use Joomla\CMS\Updater\Updater;
|
||||
use SimpleXMLElement;
|
||||
|
||||
/**
|
||||
* A helper Model to interact with Joomla!'s extensions update feature
|
||||
*/
|
||||
class Update extends Model
|
||||
{
|
||||
/** @var Updater The Joomla! updater object */
|
||||
protected $updater = null;
|
||||
|
||||
/** @var int The extension_id of this component */
|
||||
protected $extension_id = 0;
|
||||
|
||||
/** @var string The currently installed version, as reported by the #__extensions table */
|
||||
protected $version = 'dev';
|
||||
|
||||
/** @var string The name of the component e.g. com_something */
|
||||
protected $component = 'com_foobar';
|
||||
|
||||
/** @var string The URL to the component's update XML stream */
|
||||
protected $updateSite = null;
|
||||
|
||||
/** @var string The name to the component's update site (description of the update XML stream) */
|
||||
protected $updateSiteName = null;
|
||||
|
||||
/** @var string The extra query to append to (commercial) components' download URLs */
|
||||
protected $extraQuery = null;
|
||||
|
||||
/** @var string The component Options key which stores a copy of the Download ID */
|
||||
protected $paramsKey = 'update_dlid';
|
||||
|
||||
/**
|
||||
* Public constructor. Initialises the protected members as well. Useful $config keys:
|
||||
* update_component The component name, e.g. com_foobar
|
||||
* update_version The default version if the manifest cache is unreadable
|
||||
* update_site The URL to the component's update XML stream
|
||||
* update_extraquery The extra query to append to (commercial) components' download URLs
|
||||
* update_sitename The update site's name (description)
|
||||
* update_paramskey The component parameters key which holds the license key in J3 (and a copy of it in J4)
|
||||
*
|
||||
* @param array $config
|
||||
*/
|
||||
public function __construct($config = [])
|
||||
{
|
||||
$container = Container::getInstance('com_FOOBAR');
|
||||
|
||||
if (isset($config['update_container']) && is_object($config['update_container']) && ($config['update_container'] instanceof Container))
|
||||
{
|
||||
$container = $config['update_container'];
|
||||
}
|
||||
|
||||
parent::__construct($container);
|
||||
|
||||
// Get an instance of the updater class
|
||||
$this->updater = Updater::getInstance();
|
||||
|
||||
// Get the component name
|
||||
if (isset($config['update_component']))
|
||||
{
|
||||
$this->component = $config['update_component'];
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->component = $this->input->getCmd('option', '');
|
||||
}
|
||||
|
||||
// Get the component version
|
||||
if (isset($config['update_version']))
|
||||
{
|
||||
$this->version = $config['update_version'];
|
||||
}
|
||||
|
||||
// Get the update site
|
||||
if (isset($config['update_site']))
|
||||
{
|
||||
$this->updateSite = $config['update_site'];
|
||||
}
|
||||
|
||||
// Get the extra query
|
||||
if (isset($config['update_extraquery']))
|
||||
{
|
||||
$this->extraQuery = $config['update_extraquery'];
|
||||
}
|
||||
|
||||
// Get the extra query
|
||||
if (isset($config['update_sitename']))
|
||||
{
|
||||
$this->updateSiteName = $config['update_sitename'];
|
||||
}
|
||||
|
||||
// Get the extra query
|
||||
if (isset($config['update_paramskey']))
|
||||
{
|
||||
$this->paramsKey = $config['update_paramskey'];
|
||||
}
|
||||
|
||||
// Get the extension type
|
||||
$extension = $this->getExtensionObject();
|
||||
|
||||
if (is_object($extension))
|
||||
{
|
||||
$this->extension_id = $extension->extension_id;
|
||||
|
||||
if (empty($this->version) || ($this->version == 'dev'))
|
||||
{
|
||||
$data = json_decode($extension->manifest_cache, true);
|
||||
|
||||
if (isset($data['version']))
|
||||
{
|
||||
$this->version = $data['version'];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the license key for a paid extension.
|
||||
*
|
||||
* On Joomla! 3 or when $forceLegacy is true we look in the component Options.
|
||||
*
|
||||
* On Joomla! 4 we use the information in the dlid element of the extension's XML manifest to parse the extra_query
|
||||
* fields of all configured update sites of the extension. This is the same thing Joomla does when it tries to
|
||||
* determine the license key of our extension when installing updates. If the extension is missing, it has no
|
||||
* associated update sites, the update sites are missing / rebuilt / disassociated from the extension or the
|
||||
* extra_query of all update site records is empty we parse the $extraQuery set in the constructor, if any. Also
|
||||
* note that on Joomla 4 mode if the extension does not exist, does not have a manifest or does not have a valid
|
||||
* dlid element in its manifest we will end up returning an empty string, just like Joomla! itself would have done
|
||||
* when installing updates.
|
||||
*
|
||||
* @param bool $forceLegacy Should I always retrieve the legacy license key, even in J4?
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getLicenseKey($forceLegacy = false)
|
||||
{
|
||||
$legacyParamsKey = $this->getLegacyParamsKey();
|
||||
|
||||
// Joomla 3 (Legacy): Download ID stored in the component options
|
||||
if ($forceLegacy || !version_compare(JVERSION, '3.999.999', 'gt'))
|
||||
{
|
||||
return $this->container->params->get($legacyParamsKey, '');
|
||||
}
|
||||
|
||||
// Joomla! 4. We need to parse the extra_query of the update sites to get the correct Download ID.
|
||||
$updateSites = $this->getUpdateSites();
|
||||
$extra_query = array_reduce($updateSites, function ($extra_query, $updateSite) {
|
||||
if (!empty($extra_query))
|
||||
{
|
||||
return $extra_query;
|
||||
}
|
||||
|
||||
return $updateSite['extra_query'];
|
||||
}, '');
|
||||
|
||||
// Fall back to legacy extra query
|
||||
if (empty($extra_query))
|
||||
{
|
||||
$extra_query = $this->extraQuery;
|
||||
}
|
||||
|
||||
// Return the parsed results.
|
||||
return $this->getLicenseKeyFromExtraQuery($extra_query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the contents of all the update sites of the configured extension
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getUpdateSites()
|
||||
{
|
||||
$updateSiteIDs = $this->getUpdateSiteIds();
|
||||
$db = $this->container->db;
|
||||
$query = $db->getQuery(true)
|
||||
->select('*')
|
||||
->from($db->qn('#__update_sites'))
|
||||
->where($db->qn('update_site_id') . ' IN (' . implode(', ', $updateSiteIDs) . ')');
|
||||
|
||||
try
|
||||
{
|
||||
$db->setQuery($query);
|
||||
|
||||
$ret = $db->loadAssocList('update_site_id');
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
$ret = null;
|
||||
}
|
||||
|
||||
return empty($ret) ? [] : $ret;
|
||||
}
|
||||
|
||||
public function setLicenseKey($licenseKey)
|
||||
{
|
||||
$legacyParamsKey = $this->getLegacyParamsKey();
|
||||
|
||||
// Sanitize and validate the license key. If it's not valid we set an empty license key.
|
||||
$licenseKey = $this->sanitizeLicenseKey($licenseKey);
|
||||
|
||||
if (!$this->isValidLicenseKey($licenseKey))
|
||||
{
|
||||
$licenseKey = '';
|
||||
}
|
||||
|
||||
// Update $this->extraQuery.
|
||||
$this->extraQuery = $this->getExtraQueryString($licenseKey);
|
||||
|
||||
// Save the license key in the component options ($legacyParamsKey)
|
||||
$this->container->params->set($legacyParamsKey, $licenseKey);
|
||||
$this->container->params->save();
|
||||
|
||||
// Apply the new extra_query to the update site
|
||||
$this->refreshUpdateSite();
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies a Joomla 3 license key from the Options storage to Joomla 4 download key storage (the extra_query column
|
||||
* of the #__update_sites table).
|
||||
*
|
||||
* This method does nothing on Joomla 3.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function upgradeLicenseKey()
|
||||
{
|
||||
// Only applies to Joomla! 4
|
||||
if (!version_compare(JVERSION, '3.999.999', 'gt'))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure we DO have a legacy license key
|
||||
$legacyKey = $this->getLicenseKey(true);
|
||||
|
||||
if (empty($legacyKey))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure we DO NOT have a J4 key. If we do, the J4 key wins and gets backported to legacy storage.
|
||||
$licenseKey = $this->getLicenseKey(false);
|
||||
|
||||
if (!empty($licenseKey))
|
||||
{
|
||||
$this->backportLicenseKey();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Save the legacy key as non-legacy. This updates the #__update_sites record, applying the license key.
|
||||
$this->setLicenseKey($legacyKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies a Joomla 4 license key from the download key storage (the extra_query column of the #__update_sites table)
|
||||
* to the legacy Options storage.
|
||||
*
|
||||
* This method does nothing on Joomla 3.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function backportLicenseKey()
|
||||
{
|
||||
$legacyParamsKey = $this->getLegacyParamsKey();
|
||||
|
||||
// Only applies to Joomla! 4
|
||||
if (!version_compare(JVERSION, '3.999.999', 'gt'))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure we DO have a J4 key
|
||||
$licenseKey = $this->getLicenseKey(false);
|
||||
|
||||
if (empty($licenseKey))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure that the legacy key is NOT the same as the J4 key
|
||||
$legacyKey = $this->getLicenseKey(true);
|
||||
|
||||
if ($legacyKey == $licenseKey)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Save the license key to the legacy storage (component options)
|
||||
$this->container->params->set($legacyParamsKey, $licenseKey);
|
||||
$this->container->params->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an extra query string based on the dlid element of the XML manifest file of the extension.
|
||||
*
|
||||
* If the extension does not exist, the manifest does not exist or it does not have a dlid element we fall back to
|
||||
* the legacy implementation of extra_query (getExtraQueryStringLegacy)
|
||||
*
|
||||
* @param string $licenseKey
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getExtraQueryString($licenseKey)
|
||||
{
|
||||
// Make sure the (sanitized) license key is valid. Otherwise we return an empty string.
|
||||
$licenseKey = $this->sanitizeLicenseKey($licenseKey);
|
||||
|
||||
if (!$this->isValidLicenseKey($licenseKey))
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
// Get a fallback extra query using the legacy method
|
||||
$fallbackExtraQuery = $this->getExtraQueryStringLegacy($licenseKey);
|
||||
|
||||
// Get the extension XML manifest. If the extension or the manifest don't exist use the fallback extra_query.
|
||||
$extension = $this->getExtensionObject();
|
||||
|
||||
if (!$extension)
|
||||
{
|
||||
return $fallbackExtraQuery;
|
||||
}
|
||||
|
||||
$installXmlFile = $this->getManifestXML(
|
||||
$extension->element,
|
||||
$extension->type,
|
||||
(int) $extension->client_id,
|
||||
$extension->folder
|
||||
);
|
||||
|
||||
if (!$installXmlFile)
|
||||
{
|
||||
return $fallbackExtraQuery;
|
||||
}
|
||||
|
||||
// If the manifest does not have the dlid element return the fallback extra_query.
|
||||
if (!isset($installXmlFile->dlid))
|
||||
{
|
||||
return $fallbackExtraQuery;
|
||||
}
|
||||
|
||||
$prefix = (string) $installXmlFile->dlid['prefix'];
|
||||
$suffix = (string) $installXmlFile->dlid['suffix'];
|
||||
|
||||
return $prefix . $this->sanitizeLicenseKey($licenseKey) . $suffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the update information of the component, returning an array with the following keys:
|
||||
*
|
||||
* hasUpdate True if an update is available
|
||||
* version The version of the available update
|
||||
* infoURL The URL to the download page of the update
|
||||
*
|
||||
* @param bool $force Set to true if you want to forcibly reload the update information
|
||||
*
|
||||
* @return array See the method description for more information
|
||||
*/
|
||||
public function getUpdates($force = false)
|
||||
{
|
||||
$db = $this->container->db;
|
||||
|
||||
// Default response (no update)
|
||||
$updateResponse = [
|
||||
'hasUpdate' => false,
|
||||
'version' => '',
|
||||
'infoURL' => '',
|
||||
];
|
||||
|
||||
if (empty($this->extension_id))
|
||||
{
|
||||
return $updateResponse;
|
||||
}
|
||||
|
||||
// If we had to update the version number stored in the database then we should force reload the updates
|
||||
if ($this->updatedCachedVersionNumber())
|
||||
{
|
||||
$force = true;
|
||||
}
|
||||
|
||||
// If we are forcing the reload, set the last_check_timestamp to 0
|
||||
// and remove cached component update info in order to force a reload
|
||||
if ($force)
|
||||
{
|
||||
// Find the update site IDs
|
||||
$updateSiteIds = $this->getUpdateSiteIds();
|
||||
|
||||
if (empty($updateSiteIds))
|
||||
{
|
||||
return $updateResponse;
|
||||
}
|
||||
|
||||
// Set the last_check_timestamp to 0
|
||||
$query = $db->getQuery(true)
|
||||
->update($db->qn('#__update_sites'))
|
||||
->set($db->qn('last_check_timestamp') . ' = ' . $db->q('0'))
|
||||
->where($db->qn('update_site_id') . ' IN (' . implode(', ', $updateSiteIds) . ')');
|
||||
$db->setQuery($query);
|
||||
$db->execute();
|
||||
|
||||
// Remove cached component update info from #__updates
|
||||
$query = $db->getQuery(true)
|
||||
->delete($db->qn('#__updates'))
|
||||
->where($db->qn('update_site_id') . ' IN (' . implode(', ', $updateSiteIds) . ')');
|
||||
$db->setQuery($query);
|
||||
$db->execute();
|
||||
}
|
||||
|
||||
// Use the update cache timeout specified in com_installer
|
||||
$comInstallerParams = ComponentHelper::getParams('com_installer', false);
|
||||
$timeout = 3600 * $comInstallerParams->get('cachetimeout', '6');
|
||||
|
||||
// Load any updates from the network into the #__updates table
|
||||
$this->updater->findUpdates($this->extension_id, $timeout);
|
||||
|
||||
// Get the update record from the database
|
||||
$query = $db->getQuery(true)
|
||||
->select('*')
|
||||
->from($db->qn('#__updates'))
|
||||
->where($db->qn('extension_id') . ' = ' . $db->q($this->extension_id));
|
||||
$db->setQuery($query);
|
||||
$updateRecord = $db->loadObject();
|
||||
|
||||
// If we have an update record in the database return the information found there
|
||||
if (is_object($updateRecord))
|
||||
{
|
||||
$updateResponse = [
|
||||
'hasUpdate' => true,
|
||||
'version' => $updateRecord->version,
|
||||
'infoURL' => $updateRecord->infourl,
|
||||
];
|
||||
}
|
||||
|
||||
return $updateResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the update site Ids for our extension.
|
||||
*
|
||||
* @return array An array of IDs
|
||||
*/
|
||||
public function getUpdateSiteIds()
|
||||
{
|
||||
$db = $this->container->db;
|
||||
$query = $db->getQuery(true)
|
||||
->select($db->qn('update_site_id'))
|
||||
->from($db->qn('#__update_sites_extensions'))
|
||||
->where($db->qn('extension_id') . ' = ' . $db->q($this->extension_id));
|
||||
$db->setQuery($query);
|
||||
|
||||
try
|
||||
{
|
||||
$ret = $db->loadColumn(0);
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
$ret = null;
|
||||
}
|
||||
|
||||
return is_array($ret) ? $ret : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the currently installed version as reported by the #__extensions table
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getVersion()
|
||||
{
|
||||
return $this->version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the currently installed version as reported by the #__extensions table
|
||||
*
|
||||
* @param string $version
|
||||
*/
|
||||
public function setVersion($version)
|
||||
{
|
||||
$this->version = $version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the Joomla! update sites for this extension as needed
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function refreshUpdateSite()
|
||||
{
|
||||
if (empty($this->extension_id))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the update site definition we want to store to the database
|
||||
$update_site = [
|
||||
'name' => $this->updateSiteName,
|
||||
'type' => 'extension',
|
||||
'location' => $this->updateSite,
|
||||
'enabled' => 1,
|
||||
'last_check_timestamp' => 0,
|
||||
'extra_query' => $this->extraQuery,
|
||||
];
|
||||
|
||||
// Get a reference to the db driver
|
||||
$db = $this->container->db;
|
||||
|
||||
// Get the #__update_sites columns
|
||||
$columns = $db->getTableColumns('#__update_sites', true);
|
||||
|
||||
if (version_compare(JVERSION, '3.0.0', 'lt') || !array_key_exists('extra_query', $columns))
|
||||
{
|
||||
unset($update_site['extra_query']);
|
||||
}
|
||||
|
||||
// Get the update sites for our extension
|
||||
$updateSiteIds = $this->getUpdateSiteIds();
|
||||
|
||||
if (empty($updateSiteIds))
|
||||
{
|
||||
$updateSiteIds = [];
|
||||
}
|
||||
|
||||
/** @var boolean $needNewUpdateSite Do I need to create a new update site? */
|
||||
$needNewUpdateSite = true;
|
||||
|
||||
/** @var int[] $deleteOldSites Old Site IDs to delete */
|
||||
$deleteOldSites = [];
|
||||
|
||||
// Loop through all update sites
|
||||
foreach ($updateSiteIds as $id)
|
||||
{
|
||||
$query = $db->getQuery(true)
|
||||
->select('*')
|
||||
->from($db->qn('#__update_sites'))
|
||||
->where($db->qn('update_site_id') . ' = ' . $db->q($id));
|
||||
$db->setQuery($query);
|
||||
$aSite = $db->loadObject();
|
||||
|
||||
if (empty($aSite))
|
||||
{
|
||||
// Update site is now up-to-date, don't need to refresh it anymore.
|
||||
continue;
|
||||
}
|
||||
|
||||
// We have an update site that looks like ours
|
||||
if ($needNewUpdateSite && ($aSite->name == $update_site['name']) && ($aSite->location == $update_site['location']))
|
||||
{
|
||||
$needNewUpdateSite = false;
|
||||
$mustUpdate = false;
|
||||
|
||||
// Is it enabled? If not, enable it.
|
||||
if (!$aSite->enabled)
|
||||
{
|
||||
$mustUpdate = true;
|
||||
$aSite->enabled = 1;
|
||||
}
|
||||
|
||||
// Do we have the extra_query property (J 3.2+) and does it match?
|
||||
if (property_exists($aSite, 'extra_query') && isset($update_site['extra_query'])
|
||||
&& ($aSite->extra_query != $update_site['extra_query']))
|
||||
{
|
||||
$mustUpdate = true;
|
||||
$aSite->extra_query = $update_site['extra_query'];
|
||||
}
|
||||
|
||||
// Update the update site if necessary
|
||||
if ($mustUpdate)
|
||||
{
|
||||
$db->updateObject('#__update_sites', $aSite, 'update_site_id', true);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// In any other case we need to delete this update site, it's obsolete
|
||||
$deleteOldSites[] = $aSite->update_site_id;
|
||||
}
|
||||
|
||||
if (!empty($deleteOldSites))
|
||||
{
|
||||
try
|
||||
{
|
||||
$obsoleteIDsQuoted = array_map([$db, 'quote'], $deleteOldSites);
|
||||
|
||||
// Delete update sites
|
||||
$query = $db->getQuery(true)
|
||||
->delete('#__update_sites')
|
||||
->where($db->qn('update_site_id') . ' IN (' . implode(',', $obsoleteIDsQuoted) . ')');
|
||||
$db->setQuery($query)->execute();
|
||||
|
||||
// Delete update sites to extension ID records
|
||||
$query = $db->getQuery(true)
|
||||
->delete('#__update_sites_extensions')
|
||||
->where($db->qn('update_site_id') . ' IN (' . implode(',', $obsoleteIDsQuoted) . ')');
|
||||
$db->setQuery($query)->execute();
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
// Do nothing on failure
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Do we still need to create a new update site?
|
||||
if ($needNewUpdateSite)
|
||||
{
|
||||
// No update sites defined. Create a new one.
|
||||
$newSite = (object) $update_site;
|
||||
$db->insertObject('#__update_sites', $newSite);
|
||||
|
||||
$id = $db->insertid();
|
||||
$updateSiteExtension = (object) [
|
||||
'update_site_id' => $id,
|
||||
'extension_id' => $this->extension_id,
|
||||
];
|
||||
$db->insertObject('#__update_sites_extensions', $updateSiteExtension);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes any update sites which go by the same name or the same location as our update site but do not match the
|
||||
* extension ID.
|
||||
*/
|
||||
public function removeObsoleteUpdateSites()
|
||||
{
|
||||
$db = $this->container->db;
|
||||
|
||||
// Get update site IDs
|
||||
$updateSiteIDs = $this->getUpdateSiteIds();
|
||||
|
||||
// Find update sites where the name OR the location matches BUT they are not one of the update site IDs
|
||||
$query = $db->getQuery(true)
|
||||
->select($db->qn('update_site_id'))
|
||||
->from($db->qn('#__update_sites'))
|
||||
->where(
|
||||
'((' . $db->qn('name') . ' = ' . $db->q($this->updateSiteName) . ') OR ' .
|
||||
'(' . $db->qn('location') . ' = ' . $db->q($this->updateSite) . '))'
|
||||
);
|
||||
|
||||
if (!empty($updateSiteIDs))
|
||||
{
|
||||
$updateSitesQuoted = array_map([$db, 'quote'], $updateSiteIDs);
|
||||
$query->where($db->qn('update_site_id') . ' NOT IN (' . implode(',', $updateSitesQuoted) . ')');
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$ids = $db->setQuery($query)->loadColumn();
|
||||
|
||||
if (!empty($ids))
|
||||
{
|
||||
$obsoleteIDsQuoted = array_map([$db, 'quote'], $ids);
|
||||
|
||||
// Delete update sites
|
||||
$query = $db->getQuery(true)
|
||||
->delete('#__update_sites')
|
||||
->where($db->qn('update_site_id') . ' IN (' . implode(',', $obsoleteIDsQuoted) . ')');
|
||||
$db->setQuery($query)->execute();
|
||||
|
||||
// Delete update sites to extension ID records
|
||||
$query = $db->getQuery(true)
|
||||
->delete('#__update_sites_extensions')
|
||||
->where($db->qn('update_site_id') . ' IN (' . implode(',', $obsoleteIDsQuoted) . ')');
|
||||
$db->setQuery($query)->execute();
|
||||
}
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
// Do nothing on failure
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure that the version number cached in the #__extensions table is consistent with the version number set in
|
||||
* this model.
|
||||
*
|
||||
* @return bool True if we updated the version number cached in the #__extensions table.
|
||||
*
|
||||
* @since 3.1.2
|
||||
*/
|
||||
public function updatedCachedVersionNumber()
|
||||
{
|
||||
$extension = $this->getExtensionObject();
|
||||
|
||||
if (!is_object($extension))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
$data = json_decode($extension->manifest_cache, true);
|
||||
$mustUpdate = true;
|
||||
|
||||
if (isset($data['version']))
|
||||
{
|
||||
$mustUpdate = $this->version != $data['version'];
|
||||
}
|
||||
|
||||
if (!$mustUpdate)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// The cached version is wrong; let's update it
|
||||
$data['version'] = $this->version;
|
||||
$extension->manifest_cache = json_encode($data);
|
||||
$db = $this->container->db;
|
||||
|
||||
return $db->updateObject('#__extensions', $extension, ['extension_id']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an object with the #__extensions table record for the current extension.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getExtensionObject()
|
||||
{
|
||||
[$extensionPrefix, $extensionName] = explode('_', $this->component);
|
||||
|
||||
switch ($extensionPrefix)
|
||||
{
|
||||
default:
|
||||
case 'com':
|
||||
$type = 'component';
|
||||
$name = $this->component;
|
||||
break;
|
||||
|
||||
case 'pkg':
|
||||
$type = 'package';
|
||||
$name = $this->component;
|
||||
break;
|
||||
}
|
||||
|
||||
// Find the extension ID
|
||||
$db = $this->container->db;
|
||||
$query = $db->getQuery(true)
|
||||
->select('*')
|
||||
->from($db->qn('#__extensions'))
|
||||
->where($db->qn('type') . ' = ' . $db->q($type))
|
||||
->where($db->qn('element') . ' = ' . $db->q($name));
|
||||
|
||||
try
|
||||
{
|
||||
$db->setQuery($query);
|
||||
$extension = $db->loadObject();
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return $extension;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the provided string a valid license key?
|
||||
*
|
||||
* YOU SHOULD OVERRIDE THIS METHOD. The default implementation checks for valid Download IDs in the format used by
|
||||
* Akeeba software.
|
||||
*
|
||||
* @param string $licenseKey
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isValidLicenseKey($licenseKey)
|
||||
{
|
||||
return preg_match('/^([0-9]{1,}:)?[0-9a-f]{32}$/i', $licenseKey) === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes the license key.
|
||||
*
|
||||
* YOU SHOULD OVERRIDE THIS METHOD. The default implementation returns a lowercase string with all characters except
|
||||
* letters, numbers and colons removed.
|
||||
*
|
||||
* @param string $licenseKey
|
||||
*
|
||||
* @return string The sanitized license key
|
||||
*/
|
||||
public function sanitizeLicenseKey($licenseKey)
|
||||
{
|
||||
return strtolower(preg_replace("/[^a-zA-Z0-9:]/", "", $licenseKey));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the component Options key which holds a copy of the license key
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getLegacyParamsKey()
|
||||
{
|
||||
if (!empty($this->paramsKey))
|
||||
{
|
||||
return $this->paramsKey;
|
||||
}
|
||||
|
||||
$this->paramsKey = 'update_dlid';
|
||||
|
||||
return $this->paramsKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the download ID from an extra_query based on the prefix and suffix information stored in the dlid element
|
||||
* of the extension's XML manifest file.
|
||||
*
|
||||
* @param string $extra_query
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getLicenseKeyFromExtraQuery($extra_query)
|
||||
{
|
||||
$extra_query = trim($extra_query);
|
||||
|
||||
if (empty($extra_query))
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
// Get the extension XML manifest. If the extension or the manifest don't exist return an empty string.
|
||||
$extension = $this->getExtensionObject();
|
||||
|
||||
if (!$extension)
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
$installXmlFile = $this->getManifestXML(
|
||||
$extension->element,
|
||||
$extension->type,
|
||||
(int) $extension->client_id,
|
||||
$extension->folder
|
||||
);
|
||||
|
||||
if (!$installXmlFile)
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
// If the manifest does not have a dlid element return an empty string.
|
||||
if (!isset($installXmlFile->dlid))
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
// Naive parsing of the extra_query, the same way Joomla does.
|
||||
$prefix = (string) $installXmlFile->dlid['prefix'];
|
||||
$suffix = (string) $installXmlFile->dlid['suffix'];
|
||||
$licenseKey = substr($extra_query, strlen($prefix));
|
||||
|
||||
if ($licenseKey === false)
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
if ($suffix)
|
||||
{
|
||||
$licenseKey = substr($licenseKey, 0, -strlen($suffix));
|
||||
}
|
||||
|
||||
return ($licenseKey === false) ? '' : $licenseKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a legacy extra query string. Do NOT call this directly. Call getExtraQueryString() instead.
|
||||
*
|
||||
* YOU SHOULD OVERRIDE THIS METHOD. This returns dlid=SANITIZED_LICENSE_KEY which is what Akeeba Release System,
|
||||
* used to deliver all Akeeba extensions, expects.
|
||||
*
|
||||
* @param string $licenseKey The license key
|
||||
*
|
||||
* @return string The extra_query string to append to a downlaod URL to implement the license key
|
||||
*/
|
||||
protected function getExtraQueryStringLegacy($licenseKey)
|
||||
{
|
||||
if (empty($licenseKey) || !$this->isValidLicenseKey($licenseKey))
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
return 'dlid=' . $this->sanitizeLicenseKey($licenseKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the manifest XML file of a given extension.
|
||||
*
|
||||
* @param string $element element of an extension
|
||||
* @param string $type type of an extension
|
||||
* @param integer $client_id client_id of an extension
|
||||
* @param string $folder folder of an extension
|
||||
*
|
||||
* @return SimpleXMLElement
|
||||
*/
|
||||
protected function getManifestXML($element, $type, $client_id = 1, $folder = null)
|
||||
{
|
||||
$path = $client_id ? JPATH_ADMINISTRATOR : JPATH_ROOT;
|
||||
|
||||
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';
|
||||
}
|
||||
|
||||
return simplexml_load_file($path);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user