diff --git a/core/includes/batch.inc b/core/includes/batch.inc index 0b07d8e..8d15213 100644 --- a/core/includes/batch.inc +++ b/core/includes/batch.inc @@ -52,6 +52,9 @@ function _batch_page() { // Retrieve the current state of the batch. if (!$batch) { $batch = batch_load($_REQUEST['id']); + debug($_REQUEST); + debug(drupal_get_token($_REQUEST['id'])); + debug(db_query("SELECT token FROM {batch} WHERE bid = :bid", array(':bid' => $_REQUEST['id']))->fetchField()); if (!$batch) { drupal_set_message(t('No active batch.'), 'error'); drupal_goto(); @@ -454,6 +457,8 @@ function _batch_next_set() { */ function _batch_finished() { $batch = &batch_get(); + debug($batch); + error_log(var_export($batch, TRUE)); // Execute the 'finished' callbacks for each batch set, if defined. foreach ($batch['sets'] as $batch_set) { @@ -480,18 +485,17 @@ function _batch_finished() { $queue->deleteQueue(); } } + // Clean-up the session. Not needed for CLI updates. + if (isset($_SESSION)) { + unset($_SESSION['batches'][$batch['id']]); + if (empty($_SESSION['batches'])) { + unset($_SESSION['batches']); + } + } } $_batch = $batch; $batch = NULL; - // Clean-up the session. Not needed for CLI updates. - if (isset($_SESSION)) { - unset($_SESSION['batches'][$batch['id']]); - if (empty($_SESSION['batches'])) { - unset($_SESSION['batches']); - } - } - // Redirect if needed. if ($_batch['progressive']) { // Revert the 'destination' that was saved in batch_process(). diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc index 95adc95..f1bb22e 100644 --- a/core/includes/bootstrap.inc +++ b/core/includes/bootstrap.inc @@ -2575,6 +2575,8 @@ function drupal_fast_404() { * Returns TRUE if a Drupal installation is currently being attempted. */ function drupal_installation_attempted() { + return isset($GLOBALS['install_state']); + // @todo Entirely incompatible with non-interactive installer invoked from SimpleTest. return defined('MAINTENANCE_MODE') && MAINTENANCE_MODE == 'install'; } diff --git a/core/includes/cache.inc b/core/includes/cache.inc index 73bb05f..554fe95 100644 --- a/core/includes/cache.inc +++ b/core/includes/cache.inc @@ -24,13 +24,12 @@ * @see Drupal\Core\Cache\CacheBackendInterface */ function cache($bin = 'cache') { + $cache_objects = &drupal_static(__FUNCTION__, array()); + // Temporary backwards compatibiltiy layer, allow old style prefixed cache // bin names to be passed as arguments. $bin = str_replace('cache_', '', $bin); - // We do not use drupal_static() here because we do not want to change the - // storage of a cache bin mid-request. - static $cache_objects; if (!isset($cache_objects[$bin])) { $cache_backends = cache_get_backends(); $class = isset($cache_backends[$bin]) ? $cache_backends[$bin] : $cache_backends['cache']; diff --git a/core/includes/form.inc b/core/includes/form.inc index 395cca7..dfc1cb5 100644 --- a/core/includes/form.inc +++ b/core/includes/form.inc @@ -3178,6 +3178,10 @@ function form_process_checkbox($element, $form_state) { * Processes a checkboxes form element. */ function form_process_checkboxes($element) { + // The #type checkboxes element itself does not need validation. + // @todo Fix handling of values for checkboxes in programmed form submissions. + unset($element['#needs_validation']); + $value = is_array($element['#value']) ? $element['#value'] : array(); $element['#tree'] = TRUE; if (count($element['#options']) > 0) { diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc index 32ce838..84f98d9 100644 --- a/core/includes/install.core.inc +++ b/core/includes/install.core.inc @@ -224,7 +224,9 @@ function install_state_defaults() { */ function install_begin_request(&$install_state) { // Add any installation parameters passed in via the URL. - $install_state['parameters'] += $_GET; + if ($install_state['interactive']) { + $install_state['parameters'] += $_GET; + } // Validate certain core settings that are used throughout the installation. if (!empty($install_state['parameters']['profile'])) { @@ -240,11 +242,10 @@ function install_begin_request(&$install_state) { if (!$install_state['interactive']) { drupal_override_server_variables($install_state['server']); } - // The user agent header is used to pass a database prefix in the request when // running tests. However, for security reasons, it is imperative that no // installation be permitted using such a prefix. - if (isset($_SERVER['HTTP_USER_AGENT']) && strpos($_SERVER['HTTP_USER_AGENT'], "simpletest") !== FALSE) { + elseif (isset($_SERVER['HTTP_USER_AGENT']) && strpos($_SERVER['HTTP_USER_AGENT'], "simpletest") !== FALSE) { header($_SERVER['SERVER_PROTOCOL'] . ' 403 Forbidden'); exit; } @@ -459,6 +460,23 @@ function install_run_task($task, &$install_state) { } elseif ($task['type'] == 'batch') { + if (!$install_state['interactive']) { + $batch = $function($install_state); + if (!empty($batch['operations'])) { + // Build a dummy 'context' array to avoid exceptions. + $batch_context = array( + 'sandbox' => &$current_set['sandbox'], + 'results' => &$current_set['results'], + 'finished' => &$finished, + 'message' => &$task_message, + ); + foreach ($batch['operations'] as $operation) { + list($function, $args) = $operation; + call_user_func_array($function, array_merge($args, array(&$batch_context))); + } + } + return; + } // Start a new batch based on the task function, if one is not running // already. $current_batch = variable_get('install_current_batch'); @@ -1331,7 +1349,7 @@ function install_select_language(&$install_state) { } // One language, but not an interactive installation. Assume the user // knows what he is doing. - $langcode = current($files); + $file = current($files); $install_state['parameters']['langcode'] = $file->langcode; return; } diff --git a/core/includes/install.inc b/core/includes/install.inc index 41d244a..e47cd44 100644 --- a/core/includes/install.inc +++ b/core/includes/install.inc @@ -93,10 +93,10 @@ function drupal_load_updates() { * @see install_profile_info() */ function drupal_install_profile_distribution_name() { + global $install_state; // During installation, the profile information is stored in the global // installation state (it might not be saved anywhere yet). - if (drupal_installation_attempted()) { - global $install_state; + if (isset($install_state['profile_info']['distribution_name'])) { return $install_state['profile_info']['distribution_name']; } // At all other times, we load the profile via standard methods. diff --git a/core/modules/node/node.module b/core/modules/node/node.module index 3db5bbd..83c4483 100644 --- a/core/modules/node/node.module +++ b/core/modules/node/node.module @@ -3988,6 +3988,9 @@ function node_unpublish_by_keyword_action(Node $node, $context) { */ function node_requirements($phase) { $requirements = array(); + if ($phase !== 'runtime') { + return $requirements; + } // Ensure translations don't break at install time $t = get_t(); // Only show rebuild button if there are either 0, or 2 or more, rows diff --git a/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php index 6517bd1..586f634 100644 --- a/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php @@ -150,15 +150,7 @@ abstract class TestBase { ); // Store assertion for display after the test has completed. - try { - $connection = Database::getConnection('default', 'simpletest_original_default'); - } - catch (ConnectionNotDefinedException $e) { - // If the test was not set up, the simpletest_original_default - // connection does not exist. - $connection = Database::getConnection('default', 'default'); - } - $connection + self::getDatabaseConnection() ->insert('simpletest') ->fields($assertion) ->execute(); @@ -212,7 +204,8 @@ abstract class TestBase { 'file' => $caller['file'], ); - return db_insert('simpletest') + return self::getDatabaseConnection() + ->insert('simpletest') ->fields($assertion) ->execute(); } @@ -228,11 +221,24 @@ abstract class TestBase { * @see Drupal\simpletest\TestBase::insertAssert() */ public static function deleteAssert($message_id) { - return (bool) db_delete('simpletest') + return (bool) self::getDatabaseConnection() + ->delete('simpletest') ->condition('message_id', $message_id) ->execute(); } + public static function getDatabaseConnection() { + try { + $connection = Database::getConnection('default', 'simpletest_original_default'); + } + catch (ConnectionNotDefinedException $e) { + // If the test was not set up, the simpletest_original_default + // connection does not exist. + $connection = Database::getConnection('default', 'default'); + } + return $connection; + } + /** * Cycles through backtrace until the first non-assertion method is found. * @@ -593,6 +599,14 @@ abstract class TestBase { ); } Database::addConnectionInfo('default', 'default', $connection_info['default']); + + // Additionally override global $databases, since the installer does not use + // the Database connection info. + // @see install_verify_database_settings() + // @see install_database_errors() + // @todo Fix installer to use Database connection info. + global $databases; + $databases['default']['default'] = $connection_info['default']; } /** @@ -623,6 +637,8 @@ abstract class TestBase { $this->originalFileDirectory = variable_get('file_public_path', conf_path() . '/files'); $this->originalProfile = drupal_get_profile(); $this->originalUser = $user; +// $this->originalSession = isset($_SESSION) ? $_SESSION : NULL; + $this->originalGlobals = $GLOBALS; // Save and clean the shutdown callbacks array because it is static cached // and will be changed by the test run. Otherwise it will contain callbacks @@ -692,6 +708,9 @@ abstract class TestBase { // Restore original database connection. Database::removeConnection('default'); Database::renameConnection('simpletest_original_default', 'default'); + global $databases; + $connection_info = Database::getConnectionInfo('default'); + $databases['default']['default'] = $connection_info['default']; // Reset all static variables. drupal_static_reset(); @@ -714,6 +733,8 @@ abstract class TestBase { // Restore original user session. $user = $this->originalUser; +// $_SESSION = $this->originalSession; + $GLOBALS = $this->originalGlobals; drupal_save_session(TRUE); } diff --git a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php index 0856611..4ccc2b7 100644 --- a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php @@ -580,7 +580,16 @@ abstract class WebTestBase extends TestBase { protected function setUp() { global $user, $conf; $language_interface = drupal_container()->get(LANGUAGE_TYPE_INTERFACE); - + //Get old batch id and token + $batch = batch_get(); + if(!empty($batch['id'])) { + $this->batch_id = $batch['id']; + $this->old_token = drupal_get_token($this->batch_id); + } + else { + $this->batch_id = NULL; + $this->old_token = NULL; + } // Create the database prefix for this test. $this->prepareDatabasePrefix(); @@ -597,18 +606,99 @@ abstract class WebTestBase extends TestBase { // write back to persistent caches when they are destructed. $this->changeDatabasePrefix(); - // Preset the 'install_profile' system variable, so the first call into - // system_rebuild_module_data() (in drupal_install_system()) will register - // the test's profile as a module. Without this, the installation profile of - // the parent site (executing the test) is registered, and the test - // profile's hook_install() and other hook implementations are never invoked. - $conf['install_profile'] = $this->profile; - - // Perform the actual Drupal installation. - include_once DRUPAL_ROOT . '/core/includes/install.inc'; - drupal_install_system(); + // Set the 'simpletest_parent_profile' variable to add the parent profile's + // search path to the child site's search paths. + // @see drupal_system_listing() + $conf['simpletest_parent_profile'] = $this->originalProfile; - $this->preloadRegistry(); + // Run the Drupal installer non-interactively. + // @see install.php, install.core.inc + require_once DRUPAL_ROOT . '/core/includes/install.core.inc'; + $connection_info = Database::getConnectionInfo('default'); + $this->root_user = (object) array( + 'name' => 'admin', + 'mail' => 'admin@example.com', + 'pass_raw' => $this->randomName(), + ); + $settings = array( + 'interactive' => FALSE, + 'parameters' => array( + 'profile' => $this->profile, + 'langcode' => 'en', + ), + 'forms' => array( + 'install_settings_form' => array( + 'driver' => $connection_info['default']['driver'], + 'username' => $connection_info['default']['username'], + 'host' => $connection_info['default']['host'], + 'port' => $connection_info['default']['port'], + 'password' => $connection_info['default']['password'], + 'database' => $connection_info['default']['database'], + 'prefix' => $connection_info['default']['prefix'], + ), + 'install_configure_form' => array( + 'site_name' => 'Drupal', + 'site_mail' => 'simpletest@example.com', + 'account' => array( + 'name' => $this->root_user->name, + 'mail' => $this->root_user->mail, + 'pass' => array( + 'pass1' => $this->root_user->pass_raw, + 'pass2' => $this->root_user->pass_raw, + ), + ), + 'update_status_module' => array( + 1 => FALSE, + 2 => FALSE, + ), + ), + ), + ); + // When running tests through the Simpletest UI (vs. on the command line), + // Simpletest's batch conflicts with the installer's batch. Batch API does + // not support the concept of nested batches (in which the nested is not + // progressive), so we need to temporarily pretend there was no batch. + // Backup the currently running Simpletest batch. +// $parent_batch = batch_get(); + +// $parent_session = $_SESSION; +// $parent_get = $_GET; + + // Reset it and invoke the non-interactive installer. +// $batch = &batch_get(); +// $batch = array(); + + // @todo The above has been replaced with changing install_run_task() in + // install.core.inc to not invoke the Batch API at all for install tasks + // of type 'batch'. + + // @todo _batch_page() calls into batch_load() to reload the batch definition, + // but that uses drupal_get_token() to validate that the batch belongs to + // the current user. drupal_get_token() returns a different value after + // test execution, so the batch finished page does not load. +// $session_id = session_id(); + $originalSession = isset($_SESSION) ? $_SESSION : NULL; + $originalGlobals = $GLOBALS; + $request = request(); + + install_drupal($settings); + +// session_id($session_id); + $_SESSION = $originalSession; + $GLOBALS = $originalGlobals; + request($request); + + // Restore the original Simpletest batch. +// $batch = &batch_get(); +// $batch = $parent_batch; + +// $_SESSION = $parent_session; +// $_GET = $parent_get; + // @todo Simpletest batch finishes, but redirects to /batch (which in turn + // redirects to the front page, along with the message "No active batch." + // -- something in the Drupal installer code seems to reset/erase the + // Simpletest batch's ID or final redirection target. +// $this->verbose(var_export($batch, TRUE)); // Set path variables. variable_set('file_public_path', $this->public_files_directory); @@ -618,16 +708,8 @@ abstract class WebTestBase extends TestBase { // Set the 'simpletest_parent_profile' variable to add the parent profile's // search path to the child site's search paths. // @see drupal_system_listing() - // @todo This may need to be primed like 'install_profile' above. variable_set('simpletest_parent_profile', $this->originalProfile); - // Include the testing profile. - variable_set('install_profile', $this->profile); - $profile_details = install_profile_info($this->profile, 'en'); - - // Install the modules specified by the testing profile. - module_enable($profile_details['dependencies'], FALSE); - // Install modules needed for this test. This could have been passed in as // either a single array argument or a variable number of string arguments. // @todo Remove this compatibility layer in Drupal 8, and only accept @@ -641,36 +723,15 @@ abstract class WebTestBase extends TestBase { $this->assertTrue($success, t('Enabled modules: %modules', array('%modules' => implode(', ', $modules)))); } - // Run the profile tasks. - $install_profile_module_exists = db_query("SELECT 1 FROM {system} WHERE type = 'module' AND name = :name", array( - ':name' => $this->profile, - ))->fetchField(); - if ($install_profile_module_exists) { - module_enable(array($this->profile), FALSE); - } - // Reset/rebuild all data structures after enabling the modules. $this->resetAll(); - // Run cron once in that environment, as install.php does at the end of - // the installation process. - drupal_cron_run(); - // Ensure that the session is not written to the new environment and replace // the global $user session with uid 1 from the new test site. drupal_save_session(FALSE); // Login as uid 1. $user = user_load(1); - // Restore necessary variables. - variable_set('install_task', 'done'); - config('system.site')->set('mail', 'simpletest@example.com')->save(); - variable_set('date_default_timezone', date_default_timezone_get()); - - // Set up English language. - unset($conf['language_default']); - $language_interface = language_default(); - // Use the test mail class instead of the default mail handler class. variable_set('mail_system', array('default-system' => 'Drupal\Core\Mail\VariableLog')); @@ -679,42 +740,6 @@ abstract class WebTestBase extends TestBase { } /** - * Preload the registry from the testing site. - * - * This method is called by Drupal\simpletest\WebTestBase::setUp(), and preloads - * the registry from the testing site to cut down on the time it takes to - * set up a clean environment for the current test run. - */ - protected function preloadRegistry() { - // Use two separate queries, each with their own connections: copy the - // {registry} and {registry_file} tables over from the parent installation - // to the child installation. - $original_connection = Database::getConnection('default', 'simpletest_original_default'); - $test_connection = Database::getConnection(); - - foreach (array('registry', 'registry_file') as $table) { - // Find the records from the parent database. - $source_query = $original_connection - ->select($table, array(), array('fetch' => PDO::FETCH_ASSOC)) - ->fields($table); - - $dest_query = $test_connection->insert($table); - - $first = TRUE; - foreach ($source_query->execute() as $row) { - if ($first) { - $dest_query->fields(array_keys($row)); - $first = FALSE; - } - // Insert the records into the child database. - $dest_query->values($row); - } - - $dest_query->execute(); - } - } - - /** * Reset all data structures after having enabled new modules. * * This method is called by Drupal\simpletest\WebTestBase::setUp() after enabling @@ -789,6 +814,16 @@ abstract class WebTestBase extends TestBase { // Close the CURL handler. $this->curlClose(); + + if(!empty($this->batch_id)) { + //Regenerate batch token after install_drupal created a new session_id. + db_update('batch') + ->fields(array( + 'token' => drupal_get_token($this->batch_id), + )) + ->condition('token', $this->old_token) + ->execute(); + } } /**