Source code for file /joomla/installer/installer.php
Documentation is available at installer.php
* @version $Id: installer.php 6612 2007-02-13 00:47:52Z Jinx $
* @package Joomla.Framework
* @copyright Copyright (C) 2005 - 2007 Open Source Matters. All rights reserved.
* @license GNU/GPL, see LICENSE.php
* Joomla! is free software. This version may have been modified pursuant
* to the GNU General Public License, and as distributed it includes or
* is derivative of works licensed under the GNU General Public License or
* other free or open source software licenses.
* See COPYRIGHT.php for copyright notices and details.
// Check to ensure this file is within the rest of the framework
* Joomla base installer class
* @package Joomla.Framework
* Array of paths needed by the installer
* The installation manifest XML object
* True if existing files can be overwritten
* A database connector object
* Associative array of package installer handlers
* Stack of installation steps
* - Used for installation rollback
* The output from the install/uninstall scripts
* Returns a reference to the global Installer object, only creating it
* if it doesn't already exist.
* @return object An installer object
if (!isset
($instance)) {
* Get the allow overwrite switch
* @return boolean Allow overwrite switch
* Set the allow overwrite switch
* @param boolean $state Overwrite switch state
* @return boolean Previous value
* Get the database connector object
* @return object Database connector object
* Get the installation manifest object
* @return object Manifest object
* Get an installer path by name
* @param string $name Path name
* @param string $default Default value
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
$this->_paths[$name] =
$value;
* Pushes a step onto the installer stack for rolling back steps
* @param array $step Installer step
* Set an installer adapter by name
* @param string $name Adapter name
* @param object $adapter Installer adapter object
* @return boolean True if successful
// Try to load the adapter object
$class =
'JInstaller'.
ucfirst($name);
$adapter =
new $class($this);
* Installation abort method
* @param string $msg Abort message from the installer
* @param string $type Package type if defined
* @return boolean True if successful
function abort($msg=
null, $type=
null)
// placeholder in case this is necessary in the future
// Build the name of the custom rollback method for the type
$method =
'_rollback_'.
$step['type'];
// Custom rollback method handler
$stepval =
$this->_adapters[$type]->$method($step);
// Only set the return value if it is false
if ($stepval ===
false) {
// Get the next step and continue
* Package installation method
* @param string $path Path to package source folder
* @return boolean True if successful
return $this->abort(JText::_('Install path does not exist'));
return $this->abort(JText::_('Unable to detect manifest file'));
$version =
$root->attributes('version');
$rootName =
$root->name();
if ((version_compare($version, '1.5', '<') ||
$rootName ==
'mosinstall') &&
!$config->getValue('config.legacy')) {
$type =
$root->attributes('type');
// Needed for legacy reasons ... to be deprecated in next minor release
* @param string $path Path to package source folder
* @return boolean True if successful
return $this->abort(JText::_('Unable to detect manifest file'));
$version =
$root->attributes('version');
$rootName =
$root->name();
if ((version_compare($version, '1.5', '<') ||
$rootName ==
'mosinstall') &&
!$config->getValue('config.legacy')) {
$type =
$root->attributes('type');
// Needed for legacy reasons ... to be deprecated in next minor release
* Package uninstallation method
* @param string $type Package type
* @param mixed $identifier Package identifier for adapter
* @param int $cid Application ID
* @return boolean True if successful
function uninstall($type, $identifier, $cid=
0)
$this->_adapters[$type]->uninstall($identifier, $cid);
* 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
// We need to find the installation manifest file
if (!$this->_findManifest()) {
// Load the adapter(s) for the install manifest
$type =
$root->attributes('type');
// Needed for legacy reasons ... to be deprecated in next minor release
* Backward compatible Method to parse through a queries element of the
* installation manifest file and take appropriate action.
* @param object $element The xml node to process
* @return mixed Number of queries processed or False on error
// Get the database connector object
if (!is_a($element, 'JSimpleXMLElement') ||
!count($element->children())) {
// Either the tag does not exist or has no children therefore we return zero files processed.
// Get the array of query nodes to process
$queries =
$element->children();
if (count($queries) ==
0) {
// Process each query in the $queries array (children of $tagName).
foreach ($queries as $query)
$db->setQuery($query->data());
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
* @param string $version The database connector to use
* @return mixed Number of queries processed or False on error
if ($dbDriver ==
'mysqli') {
$dbCharset =
($db->hasUTF()) ?
'utf8' :
'';
if (!is_a($element, 'JSimpleXMLElement')) {
// The tag does not exist.
// Get the array of file nodes to process
$files =
$element->children();
if (count($files) ==
0) {
// Get the name of the sql file to process
foreach ($files as $file)
$fCharset =
(strtolower($file->attributes('charset')) ==
'utf8') ?
'utf8' :
'';
$fDriver =
strtolower($file->attributes('driver'));
if ($fDriver ==
'mysqli') {
if( $fCharset ==
$dbCharset &&
$fDriver ==
$dbDriver) {
$sqlfile =
$file->data();
// Check that sql files exists before reading. Otherwise raise error for rollback
// Graceful exit and rollback if read not successful
if ( $buffer ===
false ) {
// Create an array of queries from the sql file
if (count($queries) ==
0) {
// Process each query in the $queries array (split out of sql file).
foreach ($queries as $query)
if ($query !=
'' &&
$query{0} !=
'#') {
return (int)
count($queries);
* Method to parse through a files element of the installation manifest and take appropriate
* @param object $element The xml node to process
* @param int $cid Application ID of application to install to
* @return boolean True on success
jimport('joomla.application.helper');
if (!is_a($element, 'JSimpleXMLElement') ||
!count($element->children())) {
// Either the tag does not exist or has no children therefore we return zero files processed.
// Get the array of file nodes to process
$files =
$element->children();
if (count($files) ==
0) {
* 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.
* - 'media' Files are copied to the JPATH_BASE/images/stories/ folder
* - 'languages' Files are copied to JPATH_BASE/languages/ folder
switch ($element->name())
if ($element->attributes('destination')) {
$folder =
$element->attributes('destination');
$destintion =
$client->path.
DS.
'images'.
DS.
$folder;
$destination =
$client->path.
DS.
'language';
$pathname =
'extension_'.
$client->name;
$destination =
$this->getPath($pathname);
$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
if ($folder =
$element->attributes('folder')) {
$source =
$this->getPath('source').
DS.
$folder;
$source =
$this->getPath('source');
// 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.
* We will only install language files where a core language pack
if ($file->name() ==
'language' &&
$file->attributes('tag') !=
'') {
$path['src'] =
$source.
DS.
$file->data();
$path['dest'] =
$destination.
DS.
$file->attributes('tag').
DS.
basename($file->data());
// If the language folder is not present, then the core pack hasn't been installed... ignore
$path['src'] =
$source.
DS.
$file->data();
$path['dest'] =
$destination.
DS.
$file->data();
// Is this path a file or folder?
$path['type'] =
( $file->name() ==
'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,
if (basename($path['dest']) !=
$path['dest']) {
// Add the file to the copyfiles array
* Method to parse through a languages element of the installation manifest and take appropriate
* @param object $element The xml node to process
* @param int $cid Application ID of application to install to
* @return boolean True on success
jimport('joomla.application.helper');
if (!is_a($element, 'JSimpleXMLElement') ||
!count($element->children())) {
// Either the tag does not exist or has no children therefore we return zero files processed.
// Get the array of file nodes to process
$files =
$element->children();
if (count($files) ==
0) {
* 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.
DS.
'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
if ($folder =
$element->attributes('folder')) {
$source =
$this->getPath('source').
DS.
$folder;
$source =
$this->getPath('source');
// Process each file in the $files array (children of $tagName).
foreach ($files 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
if ($file->attributes('tag') !=
'') {
$path['src'] =
$source.
DS.
$file->data();
$path['dest'] =
$destination.
DS.
$file->attributes('tag').
DS.
basename($file->data());
// If the language folder is not present, then the core pack hasn't been installed... ignore
$path['src'] =
$source.
DS.
$file->data();
$path['dest'] =
$destination.
DS.
$file->data();
* 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,
if (basename($path['dest']) !=
$path['dest']) {
// Add the file to the copyfiles array
* Method to parse through a media element of the installation manifest and take appropriate
* @param object $element The xml node to process
* @param int $cid Application ID of application to install to
* @return boolean True on success
jimport('joomla.application.helper');
if (!is_a($element, 'JSimpleXMLElement') ||
!count($element->children())) {
// Either the tag does not exist or has no children therefore we return zero files processed.
// Get the array of file nodes to process
$files =
$element->children();
if (count($files) ==
0) {
* Here we set the folder we are going to copy the files to.
* Default 'media' Files are copied to the JPATH_BASE/images folder
$folder =
($element->attributes('destination')) ?
DS.
$element->attributes('destination') :
null;
$destination =
JPath::clean($client->path.
DS.
'images'.
$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
if ($folder =
$element->attributes('folder')) {
$source =
$this->getPath('source').
DS.
$folder;
$source =
$this->getPath('source');
// Process each file in the $files array (children of $tagName).
foreach ($files as $file)
$path['src'] =
$source.
DS.
$file->data();
$path['dest'] =
$destination.
DS.
$file->data();
* 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,
if (basename($path['dest']) !=
$path['dest']) {
// Add the file to the copyfiles array
* Method to parse the parameters of an extension, build the INI
* string for it's default parameters, and return the INI string.
* @return string INI string of parameter values
// Get the manifest document root element
// Get the element of the tag names
$element =
& $root->getElementByPath('params');
if (!is_a($element, 'JSimpleXMLElement') ||
!count($element->children())) {
// Either the tag does not exist or has no children therefore we return zero files processed.
// Get the array of parameter nodes to process
$params =
$element->children();
if (count($params) ==
0) {
// Process each parameter in the $params array.
foreach ($params as $param) {
if (!$name =
$param->attributes('name')) {
if (!$value =
$param->attributes('default')) {
$ini .=
$name.
"=".
$value.
"\n";
* 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
* 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
* $files must be an array of filenames. Verify that it is an array with
* at least one file to copy.
foreach ($files as $file)
// Get the source and destination paths
* The source file does not exist. Nothing to copy so set an error
* The destination file already exists and the overwrite flag is false.
* Set an error and return false.
// Copy the folder or file to the new location.
if ( $filetype ==
'folder') {
if (!(JFolder::copy($filesource, $filedest, null, $overwrite))) {
$step =
array ('type' =>
'folder', 'path' =>
$filedest);
if (!(JFile::copy($filesource, $filedest))) {
$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.
* The $files variable was either not an array or an empty array
* 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 int $cid Application ID of application to remove from
* @return boolean True on success
jimport('joomla.application.helper');
if (!is_a($element, 'JSimpleXMLElement') ||
!count($element->children())) {
// Either the tag does not exist or has no children therefore we return zero files processed.
// Get the array of file nodes to process
$files =
$element->children();
if (count($files) ==
0) {
* 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->name())
if ($element->attributes('destination')) {
$folder =
$element->attributes('destination');
$source =
$client->path.
DS.
'images'.
DS.
$folder;
$source =
$client->path.
DS.
'language';
$pathname =
'extension_'.
$client->name;
$source =
$this->getPath($pathname);
$pathname =
'extension_root';
$source =
$this->getPath($pathname);
// 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->name() ==
'language' &&
$file->attributes('tag') !=
'') {
$path =
$source.
DS.
$file->attributes('tag').
DS.
basename($file->data());
$path =
$source.
DS.
$file->data();
* Actually delete the files/folders
* Copies the installation manifest file to the extension folder in the given client
* @param int $cid Where to copy the installfile [optional: defaults to 1 (admin)]
* @return boolean True on success, False on error
jimport('joomla.application.helper');
$path['src'] =
$this->getPath('manifest');
$pathname =
'extension_'.
$client->name;
$pathname =
'extension_root';
return $this->copyFiles(array ($path), true);
* Tries to find the package manifest file
* @return boolean True on success, False on error
// Get an array of all the xml files from teh installation directory
// If at least one xml file exists
if (count($xmlfiles) >
0) {
foreach ($xmlfiles as $file)
// Is it a valid joomla installation manifest file?
$manifest =
$this->_isManifest($file);
// If the root method attribute is set to upgrade, allow file overwrite
$root =
& $manifest->document;
if ($root->attributes('method') ==
'upgrade') {
// Set the manifest object and path
// Set the installation source path to that of the manifest file
// None of the xml files found were valid install files
// No xml files were found in the install folder
* Is the xml file a valid Joomla installation manifest file
* @param string $file An xmlfile path to check
* @return mixed A JSimpleXML document, or null if the file failed to parse
function &_isManifest($file)
// If we cannot load the xml file return null
if (!$xml->loadFile($file)) {
// Free up xml parser memory and return null
* Check for a valid XML root tag.
* @todo: Remove backwards compatability in a future version
* Should be 'install', but for backward compatability we will accept 'mosinstall'.
if ($root->name() !=
'install' &&
$root->name() !=
'mosinstall') {
// Free up xml parser memory and return null
// Valid manifest file return the object
* @package Joomla.Framework
* @param string URL of file to download
* @param string Download target filename [optional]
* @return mixed Path to downloaded package or boolean false on failure
$php_errormsg =
'Error Unknown';
ini_set('user_agent', "Joomla! 1.5 Installer");
// Open the remote server socket for reading
$inputHandle =
@ fopen($url, "r");
foreach ($meta_data['wrapper_data'] as $wrapper_data)
if (substr($wrapper_data, 0, strlen("Content-Disposition")) ==
"Content-Disposition") {
$contentfilename =
explode ("\"", $wrapper_data);
$target =
$contentfilename[1];
// Set the target path if not given
$target =
$config->getValue('config.tmp_path').
DS.
basename($target);
// Initialize contents buffer
while (!feof($inputHandle))
$contents .=
fread($inputHandle, 4096);
if ($contents ==
false) {
// Close file pointer resource
// Return the name of the downloaded package
* Unpacks a file and verifies it as a Joomla element package
* Supports .gz .tar .tar.gz and .zip
* @param string $p_filename The uploaded package filename or install directory
* @return boolean True on success, False on error
$archivename =
$p_filename;
// Temporary folder to extract the archive into
// Clean the paths to use for archive extraction
// do the unpacking of the archive
if ( $result ===
false ) {
* Lets set the extraction directory and package file in the result array so we can
* cleanup everything properly later on.
$retval['extractdir'] =
$extractdir;
$retval['packagefile'] =
$archivename;
* Try to find the correct install directory. In case the package is inside a
* subdirectory detect this and set the install directory to the correct path.
* List all the items in the installation directory. If there is only one, and
* it is a folder, then we will set that folder to be the installation folder.
if (count($dirList) ==
1)
* We have found the install directory so lets set it and then move on
* to detecting the extension type.
$retval['dir'] =
$extractdir;
* Get the extension type and return the directory/type array on success or
* Method to detect the extension type from a package directory
* @param string $p_dir Path to package directory
* @return mixed Extension type string or boolean false on fail
// Search the install dir for an xml file
foreach ($files as $file)
$xmlDoc->resolveErrors(true);
if (!$xmlDoc->loadXML($file, false, true))
// Free up memory from DOMIT parser
$root =
& $xmlDoc->documentElement;
if ($root->getTagName() !=
"install" &&
$root->getTagName() !=
'mosinstall')
$type =
$root->getAttribute('type');
// Free up memory from DOMIT parser
// Free up memory from DOMIT parser
* 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
return $parts[count($parts) -
1];
* Clean up temporary uploaded package and unpacked extension
* @param string $p_file Path to the uploaded package file
* @param string $resultdir Path to the unpacked extension
* @return boolean True on success
// Does the unpacked extension directory exist?
// Is the package file a valid file?
// It might also be just a base filename
* Splits contents of a sql file into array of discreet queries
* queries need to be delimited with end of statement marker ';'
for ($i =
0; $i <
strlen($sql) -
1; $i ++
)
if ($sql[$i] ==
";" &&
!$in_string) {
if ($in_string &&
($sql[$i] ==
$in_string) &&
$buffer[1] !=
"\\") {
} elseif (!$in_string &&
($sql[$i] ==
'"' ||
$sql[$i] ==
"'") &&
(!isset
($buffer[0]) ||
$buffer[0] !=
"\\")) {
} if (isset
($buffer[1])) {