4 * Update.php for provisioned sites.
5 * This file is a derivative of the standard drupal update.php,
6 * which has been modified to allow being run from the command
10 use Drush\Log\LogLevel;
13 * Drupal's update.inc has functions that are in previous update_X.inc files
14 * for example, update_check_incompatibility() which can prove useful when
17 require_once DRUSH_DRUPAL_CORE . '/includes/update.inc';
19 use Drupal\Core\Utility\Error;
20 use Drupal\Core\Entity\EntityStorageException;
22 * Perform one update and store the results which will later be displayed on
25 * An update function can force the current and all later updates for this
26 * module to abort by returning a $ret array with an element like:
27 * $ret['#abort'] = array('success' => FALSE, 'query' => 'What went wrong');
28 * The schema version will not be updated in this case, and all the
29 * aborted updates will continue to appear on update.php as updates that
30 * have not yet been run.
33 * The module whose update will be run.
35 * The update number to run.
37 * The batch context array
39 function drush_update_do_one($module, $number, $dependency_map, &$context) {
40 $function = $module . '_update_' . $number;
42 // If this update was aborted in a previous step, or has a dependency that
43 // was aborted in a previous step, go no further.
44 if (!empty($context['results']['#abort']) && array_intersect($context['results']['#abort'], array_merge($dependency_map, array($function)))) {
48 $context['log'] = FALSE;
50 \Drupal::moduleHandler()->loadInclude($module, 'install');
53 if (function_exists($function)) {
55 if ($context['log']) {
56 Database::startLog($function);
59 drush_log("Executing " . $function);
60 $ret['results']['query'] = $function($context['sandbox']);
61 $ret['results']['success'] = TRUE;
63 // @TODO We may want to do different error handling for different exception
64 // types, but for now we'll just print the message.
65 catch (Exception $e) {
66 $ret['#abort'] = array('success' => FALSE, 'query' => $e->getMessage());
67 drush_set_error('DRUPAL_EXCEPTION', $e->getMessage());
70 if ($context['log']) {
71 $ret['queries'] = Database::getLog($function);
75 $ret['#abort'] = array('success' => FALSE);
76 drush_set_error('DRUSH_UPDATE_FUNCTION_NOT_FOUND', dt('Update function @function not found', array('@function' => $function)));
79 if (isset($context['sandbox']['#finished'])) {
80 $context['finished'] = $context['sandbox']['#finished'];
81 unset($context['sandbox']['#finished']);
84 if (!isset($context['results'][$module])) {
85 $context['results'][$module] = array();
87 if (!isset($context['results'][$module][$number])) {
88 $context['results'][$module][$number] = array();
90 $context['results'][$module][$number] = array_merge($context['results'][$module][$number], $ret);
92 if (!empty($ret['#abort'])) {
93 // Record this function in the list of updates that were aborted.
94 $context['results']['#abort'][] = $function;
97 // Record the schema update if it was completed successfully.
98 if ($context['finished'] == 1 && empty($ret['#abort'])) {
99 drupal_set_installed_schema_version($module, $number);
102 $context['message'] = 'Performing ' . $function;
106 * Clears caches and rebuilds the container.
108 * This is called in between regular updates and post updates. Do not use
109 * drush_drupal_cache_clear_all() as the cache clearing and container rebuild
110 * must happen in the same process that the updates are run in.
112 * Drupal core's update.php uses drupal_flush_all_caches() directly without
113 * explicitly rebuilding the container as the container is rebuilt on the next
114 * HTTP request of the batch.
116 * @see drush_drupal_cache_clear_all()
117 * @see \Drupal\system\Controller\DbUpdateController::triggerBatch()
119 function drush_update_cache_rebuild() {
120 drupal_flush_all_caches();
121 \Drupal::service('kernel')->rebuildContainer();
124 function update_main() {
125 // In D8, we expect to be in full bootstrap.
126 drush_bootstrap_to_phase(DRUSH_BOOTSTRAP_DRUPAL_FULL);
128 require_once DRUPAL_ROOT . '/core/includes/install.inc';
129 require_once DRUPAL_ROOT . '/core/includes/update.inc';
130 drupal_load_updates();
131 update_fix_compatibility();
133 // Check requirements before updating.
134 if (!drush_update_check_requirements()) {
135 if (!drush_confirm(dt('Requirements check reports errors. Do you wish to continue?'))) {
136 return drush_user_abort();
140 // Pending hook_update_N() implementations.
141 $pending = update_get_update_list();
143 // Pending hook_post_update_X() implementations.
144 $post_updates = \Drupal::service('update.post_update_registry')->getPendingUpdateInformation();
148 $change_summary = [];
149 if (drush_get_option('entity-updates', FALSE)) {
150 $change_summary = \Drupal::entityDefinitionUpdateManager()->getChangeSummary();
153 // Print a list of pending updates for this module and get confirmation.
154 if (count($pending) || count($change_summary) || count($post_updates)) {
155 drush_print(dt('The following updates are pending:'));
158 foreach ($change_summary as $entity_type_id => $changes) {
159 drush_print($entity_type_id . ' entity type : ');
160 foreach ($changes as $change) {
161 drush_print(strip_tags($change), 2);
165 foreach (array('update', 'post_update') as $update_type) {
166 $updates = $update_type == 'update' ? $pending : $post_updates;
167 foreach ($updates as $module => $updates) {
168 if (isset($updates['start'])) {
169 drush_print($module . ' module : ');
170 if (!empty($updates['pending'])) {
171 $start += [$module => array()];
173 $start[$module] = array_merge($start[$module], $updates['pending']);
174 foreach ($updates['pending'] as $update) {
175 drush_print(strip_tags($update), 2);
183 if (!drush_confirm(dt('Do you wish to run all pending updates?'))) {
184 return drush_user_abort();
187 drush_update_batch($start);
190 drush_log(dt("No database updates required"), LogLevel::SUCCESS);
193 return count($pending) + count($change_summary) + count($post_updates);
197 * Check update requirements and report any errors.
199 function drush_update_check_requirements() {
202 \Drupal::moduleHandler()->resetImplementations();
203 $requirements = update_check_requirements();
204 $severity = drupal_requirements_severity($requirements);
206 // If there are issues, report them.
207 if ($severity != REQUIREMENT_OK) {
208 if ($severity === REQUIREMENT_ERROR) {
211 foreach ($requirements as $requirement) {
212 if (isset($requirement['severity']) && $requirement['severity'] != REQUIREMENT_OK) {
213 $message = isset($requirement['description']) ? $requirement['description'] : '';
214 if (isset($requirement['value']) && $requirement['value']) {
215 $message .= ' (Currently using '. $requirement['title'] .' '. $requirement['value'] .')';
217 $log_level = $requirement['severity'] === REQUIREMENT_ERROR ? LogLevel::ERROR : LogLevel::WARNING;
218 drush_log($message, $log_level);
226 function _update_batch_command($id) {
227 // In D8, we expect to be in full bootstrap.
228 drush_bootstrap_to_phase(DRUSH_BOOTSTRAP_DRUPAL_FULL);
230 drush_batch_command($id);
234 * Start the database update batch process.
236 function drush_update_batch() {
237 $start = drush_get_update_list();
238 // Resolve any update dependencies to determine the actual updates that will
239 // be run and the order they will be run in.
240 $updates = update_resolve_dependencies($start);
242 // Store the dependencies for each update function in an array which the
243 // batch API can pass in to the batch operation each time it is called. (We
244 // do not store the entire update dependency array here because it is
245 // potentially very large.)
246 $dependency_map = array();
247 foreach ($updates as $function => $update) {
248 $dependency_map[$function] = !empty($update['reverse_paths']) ? array_keys($update['reverse_paths']) : array();
251 $operations = array();
253 foreach ($updates as $update) {
254 if ($update['allowed']) {
255 // Set the installed version of each module so updates will start at the
256 // correct place. (The updates are already sorted, so we can simply base
257 // this on the first one we come across in the above foreach loop.)
258 if (isset($start[$update['module']])) {
259 drupal_set_installed_schema_version($update['module'], $update['number'] - 1);
260 unset($start[$update['module']]);
262 // Add this update function to the batch.
263 $function = $update['module'] . '_update_' . $update['number'];
264 $operations[] = array('drush_update_do_one', array($update['module'], $update['number'], $dependency_map[$function]));
268 // Apply post update hooks.
269 $post_updates = \Drupal::service('update.post_update_registry')->getPendingUpdateFunctions();
271 $operations[] = ['drush_update_cache_rebuild', []];
272 foreach ($post_updates as $function) {
273 $operations[] = ['update_invoke_post_update', [$function]];
277 // Lastly, perform entity definition updates, which will update storage
278 // schema if needed. If module update functions need to work with specific
279 // entity schema they should call the entity update service for the specific
280 // update themselves.
281 // @see \Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface::applyEntityUpdate()
282 // @see \Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface::applyFieldUpdate()
283 if (drush_get_option('entity-updates', FALSE) && \Drupal::entityDefinitionUpdateManager()->needsUpdates()) {
284 $operations[] = array('drush_update_entity_definitions', array());
287 $batch['operations'] = $operations;
289 'title' => 'Updating',
290 'init_message' => 'Starting updates',
291 '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.',
292 'finished' => 'drush_update_finished',
293 'file' => 'includes/update.inc',
296 \Drupal::service('state')->set('system.maintenance_mode', TRUE);
297 drush_backend_batch_process('updatedb-batch-process');
298 \Drupal::service('state')->set('system.maintenance_mode', FALSE);
302 * Apply entity schema updates.
304 function drush_update_entity_definitions(&$context) {
306 \Drupal::entityDefinitionUpdateManager()->applyUpdates();
308 catch (EntityStorageException $e) {
309 watchdog_exception('update', $e);
310 $variables = Error::decodeException($e);
311 unset($variables['backtrace']);
312 // The exception message is run through
313 // \Drupal\Component\Utility\SafeMarkup::checkPlain() by
314 // \Drupal\Core\Utility\Error::decodeException().
315 $ret['#abort'] = array('success' => FALSE, 'query' => t('%type: !message in %function (line %line of %file).', $variables));
316 $context['results']['core']['update_entity_definitions'] = $ret;
317 $context['results']['#abort'][] = 'update_entity_definitions';
321 // Copy of protected \Drupal\system\Controller\DbUpdateController::getModuleUpdates.
322 function drush_get_update_list() {
324 $updates = update_get_update_list();
325 foreach ($updates as $module => $update) {
326 $return[$module] = $update['start'];
333 * Process and display any returned update output.
335 * @see \Drupal\system\Controller\DbUpdateController::batchFinished()
336 * @see \Drupal\system\Controller\DbUpdateController::results()
338 function drush_update_finished($success, $results, $operations) {
340 if (!drush_get_option('cache-clear', TRUE)) {
341 drush_log(dt("Skipping cache-clear operation due to --cache-clear=0 option."), LogLevel::WARNING);
344 drupal_flush_all_caches();
347 foreach ($results as $module => $updates) {
348 if ($module != '#abort') {
349 foreach ($updates as $number => $queries) {
350 foreach ($queries as $query) {
351 // If there is no message for this update, don't show anything.
352 if (empty($query['query'])) {
356 if ($query['success']) {
357 drush_log(strip_tags($query['query']));
360 drush_set_error(dt('Failed: ') . strip_tags($query['query']));
369 * Return a 2 item array with
370 * - an array where each item is a 3 item associative array describing a pending update.
371 * - an array listing the first update to run, keyed by module.
373 function updatedb_status() {
374 $pending = update_get_update_list();
377 // Ensure system module's updates run first.
378 $start['system'] = array();
380 foreach (\Drupal::entityDefinitionUpdateManager()->getChangeSummary() as $entity_type_id => $changes) {
381 foreach ($changes as $change) {
383 'module' => dt('@type entity type', array('@type' => $entity_type_id)), 'update_id' => '', 'description' => strip_tags($change));
387 // Print a list of pending updates for this module and get confirmation.
388 foreach ($pending as $module => $updates) {
389 if (isset($updates['start'])) {
390 foreach ($updates['pending'] as $update_id => $description) {
391 // Strip cruft from front.
392 $description = str_replace($update_id . ' - ', '', $description);
393 $return[] = array('module' => ucfirst($module), 'update_id' => $update_id, 'description' => $description);
395 if (isset($updates['start'])) {
396 $start[$module] = $updates['start'];
401 return array($return, $start);
405 * Apply pending entity schema updates.
407 function entity_updates_main() {
408 $change_summary = \Drupal::entityDefinitionUpdateManager()->getChangeSummary();
409 if (!empty($change_summary)) {
410 drush_print(dt('The following updates are pending:'));
413 foreach ($change_summary as $entity_type_id => $changes) {
414 drush_print($entity_type_id . ' entity type : ');
415 foreach ($changes as $change) {
416 drush_print(strip_tags($change), 2);
420 if (!drush_confirm(dt('Do you wish to run all pending updates?'))) {
421 return drush_user_abort();
424 $operations[] = array('drush_update_entity_definitions', array());
427 $batch['operations'] = $operations;
429 'title' => 'Updating',
430 'init_message' => 'Starting updates',
431 '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.',
432 'finished' => 'drush_update_finished',
433 'file' => 'includes/update.inc',
436 \Drupal::service('state')->set('system.maintenance_mode', TRUE);
437 drush_backend_batch_process('updatedb-batch-process');
438 \Drupal::service('state')->set('system.maintenance_mode', FALSE);
441 drush_log(dt("No entity schema updates required"), LogLevel::SUCCESS);