Index: update/update.api.php =================================================================== RCS file: /cvs/drupal/drupal/modules/update/update.api.php,v retrieving revision 1.2 diff -u -p -r1.2 update.api.php --- update/update.api.php 24 May 2009 17:39:35 -0000 1.2 +++ update/update.api.php 3 Jun 2009 07:13:49 -0000 @@ -41,5 +41,81 @@ function hook_update_status_alter(&$proj } /** + * Define additional backends to be supported for installing/updating modules + * and themes from drupal.org. + * + * A backend is a method by which files can be transferred to the server where + * Drupal resides. In this hook, any additional backends that a module provides + * should be listed. An array should be returned, with the keys corresponding + * to the internal name used for the callbacks listed below, and the values + * corresponding to a human-readable name. + * + * By default, ftp and ssh are enabled if the appropriate PHP extensions are + * installed. These backends are handled by upate.module. Any additional + * backends defined here should also define the following callbacks: + * - "update_add_extension_NAME($files, $settings)": + * NAME here corresponds to the keys of the return array; for this example, + * this function would be update_btp_add_extension(). This function should + * upload the files specified by $files to the server specified by the + * array $settings, which may contain data such as the password, username, + * or server location. Should return TRUE on success, or FALSE on failure. + * + * - "update_remove_extension_NAME($files, $settings)": + * NAME here corresponds to the keys of the return array; for this example, + * this function would be update_btp_remove_extension(). This function + * should remove the files specified by $files from the server specified by + * the array $settings, which may contain data such as the password, + * username, or server location. Should return TRUE on success, or FALSE on + * failure. + * + * - "update_settings_form_NAME($form)": + * NAME here corresponds to the keys of the return array; for this example, + * this function would be update_btp_settings_form(). This function is + * optional, and should be used only if the module needs to make any + * modifications or additions to the default form values, which are + * "username", "password", "host", and "root" (the path to the root Drupal + * installation). The settings entered by the user will be passed back to + * the two above functions as the $settings array. + * + * @return + * An associative array where the keys correspond to the internal name of the + * backend, used for function calls, and the values correspond to the human + * readable name of the backend. + */ +function hook_update_backend() { + $backends = array(); + + // BTP (Banana Transfer Protocol) will only be available if the function + // btp_unpeel() exists. + if (function_exists('btp_unpeel')) { + $backends['btp'] = t('BTP'); + } + + return $backends; +} + +/** + * Defines additional methods of untarring modules and themes fetched from + * drupal.org. It is necessary to be able to add pluggable untar methods + * because in certain instances, the generic tar command may not be available. + * + * @return + * An associative array where the keys correspond to the internal name of the + * backend, used for function calls, and the values correspond to the human + * readable name of the backend. + */ +function hook_update_untar() { + $untar_methods = array(); + + // See if the proper PEAR package exists. + @include_once('Archive/Tar.php'); + if (class_exists('Archive_Tar')) { + $untar_methods['archive_tar_untar'] = t('Archive Tar PEAR package'); + } + + return $untar_methods; +} + +/** * @} End of "addtogroup hooks". */ Index: update/update.info =================================================================== RCS file: /cvs/drupal/drupal/modules/update/update.info,v retrieving revision 1.5 diff -u -p -r1.5 update.info --- update/update.info 11 Oct 2008 02:33:12 -0000 1.5 +++ update/update.info 3 Jun 2009 07:13:49 -0000 @@ -4,9 +4,12 @@ description = Checks the status of avail version = VERSION package = Core core = 7.x -files[] = update.module files[] = update.compare.inc files[] = update.fetch.inc +files[] = update.ftp-extension.inc +files[] = update.ftp-wrapper.inc +files[] = update.install +files[] = update.module files[] = update.report.inc files[] = update.settings.inc -files[] = update.install +files[] = update.ssh.inc Index: update/update.module =================================================================== RCS file: /cvs/drupal/drupal/modules/update/update.module,v retrieving revision 1.35 diff -u -p -r1.35 update.module --- update/update.module 27 May 2009 18:34:02 -0000 1.35 +++ update/update.module 3 Jun 2009 07:13:49 -0000 @@ -286,6 +286,48 @@ function update_cron() { } /** +* Implementation of hook_update_backend(). +*/ +function update_update_backend() { + $backends = array(); + + // SSH2 lib backend is only available if the proper PHP extension is + // installed. + if (function_exists('ssh2_connect')) { + $backends['ssh'] = t('SSH'); + } + // FTP backend is only available if the proper PHP extension is installed or + // allow_url_fopen is on. + if (function_exists('ftp_connect')) { + $backends['ftp_extension'] = t('FTP'); + } + // Only present the ftp wrapper option as a fallback if the ftp extension + // isn't available. + elseif (ini_get('allow_url_fopen')) { + $backends['ftp_wrapper'] = t('FTP'); + } + + return $backends; +} + +/** +* Implementation of hook_update_untar(). +*/ +function update_update_untar() { + $untar_methods = array(); + + // See if we have a way to untar the files. + $handle = popen('tar --version', 'r'); + if (fgets($handle)) { + $untar_methods['update_untar_command'] = t('Command line untar'); + } + pclose($handle); + + return $untar_methods; +} + + +/** * Implement hook_form_FORM_ID_alter(). * * Adds a submit handler to the system modules and themes forms, so that if a @@ -621,3 +663,232 @@ function update_flush_caches() { /** * @} End of "defgroup update_status_cache". */ + +/** + * 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 update_get_file($path) { + // Get each of the specified files. + $parsed_url = parse_url($path); + $local = file_directory_temp() . '/update-cache/' . basename($parsed_url['path']); + if (!file_exists(file_directory_temp() . '/update-cache/')) { + mkdir(file_directory_temp() . '/update-cache/'); + } + + // Check the cache and download the file if needed. + if (!file_exists($local)) { + // $result->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 $method + * The method to use to untar the file. This should be a string that + * corresponds to a function name that will take $file and $directory as + * parameters and return an array of the extracted files on success or FALSE + * on failure. + * @param $file + * The file to untar. + * @param $directory + * The destination directory of the file; for example, 'sites/all/modules'. + * @return + * An array containing the locations of the extracted files, or FALSE on + * failure. + */ +function update_untar($method, $file, $directory) { + $available_untar_methods = update_list_untar_methods(); + if (drupal_function_exists($method) && array_key_exists($method, $available_untar_methods)) { + return $method($file, $directory); + } + return FALSE; +} + +/** + * The standard untar callback that simply uses the command line tar command to + * untar the file. + * + * @param $file + * The file to untar. + * @param $directory + * The destination directory of the file; for example, 'sites/all/modules'. + * @return + * An array containing the locations of the extracted files, or FALSE on + * failure. + */ +function update_untar_command($file, $directory) { + $directory_parts = explode('/', $directory); + $directory = file_directory_temp() . '/update-extraction'; + if (!file_exists($directory)) { + mkdir($directory); + } + foreach ($directory_parts as $directory_part) { + $directory .= "/$directory_part"; + if (!file_exists($directory)) { + mkdir($directory); + } + } + $file_safe = escapeshellarg($file); + $directory_safe = escapeshellarg($directory); + $file_list = array(); + + // Try to use tar to extract the files. + if (function_exists('popen')) { + $handle = popen("tar -zvxf $file_safe -C $directory_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; +} + +/** + * 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 update_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(UPDATE_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 update_extensions_runnable() { + // See if we have any available backends. + $backends = update_list_backends(); + $untar_methods = update_list_untar_methods(); + return !empty($backends) && !empty($untar_methods); +} + +/** + * 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 $files + * An array of files to be added by the specified backend. + * @param $settings + * An array of settings, which will vary among different backends, but may + * include data such as the username, password, and host. + * @return + * TRUE on success, or FALSE on failure. + */ +function update_add_extension($backend, $files, $settings) { + $function = 'update_add_extension_' . $backend; + if (drupal_function_exists($function)) { + return $function($files, $settings); + } + 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 $settings + * An array of settings, which will vary among different backends, but may + * include data such as the username, password, and host. + * @return + * TRUE on success, or FALSE on failure. + */ +function update_remove_extension($backend, $files, $settings) { + $function = 'update_remove_extension_' . $backend; + if (drupal_function_exists($function)) { + return $function($files, $settings); + } + 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 update_list_backends() { + $backends = module_invoke_all('update_backend'); + asort($backends); + return $backends; +} + +/** + * Get a list of all available untar methods that are able to unpack a module + * or theme tarball from drupal.org. + * + * @return + * Array containing the names of all available untar methods, with the keys + * as the function names. + */ +function update_list_untar_methods() { + $untar_methods = module_invoke_all('update_untar'); + asort($untar_methods); + return $untar_methods; +}