diff --git a/commands/core/core.drush.inc b/commands/core/core.drush.inc index ba972e6423..e5b95ccd12 100644 --- a/commands/core/core.drush.inc +++ b/commands/core/core.drush.inc @@ -115,7 +115,6 @@ function core_drush_engine_type_info() { */ function core_drush_engine_drupal() { $engines = array( - 'update'=> array(), 'environment' => array(), 'site_install' => array(), 'pm' => array(), diff --git a/commands/core/drupal/update.inc b/commands/core/drupal/update.inc deleted file mode 100644 index cd7157e17b..0000000000 --- a/commands/core/drupal/update.inc +++ /dev/null @@ -1,387 +0,0 @@ - FALSE, 'query' => 'What went wrong'); - * The schema version will not be updated in this case, and all the - * aborted updates will continue to appear on update.php as updates that - * have not yet been run. - * - * @param $module - * The module whose update will be run. - * @param $number - * The update number to run. - * @param $context - * The batch context array - */ -function drush_update_do_one($module, $number, $dependency_map, &$context) { - $function = $module . '_update_' . $number; - - // If this update was aborted in a previous step, or has a dependency that - // was aborted in a previous step, go no further. - if (!empty($context['results']['#abort']) && array_intersect($context['results']['#abort'], array_merge($dependency_map, array($function)))) { - return; - } - - $context['log'] = FALSE; - - \Drupal::moduleHandler()->loadInclude($module, 'install'); - - $ret = array(); - if (function_exists($function)) { - try { - if ($context['log']) { - Database::startLog($function); - } - - drush_log("Executing " . $function); - $ret['results']['query'] = $function($context['sandbox']); - $ret['results']['success'] = TRUE; - } - // @TODO We may want to do different error handling for different exception - // types, but for now we'll just print the message. - catch (Exception $e) { - $ret['#abort'] = array('success' => FALSE, 'query' => $e->getMessage()); - drush_set_error('DRUPAL_EXCEPTION', $e->getMessage()); - } - - if ($context['log']) { - $ret['queries'] = Database::getLog($function); - } - } - else { - $ret['#abort'] = array('success' => FALSE); - drush_set_error('DRUSH_UPDATE_FUNCTION_NOT_FOUND', dt('Update function @function not found', array('@function' => $function))); - } - - if (isset($context['sandbox']['#finished'])) { - $context['finished'] = $context['sandbox']['#finished']; - unset($context['sandbox']['#finished']); - } - - if (!isset($context['results'][$module])) { - $context['results'][$module] = array(); - } - if (!isset($context['results'][$module][$number])) { - $context['results'][$module][$number] = array(); - } - $context['results'][$module][$number] = array_merge($context['results'][$module][$number], $ret); - - if (!empty($ret['#abort'])) { - // Record this function in the list of updates that were aborted. - $context['results']['#abort'][] = $function; - } - - // Record the schema update if it was completed successfully. - if ($context['finished'] == 1 && empty($ret['#abort'])) { - drupal_set_installed_schema_version($module, $number); - } - - $context['message'] = 'Performing ' . $function; -} - -function update_main() { - // In D8, we expect to be in full bootstrap. - drush_bootstrap_to_phase(DRUSH_BOOTSTRAP_DRUPAL_FULL); - - require_once DRUPAL_ROOT . '/core/includes/install.inc'; - require_once DRUPAL_ROOT . '/core/includes/update.inc'; - drupal_load_updates(); - update_fix_compatibility(); - - // Pending hook_update_N() implementations. - $pending = update_get_update_list(); - - // Pending hook_post_update_X() implementations. - $post_updates = \Drupal::service('update.post_update_registry')->getPendingUpdateInformation(); - - $start = array(); - - $change_summary = []; - if (drush_get_option('entity-updates', FALSE)) { - $change_summary = \Drupal::entityDefinitionUpdateManager()->getChangeSummary(); - } - - // Print a list of pending updates for this module and get confirmation. - if (count($pending) || count($change_summary) || count($post_updates)) { - drush_print(dt('The following updates are pending:')); - drush_print(); - - foreach ($change_summary as $entity_type_id => $changes) { - drush_print($entity_type_id . ' entity type : '); - foreach ($changes as $change) { - drush_print(strip_tags($change), 2); - } - } - - foreach (array('update', 'post_update') as $update_type) { - $updates = $update_type == 'update' ? $pending : $post_updates; - foreach ($updates as $module => $updates) { - if (isset($updates['start'])) { - drush_print($module . ' module : '); - if (!empty($updates['pending'])) { - $start += [$module => array()]; - - $start[$module] = array_merge($start[$module], $updates['pending']); - foreach ($updates['pending'] as $update) { - drush_print(strip_tags($update), 2); - } - } - drush_print(); - } - } - } - - if (!drush_confirm(dt('Do you wish to run all pending updates?'))) { - return drush_user_abort(); - } - - drush_update_batch($start); - } - else { - drush_log(dt("No database updates required"), LogLevel::SUCCESS); - } - - return count($pending) + count($change_summary) + count($post_updates); -} - -function _update_batch_command($id) { - // In D8, we expect to be in full bootstrap. - drush_bootstrap_to_phase(DRUSH_BOOTSTRAP_DRUPAL_FULL); - - drush_batch_command($id); -} - -/** - * Start the database update batch process. - */ -function drush_update_batch() { - $start = drush_get_update_list(); - // Resolve any update dependencies to determine the actual updates that will - // be run and the order they will be run in. - $updates = update_resolve_dependencies($start); - - // Store the dependencies for each update function in an array which the - // batch API can pass in to the batch operation each time it is called. (We - // do not store the entire update dependency array here because it is - // potentially very large.) - $dependency_map = array(); - foreach ($updates as $function => $update) { - $dependency_map[$function] = !empty($update['reverse_paths']) ? array_keys($update['reverse_paths']) : array(); - } - - $operations = array(); - - foreach ($updates as $update) { - if ($update['allowed']) { - // Set the installed version of each module so updates will start at the - // correct place. (The updates are already sorted, so we can simply base - // this on the first one we come across in the above foreach loop.) - if (isset($start[$update['module']])) { - drupal_set_installed_schema_version($update['module'], $update['number'] - 1); - unset($start[$update['module']]); - } - // Add this update function to the batch. - $function = $update['module'] . '_update_' . $update['number']; - $operations[] = array('drush_update_do_one', array($update['module'], $update['number'], $dependency_map[$function])); - } - } - - // Apply post update hooks. - $post_updates = \Drupal::service('update.post_update_registry')->getPendingUpdateFunctions(); - if ($post_updates) { - $operations[] = ['drush_drupal_cache_clear_all', []]; - foreach ($post_updates as $function) { - $operations[] = ['update_invoke_post_update', [$function]]; - } - } - - // Lastly, perform entity definition updates, which will update storage - // schema if needed. If module update functions need to work with specific - // entity schema they should call the entity update service for the specific - // update themselves. - // @see \Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface::applyEntityUpdate() - // @see \Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface::applyFieldUpdate() - if (drush_get_option('entity-updates', FALSE) && \Drupal::entityDefinitionUpdateManager()->needsUpdates()) { - $operations[] = array('drush_update_entity_definitions', array()); - } - - $batch['operations'] = $operations; - $batch += array( - 'title' => 'Updating', - 'init_message' => 'Starting updates', - 'error_message' => 'An unrecoverable error has occurred. You can find the error message below. It is advised to copy it to the clipboard for reference.', - 'finished' => 'drush_update_finished', - 'file' => 'includes/update.inc', - ); - batch_set($batch); - \Drupal::service('state')->set('system.maintenance_mode', TRUE); - drush_backend_batch_process('updatedb-batch-process'); - \Drupal::service('state')->set('system.maintenance_mode', FALSE); -} - -/** - * Apply entity schema updates. - */ -function drush_update_entity_definitions(&$context) { - try { - \Drupal::entityDefinitionUpdateManager()->applyUpdates(); - } - catch (EntityStorageException $e) { - watchdog_exception('update', $e); - $variables = Error::decodeException($e); - unset($variables['backtrace']); - // The exception message is run through - // \Drupal\Component\Utility\SafeMarkup::checkPlain() by - // \Drupal\Core\Utility\Error::decodeException(). - $ret['#abort'] = array('success' => FALSE, 'query' => t('%type: !message in %function (line %line of %file).', $variables)); - $context['results']['core']['update_entity_definitions'] = $ret; - $context['results']['#abort'][] = 'update_entity_definitions'; - } -} - -// Copy of protected \Drupal\system\Controller\DbUpdateController::getModuleUpdates. -function drush_get_update_list() { - $return = array(); - $updates = update_get_update_list(); - foreach ($updates as $module => $update) { - $return[$module] = $update['start']; - } - - return $return; -} - -/** - * Process and display any returned update output. - * - * @see \Drupal\system\Controller\DbUpdateController::batchFinished() - * @see \Drupal\system\Controller\DbUpdateController::results() - */ -function drush_update_finished($success, $results, $operations) { - - if (!drush_get_option('cache-clear', TRUE)) { - drush_log(dt("Skipping cache-clear operation due to --cache-clear=0 option."), LogLevel::WARNING); - } - else { - drupal_flush_all_caches(); - } - - foreach ($results as $module => $updates) { - if ($module != '#abort') { - foreach ($updates as $number => $queries) { - foreach ($queries as $query) { - // If there is no message for this update, don't show anything. - if (empty($query['query'])) { - continue; - } - - if ($query['success']) { - drush_log(strip_tags($query['query'])); - } - else { - drush_set_error(dt('Failed: ') . strip_tags($query['query'])); - } - } - } - } - } -} - -/** - * Return a 2 item array with - * - an array where each item is a 3 item associative array describing a pending update. - * - an array listing the first update to run, keyed by module. - */ -function updatedb_status() { - $pending = update_get_update_list(); - - $return = array(); - // Ensure system module's updates run first. - $start['system'] = array(); - - foreach (\Drupal::entityDefinitionUpdateManager()->getChangeSummary() as $entity_type_id => $changes) { - foreach ($changes as $change) { - $return[] = array( - 'module' => dt('@type entity type', array('@type' => $entity_type_id)), 'update_id' => '', 'description' => strip_tags($change)); - } - } - - // Print a list of pending updates for this module and get confirmation. - foreach ($pending as $module => $updates) { - if (isset($updates['start'])) { - foreach ($updates['pending'] as $update_id => $description) { - // Strip cruft from front. - $description = str_replace($update_id . ' - ', '', $description); - $return[] = array('module' => ucfirst($module), 'update_id' => $update_id, 'description' => $description); - } - if (isset($updates['start'])) { - $start[$module] = $updates['start']; - } - } - } - - return array($return, $start); -} - -/** - * Apply pending entity schema updates. - */ -function entity_updates_main() { - $change_summary = \Drupal::entityDefinitionUpdateManager()->getChangeSummary(); - if (!empty($change_summary)) { - drush_print(dt('The following updates are pending:')); - drush_print(); - - foreach ($change_summary as $entity_type_id => $changes) { - drush_print($entity_type_id . ' entity type : '); - foreach ($changes as $change) { - drush_print(strip_tags($change), 2); - } - } - - if (!drush_confirm(dt('Do you wish to run all pending updates?'))) { - return drush_user_abort(); - } - - $operations[] = array('drush_update_entity_definitions', array()); - - - $batch['operations'] = $operations; - $batch += array( - 'title' => 'Updating', - 'init_message' => 'Starting updates', - 'error_message' => 'An unrecoverable error has occurred. You can find the error message below. It is advised to copy it to the clipboard for reference.', - 'finished' => 'drush_update_finished', - 'file' => 'includes/update.inc', - ); - batch_set($batch); - \Drupal::service('state')->set('system.maintenance_mode', TRUE); - drush_backend_batch_process('updatedb-batch-process'); - \Drupal::service('state')->set('system.maintenance_mode', FALSE); - } - else { - drush_log(dt("No entity schema updates required"), LogLevel::SUCCESS); - } -} diff --git a/commands/core/drupal/update_7.inc b/commands/core/drupal/update_7.inc deleted file mode 100644 index d18e046b77..0000000000 --- a/commands/core/drupal/update_7.inc +++ /dev/null @@ -1,339 +0,0 @@ - FALSE, 'query' => 'What went wrong'); - * The schema version will not be updated in this case, and all the - * aborted updates will continue to appear on update.php as updates that - * have not yet been run. - * - * @param $module - * The module whose update will be run. - * @param $number - * The update number to run. - * @param $context - * The batch context array - */ -function drush_update_do_one($module, $number, $dependency_map, &$context) { - $function = $module . '_update_' . $number; - - // If this update was aborted in a previous step, or has a dependency that - // was aborted in a previous step, go no further. - if (!empty($context['results']['#abort']) && array_intersect($context['results']['#abort'], array_merge($dependency_map, array($function)))) { - return; - } - - $context['log'] = FALSE; - - $ret = array(); - if (function_exists($function)) { - try { - if ($context['log']) { - Database::startLog($function); - } - - drush_log("Executing " . $function); - $ret['results']['query'] = $function($context['sandbox']); - - // If the update hook returned a status message (common in batch updates), - // show it to the user. - if ($ret['results']['query']) { - drush_log($ret['results']['query'], LogLevel::OK); - } - - $ret['results']['success'] = TRUE; - } - // @TODO We may want to do different error handling for different exception - // types, but for now we'll just print the message. - catch (Exception $e) { - $ret['#abort'] = array('success' => FALSE, 'query' => $e->getMessage()); - drush_set_error('DRUPAL_EXCEPTION', $e->getMessage()); - } - - if ($context['log']) { - $ret['queries'] = Database::getLog($function); - } - } - - if (isset($context['sandbox']['#finished'])) { - $context['finished'] = $context['sandbox']['#finished']; - unset($context['sandbox']['#finished']); - } - - if (!isset($context['results'][$module])) { - $context['results'][$module] = array(); - } - if (!isset($context['results'][$module][$number])) { - $context['results'][$module][$number] = array(); - } - $context['results'][$module][$number] = array_merge($context['results'][$module][$number], $ret); - - if (!empty($ret['#abort'])) { - // Record this function in the list of updates that were aborted. - $context['results']['#abort'][] = $function; - } - - // Record the schema update if it was completed successfully. - if ($context['finished'] == 1 && empty($ret['#abort'])) { - drupal_set_installed_schema_version($module, $number); - } - - $context['message'] = 'Performed update: ' . $function; -} - -/** - * Check update requirements and report any errors. - */ -function update_check_requirements() { - $warnings = FALSE; - - // Check the system module and update.php requirements only. - $requirements = system_requirements('update'); - $requirements += update_extra_requirements(); - - // If there are issues, report them. - foreach ($requirements as $requirement) { - if (isset($requirement['severity']) && $requirement['severity'] > REQUIREMENT_OK) { - $message = isset($requirement['description']) ? $requirement['description'] : ''; - if (isset($requirement['value']) && $requirement['value']) { - $message .= ' (Currently using ' . $requirement['title'] . ' ' . $requirement['value'] . ')'; - } - $warnings = TRUE; - drupal_set_message($message, LogLevel::WARNING); - } - } - return $warnings; -} - - -function update_main_prepare() { - // Some unavoidable errors happen because the database is not yet up-to-date. - // Our custom error handler is not yet installed, so we just suppress them. - drush_errors_off(); - - // We prepare a minimal bootstrap for the update requirements check to avoid - // reaching the PHP memory limit. - $core = DRUSH_DRUPAL_CORE; - require_once $core . '/includes/bootstrap.inc'; - require_once $core . '/includes/common.inc'; - require_once $core . '/includes/file.inc'; - require_once $core . '/includes/entity.inc'; - include_once $core . '/includes/unicode.inc'; - - update_prepare_d7_bootstrap(); - drupal_bootstrap(DRUPAL_BOOTSTRAP_SESSION); - - require_once $core . '/includes/install.inc'; - require_once $core . '/modules/system/system.install'; - - // Load module basics. - include_once $core . '/includes/module.inc'; - $module_list['system']['filename'] = 'modules/system/system.module'; - module_list(TRUE, FALSE, FALSE, $module_list); - drupal_load('module', 'system'); - - // Reset the module_implements() cache so that any new hook implementations - // in updated code are picked up. - module_implements('', FALSE, TRUE); - - // Set up $language, since the installer components require it. - drupal_language_initialize(); - - // Set up theme system for the maintenance page. - drupal_maintenance_theme(); - - // Check the update requirements for Drupal. - update_check_requirements(); - - // update_fix_d7_requirements() needs to run before bootstrapping beyond path. - // So bootstrap to DRUPAL_BOOTSTRAP_LANGUAGE then include unicode.inc. - drupal_bootstrap(DRUPAL_BOOTSTRAP_LANGUAGE); - - update_fix_d7_requirements(); - - // Clear the module_implements() cache before the full bootstrap. The calls - // above to drupal_maintenance_theme() and update_check_requirements() have - // invoked hooks before all modules have actually been loaded by the full - // bootstrap. This means that the module_implements() results for any hooks - // that have been invoked, including hook_module_implements_alter(), is a - // smaller set of modules than should be returned normally. - // @see https://github.com/drush-ops/drush/pull/399 - module_implements('', FALSE, TRUE); - - // Now proceed with a full bootstrap. - - drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_FULL); - drupal_maintenance_theme(); - - drush_errors_on(); - - include_once DRUPAL_ROOT . '/includes/batch.inc'; - drupal_load_updates(); - - update_fix_compatibility(); - - // Change query-strings on css/js files to enforce reload for all users. - _drupal_flush_css_js(); - // Flush the cache of all data for the update status module. - if (db_table_exists('cache_update')) { - cache_clear_all('*', 'cache_update', TRUE); - } - - module_list(TRUE, FALSE, TRUE); -} - -function update_main() { - update_main_prepare(); - - list($pending, $start) = updatedb_status(); - if ($pending) { - // @todo get table header working. - // $headers = array(dt('Module'), dt('ID'), dt('Description')); - drush_print_table($pending); - if (!drush_confirm(dt('Do you wish to run all pending updates?'))) { - return drush_user_abort(); - } - drush_update_batch($start); - } - else { - drush_log(dt("No database updates required"), LogLevel::SUCCESS); - } - - return count($pending); -} - -function _update_batch_command($id) { - update_main_prepare(); - drush_batch_command($id); -} - -/** - * Start the database update batch process. - * - * @param $start - * An array of all the modules and which update to start at. - * @param $redirect - * Path to redirect to when the batch has finished processing. - * @param $url - * URL of the batch processing page (should only be used for separate - * scripts like update.php). - * @param $batch - * Optional parameters to pass into the batch API. - * @param $redirect_callback - * (optional) Specify a function to be called to redirect to the progressive - * processing page. - */ -function drush_update_batch($start) { - // Resolve any update dependencies to determine the actual updates that will - // be run and the order they will be run in. - $updates = update_resolve_dependencies($start); - - // Store the dependencies for each update function in an array which the - // batch API can pass in to the batch operation each time it is called. (We - // do not store the entire update dependency array here because it is - // potentially very large.) - $dependency_map = array(); - foreach ($updates as $function => $update) { - $dependency_map[$function] = !empty($update['reverse_paths']) ? array_keys($update['reverse_paths']) : array(); - } - - $operations = array(); - foreach ($updates as $update) { - if ($update['allowed']) { - // Set the installed version of each module so updates will start at the - // correct place. (The updates are already sorted, so we can simply base - // this on the first one we come across in the above foreach loop.) - if (isset($start[$update['module']])) { - drupal_set_installed_schema_version($update['module'], $update['number'] - 1); - unset($start[$update['module']]); - } - // Add this update function to the batch. - $function = $update['module'] . '_update_' . $update['number']; - $operations[] = array('drush_update_do_one', array($update['module'], $update['number'], $dependency_map[$function])); - } - } - - $batch['operations'] = $operations; - $batch += array( - 'title' => 'Updating', - 'init_message' => 'Starting updates', - 'error_message' => 'An unrecoverable error has occurred. You can find the error message below. It is advised to copy it to the clipboard for reference.', - 'finished' => 'drush_update_finished', - 'file' => 'includes/update.inc', - ); - batch_set($batch); - drush_backend_batch_process('updatedb-batch-process'); -} - - - -function drush_update_finished($success, $results, $operations) { - // Nothing to do here. All caches already cleared. Kept as documentation of 'finished' callback. -} - -/** - * Return a 2 item array with - * - an array where each item is a 3 item associative array describing a pending update. - * - an array listing the first update to run, keyed by module. - */ -function updatedb_status() { - $pending = update_get_update_list(); - - $return = array(); - // Ensure system module's updates run first. - $start['system'] = array(); - - // Print a list of pending updates for this module and get confirmation. - foreach ($pending as $module => $updates) { - if (isset($updates['start'])) { - foreach ($updates['pending'] as $update_id => $description) { - // Strip cruft from front. - $description = str_replace($update_id . ' - ', '', $description); - $return[] = array('module' => ucfirst($module), 'update_id' => $update_id, 'description' => $description); - } - if (isset($updates['start'])) { - $start[$module] = $updates['start']; - } - } - } - return array($return, $start); -} diff --git a/includes/batch.inc b/includes/batch.inc index 89c6c95f40..0f736c9dde 100644 --- a/includes/batch.inc +++ b/includes/batch.inc @@ -117,12 +117,7 @@ function _drush_backend_batch_process($command = 'batch-process', $args, $option // The batch is now completely built. Allow other modules to make changes // to the batch so that it is easier to reuse batch processes in other // enviroments. - if (drush_drupal_major_version() >= 8) { - \Drupal::moduleHandler()->alter('batch', $batch); - } - else { - drupal_alter('batch', $batch); - } + \Drupal::moduleHandler()->alter('batch', $batch); // Assign an arbitrary id: don't rely on a serial column in the 'batch' // table, since non-progressive batches skip database storage completely. @@ -139,26 +134,11 @@ function _drush_backend_batch_process($command = 'batch-process', $args, $option drush_include_engine('drupal', 'environment'); // Store the batch. - if (drush_drupal_major_version() >= 8) { - /** @var \Drupal\Core\Batch\BatchStorage $batch_storage */ - $batch_storage = \Drupal::service('batch.storage'); - $batch_storage->create($batch); - } - else { - db_insert('batch') - ->fields(array( - 'bid' => $batch['id'], - 'timestamp' => REQUEST_TIME, - 'token' => drush_get_token($batch['id']), - 'batch' => serialize($batch), - )) - ->execute(); - } + /** @var \Drupal\Core\Batch\BatchStorage $batch_storage */ + $batch_storage = \Drupal::service('batch.storage'); + $batch_storage->create($batch); $finished = FALSE; - // Not used in D8+ and possibly earlier. - global $user; - while (!$finished) { $data = drush_invoke_process('@self', $command, $args, array('user' => \Drupal::currentUser()->id())); diff --git a/lib/Drush/Commands/core/BatchCommands.php b/lib/Drush/Commands/core/BatchCommands.php index 8c7b867ef9..eb1df66e5c 100644 --- a/lib/Drush/Commands/core/BatchCommands.php +++ b/lib/Drush/Commands/core/BatchCommands.php @@ -19,17 +19,4 @@ public function process($batch_id) { drush_batch_command($batch_id); } - /** - * Perform update functions. - * - * @command updatedb-batch-process - * @param $batch_id The batch id that will be processed. - * @hidden - * @bootstrap DRUSH_BOOTSTRAP_DRUPAL_FULL - */ - public function updatedb_process($batch_id) { - drush_include_engine('drupal', 'update'); - _update_batch_command($batch_id); - } - } diff --git a/lib/Drush/Commands/core/UpdateDBCommands.php b/lib/Drush/Commands/core/UpdateDBCommands.php index 915cf2ec26..c38eda645c 100644 --- a/lib/Drush/Commands/core/UpdateDBCommands.php +++ b/lib/Drush/Commands/core/UpdateDBCommands.php @@ -1,6 +1,8 @@ TRUE]) { + public function updatedb($options = ['cache-clear' => TRUE, 'entity-updates' => FALSE]) { if (drush_get_context('DRUSH_SIMULATE')) { $this->logger()->info(dt('updatedb command does not support --simulate option.')); return TRUE; } - drush_include_engine('drupal', 'update'); - $result = update_main(); + $result = $this->updateMain($options); if ($result === FALSE) { throw new \Exception('Database updates not complete.'); } @@ -32,7 +33,7 @@ public function updatedb($options = ['cache-clear' => TRUE]) { // Clear all caches in a new process. We just performed major surgery. drush_drupal_cache_clear_all(); - $this->logger()->log(LogLevel::SUCCESS, dt('Finished performing updates.')); + $this->logger()->success(dt('Finished performing updates.')); } } @@ -50,14 +51,13 @@ public function entity_updates($options = ['cache-clear' => TRUE]) { $this->logger()->info(dt('entity-updates command does not support --simulate option.')); } - drush_include_engine('drupal', 'update'); - if (entity_updates_main() === FALSE) { + if ($this->entityUpdatesMain() === FALSE) { throw new \Exception('Entity updates not run.'); } drush_drupal_cache_clear_all(); - $this->logger()->log(LogLevel::SUCCESS, dt('Finished performing updates.')); + $this->logger()->success(dt('Finished performing updates.')); } /** @@ -75,15 +75,388 @@ public function entity_updates($options = ['cache-clear' => TRUE]) { * @default-fields module,update_id,description * @return \Consolidation\OutputFormatters\StructuredData\RowsOfFields */ - public function updatedb_status($options = ['format'=> 'table']) { + public function updatedbStatus($options = ['format'=> 'table']) { require_once DRUSH_DRUPAL_CORE . '/includes/install.inc'; drupal_load_updates(); - drush_include_engine('drupal', 'update'); - list($pending, $start) = updatedb_status(); + list($pending, $start) = $this->getUpdatedbStatus(); if (empty($pending)) { - $this->logger()->info(dt("No database updates required")); + $this->logger()->success(dt("No database updates required")); + } + else { + return new RowsOfFields($pending); + } + } + + /** + * Perform update functions. + * + * @command updatedb-batch-process + * @param $batch_id The batch id that will be processed. + * @hidden + * @bootstrap DRUSH_BOOTSTRAP_DRUPAL_FULL + */ + public function updatedbProcess($batch_id) { + drush_batch_command($batch_id); + } + + /** + * Perform one update and store the results which will later be displayed on + * the finished page. + * + * An update function can force the current and all later updates for this + * module to abort by returning a $ret array with an element like: + * $ret['#abort'] = array('success' => FALSE, 'query' => 'What went wrong'); + * The schema version will not be updated in this case, and all the + * aborted updates will continue to appear on update.php as updates that + * have not yet been run. + * + * @param $module + * The module whose update will be run. + * @param $number + * The update number to run. + * @param $context + * The batch context array + */ + function updateDoOne($module, $number, $dependency_map, &$context) { + $function = $module . '_update_' . $number; + + // If this update was aborted in a previous step, or has a dependency that + // was aborted in a previous step, go no further. + if (!empty($context['results']['#abort']) && array_intersect($context['results']['#abort'], array_merge($dependency_map, array($function)))) { + return; + } + + $context['log'] = FALSE; + + \Drupal::moduleHandler()->loadInclude($module, 'install'); + + $ret = array(); + if (function_exists($function)) { + try { + if ($context['log']) { + Database::startLog($function); + } + + $this->logger()->notice("Executing " . $function); + $ret['results']['query'] = $function($context['sandbox']); + $ret['results']['success'] = TRUE; + } + // @TODO We may want to do different error handling for different exception + // types, but for now we'll just print the message. + catch (Exception $e) { + $ret['#abort'] = array('success' => FALSE, 'query' => $e->getMessage()); + $this->logger()->warning($e->getMessage()); + } + + if ($context['log']) { + $ret['queries'] = Database::getLog($function); + } + } + else { + $ret['#abort'] = array('success' => FALSE); + $this->logger()->warning(dt('Update function @function not found', array('@function' => $function))); + } + + if (isset($context['sandbox']['#finished'])) { + $context['finished'] = $context['sandbox']['#finished']; + unset($context['sandbox']['#finished']); + } + + if (!isset($context['results'][$module])) { + $context['results'][$module] = array(); + } + if (!isset($context['results'][$module][$number])) { + $context['results'][$module][$number] = array(); + } + $context['results'][$module][$number] = array_merge($context['results'][$module][$number], $ret); + + if (!empty($ret['#abort'])) { + // Record this function in the list of updates that were aborted. + $context['results']['#abort'][] = $function; + } + + // Record the schema update if it was completed successfully. + if ($context['finished'] == 1 && empty($ret['#abort'])) { + drupal_set_installed_schema_version($module, $number); + } + + $context['message'] = 'Performing ' . $function; + } + + function updateMain($options) { + // In D8, we expect to be in full bootstrap. + drush_bootstrap_to_phase(DRUSH_BOOTSTRAP_DRUPAL_FULL); + + require_once DRUPAL_ROOT . '/core/includes/install.inc'; + require_once DRUPAL_ROOT . '/core/includes/update.inc'; + drupal_load_updates(); + update_fix_compatibility(); + + // Pending hook_update_N() implementations. + $pending = update_get_update_list(); + + // Pending hook_post_update_X() implementations. + $post_updates = \Drupal::service('update.post_update_registry')->getPendingUpdateInformation(); + + $start = array(); + + $change_summary = []; + if ($options['entity-updates']) { + $change_summary = \Drupal::entityDefinitionUpdateManager()->getChangeSummary(); + } + + // Print a list of pending updates for this module and get confirmation. + if (count($pending) || count($change_summary) || count($post_updates)) { + drush_print(dt('The following updates are pending:')); + drush_print(); + + foreach ($change_summary as $entity_type_id => $changes) { + drush_print($entity_type_id . ' entity type : '); + foreach ($changes as $change) { + drush_print(strip_tags($change), 2); + } + } + + foreach (array('update', 'post_update') as $update_type) { + $updates = $update_type == 'update' ? $pending : $post_updates; + foreach ($updates as $module => $updates) { + if (isset($updates['start'])) { + drush_print($module . ' module : '); + if (!empty($updates['pending'])) { + $start += [$module => array()]; + + $start[$module] = array_merge($start[$module], $updates['pending']); + foreach ($updates['pending'] as $update) { + drush_print(strip_tags($update), 2); + } + } + drush_print(); + } + } + } + + if (!drush_confirm(dt('Do you wish to run all pending updates?'))) { + return drush_user_abort(); + } + + $this->updateBatch($options); + } + else { + $this->logger()->success(dt("No database updates required")); + } + + return count($pending) + count($change_summary) + count($post_updates); + } + + /** + * Start the database update batch process. + */ + function updateBatch($options) { + $start = $this->getUpdateList(); + // Resolve any update dependencies to determine the actual updates that will + // be run and the order they will be run in. + $updates = update_resolve_dependencies($start); + + // Store the dependencies for each update function in an array which the + // batch API can pass in to the batch operation each time it is called. (We + // do not store the entire update dependency array here because it is + // potentially very large.) + $dependency_map = array(); + foreach ($updates as $function => $update) { + $dependency_map[$function] = !empty($update['reverse_paths']) ? array_keys($update['reverse_paths']) : array(); + } + + $operations = array(); + + foreach ($updates as $update) { + if ($update['allowed']) { + // Set the installed version of each module so updates will start at the + // correct place. (The updates are already sorted, so we can simply base + // this on the first one we come across in the above foreach loop.) + if (isset($start[$update['module']])) { + drupal_set_installed_schema_version($update['module'], $update['number'] - 1); + unset($start[$update['module']]); + } + // Add this update function to the batch. + $function = $update['module'] . '_update_' . $update['number']; + $operations[] = array([$this, 'updateDoOne'], array($update['module'], $update['number'], $dependency_map[$function])); + } + } + + // Apply post update hooks. + $post_updates = \Drupal::service('update.post_update_registry')->getPendingUpdateFunctions(); + if ($post_updates) { + $operations[] = ['drush_drupal_cache_clear_all', []]; + foreach ($post_updates as $function) { + $operations[] = ['update_invoke_post_update', [$function]]; + } + } + + // Lastly, perform entity definition updates, which will update storage + // schema if needed. If module update functions need to work with specific + // entity schema they should call the entity update service for the specific + // update themselves. + // @see \Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface::applyEntityUpdate() + // @see \Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface::applyFieldUpdate() + if ($options['entity-updates'] && \Drupal::entityDefinitionUpdateManager()->needsUpdates()) { + $operations[] = array([$this, 'updateEntityDefinitions'], array()); + } + + $batch['operations'] = $operations; + $batch += array( + 'title' => 'Updating', + 'init_message' => 'Starting updates', + 'error_message' => 'An unrecoverable error has occurred. You can find the error message below. It is advised to copy it to the clipboard for reference.', + 'finished' => [$this, 'drush_update_finished'], + ); + batch_set($batch); + \Drupal::service('state')->set('system.maintenance_mode', TRUE); + drush_backend_batch_process('updatedb-batch-process'); + \Drupal::service('state')->set('system.maintenance_mode', FALSE); + } + + /** + * Apply entity schema updates. + */ + function updateEntityDefinitions(&$context) { + try { + \Drupal::entityDefinitionUpdateManager()->applyUpdates(); + } + catch (EntityStorageException $e) { + watchdog_exception('update', $e); + $variables = Error::decodeException($e); + unset($variables['backtrace']); + // The exception message is run through + // \Drupal\Component\Utility\SafeMarkup::checkPlain() by + // \Drupal\Core\Utility\Error::decodeException(). + $ret['#abort'] = array('success' => FALSE, 'query' => t('%type: !message in %function (line %line of %file).', $variables)); + $context['results']['core']['update_entity_definitions'] = $ret; + $context['results']['#abort'][] = 'update_entity_definitions'; + } + } + +// Copy of protected \Drupal\system\Controller\DbUpdateController::getModuleUpdates. + function getUpdateList() { + $return = array(); + $updates = update_get_update_list(); + foreach ($updates as $module => $update) { + $return[$module] = $update['start']; + } + + return $return; + } + + /** + * Process and display any returned update output. + * + * @see \Drupal\system\Controller\DbUpdateController::batchFinished() + * @see \Drupal\system\Controller\DbUpdateController::results() + */ + function updateFinished($success, $results, $operations) { + + if (!drush_get_option('cache-clear', TRUE)) { + drush_log(dt("Skipping cache-clear operation due to --cache-clear=0 option."), LogLevel::WARNING); + } + else { + drupal_flush_all_caches(); + } + + foreach ($results as $module => $updates) { + if ($module != '#abort') { + foreach ($updates as $number => $queries) { + foreach ($queries as $query) { + // If there is no message for this update, don't show anything. + if (empty($query['query'])) { + continue; + } + + if ($query['success']) { + drush_log(strip_tags($query['query'])); + } + else { + drush_set_error(dt('Failed: ') . strip_tags($query['query'])); + } + } + } + } + } + } + + /** + * Return a 2 item array with + * - an array where each item is a 3 item associative array describing a pending update. + * - an array listing the first update to run, keyed by module. + */ + function getUpdatedbStatus() { + require_once DRUPAL_ROOT . '/core/includes/update.inc'; + $pending = \update_get_update_list(); + + $return = array(); + // Ensure system module's updates run first. + $start['system'] = array(); + + foreach (\Drupal::entityDefinitionUpdateManager()->getChangeSummary() as $entity_type_id => $changes) { + foreach ($changes as $change) { + $return[] = array( + 'module' => dt('@type entity type', array('@type' => $entity_type_id)), 'update_id' => '', 'description' => strip_tags($change)); + } + } + + // Print a list of pending updates for this module and get confirmation. + foreach ($pending as $module => $updates) { + if (isset($updates['start'])) { + foreach ($updates['pending'] as $update_id => $description) { + // Strip cruft from front. + $description = str_replace($update_id . ' - ', '', $description); + $return[] = array('module' => ucfirst($module), 'update_id' => $update_id, 'description' => $description); + } + if (isset($updates['start'])) { + $start[$module] = $updates['start']; + } + } + } + + return array($return, $start); + } + + /** + * Apply pending entity schema updates. + */ + function entityUpdatesMain() { + $change_summary = \Drupal::entityDefinitionUpdateManager()->getChangeSummary(); + if (!empty($change_summary)) { + drush_print(dt('The following updates are pending:')); + drush_print(); + + foreach ($change_summary as $entity_type_id => $changes) { + drush_print($entity_type_id . ' entity type : '); + foreach ($changes as $change) { + drush_print(strip_tags($change), 2); + } + } + + if (!drush_confirm(dt('Do you wish to run all pending updates?'))) { + return drush_user_abort(); + } + + $operations[] = array([$this, 'updateEntityDefinitions'], array()); + + + $batch['operations'] = $operations; + $batch += array( + 'title' => 'Updating', + 'init_message' => 'Starti ng updates', + 'error_message' => 'An unrecoverable error has occurred. You can find the error message below. It is advised to copy it to the clipboard for reference.', + 'finished' => [$this, 'updateFinished'], + ); + batch_set($batch); + \Drupal::service('state')->set('system.maintenance_mode', TRUE); + drush_backend_batch_process('updatedb-batch-process'); + \Drupal::service('state')->set('system.maintenance_mode', FALSE); + } + else { + $this->logger()->success(dt("No entity schema updates required")); } - return new RowsOfFields($pending); } } \ No newline at end of file