=== added directory 'modules/update/connection' === added file 'modules/update/connection/update.connection.inc' --- modules/update/connection/update.connection.inc 1970-01-01 00:00:00 +0000 +++ modules/update/connection/update.connection.inc 2009-06-09 03:47:14 +0000 @@ -0,0 +1,94 @@ +copyDirectory($source, DRUPAL_ROOT . '/' . drupal_get_path($type, $name)); + } + + /** + * Copies a directory. + * + * @param $source + * The source path. + * @param $destination + * The destination path. + */ + function copyDirectory($source, $destination) { + $this->mkdir($destination . basename($source)); + foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($source), RecursiveIteratorIterator::SELF_FIRST) as $filename => $file) { + $relative_path = basename($source) . substr($filename, strlen($source)); + if ($file->isDir()) { + $this->mkdir($destination . $relative_path); + } + else { + $this->copyFile($file->getPathName(), $destination . $relative_path); + } + } + } + + /** + * Creates a directory. + * + * @param $directory + * The directory to be created. + */ + abstract function mkdir($directory); + + /** + * Removes a directory. + * + * @param $directory + * The directory to be removed. + */ + abstract function rmdir($directory); + + /** + * Copies a file. + * + * @param $source + * The source file. + * @param $destination + * The destination file. + */ + abstract function copyFile($source, $destination); +} + +/** + * ConnectionException class. + */ +class ConnectionException extends Exception { + public $params = array(); + function __construct($message, $code, $params = array()) { + $this->params = $params; + parent::__construct($message, $code); + } + + public function getParams() { + return $this->params; + } +} === added file 'modules/update/connection/update.ftp.inc' --- modules/update/connection/update.ftp.inc 1970-01-01 00:00:00 +0000 +++ modules/update/connection/update.ftp.inc 2009-06-09 03:42:07 +0000 @@ -0,0 +1,102 @@ + 'localhost', 'port' => 21); + $this->connection = 'ftp://' . urlencode($settings['username']) . ':' . urlencode($settings['password']) . '@' . $settings['hostname'] . ':' . $settings['hostname'] . '/'; + if (!is_dir($this->connection)) { + throw new ConnectionException("FTP Connection failed"); + } + parent::__construct($settings); + } + + function copyFile($source, $destination) { + if (!@copy($this->connection . '/' . $source, $this->connection . '/' . $destination)) { + throw new ConnectionException("Cannot copy @source_file to @destination_file", null, array("@source" => $source, "@destination" => $destination)); + } + } + + function rmDir($directory) { + if (is_dir($directory)) { + $dh = opendir($directory); + while (($resource = readdir($dh)) !== FALSE) { + if ($resource == '.' || $resource == '..') { + continue; + } + $full_path = $directory . DIRECTORY_SEPARATOR . $resource; + if (is_file($full_path)) { + unlink($full_path); + } elseif (is_dir($full_path)) { + $this->_rmDir($full_path . '/'); + } + } + closedir($dh); + if (!rmdir($directory)) { + $exception = new ConnectionException("Cannot remove @directory", null, array("@directory" => $directory)); + throw $exception; + } + } + } + + function mkdir($directory) { + if (!@mkdir($directory)) { + $exception = new ConnectionException("Cannot create directory @directory", null, array("@directory" => $directory)); + throw $exception; + } + } +} + +class UpdateFTPExtension extends UpdateConnection { + function __construct($settings) { + $settings += array('hostname' => 'localhost', 'port' => 21); + $this->connection = ftp_connect($settings['hostname'], $settings['port']); + + if (!$this->connection) { + throw new ConnectionException("Cannot connect to FTP Server, please check settings"); + } + if (!ftp_login($this->connection, $settings['username'], $settings['password'])) { + throw new ConnectionException("Cannot login to FTP server, please check username and password"); + } + parent::__construct($settings); + } + + function copyFile($source, $destination) { + if (!@ftp_put($this->connection, $destination, $source, FTP_BINARY)) { + throw new ConnectionException("Cannot move @source to @destination", null, array("@source" => $source, "@destination" => $destination)); + } + } + + function mkdir($directory) { + if (!@ftp_mkdir($this->connection, $directory)) { + throw new ConnectionException("Cannot create directory @directory", null, array("@directory" => $directory)); + } + } + + function rmdir($directory) { + $pwd = ftp_pwd($this->connection); + if (!@ftp_chdir($this->connection, $directory)) { + throw new ConnectionException("Unable to change to directory @directory", null, array('@directory' => $directory)); + } + $list = @ftp_nlist($this->connection, '.'); + foreach ($list as $item){ + if ($item == '.' || $item == '..') { + continue; + } + if (@ftp_chdir($this->connection, $item)){ + ftp_chdir($this->connection, '..'); + $this->rmdir($item); + } + elseif (!ftp_delete($this->connection, $item)) { + throw new ConnectionException("Unable to remove to file @file", null, array('@file' => $item)); + } + } + ftp_chdir($this->connection, $pwd); + if (!ftp_rmdir($this->connection, $directory)) { + throw new ConnectionException("Unable to remove to directory @directory", null, array('@directory' => $directory)); + } + } +} === added file 'modules/update/connection/update.ssh.inc' --- modules/update/connection/update.ssh.inc 1970-01-01 00:00:00 +0000 +++ modules/update/connection/update.ssh.inc 2009-06-09 03:42:13 +0000 @@ -0,0 +1,46 @@ + 'localhost', 'port' => 22); + $this->connection = @ssh2_connect($setings['hostname'], $settings['port']); + if (!$this->connection) { + throw new ConnectionException("SSH Connection failed"); + } + if (!@ssh2_auth_password($this->connection, $settings['username'], $settings['password'])) { + throw new ConnectionException("The supplied username/password combination was not accepted."); + } + } + + function copyFile($source, $destination) { + if (!ssh2_scp_send($this->connection, $source, $destination)) { + throw new ConnectionException("Cannot copy @source_file to @destination_file", NULL, array("@source" => $source, "@destination" => $destination)); + } + } + + function copyDirectory($source, $destination) { + if (!@ssh2_exec($this->connection, 'cp -Rp ' . escapeshellarg($source) . " " . escapeshellarg($destination))) { + throw new ConnectionException("Cannot copy directory @directory", NULL, array("@directory" => $source)); + } + } + + function mkdir($directory) { + if (!@ssh2_exec($this->connection, 'mkdir ' . escapeshellarg("$directory"))) { + throw new ConnectionException("Cannot create directory @directory", null, array("@directory" => $directory)); + } + } + + function rmdir($directory) { + if (!@ssh2_exec($this->connection, 'rm -Rf ' . escapeshellarg("$directory"))) { + throw new ConnectionException("Cannot remove @directory", null, array("@directory" => $directory)); + } + } +} === added file 'modules/update/connection/update.test.inc' --- modules/update/connection/update.test.inc 1970-01-01 00:00:00 +0000 +++ modules/update/connection/update.test.inc 2009-06-09 03:42:20 +0000 @@ -0,0 +1,47 @@ +connection = new MockTestConnection($settings); + $this->connection->connectionString = 'test://' . urlencode($settings['username']) . ':' . urlencode($settings['password']) . '@' . $settings['hostname'] . ':' . $settings['hostname'] . '/'; + } + + function getConnection() { + return $this->connection; + } + + function copyFile($source, $destination) { + $this->connection->run("copyFile $source $destination"); + } + + function rmDir($directory) { + $this->connection->run("rmdir $directory"); + } + + function mkdir($directory) { + $this->connection->run("mkdir $directory"); + } + + function copyDirectory($source, $destination) { + $this->connection->run("copyDirectory $source $destination"); + } +} + +class MockTestConnection { + var $commandsRun = array(); + var $connectionString; + + function run($cmd) { + $this->commandsRun[] = $cmd; + } + + function flushCommands() { + $out = $this->commandsRun; + $this->commandsRun = array(); + return $out; + } +} === modified file 'modules/update/update.info' --- modules/update/update.info 2008-10-11 02:32:32 +0000 +++ modules/update/update.info 2009-06-09 01:46:05 +0000 @@ -10,3 +10,7 @@ files[] = update.fetch.inc files[] = update.report.inc files[] = update.settings.inc files[] = update.install +files[] = connection/update.ftp.inc +files[] = connection/update.ssh.inc +files[] = connection/update.test.inc +files[] = connection/update.connection.inc === modified file 'modules/update/update.module' --- modules/update/update.module 2009-06-08 05:00:11 +0000 +++ modules/update/update.module 2009-06-09 03:54:07 +0000 @@ -146,6 +146,13 @@ function update_menu() { 'access arguments' => array('administer site configuration'), 'type' => MENU_CALLBACK, ); + $items['admin/update/download-and-install'] = array( + 'title' => 'Download a module and install it * REMOVE THIS BEFORE RELEASE! THIS IS A DEMO! * ', + 'page callback' => 'update_download_install', + 'access arguments' => array('administer site configuration'), + 'type' => MENU_CALLBACK, + ); + return $items; } @@ -632,3 +639,168 @@ 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; +} + + +/** + * 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; +} + +/** +* Implementation of hook_update_connections(). +*/ +function update_update_connections() { + $connections = array(); + + // SSH2 lib connection is only available if the proper PHP extension is + // installed. + if (function_exists('ssh2_connect')) { + $connections['ssh'] = array( + 'title' => t('SSH'), + 'class' => 'UpdateSSHConnection', + ); + } + if (function_exists('ftp_connect')) { + $connections['ftp_extension'] = array( + 'title' => t('FTP Extension'), + 'class' => 'UpdateFTPExtension', + ); + } + + if (ini_get('allow_url_fopen')) { + $connections['ftp_wrapper'] = array( + 'title' => t('FTP Wrapper'), + 'class' => 'UpdateFTPWrapper', + ); + } + return $connections; +} + +/** +* 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_cli'] = array( + 'title' => t('Command line untar'), + 'class' => 'UpdateUntar', + ); + } + pclose($handle); + + return $untar_methods; +} + +/** + * Sample (simplified) page callback to handle an update from update status. + * REMOVE THIS BEFORE RELEASE! THIS IS A DEMO! + */ +function update_download_install($type, $name, $password) { + $connections = module_invoke_all('update_connection'); + if (!$connections) { + return t('No available connection backend.'); + } + $untar_methods = module_invoke_all('update_untar'); + if (!$untar_methods) { + return t('Nothing to untar with.'); + } + $available = update_get_available(); + module_load_include('inc', 'update', 'update.compare'); + $data = update_calculate_project_data($available); + if (empty($data[$name])) { + return t('@module could not be upgraded.', array('@module' => $module_name)); + } + $tarball = update_get_file($project['releases'][$project['latest_version']]['download_link']); + $untar_class = $connections[variable_get('update_preferred_untar', 'update_untar_cli')]['class']; + $untar = new $untar_class; + $files = $untar->untar($tarball); + $source = file_directory_temp() . '/update-extraction/' . $files[0]; + + $settings = variable_get('update_connection_settings', array()); + $settings['password'] = $password; + $connection_class = $connections[variable_get('update_preferred_connection', 'ftp_wrapper')]['class']; + $connection = new $connection_class($settings); + // @TODO do we want to remove the extension first? Maybe -- but usually files + // are overwriteable and the possible failures and race conditions are + // problematic. + $connection->addExtension($source, $type, $name); + drupal_goto('admin/reports/updates'); +} === added file 'modules/update/update.untar.inc' --- modules/update/update.untar.inc 1970-01-01 00:00:00 +0000 +++ modules/update/update.untar.inc 2009-06-09 03:45:38 +0000 @@ -0,0 +1,48 @@ +