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 * Global flag to identify update.php run, and so avoid various unwanted
14 * operations, such as hook_init() and hook_exit() invokes, css/js preprocessing
15 * and translation, and solve some theming issues. This flag is checked on several
16 * places in Drupal code (not just update.php).
18 define('MAINTENANCE_MODE', 'update');
21 * Drupal's update.inc has functions that are in previous update_X.inc files
22 * for example, update_check_incompatibility() which can prove useful when
25 require_once DRUSH_DRUPAL_CORE . '/includes/update.inc';
27 * Returns (and optionally stores) extra requirements that only apply during
28 * particular parts of the update.php process.
30 function update_extra_requirements($requirements = NULL) {
31 static $extra_requirements = array();
32 if (isset($requirements)) {
33 $extra_requirements += $requirements;
35 return $extra_requirements;
39 * Perform one update and store the results which will later be displayed on
42 * An update function can force the current and all later updates for this
43 * module to abort by returning a $ret array with an element like:
44 * $ret['#abort'] = array('success' => FALSE, 'query' => 'What went wrong');
45 * The schema version will not be updated in this case, and all the
46 * aborted updates will continue to appear on update.php as updates that
47 * have not yet been run.
50 * The module whose update will be run.
52 * The update number to run.
54 * The batch context array
56 function drush_update_do_one($module, $number, $dependency_map, &$context) {
57 $function = $module . '_update_' . $number;
59 // If this update was aborted in a previous step, or has a dependency that
60 // was aborted in a previous step, go no further.
61 if (!empty($context['results']['#abort']) && array_intersect($context['results']['#abort'], array_merge($dependency_map, array($function)))) {
65 $context['log'] = FALSE;
68 if (function_exists($function)) {
70 if ($context['log']) {
71 Database::startLog($function);
74 drush_log("Executing " . $function);
75 $ret['results']['query'] = $function($context['sandbox']);
77 // If the update hook returned a status message (common in batch updates),
78 // show it to the user.
79 if ($ret['results']['query']) {
80 drush_log($ret['results']['query'], LogLevel::OK);
83 $ret['results']['success'] = TRUE;
85 // @TODO We may want to do different error handling for different exception
86 // types, but for now we'll just print the message.
87 catch (Exception $e) {
88 $ret['#abort'] = array('success' => FALSE, 'query' => $e->getMessage());
89 drush_set_error('DRUPAL_EXCEPTION', $e->getMessage());
92 if ($context['log']) {
93 $ret['queries'] = Database::getLog($function);
97 if (isset($context['sandbox']['#finished'])) {
98 $context['finished'] = $context['sandbox']['#finished'];
99 unset($context['sandbox']['#finished']);
102 if (!isset($context['results'][$module])) {
103 $context['results'][$module] = array();
105 if (!isset($context['results'][$module][$number])) {
106 $context['results'][$module][$number] = array();
108 $context['results'][$module][$number] = array_merge($context['results'][$module][$number], $ret);
110 if (!empty($ret['#abort'])) {
111 // Record this function in the list of updates that were aborted.
112 $context['results']['#abort'][] = $function;
115 // Record the schema update if it was completed successfully.
116 if ($context['finished'] == 1 && empty($ret['#abort'])) {
117 drupal_set_installed_schema_version($module, $number);
120 $context['message'] = 'Performed update: ' . $function;
124 * Check update requirements and report any errors.
126 function update_check_requirements() {
129 // Check the system module and update.php requirements only.
130 $requirements = system_requirements('update');
131 $requirements += update_extra_requirements();
133 // If there are issues, report them.
134 foreach ($requirements as $requirement) {
135 if (isset($requirement['severity']) && $requirement['severity'] > REQUIREMENT_OK) {
136 $message = isset($requirement['description']) ? $requirement['description'] : '';
137 if (isset($requirement['value']) && $requirement['value']) {
138 $message .= ' (Currently using ' . $requirement['title'] . ' ' . $requirement['value'] . ')';
141 drupal_set_message($message, LogLevel::WARNING);
148 function update_main_prepare() {
149 // Some unavoidable errors happen because the database is not yet up-to-date.
150 // Our custom error handler is not yet installed, so we just suppress them.
153 // We prepare a minimal bootstrap for the update requirements check to avoid
154 // reaching the PHP memory limit.
155 $core = DRUSH_DRUPAL_CORE;
156 require_once $core . '/includes/bootstrap.inc';
157 require_once $core . '/includes/common.inc';
158 require_once $core . '/includes/file.inc';
159 require_once $core . '/includes/entity.inc';
160 include_once $core . '/includes/unicode.inc';
162 update_prepare_d7_bootstrap();
163 drupal_bootstrap(DRUPAL_BOOTSTRAP_SESSION);
165 require_once $core . '/includes/install.inc';
166 require_once $core . '/modules/system/system.install';
168 // Load module basics.
169 include_once $core . '/includes/module.inc';
170 $module_list['system']['filename'] = 'modules/system/system.module';
171 module_list(TRUE, FALSE, FALSE, $module_list);
172 drupal_load('module', 'system');
174 // Reset the module_implements() cache so that any new hook implementations
175 // in updated code are picked up.
176 module_implements('', FALSE, TRUE);
178 // Set up $language, since the installer components require it.
179 drupal_language_initialize();
181 // Set up theme system for the maintenance page.
182 drupal_maintenance_theme();
184 // Check the update requirements for Drupal.
185 update_check_requirements();
187 // update_fix_d7_requirements() needs to run before bootstrapping beyond path.
188 // So bootstrap to DRUPAL_BOOTSTRAP_LANGUAGE then include unicode.inc.
189 drupal_bootstrap(DRUPAL_BOOTSTRAP_LANGUAGE);
191 update_fix_d7_requirements();
193 // Clear the module_implements() cache before the full bootstrap. The calls
194 // above to drupal_maintenance_theme() and update_check_requirements() have
195 // invoked hooks before all modules have actually been loaded by the full
196 // bootstrap. This means that the module_implements() results for any hooks
197 // that have been invoked, including hook_module_implements_alter(), is a
198 // smaller set of modules than should be returned normally.
199 // @see https://github.com/drush-ops/drush/pull/399
200 module_implements('', FALSE, TRUE);
202 // Now proceed with a full bootstrap.
204 drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_FULL);
205 drupal_maintenance_theme();
209 include_once DRUPAL_ROOT . '/includes/batch.inc';
210 drupal_load_updates();
212 update_fix_compatibility();
214 // Change query-strings on css/js files to enforce reload for all users.
215 _drupal_flush_css_js();
216 // Flush the cache of all data for the update status module.
217 if (db_table_exists('cache_update')) {
218 cache_clear_all('*', 'cache_update', TRUE);
221 module_list(TRUE, FALSE, TRUE);
224 function update_main() {
225 update_main_prepare();
227 list($pending, $start) = updatedb_status();
229 // @todo get table header working.
230 // $headers = array(dt('Module'), dt('ID'), dt('Description'));
231 drush_print_table($pending);
232 if (!drush_confirm(dt('Do you wish to run all pending updates?'))) {
233 return drush_user_abort();
235 drush_update_batch($start);
238 drush_log(dt("No database updates required"), LogLevel::SUCCESS);
241 return count($pending);
244 function _update_batch_command($id) {
245 update_main_prepare();
246 drush_batch_command($id);
250 * Start the database update batch process.
253 * An array of all the modules and which update to start at.
255 * Path to redirect to when the batch has finished processing.
257 * URL of the batch processing page (should only be used for separate
258 * scripts like update.php).
260 * Optional parameters to pass into the batch API.
261 * @param $redirect_callback
262 * (optional) Specify a function to be called to redirect to the progressive
265 function drush_update_batch($start) {
266 // Resolve any update dependencies to determine the actual updates that will
267 // be run and the order they will be run in.
268 $updates = update_resolve_dependencies($start);
270 // Store the dependencies for each update function in an array which the
271 // batch API can pass in to the batch operation each time it is called. (We
272 // do not store the entire update dependency array here because it is
273 // potentially very large.)
274 $dependency_map = array();
275 foreach ($updates as $function => $update) {
276 $dependency_map[$function] = !empty($update['reverse_paths']) ? array_keys($update['reverse_paths']) : array();
279 $operations = array();
280 foreach ($updates as $update) {
281 if ($update['allowed']) {
282 // Set the installed version of each module so updates will start at the
283 // correct place. (The updates are already sorted, so we can simply base
284 // this on the first one we come across in the above foreach loop.)
285 if (isset($start[$update['module']])) {
286 drupal_set_installed_schema_version($update['module'], $update['number'] - 1);
287 unset($start[$update['module']]);
289 // Add this update function to the batch.
290 $function = $update['module'] . '_update_' . $update['number'];
291 $operations[] = array('drush_update_do_one', array($update['module'], $update['number'], $dependency_map[$function]));
295 $batch['operations'] = $operations;
297 'title' => 'Updating',
298 'init_message' => 'Starting updates',
299 '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.',
300 'finished' => 'drush_update_finished',
301 'file' => 'includes/update.inc',
304 drush_backend_batch_process('updatedb-batch-process');
309 function drush_update_finished($success, $results, $operations) {
310 // Nothing to do here. All caches already cleared. Kept as documentation of 'finished' callback.
314 * Return a 2 item array with
315 * - an array where each item is a 3 item associative array describing a pending update.
316 * - an array listing the first update to run, keyed by module.
318 function updatedb_status() {
319 $pending = update_get_update_list();
322 // Ensure system module's updates run first.
323 $start['system'] = array();
325 // Print a list of pending updates for this module and get confirmation.
326 foreach ($pending as $module => $updates) {
327 if (isset($updates['start'])) {
328 foreach ($updates['pending'] as $update_id => $description) {
329 // Strip cruft from front.
330 $description = str_replace($update_id . ' - ', '', $description);
331 $return[] = array('module' => ucfirst($module), 'update_id' => $update_id, 'description' => $description);
333 if (isset($updates['start'])) {
334 $start[$module] = $updates['start'];
338 return array($return, $start);