Version 1
[yaffs-website] / vendor / drush / drush / commands / core / drupal / update_6.inc
1 <?php
2 /**
3  * @file
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
7  *   line.
8  */
9
10 use Drush\Log\LogLevel;
11
12 define('MAINTENANCE_MODE', 'update');
13
14
15 /**
16  * Add a column to a database using syntax appropriate for PostgreSQL.
17  * Save result of SQL commands in $ret array.
18  *
19  * Note: when you add a column with NOT NULL and you are not sure if there are
20  * already rows in the table, you MUST also add DEFAULT. Otherwise PostgreSQL
21  * won't work when the table is not empty, and db_add_column() will fail.
22  * To have an empty string as the default, you must use: 'default' => "''"
23  * in the $attributes array. If NOT NULL and DEFAULT are set the PostgreSQL
24  * version will set values of the added column in old rows to the
25  * DEFAULT value.
26  *
27  * @param $ret
28  *   Array to which results will be added.
29  * @param $table
30  *   Name of the table, without {}
31  * @param $column
32  *   Name of the column
33  * @param $type
34  *   Type of column
35  * @param $attributes
36  *   Additional optional attributes. Recognized attributes:
37  *     not null => TRUE|FALSE
38  *     default  => NULL|FALSE|value (the value must be enclosed in '' marks)
39  * @return
40  *   nothing, but modifies $ret parameter.
41  */
42 function db_add_column(&$ret, $table, $column, $type, $attributes = array()) {
43   if (array_key_exists('not null', $attributes) and $attributes['not null']) {
44     $not_null = 'NOT NULL';
45   }
46   if (array_key_exists('default', $attributes)) {
47     if (!isset($attributes['default'])) {
48       $default_val = 'NULL';
49       $default = 'default NULL';
50     }
51     elseif ($attributes['default'] === FALSE) {
52       $default = '';
53     }
54     else {
55       $default_val = "$attributes[default]";
56       $default = "default $attributes[default]";
57     }
58   }
59
60   $ret[] = update_sql("ALTER TABLE {". $table ."} ADD $column $type");
61   if (!empty($default)) {
62     $ret[] = update_sql("ALTER TABLE {". $table ."} ALTER $column SET $default");
63   }
64   if (!empty($not_null)) {
65     if (!empty($default)) {
66       $ret[] = update_sql("UPDATE {". $table ."} SET $column = $default_val");
67     }
68     $ret[] = update_sql("ALTER TABLE {". $table ."} ALTER $column SET NOT NULL");
69   }
70 }
71
72 /**
73  * Change a column definition using syntax appropriate for PostgreSQL.
74  * Save result of SQL commands in $ret array.
75  *
76  * Remember that changing a column definition involves adding a new column
77  * and dropping an old one. This means that any indices, primary keys and
78  * sequences from serial-type columns are dropped and might need to be
79  * recreated.
80  *
81  * @param $ret
82  *   Array to which results will be added.
83  * @param $table
84  *   Name of the table, without {}
85  * @param $column
86  *   Name of the column to change
87  * @param $column_new
88  *   New name for the column (set to the same as $column if you don't want to change the name)
89  * @param $type
90  *   Type of column
91  * @param $attributes
92  *   Additional optional attributes. Recognized attributes:
93  *     not null => TRUE|FALSE
94  *     default  => NULL|FALSE|value (with or without '', it won't be added)
95  * @return
96  *   nothing, but modifies $ret parameter.
97  */
98 function db_change_column(&$ret, $table, $column, $column_new, $type, $attributes = array()) {
99   if (array_key_exists('not null', $attributes) and $attributes['not null']) {
100     $not_null = 'NOT NULL';
101   }
102   if (array_key_exists('default', $attributes)) {
103     if (!isset($attributes['default'])) {
104       $default_val = 'NULL';
105       $default = 'default NULL';
106     }
107     elseif ($attributes['default'] === FALSE) {
108       $default = '';
109     }
110     else {
111       $default_val = "$attributes[default]";
112       $default = "default $attributes[default]";
113     }
114   }
115
116   $ret[] = update_sql("ALTER TABLE {". $table ."} RENAME $column TO ". $column ."_old");
117   $ret[] = update_sql("ALTER TABLE {". $table ."} ADD $column_new $type");
118   $ret[] = update_sql("UPDATE {". $table ."} SET $column_new = ". $column ."_old");
119   if ($default) { $ret[] = update_sql("ALTER TABLE {". $table ."} ALTER $column_new SET $default"); }
120   if ($not_null) { $ret[] = update_sql("ALTER TABLE {". $table ."} ALTER $column_new SET NOT NULL"); }
121   $ret[] = update_sql("ALTER TABLE {". $table ."} DROP ". $column ."_old");
122 }
123
124
125 /**
126  * Disable anything in the {system} table that is not compatible with the
127  * current version of Drupal core.
128  */
129 function update_fix_compatibility() {
130   $ret = array();
131   $incompatible = array();
132   $query = db_query("SELECT name, type, status FROM {system} WHERE status = 1 AND type IN ('module','theme')");
133   while ($result = db_fetch_object($query)) {
134     if (update_check_incompatibility($result->name, $result->type)) {
135       $incompatible[] = $result->name;
136       drush_log(dt("!type !name is incompatible with this release of Drupal, and will be disabled.",
137         array("!type" => $result->type, '!name' => $result->name)), LogLevel::WARNING);
138     }
139   }
140   if (!empty($incompatible)) {
141
142     $ret[] = update_sql("UPDATE {system} SET status = 0 WHERE name IN ('". implode("','", $incompatible) ."')");
143   }
144   return $ret;
145 }
146
147 /**
148  * Helper function to test compatibility of a module or theme.
149  */
150 function update_check_incompatibility($name, $type = 'module') {
151   static $themes, $modules;
152
153   // Store values of expensive functions for future use.
154   if (empty($themes) || empty($modules)) {
155     drush_include_engine('drupal', 'environment');
156     $themes = _system_theme_data();
157     $modules = module_rebuild_cache();
158   }
159
160   if ($type == 'module' && isset($modules[$name])) {
161     $file = $modules[$name];
162   }
163   else if ($type == 'theme' && isset($themes[$name])) {
164     $file = $themes[$name];
165   }
166   if (!isset($file)
167       || !isset($file->info['core'])
168       || $file->info['core'] != drush_get_drupal_core_compatibility()
169       || version_compare(phpversion(), $file->info['php']) < 0) {
170     return TRUE;
171   }
172   return FALSE;
173 }
174
175 /**
176  * Perform Drupal 5.x to 6.x updates that are required for update.php
177  * to function properly.
178  *
179  * This function runs when update.php is run the first time for 6.x,
180  * even before updates are selected or performed.  It is important
181  * that if updates are not ultimately performed that no changes are
182  * made which make it impossible to continue using the prior version.
183  * Just adding columns is safe.  However, renaming the
184  * system.description column to owner is not.  Therefore, we add the
185  * system.owner column and leave it to system_update_6008() to copy
186  * the data from description and remove description. The same for
187  * renaming locales_target.locale to locales_target.language, which
188  * will be finished by locale_update_6002().
189  */
190 function update_fix_d6_requirements() {
191   $ret = array();
192
193   if (drupal_get_installed_schema_version('system') < 6000 && !variable_get('update_d6_requirements', FALSE)) {
194     $spec = array('type' => 'int', 'size' => 'small', 'default' => 0, 'not null' => TRUE);
195     db_add_field($ret, 'cache', 'serialized', $spec);
196     db_add_field($ret, 'cache_filter', 'serialized', $spec);
197     db_add_field($ret, 'cache_page', 'serialized', $spec);
198     db_add_field($ret, 'cache_menu', 'serialized', $spec);
199
200     db_add_field($ret, 'system', 'info', array('type' => 'text'));
201     db_add_field($ret, 'system', 'owner', array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''));
202     if (db_table_exists('locales_target')) {
203       db_add_field($ret, 'locales_target', 'language', array('type' => 'varchar', 'length' => 12, 'not null' => TRUE, 'default' => ''));
204     }
205     if (db_table_exists('locales_source')) {
206       db_add_field($ret, 'locales_source', 'textgroup', array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => 'default'));
207       db_add_field($ret, 'locales_source', 'version', array('type' => 'varchar', 'length' => 20, 'not null' => TRUE, 'default' => 'none'));
208     }
209     variable_set('update_d6_requirements', TRUE);
210
211     // Create the cache_block table. See system_update_6027() for more details.
212     $schema['cache_block'] = array(
213       'fields' => array(
214         'cid'        => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''),
215         'data'       => array('type' => 'blob', 'not null' => FALSE, 'size' => 'big'),
216         'expire'     => array('type' => 'int', 'not null' => TRUE, 'default' => 0),
217         'created'    => array('type' => 'int', 'not null' => TRUE, 'default' => 0),
218         'headers'    => array('type' => 'text', 'not null' => FALSE),
219         'serialized' => array('type' => 'int', 'size' => 'small', 'not null' => TRUE, 'default' => 0)
220       ),
221       'indexes' => array('expire' => array('expire')),
222       'primary key' => array('cid'),
223     );
224     db_create_table($ret, 'cache_block', $schema['cache_block']);
225
226     // Create the semaphore table now -- the menu system after 6.15 depends on
227     // this table, and menu code runs in updates prior to the table being
228     // created in its original update function, system_update_6054().
229     $schema['semaphore'] = array(
230       'fields' => array(
231         'name' => array(
232         'type' => 'varchar',
233         'length' => 255,
234         'not null' => TRUE,
235         'default' => ''),
236       'value' => array(
237         'type' => 'varchar',
238         'length' => 255,
239         'not null' => TRUE,
240         'default' => ''),
241       'expire' => array(
242         'type' => 'float',
243         'size' => 'big',
244         'not null' => TRUE),
245       ),
246      'indexes' => array('expire' => array('expire')),
247      'primary key' => array('name'),
248     );
249     db_create_table($ret, 'semaphore', $schema['semaphore']);
250   }
251
252   return $ret;
253 }
254
255 /**
256  * Check update requirements and report any errors.
257  */
258 function update_check_requirements() {
259   // Check the system module requirements only.
260   $requirements = module_invoke('system', 'requirements', 'update');
261   $severity = drupal_requirements_severity($requirements);
262
263   // If there are issues, report them.
264   if ($severity != REQUIREMENT_OK) {
265     foreach ($requirements as $requirement) {
266       if (isset($requirement['severity']) && $requirement['severity'] != REQUIREMENT_OK) {
267         $message = isset($requirement['description']) ? $requirement['description'] : '';
268         if (isset($requirement['value']) && $requirement['value']) {
269           $message .= ' (Currently using '. $requirement['title'] .' '. $requirement['value'] .')';
270         }
271         drush_log($message, LogLevel::WARNING);
272       }
273     }
274   }
275 }
276
277 /**
278  * Create the batch table.
279  *
280  * This is part of the Drupal 5.x to 6.x migration.
281  */
282 function update_create_batch_table() {
283
284   // If batch table exists, update is not necessary
285   if (db_table_exists('batch')) {
286     return;
287   }
288
289   $schema['batch'] = array(
290     'fields' => array(
291       'bid'       => array('type' => 'serial', 'unsigned' => TRUE, 'not null' => TRUE),
292       'token'     => array('type' => 'varchar', 'length' => 64, 'not null' => TRUE),
293       'timestamp' => array('type' => 'int', 'not null' => TRUE),
294       'batch'     => array('type' => 'text', 'not null' => FALSE, 'size' => 'big')
295     ),
296     'primary key' => array('bid'),
297     'indexes' => array('token' => array('token')),
298   );
299
300   $ret = array();
301   db_create_table($ret, 'batch', $schema['batch']);
302   return $ret;
303 }
304
305 function update_main_prepare() {
306   global $profile;
307   // Some unavoidable errors happen because the database is not yet up-to-date.
308   // Our custom error handler is not yet installed, so we just suppress them.
309   drush_errors_off();
310
311   require_once './includes/bootstrap.inc';
312   // Minimum load of components.
313   // This differs from the Drupal 6 update.php workflow for compatbility with
314   // the Drupal 6 backport of module_implements() caching.
315   // @see http://drupal.org/node/557542
316   drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_DATABASE);
317   require_once './includes/install.inc';
318   require_once './includes/file.inc';
319   require_once './modules/system/system.install';
320
321   // Load module basics.
322   include_once './includes/module.inc';
323   $module_list['system']['filename'] = 'modules/system/system.module';
324   $module_list['filter']['filename'] = 'modules/filter/filter.module';
325   module_list(TRUE, FALSE, FALSE, $module_list);
326   module_implements('', FALSE, TRUE);
327
328   drupal_load('module', 'system');
329   drupal_load('module', 'filter');
330
331   // Set up $language, since the installer components require it.
332   drupal_init_language();
333
334   // Set up theme system for the maintenance page.
335   drupal_maintenance_theme();
336
337   // Check the update requirements for Drupal.
338   update_check_requirements();
339
340   drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_FULL);
341   $profile = variable_get('install_profile', 'default');
342   // Updates only run reliably if user ID #1 is logged in. For example, node_delete() requires elevated perms in D5/6.
343   if (!drush_get_context('DRUSH_USER')) {
344     drush_set_option('user', 1);
345     drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_LOGIN);
346   }
347
348   // This must happen *after* drupal_bootstrap(), since it calls
349   // variable_(get|set), which only works after a full bootstrap.
350   _drush_log_update_sql(update_create_batch_table());
351
352   // Turn error reporting back on. From now on, only fatal errors (which are
353   // not passed through the error handler) will cause a message to be printed.
354   drush_errors_on();
355
356   // Perform Drupal 5.x to 6.x updates that are required for update.php to function properly.
357   _drush_log_update_sql(update_fix_d6_requirements());
358
359   // Must unset $theme->status in order to safely rescan and repopulate
360   // the system table to ensure we have a full picture of the platform.
361   // This is needed because $theme->status is set to 0 in a call to
362   // list_themes() done by drupal_maintenance_theme().
363   // It is a issue with _system_theme_data() that returns its own cache
364   // variable and can be modififed by others. When this is fixed in
365   // drupal core we can remove this unset.
366   // For reference see: http://drupal.org/node/762754
367   $themes = _system_theme_data();
368   foreach ($themes as $theme) {
369     unset($theme->status);
370   }
371   drush_get_extensions();
372
373   include_once './includes/batch.inc';
374   drupal_load_updates();
375
376   // Disable anything in the {system} table that is not compatible with the current version of Drupal core.
377   _drush_log_update_sql(update_fix_compatibility());
378 }
379
380 function update_main() {
381   update_main_prepare();
382
383   list($pending, $start) = updatedb_status();
384
385   // Print a list of pending updates for this module and get confirmation.
386   if ($pending) {
387     // @todo get table header working
388     // array_unshift($pending, array(dt('Module'), dt('ID'), dt('Description')));
389     drush_print_table($pending, FALSE);
390     if (!drush_confirm(dt('Do you wish to run all pending updates?'))) {
391       return drush_user_abort();
392     }
393     // Proceed with running all pending updates.
394     $operations = array();
395     foreach ($start as $module => $version) {
396       drupal_set_installed_schema_version($module, $version - 1);
397       $updates = drupal_get_schema_versions($module);
398       $max_version = max($updates);
399       if ($version <= $max_version) {
400         drush_log(dt('Updating module @module from schema version @start to schema version @max', array('@module' => $module, '@start' => $version - 1, '@max' => $max_version)));
401         foreach ($updates as $update) {
402           if ($update >= $version) {
403             $operations[] = array('_update_do_one', array($module, $update));
404           }
405         }
406       }
407       else {
408         drush_log(dt('No database updates for module @module', array('@module' => $module)), LogLevel::SUCCESS);
409       }
410     }
411     $batch = array(
412       'operations' => $operations,
413       'title' => 'Updating',
414       'init_message' => 'Starting updates',
415       '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.',
416       'finished' => 'update_finished',
417     );
418     batch_set($batch);
419     $batch =& batch_get();
420     $batch['progressive'] = FALSE;
421     drush_backend_batch_process('updatedb-batch-process');
422   }
423   else {
424     drush_log(dt("No database updates required"), LogLevel::SUCCESS);
425   }
426
427   return count($pending);
428 }
429
430 /**
431  * A simplified version of the batch_do_one function from update.php
432  *
433  * This does not mess with sessions and the like, as it will be used
434  * from the command line
435  */
436 function _update_do_one($module, $number, &$context) {
437   // If updates for this module have been aborted
438   // in a previous step, go no further.
439   if (!empty($context['results'][$module]['#abort'])) {
440     return;
441   }
442
443   $function = $module .'_update_'. $number;
444   drush_log("Executing $function", LogLevel::SUCCESS);
445
446   if (function_exists($function)) {
447     $ret = $function($context['sandbox']);
448     $context['results'][$module] = $ret;
449     _drush_log_update_sql($ret);
450   }
451
452   if (isset($ret['#finished'])) {
453     $context['finished'] = $ret['#finished'];
454     unset($ret['#finished']);
455   }
456
457   if ($context['finished'] == 1 && empty($context['results'][$module]['#abort'])) {
458     drupal_set_installed_schema_version($module, $number);
459   }
460
461 }
462
463 function _update_batch_command($id) {
464   update_main_prepare();
465   drush_batch_command($id);
466 }
467
468 /**
469  * Return a 2 item array with
470  *  - an array where each item is a 3 item associative array describing a pending update.
471  *  - an array listing the first update to run, keyed by module.
472  */
473 function updatedb_status() {
474   $return = array();
475
476   $modules = drupal_get_installed_schema_version(NULL, FALSE, TRUE);
477   foreach ($modules as $module => $schema_version) {
478     $updates = drupal_get_schema_versions($module);
479     // Skip incompatible module updates completely, otherwise test schema versions.
480     if (!update_check_incompatibility($module) && $updates !== FALSE && $schema_version >= 0) {
481       // module_invoke returns NULL for nonexisting hooks, so if no updates
482       // are removed, it will == 0.
483       $last_removed = module_invoke($module, 'update_last_removed');
484       if ($schema_version < $last_removed) {
485         drush_set_error('PROVISION_DRUPAL_UPDATE_FAILED', dt( $module .' module can not be updated. Its schema version is '. $schema_version .'. Updates up to and including '. $last_removed .' have been removed in this release. In order to update '. $module .' module, you will first <a href="http://drupal.org/upgrade">need to upgrade</a> to the last version in which these updates were available.'));
486         continue;
487       }
488
489       $updates = drupal_map_assoc($updates);
490
491       // Record the starting update number for each module.
492       foreach (array_keys($updates) as $update) {
493         if ($update > $schema_version) {
494           $start[$module] = $update;
495           break;
496         }
497       }
498       if (isset($start['system'])) {
499         // Ensure system module's updates run first.
500         $start = array('system' => $start['system']) + $start;
501       }
502
503       // Record any pending updates. Used for confirmation prompt.
504       foreach (array_keys($updates) as $update) {
505         if ($update > $schema_version) {
506           if (class_exists('ReflectionFunction')) {
507             // The description for an update comes from its Doxygen.
508             $func = new ReflectionFunction($module. '_update_'. $update);
509             $description = trim(str_replace(array("\n", '*', '/'), '', $func->getDocComment()));
510           }
511           if (empty($description)) {
512             $description = dt('description not available');
513           }
514
515           $return[] = array('module' => ucfirst($module), 'update_id' => $update, 'description' => $description);
516         }
517       }
518     }
519   }
520
521   return array($return, $start);
522 }