function update_script_selection_form($form, &$form_state) { $count = 0; $incompatible_count = 0; $form['start'] = array('#tree' => TRUE, '#type' => 'fieldset', '#collapsed' => TRUE, '#collapsible' => TRUE); // Ensure system.module's updates appear first. $form['start']['system'] = array(); $updates = update_get_update_list(); $starting_updates = array(); $incompatible_updates_exist = FALSE; foreach ($updates as $module => $update) { if (!isset($update['start'])) { $form['start'][$module] = array('#title' => $module, '#item' => $update['warning'], '#prefix' => '<div class="warning">', '#suffix' => '</div>'); $incompatible_updates_exist = TRUE; continue; } if (!empty($update['pending'])) { $starting_updates[$module] = $update['start']; $form['start'][$module] = array('#type' => 'hidden', '#value' => $update['start']); $form['start'][$module . '_updates'] = array('#theme' => 'item_list', '#items' => $update['pending'], '#title' => $module . ' module'); } if (isset($update['pending'])) { $count = $count + count($update['pending']); } } // Find and label any incompatible updates. foreach (update_resolve_dependencies($starting_updates) as $function => $data) { if (!$data['allowed']) { $incompatible_updates_exist = TRUE; $incompatible_count++; $module_update_key = $data['module'] . '_updates'; if (isset($form['start'][$module_update_key]['#items'][$data['number']])) { $text = $data['missing_dependencies'] ? 'This update will been skipped due to the following missing dependencies: <em>' . implode(', ', $data['missing_dependencies']) . '</em>' : "This update will be skipped due to an error in the module's code."; $form['start'][$module_update_key]['#items'][$data['number']] .= '<div class="warning">' . $text . '</div>'; } // Move the module containing this update to the top of the list. $form['start'] = array($module_update_key => $form['start'][$module_update_key]) + $form['start']; } } // Warn the user if any updates were incompatible. if ($incompatible_updates_exist) { drupal_set_message('Some of the pending updates cannot be applied because their dependencies were not met.', 'warning'); } if (empty($count)) { drupal_set_message(t('No pending updates.')); unset($form); $form['links'] = array('#markup' => theme('item_list', array('items' => update_helpful_links()))); } else { $form['help'] = array('#markup' => '<p>The version of Drupal you are updating from has been automatically detected.</p>', '#weight' => -5); if ($incompatible_count) { $form['start']['#title'] = format_plural($count, '1 pending update (@number_applied to be applied, @number_incompatible skipped)', '@count pending updates (@number_applied to be applied, @number_incompatible skipped)', array('@number_applied' => $count - $incompatible_count, '@number_incompatible' => $incompatible_count)); } else { $form['start']['#title'] = format_plural($count, '1 pending update', '@count pending updates'); } $form['has_js'] = array('#type' => 'hidden', '#default_value' => FALSE); $form['actions'] = array('#type' => 'actions'); $form['actions']['submit'] = array('#type' => 'submit', '#value' => 'Apply pending updates'); } return $form; }
/** * @return array * * @see update_script_selection_form() */ private function listUpdates() { // [ // 'oxygen' => [ // 'pending' => [ // 7001 => 'Update description.', // ], // 'start' => 7001, // ], $list = array(); require_once DRUPAL_ROOT . '/includes/install.inc'; drupal_load_updates(); foreach (update_get_update_list() as $extension => $info) { if (!isset($info['start'])) { // @todo: The update is incompatible, show a warning? continue; } $list[$extension] = $info['start']; } $updates = array(); foreach (update_resolve_dependencies($list) as $update) { if (!$update['allowed']) { if ($update['missing_dependencies']) { // Some module dependency is missing, so it's not safe to update. continue; } else { // There was a PHP syntax error in the module. continue; } } $updates = array($update['module'] => $update['number']) + $updates; } return $updates; }
function testMissingUpdate() { $starting_updates = array('update_test_2' => 8001); $update_graph = update_resolve_dependencies($starting_updates); $this->assertTrue($update_graph['update_test_2_update_8001']['allowed'], "The module's first update function is allowed to run, since it does not have any missing dependencies."); $this->assertFalse($update_graph['update_test_2_update_8002']['allowed'], "The module's second update function is not allowed to run, since it has a direct dependency on a missing update."); $this->assertFalse($update_graph['update_test_2_update_8003']['allowed'], "The module's third update function is not allowed to run, since it has an indirect dependency on a missing update."); }
/** * Test that dependencies between modules are resolved correctly. */ function testUpdateOrderingModuleInterdependency() { $starting_updates = array('update_test_2' => 8001, 'update_test_3' => 8001); $update_order = array_keys(update_resolve_dependencies($starting_updates)); // Make sure that each dependency is satisfied. $first_dependency_satisfied = array_search('update_test_2_update_8001', $update_order) < array_search('update_test_3_update_8001', $update_order); $this->assertTrue($first_dependency_satisfied, 'The dependency of the second module on the first module is respected by the update function order.'); $second_dependency_satisfied = array_search('update_test_3_update_8001', $update_order) < array_search('update_test_2_update_8002', $update_order); $this->assertTrue($second_dependency_satisfied, 'The dependency of the first module on the second module is respected by the update function order.'); }
/** * Starts the database update batch process. * * @param \Symfony\Component\HttpFoundation\Request $request * The current request object. */ protected function triggerBatch(Request $request) { $maintenance_mode = $this->state->get('system.maintenance_mode', FALSE); // Store the current maintenance mode status in the session so that it can // be restored at the end of the batch. $_SESSION['maintenance_mode'] = $maintenance_mode; // During the update, always put the site into maintenance mode so that // in-progress schema changes do not affect visiting users. if (empty($maintenance_mode)) { $this->state->set('system.maintenance_mode', TRUE); } $operations = array(); // Resolve any update dependencies to determine the actual updates that will // be run and the order they will be run in. $start = $this->getModuleUpdates(); $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(); } // Determine updates to be performed. foreach ($updates as $function => $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']]); } $operations[] = array('update_do_one', array($update['module'], $update['number'], $dependency_map[$function])); } } $post_updates = $this->postUpdateRegistry->getPendingUpdateFunctions(); if ($post_updates) { // Now we rebuild all caches and after that execute the hook_post_update() // functions. $operations[] = ['drupal_flush_all_caches', []]; foreach ($post_updates as $function) { $operations[] = ['update_invoke_post_update', [$function]]; } } $batch['operations'] = $operations; $batch += array('title' => $this->t('Updating'), 'init_message' => $this->t('Starting updates'), 'error_message' => $this->t('An unrecoverable error has occurred. You can find the error message below. It is advised to copy it to the clipboard for reference.'), 'finished' => array('\\Drupal\\system\\Controller\\DbUpdateController', 'batchFinished')); batch_set($batch); // @todo Revisit once https://www.drupal.org/node/2548095 is in. return batch_process(Url::fromUri('base://results'), Url::fromUri('base://start')); }
/** * Starts the database update batch process. * * @param \Symfony\Component\HttpFoundation\Request $request * The current request object. */ protected function triggerBatch(Request $request) { // During the update, bring the site offline so that schema changes do not // affect visiting users. $maintenance_mode = $this->config('system.maintenance')->get('enabled'); if (isset($maintenance_mode)) { $_SESSION['maintenance_mode'] = $maintenance_mode; } if (empty($_SESSION['maintenance_mode'])) { $this->state->set('system.maintenance_mode', TRUE); } $operations = array(); // First of all perform entity definition updates, which will update // storage schema if needed, so that module update functions work with // the correct entity schema. if ($this->entityDefinitionUpdateManager->needsUpdates()) { $operations[] = array('update_entity_definitions', array('system', '0 - Update entity definitions')); } // Resolve any update dependencies to determine the actual updates that will // be run and the order they will be run in. $start = $this->getModuleUpdates(); $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(); } // Determine updates to be performed. 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('update_do_one', array($update['module'], $update['number'], $dependency_map[$function])); } } $batch['operations'] = $operations; $batch += array('title' => $this->t('Updating'), 'init_message' => $this->t('Starting updates'), 'error_message' => $this->t('An unrecoverable error has occurred. You can find the error message below. It is advised to copy it to the clipboard for reference.'), 'finished' => array('\\Drupal\\system\\Controller\\DbUpdateController', 'batchFinished')); batch_set($batch); return batch_process('update.php/results', Url::fromRoute('system.db_update', array('op' => 'start'))); }
/** * Starts the database update batch process. * * @param \Symfony\Component\HttpFoundation\Request $request * The current request object. */ protected function triggerBatch(Request $request) { $maintenance_mode = $this->state->get('system.maintenance_mode', FALSE); // Store the current maintenance mode status in the session so that it can // be restored at the end of the batch. $_SESSION['maintenance_mode'] = $maintenance_mode; // During the update, always put the site into maintenance mode so that // in-progress schema changes do not affect visiting users. if (empty($maintenance_mode)) { $this->state->set('system.maintenance_mode', TRUE); } $operations = array(); // Resolve any update dependencies to determine the actual updates that will // be run and the order they will be run in. $start = $this->getModuleUpdates(); $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(); } // Determine updates to be performed. foreach ($updates as $function => $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']]); } $operations[] = array('update_do_one', array($update['module'], $update['number'], $dependency_map[$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 ($this->entityDefinitionUpdateManager->needsUpdates()) { $operations[] = array('update_entity_definitions', array()); } $batch['operations'] = $operations; $batch += array('title' => $this->t('Updating'), 'init_message' => $this->t('Starting updates'), 'error_message' => $this->t('An unrecoverable error has occurred. You can find the error message below. It is advised to copy it to the clipboard for reference.'), 'finished' => array('\\Drupal\\system\\Controller\\DbUpdateController', 'batchFinished')); batch_set($batch); return batch_process(Url::fromUri($request->getUriForPath('/results')), Url::fromUri($request->getUriForPath('/start'))); }