diff -u includes/theme.maintenance.inc includes/theme.maintenance.inc --- includes/theme.maintenance.inc 15 Oct 2009 09:49:31 -0000 +++ includes/theme.maintenance.inc 15 Oct 2009 11:00:32 -0000 @@ -235,12 +235,12 @@ * - message: The log message. * - success: A boolean indicating failure or success. */ -function theme_update_plugin_manager_message($variables) { +function theme_authorize_message($variables) { $output = ''; $message = $variables['message']; $success = $variables['success']; if ($success) { - $output .= '
  • ' . check_plain($message) . '
  • '; + $output .= '
  • ' . $message . '
  • '; } else { $output .= '
  • ' . t('Failed') . ': ' . $message . '
  • '; diff -u includes/updater.inc includes/updater.inc --- includes/updater.inc 15 Oct 2009 08:54:53 -0000 +++ includes/updater.inc 15 Oct 2009 10:47:36 -0000 @@ -220,6 +220,9 @@ throw new UpdaterException(t('Fatal error in update, cowardly refusing to wipe out the install directory.')); } + // Make sure the installation parent directory exists and is writable. + $this->prepareInstallDirectory($filetransfer, $args['install_dir']); + // Note: If the project is installed in sites/all, it will not be // deleted. It will be installed in sites/default as that will override // the sites/all reference and not break other sites which are using it. @@ -230,10 +233,14 @@ // Copy the directory in place. $filetransfer->copyDirectory($this->source, $args['install_dir']); -// plugin_make_world_executable($filetransfer, $args['install_dir'] . '/' . $this->name); + + // Make sure what we just installed is readable by the web server. + $this->makeWorldReadable($filetransfer, $args['install_dir'] . '/' . $this->name); + // Run the updates. // @TODO: decide if we want to implement this. $this->postUpdate(); + // For now, just return a list of links of things to do. return $this->postUpdateTasks(); } @@ -257,9 +264,16 @@ try { // Establish arguments with possible overrides. $args = $this->getInstallArgs($overrides); + + // Make sure the installation parent directory exists and is writable. + $this->prepareInstallDirectory($filetransfer, $args['install_dir']); + // Copy the directory in place. $filetransfer->copyDirectory($this->source, $args['install_dir']); -// plugin_make_world_executable($filetransfer, $args['install_dir'] . '/' . $this->name); + + // Make sure what we just installed is readable by the web server. + $this->makeWorldReadable($filetransfer, $args['install_dir'] . '/' . $this->name); + // Potentially enable something? // @TODO: decide if we want to implement this. $this->postInstall(); @@ -272,6 +286,67 @@ } /** + * Make sure the installation parent directory exists and is writable. + * + * @param FileTransfer $filetransfer + * Object which is a child of FileTransfer. + * @param string $directory + * The installation directory to prepare. + */ + public function prepareInstallDirectory(&$filetransfer, $directory) { + // Make the parent dir writable if need be and create the dir. + if (!is_dir($directory)) { + $parent_dir = dirname($directory); + if (!is_writable($parent_dir)) { + @chmod($parent_dir, 0755); + // It is expected that this will fail if the directory is owned by the + // FTP user. If the FTP user == web server, it will succeed. + try { + $filetransfer->createDirectory($directory); + $this->makeWorldReadable($filetransfer, $directory); + } + catch (FileTransferException $e) { + // Probably still not writable. Try to chmod and do it again. + // @todo: Make a new exception class so we can catch it differently. + try { + $old_perms = substr(sprintf('%o', fileperms($parent_dir)), -4); + $filetransfer->chmod($parent_dir, 0755); + $filetransfer->createDirectory($directory); + $this->makeWorldReadable($filetransfer, $directory); + // Put the permissions back. + $filetransfer->chmod($parent_dir, intval($old_perms, 8)); + } + catch (FileTransferException $e) { + $message = t($e->getMessage(), $e->arguments); + $throw_message = t('Unable to create %directory due to the following: %reason', array('%directory' => $install_location, '%reason' => $message)); + throw new UpdaterException($throw_message); + } + } + // Put the parent directory back. + @chmod($parent_dir, 0555); + } + } + } + + /** + * Ensure that a given directory is world readable. + * + * @param FileTransfer $filetransfer + * Object which is a child of FileTransfer. + * @param string $path + * The file path to make world readable. + * @param bool $recursive + * If the chmod should be applied recursively. + */ + public function makeWorldReadable(&$filetransfer, $path, $recursive = TRUE) { + if (!is_executable($path)) { + // Set it to read + execute. + $new_perms = substr(sprintf('%o', fileperms($path)), -4, -1) . "5"; + $filetransfer->chmod($path, intval($new_perms, 8), $recursive); + } + } + + /** * Perform a backup. * * @todo Not implemented. diff -u modules/update/update.authorize.inc modules/update/update.authorize.inc --- modules/update/update.authorize.inc 15 Oct 2009 01:20:59 -0000 +++ modules/update/update.authorize.inc 15 Oct 2009 11:22:35 -0000 @@ -43,7 +43,7 @@ 'title' => t('Installing updates'), 'init_message' => t('Preparing to update your site'), 'operations' => $operations, - 'finished' => 'update_authorize_batch_finished', + 'finished' => 'update_authorize_update_batch_finished', 'file' => drupal_get_path('module', 'update') . '/update.authorize.inc', ); @@ -85,7 +85,7 @@ 'init_message' => t('Preparing to install'), 'operations' => $operations, // @todo Use a different finished callback for different messages? - 'finished' => 'update_authorize_batch_finished', + 'finished' => 'update_authorize_install_batch_finished', 'file' => drupal_get_path('module', 'update') . '/update.authorize.inc', ); batch_set($batch); @@ -167,24 +167,94 @@ } /** - * Batch callback for when the authorized operations batch is finished. + * Batch callback for when the authorized update batch is finished. + * + * This processes the results and stashes them into SESSION such that + * authorize.php will render a report. Also responsible for putting the site + * back online and clearing the update status cache after a successful update. */ -function update_authorize_batch_finished($success, $results) { +function update_authorize_update_batch_finished($success, $results) { foreach ($results['log'] as $project => $messages) { if (!empty($messages['#abort'])) { $success = FALSE; } } + $offline = variable_get('site_offline', FALSE); if ($success) { + // Now that the update completed, we need to clear the cache of available + // update data and recompute our status, so prevent show bogus results. + _update_authorize_clear_update_status(); + + if ($offline) { + variable_set('site_offline', FALSE); + $page_message = array( + 'message' => t('Update was completed successfully. Your site has been taken out of maintenance mode.'), + 'type' => 'status', + ); + } + else { + $page_message = array( + 'message' => t('Update was completed successfully.'), + 'type' => 'status', + ); + } + } + elseif (!$offline) { + $page_message = array( + 'message' => t('Update failed! See the log below for more information.'), + 'type' => 'error', + ); + } + else { + $page_message = array( + 'message' => t('Update failed! See the log below for more information. Your site is still in maintenance mode.'), + 'type' => 'error', + ); + } + + // Set all these values into the SESSION so authorize.php can display them. + $_SESSION['authorize_results']['success'] = $success; + $_SESSION['authorize_results']['page_message'] = $page_message; + $_SESSION['authorize_results']['messages'] = $results['log']; + $_SESSION['authorize_results']['tasks'] = $results['tasks']; +} + +/** + * Batch callback for when the authorized install batch is finished. + * + * This processes the results and stashes them into SESSION such that + * authorize.php will render a report. Also responsible for putting the site + * back online after a successful install if necessary. + */ +function update_authorize_install_batch_finished($success, $results) { + foreach ($results['log'] as $project => $messages) { + if (!empty($messages['#abort'])) { + $success = FALSE; + } + } + $offline = variable_get('site_offline', FALSE); + if ($success && $offline) { variable_set('site_offline', FALSE); $page_message = array( - 'message' => t('Update was completed successfully! Your site has been taken out of maintenance mode.'), + 'message' => t('Installation was completed successfully. Your site has been taken out of maintenance mode.'), + 'type' => 'status', + ); + } + elseif ($success && !$offline) { + $page_message = array( + 'message' => t('Installation was completed successfully.'), 'type' => 'status', ); } + elseif (!$success && !$offline) { + $page_message = array( + 'message' => t('Installation failed! See the log below for more information.'), + 'type' => 'error', + ); + } else { $page_message = array( - 'message' => t('Update failed! See the log below for more information. Your site is still in maintenance mode.'), + 'message' => t('Installation failed! See the log below for more information. Your site is still in maintenance mode.'), 'type' => 'error', ); } @@ -209,0 +280,23 @@ +/** + * Private helper function to clear cached available update status data. + * + * Since this function is run at such a low bootstrap level, update.module is + * not loaded. So, we can't just call _update_cache_clear(). However, the + * database is bootstrapped, so we can do a query ourselves to clear out what + * we want to clear. + * + * Note that we do not want to just truncate the table, since that would + * remove items related to currently pending fetch attempts. + * + * @see update_authorize_update_batch_finished() + * @see _update_cache_clear() + */ +function _update_authorize_clear_update_status() { + $query = db_delete('cache_update'); + $query->condition( + db_or() + ->condition('cid', 'update_project_%', 'LIKE') + ->condition('cid', 'available_releases::%', 'LIKE') + ); + $query->execute(); +}