1206 lines
32 KiB
PHP
1206 lines
32 KiB
PHP
<?php
|
|
/**
|
|
* @package FrameworkOnFramework
|
|
* @subpackage utils
|
|
* @copyright Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
|
|
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
|
*/
|
|
|
|
// Protect from unauthorized access
|
|
defined('F0F_INCLUDED') or die;
|
|
|
|
if (version_compare(JVERSION, '2.5.0', 'lt'))
|
|
{
|
|
jimport('joomla.updater.updater');
|
|
}
|
|
|
|
/**
|
|
* A helper Model to interact with Joomla!'s extensions update feature
|
|
*/
|
|
class F0FUtilsUpdate extends F0FModel
|
|
{
|
|
/** @var JUpdater 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 machine readable name of the component e.g. com_something */
|
|
protected $component = 'com_foobar';
|
|
|
|
/** @var string The human readable name of the component e.g. Your Component's Name. Used for emails. */
|
|
protected $componentDescription = '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 common parameters' key, used for storing data in the #__akeeba_common table */
|
|
protected $commonKey = 'foobar';
|
|
|
|
/**
|
|
* The common parameters table. It's a simple table with key(VARCHAR) and value(LONGTEXT) fields.
|
|
* Here is an example MySQL CREATE TABLE command to make this kind of table:
|
|
*
|
|
* CREATE TABLE `#__akeeba_common` (
|
|
* `key` varchar(255) NOT NULL,
|
|
* `value` longtext NOT NULL,
|
|
* PRIMARY KEY (`key`)
|
|
* ) DEFAULT COLLATE utf8_general_ci CHARSET=utf8;
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $commonTable = '#__akeeba_common';
|
|
|
|
/**
|
|
* Subject of the component update emails
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $updateEmailSubject = 'THIS EMAIL IS SENT FROM YOUR SITE "[SITENAME]" - Update available for [COMPONENT], new version [VERSION]';
|
|
|
|
/**
|
|
* Body of the component update email
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $updateEmailBody= <<< ENDBLOCK
|
|
This email IS NOT sent by the authors of [COMPONENT].
|
|
It is sent automatically by your own site, [SITENAME].
|
|
|
|
================================================================================
|
|
UPDATE INFORMATION
|
|
================================================================================
|
|
|
|
Your site has determined that there is an updated version of [COMPONENT]
|
|
available for download.
|
|
|
|
New version number: [VERSION]
|
|
|
|
This email is sent to you by your site to remind you of this fact. The authors
|
|
of the software will never contact you about available updates.
|
|
|
|
================================================================================
|
|
WHY AM I RECEIVING THIS EMAIL?
|
|
================================================================================
|
|
|
|
This email has been automatically sent by a CLI script or Joomla! plugin you, or
|
|
the person who built or manages your site, has installed and explicitly
|
|
activated. This script or plugin looks for updated versions of the software and
|
|
sends an email notification to all Super Users. You will receive several similar
|
|
emails from your site, up to 6 times per day, until you either update the
|
|
software or disable these emails.
|
|
|
|
To disable these emails, please contact your site administrator.
|
|
|
|
If you do not understand what this means, please do not contact the authors of
|
|
the software. They are NOT sending you this email and they cannot help you.
|
|
Instead, please contact the person who built or manages your site.
|
|
|
|
================================================================================
|
|
WHO SENT ME THIS EMAIL?
|
|
================================================================================
|
|
|
|
This email is sent to you by your own site, [SITENAME]
|
|
ENDBLOCK;
|
|
|
|
/**
|
|
* 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)
|
|
*
|
|
* @param array $config
|
|
*/
|
|
public function __construct($config = array())
|
|
{
|
|
parent::__construct($config);
|
|
|
|
// Get an instance of the updater class
|
|
$this->updater = JUpdater::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 description
|
|
if (isset($config['update_component_description']))
|
|
{
|
|
$this->component = $config['update_component_description'];
|
|
}
|
|
else
|
|
{
|
|
// Try to auto-translate (hopefully you've loaded the language files)
|
|
$key = strtoupper($this->component);
|
|
$description = JText::_($key);
|
|
}
|
|
|
|
// Get the component version
|
|
if (isset($config['update_version']))
|
|
{
|
|
$this->version = $config['update_version'];
|
|
}
|
|
|
|
// Get the common key
|
|
if (isset($config['common_key']))
|
|
{
|
|
$this->commonKey = $config['common_key'];
|
|
}
|
|
else
|
|
{
|
|
// Convert com_foobar, pkg_foobar etc to "foobar"
|
|
$this->commonKey = substr($this->component, 4);
|
|
}
|
|
|
|
// 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 update site's name
|
|
if (isset($config['update_sitename']))
|
|
{
|
|
$this->updateSiteName = $config['update_sitename'];
|
|
}
|
|
|
|
// Find the extension ID
|
|
$db = F0FPlatform::getInstance()->getDbo();
|
|
$query = $db->getQuery(true)
|
|
->select('*')
|
|
->from($db->qn('#__extensions'))
|
|
->where($db->qn('type') . ' = ' . $db->q('component'))
|
|
->where($db->qn('element') . ' = ' . $db->q($this->component));
|
|
$db->setQuery($query);
|
|
$extension = $db->loadObject();
|
|
|
|
if (is_object($extension))
|
|
{
|
|
$this->extension_id = $extension->extension_id;
|
|
$data = json_decode($extension->manifest_cache, true);
|
|
|
|
if (isset($data['version']))
|
|
{
|
|
$this->version = $data['version'];
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
* @param string $preferredMethod Preferred update method: 'joomla' or 'classic'
|
|
*
|
|
* @return array See the method description for more information
|
|
*/
|
|
public function getUpdates($force = false, $preferredMethod = null)
|
|
{
|
|
// Default response (no update)
|
|
$updateResponse = array(
|
|
'hasUpdate' => false,
|
|
'version' => '',
|
|
'infoURL' => '',
|
|
'downloadURL' => '',
|
|
);
|
|
|
|
if (empty($this->extension_id))
|
|
{
|
|
return $updateResponse;
|
|
}
|
|
|
|
$updateRecord = $this->findUpdates($force, $preferredMethod);
|
|
|
|
// If we have an update record in the database return the information found there
|
|
if (is_object($updateRecord))
|
|
{
|
|
$updateResponse = array(
|
|
'hasUpdate' => true,
|
|
'version' => $updateRecord->version,
|
|
'infoURL' => $updateRecord->infourl,
|
|
'downloadURL' => $updateRecord->downloadurl,
|
|
);
|
|
}
|
|
|
|
return $updateResponse;
|
|
}
|
|
|
|
/**
|
|
* Find the available update record object. If we're at the latest version it will return null.
|
|
*
|
|
* Please see getUpdateMethod for information on how the $preferredMethod is handled and what it means.
|
|
*
|
|
* @param bool $force Should I forcibly reload the updates from the server?
|
|
* @param string $preferredMethod Preferred update method: 'joomla' or 'classic'
|
|
*
|
|
* @return \stdClass|null
|
|
*/
|
|
public function findUpdates($force, $preferredMethod = null)
|
|
{
|
|
$preferredMethod = $this->getUpdateMethod($preferredMethod);
|
|
|
|
switch ($preferredMethod)
|
|
{
|
|
case 'joomla':
|
|
return $this->findUpdatesJoomla($force);
|
|
break;
|
|
|
|
default:
|
|
case 'classic':
|
|
return $this->findUpdatesClassic($force);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the update site Ids for our extension.
|
|
*
|
|
* @return mixed An array of Ids or null if the query failed.
|
|
*/
|
|
public function getUpdateSiteIds()
|
|
{
|
|
$db = F0FPlatform::getInstance()->getDbo();
|
|
$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);
|
|
$updateSiteIds = $db->loadColumn(0);
|
|
|
|
return $updateSiteIds;
|
|
}
|
|
|
|
/**
|
|
* Get the currently installed version as reported by the #__extensions table
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getVersion()
|
|
{
|
|
return $this->version;
|
|
}
|
|
|
|
/**
|
|
* Returns the name of the component, e.g. com_foobar
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getComponentName()
|
|
{
|
|
return $this->component;
|
|
}
|
|
|
|
/**
|
|
* Returns the human readable component name, e.g. Foobar Component
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getComponentDescription()
|
|
{
|
|
return $this->componentDescription;
|
|
}
|
|
|
|
/**
|
|
* Returns the numeric extension ID for the component
|
|
*
|
|
* @return int
|
|
*/
|
|
public function getExtensionId()
|
|
{
|
|
return $this->extension_id;
|
|
}
|
|
|
|
/**
|
|
* Returns the update site URL, i.e. the URL to the XML update stream
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getUpdateSite()
|
|
{
|
|
return $this->updateSite;
|
|
}
|
|
|
|
/**
|
|
* Returns the human readable description of the update site
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getUpdateSiteName()
|
|
{
|
|
return $this->updateSiteName;
|
|
}
|
|
|
|
/**
|
|
* 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()
|
|
{
|
|
// Joomla! 1.5 does not have update sites.
|
|
if (version_compare(JVERSION, '1.6.0', 'lt'))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (empty($this->extension_id))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Remove obsolete update sites that don't match our extension ID but match our name or update site location
|
|
$this->removeObsoleteUpdateSites();
|
|
|
|
// Create the update site definition we want to store to the database
|
|
$update_site = array(
|
|
'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 = F0FPlatform::getInstance()->getDbo();
|
|
|
|
// Get the #__update_sites columns
|
|
$columns = $db->getTableColumns('#__update_sites', true);
|
|
|
|
if (version_compare(JVERSION, '3.2.0', 'lt') || !array_key_exists('extra_query', $columns))
|
|
{
|
|
unset($update_site['extra_query']);
|
|
}
|
|
|
|
if (version_compare(JVERSION, '2.5.0', 'lt') || !array_key_exists('extra_query', $columns))
|
|
{
|
|
unset($update_site['last_check_timestamp']);
|
|
}
|
|
|
|
// Get the update sites for our extension
|
|
$updateSiteIds = $this->getUpdateSiteIds();
|
|
|
|
if (empty($updateSiteIds))
|
|
{
|
|
$updateSiteIds = array();
|
|
}
|
|
|
|
/** @var boolean $needNewUpdateSite Do I need to create a new update site? */
|
|
$needNewUpdateSite = true;
|
|
|
|
/** @var int[] $deleteOldSites Old Site IDs to delete */
|
|
$deleteOldSites = array();
|
|
|
|
// 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(array($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)array(
|
|
'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->getDbo();
|
|
|
|
// 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(array($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(array($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;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the update method we should use, 'joomla' or 'classic'
|
|
*
|
|
* You can defined the preferred update method: 'joomla' uses JUpdater whereas 'classic' handles update caching and
|
|
* parsing internally. If you are on Joomla! 3.1 or earlier this option is forced to 'classic' since these old
|
|
* Joomla! versions couldn't handle updates of commercial components correctly (that's why I contributed the fix to
|
|
* that problem, the extra_query field that's present in Joomla! 3.2 onwards).
|
|
*
|
|
* If 'classic' is defined then it will be used in *all* Joomla! versions. It's the most stable method for fetching
|
|
* update information.
|
|
*
|
|
* @param string $preferred Preferred update method. One of 'joomla' or 'classic'.
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getUpdateMethod($preferred = null)
|
|
{
|
|
$method = $preferred;
|
|
|
|
// Make sure the update fetch method is valid, otherwise load the component's "update_method" parameter.
|
|
$validMethods = array('joomla', 'classic');
|
|
|
|
if (!in_array($method, $validMethods))
|
|
{
|
|
$method = F0FUtilsConfigHelper::getComponentConfigurationValue($this->component, 'update_method', 'joomla');
|
|
}
|
|
|
|
// We can't handle updates using Joomla!'s extensions updater in Joomla! 3.1 and earlier
|
|
if (($method == 'joomla') && version_compare(JVERSION, '3.2.0', 'lt'))
|
|
{
|
|
$method = 'classic';
|
|
}
|
|
|
|
return $method;
|
|
}
|
|
|
|
/**
|
|
* Find the available update record object. If we're at the latest version it will return null.
|
|
*
|
|
* @param bool $force Should I forcibly reload the updates from the server?
|
|
*
|
|
* @return \stdClass|null
|
|
*/
|
|
protected function findUpdatesJoomla($force = false)
|
|
{
|
|
$db = F0FPlatform::getInstance()->getDbo();
|
|
|
|
// 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 null;
|
|
}
|
|
|
|
// Set the last_check_timestamp to 0
|
|
if (version_compare(JVERSION, '2.5.0', 'ge'))
|
|
{
|
|
$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
|
|
$timeout = 3600 * F0FUtilsConfigHelper::getComponentConfigurationValue('com_installer', '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);
|
|
|
|
try
|
|
{
|
|
$updateObject = $db->loadObject();
|
|
}
|
|
catch (Exception $e)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
if (!is_object($updateObject))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
$updateObject->downloadurl = '';
|
|
|
|
JLoader::import('joomla.updater.update');
|
|
|
|
if (class_exists('JUpdate'))
|
|
{
|
|
$update = new JUpdate();
|
|
$update->loadFromXML($updateObject->detailsurl);
|
|
|
|
if (isset($update->get('downloadurl')->_data))
|
|
{
|
|
$url = trim($update->downloadurl->_data);
|
|
|
|
$extra_query = isset($updateObject->extra_query) ? $updateObject->extra_query : $this->extraQuery;
|
|
|
|
if ($extra_query)
|
|
{
|
|
if (strpos($url, '?') === false)
|
|
{
|
|
$url .= '?';
|
|
}
|
|
else
|
|
{
|
|
$url .= '&';
|
|
}
|
|
|
|
$url .= $extra_query;
|
|
}
|
|
|
|
$updateObject->downloadurl = $url;
|
|
}
|
|
}
|
|
|
|
return $updateObject;
|
|
}
|
|
|
|
/**
|
|
* Find the available update record object. If we're at the latest version return null.
|
|
*
|
|
* @param bool $force Should I forcibly reload the updates from the server?
|
|
*
|
|
* @return \stdClass|null
|
|
*/
|
|
protected function findUpdatesClassic($force = false)
|
|
{
|
|
$allUpdates = $this->loadUpdatesClassic($force);
|
|
|
|
if (empty($allUpdates))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
$bestVersion = '0.0.0';
|
|
$bestUpdate = null;
|
|
$bestUpdateObject = null;
|
|
|
|
foreach($allUpdates as $update)
|
|
{
|
|
if (!isset($update['version']))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (version_compare($bestVersion, $update['version'], 'lt'))
|
|
{
|
|
$bestVersion = $update['version'];
|
|
$bestUpdate = $update;
|
|
}
|
|
}
|
|
|
|
// If the current version is newer or equal to the best one, unset it. Otherwise the user will be always prompted to update
|
|
if(version_compare($this->version, $bestVersion, 'ge'))
|
|
{
|
|
$bestUpdate = null;
|
|
$bestVersion = '0.0.0';
|
|
}
|
|
|
|
if (!is_null($bestUpdate))
|
|
{
|
|
$url = '';
|
|
|
|
if (isset($bestUpdate['downloads']) && isset($bestUpdate['downloads'][0])
|
|
&& isset($bestUpdate['downloads'][0]['url']))
|
|
{
|
|
$url = $bestUpdate['downloads'][0]['url'];
|
|
}
|
|
|
|
if ($this->extraQuery)
|
|
{
|
|
if (strpos($url, '?') === false)
|
|
{
|
|
$url .= '?';
|
|
}
|
|
else
|
|
{
|
|
$url .= '&';
|
|
}
|
|
|
|
$url .= $this->extraQuery;
|
|
}
|
|
|
|
$bestUpdateObject = (object)array(
|
|
'update_id' => 0,
|
|
'update_site_id' => 0,
|
|
'extension_id' => $this->extension_id,
|
|
'name' => $this->updateSiteName,
|
|
'description' => $bestUpdate['description'],
|
|
'element' => $bestUpdate['element'],
|
|
'type' => $bestUpdate['type'],
|
|
'folder' => count($bestUpdate['folder']) ? $bestUpdate['folder'][0] : '',
|
|
'client_id' => isset($bestUpdate['client']) ? $bestUpdate['client'] : 0,
|
|
'version' => $bestUpdate['version'],
|
|
'data' => '',
|
|
'detailsurl' => $this->updateSite,
|
|
'infourl' => $bestUpdate['infourl']['url'],
|
|
'extra_query' => $this->extraQuery,
|
|
'downloadurl' => $url,
|
|
);
|
|
}
|
|
|
|
return $bestUpdateObject;
|
|
}
|
|
|
|
/**
|
|
* Load all available updates without going through JUpdate
|
|
*
|
|
* @param bool $force Should I forcibly reload the updates from the server?
|
|
*
|
|
* @return array
|
|
*/
|
|
protected function loadUpdatesClassic($force = false)
|
|
{
|
|
// Is the cache busted? If it is I set $force = true to make sure I download fresh updates
|
|
if (!$force)
|
|
{
|
|
// Get the cache timeout. On older Joomla! installations it will always default to 6 hours.
|
|
$timeout = 3600 * F0FUtilsConfigHelper::getComponentConfigurationValue('com_installer', 'cachetimeout', '6');
|
|
|
|
// Do I need to check for updates?
|
|
$lastCheck = $this->getCommonParameter('lastcheck', 0);
|
|
$now = time();
|
|
|
|
if (($now - $lastCheck) >= $timeout)
|
|
{
|
|
$force = true;
|
|
}
|
|
}
|
|
|
|
// Get the cached JSON-encoded updates list
|
|
$rawUpdates = $this->getCommonParameter('allUpdates', '');
|
|
|
|
// Am I forced to reload the XML file (explicitly or because the cache is busted)?
|
|
if ($force)
|
|
{
|
|
// Set the timestamp
|
|
$now = time();
|
|
$this->setCommonParameter('lastcheck', $now);
|
|
|
|
// Get all available updates
|
|
$updateHelper = new F0FUtilsUpdateExtension();
|
|
$updates = $updateHelper->getUpdatesFromExtension($this->updateSite);
|
|
|
|
// Save the raw updates list in the database
|
|
$rawUpdates = json_encode($updates);
|
|
$this->setCommonParameter('allUpdates', $rawUpdates);
|
|
}
|
|
|
|
// Decode the updates list
|
|
$updates = json_decode($rawUpdates, true);
|
|
|
|
// Walk through the updates and find the ones compatible with our Joomla! and PHP version
|
|
$compatibleUpdates = array();
|
|
|
|
// Get the Joomla! version family (e.g. 2.5)
|
|
$jVersion = JVERSION;
|
|
$jVersionParts = explode('.', $jVersion);
|
|
$jVersionShort = $jVersionParts[0] . '.' . $jVersionParts[1];
|
|
|
|
// Get the PHP version family (e.g. 5.6)
|
|
$phpVersion = PHP_VERSION;
|
|
$phpVersionParts = explode('.', $phpVersion);
|
|
$phpVersionShort = $phpVersionParts[0] . '.' . $phpVersionParts[1];
|
|
|
|
foreach ($updates as $update)
|
|
{
|
|
// No platform?
|
|
if (!isset($update['targetplatform']))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Wrong platform?
|
|
if ($update['targetplatform']['name'] != 'joomla')
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Get the target Joomla! version
|
|
$targetJoomlaVersion = $update['targetplatform']['version'];
|
|
$targetVersionParts = explode('.', $targetJoomlaVersion);
|
|
$targetVersionShort = $targetVersionParts[0] . '.' . $targetVersionParts[1];
|
|
|
|
// The target version MUST be in the same Joomla! branch
|
|
if ($jVersionShort != $targetVersionShort)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// If the target version is major.minor.revision we must make sure our current JVERSION is AT LEAST equal to that.
|
|
if (version_compare($targetJoomlaVersion, JVERSION, 'gt'))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Do I have target PHP versions?
|
|
if (isset($update['ars-phpcompat']))
|
|
{
|
|
$phpCompatible = false;
|
|
|
|
foreach ($update['ars-phpcompat'] as $entry)
|
|
{
|
|
// Get the target PHP version family
|
|
$targetPHPVersion = $entry['@attributes']['version'];
|
|
$targetPHPVersionParts = explode('.', $targetPHPVersion);
|
|
$targetPHPVersionShort = $targetPHPVersionParts[0] . '.' . $targetPHPVersionParts[1];
|
|
|
|
// The target PHP version MUST be in the same PHP branch
|
|
if ($phpVersionShort != $targetPHPVersionShort)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// If the target version is major.minor.revision we must make sure our current PHP_VERSION is AT LEAST equal to that.
|
|
if (version_compare($targetPHPVersion, PHP_VERSION, 'gt'))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
$phpCompatible = true;
|
|
break;
|
|
}
|
|
|
|
if (!$phpCompatible)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// All checks pass. Add this update to the list of compatible updates.
|
|
$compatibleUpdates[] = $update;
|
|
}
|
|
|
|
return $compatibleUpdates;
|
|
}
|
|
|
|
/**
|
|
* Get a common parameter from the #__akeeba_common table
|
|
*
|
|
* @param string $key The key to retrieve
|
|
* @param mixed $default The default value in case none is set
|
|
*
|
|
* @return mixed The saved parameter value (or $default, if nothing is currently set)
|
|
*/
|
|
protected function getCommonParameter($key, $default = null)
|
|
{
|
|
$dbKey = $this->commonKey . '_autoupdate_' . $key;
|
|
|
|
$db = F0FPlatform::getInstance()->getDbo();
|
|
|
|
$query = $db->getQuery(true)
|
|
->select($db->qn('value'))
|
|
->from($db->qn($this->commonTable))
|
|
->where($db->qn('key') . ' = ' . $db->q($dbKey));
|
|
|
|
$result = $db->setQuery($query)->loadResult();
|
|
|
|
if (!$result)
|
|
{
|
|
return $default;
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Set a common parameter from the #__akeeba_common table
|
|
*
|
|
* @param string $key The key to set
|
|
* @param mixed $value The value to set
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function setCommonParameter($key, $value)
|
|
{
|
|
$dbKey = $this->commonKey . '_autoupdate_' . $key;
|
|
|
|
$db = F0FPlatform::getInstance()->getDbo();
|
|
|
|
$query = $db->getQuery(true)
|
|
->select('COUNT(*)')
|
|
->from($db->qn($this->commonTable))
|
|
->where($db->qn('key') . ' = ' . $db->q($dbKey));
|
|
$count = $db->setQuery($query)->loadResult();
|
|
|
|
if ($count)
|
|
{
|
|
$query = $db->getQuery(true)
|
|
->update($db->qn($this->commonTable))
|
|
->set($db->qn('value') . ' = ' . $db->q($value))
|
|
->where($db->qn('key') . ' = ' . $db->q($dbKey));
|
|
$db->setQuery($query)->execute();
|
|
}
|
|
else
|
|
{
|
|
$data = (object)array(
|
|
'key' => $dbKey,
|
|
'value' => $value,
|
|
);
|
|
|
|
$db->insertObject($this->commonTable, $data);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Proxy to updateComponent(). Required since old versions of our software had an updateComponent method declared
|
|
* private. If we set the updateComponent() method public we cause a fatal error.
|
|
*
|
|
* @return string
|
|
*/
|
|
public function doUpdateComponent()
|
|
{
|
|
return $this->updateComponent();
|
|
}
|
|
|
|
/**
|
|
* Automatically install the extension update under Joomla! 1.5.5 or later (web) / 3.0 or later (CLI).
|
|
*
|
|
* @return string The update message
|
|
*/
|
|
private function updateComponent()
|
|
{
|
|
$isCli = F0FPlatform::getInstance()->isCli();
|
|
$minVersion = $isCli ? '3.0.0' : '1.5.5';
|
|
$errorQualifier = $isCli ? ' using an unattended CLI CRON script ' : ' ';
|
|
|
|
if (version_compare(JVERSION, $minVersion, 'lt'))
|
|
{
|
|
return "Extension updates{$errorQualifier}only work with Joomla! $minVersion and later.";
|
|
}
|
|
|
|
try
|
|
{
|
|
$updatePackagePath = $this->downloadUpdate();
|
|
}
|
|
catch (Exception $e)
|
|
{
|
|
return $e->getMessage();
|
|
}
|
|
|
|
// Unpack the downloaded package file
|
|
jimport('joomla.installer.helper');
|
|
jimport('cms.installer.helper');
|
|
$package = JInstallerHelper::unpack($updatePackagePath);
|
|
|
|
if (!$package)
|
|
{
|
|
// Clean up
|
|
if (JFile::exists($updatePackagePath))
|
|
{
|
|
JFile::delete($updatePackagePath);
|
|
}
|
|
|
|
return "An error occurred while unpacking the file. Please double check your Joomla temp-directory setting in Global Configuration.";
|
|
}
|
|
|
|
$installer = new JInstaller;
|
|
$installed = $installer->install($package['extractdir']);
|
|
|
|
// Let's cleanup the downloaded archive and the temp folder
|
|
if (JFolder::exists($package['extractdir']))
|
|
{
|
|
JFolder::delete($package['extractdir']);
|
|
}
|
|
|
|
if (JFile::exists($package['packagefile']))
|
|
{
|
|
JFile::delete($package['packagefile']);
|
|
}
|
|
|
|
if ($installed)
|
|
{
|
|
return "Component successfully updated";
|
|
}
|
|
else
|
|
{
|
|
return "An error occurred while trying to update the component";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Downloads the latest update package to Joomla!'s temporary directory
|
|
*
|
|
* @return string The absolute path to the downloaded update package.
|
|
*/
|
|
public function downloadUpdate()
|
|
{
|
|
// Get the update URL
|
|
$updateInformation = $this->getUpdates();
|
|
$url = $updateInformation['downloadURL'];
|
|
|
|
if (empty($url))
|
|
{
|
|
throw new RuntimeException("No download URL was provided in the update information");
|
|
}
|
|
|
|
$config = JFactory::getConfig();
|
|
$tmp_dest = $config->get('tmp_path');
|
|
|
|
if (!$tmp_dest)
|
|
{
|
|
throw new RuntimeException("You must set a non-empty Joomla! temp-directory in Global Configuration before continuing.");
|
|
}
|
|
|
|
if (!JFolder::exists($tmp_dest))
|
|
{
|
|
throw new RuntimeException("Joomla!'s temp-directory does not exist. Please set the correct path in Global Configuration before continuing.");
|
|
}
|
|
|
|
// Get the target filename
|
|
$filename = $this->component . '.zip';
|
|
$filename = rtrim($tmp_dest, '\\/') . '/' . $filename;
|
|
|
|
try
|
|
{
|
|
$downloader = new F0FDownload();
|
|
$data = $downloader->getFromURL($url);
|
|
}
|
|
catch (Exception $e)
|
|
{
|
|
$code =$e->getCode();
|
|
$message =$e->getMessage();
|
|
throw new RuntimeException("An error occurred while trying to download the update package. Double check your Download ID and your server's network settings. The error message was: #$code: $message");
|
|
}
|
|
|
|
if (!JFile::write($filename, $data))
|
|
{
|
|
if (!file_put_contents($filename, $data))
|
|
{
|
|
throw new RuntimeException("Joomla!'s temp-directory is not writeable. Please check its permissions or set a different, writeable path in Global Configuration before continuing.");
|
|
}
|
|
}
|
|
|
|
return $filename;
|
|
}
|
|
|
|
/**
|
|
* Gets a file name out of a url
|
|
*
|
|
* @param string $url URL to get name from
|
|
*
|
|
* @return mixed String filename or boolean false if failed
|
|
*/
|
|
private function getFilenameFromURL($url)
|
|
{
|
|
if (is_string($url))
|
|
{
|
|
$parts = explode('/', $url);
|
|
|
|
return $parts[count($parts) - 1];
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Proxy to sendNotificationEmail(). Required since old versions of our software had a sendNotificationEmail method
|
|
* declared private. If we set the sendNotificationEmail() method public we cause a fatal error.
|
|
*
|
|
* @param string $version The new version of our software
|
|
* @param string $email The email address to send the notification to
|
|
*
|
|
* @return mixed The result of JMail::send()
|
|
*/
|
|
public function doSendNotificationEmail($version, $email)
|
|
{
|
|
return $this->sendNotificationEmail($version, $email);
|
|
}
|
|
|
|
/**
|
|
* Sends an update notification email
|
|
*
|
|
* @param string $version The new version of our software
|
|
* @param string $email The email address to send the notification to
|
|
*
|
|
* @return mixed The result of JMail::send()
|
|
*/
|
|
private function sendNotificationEmail($version, $email)
|
|
{
|
|
$email_subject = $this->updateEmailSubject;
|
|
$email_body = $this->updateEmailBody;
|
|
|
|
$jconfig = JFactory::getConfig();
|
|
$sitename = $jconfig->get('sitename');
|
|
|
|
$substitutions = array(
|
|
'[VERSION]' => $version,
|
|
'[SITENAME]' => $sitename,
|
|
'[COMPONENT]' => $this->componentDescription,
|
|
);
|
|
|
|
$email_subject = str_replace(array_keys($substitutions), array_values($substitutions), $email_subject);
|
|
$email_body = str_replace(array_keys($substitutions), array_values($substitutions), $email_body);
|
|
|
|
$mailer = JFactory::getMailer();
|
|
|
|
$mailfrom = $jconfig->get('mailfrom');
|
|
$fromname = $jconfig->get('fromname');
|
|
|
|
$mailer->setSender(array( $mailfrom, $fromname ));
|
|
$mailer->addRecipient($email);
|
|
$mailer->setSubject($email_subject);
|
|
$mailer->setBody($email_body);
|
|
|
|
return $mailer->Send();
|
|
}
|
|
} |