Index: includes/bootstrap.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/bootstrap.inc,v retrieving revision 1.271 diff -u -p -r1.271 bootstrap.inc --- includes/bootstrap.inc 1 Mar 2009 09:32:17 -0000 1.271 +++ includes/bootstrap.inc 11 Mar 2009 21:14:43 -0000 @@ -235,6 +235,11 @@ define('REGISTRY_RESET_LOOKUP_CACHE', 1) define('REGISTRY_WRITE_LOOKUP_CACHE', 2); /** + * URL to check for updates, if a given project doesn't define its own. + */ +define('EXTENSIONS_DEFAULT_URL', 'http://updates.drupal.org/release-history'); + +/** * @} End of "Title text filtering flags". */ @@ -1615,3 +1620,92 @@ function registry_rebuild() { /** * @} End of "ingroup registry". */ + +/** + * Attempts to get a file using drupal_http_request and to store it locally. + * + * @param $path + * The URL of the file to grab. + * @return + * On success the address the files was saved to, FALSE on failure. + */ +function extensions_get_file($path) { + require_once DRUPAL_ROOT . '/includes/extensions.inc'; + return _extensions_get_file($path); +} + +/** + * Add an extension to the file system. + * + * @param $backend + * The machine-readable name of the backend handling the addition to the file + * system; for example, 'ftp'. + * @param $type + * The type of extension being added, such as 'module' or 'theme'. + * @param $files + * An array of files to be added by the specified backend. + * @param $root + * The location of the drupal installation; may be different from DRUPAL_ROOT + * depending on the backend being used. + * @param $host + * The host server for the backend, such as an FTP server for the ftp + * backend. + * @param $username + * The username to use to log in to the backend. + * @param $password + * The password to use to log in to the backend. + * @return + * TRUE on success, or FALSE on failure. + */ +function extensions_add_extension($backend, $type, $files, $root, $host, $username, $password) { + require_once DRUPAL_ROOT . '/includes/extensions.inc'; + $function = $backend . '_add_extension'; + if (drupal_function_exists($function)) { + return $function($type, $files, $root, $host, $username, $password); + } + return FALSE; +} + +/** + * Remove an extension from the file system. + * + * @param $backend + * The machine-readable name of the backend handling the removal from the + * file system; for example, 'ftp'. + * @param $type + * The type of extension being removed, such as 'module' or 'theme'. + * @param $files + * An array of files to be removed by the specified backend. + * @param $root + * The location of the drupal installation; may be different from DRUPAL_ROOT + * depending on the backend being used. + * @param $host + * The host server for the backend, such as an FTP server for the ftp + * backend. + * @param $username + * The username to use to log in to the backend. + * @param $password + * The password to use to log in to the backend. + * @return + * TRUE on success, or FALSE on failure. + */ +function extensions_remove_extension($backend, $type, $files, $root, $host, $username, $password) { + require_once DRUPAL_ROOT . '/includes/extensions.inc'; + $function = $backend . '_remove_extension'; + if (drupal_function_exists($function)) { + return $function($type, $files, $root, $host, $username, $password); + } + return FALSE; +} + +/** + * Get a list of all available backends that can upload files onto the system. + * + * @return + * Array containing the names of all available backends. + */ +function extension_list_backends() { + $backends = module_invoke_all('extension_backends'); + sort($backends); + return $backends; +} \ No newline at end of file Index: includes/extensions.ftp.inc =================================================================== RCS file: includes/extensions.ftp.inc diff -N includes/extensions.ftp.inc --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ includes/extensions.ftp.inc 11 Mar 2009 23:04:10 -0000 @@ -0,0 +1,368 @@ + $file) { + ftp_chdir($connect, '/'); + if (trim($file, '\\/') != $file) { + if (!@ftp_mkdir($connect, "$ftp_path/$file") && !@ftp_chdir($connect, "$ftp_path/$file")) { + drupal_set_message(t('Unable to create directory @directory.', array('@directory' => $file)),'error'); + return FALSE; + } + } + else { + if (!@ftp_put($connect, "$ftp_path/$file", $extract_dir . $file, FTP_BINARY)) { + drupal_set_message(t('Unable to upload @file.', array('@file' => $file)), 'error'); + return FALSE; + } + } + } + return TRUE; +} + +/** + * Install the supplied files to the appropriate locations using the ftp + * stream wrapper. + * + * @param $type + * The type of extension being added, such as 'module' or 'theme'. + * @param $files + * An array of files to be added by the specified backend. + * @param $root + * The location of the drupal installation; may be different from DRUPAL_ROOT + * depending on the backend being used. + * @param $host + * The host server for the backend, such as an FTP server for the ftp + * backend. + * @param $username + * The username to use to log in to the backend. + * @param $password + * The password to use to log in to the backend. + * @return + * TRUE on success, or FALSE on failure. + */ +function ftp_add_extension_wrapper($type, $files, $root, $host, $username, $password) { + $dir = "sites/all/${type}s"; + + // Write the common part of the url. + $ftp_base_dir = 'ftp://' . urlencode($username) . ':' . urlencode($password) . "@$host/"; + + if (!@is_dir($ftp_base_dir)) { + drupal_set_message(t('The supplied username/password combination was not accepted, or no local ftp server is running.'), 'error'); + return FALSE; + } + + $ftp_path = $ftp_base_dir . $root . '/' . $dir; + + // If it's not a valid path, then quit. + if (!is_dir($ftp_path)) { + drupal_set_message(t('Could not guess the ftp directory for drupal.'), 'error'); + return FALSE; + } + + // Prepare the directories to use later. + $extract_dir = file_directory_path() . '/extensions_extraction/'; + + // Process each of the files. + foreach ($files as $index => $file) { + if (trim($file, "\\/") != $file) { + @mkdir("$ftp_path/$file"); + } + else { + @copy($extract_dir . $file, "$ftp_path/$file"); + } + } + return TRUE; +} + +/** + * Remove the supplied files. + * + * @param $type + * The type of extension being removed, such as 'module' or 'theme'. + * @param $files + * An array of files to be removed by the specified backend. + * @param $root + * The location of the drupal installation; may be different from DRUPAL_ROOT + * depending on the backend being used. + * @param $host + * The host server for the backend, such as an FTP server for the ftp + * backend. + * @param $username + * The username to use to log in to the backend. + * @param $password + * The password to use to log in to the backend. + * @return + * TRUE on success, or FALSE on failure. + */ +function ftp_remove_extension($type, $files, $root, $host, $username, $password) { + if (function_exists('ftp_connect')) { + return ftp_remove_extension_library($type, $files, $root, $host, $username, $password); + } + return ftp_remove_extension_wrapper($type, $files, $root, $host, $username, $password); +} + +/** + * Remove the files using ftp without + * the stream wrapper. + * + * @param $type + * The type of extension being removed, such as 'module' or 'theme'. + * @param $files + * An array of files to be removed by the specified backend. + * @param $root + * The location of the drupal installation; may be different from DRUPAL_ROOT + * depending on the backend being used. + * @param $host + * The host server for the backend, such as an FTP server for the ftp + * backend. + * @param $username + * The username to use to log in to the backend. + * @param $password + * The password to use to log in to the backend. + * @return + * TRUE on success, or FALSE on failure. + */ +function ftp_remove_extension_library($type, $files, $root, $host, $username, $password) { + $directory = "sites/all/${type}s"; + + // Connect to the local ftp server. + $connect = ftp_connect($host); + if (!$connect) { + drupal_set_message(t('No ftp server could be found.'), 'error'); + return FALSE; + } + + // Login to the local ftp server. + if (!@ftp_login($connect, $username, $password)) { + drupal_set_message(t('Could not login to the ftp server.'), 'error'); + return FALSE; + } + + // Try to guess the ftp address for the drupal install. + // Start by putting the entire address together and replacing \ with /. + $local_path = str_replace('\\', '/', $_SERVER['DOCUMENT_ROOT'] . base_path()); + + // Grab the last on off the end and explode it. + $local_path = explode('/', rtrim($local_path, '/')); + + $ftp_path = $root . $dir; + + // If we can't change to the proper directory, abort. + if (!@ftp_chdir($connect, $ftp_path)) { + drupal_set_message(t('Could not reach the proper directory.'), 'error'); + return FALSE; + } + ftp_chdir($connect, '/'); + + // Process each of the files. + foreach ($files as $file) { + if (trim($file, '\\/') != $file) { + if (!ftp_remove_extension_rmdir($connect, "$root/$directory/$files")) { + return FALSE; + } + } + } + return TRUE; +} + +/** + * Helper function for ftp_remove_extension_library(). + * + * @param $connect + * The FTP connection to use to remove the directory. + * @param $directory + * The directory to remove. + * @return + * TRUE on success, or FALSE on failure. + */ +function ftp_remove_extension_rmdir($connect, $directory){ + $pwd = ftp_pwd($connect); + if (!@ftp_chdir($connect,$dir)) { + return FALSE; + } + $list = @ftp_nlist($connect,'.'); + foreach($list as $item){ + if ($item=='.' || $item=='..') { + continue; + } + if (@ftp_chdir($connect,$item)){ + ftp_chdir($connect,'..'); + if (!ftp_plugin_manager_rmdir($connect,$item)) { + return FALSE; + } + } + elseif (!ftp_delete($connect,$item)) { + return FALSE; + } + } + ftp_chdir($connect, $pwd); + if (!ftp_rmdir($connect, $dir)) { + return FAlSE; + } + return TRUE; +} + +/** + * Remove the supplied folder using the ftp stream wrapper. + * + * @param $type + * The type of extension being removed, such as 'module' or 'theme'. + * @param $files + * An array of files to be removed by the specified backend. + * @param $root + * The location of the drupal installation; may be different from DRUPAL_ROOT + * depending on the backend being used. + * @param $host + * The host server for the backend, such as an FTP server for the ftp + * backend. + * @param $username + * The username to use to log in to the backend. + * @param $password + * The password to use to log in to the backend. + * @return + * TRUE on success, or FALSE on failure. + */ +function ftp_remove_extension_wrapper($type, $files, $root, $host, $username, $password) { + $dir = ''; + + // Write the common part of the url + $ftp_base_dir = 'ftp://' . urlencode($username) . ':' . urlencode($password) . "@$host/"; + + if (!@is_dir($ftp_base_dir)) { + drupal_set_message(t('The supplied username/password combination was not accepted, or no ftp server is running.'), 'error'); + return FALSE; + } + + $ftp_path = $root . $dir; + + // If we the FTP path is not valid, abort. + if (!is_dir($ftp_path)) { + drupal_set_message(t('Could not reach the proper directory.'), 'error'); + return FALSE; + } + // Process each of the files. + foreach ($files as $file) { + if (trim($file, '\\/') != $file) { + if (!ftp_remove_extension_rmdir_stream("$ftp_path/$file")) { + return FALSE; + } + } + } + return TRUE; +} + +/** + * Helper function for ftp_remove_extension_wrapper(). Removes a directory + * through the FTP stream. + * + * @param $target + * The target folder to remove. + * @return + * TRUE on success, or FALSE on failure. + */ +function ftp_remove_extension_rmdir_stream($target) { + if(is_dir($target) && is_writeable($target)) { + foreach (new DirectoryIterator($target) as $resource) { + if ($resource->isDot()) { + unset($resource); + continue; + } + if ($resource->isFile()) { + ftp_plugin_manager_rmdir_stream($resource->getPathName()); + } + elseif ($resource->isDir()) { + ftp_plugin_manager_rmdir_stream($resource->getRealPath()); + } + unset($resource); + } + if (@rmdir($target)) { + return TRUE; + } + } + return FALSE; +} \ No newline at end of file Index: includes/extensions.inc =================================================================== RCS file: includes/extensions.inc diff -N includes/extensions.inc --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ includes/extensions.inc 11 Mar 2009 21:28:00 -0000 @@ -0,0 +1,179 @@ +data is the actual contents of the downloaded file. This saves + // it into a local file, whose path is stored in $local. $local is stored + // relative to the drupal installation. + $result = drupal_http_request($path); + if ($result->code != 200 || !file_save_data($result->data, $local)) { + drupal_set_message(t('@remote could not be saved.', array('@remote' => $path)), 'error'); + return FALSE; + } + } + return $local; +} + +/** + * Untars a file. + * + * @param $file + * The file to untar. + * @return + * An array containing the locations of the extracted files, or FALSE on + * failure. + */ +function extensions_untar($file) { + $dir = file_directory_temp() . '/extensions_extraction/'; + $file_safe = escapeshellarg($file); + $dir_safe = escapeshellarg($dir); + $file_list = array(); + + // Try to use tar to extract the files. + if (function_exists('popen')) { + $handle = popen("tar -zvxf $file_safe -C $dir_safe", "r"); + while ($line = fgets($handle)) { + $file_list[] = trim($line); + } + pclose($handle); + } + + // If tar returned something, then it is present, so return it. + if (!empty($file_list)) { + return $file_list; + } + + return FALSE; +} + +/** + * Downloads the list of available themes/modules. + * + * @return + * None. + */ +function extensions_list_reload() { + // Download a new copy of the project-list. + $file = drupal_http_request(EXTENSION_DEFAULT_URL . '/project-list/all'); + if (isset($file->error)) { + drupal_set_message(t($file->error), 'error'); + return; + } + + // Parse it. + $xml = simplexml_load_string($file->data); + if ($xml == FALSE) { + return; + } + + // @todo Clear cache here. + + // Look at each project node. + $drupal_version = constant("DRUPAL_CORE_COMPATIBILITY"); + foreach ($xml->project AS $project) { + // If there isn't a release for this version of Drupal then skip it. + if (!isset($project->api_versions)) { + continue; + } + $versions = (array) $project->api_versions; + if (!is_array($versions['api_version'])) { + $versions['api_version'] = array($versions['api_version']); + } + if (!in_array($drupal_version, $versions['api_version'])) { + continue; + } + + // @todo Cache data here. + } + drupal_set_message(t('The repository was updated successfully.'), 'status'); + variable_set('extensions_last_reload', REQUEST_TIME); +} + +/** + * Get the information about each extension. + * + * @param $projects + * The project or projects on which information is desired. + * @return + * An array containing info on the supplied projects. + */ +function extensions_get_release_history($projects) { + $version = DRUPAL_CORE_COMPATIBILITY; + $results = array(); + + // If projects isn't an array, turn it into one. + if (!is_array($projects)) { + $projects = array($projects); + } + + // Look up the data for every project requested. + foreach ($projects as $project) { + $file = drupal_http_request(EXTENSIONS_DEFAULT_URL . "/$project/$version"); + $xml = simplexml_load_string($file->data); + + // If it failed, then quit. + if ($xml == FALSE) { + drupal_set_message(t('Downloading the release history failed for @project.', array('@project' => $project)), "error"); + return FALSE; + } + + // Get the title, release_link and download_link. + $results[$project]['title'] = (string)$xml->title; + + // Get information about every release. + foreach ($xml->releases->release AS $release) { + $release_version = (string)$release->version; + $results[$project]['release'][] = array( + 'release_link' => (string)$release->release_link, + 'download_link' => (string)$release->download_link, + 'date' => (string)$release->date, + 'version' => $release_version, + ); + $results[$project]['version'][] = $release_version; + } + } + + // Order them and then return the results. + ksort($results); + return $results; +} + +/** + * See if everything that is needed to use the extension manager is available. + * + * @return + * TRUE if all of the required dependencies are available, FALSE otherwise. + */ +function extensions_runnable() { + // See if we have a way to untar the files. + $handle = popen("tar --version", "r"); + if (!fgets($handle)) { + pclose($handle); + return FALSE; + } + pclose($handle); + + // See if we have any available backends. + $backends = extension_list_backends(); + return !empty($backends); +} \ No newline at end of file Index: includes/extensions.ssh.inc =================================================================== RCS file: includes/extensions.ssh.inc diff -N includes/extensions.ssh.inc --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ includes/extensions.ssh.inc 11 Mar 2009 21:41:37 -0000 @@ -0,0 +1,121 @@ +