|
1 <?php |
|
2 |
|
3 /** |
|
4 * @file |
|
5 * Drupal database update API. |
|
6 * |
|
7 * This file contains functions to perform database updates for a Drupal |
|
8 * installation. It is included and used extensively by update.php. |
|
9 */ |
|
10 |
|
11 /** |
|
12 * Minimum schema version of Drupal 6 required for upgrade to Drupal 7. |
|
13 * |
|
14 * Upgrades from Drupal 6 to Drupal 7 require that Drupal 6 be running |
|
15 * the most recent version, or the upgrade could fail. We can't easily |
|
16 * check the Drupal 6 version once the update process has begun, so instead |
|
17 * we check the schema version of system.module in the system table. |
|
18 */ |
|
19 define('REQUIRED_D6_SCHEMA_VERSION', '6055'); |
|
20 |
|
21 /** |
|
22 * Disable any items in the {system} table that are not core compatible. |
|
23 */ |
|
24 function update_fix_compatibility() { |
|
25 $incompatible = array(); |
|
26 $result = db_query("SELECT name, type, status FROM {system} WHERE status = 1 AND type IN ('module','theme')"); |
|
27 foreach ($result as $row) { |
|
28 if (update_check_incompatibility($row->name, $row->type)) { |
|
29 $incompatible[] = $row->name; |
|
30 } |
|
31 } |
|
32 if (!empty($incompatible)) { |
|
33 db_update('system') |
|
34 ->fields(array('status' => 0)) |
|
35 ->condition('name', $incompatible, 'IN') |
|
36 ->execute(); |
|
37 } |
|
38 } |
|
39 |
|
40 /** |
|
41 * Tests the compatibility of a module or theme. |
|
42 */ |
|
43 function update_check_incompatibility($name, $type = 'module') { |
|
44 static $themes, $modules; |
|
45 |
|
46 // Store values of expensive functions for future use. |
|
47 if (empty($themes) || empty($modules)) { |
|
48 // We need to do a full rebuild here to make sure the database reflects any |
|
49 // code changes that were made in the filesystem before the update script |
|
50 // was initiated. |
|
51 $themes = system_rebuild_theme_data(); |
|
52 $modules = system_rebuild_module_data(); |
|
53 } |
|
54 |
|
55 if ($type == 'module' && isset($modules[$name])) { |
|
56 $file = $modules[$name]; |
|
57 } |
|
58 elseif ($type == 'theme' && isset($themes[$name])) { |
|
59 $file = $themes[$name]; |
|
60 } |
|
61 if (!isset($file) |
|
62 || !isset($file->info['core']) |
|
63 || $file->info['core'] != DRUPAL_CORE_COMPATIBILITY |
|
64 || version_compare(phpversion(), $file->info['php']) < 0) { |
|
65 return TRUE; |
|
66 } |
|
67 return FALSE; |
|
68 } |
|
69 |
|
70 /** |
|
71 * Performs extra steps required to bootstrap when using a Drupal 6 database. |
|
72 * |
|
73 * Users who still have a Drupal 6 database (and are in the process of |
|
74 * updating to Drupal 7) need extra help before a full bootstrap can be |
|
75 * achieved. This function does the necessary preliminary work that allows |
|
76 * the bootstrap to be successful. |
|
77 * |
|
78 * No access check has been performed when this function is called, so no |
|
79 * irreversible changes to the database are made here. |
|
80 */ |
|
81 function update_prepare_d7_bootstrap() { |
|
82 // Allow the bootstrap to proceed even if a Drupal 6 settings.php file is |
|
83 // still being used. |
|
84 include_once DRUPAL_ROOT . '/includes/install.inc'; |
|
85 drupal_bootstrap(DRUPAL_BOOTSTRAP_CONFIGURATION); |
|
86 global $databases, $db_url, $db_prefix, $update_rewrite_settings; |
|
87 if (empty($databases) && !empty($db_url)) { |
|
88 $databases = update_parse_db_url($db_url, $db_prefix); |
|
89 // Record the fact that the settings.php file will need to be rewritten. |
|
90 $update_rewrite_settings = TRUE; |
|
91 $settings_file = conf_path() . '/settings.php'; |
|
92 $writable = drupal_verify_install_file($settings_file, FILE_EXIST|FILE_READABLE|FILE_WRITABLE); |
|
93 $requirements = array( |
|
94 'settings file' => array( |
|
95 'title' => 'Settings file', |
|
96 'value' => $writable ? 'The settings file is writable.' : 'The settings file is not writable.', |
|
97 'severity' => $writable ? REQUIREMENT_OK : REQUIREMENT_ERROR, |
|
98 '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>.', |
|
99 ), |
|
100 ); |
|
101 update_extra_requirements($requirements); |
|
102 } |
|
103 |
|
104 // The new {blocked_ips} table is used in Drupal 7 to store a list of |
|
105 // banned IP addresses. If this table doesn't exist then we are still |
|
106 // running on a Drupal 6 database, so we suppress the unavoidable errors |
|
107 // that occur by creating a static list. |
|
108 $GLOBALS['conf']['blocked_ips'] = array(); |
|
109 |
|
110 // Check that PDO is available and that the correct PDO database driver is |
|
111 // loaded. Bootstrapping to DRUPAL_BOOTSTRAP_DATABASE will result in a fatal |
|
112 // error otherwise. |
|
113 $message = ''; |
|
114 $pdo_link = 'http://drupal.org/requirements/pdo'; |
|
115 // Check that PDO is loaded. |
|
116 if (!extension_loaded('pdo')) { |
|
117 $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>'; |
|
118 } |
|
119 // The PDO::ATTR_DEFAULT_FETCH_MODE constant is not available in the PECL |
|
120 // version of PDO. |
|
121 elseif (!defined('PDO::ATTR_DEFAULT_FETCH_MODE')) { |
|
122 $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.'; |
|
123 $pdo_link = 'http://drupal.org/requirements/pdo#pecl'; |
|
124 } |
|
125 // Check that the correct driver is loaded for the database being updated. |
|
126 // If we have no driver information (for example, if someone tried to create |
|
127 // the Drupal 7 $databases array themselves but did not do it correctly), |
|
128 // this message will be confusing, so do not perform the check; instead, just |
|
129 // let the database connection fail in the code that follows. |
|
130 elseif (isset($databases['default']['default']['driver']) && !in_array($databases['default']['default']['driver'], PDO::getAvailableDrivers())) { |
|
131 $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>'; |
|
132 } |
|
133 if ($message) { |
|
134 print $message . '<p>See the <a href="' . $pdo_link . '">system requirements page</a> for more information.</p>'; |
|
135 exit(); |
|
136 } |
|
137 |
|
138 // Allow the database system to work even if the registry has not been |
|
139 // created yet. |
|
140 drupal_bootstrap(DRUPAL_BOOTSTRAP_DATABASE); |
|
141 |
|
142 // If the site has not updated to Drupal 7 yet, check to make sure that it is |
|
143 // running an up-to-date version of Drupal 6 before proceeding. Note this has |
|
144 // to happen AFTER the database bootstraps because of |
|
145 // drupal_get_installed_schema_version(). |
|
146 $system_schema = drupal_get_installed_schema_version('system'); |
|
147 if ($system_schema < 7000) { |
|
148 $has_required_schema = $system_schema >= REQUIRED_D6_SCHEMA_VERSION; |
|
149 $requirements = array( |
|
150 'drupal 6 version' => array( |
|
151 'title' => 'Drupal 6 version', |
|
152 'value' => $has_required_schema ? 'You are running a current version of Drupal 6.' : 'You are not running a current version of Drupal 6', |
|
153 'severity' => $has_required_schema ? REQUIREMENT_OK : REQUIREMENT_ERROR, |
|
154 'description' => $has_required_schema ? '' : 'Please update your Drupal 6 installation to the most recent version before attempting to upgrade to Drupal 7', |
|
155 ), |
|
156 ); |
|
157 |
|
158 // Make sure that the database environment is properly set up. |
|
159 try { |
|
160 db_run_tasks(db_driver()); |
|
161 } |
|
162 catch (DatabaseTaskException $e) { |
|
163 $requirements['database tasks'] = array( |
|
164 'title' => 'Database environment', |
|
165 'value' => 'There is a problem with your database environment', |
|
166 'severity' => REQUIREMENT_ERROR, |
|
167 'description' => $e->getMessage(), |
|
168 ); |
|
169 } |
|
170 |
|
171 update_extra_requirements($requirements); |
|
172 |
|
173 // Allow a D6 session to work, since the upgrade has not been performed yet. |
|
174 $d6_session_name = update_get_d6_session_name(); |
|
175 if (!empty($_COOKIE[$d6_session_name])) { |
|
176 // Set the current sid to the one found in the D6 cookie. |
|
177 $sid = $_COOKIE[$d6_session_name]; |
|
178 $_COOKIE[session_name()] = $sid; |
|
179 session_id($sid); |
|
180 } |
|
181 |
|
182 // Upgrading from D6 to D7.{0,1,2,3,4,8,...} is different than upgrading |
|
183 // from D6 to D7.{5,6,7} which should be considered broken. To be able to |
|
184 // properly handle this difference in node_update_7012 we need to keep track |
|
185 // of whether a D6 > D7 upgrade or a D7 > D7 update is running. |
|
186 // Since variable_set() is not available here, the D6 status is being saved |
|
187 // in a local variable to be able to store it later. |
|
188 $update_d6 = TRUE; |
|
189 } |
|
190 |
|
191 // Create the registry tables. |
|
192 if (!db_table_exists('registry')) { |
|
193 $schema['registry'] = array( |
|
194 'fields' => array( |
|
195 'name' => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''), |
|
196 'type' => array('type' => 'varchar', 'length' => 9, 'not null' => TRUE, 'default' => ''), |
|
197 'filename' => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''), |
|
198 'module' => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''), |
|
199 'weight' => array('type' => 'int', 'not null' => TRUE, 'default' => 0), |
|
200 ), |
|
201 'primary key' => array('name', 'type'), |
|
202 'indexes' => array( |
|
203 'hook' => array('type', 'weight', 'module'), |
|
204 ), |
|
205 ); |
|
206 db_create_table('registry', $schema['registry']); |
|
207 } |
|
208 if (!db_table_exists('registry_file')) { |
|
209 $schema['registry_file'] = array( |
|
210 'fields' => array( |
|
211 'filename' => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE), |
|
212 'hash' => array('type' => 'varchar', 'length' => 64, 'not null' => TRUE), |
|
213 ), |
|
214 'primary key' => array('filename'), |
|
215 ); |
|
216 db_create_table('registry_file', $schema['registry_file']); |
|
217 } |
|
218 |
|
219 // Older versions of Drupal 6 do not include the semaphore table, which is |
|
220 // required to bootstrap, so we add it now so that we can bootstrap and |
|
221 // provide a reasonable error message. |
|
222 if (!db_table_exists('semaphore')) { |
|
223 $semaphore = array( |
|
224 'description' => 'Table for holding semaphores, locks, flags, etc. that cannot be stored as Drupal variables since they must not be cached.', |
|
225 'fields' => array( |
|
226 'name' => array( |
|
227 'description' => 'Primary Key: Unique name.', |
|
228 'type' => 'varchar', |
|
229 'length' => 255, |
|
230 'not null' => TRUE, |
|
231 'default' => '' |
|
232 ), |
|
233 'value' => array( |
|
234 'description' => 'A value for the semaphore.', |
|
235 'type' => 'varchar', |
|
236 'length' => 255, |
|
237 'not null' => TRUE, |
|
238 'default' => '' |
|
239 ), |
|
240 'expire' => array( |
|
241 'description' => 'A Unix timestamp with microseconds indicating when the semaphore should expire.', |
|
242 'type' => 'float', |
|
243 'size' => 'big', |
|
244 'not null' => TRUE |
|
245 ), |
|
246 ), |
|
247 'indexes' => array( |
|
248 'value' => array('value'), |
|
249 'expire' => array('expire'), |
|
250 ), |
|
251 'primary key' => array('name'), |
|
252 ); |
|
253 db_create_table('semaphore', $semaphore); |
|
254 } |
|
255 |
|
256 // The new cache_bootstrap bin is required to bootstrap to |
|
257 // DRUPAL_BOOTSTRAP_SESSION, so create it here rather than in |
|
258 // update_fix_d7_requirements(). |
|
259 if (!db_table_exists('cache_bootstrap')) { |
|
260 $cache_bootstrap = array( |
|
261 'description' => 'Cache table for data required to bootstrap Drupal, may be routed to a shared memory cache.', |
|
262 'fields' => array( |
|
263 'cid' => array( |
|
264 'description' => 'Primary Key: Unique cache ID.', |
|
265 'type' => 'varchar', |
|
266 'length' => 255, |
|
267 'not null' => TRUE, |
|
268 'default' => '', |
|
269 ), |
|
270 'data' => array( |
|
271 'description' => 'A collection of data to cache.', |
|
272 'type' => 'blob', |
|
273 'not null' => FALSE, |
|
274 'size' => 'big', |
|
275 ), |
|
276 'expire' => array( |
|
277 'description' => 'A Unix timestamp indicating when the cache entry should expire, or 0 for never.', |
|
278 'type' => 'int', |
|
279 'not null' => TRUE, |
|
280 'default' => 0, |
|
281 ), |
|
282 'created' => array( |
|
283 'description' => 'A Unix timestamp indicating when the cache entry was created.', |
|
284 'type' => 'int', |
|
285 'not null' => TRUE, |
|
286 'default' => 0, |
|
287 ), |
|
288 'serialized' => array( |
|
289 'description' => 'A flag to indicate whether content is serialized (1) or not (0).', |
|
290 'type' => 'int', |
|
291 'size' => 'small', |
|
292 'not null' => TRUE, |
|
293 'default' => 0, |
|
294 ), |
|
295 ), |
|
296 'indexes' => array( |
|
297 'expire' => array('expire'), |
|
298 ), |
|
299 'primary key' => array('cid'), |
|
300 ); |
|
301 db_create_table('cache_bootstrap', $cache_bootstrap); |
|
302 } |
|
303 |
|
304 // Set a valid timezone for 6 -> 7 upgrade process. |
|
305 drupal_bootstrap(DRUPAL_BOOTSTRAP_VARIABLES); |
|
306 $timezone_offset = variable_get('date_default_timezone', 0); |
|
307 if (is_numeric($timezone_offset)) { |
|
308 // Save the original offset. |
|
309 variable_set('date_temporary_timezone', $timezone_offset); |
|
310 // Set the timezone for this request only. |
|
311 $GLOBALS['conf']['date_default_timezone'] = 'UTC'; |
|
312 } |
|
313 |
|
314 // This allows update functions to tell if an upgrade from D6 is running. |
|
315 if (!empty($update_d6)) { |
|
316 variable_set('update_d6', TRUE); |
|
317 } |
|
318 } |
|
319 |
|
320 /** |
|
321 * A helper function that modules can use to assist with the transformation |
|
322 * from numeric block deltas to string block deltas during the 6.x -> 7.x |
|
323 * upgrade. |
|
324 * |
|
325 * @todo This function should be removed in 8.x. |
|
326 * |
|
327 * @param $sandbox |
|
328 * An array holding data for the batch process. |
|
329 * @param $renamed_deltas |
|
330 * An associative array. Keys are module names, values an associative array |
|
331 * mapping the old block deltas to the new block deltas for the module. |
|
332 * Example: |
|
333 * @code |
|
334 * $renamed_deltas = array( |
|
335 * 'mymodule' => |
|
336 * array( |
|
337 * 0 => 'mymodule-block-1', |
|
338 * 1 => 'mymodule-block-2', |
|
339 * ), |
|
340 * ); |
|
341 * @endcode |
|
342 * @param $moved_deltas |
|
343 * An associative array. Keys are source module names, values an associative |
|
344 * array mapping the (possibly renamed) block name to the new module name. |
|
345 * Example: |
|
346 * @code |
|
347 * $moved_deltas = array( |
|
348 * 'user' => |
|
349 * array( |
|
350 * 'navigation' => 'system', |
|
351 * ), |
|
352 * ); |
|
353 * @endcode |
|
354 */ |
|
355 function update_fix_d7_block_deltas(&$sandbox, $renamed_deltas, $moved_deltas) { |
|
356 // Loop through each block and make changes to the block tables. |
|
357 // Only run this the first time through the batch update. |
|
358 if (!isset($sandbox['progress'])) { |
|
359 // Determine whether to use the old or new block table names. |
|
360 $block_tables = db_table_exists('blocks') ? array('blocks', 'blocks_roles') : array('block', 'block_role'); |
|
361 foreach ($block_tables as $table) { |
|
362 foreach ($renamed_deltas as $module => $deltas) { |
|
363 foreach ($deltas as $old_delta => $new_delta) { |
|
364 // Only do the update if the old block actually exists. |
|
365 $block_exists = db_query("SELECT COUNT(*) FROM {" . $table . "} WHERE module = :module AND delta = :delta", array( |
|
366 ':module' => $module, |
|
367 ':delta' => $old_delta, |
|
368 )) |
|
369 ->fetchField(); |
|
370 if ($block_exists) { |
|
371 // Delete any existing blocks with the new module+delta. |
|
372 db_delete($table) |
|
373 ->condition('module', $module) |
|
374 ->condition('delta', $new_delta) |
|
375 ->execute(); |
|
376 // Rename the old block to the new module+delta. |
|
377 db_update($table) |
|
378 ->fields(array('delta' => $new_delta)) |
|
379 ->condition('module', $module) |
|
380 ->condition('delta', $old_delta) |
|
381 ->execute(); |
|
382 } |
|
383 } |
|
384 } |
|
385 foreach ($moved_deltas as $old_module => $deltas) { |
|
386 foreach ($deltas as $delta => $new_module) { |
|
387 // Only do the update if the old block actually exists. |
|
388 $block_exists = db_query("SELECT COUNT(*) FROM {" . $table . "} WHERE module = :module AND delta = :delta", array( |
|
389 ':module' => $old_module, |
|
390 ':delta' => $delta, |
|
391 )) |
|
392 ->fetchField(); |
|
393 if ($block_exists) { |
|
394 // Delete any existing blocks with the new module+delta. |
|
395 db_delete($table) |
|
396 ->condition('module', $new_module) |
|
397 ->condition('delta', $delta) |
|
398 ->execute(); |
|
399 // Rename the old block to the new module+delta. |
|
400 db_update($table) |
|
401 ->fields(array('module' => $new_module)) |
|
402 ->condition('module', $old_module) |
|
403 ->condition('delta', $delta) |
|
404 ->execute(); |
|
405 } |
|
406 } |
|
407 } |
|
408 } |
|
409 |
|
410 // Initialize batch update information. |
|
411 $sandbox['progress'] = 0; |
|
412 $sandbox['last_user_processed'] = -1; |
|
413 $sandbox['max'] = db_query("SELECT COUNT(*) FROM {users} WHERE data LIKE :block", array( |
|
414 ':block' => '%' . db_like(serialize('block')) . '%', |
|
415 )) |
|
416 ->fetchField(); |
|
417 } |
|
418 // Now do the batch update of the user-specific block visibility settings. |
|
419 $limit = 100; |
|
420 $result = db_select('users', 'u') |
|
421 ->fields('u', array('uid', 'data')) |
|
422 ->condition('uid', $sandbox['last_user_processed'], '>') |
|
423 ->condition('data', '%' . db_like(serialize('block')) . '%', 'LIKE') |
|
424 ->orderBy('uid', 'ASC') |
|
425 ->range(0, $limit) |
|
426 ->execute(); |
|
427 foreach ($result as $row) { |
|
428 $data = unserialize($row->data); |
|
429 $user_needs_update = FALSE; |
|
430 foreach ($renamed_deltas as $module => $deltas) { |
|
431 foreach ($deltas as $old_delta => $new_delta) { |
|
432 if (isset($data['block'][$module][$old_delta])) { |
|
433 // Transfer the old block visibility settings to the newly-renamed |
|
434 // block, and mark this user for a database update. |
|
435 $data['block'][$module][$new_delta] = $data['block'][$module][$old_delta]; |
|
436 unset($data['block'][$module][$old_delta]); |
|
437 $user_needs_update = TRUE; |
|
438 } |
|
439 } |
|
440 } |
|
441 foreach ($moved_deltas as $old_module => $deltas) { |
|
442 foreach ($deltas as $delta => $new_module) { |
|
443 if (isset($data['block'][$old_module][$delta])) { |
|
444 // Transfer the old block visibility settings to the moved |
|
445 // block, and mark this user for a database update. |
|
446 $data['block'][$new_module][$delta] = $data['block'][$old_module][$delta]; |
|
447 unset($data['block'][$old_module][$delta]); |
|
448 $user_needs_update = TRUE; |
|
449 } |
|
450 } |
|
451 } |
|
452 // Update the current user. |
|
453 if ($user_needs_update) { |
|
454 db_update('users') |
|
455 ->fields(array('data' => serialize($data))) |
|
456 ->condition('uid', $row->uid) |
|
457 ->execute(); |
|
458 } |
|
459 // Update our progress information for the batch update. |
|
460 $sandbox['progress']++; |
|
461 $sandbox['last_user_processed'] = $row->uid; |
|
462 } |
|
463 // Indicate our current progress to the batch update system. |
|
464 if ($sandbox['progress'] < $sandbox['max']) { |
|
465 $sandbox['#finished'] = $sandbox['progress'] / $sandbox['max']; |
|
466 } |
|
467 } |
|
468 |
|
469 /** |
|
470 * Perform Drupal 6.x to 7.x updates that are required for update.php |
|
471 * to function properly. |
|
472 * |
|
473 * This function runs when update.php is run the first time for 7.x, |
|
474 * even before updates are selected or performed. It is important |
|
475 * that if updates are not ultimately performed that no changes are |
|
476 * made which make it impossible to continue using the prior version. |
|
477 */ |
|
478 function update_fix_d7_requirements() { |
|
479 global $conf; |
|
480 |
|
481 // Rewrite the settings.php file if necessary, see |
|
482 // update_prepare_d7_bootstrap(). |
|
483 global $update_rewrite_settings, $db_url, $db_prefix; |
|
484 if (!empty($update_rewrite_settings)) { |
|
485 $databases = update_parse_db_url($db_url, $db_prefix); |
|
486 $salt = drupal_hash_base64(drupal_random_bytes(55)); |
|
487 file_put_contents(conf_path() . '/settings.php', "\n" . '$databases = ' . var_export($databases, TRUE) . ";\n\$drupal_hash_salt = '$salt';", FILE_APPEND); |
|
488 } |
|
489 if (drupal_get_installed_schema_version('system') < 7000 && !variable_get('update_d7_requirements', FALSE)) { |
|
490 // Change 6.x system table field values to 7.x equivalent. |
|
491 // Change field values. |
|
492 db_change_field('system', 'schema_version', 'schema_version', array( |
|
493 'type' => 'int', |
|
494 'size' => 'small', |
|
495 'not null' => TRUE, |
|
496 'default' => -1) |
|
497 ); |
|
498 db_change_field('system', 'status', 'status', array( |
|
499 'type' => 'int', 'not null' => TRUE, 'default' => 0)); |
|
500 db_change_field('system', 'weight', 'weight', array( |
|
501 'type' => 'int', 'not null' => TRUE, 'default' => 0)); |
|
502 db_change_field('system', 'bootstrap', 'bootstrap', array( |
|
503 'type' => 'int', 'not null' => TRUE, 'default' => 0)); |
|
504 // Drop and recreate 6.x indexes. |
|
505 db_drop_index('system', 'bootstrap'); |
|
506 db_add_index('system', 'bootstrap', array( |
|
507 'status', 'bootstrap', array('type', 12), 'weight', 'name')); |
|
508 |
|
509 db_drop_index('system' ,'modules'); |
|
510 db_add_index('system', 'modules', array(array( |
|
511 'type', 12), 'status', 'weight', 'name')); |
|
512 |
|
513 db_drop_index('system', 'type_name'); |
|
514 db_add_index('system', 'type_name', array(array('type', 12), 'name')); |
|
515 // Add 7.x indexes. |
|
516 db_add_index('system', 'system_list', array('weight', 'name')); |
|
517 |
|
518 // Add the cache_path table. |
|
519 if (db_table_exists('cache_path')) { |
|
520 db_drop_table('cache_path'); |
|
521 } |
|
522 require_once('./modules/system/system.install'); |
|
523 $schema['cache_path'] = system_schema_cache_7054(); |
|
524 $schema['cache_path']['description'] = 'Cache table used for path alias lookups.'; |
|
525 db_create_table('cache_path', $schema['cache_path']); |
|
526 |
|
527 // system_update_7042() renames columns, but these are needed to bootstrap. |
|
528 // Add empty columns for now. |
|
529 db_add_field('url_alias', 'source', array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => '')); |
|
530 db_add_field('url_alias', 'alias', array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => '')); |
|
531 |
|
532 // Add new columns to {menu_router}. |
|
533 db_add_field('menu_router', 'delivery_callback', array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => '')); |
|
534 db_add_field('menu_router', 'context', array( |
|
535 'description' => 'Only for local tasks (tabs) - the context of a local task to control its placement.', |
|
536 'type' => 'int', |
|
537 'not null' => TRUE, |
|
538 'default' => 0, |
|
539 )); |
|
540 db_drop_index('menu_router', 'tab_parent'); |
|
541 db_add_index('menu_router', 'tab_parent', array(array('tab_parent', 64), 'weight', 'title')); |
|
542 db_add_field('menu_router', 'theme_callback', array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => '')); |
|
543 db_add_field('menu_router', 'theme_arguments', array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => '')); |
|
544 |
|
545 // Add the role_permission table. |
|
546 $schema['role_permission'] = array( |
|
547 'fields' => array( |
|
548 'rid' => array( |
|
549 'type' => 'int', |
|
550 'unsigned' => TRUE, |
|
551 'not null' => TRUE, |
|
552 ), |
|
553 'permission' => array( |
|
554 'type' => 'varchar', |
|
555 'length' => 128, |
|
556 'not null' => TRUE, |
|
557 'default' => '', |
|
558 ), |
|
559 ), |
|
560 'primary key' => array('rid', 'permission'), |
|
561 'indexes' => array( |
|
562 'permission' => array('permission'), |
|
563 ), |
|
564 ); |
|
565 db_create_table('role_permission', $schema['role_permission']); |
|
566 |
|
567 // Drops and recreates semaphore value index. |
|
568 db_drop_index('semaphore', 'value'); |
|
569 db_add_index('semaphore', 'value', array('value')); |
|
570 |
|
571 $schema['date_format_type'] = array( |
|
572 'description' => 'Stores configured date format types.', |
|
573 'fields' => array( |
|
574 'type' => array( |
|
575 'description' => 'The date format type, e.g. medium.', |
|
576 'type' => 'varchar', |
|
577 'length' => 64, |
|
578 'not null' => TRUE, |
|
579 ), |
|
580 'title' => array( |
|
581 'description' => 'The human readable name of the format type.', |
|
582 'type' => 'varchar', |
|
583 'length' => 255, |
|
584 'not null' => TRUE, |
|
585 ), |
|
586 'locked' => array( |
|
587 'description' => 'Whether or not this is a system provided format.', |
|
588 'type' => 'int', |
|
589 'size' => 'tiny', |
|
590 'default' => 0, |
|
591 'not null' => TRUE, |
|
592 ), |
|
593 ), |
|
594 'primary key' => array('type'), |
|
595 ); |
|
596 |
|
597 $schema['date_formats'] = array( |
|
598 'description' => 'Stores configured date formats.', |
|
599 'fields' => array( |
|
600 'dfid' => array( |
|
601 'description' => 'The date format identifier.', |
|
602 'type' => 'serial', |
|
603 'not null' => TRUE, |
|
604 'unsigned' => TRUE, |
|
605 ), |
|
606 'format' => array( |
|
607 'description' => 'The date format string.', |
|
608 'type' => 'varchar', |
|
609 'length' => 100, |
|
610 'not null' => TRUE, |
|
611 ), |
|
612 'type' => array( |
|
613 'description' => 'The date format type, e.g. medium.', |
|
614 'type' => 'varchar', |
|
615 'length' => 64, |
|
616 'not null' => TRUE, |
|
617 ), |
|
618 'locked' => array( |
|
619 'description' => 'Whether or not this format can be modified.', |
|
620 'type' => 'int', |
|
621 'size' => 'tiny', |
|
622 'default' => 0, |
|
623 'not null' => TRUE, |
|
624 ), |
|
625 ), |
|
626 'primary key' => array('dfid'), |
|
627 'unique keys' => array('formats' => array('format', 'type')), |
|
628 ); |
|
629 |
|
630 $schema['date_format_locale'] = array( |
|
631 'description' => 'Stores configured date formats for each locale.', |
|
632 'fields' => array( |
|
633 'format' => array( |
|
634 'description' => 'The date format string.', |
|
635 'type' => 'varchar', |
|
636 'length' => 100, |
|
637 'not null' => TRUE, |
|
638 ), |
|
639 'type' => array( |
|
640 'description' => 'The date format type, e.g. medium.', |
|
641 'type' => 'varchar', |
|
642 'length' => 64, |
|
643 'not null' => TRUE, |
|
644 ), |
|
645 'language' => array( |
|
646 'description' => 'A {languages}.language for this format to be used with.', |
|
647 'type' => 'varchar', |
|
648 'length' => 12, |
|
649 'not null' => TRUE, |
|
650 ), |
|
651 ), |
|
652 'primary key' => array('type', 'language'), |
|
653 ); |
|
654 |
|
655 db_create_table('date_format_type', $schema['date_format_type']); |
|
656 // Sites that have the Drupal 6 Date module installed already have the |
|
657 // following tables. |
|
658 if (db_table_exists('date_formats')) { |
|
659 db_rename_table('date_formats', 'd6_date_formats'); |
|
660 } |
|
661 db_create_table('date_formats', $schema['date_formats']); |
|
662 if (db_table_exists('date_format_locale')) { |
|
663 db_rename_table('date_format_locale', 'd6_date_format_locale'); |
|
664 } |
|
665 db_create_table('date_format_locale', $schema['date_format_locale']); |
|
666 |
|
667 // Add the queue table. |
|
668 $schema['queue'] = array( |
|
669 'description' => 'Stores items in queues.', |
|
670 'fields' => array( |
|
671 'item_id' => array( |
|
672 'type' => 'serial', |
|
673 'unsigned' => TRUE, |
|
674 'not null' => TRUE, |
|
675 'description' => 'Primary Key: Unique item ID.', |
|
676 ), |
|
677 'name' => array( |
|
678 'type' => 'varchar', |
|
679 'length' => 255, |
|
680 'not null' => TRUE, |
|
681 'default' => '', |
|
682 'description' => 'The queue name.', |
|
683 ), |
|
684 'data' => array( |
|
685 'type' => 'blob', |
|
686 'not null' => FALSE, |
|
687 'size' => 'big', |
|
688 'serialize' => TRUE, |
|
689 'description' => 'The arbitrary data for the item.', |
|
690 ), |
|
691 'expire' => array( |
|
692 'type' => 'int', |
|
693 'not null' => TRUE, |
|
694 'default' => 0, |
|
695 'description' => 'Timestamp when the claim lease expires on the item.', |
|
696 ), |
|
697 'created' => array( |
|
698 'type' => 'int', |
|
699 'not null' => TRUE, |
|
700 'default' => 0, |
|
701 'description' => 'Timestamp when the item was created.', |
|
702 ), |
|
703 ), |
|
704 'primary key' => array('item_id'), |
|
705 'indexes' => array( |
|
706 'name_created' => array('name', 'created'), |
|
707 'expire' => array('expire'), |
|
708 ), |
|
709 ); |
|
710 // Check for queue table that may remain from D5 or D6, if found |
|
711 //drop it. |
|
712 if (db_table_exists('queue')) { |
|
713 db_drop_table('queue'); |
|
714 } |
|
715 |
|
716 db_create_table('queue', $schema['queue']); |
|
717 |
|
718 // Create the sequences table. |
|
719 $schema['sequences'] = array( |
|
720 'description' => 'Stores IDs.', |
|
721 'fields' => array( |
|
722 'value' => array( |
|
723 'description' => 'The value of the sequence.', |
|
724 'type' => 'serial', |
|
725 'unsigned' => TRUE, |
|
726 'not null' => TRUE, |
|
727 ), |
|
728 ), |
|
729 'primary key' => array('value'), |
|
730 ); |
|
731 // Check for sequences table that may remain from D5 or D6, if found |
|
732 //drop it. |
|
733 if (db_table_exists('sequences')) { |
|
734 db_drop_table('sequences'); |
|
735 } |
|
736 db_create_table('sequences', $schema['sequences']); |
|
737 // Initialize the table with the maximum current increment of the tables |
|
738 // that will rely on it for their ids. |
|
739 $max_aid = db_query('SELECT MAX(aid) FROM {actions_aid}')->fetchField(); |
|
740 $max_uid = db_query('SELECT MAX(uid) FROM {users}')->fetchField(); |
|
741 $max_batch_id = db_query('SELECT MAX(bid) FROM {batch}')->fetchField(); |
|
742 db_insert('sequences')->fields(array('value' => max($max_aid, $max_uid, $max_batch_id)))->execute(); |
|
743 |
|
744 // Add column for locale context. |
|
745 if (db_table_exists('locales_source')) { |
|
746 db_add_field('locales_source', 'context', array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => '', 'description' => 'The context this string applies to.')); |
|
747 } |
|
748 |
|
749 // Rename 'site_offline_message' variable to 'maintenance_mode_message' |
|
750 // and 'site_offline' variable to 'maintenance_mode'. |
|
751 // Old variable is removed in update for system.module, see |
|
752 // system_update_7072(). |
|
753 if ($message = variable_get('site_offline_message', NULL)) { |
|
754 variable_set('maintenance_mode_message', $message); |
|
755 } |
|
756 // Old variable is removed in update for system.module, see |
|
757 // system_update_7069(). |
|
758 $site_offline = variable_get('site_offline', -1); |
|
759 if ($site_offline != -1) { |
|
760 variable_set('maintenance_mode', $site_offline); |
|
761 } |
|
762 |
|
763 // Add ssid column and index. |
|
764 db_add_field('sessions', 'ssid', array('description' => "Secure session ID. The value is generated by Drupal's session handlers.", 'type' => 'varchar', 'length' => 128, 'not null' => TRUE, 'default' => '')); |
|
765 db_add_index('sessions', 'ssid', array('ssid')); |
|
766 // Drop existing primary key. |
|
767 db_drop_primary_key('sessions'); |
|
768 // Add new primary key. |
|
769 db_add_primary_key('sessions', array('sid', 'ssid')); |
|
770 |
|
771 // Allow longer javascript file names. |
|
772 if (db_table_exists('languages')) { |
|
773 db_change_field('languages', 'javascript', 'javascript', array('type' => 'varchar', 'length' => 64, 'not null' => TRUE, 'default' => '')); |
|
774 } |
|
775 |
|
776 // Rename action description to label. |
|
777 db_change_field('actions', 'description', 'label', array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => '0')); |
|
778 |
|
779 variable_set('update_d7_requirements', TRUE); |
|
780 } |
|
781 |
|
782 update_fix_d7_install_profile(); |
|
783 } |
|
784 |
|
785 /** |
|
786 * Register the currently installed profile in the system table. |
|
787 * |
|
788 * Installation profiles are now treated as modules by Drupal, and have an |
|
789 * upgrade path based on their schema version in the system table. |
|
790 * |
|
791 * The installation profile will be set to schema_version 0, as it has already |
|
792 * been installed. Any other hook_update_N functions provided by the |
|
793 * installation profile will be run by update.php. |
|
794 */ |
|
795 function update_fix_d7_install_profile() { |
|
796 $profile = drupal_get_profile(); |
|
797 |
|
798 // 'Default' profile has been renamed to 'Standard' in D7. |
|
799 // We change the profile here to prevent a broken record in the system table. |
|
800 // See system_update_7049(). |
|
801 if ($profile == 'default') { |
|
802 $profile = 'standard'; |
|
803 variable_set('install_profile', $profile); |
|
804 } |
|
805 |
|
806 $results = db_select('system', 's') |
|
807 ->fields('s', array('name', 'schema_version')) |
|
808 ->condition('name', $profile) |
|
809 ->condition('type', 'module') |
|
810 ->execute() |
|
811 ->fetchAll(); |
|
812 |
|
813 if (empty($results)) { |
|
814 $filename = 'profiles/' . $profile . '/' . $profile . '.profile'; |
|
815 |
|
816 // Read profile info file |
|
817 $info = drupal_parse_info_file(dirname($filename) . '/' . $profile . '.info'); |
|
818 |
|
819 // Merge in defaults. |
|
820 $info = $info + array( |
|
821 'dependencies' => array(), |
|
822 'description' => '', |
|
823 'package' => 'Other', |
|
824 'version' => NULL, |
|
825 'php' => DRUPAL_MINIMUM_PHP, |
|
826 'files' => array(), |
|
827 ); |
|
828 |
|
829 $values = array( |
|
830 'filename' => $filename, |
|
831 'name' => $profile, |
|
832 'info' => serialize($info), |
|
833 'schema_version' => 0, |
|
834 'type' => 'module', |
|
835 'status' => 1, |
|
836 'owner' => '', |
|
837 ); |
|
838 |
|
839 // Installation profile hooks are always executed last by the module system |
|
840 $values['weight'] = 1000; |
|
841 |
|
842 // Initializing the system table entry for the installation profile |
|
843 db_insert('system') |
|
844 ->fields(array_keys($values)) |
|
845 ->values($values) |
|
846 ->execute(); |
|
847 |
|
848 // Reset the cached schema version. |
|
849 drupal_get_installed_schema_version($profile, TRUE); |
|
850 |
|
851 // Load the updates again to make sure the installation profile updates |
|
852 // are loaded. |
|
853 drupal_load_updates(); |
|
854 } |
|
855 } |
|
856 |
|
857 /** |
|
858 * Parse pre-Drupal 7 database connection URLs and return D7 compatible array. |
|
859 * |
|
860 * @return |
|
861 * Drupal 7 DBTNG compatible array of database connection information. |
|
862 */ |
|
863 function update_parse_db_url($db_url, $db_prefix) { |
|
864 $databases = array(); |
|
865 if (!is_array($db_url)) { |
|
866 $db_url = array('default' => $db_url); |
|
867 } |
|
868 foreach ($db_url as $database => $url) { |
|
869 $url = parse_url($url); |
|
870 $databases[$database]['default'] = array( |
|
871 // MySQLi uses the mysql driver. |
|
872 'driver' => $url['scheme'] == 'mysqli' ? 'mysql' : $url['scheme'], |
|
873 // Remove the leading slash to get the database name. |
|
874 'database' => substr(urldecode($url['path']), 1), |
|
875 'username' => urldecode($url['user']), |
|
876 'password' => isset($url['pass']) ? urldecode($url['pass']) : '', |
|
877 'host' => urldecode($url['host']), |
|
878 'port' => isset($url['port']) ? urldecode($url['port']) : '', |
|
879 ); |
|
880 if (isset($db_prefix)) { |
|
881 $databases[$database]['default']['prefix'] = $db_prefix; |
|
882 } |
|
883 } |
|
884 return $databases; |
|
885 } |
|
886 |
|
887 /** |
|
888 * Constructs a session name compatible with a D6 environment. |
|
889 * |
|
890 * @return |
|
891 * D6-compatible session name string. |
|
892 * |
|
893 * @see drupal_settings_initialize() |
|
894 */ |
|
895 function update_get_d6_session_name() { |
|
896 global $base_url, $cookie_domain; |
|
897 $cookie_secure = ini_get('session.cookie_secure'); |
|
898 |
|
899 // If a custom cookie domain is set in settings.php, that variable forms |
|
900 // the basis of the session name. Re-compute the D7 hashing method to find |
|
901 // out if $cookie_domain was used as the session name. |
|
902 if (($cookie_secure ? 'SSESS' : 'SESS') . substr(hash('sha256', $cookie_domain), 0, 32) == session_name()) { |
|
903 $session_name = $cookie_domain; |
|
904 } |
|
905 else { |
|
906 // Otherwise use $base_url as session name, without the protocol |
|
907 // to use the same session identifiers across HTTP and HTTPS. |
|
908 list( , $session_name) = explode('://', $base_url, 2); |
|
909 } |
|
910 |
|
911 if ($cookie_secure) { |
|
912 $session_name .= 'SSL'; |
|
913 } |
|
914 |
|
915 return 'SESS' . md5($session_name); |
|
916 } |
|
917 |
|
918 /** |
|
919 * Implements callback_batch_operation(). |
|
920 * |
|
921 * Performs one update and stores the results for display on the results page. |
|
922 * |
|
923 * If an update function completes successfully, it should return a message |
|
924 * as a string indicating success, for example: |
|
925 * @code |
|
926 * return t('New index added successfully.'); |
|
927 * @endcode |
|
928 * |
|
929 * Alternatively, it may return nothing. In that case, no message |
|
930 * will be displayed at all. |
|
931 * |
|
932 * If it fails for whatever reason, it should throw an instance of |
|
933 * DrupalUpdateException with an appropriate error message, for example: |
|
934 * @code |
|
935 * throw new DrupalUpdateException(t('Description of what went wrong')); |
|
936 * @endcode |
|
937 * |
|
938 * If an exception is thrown, the current update and all updates that depend on |
|
939 * it will be aborted. The schema version will not be updated in this case, and |
|
940 * all the aborted updates will continue to appear on update.php as updates |
|
941 * that have not yet been run. |
|
942 * |
|
943 * If an update function needs to be re-run as part of a batch process, it |
|
944 * should accept the $sandbox array by reference as its first parameter |
|
945 * and set the #finished property to the percentage completed that it is, as a |
|
946 * fraction of 1. |
|
947 * |
|
948 * @param $module |
|
949 * The module whose update will be run. |
|
950 * @param $number |
|
951 * The update number to run. |
|
952 * @param $dependency_map |
|
953 * An array whose keys are the names of all update functions that will be |
|
954 * performed during this batch process, and whose values are arrays of other |
|
955 * update functions that each one depends on. |
|
956 * @param $context |
|
957 * The batch context array. |
|
958 * |
|
959 * @see update_resolve_dependencies() |
|
960 */ |
|
961 function update_do_one($module, $number, $dependency_map, &$context) { |
|
962 $function = $module . '_update_' . $number; |
|
963 |
|
964 // If this update was aborted in a previous step, or has a dependency that |
|
965 // was aborted in a previous step, go no further. |
|
966 if (!empty($context['results']['#abort']) && array_intersect($context['results']['#abort'], array_merge($dependency_map, array($function)))) { |
|
967 return; |
|
968 } |
|
969 |
|
970 $ret = array(); |
|
971 if (function_exists($function)) { |
|
972 try { |
|
973 $ret['results']['query'] = $function($context['sandbox']); |
|
974 $ret['results']['success'] = TRUE; |
|
975 } |
|
976 // @TODO We may want to do different error handling for different |
|
977 // exception types, but for now we'll just log the exception and |
|
978 // return the message for printing. |
|
979 catch (Exception $e) { |
|
980 watchdog_exception('update', $e); |
|
981 |
|
982 require_once DRUPAL_ROOT . '/includes/errors.inc'; |
|
983 $variables = _drupal_decode_exception($e); |
|
984 // The exception message is run through check_plain() by _drupal_decode_exception(). |
|
985 $ret['#abort'] = array('success' => FALSE, 'query' => t('%type: !message in %function (line %line of %file).', $variables)); |
|
986 } |
|
987 } |
|
988 |
|
989 if (isset($context['sandbox']['#finished'])) { |
|
990 $context['finished'] = $context['sandbox']['#finished']; |
|
991 unset($context['sandbox']['#finished']); |
|
992 } |
|
993 |
|
994 if (!isset($context['results'][$module])) { |
|
995 $context['results'][$module] = array(); |
|
996 } |
|
997 if (!isset($context['results'][$module][$number])) { |
|
998 $context['results'][$module][$number] = array(); |
|
999 } |
|
1000 $context['results'][$module][$number] = array_merge($context['results'][$module][$number], $ret); |
|
1001 |
|
1002 if (!empty($ret['#abort'])) { |
|
1003 // Record this function in the list of updates that were aborted. |
|
1004 $context['results']['#abort'][] = $function; |
|
1005 } |
|
1006 |
|
1007 // Record the schema update if it was completed successfully. |
|
1008 if ($context['finished'] == 1 && empty($ret['#abort'])) { |
|
1009 drupal_set_installed_schema_version($module, $number); |
|
1010 } |
|
1011 |
|
1012 $context['message'] = 'Updating ' . check_plain($module) . ' module'; |
|
1013 } |
|
1014 |
|
1015 /** |
|
1016 * @class Exception class used to throw error if a module update fails. |
|
1017 */ |
|
1018 class DrupalUpdateException extends Exception { } |
|
1019 |
|
1020 /** |
|
1021 * Starts the database update batch process. |
|
1022 * |
|
1023 * @param $start |
|
1024 * An array whose keys contain the names of modules to be updated during the |
|
1025 * current batch process, and whose values contain the number of the first |
|
1026 * requested update for that module. The actual updates that are run (and the |
|
1027 * order they are run in) will depend on the results of passing this data |
|
1028 * through the update dependency system. |
|
1029 * @param $redirect |
|
1030 * Path to redirect to when the batch has finished processing. |
|
1031 * @param $url |
|
1032 * URL of the batch processing page (should only be used for separate |
|
1033 * scripts like update.php). |
|
1034 * @param $batch |
|
1035 * Optional parameters to pass into the batch API. |
|
1036 * @param $redirect_callback |
|
1037 * (optional) Specify a function to be called to redirect to the progressive |
|
1038 * processing page. |
|
1039 * |
|
1040 * @see update_resolve_dependencies() |
|
1041 */ |
|
1042 function update_batch($start, $redirect = NULL, $url = NULL, $batch = array(), $redirect_callback = 'drupal_goto') { |
|
1043 // During the update, bring the site offline so that schema changes do not |
|
1044 // affect visiting users. |
|
1045 $_SESSION['maintenance_mode'] = variable_get('maintenance_mode', FALSE); |
|
1046 if ($_SESSION['maintenance_mode'] == FALSE) { |
|
1047 variable_set('maintenance_mode', TRUE); |
|
1048 } |
|
1049 |
|
1050 // Resolve any update dependencies to determine the actual updates that will |
|
1051 // be run and the order they will be run in. |
|
1052 $updates = update_resolve_dependencies($start); |
|
1053 |
|
1054 // Store the dependencies for each update function in an array which the |
|
1055 // batch API can pass in to the batch operation each time it is called. (We |
|
1056 // do not store the entire update dependency array here because it is |
|
1057 // potentially very large.) |
|
1058 $dependency_map = array(); |
|
1059 foreach ($updates as $function => $update) { |
|
1060 $dependency_map[$function] = !empty($update['reverse_paths']) ? array_keys($update['reverse_paths']) : array(); |
|
1061 } |
|
1062 |
|
1063 $operations = array(); |
|
1064 foreach ($updates as $update) { |
|
1065 if ($update['allowed']) { |
|
1066 // Set the installed version of each module so updates will start at the |
|
1067 // correct place. (The updates are already sorted, so we can simply base |
|
1068 // this on the first one we come across in the above foreach loop.) |
|
1069 if (isset($start[$update['module']])) { |
|
1070 drupal_set_installed_schema_version($update['module'], $update['number'] - 1); |
|
1071 unset($start[$update['module']]); |
|
1072 } |
|
1073 // Add this update function to the batch. |
|
1074 $function = $update['module'] . '_update_' . $update['number']; |
|
1075 $operations[] = array('update_do_one', array($update['module'], $update['number'], $dependency_map[$function])); |
|
1076 } |
|
1077 } |
|
1078 $batch['operations'] = $operations; |
|
1079 $batch += array( |
|
1080 'title' => 'Updating', |
|
1081 'init_message' => 'Starting updates', |
|
1082 '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.', |
|
1083 'finished' => 'update_finished', |
|
1084 'file' => 'includes/update.inc', |
|
1085 ); |
|
1086 batch_set($batch); |
|
1087 batch_process($redirect, $url, $redirect_callback); |
|
1088 } |
|
1089 |
|
1090 /** |
|
1091 * Implements callback_batch_finished(). |
|
1092 * |
|
1093 * Finishes the update process and stores the results for eventual display. |
|
1094 * |
|
1095 * After the updates run, all caches are flushed. The update results are |
|
1096 * stored into the session (for example, to be displayed on the update results |
|
1097 * page in update.php). Additionally, if the site was off-line, now that the |
|
1098 * update process is completed, the site is set back online. |
|
1099 * |
|
1100 * @param $success |
|
1101 * Indicate that the batch API tasks were all completed successfully. |
|
1102 * @param $results |
|
1103 * An array of all the results that were updated in update_do_one(). |
|
1104 * @param $operations |
|
1105 * A list of all the operations that had not been completed by the batch API. |
|
1106 * |
|
1107 * @see update_batch() |
|
1108 */ |
|
1109 function update_finished($success, $results, $operations) { |
|
1110 // Remove the D6 upgrade flag variable so that subsequent update runs do not |
|
1111 // get the wrong context. |
|
1112 variable_del('update_d6'); |
|
1113 |
|
1114 // Clear the caches in case the data has been updated. |
|
1115 drupal_flush_all_caches(); |
|
1116 |
|
1117 $_SESSION['update_results'] = $results; |
|
1118 $_SESSION['update_success'] = $success; |
|
1119 $_SESSION['updates_remaining'] = $operations; |
|
1120 |
|
1121 // Now that the update is done, we can put the site back online if it was |
|
1122 // previously in maintenance mode. |
|
1123 if (isset($_SESSION['maintenance_mode']) && $_SESSION['maintenance_mode'] == FALSE) { |
|
1124 variable_set('maintenance_mode', FALSE); |
|
1125 unset($_SESSION['maintenance_mode']); |
|
1126 } |
|
1127 } |
|
1128 |
|
1129 /** |
|
1130 * Returns a list of all the pending database updates. |
|
1131 * |
|
1132 * @return |
|
1133 * An associative array keyed by module name which contains all information |
|
1134 * about database updates that need to be run, and any updates that are not |
|
1135 * going to proceed due to missing requirements. The system module will |
|
1136 * always be listed first. |
|
1137 * |
|
1138 * The subarray for each module can contain the following keys: |
|
1139 * - start: The starting update that is to be processed. If this does not |
|
1140 * exist then do not process any updates for this module as there are |
|
1141 * other requirements that need to be resolved. |
|
1142 * - warning: Any warnings about why this module can not be updated. |
|
1143 * - pending: An array of all the pending updates for the module including |
|
1144 * the update number and the description from source code comment for |
|
1145 * each update function. This array is keyed by the update number. |
|
1146 */ |
|
1147 function update_get_update_list() { |
|
1148 // Make sure that the system module is first in the list of updates. |
|
1149 $ret = array('system' => array()); |
|
1150 |
|
1151 $modules = drupal_get_installed_schema_version(NULL, FALSE, TRUE); |
|
1152 foreach ($modules as $module => $schema_version) { |
|
1153 // Skip uninstalled and incompatible modules. |
|
1154 if ($schema_version == SCHEMA_UNINSTALLED || update_check_incompatibility($module)) { |
|
1155 continue; |
|
1156 } |
|
1157 // Otherwise, get the list of updates defined by this module. |
|
1158 $updates = drupal_get_schema_versions($module); |
|
1159 if ($updates !== FALSE) { |
|
1160 // module_invoke returns NULL for nonexisting hooks, so if no updates |
|
1161 // are removed, it will == 0. |
|
1162 $last_removed = module_invoke($module, 'update_last_removed'); |
|
1163 if ($schema_version < $last_removed) { |
|
1164 $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.'; |
|
1165 continue; |
|
1166 } |
|
1167 |
|
1168 $updates = drupal_map_assoc($updates); |
|
1169 foreach (array_keys($updates) as $update) { |
|
1170 if ($update > $schema_version) { |
|
1171 // The description for an update comes from its Doxygen. |
|
1172 $func = new ReflectionFunction($module . '_update_' . $update); |
|
1173 $description = str_replace(array("\n", '*', '/'), '', $func->getDocComment()); |
|
1174 $ret[$module]['pending'][$update] = "$update - $description"; |
|
1175 if (!isset($ret[$module]['start'])) { |
|
1176 $ret[$module]['start'] = $update; |
|
1177 } |
|
1178 } |
|
1179 } |
|
1180 if (!isset($ret[$module]['start']) && isset($ret[$module]['pending'])) { |
|
1181 $ret[$module]['start'] = $schema_version; |
|
1182 } |
|
1183 } |
|
1184 } |
|
1185 |
|
1186 if (empty($ret['system'])) { |
|
1187 unset($ret['system']); |
|
1188 } |
|
1189 return $ret; |
|
1190 } |
|
1191 |
|
1192 /** |
|
1193 * Resolves dependencies in a set of module updates, and orders them correctly. |
|
1194 * |
|
1195 * This function receives a list of requested module updates and determines an |
|
1196 * appropriate order to run them in such that all update dependencies are met. |
|
1197 * Any updates whose dependencies cannot be met are included in the returned |
|
1198 * array but have the key 'allowed' set to FALSE; the calling function should |
|
1199 * take responsibility for ensuring that these updates are ultimately not |
|
1200 * performed. |
|
1201 * |
|
1202 * In addition, the returned array also includes detailed information about the |
|
1203 * dependency chain for each update, as provided by the depth-first search |
|
1204 * algorithm in drupal_depth_first_search(). |
|
1205 * |
|
1206 * @param $starting_updates |
|
1207 * An array whose keys contain the names of modules with updates to be run |
|
1208 * and whose values contain the number of the first requested update for that |
|
1209 * module. |
|
1210 * |
|
1211 * @return |
|
1212 * An array whose keys are the names of all update functions within the |
|
1213 * provided modules that would need to be run in order to fulfill the |
|
1214 * request, arranged in the order in which the update functions should be |
|
1215 * run. (This includes the provided starting update for each module and all |
|
1216 * subsequent updates that are available.) The values are themselves arrays |
|
1217 * containing all the keys provided by the drupal_depth_first_search() |
|
1218 * algorithm, which encode detailed information about the dependency chain |
|
1219 * for this update function (for example: 'paths', 'reverse_paths', 'weight', |
|
1220 * and 'component'), as well as the following additional keys: |
|
1221 * - 'allowed': A boolean which is TRUE when the update function's |
|
1222 * dependencies are met, and FALSE otherwise. Calling functions should |
|
1223 * inspect this value before running the update. |
|
1224 * - 'missing_dependencies': An array containing the names of any other |
|
1225 * update functions that are required by this one but that are unavailable |
|
1226 * to be run. This array will be empty when 'allowed' is TRUE. |
|
1227 * - 'module': The name of the module that this update function belongs to. |
|
1228 * - 'number': The number of this update function within that module. |
|
1229 * |
|
1230 * @see drupal_depth_first_search() |
|
1231 */ |
|
1232 function update_resolve_dependencies($starting_updates) { |
|
1233 // Obtain a dependency graph for the requested update functions. |
|
1234 $update_functions = update_get_update_function_list($starting_updates); |
|
1235 $graph = update_build_dependency_graph($update_functions); |
|
1236 |
|
1237 // Perform the depth-first search and sort the results. |
|
1238 require_once DRUPAL_ROOT . '/includes/graph.inc'; |
|
1239 drupal_depth_first_search($graph); |
|
1240 uasort($graph, 'drupal_sort_weight'); |
|
1241 |
|
1242 foreach ($graph as $function => &$data) { |
|
1243 $module = $data['module']; |
|
1244 $number = $data['number']; |
|
1245 // If the update function is missing and has not yet been performed, mark |
|
1246 // it and everything that ultimately depends on it as disallowed. |
|
1247 if (update_is_missing($module, $number, $update_functions) && !update_already_performed($module, $number)) { |
|
1248 $data['allowed'] = FALSE; |
|
1249 foreach (array_keys($data['paths']) as $dependent) { |
|
1250 $graph[$dependent]['allowed'] = FALSE; |
|
1251 $graph[$dependent]['missing_dependencies'][] = $function; |
|
1252 } |
|
1253 } |
|
1254 elseif (!isset($data['allowed'])) { |
|
1255 $data['allowed'] = TRUE; |
|
1256 $data['missing_dependencies'] = array(); |
|
1257 } |
|
1258 // Now that we have finished processing this function, remove it from the |
|
1259 // graph if it was not part of the original list. This ensures that we |
|
1260 // never try to run any updates that were not specifically requested. |
|
1261 if (!isset($update_functions[$module][$number])) { |
|
1262 unset($graph[$function]); |
|
1263 } |
|
1264 } |
|
1265 |
|
1266 return $graph; |
|
1267 } |
|
1268 |
|
1269 /** |
|
1270 * Returns an organized list of update functions for a set of modules. |
|
1271 * |
|
1272 * @param $starting_updates |
|
1273 * An array whose keys contain the names of modules and whose values contain |
|
1274 * the number of the first requested update for that module. |
|
1275 * |
|
1276 * @return |
|
1277 * An array containing all the update functions that should be run for each |
|
1278 * module, including the provided starting update and all subsequent updates |
|
1279 * that are available. The keys of the array contain the module names, and |
|
1280 * each value is an ordered array of update functions, keyed by the update |
|
1281 * number. |
|
1282 * |
|
1283 * @see update_resolve_dependencies() |
|
1284 */ |
|
1285 function update_get_update_function_list($starting_updates) { |
|
1286 // Go through each module and find all updates that we need (including the |
|
1287 // first update that was requested and any updates that run after it). |
|
1288 $update_functions = array(); |
|
1289 foreach ($starting_updates as $module => $version) { |
|
1290 $update_functions[$module] = array(); |
|
1291 $updates = drupal_get_schema_versions($module); |
|
1292 if ($updates !== FALSE) { |
|
1293 $max_version = max($updates); |
|
1294 if ($version <= $max_version) { |
|
1295 foreach ($updates as $update) { |
|
1296 if ($update >= $version) { |
|
1297 $update_functions[$module][$update] = $module . '_update_' . $update; |
|
1298 } |
|
1299 } |
|
1300 } |
|
1301 } |
|
1302 } |
|
1303 return $update_functions; |
|
1304 } |
|
1305 |
|
1306 /** |
|
1307 * Constructs a graph which encodes the dependencies between module updates. |
|
1308 * |
|
1309 * This function returns an associative array which contains a "directed graph" |
|
1310 * representation of the dependencies between a provided list of update |
|
1311 * functions, as well as any outside update functions that they directly depend |
|
1312 * on but that were not in the provided list. The vertices of the graph |
|
1313 * represent the update functions themselves, and each edge represents a |
|
1314 * requirement that the first update function needs to run before the second. |
|
1315 * For example, consider this graph: |
|
1316 * |
|
1317 * system_update_7000 ---> system_update_7001 ---> system_update_7002 |
|
1318 * |
|
1319 * Visually, this indicates that system_update_7000() must run before |
|
1320 * system_update_7001(), which in turn must run before system_update_7002(). |
|
1321 * |
|
1322 * The function takes into account standard dependencies within each module, as |
|
1323 * shown above (i.e., the fact that each module's updates must run in numerical |
|
1324 * order), but also finds any cross-module dependencies that are defined by |
|
1325 * modules which implement hook_update_dependencies(), and builds them into the |
|
1326 * graph as well. |
|
1327 * |
|
1328 * @param $update_functions |
|
1329 * An organized array of update functions, in the format returned by |
|
1330 * update_get_update_function_list(). |
|
1331 * |
|
1332 * @return |
|
1333 * A multidimensional array representing the dependency graph, suitable for |
|
1334 * passing in to drupal_depth_first_search(), but with extra information |
|
1335 * about each update function also included. Each array key contains the name |
|
1336 * of an update function, including all update functions from the provided |
|
1337 * list as well as any outside update functions which they directly depend |
|
1338 * on. Each value is an associative array containing the following keys: |
|
1339 * - 'edges': A representation of any other update functions that immediately |
|
1340 * depend on this one. See drupal_depth_first_search() for more details on |
|
1341 * the format. |
|
1342 * - 'module': The name of the module that this update function belongs to. |
|
1343 * - 'number': The number of this update function within that module. |
|
1344 * |
|
1345 * @see drupal_depth_first_search() |
|
1346 * @see update_resolve_dependencies() |
|
1347 */ |
|
1348 function update_build_dependency_graph($update_functions) { |
|
1349 // Initialize an array that will define a directed graph representing the |
|
1350 // dependencies between update functions. |
|
1351 $graph = array(); |
|
1352 |
|
1353 // Go through each update function and build an initial list of dependencies. |
|
1354 foreach ($update_functions as $module => $functions) { |
|
1355 $previous_function = NULL; |
|
1356 foreach ($functions as $number => $function) { |
|
1357 // Add an edge to the directed graph representing the fact that each |
|
1358 // update function in a given module must run after the update that |
|
1359 // numerically precedes it. |
|
1360 if ($previous_function) { |
|
1361 $graph[$previous_function]['edges'][$function] = TRUE; |
|
1362 } |
|
1363 $previous_function = $function; |
|
1364 |
|
1365 // Define the module and update number associated with this function. |
|
1366 $graph[$function]['module'] = $module; |
|
1367 $graph[$function]['number'] = $number; |
|
1368 } |
|
1369 } |
|
1370 |
|
1371 // Now add any explicit update dependencies declared by modules. |
|
1372 $update_dependencies = update_retrieve_dependencies(); |
|
1373 foreach ($graph as $function => $data) { |
|
1374 if (!empty($update_dependencies[$data['module']][$data['number']])) { |
|
1375 foreach ($update_dependencies[$data['module']][$data['number']] as $module => $number) { |
|
1376 $dependency = $module . '_update_' . $number; |
|
1377 $graph[$dependency]['edges'][$function] = TRUE; |
|
1378 $graph[$dependency]['module'] = $module; |
|
1379 $graph[$dependency]['number'] = $number; |
|
1380 } |
|
1381 } |
|
1382 } |
|
1383 |
|
1384 return $graph; |
|
1385 } |
|
1386 |
|
1387 /** |
|
1388 * Determines if a module update is missing or unavailable. |
|
1389 * |
|
1390 * @param $module |
|
1391 * The name of the module. |
|
1392 * @param $number |
|
1393 * The number of the update within that module. |
|
1394 * @param $update_functions |
|
1395 * An organized array of update functions, in the format returned by |
|
1396 * update_get_update_function_list(). This should represent all module |
|
1397 * updates that are requested to run at the time this function is called. |
|
1398 * |
|
1399 * @return |
|
1400 * TRUE if the provided module update is not installed or is not in the |
|
1401 * provided list of updates to run; FALSE otherwise. |
|
1402 */ |
|
1403 function update_is_missing($module, $number, $update_functions) { |
|
1404 return !isset($update_functions[$module][$number]) || !function_exists($update_functions[$module][$number]); |
|
1405 } |
|
1406 |
|
1407 /** |
|
1408 * Determines if a module update has already been performed. |
|
1409 * |
|
1410 * @param $module |
|
1411 * The name of the module. |
|
1412 * @param $number |
|
1413 * The number of the update within that module. |
|
1414 * |
|
1415 * @return |
|
1416 * TRUE if the database schema indicates that the update has already been |
|
1417 * performed; FALSE otherwise. |
|
1418 */ |
|
1419 function update_already_performed($module, $number) { |
|
1420 return $number <= drupal_get_installed_schema_version($module); |
|
1421 } |
|
1422 |
|
1423 /** |
|
1424 * Invokes hook_update_dependencies() in all installed modules. |
|
1425 * |
|
1426 * This function is similar to module_invoke_all(), with the main difference |
|
1427 * that it does not require that a module be enabled to invoke its hook, only |
|
1428 * that it be installed. This allows the update system to properly perform |
|
1429 * updates even on modules that are currently disabled. |
|
1430 * |
|
1431 * @return |
|
1432 * An array of return values obtained by merging the results of the |
|
1433 * hook_update_dependencies() implementations in all installed modules. |
|
1434 * |
|
1435 * @see module_invoke_all() |
|
1436 * @see hook_update_dependencies() |
|
1437 */ |
|
1438 function update_retrieve_dependencies() { |
|
1439 $return = array(); |
|
1440 // Get a list of installed modules, arranged so that we invoke their hooks in |
|
1441 // the same order that module_invoke_all() does. |
|
1442 $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(); |
|
1443 foreach ($modules as $module) { |
|
1444 $function = $module . '_update_dependencies'; |
|
1445 if (function_exists($function)) { |
|
1446 $result = $function(); |
|
1447 // Each implementation of hook_update_dependencies() returns a |
|
1448 // multidimensional, associative array containing some keys that |
|
1449 // represent module names (which are strings) and other keys that |
|
1450 // represent update function numbers (which are integers). We cannot use |
|
1451 // array_merge_recursive() to properly merge these results, since it |
|
1452 // treats strings and integers differently. Therefore, we have to |
|
1453 // explicitly loop through the expected array structure here and perform |
|
1454 // the merge manually. |
|
1455 if (isset($result) && is_array($result)) { |
|
1456 foreach ($result as $module => $module_data) { |
|
1457 foreach ($module_data as $update => $update_data) { |
|
1458 foreach ($update_data as $module_dependency => $update_dependency) { |
|
1459 // If there are redundant dependencies declared for the same |
|
1460 // update function (so that it is declared to depend on more than |
|
1461 // one update from a particular module), record the dependency on |
|
1462 // the highest numbered update here, since that automatically |
|
1463 // implies the previous ones. For example, if one module's |
|
1464 // implementation of hook_update_dependencies() required this |
|
1465 // ordering: |
|
1466 // |
|
1467 // system_update_7001 ---> user_update_7000 |
|
1468 // |
|
1469 // but another module's implementation of the hook required this |
|
1470 // one: |
|
1471 // |
|
1472 // system_update_7002 ---> user_update_7000 |
|
1473 // |
|
1474 // we record the second one, since system_update_7001() is always |
|
1475 // guaranteed to run before system_update_7002() anyway (within |
|
1476 // an individual module, updates are always run in numerical |
|
1477 // order). |
|
1478 if (!isset($return[$module][$update][$module_dependency]) || $update_dependency > $return[$module][$update][$module_dependency]) { |
|
1479 $return[$module][$update][$module_dependency] = $update_dependency; |
|
1480 } |
|
1481 } |
|
1482 } |
|
1483 } |
|
1484 } |
|
1485 } |
|
1486 } |
|
1487 |
|
1488 return $return; |
|
1489 } |