diff --git includes/entity.inc includes/entity.inc index 8ba2e70..80af87e 100644 --- includes/entity.inc +++ includes/entity.inc @@ -126,7 +126,6 @@ class DrupalDefaultEntityController implements DrupalEntityControllerInterface { ->execute() ->fetchAllAssoc($this->idKey); } - // Pass all entities loaded from the database through $this->attachLoad(), // which attaches fields (if supported by the entity type) and calls the // entity type specific load callback, for example hook_node_load(). diff --git includes/filetransfer/filetransfer.inc includes/filetransfer/filetransfer.inc index 9efe66a..1a4f7f4 100644 --- includes/filetransfer/filetransfer.inc +++ includes/filetransfer/filetransfer.inc @@ -57,12 +57,18 @@ abstract class FileTransfer { * The source path. * @param $destination * The destination path. + * @param copyInto + * If the contents of $source should be coppied into $destination + * or if $source itself should be coppied into $destination. + * If this is NULL, then a trailing slash in $source will indicate + * that the contents of $source should be coppied. */ - public final function copyDirectory($source, $destination) { + public final function copyDirectory($source, $destination, $copyInto = NULL) { + $copyInto = $copyInto ? $copyInto : (substr($source, -1) == '/'); $source = $this->sanitizePath($source); $destination = $this->fixRemotePath($destination); $this->checkPath($destination); - $this->copyDirectoryJailed($source, $destination); + $this->copyDirectoryJailed($source, $destination, $copyInto); } /** @@ -171,16 +177,15 @@ abstract class FileTransfer { } /** - * Changes backslahes to slashes, also removes a trailing slash. - * - * @param string $path - * @return string - */ + * Changes backslahes to slashes and removes a trailing slash. + * + * @param string $path + * @return string; + */ function sanitizePath($path) { + substr($path, -1, 0) == '/' && $path = substr($path, 0, 1); + $path = str_replace('\\', '/', $path); // Windows path sanitiation. - if (substr($path, -1) == '/') { - $path = substr($path, 0, -1); - } return $path; } @@ -193,16 +198,32 @@ abstract class FileTransfer { * The source path. * @param $destination * The destination path. + * @param copyInto + * If the contents of $source should be coppied into $destination + * or if $source itself should be coppied into $destination. Defaults to FALSE */ - protected function copyDirectoryJailed($source, $destination) { - if ($this->isDirectory($destination)) { - $destination = $destination . '/' . basename($source); + protected function copyDirectoryJailed($source, $destination, $copyInto = FALSE) { + // If $source ends in "/", replace the contents of $destination with the contents of $source. + if ($copyInto) { + if (!$this->isDirectory($destination)) { + throw new FileTransferException('Cannot copy the contents of %source to %destination. %destination does not exist.', NULL, array('%source' => $source, '%destination' => $destination)); + } + } + else { + // If the destination exists, create a new directory inside of it and + // change the destination. + if ($this->isDirectory($destination)) { + $destination = $destination . '/' . basename($source); + } + $this->createDirectory($destination); } - $this->createDirectory($destination); + foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($source), RecursiveIteratorIterator::SELF_FIRST) as $filename => $file) { $relative_path = substr($filename, strlen($source)); if ($file->isDir()) { - $this->createDirectory($destination . $relative_path); + if (!$this->isDirectory($destination . $relative_path)) { + $this->createDirectory($destination . $relative_path); + } } else { $this->copyFile($file->getPathName(), $destination . $relative_path); diff --git includes/updater.inc includes/updater.inc index 307c305..0693fd6 100644 --- includes/updater.inc +++ includes/updater.inc @@ -54,17 +54,28 @@ interface DrupalUpdaterInterface { /** * Actions to run after an install has occurred. + * + * @param FileTransfer $filetransfer + * @param array $args + * @return void */ - public function postInstall(); + public function postInstall($filetransfer, $args); /** * Actions to run after an update has occurred. + * + * @param FileTransfer $filetransfer + * @param array $args + * @return void */ - public function postUpdate(); + public function postUpdate($filetransfer, $args); + + public function install(&$filetransfer, $options = array()); + public function update(&$filetransfer, $options = array()); } /** - * Base class for Updaters used in Drupal. + * Base class for Updaters used in Drupal for contributed projects. */ class Updater { @@ -91,12 +102,12 @@ class Updater { * @return Updater */ public static function factory($source) { - if (is_dir($source)) { - $updater = self::getUpdaterFromDirectory($source); - } - else { - throw new UpdaterException(t('Unable to determine the type of the source directory.')); + + if (!is_dir($source)) { + throw new UpdaterException(t('Source directory (%directory) does not exist', array('%directory' => $source))); } + + $updater = self::getUpdaterFromDirectory($source); return new $updater($source); } @@ -117,7 +128,7 @@ class Updater { return $class; } } - throw new UpdaterException(t('Cannot determine the type of project.')); + throw new UpdaterException(t('Unable to determine the type of the source directory.')); } /** @@ -190,6 +201,7 @@ class Updater { 'make_backup' => FALSE, 'install_dir' => $this->getInstallDirectory(), 'backup_dir' => $this->getBackupDir(), + 'replace_within_install_directory' => FALSE, ); return array_merge($args, $overrides); } @@ -224,23 +236,32 @@ class Updater { // 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. - if (is_dir($args['install_dir'] . '/' . $this->name)) { - // Remove the existing installed file. - $filetransfer->removeDirectory($args['install_dir'] . '/' . $this->name); + $source = $this->source; + if (TRUE === $args['replace_within_install_directory']) { + // This will copy the contents of the directory, not the directory itself. + if (substr($source, -1) != '/') { + $source .= '/'; + } + } + else { + if (substr($source, -1) == '/') { + $source = substr($source, 0, -1); + } + // 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. + if (is_dir($args['install_dir'] . '/' . $this->name)) { + // Remove the existing installed file. + $filetransfer->removeDirectory($args['install_dir'] . '/' . $this->name); + } } // Copy the directory in place. - $filetransfer->copyDirectory($this->source, $args['install_dir']); - - // Make sure what we just installed is readable by the web server. - $this->makeWorldReadable($filetransfer, $args['install_dir'] . '/' . $this->name); + $filetransfer->copyDirectory($source, $args['install_dir']); // Run the updates. // @TODO: decide if we want to implement this. - $this->postUpdate(); + $this->postUpdate($filetransfer, $args); // For now, just return a list of links of things to do. return $this->postUpdateTasks(); @@ -272,12 +293,9 @@ class Updater { // Copy the directory in place. $filetransfer->copyDirectory($this->source, $args['install_dir']); - // 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(); + $this->postInstall($filetransfer, $args); // For now, just return a list of links of things to do. return $this->postInstallTasks(); } @@ -365,13 +383,17 @@ class Updater { /** * Perform actions after new code is updated. */ - public function postUpdate() { + public function postUpdate($filetransfer, $args) { + // Make sure what we just installed is readable by the web server. + $this->makeWorldReadable($filetransfer, $args['install_dir'] . '/' . $this->name); } /** * Perform actions after installation. */ - public function postInstall() { + public function postInstall($filetransfer, $args) { + // Make sure what we just installed is readable by the web server. + $this->makeWorldReadable($filetransfer, $args['install_dir'] . '/' . $this->name); } /** diff --git modules/field/field.attach.inc modules/field/field.attach.inc index 50dbe05..59e039c 100644 --- modules/field/field.attach.inc +++ modules/field/field.attach.inc @@ -834,6 +834,7 @@ function field_attach_insert($obj_type, $object) { // Collect the storage backends used by the remaining fields in the objects. $storages = array(); foreach (field_info_instances($obj_type, $bundle) as $instance) { + dpr($instance); $field = field_info_field_by_id($instance['field_id']); $field_id = $field['id']; $field_name = $field['field_name']; diff --git modules/simpletest/drupal_web_test_case.php modules/simpletest/drupal_web_test_case.php index 03b03eb..5a89b0a 100644 --- modules/simpletest/drupal_web_test_case.php +++ modules/simpletest/drupal_web_test_case.php @@ -1048,8 +1048,14 @@ class DrupalWebTestCase extends DrupalTestCase { // Create test directory ahead of installation so fatal errors and debug // information can be logged during installation process. - $directory = $this->originalFileDirectory . '/simpletest/' . substr($db_prefix, 10); + // Use temporary files directory with the same prefix as the database. + $public_files_directory = $this->originalFileDirectory . '/simpletest/' . substr($db_prefix, 10); + $private_files_directory = $public_files_directory . '/private'; + + // Create the directories + $directory = file_directory_path('public'); file_prepare_directory($directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS); + file_prepare_directory($private_files_directory, FILE_CREATE_DIRECTORY); // Log fatal errors. ini_set('log_errors', 1); @@ -1111,21 +1117,12 @@ class DrupalWebTestCase extends DrupalTestCase { unset($GLOBALS['conf']['language_default']); $language = language_default(); - // Use the test mail class instead of the default mail handler class. - variable_set('mail_system', array('default-system' => 'TestingMailSystem')); - - // Use temporary files directory with the same prefix as the database. - $public_files_directory = $this->originalFileDirectory . '/' . $db_prefix; - $private_files_directory = $public_files_directory . '/private'; - // Set path variables variable_set('file_public_path', $public_files_directory); variable_set('file_private_path', $private_files_directory); - // Create the directories - $directory = file_directory_path('public'); - file_prepare_directory($directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS); - file_prepare_directory($private_files_directory, FILE_CREATE_DIRECTORY); + // Use the test mail class instead of the default mail handler class. + variable_set('mail_system', array('default-system' => 'TestingMailSystem')); drupal_set_time_limit($this->timeLimit); } @@ -1180,7 +1177,7 @@ class DrupalWebTestCase extends DrupalTestCase { if (preg_match('/simpletest\d+/', $db_prefix)) { // Delete temporary files directory. - file_unmanaged_delete_recursive($this->originalFileDirectory . '/' . $db_prefix); + file_unmanaged_delete_recursive($this->originalFileDirectory . '/simpletest/' . substr($db_prefix, 10)); // Remove all prefixed tables (all the tables in the schema). $schema = drupal_get_schema(NULL, TRUE); diff --git modules/simpletest/simpletest.module modules/simpletest/simpletest.module index 76f0396..8f75a98 100644 --- modules/simpletest/simpletest.module +++ modules/simpletest/simpletest.module @@ -444,11 +444,11 @@ function simpletest_clean_database() { * Find all leftover temporary directories and remove them. */ function simpletest_clean_temporary_directories() { - $files = scandir('public://'); + $files = scandir('public://simpletest'); $count = 0; foreach ($files as $file) { - $path = 'public://' . $file; - if (is_dir($path) && preg_match('/^simpletest\d+/', $file)) { + $path = 'public://simpletest/' . $file; + if (is_dir($path) && is_numeric($file)) { file_unmanaged_delete_recursive($path); $count++; } diff --git modules/simpletest/tests/filetransfer.test modules/simpletest/tests/filetransfer.test index c0a5657..ca4cb89 100644 --- modules/simpletest/tests/filetransfer.test +++ modules/simpletest/tests/filetransfer.test @@ -87,10 +87,41 @@ class FileTranferTest extends DrupalWebTestCase { } $this->assertTrue($gotit, 'Was able to copy a directory inside of the jailed area'); } + + function testFileCopy() { + $source = $this->_buildFakeModule(); + $destination = DRUPAL_ROOT . '/'. file_directory_path(); + $this->testConnection->shouldIsDirectoryReturnTrue = TRUE; + + // Try it with no trailing slash. + // This should try to create $destination/fake/fake + $this->testConnection->copyDirectory($source, $destination . '/fake'); + $commands = $this->testConnection->connection->flushCommands(); + $fail = TRUE; + foreach ($commands as $command) { + if (preg_match('/mkdir/', $command)) { + $fail = FALSE; + } + } + + $this->assertFalse($fail, 'Coppied source directory to destination'); + + $this->testConnection->copyDirectory($source . '/', $destination . '/fake'); + $commands = $this->testConnection->connection->flushCommands(); + $fail = FALSE; + foreach ($commands as $command) { + if (preg_match('/mkdir/', $command)) { + $this->fail('Copy into should not have created any directories'); + $fail = TRUE; + } + } + $this->assertFalse($fail, 'Coppied contents of source directory into destination'); + } + } /** - * Mock FileTransfer object for test case. + * Mock FileTransfer obdject for test case. */ class TestFileTransfer extends FileTransfer { protected $host = NULL; diff --git modules/system/system.module modules/system/system.module index e63d5a6..7e22bf8 100644 --- modules/system/system.module +++ modules/system/system.module @@ -1620,6 +1620,11 @@ function system_authorized_batch_process() { */ function system_updater_info() { return array( + 'core' => array( + 'class' => 'CoreUpdater', + 'name' => t('Update Core'), + 'weight' => -100, + ), 'module' => array( 'class' => 'ModuleUpdater', 'name' => t('Update modules'), diff --git modules/system/system.updater.inc modules/system/system.updater.inc index b5f1d15..9e274c9 100644 --- modules/system/system.updater.inc +++ modules/system/system.updater.inc @@ -6,7 +6,7 @@ * Subclasses of the Updater class to update Drupal core knows how to update. * At this time, only modules and themes are supported. */ - + /** * Class for updating modules using FileTransfer classes via authorize.php. */ @@ -67,14 +67,14 @@ class ModuleUpdater extends Updater implements DrupalUpdaterInterface { public function postInstallTasks() { return array( - l(t('Enable newly added modules in !project', array('!project' => $this->title)), 'admin/config/modules'), + l(t('Enable newly added modules in !project', array('!project' => $this->title)), 'admin/config/modules'), ); } public function postUpdateTasks() { // @todo: If there are schema updates. return array( - l(t('Run database updates for !project', array('!project' => $this->title)), 'update.php'), + l(t('Run database updates for !project', array('!project' => $this->title)), 'update.php'), ); } @@ -114,7 +114,8 @@ class ThemeUpdater extends Updater implements DrupalUpdaterInterface { return (bool) drupal_get_path('theme', $project_name); } - public function postInstall() { + public function postInstall($filetransfer, $args) { + parent::postInstall($filetransfer, $args); // Update the system table. clearstatcache(); drupal_static_reset('_system_rebuild_theme_data'); @@ -122,15 +123,64 @@ class ThemeUpdater extends Updater implements DrupalUpdaterInterface { // Active the theme db_update('system') - ->fields(array('status' => 1)) - ->condition('type', 'theme') - ->condition('name', $this->name) - ->execute(); + ->fields(array('status' => 1)) + ->condition('type', 'theme') + ->condition('name', $this->name) + ->execute(); + } + + public function postInstallTasks() { + return array( + l(t('Set the !project theme as default', array('!project' => $this->title)), 'admin/appearance'), + ); + } +} + +/** + * Class for updating themes using FileTransfer classes via authorize.php. + */ +class CoreUpdater extends Updater implements DrupalUpdaterInterface { + + public function getInstallDirectory() { + return DRUPAL_ROOT; + } + + protected function getInstallArgs($overrides = array()) { + $overrides += array('replace_within_install_directory' => TRUE); + return parent::getInstallArgs($overrides); + } + + public function isInstalled() { + return TRUE; } - + + static function canUpdateDirectory($directory) { + return is_file($directory . '/robots.txt'); + } + + /** + * (non-PHPdoc) + * @see sites/default/private/temp/update-extraction/drupal/includes/Updater#postInstall() + */ + public function postInstall($filetransfer, $args) { + // Pray. + } + public function postInstallTasks() { return array( - l(t('Set the !project theme as default', array('!project' => $this->title)), 'admin/appearance'), + l(t('Set the !project theme as default', array('!project' => $this->title)), 'admin/appearance'), ); } + + static function getProjectName($directory) { + return 'drupal'; + } + + static function getProjectTitle() { + return 'Drupal'; + } + + function postUpdate($filetransfer, $args) { + // Pray + } } diff --git modules/update/update.authorize.inc modules/update/update.authorize.inc index 023f3f5..0a42e75 100644 --- modules/update/update.authorize.inc +++ modules/update/update.authorize.inc @@ -44,7 +44,7 @@ function update_authorize_run_update($filetransfer, $projects) { 'finished' => 'update_authorize_update_batch_finished', 'file' => drupal_get_path('module', 'update') . '/update.authorize.inc', ); - + batch_set($batch); // Invoke the batch via authorize.php. system_authorized_batch_process(); diff --git modules/update/update.manager.inc modules/update/update.manager.inc index 649562a..5d7f1f0 100644 --- modules/update/update.manager.inc +++ modules/update/update.manager.inc @@ -35,7 +35,7 @@ * sets up a batch to copy each extracted update from the temporary location * into the live web root. */ - + /** * @defgroup update_manager_update Update manager for updating existing code. * @{ @@ -157,30 +157,14 @@ function update_manager_update_form($form, $form_state = array(), $context) { $entry['#attributes'] = array('class' => array('update-' . $type)); - // Drupal core needs to be upgraded manually. - $needs_manual = $project['project_type'] == 'core'; - - if ($needs_manual) { - // Since it won't be tableselect, #weight will add an extra column to the - // table if it's defined, so just unset it. The order doesn't matter that - // much in the manual updates table, anyway. - unset($entry['#weight']); - } - else { - $form['project_downloads'][$name] = array( - '#type' => 'value', - '#value' => $recommended_release['download_link'], - ); - } - // Based on what kind of project this is, save the entry into the // appropriate subarray. switch ($project['project_type']) { case 'core': // Core needs manual updates at this time. - $projects['manual'][$name] = $entry; - break; - + //$projects['manual'][$name] = $entry; + // break; + case 'module': case 'theme': $projects['enabled'][$name] = $entry; @@ -241,17 +225,6 @@ function update_manager_update_form($form, $form_state = array(), $context) { $form['#validate'][] = 'update_manager_update_form_validate'; } - if (!empty($projects['manual'])) { - $prefix = '

' . t('Manual updates required') . '

'; - $prefix .= '

' . t('Updates of Drupal core are not supported at this time.') . '

'; - $form['manual_updates'] = array( - '#type' => 'markup', - '#markup' => theme('table', array('header' => $headers, 'rows' => $projects['manual'])), - '#prefix' => $prefix, - '#weight' => 20, - ); - } - return $form; } @@ -420,11 +393,23 @@ function update_manager_confirm_update_form_submit($form, &$form_state) { $directory = _update_manager_extract_directory(); $projects = $_SESSION['update_manager_update_projects']; - unset($_SESSION['update_manager_update_projects']); + + //@todo: Put this back + //unset($_SESSION['update_manager_update_projects']); + + //@todo:Drupal core doesn't have "drupal" as dir name :( I guess we should start looking in the tarballs and getting the first directory we see. + // Or change the package format to have a spec file. foreach ($projects as $project => $url) { $project_location = $directory . '/' . $project; - $updater = Updater::factory($project_location); + + try { + $updater = Updater::factory($project_location); + } catch(Exception $e) { + form_set_error('submit', $e->getMessage()); + return; + } + $project_real_location = drupal_realpath($project_location); $updates[] = array( 'project' => $project, @@ -432,7 +417,6 @@ function update_manager_confirm_update_form_submit($form, &$form_state) { 'local_url' => $project_real_location, ); } - // If the owner of the last directory we extracted is the same as the // owner of our configuration directory (e.g. sites/default) where we're // trying to install the code, there's no need to prompt for FTP/SSH @@ -565,7 +549,7 @@ function update_manager_install_form_submit($form, &$form_state) { // @todo: find out if the module is already instealled, if so, throw an error. $local_cache = $finfo->uri; } - + $directory = _update_manager_extract_directory(); try { $archive = update_manager_archive_extract($local_cache, $directory); @@ -601,12 +585,12 @@ function update_manager_install_form_submit($form, &$form_state) { if (!$project_title) { form_set_error($field, t('Unable to determine %project name.', array('%project' => $project))); } - + if ($updater->isInstalled()) { form_set_error($field, t('%project is already installed.', array('%project' => $project_title))); return; } - + $project_real_location = drupal_realpath($project_location); $arguments = array( 'project' => $project, @@ -733,7 +717,7 @@ function update_manager_file_get($url) { if (!file_exists($cache_directory)) { mkdir($cache_directory); } - + if (!file_exists($local)) { return system_retrieve_file($url, $local); } diff --git sites/all/modules/README.txt sites/all/modules/README.txt deleted file mode 100644 index f503d7d..0000000 --- sites/all/modules/README.txt +++ /dev/null @@ -1,5 +0,0 @@ -// $Id: README.txt,v 1.1 2009-01-22 04:33:38 webchick Exp $ - -This directory should be used to place downloaded and custom modules -which are common to all sites. This will allow you to more easily -update Drupal core files. diff --git sites/default/default.settings.php sites/default/default.settings.php old mode 100644 new mode 100755