update.inc 54.6 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
<?php
// $Id$

/**
 * @file
 * Drupal database update API.
 *
 * This file contains functions to perform database updates for a Drupal
 * installation. It is included and used extensively by update.php.
 */

12
13
14
15
16
17
18
19
20
21
/**
 * Minimum schema version of Drupal 6 required for upgrade to Drupal 7.
 *
 * Upgrades from Drupal 6 to Drupal 7 require that Drupal 6 be running
 * the most recent version, or the upgrade could fail. We can't easily
 * check the Drupal 6 version once the update process has begun, so instead
 * we check the schema version of system.module in the system table.
 */
define('REQUIRED_D6_SCHEMA_VERSION', '6055');

22
/**
23
 * Disable any items in the {system} table that are not core compatible.
24
25
26
 */
function update_fix_compatibility() {
  $incompatible = array();
27
28
29
30
  $result = db_query("SELECT name, type, status FROM {system} WHERE status = 1 AND type IN ('module','theme')");
  foreach ($result as $row) {
    if (update_check_incompatibility($row->name, $row->type)) {
      $incompatible[] = $row->name;
31
32
33
    }
  }
  if (!empty($incompatible)) {
34
35
36
37
    db_update('system')
      ->fields(array('status' => 0))
      ->condition('name', $incompatible, 'IN')
      ->execute();
38
39
40
41
42
43
44
45
46
47
48
  }
}

/**
 * Helper function to test compatibility of a module or theme.
 */
function update_check_incompatibility($name, $type = 'module') {
  static $themes, $modules;

  // Store values of expensive functions for future use.
  if (empty($themes) || empty($modules)) {
49
50
51
52
    // We need to do a full rebuild here to make sure the database reflects any
    // code changes that were made in the filesystem before the update script
    // was initiated.
    $themes = system_rebuild_theme_data();
53
    $modules = system_rebuild_module_data();
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
  }

  if ($type == 'module' && isset($modules[$name])) {
    $file = $modules[$name];
  }
  elseif ($type == 'theme' && isset($themes[$name])) {
    $file = $themes[$name];
  }
  if (!isset($file)
      || !isset($file->info['core'])
      || $file->info['core'] != DRUPAL_CORE_COMPATIBILITY
      || version_compare(phpversion(), $file->info['php']) < 0
      || ($type == 'module' && empty($file->info['files']))) {
    return TRUE;
  }
  return FALSE;
}

/**
73
74
 * Performs extra steps required to bootstrap when using a Drupal 6 database.
 *
75
76
77
78
79
80
 * Users who still have a Drupal 6 database (and are in the process of
 * updating to Drupal 7) need extra help before a full bootstrap can be
 * achieved. This function does the necessary preliminary work that allows
 * the bootstrap to be successful.
 *
 * No access check has been performed when this function is called, so no
81
 * irreversible changes to the database are made here.
82
83
84
85
86
87
 */
function update_prepare_d7_bootstrap() {
  // Allow the bootstrap to proceed even if a Drupal 6 settings.php file is
  // still being used.
  include_once DRUPAL_ROOT . '/includes/install.inc';
  drupal_bootstrap(DRUPAL_BOOTSTRAP_CONFIGURATION);
88
  global $databases, $db_url, $db_prefix, $update_rewrite_settings;
89
  if (empty($databases) && !empty($db_url)) {
90
    $databases = update_parse_db_url($db_url, $db_prefix);
91
92
93
94
95
96
97
98
99
    // Record the fact that the settings.php file will need to be rewritten.
    $update_rewrite_settings = TRUE;
    $settings_file = conf_path() . '/settings.php';
    $writable = drupal_verify_install_file($settings_file, FILE_EXIST|FILE_READABLE|FILE_WRITABLE);
    $requirements = array(
      'settings file' => array(
        'title' => 'Settings file',
        'value' => $writable ? 'The settings file is writable.' : 'The settings file is not writable.',
        'severity' => $writable ? REQUIREMENT_OK : REQUIREMENT_ERROR,
100
        'description' => $writable ? '' : 'Drupal requires write permissions to <em>' . $settings_file . '</em> during the update process. If you are unsure how to grant file permissions, consult the <a href="http://drupal.org/server-permissions">online handbook</a>.',
101
102
103
104
      ),
    );
    update_extra_requirements($requirements);
  }
105
106
107
108
109
110
111

  // The new {blocked_ips} table is used in Drupal 7 to store a list of
  // banned IP addresses. If this table doesn't exist then we are still
  // running on a Drupal 6 database, so we suppress the unavoidable errors
  // that occur by creating a static list.
  $GLOBALS['conf']['blocked_ips'] = array();

112
113
114
115
  // Check that PDO is available and that the correct PDO database driver is
  // loaded. Bootstrapping to DRUPAL_BOOTSTRAP_DATABASE will result in a fatal
  // error otherwise.
  $message = '';
116
  $pdo_link = 'http://drupal.org/requirements/pdo';
117
118
119
120
  // Check that PDO is loaded.
  if (!extension_loaded('pdo')) {
    $message = '<h2>PDO is required!</h2><p>Drupal 7 requires PHP ' . DRUPAL_MINIMUM_PHP . ' or higher with the PHP Data Objects (PDO) extension enabled.</p>';
  }
121
122
123
124
125
126
  // The PDO::ATTR_DEFAULT_FETCH_MODE constant is not available in the PECL
  // version of PDO.
  elseif (!defined('PDO::ATTR_DEFAULT_FETCH_MODE')) {
    $message = '<h2>The wrong version of PDO is installed!</h2><p>Drupal 7 requires the PHP Data Objects (PDO) extension from PHP core to be enabled. This system has the older PECL version installed.';
    $pdo_link = 'http://drupal.org/requirements/pdo#pecl';
  }
127
  // Check that the correct driver is loaded for the database being updated.
128
129
130
131
132
  // If we have no driver information (for example, if someone tried to create
  // the Drupal 7 $databases array themselves but did not do it correctly),
  // this message will be confusing, so do not perform the check; instead, just
  // let the database connection fail in the code that follows.
  elseif (isset($databases['default']['default']['driver']) && !in_array($databases['default']['default']['driver'], PDO::getAvailableDrivers())) {
133
    $message = '<h2>A PDO database driver is required!</h2><p>You need to enable the PDO_' . strtoupper($databases['default']['default']['driver']) . ' database driver for PHP ' . DRUPAL_MINIMUM_PHP . ' or higher so that Drupal 7 can access the database.</p>';
134
135
  }
  if ($message) {
136
    print $message . '<p>See the <a href="' . $pdo_link . '">system requirements page</a> for more information.</p>';
137
138
    exit();
  }
139

140
141
142
  // Allow the database system to work even if the registry has not been
  // created yet.
  drupal_bootstrap(DRUPAL_BOOTSTRAP_DATABASE);
143

144
145
146
147
  // If the site has not updated to Drupal 7 yet, check to make sure that it is
  // running an up-to-date version of Drupal 6 before proceeding. Note this has
  // to happen AFTER the database bootstraps because of
  // drupal_get_installed_schema_version().
148
  $system_schema = drupal_get_installed_schema_version('system');
149
150
151
152
153
154
155
156
157
158
159
160
  if ($system_schema < 7000) {
    $has_required_schema = $system_schema >= REQUIRED_D6_SCHEMA_VERSION;
    $requirements = array(
      'drupal 6 version' => array(
        'title' => 'Drupal 6 version',
        'value' => $has_required_schema ? 'You are running a current version of Drupal 6.' : 'You are not running a current version of Drupal 6',
        'severity' => $has_required_schema ? REQUIREMENT_OK : REQUIREMENT_ERROR,
        'description' => $has_required_schema ? '' : 'Please update your Drupal 6 installation to the most recent version before attempting to upgrade to Drupal 7',
      ),
    );
    update_extra_requirements($requirements);
  }
161

162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
  // Create the registry tables.
  if (!db_table_exists('registry')) {
    $schema['registry'] = array(
      'fields' => array(
        'name'   => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''),
        'type'   => array('type' => 'varchar', 'length' => 9, 'not null' => TRUE, 'default' => ''),
        'filename'   => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''),
        'module'   => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''),
        'weight'   => array('type' => 'int', 'not null' => TRUE, 'default' => 0),
      ),
      'primary key' => array('name', 'type'),
      'indexes' => array(
        'hook' => array('type', 'weight', 'module'),
      ),
    );
    db_create_table('registry', $schema['registry']);
  }
  if (!db_table_exists('registry_file')) {
    $schema['registry_file'] = array(
      'fields' => array(
        'filename'   => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE),
183
        'hash'   => array('type' => 'varchar', 'length' => 64, 'not null' => TRUE),
184
185
186
187
188
189
      ),
      'primary key' => array('filename'),
    );
    db_create_table('registry_file', $schema['registry_file']);
  }

190
191
192
  // Older versions of Drupal 6 do not include the semaphore table, which is 
  // required to bootstrap, so we add it now so that we can bootstrap and
  // provide a reasonable error message.
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
  if (!db_table_exists('semaphore')) {
    $semaphore = array(
      'description' => 'Table for holding semaphores, locks, flags, etc. that cannot be stored as Drupal variables since they must not be cached.',
      'fields' => array(
        'name' => array(
          'description' => 'Primary Key: Unique name.',
          'type' => 'varchar',
          'length' => 255,
          'not null' => TRUE,
          'default' => ''
        ),
        'value' => array(
          'description' => 'A value for the semaphore.',
          'type' => 'varchar',
          'length' => 255,
          'not null' => TRUE,
          'default' => ''
        ),
        'expire' => array(
          'description' => 'A Unix timestamp with microseconds indicating when the semaphore should expire.',
          'type' => 'float',
          'size' => 'big',
          'not null' => TRUE
        ),
      ),
      'indexes' => array(
        'value' => array('value'),
        'expire' => array('expire'),
      ),
      'primary key' => array('name'),
    );
    db_create_table('semaphore', $semaphore);
  }

227
228
229
  // The new cache_bootstrap bin is required to bootstrap to
  // DRUPAL_BOOTSTRAP_SESSION, so create it here rather than in
  // update_fix_d7_requirements().
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
  if (!db_table_exists('cache_bootstrap')) {
    $cache_bootstrap = array(
      'description' => 'Cache table for data required to bootstrap Drupal, may be routed to a shared memory cache.',
      'fields' => array(
        'cid' => array(
          'description' => 'Primary Key: Unique cache ID.',
          'type' => 'varchar',
          'length' => 255,
          'not null' => TRUE,
          'default' => '',
        ),
        'data' => array(
          'description' => 'A collection of data to cache.',
          'type' => 'blob',
          'not null' => FALSE,
          'size' => 'big',
        ),
        'expire' => array(
          'description' => 'A Unix timestamp indicating when the cache entry should expire, or 0 for never.',
          'type' => 'int',
          'not null' => TRUE,
          'default' => 0,
        ),
        'created' => array(
          'description' => 'A Unix timestamp indicating when the cache entry was created.',
          'type' => 'int',
          'not null' => TRUE,
          'default' => 0,
        ),
        'serialized' => array(
          'description' => 'A flag to indicate whether content is serialized (1) or not (0).',
          'type' => 'int',
          'size' => 'small',
          'not null' => TRUE,
          'default' => 0,
        ),
266
      ),
267
268
      'indexes' => array(
        'expire' => array('expire'),
269
      ),
270
271
272
273
      'primary key' => array('cid'),
    );
    db_create_table('cache_bootstrap', $cache_bootstrap);
  }
274
275
276

  // Set a valid timezone for 6 -> 7 upgrade process.
  drupal_bootstrap(DRUPAL_BOOTSTRAP_VARIABLES);
277
278
279
280
281
  $timezone_offset = variable_get('date_default_timezone', 0);
  if (is_numeric($timezone_offset)) {
    // Save the original offset.
    variable_set('date_temporary_timezone', $timezone_offset);
    // Set the timezone for this request only.
282
283
    $GLOBALS['conf']['date_default_timezone'] = 'UTC';
  }
284
285
}

286
287
288
289
290
291
292
293
294
295
296
297
298
/**
 * A helper function that modules can use to assist with the transformation
 * from numeric block deltas to string block deltas during the 6.x -> 7.x
 * upgrade.
 *
 * @todo This function should be removed in 8.x.
 *
 * @param $sandbox
 *   An array holding data for the batch process.
 * @param $renamed_deltas
 *   An associative array.  Keys are module names, values an associative array
 *   mapping the old block deltas to the new block deltas for the module.
 *   Example:
299
 *   @code
300
301
302
303
304
 *     $renamed_deltas = array(
 *       'mymodule' =>
 *         array(
 *           0 => 'mymodule-block-1',
 *           1 => 'mymodule-block-2',
305
 *         ),
306
 *     );
307
308
309
310
311
312
313
314
315
316
317
318
319
 *   @endcode
 * @param $moved_deltas
 *   An associative array. Keys are source module names, values an associative
 *   array mapping the (possibly renamed) block name to the new module name.
 *   Example:
 *   @code
 *     $moved_deltas = array(
 *       'user' =>
 *         array(
 *           'navigation' => 'system',
 *         ),
 *     );
 *   @endcode
320
 */
321
function update_fix_d7_block_deltas(&$sandbox, $renamed_deltas, $moved_deltas) {
322
323
324
  // Loop through each block and make changes to the block tables.
  // Only run this the first time through the batch update.
  if (!isset($sandbox['progress'])) {
325
326
    // Determine whether to use the old or new block table names.
    $block_tables = db_table_exists('blocks') ? array('blocks', 'blocks_roles') : array('block', 'block_role');
327
328
329
330
331
    foreach ($block_tables as $table) {
      foreach ($renamed_deltas as $module => $deltas) {
        foreach ($deltas as $old_delta => $new_delta) {
          // Only do the update if the old block actually exists.
          $block_exists = db_query("SELECT COUNT(*) FROM {" . $table . "} WHERE module = :module AND delta = :delta", array(
332
333
334
335
              ':module' => $module,
              ':delta' => $old_delta,
            ))
            ->fetchField();
336
          if ($block_exists) {
337
338
339
340
341
            db_update($table)
              ->fields(array('delta' => $new_delta))
              ->condition('module', $module)
              ->condition('delta', $old_delta)
              ->execute();
342
343
344
          }
        }
      }
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
      foreach ($moved_deltas as $old_module => $deltas) {
        foreach ($deltas as $delta => $new_module) {
          // Only do the update if the old block actually exists.
          $block_exists = db_query("SELECT COUNT(*) FROM {" . $table . "} WHERE module = :module AND delta = :delta", array(
              ':module' => $old_module,
              ':delta' => $delta,
            ))
            ->fetchField();
          if ($block_exists) {
            db_update($table)
              ->fields(array('module' => $new_module))
              ->condition('module', $old_module)
              ->condition('delta', $delta)
              ->execute();
          }
        }
      }
362
363
364
365
366
367
368
369
370
371
372
373
    }

    // Initialize batch update information.
    $sandbox['progress'] = 0;
    $sandbox['last_user_processed'] = -1;
    $sandbox['max'] = db_query("SELECT COUNT(*) FROM {users} WHERE data IS NOT NULL")->fetchField();
  }
  // Now do the batch update of the user-specific block visibility settings.
  $limit = 100;
  $result = db_select('users', 'u')
    ->fields('u', array('uid', 'data'))
    ->condition('uid', $sandbox['last_user_processed'], '>')
374
    ->orderBy('uid', 'ASC')
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
    ->where('data IS NOT NULL')
    ->range(0, $limit)
    ->execute();
  foreach ($result as $row) {
    $data = unserialize($row->data);
    $user_needs_update = FALSE;
    foreach ($renamed_deltas as $module => $deltas) {
      foreach ($deltas as $old_delta => $new_delta) {
        if (isset($data['block'][$module][$old_delta])) {
          // Transfer the old block visibility settings to the newly-renamed
          // block, and mark this user for a database update.
          $data['block'][$module][$new_delta] = $data['block'][$module][$old_delta];
          unset($data['block'][$module][$old_delta]);
          $user_needs_update = TRUE;
        }
      }
    }
392
393
394
395
396
397
398
399
400
401
402
    foreach ($moved_deltas as $old_module => $deltas) {
      foreach ($deltas as $delta => $new_module) {
        if (isset($data['block'][$old_module][$delta])) {
          // Transfer the old block visibility settings to the moved
          // block, and mark this user for a database update.
          $data['block'][$new_module][$delta] = $data['block'][$old_module][$delta];
          unset($data['block'][$old_module][$delta]);
          $user_needs_update = TRUE;
        }
      }
    }
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
    // Update the current user.
    if ($user_needs_update) {
      db_update('users')
        ->fields(array('data' => serialize($data)))
        ->condition('uid', $row->uid)
        ->execute();
    }
    // Update our progress information for the batch update.
    $sandbox['progress']++;
    $sandbox['last_user_processed'] = $row->uid;
  }
  // Indicate our current progress to the batch update system.
  if ($sandbox['progress'] < $sandbox['max']) {
    $sandbox['#finished'] = $sandbox['progress'] / $sandbox['max'];
  }
}

420
421
422
423
424
425
426
427
428
429
/**
 * Perform Drupal 6.x to 7.x updates that are required for update.php
 * to function properly.
 *
 * This function runs when update.php is run the first time for 7.x,
 * even before updates are selected or performed. It is important
 * that if updates are not ultimately performed that no changes are
 * made which make it impossible to continue using the prior version.
 */
function update_fix_d7_requirements() {
430
  global $conf;
431

432
433
  // Rewrite the settings.php file if necessary, see
  // update_prepare_d7_bootstrap().
434
  global $update_rewrite_settings, $db_url, $db_prefix;
435
  if (!empty($update_rewrite_settings)) {
436
    $databases = update_parse_db_url($db_url, $db_prefix);
437
    $salt = drupal_hash_base64(drupal_random_bytes(55));
438
    file_put_contents(conf_path() . '/settings.php', "\n" . '$databases = ' . var_export($databases, TRUE) . ";\n\$drupal_hash_salt = '$salt';", FILE_APPEND);
439
440
  }
  if (drupal_get_installed_schema_version('system') < 7000 && !variable_get('update_d7_requirements', FALSE)) {
441
442
443
444
445
446
447
448
449
    // Change 6.x system table field values to 7.x equivalent.
    // Change field values.
    db_change_field('system', 'schema_version', 'schema_version', array(
     'type' => 'int',
     'size' => 'small',
     'not null' => TRUE,
     'default' => -1)
    );
    db_change_field('system', 'status', 'status', array(
450
      'type' => 'int', 'not null' => TRUE, 'default' => 0));
451
    db_change_field('system', 'weight', 'weight', array(
452
      'type' => 'int', 'not null' => TRUE, 'default' => 0));
453
    db_change_field('system', 'bootstrap', 'bootstrap', array(
454
      'type' => 'int', 'not null' => TRUE, 'default' => 0));
455
456
457
458
459
460
461
462
463
464
465
466
467
468
    // Drop and recreate 6.x indexes.
    db_drop_index('system', 'bootstrap');
    db_add_index('system', 'bootstrap', array(
      'status', 'bootstrap', array('type', 12), 'weight', 'name'));

    db_drop_index('system' ,'modules');
    db_add_index('system', 'modules', array(array(
      'type', 12), 'status', 'weight', 'name'));

    db_drop_index('system', 'type_name');
    db_add_index('system', 'type_name', array(array('type', 12), 'name'));
    // Add 7.x indexes.
    db_add_index('system', 'system_list', array('weight', 'name'));

469
    // Add the cache_path table.
470
471
    require_once('./modules/system/system.install');
    $schema['cache_path'] = system_schema_cache_7054();
472
    $schema['cache_path']['description'] = 'Cache table used for path alias lookups.';
473
    db_create_table('cache_path', $schema['cache_path']);
474
475

    // system_update_7042() renames columns, but these are needed to bootstrap.
476
    // Add empty columns for now.
477
478
    db_add_field('url_alias', 'source', array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''));
    db_add_field('url_alias', 'alias', array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''));
479

480
    // Add new columns to {menu_router}.
webchick's avatar
webchick committed
481
    db_add_field('menu_router', 'delivery_callback', array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''));
482
483
484
485
486
487
    db_add_field('menu_router', 'context', array(
      'description' => 'Only for local tasks (tabs) - the context of a local task to control its placement.',
      'type' => 'int',
      'not null' => TRUE,
      'default' => 0,
    ));
488
489
    db_drop_index('menu_router', 'tab_parent');
    db_add_index('menu_router', 'tab_parent', array(array('tab_parent', 64), 'weight', 'title'));
490
491
    db_add_field('menu_router', 'theme_callback', array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''));
    db_add_field('menu_router', 'theme_arguments', array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''));
webchick's avatar
webchick committed
492

493
    // Add the role_permission table.
494
495
496
497
498
499
500
501
502
    $schema['role_permission'] = array(
      'fields' => array(
        'rid' => array(
        'type' => 'int',
        'unsigned' => TRUE,
        'not null' => TRUE,
      ),
      'permission' => array(
        'type' => 'varchar',
503
        'length' => 128,
504
505
506
507
508
509
510
511
512
513
514
        'not null' => TRUE,
        'default' => '',
      ),
    ),
    'primary key' => array('rid', 'permission'),
    'indexes' => array(
      'permission' => array('permission'),
      ),
    );
    db_create_table('role_permission', $schema['role_permission']);

515
    // Drops and recreates semaphore value index.
516
    db_drop_index('semaphore', 'value');
517
    db_add_index('semaphore', 'value', array('value'));
518

519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
    $schema['date_format_type'] = array(
      'description' => 'Stores configured date format types.',
      'fields' => array(
        'type' => array(
          'description' => 'The date format type, e.g. medium.',
          'type' => 'varchar',
          'length' => 64,
          'not null' => TRUE,
        ),
        'title' => array(
          'description' => 'The human readable name of the format type.',
          'type' => 'varchar',
          'length' => 255,
          'not null' => TRUE,
        ),
        'locked' => array(
          'description' => 'Whether or not this is a system provided format.',
          'type' => 'int',
          'size' => 'tiny',
          'default' => 0,
          'not null' => TRUE,
        ),
      ),
      'primary key' => array('type'),
    );

    $schema['date_formats'] = array(
      'description' => 'Stores configured date formats.',
      'fields' => array(
        'dfid' => array(
          'description' => 'The date format identifier.',
          'type' => 'serial',
          'not null' => TRUE,
          'unsigned' => TRUE,
        ),
        'format' => array(
          'description' => 'The date format string.',
          'type' => 'varchar',
          'length' => 100,
          'not null' => TRUE,
        ),
        'type' => array(
          'description' => 'The date format type, e.g. medium.',
          'type' => 'varchar',
          'length' => 64,
          'not null' => TRUE,
        ),
        'locked' => array(
          'description' => 'Whether or not this format can be modified.',
          'type' => 'int',
          'size' => 'tiny',
          'default' => 0,
          'not null' => TRUE,
        ),
      ),
      'primary key' => array('dfid'),
      'unique keys' => array('formats' => array('format', 'type')),
    );

    $schema['date_format_locale'] = array(
      'description' => 'Stores configured date formats for each locale.',
      'fields' => array(
        'format' => array(
          'description' => 'The date format string.',
          'type' => 'varchar',
          'length' => 100,
          'not null' => TRUE,
        ),
        'type' => array(
          'description' => 'The date format type, e.g. medium.',
          'type' => 'varchar',
          'length' => 64,
          'not null' => TRUE,
        ),
        'language' => array(
          'description' => 'A {languages}.language for this format to be used with.',
          'type' => 'varchar',
          'length' => 12,
          'not null' => TRUE,
        ),
      ),
      'primary key' => array('type', 'language'),
    );

    db_create_table('date_format_type', $schema['date_format_type']);
604
605
606
607
608
    // Sites that have the Drupal 6 Date module installed already have the
    // following tables.
    if (db_table_exists('date_formats')) {
      db_rename_table('date_formats', 'd6_date_formats');
    }
609
    db_create_table('date_formats', $schema['date_formats']);
610
611
612
    if (db_table_exists('date_format_locale')) {
      db_rename_table('date_format_locale', 'd6_date_format_locale');
    }
613
614
    db_create_table('date_format_locale', $schema['date_format_locale']);

615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
    // Add the queue table.
    $schema['queue'] = array(
      'description' => 'Stores items in queues.',
      'fields' => array(
        'item_id' => array(
          'type' => 'serial',
          'unsigned' => TRUE,
          'not null' => TRUE,
          'description' => 'Primary Key: Unique item ID.',
        ),
        'name' => array(
          'type' => 'varchar',
          'length' => 255,
          'not null' => TRUE,
          'default' => '',
          'description' => 'The queue name.',
        ),
        'data' => array(
633
          'type' => 'blob',
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
          'not null' => FALSE,
          'size' => 'big',
          'serialize' => TRUE,
          'description' => 'The arbitrary data for the item.',
        ),
        'expire' => array(
          'type' => 'int',
          'not null' => TRUE,
          'default' => 0,
          'description' => 'Timestamp when the claim lease expires on the item.',
        ),
        'created' => array(
          'type' => 'int',
          'not null' => TRUE,
          'default' => 0,
          'description' => 'Timestamp when the item was created.',
        ),
      ),
      'primary key' => array('item_id'),
      'indexes' => array(
        'name_created' => array('name', 'created'),
        'expire' => array('expire'),
      ),
    );
658
659
660
661
662
663
    // Check for queue table that may remain from D5 or D6, if found
    //drop it.
    if (db_table_exists('queue')) {
      db_drop_table('queue');
    }

664
665
    db_create_table('queue', $schema['queue']);

666
667
668
669
670
671
672
673
674
675
676
677
678
    // Create the sequences table.
    $schema['sequences'] = array(
      'description' => 'Stores IDs.',
      'fields' => array(
        'value' => array(
          'description' => 'The value of the sequence.',
          'type' => 'serial',
          'unsigned' => TRUE,
          'not null' => TRUE,
        ),
       ),
      'primary key' => array('value'),
    );
679
680
681
682
683
    // Check for sequences table that may remain from D5 or D6, if found
    //drop it.
    if (db_table_exists('sequences')) {
      db_drop_table('sequences');
    }
684
685
686
687
688
689
690
691
    db_create_table('sequences', $schema['sequences']);
    // Initialize the table with the maximum current increment of the tables
    // that will rely on it for their ids.
    $max_aid = db_query('SELECT MAX(aid) FROM {actions_aid}')->fetchField();
    $max_uid = db_query('SELECT MAX(uid) FROM {users}')->fetchField();
    $max_batch_id = db_query('SELECT MAX(bid) FROM {batch}')->fetchField();
    db_insert('sequences')->fields(array('value' => max($max_aid, $max_uid, $max_batch_id)))->execute();

692
693
    // Add column for locale context.
    if (db_table_exists('locales_source')) {
694
      db_add_field('locales_source', 'context', array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => '', 'description' => 'The context this string applies to.'));
695
    }
696
697

    // Rename 'site_offline_message' variable to 'maintenance_mode_message'.
698
699
    // Old variable is removed in update for system.module, see
    // system_update_7036().
700
701
702
703
    if ($message = variable_get('site_offline_message', NULL)) {
      variable_set('maintenance_mode_message', $message);
    }

704
    // Add ssid column and index.
705
    db_add_field('sessions', 'ssid', array('description' => "Secure session ID. The value is generated by PHP's Session API.", 'type' => 'varchar', 'length' => 128, 'not null' => TRUE, 'default' => ''));
706
    db_add_index('sessions', 'ssid', array('ssid'));
707
708
709
710
    // Drop existing primary key.
    db_drop_primary_key('sessions');
    // Add new primary key.
    db_add_primary_key('sessions', array('sid', 'ssid'));
711

712
713
714
715
716
    // Allow longer javascript file names.
    if (db_table_exists('languages')) {
      db_change_field('languages', 'javascript', 'javascript', array('type' => 'varchar', 'length' => 64, 'not null' => TRUE, 'default' => ''));
    }

717
718
719
    // Rename action description to label.
    db_change_field('actions', 'description', 'label', array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => '0'));

720
    variable_set('update_d7_requirements', TRUE);
721
722
  }

723
  update_fix_d7_install_profile();
724
725
}

726
727
728
/**
 * Register the currently installed profile in the system table.
 *
729
730
 * Install profiles are now treated as modules by Drupal, and have an upgrade
 * path based on their schema version in the system table.
731
 *
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
 * The install profile will be set to schema_version 0, as it has already been
 * installed. Any other hook_update_N functions provided by the install profile
 * will be run by update.php.
 */
function update_fix_d7_install_profile() {
  $profile = drupal_get_profile();

  $results = db_select('system', 's')
    ->fields('s', array('name', 'schema_version'))
    ->condition('name', $profile)
    ->condition('type', 'module')
    ->execute()
    ->fetchAll();

  if (empty($results)) {
    $filename = 'profiles/' . $profile . '/' . $profile . '.profile';

    // Read profile info file
    $info = drupal_parse_info_file(dirname($filename) . '/' . $profile . '.info');

    // Merge in defaults.
    $info = $info + array(
      'dependencies' => array(),
      'description' => '',
      'package' => 'Other',
      'version' => NULL,
      'php' => DRUPAL_MINIMUM_PHP,
      'files' => array(),
    );

    // The install profile is always required.
    $file->info['required'] = TRUE;

    $values = array(
      'filename' => $filename,
      'name' => $profile,
      'info' => serialize($info),
      'schema_version' => 0,
      'type' => 'module',
      'status' => 1,
      'owner' => '',
    );

    // Install profile hooks are always executed last by the module system
    $values['weight'] = 1000;

    // Initializing the system table entry for the install profile
    db_insert('system')
      ->fields(array_keys($values))
      ->values($values)
      ->execute();

    // Reset the cached schema version.
    drupal_get_installed_schema_version($profile, TRUE);

    // Load the updates again to make sure the install profile updates are loaded
    drupal_load_updates();
  }
}

792
/**
793
 * Parse pre-Drupal 7 database connection URLs and return D7 compatible array.
794
795
796
 *
 * @return
 *   Drupal 7 DBTNG compatible array of database connection information.
797
 */
798
function update_parse_db_url($db_url, $db_prefix) {
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
  $databases = array();
  if (!is_array($db_url)) {
    $db_url = array('default' => $db_url);
  }
  foreach ($db_url as $database => $url) {
    $url = parse_url($url);
    $databases[$database]['default'] = array(
      // MySQLi uses the mysql driver.
      'driver' => $url['scheme'] == 'mysqli' ? 'mysql' : $url['scheme'],
      // Remove the leading slash to get the database name.
      'database' => substr(urldecode($url['path']), 1),
      'username' => urldecode($url['user']),
      'password' => isset($url['pass']) ? urldecode($url['pass']) : '',
      'host' => urldecode($url['host']),
      'port' => isset($url['port']) ? urldecode($url['port']) : '',
    );
815
816
817
    if (isset($db_prefix)) {
      $databases[$database]['default']['prefix'] = $db_prefix;
    }
818
819
820
821
822
  }
  return $databases;
}

/**
823
 * Perform one update and store the results for display on finished page.
824
 *
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
 * If an update function completes successfully, it should return a message
 * as a string indicating success, for example:
 * @code
 * return t('New index added successfully.');
 * @endcode
 *
 * Alternatively, it may return nothing. In that case, no message
 * will be displayed at all.
 *
 * If it fails for whatever reason, it should throw an instance of
 * DrupalUpdateException with an appropriate error message, for example:
 * @code
 * throw new DrupalUpdateException(t('Description of what went wrong'));
 * @endcode
 *
840
841
842
843
 * If an exception is thrown, the current update and all updates that depend on
 * it will be aborted. 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.
844
 *
845
846
847
848
849
 * If an update function needs to be re-run as part of a batch process, it
 * should accept the $sandbox array by reference as its first parameter
 * and set the #finished property to the percentage completed that it is, as a
 * fraction of 1.
 *
850
851
852
853
 * @param $module
 *   The module whose update will be run.
 * @param $number
 *   The update number to run.
854
855
856
857
 * @param $dependency_map
 *   An array whose keys are the names of all update functions that will be
 *   performed during this batch process, and whose values are arrays of other
 *   update functions that each one depends on.
858
 * @param $context
859
860
861
 *   The batch context array.
 *
 * @see update_resolve_dependencies()
862
 */
863
864
865
866
867
function 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.
868
  if (!empty($context['results']['#abort']) && array_intersect($context['results']['#abort'], array_merge($dependency_map, array($function)))) {
869
870
871
    return;
  }

872
  $ret = array();
873
  if (function_exists($function)) {
874
875
876
877
    try {
      $ret['results']['query'] = $function($context['sandbox']);
      $ret['results']['success'] = TRUE;
    }
878
879
880
    // @TODO We may want to do different error handling for different
    // exception types, but for now we'll just log the exception and
    // return the message for printing.
881
    catch (Exception $e) {
882
883
884
885
886
      watchdog_exception('update', $e);

      require_once DRUPAL_ROOT . '/includes/errors.inc';
      $variables = _drupal_decode_exception($e);
      $ret['#abort'] = array('success' => FALSE, 'query' => t('%type: %message in %function (line %line of %file).', $variables));
887
    }
888
889
  }

890
891
892
893
894
  if (isset($context['sandbox']['#finished'])) {
    $context['finished'] = $context['sandbox']['#finished'];
    unset($context['sandbox']['#finished']);
  }

895
896
897
898
899
900
901
902
903
  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'])) {
904
905
    // Record this function in the list of updates that were aborted.
    $context['results']['#abort'][] = $function;
906
  }
907

908
  // Record the schema update if it was completed successfully.
909
  if ($context['finished'] == 1 && empty($ret['#abort'])) {
910
911
912
913
914
915
    drupal_set_installed_schema_version($module, $number);
  }

  $context['message'] = 'Updating ' . check_plain($module) . ' module';
}

916
917
918
919
920
/**
 * @class Exception class used to throw error if a module update fails.
 */
class DrupalUpdateException extends Exception { }

921
922
923
924
/**
 * Start the database update batch process.
 *
 * @param $start
925
926
927
928
929
 *   An array whose keys contain the names of modules to be updated during the
 *   current batch process, and whose values contain the number of the first
 *   requested update for that module. The actual updates that are run (and the
 *   order they are run in) will depend on the results of passing this data
 *   through the update dependency system.
930
931
932
933
934
935
936
 * @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.
937
938
939
 * @param $redirect_callback
 *   (optional) Specify a function to be called to redirect to the progressive
 *   processing page.
940
941
 *
 * @see update_resolve_dependencies()
942
 */
943
function update_batch($start, $redirect = NULL, $url = NULL, $batch = array(), $redirect_callback = 'drupal_goto') {
944
945
  // During the update, bring the site offline so that schema changes do not
  // affect visiting users.
946
947
948
  $_SESSION['maintenance_mode'] = variable_get('maintenance_mode', FALSE);
  if ($_SESSION['maintenance_mode'] == FALSE) {
    variable_set('maintenance_mode', TRUE);
949
950
  }

951
952
953
954
955
956
957
958
959
960
961
962
963
  // 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();
  }

964
  $operations = array();
965
966
967
968
969
970
971
972
  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']]);
973
      }
974
      // Add this update function to the batch.
975
976
      $function = $update['module'] . '_update_' . $update['number'];
      $operations[] = array('update_do_one', array($update['module'], $update['number'], $dependency_map[$function]));
977
978
979
980
981
982
983
984
985
986
987
    }
  }
  $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' => 'update_finished',
    'file' => 'includes/update.inc',
  );
  batch_set($batch);
988
  batch_process($redirect, $url, $redirect_callback);
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
}

/**
 * Finish the update process and store results for eventual display.
 *
 * After the updates run, all caches are flushed. The update results are
 * stored into the session (for example, to be displayed on the update results
 * page in update.php). Additionally, if the site was off-line, now that the
 * update process is completed, the site is set back online.
 *
 * @param $success
 *   Indicate that the batch API tasks were all completed successfully.
 * @param $results
 *   An array of all the results that were updated in update_do_one().
 * @param $operations
 *   A list of all the operations that had not been completed by the batch API.
 *
 * @see update_batch()
 */
function update_finished($success, $results, $operations) {
  // Clear the caches in case the data has been updated.
  drupal_flush_all_caches();

  $_SESSION['update_results'] = $results;
  $_SESSION['update_success'] = $success;
  $_SESSION['updates_remaining'] = $operations;

  // Now that the update is done, we can put the site back online if it was
1017
1018
1019
1020
  // previously in maintenance mode.
  if (isset($_SESSION['maintenance_mode']) && $_SESSION['maintenance_mode'] == FALSE) {
    variable_set('maintenance_mode', FALSE);
    unset($_SESSION['maintenance_mode']);
1021
1022
1023
  }
}

1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
/**
 * Return a list of all the pending database updates.
 *
 * @return
 *   An associative array keyed by module name which contains all information
 *   about database updates that need to be run, and any updates that are not
 *   going to proceed due to missing requirements. The system module will
 *   always be listed first.
 *
 *   The subarray for each module can contain the following keys:
 *   - start: The starting update that is to be processed. If this does not
 *       exist then do not process any updates for this module as there are
 *       other requirements that need to be resolved.
 *   - warning: Any warnings about why this module can not be updated.
 *   - pending: An array of all the pending updates for the module including
 *       the update number and the description from source code comment for
 *       each update function. This array is keyed by the update number.
 */
function update_get_update_list() {
  // Make sure that the system module is first in the list of updates.
  $ret = array('system' => array());
1045

1046
1047
  $modules = drupal_get_installed_schema_version(NULL, FALSE, TRUE);
  foreach ($modules as $module => $schema_version) {
1048
1049
1050
1051
1052
    // Skip uninstalled and incompatible modules.
    if ($schema_version == SCHEMA_UNINSTALLED || update_check_incompatibility($module)) {
      continue;
    }
    // Otherwise, get the list of updates defined by this module.
1053
    $updates = drupal_get_schema_versions($module);
1054
    if ($updates !== FALSE) {
1055
1056
1057
1058
1059
1060
1061
      // module_invoke returns NULL for nonexisting hooks, so if no updates
      // are removed, it will == 0.
      $last_removed = module_invoke($module, 'update_last_removed');
      if ($schema_version < $last_removed) {
        $ret[$module]['warning'] = '<em>' . $module . '</em> 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 <em>' . $module . '</em> module, you will first <a href="http://drupal.org/upgrade">need to upgrade</a> to the last version in which these updates were available.';
        continue;
      }
1062

1063
1064
1065
      $updates = drupal_map_assoc($updates);
      foreach (array_keys($updates) as $update) {
        if ($update > $schema_version) {
1066
          // The description for an update comes from its Doxygen.
1067
          $func = new ReflectionFunction($module . '_update_' . $update);
1068
1069
1070
1071
          $description = str_replace(array("\n", '*', '/'), '', $func->getDocComment());
          $ret[$module]['pending'][$update] = "$update - $description";
          if (!isset($ret[$module]['start'])) {
            $ret[$module]['start'] = $update;
1072
1073
1074
1075
1076
1077
1078
1079
          }
        }
      }
      if (!isset($ret[$module]['start']) && isset($ret[$module]['pending'])) {
        $ret[$module]['start'] = $schema_version;
      }
    }
  }
1080

1081
1082
1083
1084
1085
1086
  if (empty($ret['system'])) {
    unset($ret['system']);
  }
  return $ret;
}

1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
/**
 * Resolves dependencies in a set of module updates, and orders them correctly.
 *
 * This function receives a list of requested module updates and determines an
 * appropriate order to run them in such that all update dependencies are met.
 * Any updates whose dependencies cannot be met are included in the returned
 * array but have the key 'allowed' set to FALSE; the calling function should
 * take responsibility for ensuring that these updates are ultimately not
 * performed.
 *
 * In addition, the returned array also includes detailed information about the
 * dependency chain for each update, as provided by the depth-first search
 * algorithm in drupal_depth_first_search().
 *
 * @param $starting_updates
 *   An array whose keys contain the names of modules with updates to be run
 *   and whose values contain the number of the first requested update for that
 *   module.
 *
 * @return
 *   An array whose keys are the names of all update functions within the
 *   provided modules that would need to be run in order to fulfill the
 *   request, arranged in the order in which the update functions should be
 *   run. (This includes the provided starting update for each module and all
 *   subsequent updates that are available.) The values are themselves arrays
 *   containing all the keys provided by the drupal_depth_first_search()
 *   algorithm, which encode detailed information about the dependency chain
 *   for this update function (for example: 'paths', 'reverse_paths', 'weight',
 *   and 'component'), as well as the following additional keys:
 *   - 'allowed': A boolean which is TRUE when the update function's
 *     dependencies are met, and FALSE otherwise. Calling functions should
 *     inspect this value before running the update.
 *   - 'missing_dependencies': An array containing the names of any other
 *     update functions that are required by this one but that are unavailable
 *     to be run. This array will be empty when 'allowed' is TRUE.
 *   - 'module': The name of the module that this update function belongs to.
 *   - 'number': The number of this update function within that module.
 *
 * @see drupal_depth_first_search()
 */
function update_resolve_dependencies($starting_updates) {
  // Obtain a dependency graph for the requested update functions.
  $update_functions = update_get_update_function_list($starting_updates);
  $graph = update_build_dependency_graph($update_functions);

  // Perform the depth-first search and sort the results.
  require_once DRUPAL_ROOT . '/includes/graph.inc';
  drupal_depth_first_search($graph);
  uasort($graph, 'drupal_sort_weight');

  foreach ($graph as $function => &$data) {
    $module = $data['module'];
    $number = $data['number'];
    // If the update function is missing and has not yet been performed, mark
    // it and everything that ultimately depends on it as disallowed.
    if (update_is_missing($module, $number, $update_functions) && !update_already_performed($module, $number)) {
      $data['allowed'] = FALSE;
      foreach (array_keys($data['paths']) as $dependent) {
        $graph[$dependent]['allowed'] = FALSE;
        $graph[$dependent]['missing_dependencies'][] = $function;
      }
    }
    elseif (!isset($data['allowed'])) {
      $data['allowed'] = TRUE;
      $data['missing_dependencies'] = array();
    }
    // Now that we have finished processing this function, remove it from the
    // graph if it was not part of the original list. This ensures that we
    // never try to run any updates that were not specifically requested.
    if (!isset($update_functions[$module][$number])) {
      unset($graph[$function]);
    }
  }

  return $graph;
}

/**
 * Returns an organized list of update functions for a set of modules.
 *
 * @param $starting_updates
 *   An array whose keys contain the names of modules and whose values contain
 *   the number of the first requested update for that module.
 *
 * @return
 *   An array containing all the update functions that should be run for each
 *   module, including the provided starting update and all subsequent updates
 *   that are available. The keys of the array contain the module names, and
 *   each value is an ordered array of update functions, keyed by the update
 *   number.
 *
 * @see update_resolve_dependencies()
 */
function update_get_update_function_list($starting_updates) {
  // Go through each module and find all updates that we need (including the
  // first update that was requested and any updates that run after it).
  $update_functions = array();
  foreach ($starting_updates as $module => $version) {
    $update_functions[$module] = array();
    $updates = drupal_get_schema_versions($module);
1187
1188
1189
1190
1191
1192
1193
    if ($updates !== FALSE) {
      $max_version = max($updates);
      if ($version <= $max_version) {
        foreach ($updates as $update) {
          if ($update >= $version) {
            $update_functions[$module][$update] = $module . '_update_' . $update;
          }
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
        }
      }
    }
  }
  return $update_functions;
}

/**
 * Constructs a graph which encodes the dependencies between module updates.
 *
 * This function returns an associative array which contains a "directed graph"
 * representation of the dependencies between a provided list of update
 * functions, as well as any outside update functions that they directly depend
 * on but that were not in the provided list. The vertices of the graph
 * represent the update functions themselves, and each edge represents a
 * requirement that the first update function needs to run before the second.
 * For example, consider this graph:
 *
 * system_update_7000 ---> system_update_7001 ---> system_update_7002
 *
 * Visually, this indicates that system_update_7000() must run before
 * system_update_7001(), which in turn must run before system_update_7002().
 *
 * The function takes into account standard dependencies within each module, as
 * shown above (i.e., the fact that each module's updates must run in numerical
 * order), but also finds any cross-module dependencies that are defined by
 * modules which implement hook_update_dependencies(), and builds them into the
 * graph as well.
 *
 * @param $update_functions
 *   An organized array of update functions, in the format returned by
 *   update_get_update_function_list().
 *
 * @return
 *   A multidimensional array representing the dependency graph, suitable for
 *   passing in to drupal_depth_first_search(), but with extra information
 *   about each update function also included. Each array key contains the name
 *   of an update function, including all update functions from the provided
 *   list as well as any outside update functions which they directly depend
 *   on. Each value is an associative array containing the following keys:
 *   - 'edges': A representation of any other update functions that immediately
 *     depend on this one. See drupal_depth_first_search() for more details on
 *     the format.
 *   - 'module': The name of the module that this update function belongs to.
 *   - 'number': The number of this update function within that module.
 *
 * @see drupal_depth_first_search()
 * @see update_resolve_dependencies()
 */
function update_build_dependency_graph($update_functions) {
  // Initialize an array that will define a directed graph representing the
  // dependencies between update functions.
  $graph = array();

  // Go through each update function and build an initial list of dependencies.
  foreach ($update_functions as $module => $functions) {
    $previous_function = NULL;
    foreach ($functions as $number => $function) {
      // Add an edge to the directed graph representing the fact that each
      // update function in a given module must run after the update that
      // numerically precedes it.
      if ($previous_function) {
        $graph[$previous_function]['edges'][$function] = TRUE;
      }
      $previous_function = $function;

      // Define the module and update number associated with this function.
      $graph[$function]['module'] = $module;
      $graph[$function]['number'] = $number;
    }
  }

  // Now add any explicit update dependencies declared by modules.
1267
  $update_dependencies = update_retrieve_dependencies();
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
  foreach ($graph as $function => $data) {
    if (!empty($update_dependencies[$data['module']][$data['number']])) {
      foreach ($update_dependencies[$data['module']][$data['number']] as $module => $number) {
        $dependency = $module . '_update_' . $number;
        $graph[$dependency]['edges'][$function] = TRUE;
        $graph[$dependency]['module'] = $module;
        $graph[$dependency]['number'] = $number;
      }
    }
  }

  return $graph;
}

/**
 * Determines if a module update is missing or unavailable.
 *
 * @param $module
 *   The name of the module.
 * @param $number
 *   The number of the update within that module.
 * @param $update_functions
 *   An organized array of update functions, in the format returned by
 *   update_get_update_function_list(). This should represent all module
 *   updates that are requested to run at the time this function is called.
 *
 * @return
 *   TRUE if the provided module update is not installed or is not in the
 *   provided list of updates to run; FALSE otherwise.
 */
function update_is_missing($module, $number, $update_functions) {
  return !isset($update_functions[$module][$number]) || !function_exists($update_functions[$module][$number]);
}

/**
 * Determines if a module update has already been performed.
 *
 * @param $module
 *   The name of the module.
 * @param $number
 *   The number of the update within that module.
 *
 * @return
 *   TRUE if the database schema indicates that the update has already been
 *   performed; FALSE otherwise.
 */
function update_already_performed($module, $number) {
  return $number <= drupal_get_installed_schema_version($module);
}

/**
1319
 * Invoke hook_update_dependencies() in all installed modules.
1320
 *
1321
1322
1323
1324
 * This function is similar to module_invoke_all(), with the main difference
 * that it does not require that a module be enabled to invoke its hook, only
 * that it be installed. This allows the update system to properly perform
 * updates even on modules that are currently disabled.
1325
1326
 *
 * @return
1327
1328
 *   An array of return values obtained by merging the results of the
 *   hook_update_dependencies() implementations in all installed modules.
1329
1330
 *
 * @see module_invoke_all()
1331
 * @see hook_update_dependencies()
1332
 */
1333
function update_retrieve_dependencies() {
1334
  $return = array();
1335
1336
1337
  // Get a list of installed modules, arranged so that we invoke their hooks in
  // the same order that module_invoke_all() does.
  $modules = db_query("SELECT name FROM {system} WHERE type = 'module' AND schema_version != :schema ORDER BY weight ASC, name ASC", array(':schema' => SCHEMA_UNINSTALLED))->fetchCol();
1338
  foreach ($modules as $module) {
1339
    $function = $module . '_update_dependencies';
1340
    if (function_exists($function)) {
1341
1342
1343
1344
1345
1346
1347
1348
1349
      $result = $function();
      // Each implementation of hook_update_dependencies() returns a
      // multidimensional, associative array containing some keys that
      // represent module names (which are strings) and other keys that
      // represent update function numbers (which are integers). We cannot use
      // array_merge_recursive() to properly merge these results, since it
      // treats strings and integers differently. Therefore, we have to
      // explicitly loop through the expected array structure here and perform
      // the merge manually.
1350
      if (isset($result) && is_array($result)) {
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
        foreach ($result as $module => $module_data) {
          foreach ($module_data as $update => $update_data) {
            foreach ($update_data as $module_dependency => $update_dependency) {
              // If there are redundant dependencies declared for the same
              // update function (so that it is declared to depend on more than
              // one update from a particular module), record the dependency on
              // the highest numbered update here, since that automatically
              // implies the previous ones. For example, if one module's
              // implementation of hook_update_dependencies() required this
              // ordering:
              //
              // system_update_7001 ---> user_update_7000
              //
              // but another module's implementation of the hook required this
              // one:
              //
              // system_update_7002 ---> user_update_7000
              //
              // we record the second one, since system_update_7001() is always
              // guaranteed to run before system_update_7002() anyway (within
              // an individual module, updates are always run in numerical
              // order).
              if (!isset($return[$module][$update][$module_dependency]) || $update_dependency > $return[$module][$update][$module_dependency]) {
                $return[$module][$update][$module_dependency] = $update_dependency;
              }
            }
          }
        }
1379
1380
1381
1382
1383
1384
1385
      }
    }
  }

  return $return;
}

1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
/**
 * @defgroup update-api-6.x-to-7.x Update versions of API functions.
 * @{
 * Functions similar to normal API function but not firing hooks.
 *
 * During update, it is impossible to judge the consequences of firing a hook
 * as it might hit a module not yet updated. So simplified versions of some
 * core APIs are provided.
 */

/**
 * @} End of "defgroup update-api-6.x-to-7.x"
 */