2151 lines
49 KiB
PHP
2151 lines
49 KiB
PHP
<?php
|
|
/**
|
|
* @package Joomla.Libraries
|
|
* @subpackage Installer
|
|
*
|
|
* @copyright Copyright (C) 2005 - 2013 Open Source Matters, Inc. All rights reserved.
|
|
* @license GNU General Public License version 2 or later; see LICENSE
|
|
*/
|
|
|
|
defined('JPATH_PLATFORM') or die;
|
|
|
|
jimport('joomla.filesystem.file');
|
|
jimport('joomla.filesystem.folder');
|
|
jimport('joomla.filesystem.path');
|
|
jimport('joomla.base.adapter');
|
|
|
|
/**
|
|
* Joomla base installer class
|
|
*
|
|
* @package Joomla.Libraries
|
|
* @subpackage Installer
|
|
* @since 3.1
|
|
*/
|
|
class JInstaller extends JAdapter
|
|
{
|
|
/**
|
|
* Array of paths needed by the installer
|
|
*
|
|
* @var array
|
|
* @since 12.1
|
|
*/
|
|
protected $paths = array();
|
|
|
|
/**
|
|
* True if package is an upgrade
|
|
*
|
|
* @var boolean
|
|
* @since 12.1
|
|
*/
|
|
protected $upgrade = null;
|
|
|
|
/**
|
|
* The manifest trigger class
|
|
*
|
|
* @var object
|
|
* @since 3.1
|
|
*/
|
|
public $manifestClass = null;
|
|
|
|
/**
|
|
* True if existing files can be overwritten
|
|
* @var boolean
|
|
* @since 12.1
|
|
*/
|
|
protected $overwrite = false;
|
|
|
|
/**
|
|
* Stack of installation steps
|
|
* - Used for installation rollback
|
|
*
|
|
* @var array
|
|
* @since 12.1
|
|
*/
|
|
protected $stepStack = array();
|
|
|
|
/**
|
|
* Extension Table Entry
|
|
*
|
|
* @var JTableExtension
|
|
* @since 3.1
|
|
*/
|
|
public $extension = null;
|
|
|
|
/**
|
|
* The output from the install/uninstall scripts
|
|
*
|
|
* @var string
|
|
* @since 3.1
|
|
* */
|
|
public $message = null;
|
|
|
|
/**
|
|
* The installation manifest XML object
|
|
*
|
|
* @var object
|
|
* @since 3.1
|
|
*/
|
|
public $manifest = null;
|
|
|
|
/**
|
|
* The extension message that appears
|
|
*
|
|
* @var string
|
|
* @since 3.1
|
|
*/
|
|
protected $extension_message = null;
|
|
|
|
/**
|
|
* The redirect URL if this extension (can be null if no redirect)
|
|
*
|
|
* @var string
|
|
* @since 3.1
|
|
*/
|
|
protected $redirect_url = null;
|
|
|
|
/**
|
|
* @var JInstaller JInstaller instance container.
|
|
* @since 3.1
|
|
*/
|
|
protected static $instance;
|
|
|
|
/**
|
|
* Constructor
|
|
*
|
|
* @since 3.1
|
|
*/
|
|
public function __construct()
|
|
{
|
|
parent::__construct(__DIR__, 'JInstallerAdapter', __DIR__ . '/adapter');
|
|
|
|
// Override the default adapter folder
|
|
$this->_adapterfolder = 'adapter';
|
|
}
|
|
|
|
/**
|
|
* Returns the global Installer object, only creating it
|
|
* if it doesn't already exist.
|
|
*
|
|
* @return JInstaller An installer object
|
|
*
|
|
* @since 3.1
|
|
*/
|
|
public static function getInstance()
|
|
{
|
|
if (!isset(self::$instance))
|
|
{
|
|
self::$instance = new JInstaller;
|
|
}
|
|
return self::$instance;
|
|
}
|
|
|
|
/**
|
|
* Get the allow overwrite switch
|
|
*
|
|
* @return boolean Allow overwrite switch
|
|
*
|
|
* @since 3.1
|
|
*/
|
|
public function isOverwrite()
|
|
{
|
|
return $this->overwrite;
|
|
}
|
|
|
|
/**
|
|
* Set the allow overwrite switch
|
|
*
|
|
* @param boolean $state Overwrite switch state
|
|
*
|
|
* @return boolean True it state is set, false if it is not
|
|
*
|
|
* @since 3.1
|
|
*/
|
|
public function setOverwrite($state = false)
|
|
{
|
|
$tmp = $this->overwrite;
|
|
|
|
if ($state)
|
|
{
|
|
$this->overwrite = true;
|
|
}
|
|
else
|
|
{
|
|
$this->overwrite = false;
|
|
}
|
|
|
|
return $tmp;
|
|
}
|
|
|
|
/**
|
|
* Get the redirect location
|
|
*
|
|
* @return string Redirect location (or null)
|
|
*
|
|
* @since 3.1
|
|
*/
|
|
public function getRedirectURL()
|
|
{
|
|
return $this->redirect_url;
|
|
}
|
|
|
|
/**
|
|
* Set the redirect location
|
|
*
|
|
* @param string $newurl New redirect location
|
|
*
|
|
* @return void
|
|
*
|
|
* @since 3.1
|
|
*/
|
|
public function setRedirectURL($newurl)
|
|
{
|
|
$this->redirect_url = $newurl;
|
|
}
|
|
|
|
/**
|
|
* Get the upgrade switch
|
|
*
|
|
* @return boolean
|
|
*
|
|
* @since 3.1
|
|
*/
|
|
public function isUpgrade()
|
|
{
|
|
return $this->upgrade;
|
|
}
|
|
|
|
/**
|
|
* Set the upgrade switch
|
|
*
|
|
* @param boolean $state Upgrade switch state
|
|
*
|
|
* @return boolean True if upgrade, false otherwise
|
|
*
|
|
* @since 3.1
|
|
*/
|
|
public function setUpgrade($state = false)
|
|
{
|
|
$tmp = $this->upgrade;
|
|
|
|
if ($state)
|
|
{
|
|
$this->upgrade = true;
|
|
}
|
|
else
|
|
{
|
|
$this->upgrade = false;
|
|
}
|
|
|
|
return $tmp;
|
|
}
|
|
|
|
/**
|
|
* Get the installation manifest object
|
|
*
|
|
* @return object Manifest object
|
|
*
|
|
* @since 3.1
|
|
*/
|
|
public function getManifest()
|
|
{
|
|
if (!is_object($this->manifest))
|
|
{
|
|
$this->findManifest();
|
|
}
|
|
|
|
return $this->manifest;
|
|
}
|
|
|
|
/**
|
|
* Get an installer path by name
|
|
*
|
|
* @param string $name Path name
|
|
* @param string $default Default value
|
|
*
|
|
* @return string Path
|
|
*
|
|
* @since 3.1
|
|
*/
|
|
public function getPath($name, $default = null)
|
|
{
|
|
return (!empty($this->paths[$name])) ? $this->paths[$name] : $default;
|
|
}
|
|
|
|
/**
|
|
* Sets an installer path by name
|
|
*
|
|
* @param string $name Path name
|
|
* @param string $value Path
|
|
*
|
|
* @return void
|
|
*
|
|
* @since 3.1
|
|
*/
|
|
public function setPath($name, $value)
|
|
{
|
|
$this->paths[$name] = $value;
|
|
}
|
|
|
|
/**
|
|
* Pushes a step onto the installer stack for rolling back steps
|
|
*
|
|
* @param array $step Installer step
|
|
*
|
|
* @return void
|
|
*
|
|
* @since 3.1
|
|
*/
|
|
public function pushStep($step)
|
|
{
|
|
$this->stepStack[] = $step;
|
|
}
|
|
|
|
/**
|
|
* Installation abort method
|
|
*
|
|
* @param string $msg Abort message from the installer
|
|
* @param string $type Package type if defined
|
|
*
|
|
* @return boolean True if successful
|
|
*
|
|
* @since 3.1
|
|
* @throws RuntimeException
|
|
*/
|
|
public function abort($msg = null, $type = null)
|
|
{
|
|
$retval = true;
|
|
$step = array_pop($this->stepStack);
|
|
|
|
// Raise abort warning
|
|
if ($msg)
|
|
{
|
|
JLog::add($msg, JLog::WARNING, 'jerror');
|
|
}
|
|
|
|
while ($step != null)
|
|
{
|
|
switch ($step['type'])
|
|
{
|
|
case 'file':
|
|
// Remove the file
|
|
$stepval = JFile::delete($step['path']);
|
|
break;
|
|
|
|
case 'folder':
|
|
// Remove the folder
|
|
$stepval = JFolder::delete($step['path']);
|
|
break;
|
|
|
|
case 'query':
|
|
// Placeholder in case this is necessary in the future
|
|
// $stepval is always false because if this step was called it invariably failed
|
|
$stepval = false;
|
|
break;
|
|
|
|
case 'extension':
|
|
// Get database connector object
|
|
$db = $this->getDBO();
|
|
$query = $db->getQuery(true);
|
|
|
|
// Remove the entry from the #__extensions table
|
|
$query->delete($db->quoteName('#__extensions'))
|
|
->where($db->quoteName('extension_id') . ' = ' . (int) $step['id']);
|
|
$db->setQuery($query);
|
|
$stepval = $db->execute();
|
|
|
|
break;
|
|
|
|
default:
|
|
if ($type && is_object($this->_adapters[$type]))
|
|
{
|
|
// Build the name of the custom rollback method for the type
|
|
$method = '_rollback_' . $step['type'];
|
|
|
|
// Custom rollback method handler
|
|
if (method_exists($this->_adapters[$type], $method))
|
|
{
|
|
$stepval = $this->_adapters[$type]->$method($step);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Set it to false
|
|
$stepval = false;
|
|
}
|
|
break;
|
|
}
|
|
|
|
// Only set the return value if it is false
|
|
if ($stepval === false)
|
|
{
|
|
$retval = false;
|
|
}
|
|
|
|
// Get the next step and continue
|
|
$step = array_pop($this->stepStack);
|
|
}
|
|
|
|
$conf = JFactory::getConfig();
|
|
$debug = $conf->get('debug');
|
|
|
|
if ($debug)
|
|
{
|
|
throw new RuntimeException('Installation unexpectedly terminated: ' . $msg, 500);
|
|
}
|
|
|
|
return $retval;
|
|
}
|
|
|
|
// Adapter functions
|
|
|
|
/**
|
|
* Package installation method
|
|
*
|
|
* @param string $path Path to package source folder
|
|
*
|
|
* @return boolean True if successful
|
|
*
|
|
* @since 3.1
|
|
*/
|
|
public function install($path = null)
|
|
{
|
|
if ($path && JFolder::exists($path))
|
|
{
|
|
$this->setPath('source', $path);
|
|
}
|
|
else
|
|
{
|
|
$this->abort(JText::_('JLIB_INSTALLER_ABORT_NOINSTALLPATH'));
|
|
|
|
return false;
|
|
}
|
|
|
|
if (!$this->setupInstall())
|
|
{
|
|
$this->abort(JText::_('JLIB_INSTALLER_ABORT_DETECTMANIFEST'));
|
|
|
|
return false;
|
|
}
|
|
|
|
$type = (string) $this->manifest->attributes()->type;
|
|
|
|
if (is_object($this->_adapters[$type]))
|
|
{
|
|
// Add the languages from the package itself
|
|
if (method_exists($this->_adapters[$type], 'loadLanguage'))
|
|
{
|
|
$this->_adapters[$type]->loadLanguage($path);
|
|
}
|
|
|
|
// Fire the onExtensionBeforeInstall event.
|
|
JPluginHelper::importPlugin('extension');
|
|
$dispatcher = JEventDispatcher::getInstance();
|
|
$dispatcher->trigger(
|
|
'onExtensionBeforeInstall',
|
|
array('method' => 'install', 'type' => $type, 'manifest' => $this->manifest, 'extension' => 0)
|
|
);
|
|
|
|
// Run the install
|
|
$result = $this->_adapters[$type]->install();
|
|
|
|
// Fire the onExtensionAfterInstall
|
|
$dispatcher->trigger(
|
|
'onExtensionAfterInstall',
|
|
array('installer' => clone $this, 'eid' => $result)
|
|
);
|
|
|
|
if ($result !== false)
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Discovered package installation method
|
|
*
|
|
* @param integer $eid Extension ID
|
|
*
|
|
* @return boolean True if successful
|
|
*
|
|
* @since 3.1
|
|
*/
|
|
public function discover_install($eid = null)
|
|
{
|
|
if ($eid)
|
|
{
|
|
$this->extension = JTable::getInstance('extension');
|
|
|
|
if (!$this->extension->load($eid))
|
|
{
|
|
$this->abort(JText::_('JLIB_INSTALLER_ABORT_LOAD_DETAILS'));
|
|
|
|
return false;
|
|
}
|
|
|
|
if ($this->extension->state != -1)
|
|
{
|
|
$this->abort(JText::_('JLIB_INSTALLER_ABORT_ALREADYINSTALLED'));
|
|
|
|
return false;
|
|
}
|
|
|
|
// Lazy load the adapter
|
|
if (!isset($this->_adapters[$this->extension->type]) || !is_object($this->_adapters[$this->extension->type]))
|
|
{
|
|
if (!$this->setAdapter($this->extension->type))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (is_object($this->_adapters[$this->extension->type]))
|
|
{
|
|
if (method_exists($this->_adapters[$this->extension->type], 'discover_install'))
|
|
{
|
|
// Add the languages from the package itself
|
|
if (method_exists($this->_adapters[$this->extension->type], 'loadLanguage'))
|
|
{
|
|
$this->_adapters[$this->extension->type]->loadLanguage();
|
|
}
|
|
|
|
// Fire the onExtensionBeforeInstall event.
|
|
JPluginHelper::importPlugin('extension');
|
|
$dispatcher = JEventDispatcher::getInstance();
|
|
$dispatcher->trigger(
|
|
'onExtensionBeforeInstall',
|
|
array(
|
|
'method' => 'discover_install',
|
|
'type' => $this->extension->get('type'),
|
|
'manifest' => null,
|
|
'extension' => $this->extension->get('extension_id')
|
|
)
|
|
);
|
|
|
|
// Run the install
|
|
$result = $this->_adapters[$this->extension->type]->discover_install();
|
|
|
|
// Fire the onExtensionAfterInstall
|
|
$dispatcher->trigger(
|
|
'onExtensionAfterInstall',
|
|
array('installer' => clone $this, 'eid' => $result)
|
|
);
|
|
|
|
if ($result !== false)
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$this->abort(JText::_('JLIB_INSTALLER_ABORT_METHODNOTSUPPORTED'));
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
$this->abort(JText::_('JLIB_INSTALLER_ABORT_EXTENSIONNOTVALID'));
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Extension discover method
|
|
* Asks each adapter to find extensions
|
|
*
|
|
* @return array JExtension
|
|
*
|
|
* @since 3.1
|
|
*/
|
|
public function discover()
|
|
{
|
|
$this->loadAllAdapters();
|
|
$results = array();
|
|
|
|
foreach ($this->_adapters as $adapter)
|
|
{
|
|
// Joomla! 1.5 installation adapter legacy support
|
|
if (method_exists($adapter, 'discover'))
|
|
{
|
|
$tmp = $adapter->discover();
|
|
|
|
// If its an array and has entries
|
|
if (is_array($tmp) && count($tmp))
|
|
{
|
|
// Merge it into the system
|
|
$results = array_merge($results, $tmp);
|
|
}
|
|
}
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
/**
|
|
* Package update method
|
|
*
|
|
* @param string $path Path to package source folder
|
|
*
|
|
* @return boolean True if successful
|
|
*
|
|
* @since 3.1
|
|
*/
|
|
public function update($path = null)
|
|
{
|
|
if ($path && JFolder::exists($path))
|
|
{
|
|
$this->setPath('source', $path);
|
|
}
|
|
else
|
|
{
|
|
$this->abort(JText::_('JLIB_INSTALLER_ABORT_NOUPDATEPATH'));
|
|
return false;
|
|
}
|
|
|
|
if (!$this->setupInstall())
|
|
{
|
|
$this->abort(JText::_('JLIB_INSTALLER_ABORT_DETECTMANIFEST'));
|
|
return false;
|
|
}
|
|
|
|
$type = (string) $this->manifest->attributes()->type;
|
|
|
|
if (is_object($this->_adapters[$type]))
|
|
{
|
|
// Add the languages from the package itself
|
|
if (method_exists($this->_adapters[$type], 'loadLanguage'))
|
|
{
|
|
$this->_adapters[$type]->loadLanguage($path);
|
|
}
|
|
|
|
// Fire the onExtensionBeforeUpdate event.
|
|
JPluginHelper::importPlugin('extension');
|
|
$dispatcher = JEventDispatcher::getInstance();
|
|
$dispatcher->trigger('onExtensionBeforeUpdate', array('type' => $type, 'manifest' => $this->manifest));
|
|
|
|
// Run the update
|
|
$result = $this->_adapters[$type]->update();
|
|
|
|
// Fire the onExtensionAfterUpdate
|
|
$dispatcher->trigger(
|
|
'onExtensionAfterUpdate',
|
|
array('installer' => clone $this, 'eid' => $result)
|
|
);
|
|
|
|
if ($result !== false)
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Package uninstallation method
|
|
*
|
|
* @param string $type Package type
|
|
* @param mixed $identifier Package identifier for adapter
|
|
* @param integer $cid Application ID; deprecated in 1.6
|
|
*
|
|
* @return boolean True if successful
|
|
*
|
|
* @since 3.1
|
|
*/
|
|
public function uninstall($type, $identifier, $cid = 0)
|
|
{
|
|
if (!isset($this->_adapters[$type]) || !is_object($this->_adapters[$type]))
|
|
{
|
|
if (!$this->setAdapter($type))
|
|
{
|
|
// We failed to get the right adapter
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (is_object($this->_adapters[$type]))
|
|
{
|
|
// We don't load languages here, we get the extension adapter to work it out
|
|
// Fire the onExtensionBeforeUninstall event.
|
|
JPluginHelper::importPlugin('extension');
|
|
$dispatcher = JEventDispatcher::getInstance();
|
|
$dispatcher->trigger('onExtensionBeforeUninstall', array('eid' => $identifier));
|
|
|
|
// Run the uninstall
|
|
$result = $this->_adapters[$type]->uninstall($identifier);
|
|
|
|
// Fire the onExtensionAfterInstall
|
|
$dispatcher->trigger(
|
|
'onExtensionAfterUninstall',
|
|
array('installer' => clone $this, 'eid' => $identifier, 'result' => $result)
|
|
);
|
|
|
|
return $result;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Refreshes the manifest cache stored in #__extensions
|
|
*
|
|
* @param integer $eid Extension ID
|
|
*
|
|
* @return mixed void on success, false on error @todo missing return value ?
|
|
*
|
|
* @since 3.1
|
|
*/
|
|
public function refreshManifestCache($eid)
|
|
{
|
|
if ($eid)
|
|
{
|
|
$this->extension = JTable::getInstance('extension');
|
|
|
|
if (!$this->extension->load($eid))
|
|
{
|
|
$this->abort(JText::_('JLIB_INSTALLER_ABORT_LOAD_DETAILS'));
|
|
|
|
return false;
|
|
}
|
|
|
|
if ($this->extension->state == -1)
|
|
{
|
|
$this->abort(JText::_('JLIB_INSTALLER_ABORT_REFRESH_MANIFEST_CACHE'));
|
|
|
|
return false;
|
|
}
|
|
|
|
// Lazy load the adapter
|
|
if (!isset($this->_adapters[$this->extension->type]) || !is_object($this->_adapters[$this->extension->type]))
|
|
{
|
|
if (!$this->setAdapter($this->extension->type))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (is_object($this->_adapters[$this->extension->type]))
|
|
{
|
|
if (method_exists($this->_adapters[$this->extension->type], 'refreshManifestCache'))
|
|
{
|
|
$result = $this->_adapters[$this->extension->type]->refreshManifestCache();
|
|
|
|
if ($result !== false)
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$this->abort(JText::sprintf('JLIB_INSTALLER_ABORT_METHODNOTSUPPORTED_TYPE', $this->extension->type));
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
$this->abort(JText::_('JLIB_INSTALLER_ABORT_REFRESH_MANIFEST_CACHE_VALID'));
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Utility functions
|
|
|
|
/**
|
|
* Prepare for installation: this method sets the installation directory, finds
|
|
* and checks the installation file and verifies the installation type.
|
|
*
|
|
* @return boolean True on success
|
|
*
|
|
* @since 3.1
|
|
*/
|
|
public function setupInstall()
|
|
{
|
|
// We need to find the installation manifest file
|
|
if (!$this->findManifest())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Load the adapter(s) for the install manifest
|
|
$type = (string) $this->manifest->attributes()->type;
|
|
|
|
// Lazy load the adapter
|
|
if (!isset($this->_adapters[$type]) || !is_object($this->_adapters[$type]))
|
|
{
|
|
if (!$this->setAdapter($type))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Backward compatible method to parse through a queries element of the
|
|
* installation manifest file and take appropriate action.
|
|
*
|
|
* @param SimpleXMLElement $element The XML node to process
|
|
*
|
|
* @return mixed Number of queries processed or False on error
|
|
*
|
|
* @since 3.1
|
|
*/
|
|
public function parseQueries(SimpleXMLElement $element)
|
|
{
|
|
// Get the database connector object
|
|
$db = & $this->_db;
|
|
|
|
if (!$element || !count($element->children()))
|
|
{
|
|
// Either the tag does not exist or has no children therefore we return zero files processed.
|
|
return 0;
|
|
}
|
|
|
|
// Get the array of query nodes to process
|
|
$queries = $element->children();
|
|
|
|
if (count($queries) == 0)
|
|
{
|
|
// No queries to process
|
|
return 0;
|
|
}
|
|
|
|
// Process each query in the $queries array (children of $tagName).
|
|
foreach ($queries as $query)
|
|
{
|
|
$db->setQuery($query->data());
|
|
|
|
if (!$db->execute())
|
|
{
|
|
JLog::add(JText::sprintf('JLIB_INSTALLER_ERROR_SQL_ERROR', $db->stderr(true)), JLog::WARNING, 'jerror');
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return (int) count($queries);
|
|
}
|
|
|
|
/**
|
|
* Method to extract the name of a discreet installation sql file from the installation manifest file.
|
|
*
|
|
* @param object $element The XML node to process
|
|
*
|
|
* @return mixed Number of queries processed or False on error
|
|
*
|
|
* @since 3.1
|
|
*/
|
|
public function parseSQLFiles($element)
|
|
{
|
|
if (!$element || !count($element->children()))
|
|
{
|
|
// The tag does not exist.
|
|
return 0;
|
|
}
|
|
|
|
$queries = array();
|
|
$db = & $this->_db;
|
|
$dbDriver = strtolower($db->name);
|
|
|
|
if ($dbDriver == 'mysqli')
|
|
{
|
|
$dbDriver = 'mysql';
|
|
}
|
|
|
|
// Get the name of the sql file to process
|
|
foreach ($element->children() as $file)
|
|
{
|
|
$fCharset = (strtolower($file->attributes()->charset) == 'utf8') ? 'utf8' : '';
|
|
$fDriver = strtolower($file->attributes()->driver);
|
|
|
|
if ($fDriver == 'mysqli')
|
|
{
|
|
$fDriver = 'mysql';
|
|
}
|
|
|
|
if ($fCharset == 'utf8' && $fDriver == $dbDriver)
|
|
{
|
|
$sqlfile = $this->getPath('extension_root') . '/' . $file;
|
|
|
|
// Check that sql files exists before reading. Otherwise raise error for rollback
|
|
if (!file_exists($sqlfile))
|
|
{
|
|
JLog::add(JText::sprintf('JLIB_INSTALLER_ERROR_SQL_ERROR', $db->stderr(true)), JLog::WARNING, 'jerror');
|
|
|
|
return false;
|
|
}
|
|
|
|
$buffer = file_get_contents($sqlfile);
|
|
|
|
// Graceful exit and rollback if read not successful
|
|
if ($buffer === false)
|
|
{
|
|
JLog::add(JText::_('JLIB_INSTALLER_ERROR_SQL_READBUFFER'), JLog::WARNING, 'jerror');
|
|
|
|
return false;
|
|
}
|
|
|
|
// Create an array of queries from the sql file
|
|
$queries = JDatabaseDriver::splitSql($buffer);
|
|
|
|
if (count($queries) == 0)
|
|
{
|
|
// No queries to process
|
|
return 0;
|
|
}
|
|
|
|
// Process each query in the $queries array (split out of sql file).
|
|
foreach ($queries as $query)
|
|
{
|
|
$query = trim($query);
|
|
|
|
if ($query != '' && $query{0} != '#')
|
|
{
|
|
$db->setQuery($query);
|
|
|
|
if (!$db->execute())
|
|
{
|
|
JLog::add(JText::sprintf('JLIB_INSTALLER_ERROR_SQL_ERROR', $db->stderr(true)), JLog::WARNING, 'jerror');
|
|
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return (int) count($queries);
|
|
}
|
|
|
|
/**
|
|
* Set the schema version for an extension by looking at its latest update
|
|
*
|
|
* @param SimpleXMLElement $schema Schema Tag
|
|
* @param integer $eid Extension ID
|
|
*
|
|
* @return void
|
|
*
|
|
* @since 3.1
|
|
*/
|
|
public function setSchemaVersion(SimpleXMLElement $schema, $eid)
|
|
{
|
|
if ($eid && $schema)
|
|
{
|
|
$db = JFactory::getDbo();
|
|
$schemapaths = $schema->children();
|
|
|
|
if (!$schemapaths)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (count($schemapaths))
|
|
{
|
|
$dbDriver = strtolower($db->name);
|
|
|
|
if ($dbDriver == 'mysqli')
|
|
{
|
|
$dbDriver = 'mysql';
|
|
}
|
|
|
|
$schemapath = '';
|
|
|
|
foreach ($schemapaths as $entry)
|
|
{
|
|
$attrs = $entry->attributes();
|
|
|
|
if ($attrs['type'] == $dbDriver)
|
|
{
|
|
$schemapath = $entry;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (strlen($schemapath))
|
|
{
|
|
$files = str_replace('.sql', '', JFolder::files($this->getPath('extension_root') . '/' . $schemapath, '\.sql$'));
|
|
usort($files, 'version_compare');
|
|
|
|
// Update the database
|
|
$query = $db->getQuery(true)
|
|
->delete('#__schemas')
|
|
->where('extension_id = ' . $eid);
|
|
$db->setQuery($query);
|
|
|
|
if ($db->execute())
|
|
{
|
|
$query->clear()
|
|
->insert($db->quoteName('#__schemas'))
|
|
->columns(array($db->quoteName('extension_id'), $db->quoteName('version_id')))
|
|
->values($eid . ', ' . $db->quote(end($files)));
|
|
$db->setQuery($query);
|
|
$db->execute();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Method to process the updates for an item
|
|
*
|
|
* @param SimpleXMLElement $schema The XML node to process
|
|
* @param integer $eid Extension Identifier
|
|
*
|
|
* @return boolean Result of the operations
|
|
*
|
|
* @since 3.1
|
|
*/
|
|
public function parseSchemaUpdates(SimpleXMLElement $schema, $eid)
|
|
{
|
|
$update_count = 0;
|
|
|
|
// Ensure we have an XML element and a valid extension id
|
|
if ($eid && $schema)
|
|
{
|
|
$db = JFactory::getDbo();
|
|
$schemapaths = $schema->children();
|
|
|
|
if (count($schemapaths))
|
|
{
|
|
$dbDriver = strtolower($db->name);
|
|
|
|
if ($dbDriver == 'mysqli')
|
|
{
|
|
$dbDriver = 'mysql';
|
|
}
|
|
|
|
$schemapath = '';
|
|
|
|
foreach ($schemapaths as $entry)
|
|
{
|
|
$attrs = $entry->attributes();
|
|
|
|
if ($attrs['type'] == $dbDriver)
|
|
{
|
|
$schemapath = $entry;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (strlen($schemapath))
|
|
{
|
|
$files = str_replace('.sql', '', JFolder::files($this->getPath('extension_root') . '/' . $schemapath, '\.sql$'));
|
|
usort($files, 'version_compare');
|
|
|
|
if (!count($files))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
$query = $db->getQuery(true)
|
|
->select('version_id')
|
|
->from('#__schemas')
|
|
->where('extension_id = ' . $eid);
|
|
$db->setQuery($query);
|
|
$version = $db->loadResult();
|
|
|
|
if ($version)
|
|
{
|
|
// We have a version!
|
|
foreach ($files as $file)
|
|
{
|
|
if (version_compare($file, $version) > 0)
|
|
{
|
|
$buffer = file_get_contents($this->getPath('extension_root') . '/' . $schemapath . '/' . $file . '.sql');
|
|
|
|
// Graceful exit and rollback if read not successful
|
|
if ($buffer === false)
|
|
{
|
|
JLog::add(JText::sprintf('JLIB_INSTALLER_ERROR_SQL_READBUFFER'), JLog::WARNING, 'jerror');
|
|
|
|
return false;
|
|
}
|
|
|
|
// Create an array of queries from the sql file
|
|
$queries = JDatabaseDriver::splitSql($buffer);
|
|
|
|
if (count($queries) == 0)
|
|
{
|
|
// No queries to process
|
|
continue;
|
|
}
|
|
|
|
// Process each query in the $queries array (split out of sql file).
|
|
foreach ($queries as $q)
|
|
{
|
|
$q = trim($q);
|
|
|
|
if ($q != '' && $q{0} != '#')
|
|
{
|
|
$db->setQuery($q);
|
|
|
|
if (!$db->execute())
|
|
{
|
|
JLog::add(JText::sprintf('JLIB_INSTALLER_ERROR_SQL_ERROR', $db->stderr(true)), JLog::WARNING, 'jerror');
|
|
|
|
return false;
|
|
}
|
|
|
|
$update_count++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update the database
|
|
$query->clear()
|
|
->delete('#__schemas')
|
|
->where('extension_id = ' . $eid);
|
|
$db->setQuery($query);
|
|
|
|
if ($db->execute())
|
|
{
|
|
$query->clear()
|
|
->insert($db->quoteName('#__schemas'))
|
|
->columns(array($db->quoteName('extension_id'), $db->quoteName('version_id')))
|
|
->values($eid . ', ' . $db->quote(end($files)));
|
|
$db->setQuery($query);
|
|
$db->execute();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return $update_count;
|
|
}
|
|
|
|
/**
|
|
* Method to parse through a files element of the installation manifest and take appropriate
|
|
* action.
|
|
*
|
|
* @param SimpleXMLElement $element The XML node to process
|
|
* @param integer $cid Application ID of application to install to
|
|
* @param array $oldFiles List of old files (SimpleXMLElement's)
|
|
* @param array $oldMD5 List of old MD5 sums (indexed by filename with value as MD5)
|
|
*
|
|
* @return boolean True on success
|
|
*
|
|
* @since 3.1
|
|
*/
|
|
public function parseFiles(SimpleXMLElement $element, $cid = 0, $oldFiles = null, $oldMD5 = null)
|
|
{
|
|
// Get the array of file nodes to process; we checked whether this had children above.
|
|
if (!$element || !count($element->children()))
|
|
{
|
|
// Either the tag does not exist or has no children (hence no files to process) therefore we return zero files processed.
|
|
return 0;
|
|
}
|
|
|
|
$copyfiles = array();
|
|
|
|
// Get the client info
|
|
$client = JApplicationHelper::getClientInfo($cid);
|
|
|
|
/*
|
|
* Here we set the folder we are going to remove the files from.
|
|
*/
|
|
if ($client)
|
|
{
|
|
$pathname = 'extension_' . $client->name;
|
|
$destination = $this->getPath($pathname);
|
|
}
|
|
else
|
|
{
|
|
$pathname = 'extension_root';
|
|
$destination = $this->getPath($pathname);
|
|
}
|
|
|
|
/*
|
|
* Here we set the folder we are going to copy the files from.
|
|
*
|
|
* Does the element have a folder attribute?
|
|
*
|
|
* If so this indicates that the files are in a subdirectory of the source
|
|
* folder and we should append the folder attribute to the source path when
|
|
* copying files.
|
|
*/
|
|
|
|
$folder = (string) $element->attributes()->folder;
|
|
|
|
if ($folder && file_exists($this->getPath('source') . '/' . $folder))
|
|
{
|
|
$source = $this->getPath('source') . '/' . $folder;
|
|
}
|
|
else
|
|
{
|
|
$source = $this->getPath('source');
|
|
}
|
|
|
|
// Work out what files have been deleted
|
|
if ($oldFiles && ($oldFiles instanceof SimpleXMLElement))
|
|
{
|
|
$oldEntries = $oldFiles->children();
|
|
|
|
if (count($oldEntries))
|
|
{
|
|
$deletions = $this->findDeletedFiles($oldEntries, $element->children());
|
|
|
|
foreach ($deletions['folders'] as $deleted_folder)
|
|
{
|
|
JFolder::delete($destination . '/' . $deleted_folder);
|
|
}
|
|
|
|
foreach ($deletions['files'] as $deleted_file)
|
|
{
|
|
JFile::delete($destination . '/' . $deleted_file);
|
|
}
|
|
}
|
|
}
|
|
|
|
$path = array();
|
|
|
|
// Copy the MD5SUMS file if it exists
|
|
if (file_exists($source . '/MD5SUMS'))
|
|
{
|
|
$path['src'] = $source . '/MD5SUMS';
|
|
$path['dest'] = $destination . '/MD5SUMS';
|
|
$path['type'] = 'file';
|
|
$copyfiles[] = $path;
|
|
}
|
|
|
|
// Process each file in the $files array (children of $tagName).
|
|
foreach ($element->children() as $file)
|
|
{
|
|
$path['src'] = $source . '/' . $file;
|
|
$path['dest'] = $destination . '/' . $file;
|
|
|
|
// Is this path a file or folder?
|
|
$path['type'] = ($file->getName() == 'folder') ? 'folder' : 'file';
|
|
|
|
/*
|
|
* Before we can add a file to the copyfiles array we need to ensure
|
|
* that the folder we are copying our file to exits and if it doesn't,
|
|
* we need to create it.
|
|
*/
|
|
|
|
if (basename($path['dest']) != $path['dest'])
|
|
{
|
|
$newdir = dirname($path['dest']);
|
|
|
|
if (!JFolder::create($newdir))
|
|
{
|
|
JLog::add(JText::sprintf('JLIB_INSTALLER_ERROR_CREATE_DIRECTORY', $newdir), JLog::WARNING, 'jerror');
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Add the file to the copyfiles array
|
|
$copyfiles[] = $path;
|
|
}
|
|
|
|
return $this->copyFiles($copyfiles);
|
|
}
|
|
|
|
/**
|
|
* Method to parse through a languages element of the installation manifest and take appropriate
|
|
* action.
|
|
*
|
|
* @param SimpleXMLElement $element The XML node to process
|
|
* @param integer $cid Application ID of application to install to
|
|
*
|
|
* @return boolean True on success
|
|
*
|
|
* @since 3.1
|
|
*/
|
|
public function parseLanguages(SimpleXMLElement $element, $cid = 0)
|
|
{
|
|
// TODO: work out why the below line triggers 'node no longer exists' errors with files
|
|
if (!$element || !count($element->children()))
|
|
{
|
|
// Either the tag does not exist or has no children therefore we return zero files processed.
|
|
return 0;
|
|
}
|
|
|
|
$copyfiles = array();
|
|
|
|
// Get the client info
|
|
$client = JApplicationHelper::getClientInfo($cid);
|
|
|
|
// Here we set the folder we are going to copy the files to.
|
|
// 'languages' Files are copied to JPATH_BASE/language/ folder
|
|
|
|
$destination = $client->path . '/language';
|
|
|
|
/*
|
|
* Here we set the folder we are going to copy the files from.
|
|
*
|
|
* Does the element have a folder attribute?
|
|
*
|
|
* If so this indicates that the files are in a subdirectory of the source
|
|
* folder and we should append the folder attribute to the source path when
|
|
* copying files.
|
|
*/
|
|
|
|
$folder = (string) $element->attributes()->folder;
|
|
|
|
if ($folder && file_exists($this->getPath('source') . '/' . $folder))
|
|
{
|
|
$source = $this->getPath('source') . '/' . $folder;
|
|
}
|
|
else
|
|
{
|
|
$source = $this->getPath('source');
|
|
}
|
|
|
|
// Process each file in the $files array (children of $tagName).
|
|
foreach ($element->children() as $file)
|
|
{
|
|
/*
|
|
* Language files go in a subfolder based on the language code, ie.
|
|
* <language tag="en-US">en-US.mycomponent.ini</language>
|
|
* would go in the en-US subdirectory of the language folder.
|
|
*/
|
|
|
|
// We will only install language files where a core language pack
|
|
// already exists.
|
|
|
|
if ((string) $file->attributes()->tag != '')
|
|
{
|
|
$path['src'] = $source . '/' . $file;
|
|
|
|
if ((string) $file->attributes()->client != '')
|
|
{
|
|
// Override the client
|
|
$langclient = JApplicationHelper::getClientInfo((string) $file->attributes()->client, true);
|
|
$path['dest'] = $langclient->path . '/language/' . $file->attributes()->tag . '/' . basename((string) $file);
|
|
}
|
|
else
|
|
{
|
|
// Use the default client
|
|
$path['dest'] = $destination . '/' . $file->attributes()->tag . '/' . basename((string) $file);
|
|
}
|
|
|
|
// If the language folder is not present, then the core pack hasn't been installed... ignore
|
|
if (!JFolder::exists(dirname($path['dest'])))
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$path['src'] = $source . '/' . $file;
|
|
$path['dest'] = $destination . '/' . $file;
|
|
}
|
|
|
|
/*
|
|
* Before we can add a file to the copyfiles array we need to ensure
|
|
* that the folder we are copying our file to exits and if it doesn't,
|
|
* we need to create it.
|
|
*/
|
|
|
|
if (basename($path['dest']) != $path['dest'])
|
|
{
|
|
$newdir = dirname($path['dest']);
|
|
|
|
if (!JFolder::create($newdir))
|
|
{
|
|
JLog::add(JText::sprintf('JLIB_INSTALLER_ERROR_CREATE_DIRECTORY', $newdir), JLog::WARNING, 'jerror');
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Add the file to the copyfiles array
|
|
$copyfiles[] = $path;
|
|
}
|
|
|
|
return $this->copyFiles($copyfiles);
|
|
}
|
|
|
|
/**
|
|
* Method to parse through a media element of the installation manifest and take appropriate
|
|
* action.
|
|
*
|
|
* @param SimpleXMLElement $element The XML node to process
|
|
* @param integer $cid Application ID of application to install to
|
|
*
|
|
* @return boolean True on success
|
|
*
|
|
* @since 3.1
|
|
*/
|
|
public function parseMedia(SimpleXMLElement $element, $cid = 0)
|
|
{
|
|
if (!$element || !count($element->children()))
|
|
{
|
|
// Either the tag does not exist or has no children therefore we return zero files processed.
|
|
return 0;
|
|
}
|
|
|
|
$copyfiles = array();
|
|
|
|
// Here we set the folder we are going to copy the files to.
|
|
// Default 'media' Files are copied to the JPATH_BASE/media folder
|
|
|
|
$folder = ((string) $element->attributes()->destination) ? '/' . $element->attributes()->destination : null;
|
|
$destination = JPath::clean(JPATH_ROOT . '/media' . $folder);
|
|
|
|
// Here we set the folder we are going to copy the files from.
|
|
|
|
/*
|
|
* Does the element have a folder attribute?
|
|
* If so this indicates that the files are in a subdirectory of the source
|
|
* folder and we should append the folder attribute to the source path when
|
|
* copying files.
|
|
*/
|
|
|
|
$folder = (string) $element->attributes()->folder;
|
|
|
|
if ($folder && file_exists($this->getPath('source') . '/' . $folder))
|
|
{
|
|
$source = $this->getPath('source') . '/' . $folder;
|
|
}
|
|
else
|
|
{
|
|
$source = $this->getPath('source');
|
|
}
|
|
|
|
// Process each file in the $files array (children of $tagName).
|
|
foreach ($element->children() as $file)
|
|
{
|
|
$path['src'] = $source . '/' . $file;
|
|
$path['dest'] = $destination . '/' . $file;
|
|
|
|
// Is this path a file or folder?
|
|
$path['type'] = ($file->getName() == 'folder') ? 'folder' : 'file';
|
|
|
|
/*
|
|
* Before we can add a file to the copyfiles array we need to ensure
|
|
* that the folder we are copying our file to exits and if it doesn't,
|
|
* we need to create it.
|
|
*/
|
|
|
|
if (basename($path['dest']) != $path['dest'])
|
|
{
|
|
$newdir = dirname($path['dest']);
|
|
|
|
if (!JFolder::create($newdir))
|
|
{
|
|
JLog::add(JText::sprintf('JLIB_INSTALLER_ERROR_CREATE_DIRECTORY', $newdir), JLog::WARNING, 'jerror');
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Add the file to the copyfiles array
|
|
$copyfiles[] = $path;
|
|
}
|
|
|
|
return $this->copyFiles($copyfiles);
|
|
}
|
|
|
|
/**
|
|
* Method to parse the parameters of an extension, build the INI
|
|
* string for its default parameters, and return the INI string.
|
|
*
|
|
* @return string INI string of parameter values
|
|
*
|
|
* @since 3.1
|
|
*/
|
|
public function getParams()
|
|
{
|
|
// Validate that we have a fieldset to use
|
|
if (!isset($this->manifest->config->fields->fieldset))
|
|
{
|
|
return '{}';
|
|
}
|
|
// Getting the fieldset tags
|
|
$fieldsets = $this->manifest->config->fields->fieldset;
|
|
|
|
// Creating the data collection variable:
|
|
$ini = array();
|
|
|
|
// Iterating through the fieldsets:
|
|
foreach ($fieldsets as $fieldset)
|
|
{
|
|
if (!count($fieldset->children()))
|
|
{
|
|
// Either the tag does not exist or has no children therefore we return zero files processed.
|
|
return null;
|
|
}
|
|
|
|
// Iterating through the fields and collecting the name/default values:
|
|
foreach ($fieldset as $field)
|
|
{
|
|
// Check against the null value since otherwise default values like "0"
|
|
// cause entire parameters to be skipped.
|
|
|
|
if (($name = $field->attributes()->name) === null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (($value = $field->attributes()->default) === null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
$ini[(string) $name] = (string) $value;
|
|
}
|
|
}
|
|
|
|
return json_encode($ini);
|
|
}
|
|
|
|
/**
|
|
* Copyfiles
|
|
*
|
|
* Copy files from source directory to the target directory
|
|
*
|
|
* @param array $files Array with filenames
|
|
* @param boolean $overwrite True if existing files can be replaced
|
|
*
|
|
* @return boolean True on success
|
|
*
|
|
* @since 3.1
|
|
*/
|
|
public function copyFiles($files, $overwrite = null)
|
|
{
|
|
/*
|
|
* To allow for manual override on the overwriting flag, we check to see if
|
|
* the $overwrite flag was set and is a boolean value. If not, use the object
|
|
* allowOverwrite flag.
|
|
*/
|
|
|
|
if (is_null($overwrite) || !is_bool($overwrite))
|
|
{
|
|
$overwrite = $this->overwrite;
|
|
}
|
|
|
|
/*
|
|
* $files must be an array of filenames. Verify that it is an array with
|
|
* at least one file to copy.
|
|
*/
|
|
if (is_array($files) && count($files) > 0)
|
|
{
|
|
|
|
foreach ($files as $file)
|
|
{
|
|
// Get the source and destination paths
|
|
$filesource = JPath::clean($file['src']);
|
|
$filedest = JPath::clean($file['dest']);
|
|
$filetype = array_key_exists('type', $file) ? $file['type'] : 'file';
|
|
|
|
if (!file_exists($filesource))
|
|
{
|
|
/*
|
|
* The source file does not exist. Nothing to copy so set an error
|
|
* and return false.
|
|
*/
|
|
JLog::add(JText::sprintf('JLIB_INSTALLER_ERROR_NO_FILE', $filesource), JLog::WARNING, 'jerror');
|
|
|
|
return false;
|
|
}
|
|
elseif (($exists = file_exists($filedest)) && !$overwrite)
|
|
{
|
|
|
|
// It's okay if the manifest already exists
|
|
if ($this->getPath('manifest') == $filesource)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// The destination file already exists and the overwrite flag is false.
|
|
// Set an error and return false.
|
|
JLog::add(JText::sprintf('JLIB_INSTALLER_ERROR_FILE_EXISTS', $filedest), JLog::WARNING, 'jerror');
|
|
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
// Copy the folder or file to the new location.
|
|
if ($filetype == 'folder')
|
|
{
|
|
if (!(JFolder::copy($filesource, $filedest, null, $overwrite)))
|
|
{
|
|
JLog::add(JText::sprintf('JLIB_INSTALLER_ERROR_FAIL_COPY_FOLDER', $filesource, $filedest), JLog::WARNING, 'jerror');
|
|
|
|
return false;
|
|
}
|
|
|
|
$step = array('type' => 'folder', 'path' => $filedest);
|
|
}
|
|
else
|
|
{
|
|
if (!(JFile::copy($filesource, $filedest, null)))
|
|
{
|
|
JLog::add(JText::sprintf('JLIB_INSTALLER_ERROR_FAIL_COPY_FILE', $filesource, $filedest), JLog::WARNING, 'jerror');
|
|
|
|
return false;
|
|
}
|
|
|
|
$step = array('type' => 'file', 'path' => $filedest);
|
|
}
|
|
|
|
/*
|
|
* Since we copied a file/folder, we want to add it to the installation step stack so that
|
|
* in case we have to roll back the installation we can remove the files copied.
|
|
*/
|
|
if (!$exists)
|
|
{
|
|
$this->stepStack[] = $step;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// The $files variable was either not an array or an empty array
|
|
return false;
|
|
}
|
|
|
|
return count($files);
|
|
}
|
|
|
|
/**
|
|
* Method to parse through a files element of the installation manifest and remove
|
|
* the files that were installed
|
|
*
|
|
* @param object $element The XML node to process
|
|
* @param integer $cid Application ID of application to remove from
|
|
*
|
|
* @return boolean True on success
|
|
*
|
|
* @since 3.1
|
|
*/
|
|
public function removeFiles($element, $cid = 0)
|
|
{
|
|
if (!$element || !count($element->children()))
|
|
{
|
|
// Either the tag does not exist or has no children therefore we return zero files processed.
|
|
return true;
|
|
}
|
|
|
|
$retval = true;
|
|
|
|
// Get the client info if we're using a specific client
|
|
if ($cid > -1)
|
|
{
|
|
$client = JApplicationHelper::getClientInfo($cid);
|
|
}
|
|
else
|
|
{
|
|
$client = null;
|
|
}
|
|
|
|
// Get the array of file nodes to process
|
|
$files = $element->children();
|
|
|
|
if (count($files) == 0)
|
|
{
|
|
// No files to process
|
|
return true;
|
|
}
|
|
|
|
$folder = '';
|
|
|
|
/*
|
|
* Here we set the folder we are going to remove the files from. There are a few
|
|
* special cases that need to be considered for certain reserved tags.
|
|
*/
|
|
switch ($element->getName())
|
|
{
|
|
case 'media':
|
|
if ((string) $element->attributes()->destination)
|
|
{
|
|
$folder = (string) $element->attributes()->destination;
|
|
}
|
|
else
|
|
{
|
|
$folder = '';
|
|
}
|
|
|
|
$source = $client->path . '/media/' . $folder;
|
|
|
|
break;
|
|
|
|
case 'languages':
|
|
$lang_client = (string) $element->attributes()->client;
|
|
|
|
if ($lang_client)
|
|
{
|
|
$client = JApplicationHelper::getClientInfo($lang_client, true);
|
|
$source = $client->path . '/language';
|
|
}
|
|
else
|
|
{
|
|
if ($client)
|
|
{
|
|
$source = $client->path . '/language';
|
|
}
|
|
else
|
|
{
|
|
$source = '';
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
if ($client)
|
|
{
|
|
$pathname = 'extension_' . $client->name;
|
|
$source = $this->getPath($pathname);
|
|
}
|
|
else
|
|
{
|
|
$pathname = 'extension_root';
|
|
$source = $this->getPath($pathname);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
// Process each file in the $files array (children of $tagName).
|
|
foreach ($files as $file)
|
|
{
|
|
/*
|
|
* If the file is a language, we must handle it differently. Language files
|
|
* go in a subdirectory based on the language code, ie.
|
|
* <language tag="en_US">en_US.mycomponent.ini</language>
|
|
* would go in the en_US subdirectory of the languages directory.
|
|
*/
|
|
|
|
if ($file->getName() == 'language' && (string) $file->attributes()->tag != '')
|
|
{
|
|
if ($source)
|
|
{
|
|
$path = $source . '/' . $file->attributes()->tag . '/' . basename((string) $file);
|
|
}
|
|
else
|
|
{
|
|
$target_client = JApplicationHelper::getClientInfo((string) $file->attributes()->client, true);
|
|
$path = $target_client->path . '/language/' . $file->attributes()->tag . '/' . basename((string) $file);
|
|
}
|
|
|
|
// If the language folder is not present, then the core pack hasn't been installed... ignore
|
|
if (!JFolder::exists(dirname($path)))
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$path = $source . '/' . $file;
|
|
}
|
|
|
|
// Actually delete the files/folders
|
|
|
|
if (is_dir($path))
|
|
{
|
|
$val = JFolder::delete($path);
|
|
}
|
|
else
|
|
{
|
|
$val = JFile::delete($path);
|
|
}
|
|
|
|
if ($val === false)
|
|
{
|
|
JLog::add('Failed to delete ' . $path, JLog::WARNING, 'jerror');
|
|
$retval = false;
|
|
}
|
|
}
|
|
|
|
if (!empty($folder))
|
|
{
|
|
JFolder::delete($source);
|
|
}
|
|
|
|
return $retval;
|
|
}
|
|
|
|
/**
|
|
* Copies the installation manifest file to the extension folder in the given client
|
|
*
|
|
* @param integer $cid Where to copy the installfile [optional: defaults to 1 (admin)]
|
|
*
|
|
* @return boolean True on success, False on error
|
|
*
|
|
* @since 3.1
|
|
*/
|
|
public function copyManifest($cid = 1)
|
|
{
|
|
// Get the client info
|
|
$client = JApplicationHelper::getClientInfo($cid);
|
|
|
|
$path['src'] = $this->getPath('manifest');
|
|
|
|
if ($client)
|
|
{
|
|
$pathname = 'extension_' . $client->name;
|
|
$path['dest'] = $this->getPath($pathname) . '/' . basename($this->getPath('manifest'));
|
|
}
|
|
else
|
|
{
|
|
$pathname = 'extension_root';
|
|
$path['dest'] = $this->getPath($pathname) . '/' . basename($this->getPath('manifest'));
|
|
}
|
|
|
|
return $this->copyFiles(array($path), true);
|
|
}
|
|
|
|
/**
|
|
* Tries to find the package manifest file
|
|
*
|
|
* @return boolean True on success, False on error
|
|
*
|
|
* @since 3.1
|
|
*/
|
|
public function findManifest()
|
|
{
|
|
// Main folder manifests (higher priority)
|
|
$parentXmlfiles = JFolder::files($this->getPath('source'), '.xml$', false, true);
|
|
|
|
// Search for children manifests (lower priority)
|
|
$allXmlFiles = JFolder::files($this->getPath('source'), '.xml$', 1, true);
|
|
|
|
// Create an unique array of files ordered by priority
|
|
$xmlfiles = array_unique(array_merge($parentXmlfiles, $allXmlFiles));
|
|
|
|
// If at least one XML file exists
|
|
if (!empty($xmlfiles))
|
|
{
|
|
|
|
foreach ($xmlfiles as $file)
|
|
{
|
|
// Is it a valid Joomla installation manifest file?
|
|
$manifest = $this->isManifest($file);
|
|
|
|
if (!is_null($manifest))
|
|
{
|
|
// If the root method attribute is set to upgrade, allow file overwrite
|
|
if ((string) $manifest->attributes()->method == 'upgrade')
|
|
{
|
|
$this->upgrade = true;
|
|
$this->overwrite = true;
|
|
}
|
|
|
|
// If the overwrite option is set, allow file overwriting
|
|
if ((string) $manifest->attributes()->overwrite == 'true')
|
|
{
|
|
$this->overwrite = true;
|
|
}
|
|
|
|
// Set the manifest object and path
|
|
$this->manifest = $manifest;
|
|
$this->setPath('manifest', $file);
|
|
|
|
// Set the installation source path to that of the manifest file
|
|
$this->setPath('source', dirname($file));
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// None of the XML files found were valid install files
|
|
JLog::add(JText::_('JLIB_INSTALLER_ERROR_NOTFINDJOOMLAXMLSETUPFILE'), JLog::WARNING, 'jerror');
|
|
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
// No XML files were found in the install folder
|
|
JLog::add(JText::_('JLIB_INSTALLER_ERROR_NOTFINDXMLSETUPFILE'), JLog::WARNING, 'jerror');
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Is the XML file a valid Joomla installation manifest file.
|
|
*
|
|
* @param string $file An xmlfile path to check
|
|
*
|
|
* @return mixed A SimpleXMLElement, or null if the file failed to parse
|
|
*
|
|
* @since 3.1
|
|
*/
|
|
public function isManifest($file)
|
|
{
|
|
$xml = simplexml_load_file($file);
|
|
|
|
// If we cannot load the XML file return null
|
|
if (!$xml)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
// Check for a valid XML root tag.
|
|
if ($xml->getName() != 'extension')
|
|
{
|
|
return null;
|
|
}
|
|
|
|
// Valid manifest file return the object
|
|
return $xml;
|
|
}
|
|
|
|
/**
|
|
* Generates a manifest cache
|
|
*
|
|
* @return string serialised manifest data
|
|
*
|
|
* @since 3.1
|
|
*/
|
|
public function generateManifestCache()
|
|
{
|
|
return json_encode(self::parseXMLInstallFile($this->getPath('manifest')));
|
|
}
|
|
|
|
/**
|
|
* Cleans up discovered extensions if they're being installed some other way
|
|
*
|
|
* @param string $type The type of extension (component, etc)
|
|
* @param string $element Unique element identifier (e.g. com_content)
|
|
* @param string $folder The folder of the extension (plugins; e.g. system)
|
|
* @param integer $client The client application (administrator or site)
|
|
*
|
|
* @return object Result of query
|
|
*
|
|
* @since 3.1
|
|
*/
|
|
public function cleanDiscoveredExtension($type, $element, $folder = '', $client = 0)
|
|
{
|
|
$db = JFactory::getDbo();
|
|
$query = $db->getQuery(true)
|
|
->delete($db->quoteName('#__extensions'))
|
|
->where('type = ' . $db->quote($type))
|
|
->where('element = ' . $db->quote($element))
|
|
->where('folder = ' . $db->quote($folder))
|
|
->where('client_id = ' . (int) $client)
|
|
->where('state = -1');
|
|
$db->setQuery($query);
|
|
|
|
return $db->execute();
|
|
}
|
|
|
|
/**
|
|
* Compares two "files" entries to find deleted files/folders
|
|
*
|
|
* @param array $old_files An array of SimpleXMLElement objects that are the old files
|
|
* @param array $new_files An array of SimpleXMLElement objects that are the new files
|
|
*
|
|
* @return array An array with the delete files and folders in findDeletedFiles[files] and findDeletedFiles[folders] respectively
|
|
*
|
|
* @since 3.1
|
|
*/
|
|
public function findDeletedFiles($old_files, $new_files)
|
|
{
|
|
// The magic find deleted files function!
|
|
// The files that are new
|
|
$files = array();
|
|
|
|
// The folders that are new
|
|
$folders = array();
|
|
|
|
// The folders of the files that are new
|
|
$containers = array();
|
|
|
|
// A list of files to delete
|
|
$files_deleted = array();
|
|
|
|
// A list of folders to delete
|
|
$folders_deleted = array();
|
|
|
|
foreach ($new_files as $file)
|
|
{
|
|
switch ($file->getName())
|
|
{
|
|
case 'folder':
|
|
// Add any folders to the list
|
|
$folders[] = (string) $file; // add any folders to the list
|
|
break;
|
|
|
|
case 'file':
|
|
default:
|
|
// Add any files to the list
|
|
$files[] = (string) $file;
|
|
|
|
// Now handle the folder part of the file to ensure we get any containers
|
|
// Break up the parts of the directory
|
|
$container_parts = explode('/', dirname((string) $file));
|
|
|
|
// Make sure this is clean and empty
|
|
$container = '';
|
|
|
|
foreach ($container_parts as $part)
|
|
{
|
|
// Iterate through each part
|
|
// Add a slash if its not empty
|
|
if (!empty($container))
|
|
{
|
|
$container .= '/';
|
|
}
|
|
// Aappend the folder part
|
|
$container .= $part;
|
|
|
|
if (!in_array($container, $containers))
|
|
{
|
|
// Add the container if it doesn't already exist
|
|
$containers[] = $container;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
foreach ($old_files as $file)
|
|
{
|
|
switch ($file->getName())
|
|
{
|
|
case 'folder':
|
|
if (!in_array((string) $file, $folders))
|
|
{
|
|
// See whether the folder exists in the new list
|
|
if (!in_array((string) $file, $containers))
|
|
{
|
|
// Check if the folder exists as a container in the new list
|
|
// If it's not in the new list or a container then delete it
|
|
$folders_deleted[] = (string) $file;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'file':
|
|
default:
|
|
if (!in_array((string) $file, $files))
|
|
{
|
|
// Look if the file exists in the new list
|
|
if (!in_array(dirname((string) $file), $folders))
|
|
{
|
|
// Look if the file is now potentially in a folder
|
|
$files_deleted[] = (string) $file; // not in a folder, doesn't exist, wipe it out!
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
return array('files' => $files_deleted, 'folders' => $folders_deleted);
|
|
}
|
|
|
|
/**
|
|
* Loads an MD5SUMS file into an associative array
|
|
*
|
|
* @param string $filename Filename to load
|
|
*
|
|
* @return array Associative array with filenames as the index and the MD5 as the value
|
|
*
|
|
* @since 3.1
|
|
*/
|
|
public function loadMD5Sum($filename)
|
|
{
|
|
if (!file_exists($filename))
|
|
{
|
|
// Bail if the file doesn't exist
|
|
return false;
|
|
}
|
|
|
|
$data = file($filename, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
|
$retval = array();
|
|
|
|
foreach ($data as $row)
|
|
{
|
|
// Split up the data
|
|
$results = explode(' ', $row);
|
|
|
|
// Cull any potential prefix
|
|
$results[1] = str_replace('./', '', $results[1]);
|
|
|
|
// Throw into the array
|
|
$retval[$results[1]] = $results[0];
|
|
}
|
|
|
|
return $retval;
|
|
}
|
|
|
|
/**
|
|
* Parse a XML install manifest file.
|
|
*
|
|
* XML Root tag should be 'install' except for languages which use meta file.
|
|
*
|
|
* @param string $path Full path to XML file.
|
|
*
|
|
* @return array XML metadata.
|
|
*
|
|
* @since 12.1
|
|
*/
|
|
public static function parseXMLInstallFile($path)
|
|
{
|
|
// Read the file to see if it's a valid component XML file
|
|
$xml = simplexml_load_file($path);
|
|
|
|
if (!$xml)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Check for a valid XML root tag.
|
|
|
|
// Extensions use 'extension' as the root tag. Languages use 'metafile' instead
|
|
|
|
if ($xml->getName() != 'extension' && $xml->getName() != 'metafile')
|
|
{
|
|
unset($xml);
|
|
|
|
return false;
|
|
}
|
|
|
|
$data = array();
|
|
|
|
$data['name'] = (string) $xml->name;
|
|
|
|
// Check if we're a language. If so use metafile.
|
|
$data['type'] = $xml->getName() == 'metafile' ? 'language' : (string) $xml->attributes()->type;
|
|
|
|
$data['creationDate'] = ((string) $xml->creationDate) ? (string) $xml->creationDate : JText::_('Unknown');
|
|
$data['author'] = ((string) $xml->author) ? (string) $xml->author : JText::_('Unknown');
|
|
|
|
$data['copyright'] = (string) $xml->copyright;
|
|
$data['authorEmail'] = (string) $xml->authorEmail;
|
|
$data['authorUrl'] = (string) $xml->authorUrl;
|
|
$data['version'] = (string) $xml->version;
|
|
$data['description'] = (string) $xml->description;
|
|
$data['group'] = (string) $xml->group;
|
|
|
|
return $data;
|
|
}
|
|
}
|