system.install 125 KB
Newer Older
Dries's avatar
 
Dries committed
1
<?php
2
// $Id$
3

4 5
/**
 * Test and report Drupal installation requirements.
6 7 8 9 10
 *
 * @param $phase
 *   The current system installation phase.
 * @return
 *   An array of system requirements.
11 12 13 14
 */
function system_requirements($phase) {
  $requirements = array();
  // Ensure translations don't break at install time
15
  $t = get_t();
16 17 18 19 20 21

  // Report Drupal version
  if ($phase == 'runtime') {
    $requirements['drupal'] = array(
      'title' => $t('Drupal'),
      'value' => VERSION,
Steven Wittens's avatar
Steven Wittens committed
22 23
      'severity' => REQUIREMENT_INFO,
      'weight' => -10,
24 25 26
    );
  }

27
  // Web server information.
Steven Wittens's avatar
Steven Wittens committed
28
  $software = $_SERVER['SERVER_SOFTWARE'];
29 30
  $requirements['webserver'] = array(
    'title' => $t('Web server'),
Steven Wittens's avatar
Steven Wittens committed
31
    'value' => $software,
32 33 34 35 36
  );

  // Test PHP version
  $requirements['php'] = array(
    'title' => $t('PHP'),
37
    'value' => ($phase == 'runtime') ? l(phpversion(), 'admin/reports/status/php') : phpversion(),
38 39 40 41 42
  );
  if (version_compare(phpversion(), DRUPAL_MINIMUM_PHP) < 0) {
    $requirements['php']['description'] = $t('Your PHP installation is too old. Drupal requires at least PHP %version.', array('%version' => DRUPAL_MINIMUM_PHP));
    $requirements['php']['severity'] = REQUIREMENT_ERROR;
  }
43

44 45 46 47 48
  // Test PHP register_globals setting.
  $requirements['php_register_globals'] = array(
    'title' => $t('PHP register globals'),
  );
  $register_globals = trim(ini_get('register_globals'));
49
  // Unfortunately, ini_get() may return many different values, and we can't
50 51 52 53 54 55 56 57 58 59 60 61 62
  // be certain which values mean 'on', so we instead check for 'not off'
  // since we never want to tell the user that their site is secure
  // (register_globals off), when it is in fact on. We can only guarantee
  // register_globals is off if the value returned is 'off', '', or 0.
  if (!empty($register_globals) && strtolower($register_globals) != 'off') {
    $requirements['php_register_globals']['description'] = $t('<em>register_globals</em> is enabled. Drupal requires this configuration directive to be disabled. Your site may not be secure when <em>register_globals</em> is enabled. The PHP manual has instructions for <a href="http://php.net/configuration.changes">how to change configuration settings</a>.');
    $requirements['php_register_globals']['severity'] = REQUIREMENT_ERROR;
    $requirements['php_register_globals']['value'] = $t("Enabled ('@value')", array('@value' => $register_globals));
  }
  else {
    $requirements['php_register_globals']['value'] = $t('Disabled');
  }

63
  // Test PHP memory_limit
64
  $memory_limit = ini_get('memory_limit');
65 66
  $requirements['php_memory_limit'] = array(
    'title' => $t('PHP memory limit'),
67
    'value' => $memory_limit,
68
  );
69

70 71 72 73
  if ($memory_limit && parse_size($memory_limit) < parse_size(DRUPAL_MINIMUM_PHP_MEMORY_LIMIT)) {
    $description = '';
    if ($phase == 'install') {
      $description = $t('Consider increasing your PHP memory limit to %memory_minimum_limit to help prevent errors in the installation process.', array('%memory_minimum_limit' => DRUPAL_MINIMUM_PHP_MEMORY_LIMIT));
74 75 76
    }
    elseif ($phase == 'update') {
      $description = $t('Consider increasing your PHP memory limit to %memory_minimum_limit to help prevent errors in the update process.', array('%memory_minimum_limit' => DRUPAL_MINIMUM_PHP_MEMORY_LIMIT));
77
    }
78 79
    elseif ($phase == 'runtime') {
      $description = $t('Depending on your configuration, Drupal can run with a %memory_limit PHP memory limit. However, a %memory_minimum_limit PHP memory limit or above is recommended, especially if your site uses additional custom or contributed modules.', array('%memory_limit' => $memory_limit, '%memory_minimum_limit' => DRUPAL_MINIMUM_PHP_MEMORY_LIMIT));
80
    }
81

82 83
    if (!empty($description)) {
      if ($php_ini_path = get_cfg_var('cfg_file_path')) {
84
        $description .= ' ' . $t('Increase the memory limit by editing the memory_limit parameter in the file %configuration-file and then restart your web server (or contact your system administrator or hosting provider for assistance).', array('%configuration-file' => $php_ini_path));
85 86
      }
      else {
87
        $description .= ' ' . $t('Contact your system administrator or hosting provider for assistance with increasing your PHP memory limit.');
88
      }
89

90
      $requirements['php_memory_limit']['description'] = $description . ' ' . $t('See the <a href="@url">Drupal requirements</a> for more information.', array('@url' => 'http://drupal.org/requirements'));
91
      $requirements['php_memory_limit']['severity'] = REQUIREMENT_WARNING;
92
    }
93
  }
94

95 96 97 98 99 100 101 102
  // Test DB version
  global $db_type;
  if (function_exists('db_status_report')) {
    $requirements += db_status_report($phase);
  }

  // Test settings.php file writability
  if ($phase == 'runtime') {
103
    $conf_dir = drupal_verify_install_file(conf_path(), FILE_NOT_WRITABLE, 'dir');
104
    $conf_file = drupal_verify_install_file(conf_path() . '/settings.php', FILE_EXIST|FILE_READABLE|FILE_NOT_WRITABLE);
105
    if (!$conf_dir || !$conf_file) {
106 107 108
      $requirements['settings.php'] = array(
        'value' => $t('Not protected'),
        'severity' => REQUIREMENT_ERROR,
109
        'description' => '',
110
      );
111 112 113 114
      if (!$conf_dir) {
        $requirements['settings.php']['description'] .= $t('The directory %file is not protected from modifications and poses a security risk. You must change the directory\'s permissions to be non-writable. ', array('%file' => conf_path()));
      }
      if (!$conf_file) {
115
        $requirements['settings.php']['description'] .= $t('The file %file is not protected from modifications and poses a security risk. You must change the file\'s permissions to be non-writable.', array('%file' => conf_path() . '/settings.php'));
116
      }
117 118 119 120 121 122 123 124 125
    }
    else {
      $requirements['settings.php'] = array(
        'value' => $t('Protected'),
      );
    }
    $requirements['settings.php']['title'] = $t('Configuration file');
  }

126
  // Report cron status.
127
  if ($phase == 'runtime') {
128 129 130 131 132
    // Cron warning threshold defaults to two days.
    $threshold_warning = variable_get('cron_threshold_warning', 172800);
    // Cron error threshold defaults to two weeks.
    $threshold_error = variable_get('cron_threshold_error', 1209600);
    // Cron configuration help text.
133
    $help = $t('For more information, see the online handbook entry for <a href="@cron-handbook">configuring cron jobs</a>.', array('@cron-handbook' => 'http://drupal.org/cron'));
134 135 136

    // Determine when cron last ran. If never, use the install time to
    // determine the warning or error status.
137
    $cron_last = variable_get('cron_last', NULL);
138 139 140 141 142
    $never_run = FALSE;
    if (!is_numeric($cron_last)) {
      $never_run = TRUE;
      $cron_last = variable_get('install_time', 0);
    }
143

144 145
    // Determine severity based on time since cron last ran.
    $severity = REQUIREMENT_OK;
146
    if (REQUEST_TIME - $cron_last > $threshold_error) {
147
      $severity = REQUIREMENT_ERROR;
148
    }
149
    elseif ($never_run || (REQUEST_TIME - $cron_last > $threshold_warning)) {
150 151 152 153 154 155
      $severity = REQUIREMENT_WARNING;
    }

    // If cron hasn't been run, and the user is viewing the main
    // administration page, instead of an error, we display a helpful reminder
    // to configure cron jobs.
156
    if ($never_run && $severity != REQUIREMENT_ERROR && $_GET['q'] == 'admin' && user_access('administer site configuration')) {
157
      drupal_set_message($t('Cron has not run. Please visit the <a href="@status">status report</a> for more information.', array('@status' => url('admin/reports/status'))));
158 159
    }

160 161 162
    // Set summary and description based on values determined above.
    if ($never_run) {
      $summary = $t('Never run');
163
      $description = $t('Cron has not run.') . ' ' . $help;
164 165
    }
    else {
166
      $summary = $t('Last run !time ago', array('!time' => format_interval(REQUEST_TIME - $cron_last)));
167 168
      $description = '';
      if ($severity != REQUIREMENT_OK) {
169
        $description = $t('Cron has not run recently.') . ' ' . $help;
170 171
      }
    }
172

173
    $description .= ' ' . $t('You can <a href="@cron">run cron manually</a>.', array('@cron' => url('admin/reports/status/run-cron')));
174
    $description .= '<br />' . $t('To run cron from outside the site, go to <a href="!cron">!cron</a>', array('!cron' => url('cron.php', array('absolute' => TRUE, 'query' => 'cron_key=' . variable_get('cron_key', 'drupal')))));
175

176 177 178 179
    $requirements['cron'] = array(
      'title' => $t('Cron maintenance tasks'),
      'severity' => $severity,
      'value' => $summary,
180
      'description' => $description
181
    );
182 183
  }

184
  // Test files directory
185
  $directory = file_directory_path();
186 187 188
  $requirements['file system'] = array(
    'title' => $t('File system'),
  );
189 190 191 192 193 194

  // For installer, create the directory if possible.
  if ($phase == 'install' && !is_dir($directory) && @mkdir($directory)) {
    @chmod($directory, 0775); // Necessary for non-webserver users.
  }

195 196 197
  $is_writable = is_writable($directory);
  $is_directory = is_dir($directory);
  if (!$is_writable || !$is_directory) {
198 199
    $description = '';
    $requirements['file system']['value'] = $t('Not writable');
200 201
    if (!$is_directory) {
      $error = $t('The directory %directory does not exist.', array('%directory' => $directory));
202 203
    }
    else {
204 205
      $error = $t('The directory %directory is not writable.', array('%directory' => $directory));
    }
206
    // The files directory requirement check is done only during install and runtime.
207
    if ($phase == 'runtime') {
208
      $description = $error . ' ' . $t('You may need to set the correct directory at the <a href="@admin-file-system">file system settings page</a> or change the current directory\'s permissions so that it is writable.', array('@admin-file-system' => url('admin/settings/file-system')));
209
    }
210
    elseif ($phase == 'install') {
211 212
      // For the installer UI, we need different wording. 'value' will
      // be treated as version, so provide none there.
213
      $description = $error . ' ' . $t('An automated attempt to create this directory failed, possibly due to a permissions problem. To proceed with the installation, either create the directory and modify its permissions manually, or ensure that the installer has the permissions to create it automatically. For more information, please see INSTALL.txt or the <a href="@handbook_url">online handbook</a>.', array('@handbook_url' => 'http://drupal.org/server-permissions'));
214 215
      $requirements['file system']['value'] = '';
    }
216 217 218 219
    if (!empty($description)) {
      $requirements['file system']['description'] = $description;
      $requirements['file system']['severity'] = REQUIREMENT_ERROR;
    }
220 221 222 223 224 225 226
  }
  else {
    if (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) == FILE_DOWNLOADS_PUBLIC) {
      $requirements['file system']['value'] = $t('Writable (<em>public</em> download method)');
    }
    else {
      $requirements['file system']['value'] = $t('Writable (<em>private</em> download method)');
227 228 229
    }
  }

230 231 232
  // See if updates are available in update.php.
  if ($phase == 'runtime') {
    $requirements['update'] = array(
233
      'title' => $t('Database updates'),
234 235 236 237 238 239 240 241 242 243 244 245
      'severity' => REQUIREMENT_OK,
      'value' => $t('Up to date'),
    );

    // Check installed modules.
    foreach (module_list() as $module) {
      $updates = drupal_get_schema_versions($module);
      if ($updates !== FALSE) {
        $default = drupal_get_installed_schema_version($module);
        if (max($updates) > $default) {
          $requirements['update']['severity'] = REQUIREMENT_ERROR;
          $requirements['update']['value'] = $t('Out of date');
246
          $requirements['update']['description'] = $t('Some modules have database schema updates to install. You should run the <a href="@update">database update script</a> immediately.', array('@update' => base_path() . 'update.php'));
247 248 249 250 251 252
          break;
        }
      }
    }
  }

253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269
  // Verify the update.php access setting
  if ($phase == 'runtime') {
    if (!empty($GLOBALS['update_free_access'])) {
      $requirements['update access'] = array(
        'value' => $t('Not protected'),
        'severity' => REQUIREMENT_ERROR,
        'description' => $t('The update.php script is accessible to everyone without authentication check, which is a security risk. You must change the $update_free_access value in your settings.php back to FALSE.'),
      );
    }
    else {
      $requirements['update access'] = array(
        'value' => $t('Protected'),
      );
    }
    $requirements['update access']['title'] = $t('Access to update.php');
  }

270
  // Test Unicode library
271
  include_once DRUPAL_ROOT . '/includes/unicode.inc';
272 273
  $requirements = array_merge($requirements, unicode_requirements());

274 275 276 277 278
  // Check for update status module.
  if ($phase == 'runtime') {
    if (!module_exists('update')) {
      $requirements['update status'] = array(
        'value' => $t('Not enabled'),
279
        'severity' => REQUIREMENT_WARNING,
280 281 282 283 284 285 286
        'description' => $t('Update notifications are not enabled. It is <strong>highly recommended</strong> that you enable the update status module from the <a href="@module">module administration page</a> in order to stay up-to-date on new releases. For more information please read the <a href="@update">Update status handbook page</a>.', array('@update' => 'http://drupal.org/handbook/modules/update', '@module' => url('admin/build/modules'))),
      );
    }
    else {
      $requirements['update status'] = array(
        'value' => $t('Enabled'),
      );
287 288 289 290 291 292 293 294
      if (variable_get('drupal_http_request_fails', FALSE)) {
        $requirements['http requests'] = array(
          'title' => $t('HTTP request status'),
          'value' => $t('Fails'),
          'severity' => REQUIREMENT_ERROR,
          'description' => $t('Your system or network configuration does not allow Drupal to access web pages, resulting in reduced functionality. This could be due to your webserver configuration or PHP settings, and should be resolved in order to download information about available updates, fetch aggregator feeds, sign in via OpenID, or use other network-dependent services.'),
        );
      }
295 296 297 298
    }
    $requirements['update status']['title'] = $t('Update notifications');
  }

299 300 301
  return $requirements;
}

302 303 304
/**
 * Implementation of hook_install().
 */
305
function system_install() {
306
  if (db_driver() == 'pgsql') {
307 308 309 310 311 312
    // We create some custom types and functions using global names instead of
    // prefixing them like we do with table names. If this function is ever
    // called again (for example, by the test framework when creating prefixed
    // test databases), the global names will already exist. We therefore avoid
    // trying to create them again in that case.

313
    // Create unsigned types.
314 315 316 317 318 319 320 321 322
    if (!db_result(db_query("SELECT COUNT(*) FROM pg_constraint WHERE conname = 'int_unsigned_check'"))) {
      db_query("CREATE DOMAIN int_unsigned integer CHECK (VALUE >= 0)");
    }
    if (!db_result(db_query("SELECT COUNT(*) FROM pg_constraint WHERE conname = 'smallint_unsigned_check'"))) {
      db_query("CREATE DOMAIN smallint_unsigned smallint CHECK (VALUE >= 0)");
    }
    if (!db_result(db_query("SELECT COUNT(*) FROM pg_constraint WHERE conname = 'bigint_unsigned_check'"))) {
      db_query("CREATE DOMAIN bigint_unsigned bigint CHECK (VALUE >= 0)");
    }
323

324
    // Create functions.
325 326 327 328 329 330 331 332 333 334 335
    db_query('CREATE OR REPLACE FUNCTION "greatest"(numeric, numeric) RETURNS numeric AS
      \'SELECT CASE WHEN (($1 > $2) OR ($2 IS NULL)) THEN $1 ELSE $2 END;\'
      LANGUAGE \'sql\''
    );
    db_query('CREATE OR REPLACE FUNCTION "greatest"(numeric, numeric, numeric) RETURNS numeric AS
      \'SELECT greatest($1, greatest($2, $3));\'
      LANGUAGE \'sql\''
    );
    if (!db_result(db_query("SELECT COUNT(*) FROM pg_proc WHERE proname = 'rand'"))) {
      db_query('CREATE OR REPLACE FUNCTION "rand"() RETURNS float AS
        \'SELECT random();\'
336 337
        LANGUAGE \'sql\''
      );
338
    }
339

340 341 342
    if (!db_result(db_query("SELECT COUNT(*) FROM pg_proc WHERE proname = 'concat'"))) {
      db_query('CREATE OR REPLACE FUNCTION "concat"(text, text) RETURNS text AS
        \'SELECT $1 || $2;\'
343 344
        LANGUAGE \'sql\''
      );
345 346 347 348 349 350 351 352 353
    }
    db_query('CREATE OR REPLACE FUNCTION "if"(boolean, text, text) RETURNS text AS
      \'SELECT CASE WHEN $1 THEN $2 ELSE $3 END;\'
      LANGUAGE \'sql\''
    );
    db_query('CREATE OR REPLACE FUNCTION "if"(boolean, integer, integer) RETURNS integer AS
      \'SELECT CASE WHEN $1 THEN $2 ELSE $3 END;\'
      LANGUAGE \'sql\''
    );
354
  }
355

356
  // Create tables.
357
  $modules = array('system', 'filter', 'block', 'user', 'node', 'comment', 'taxonomy');
358 359
  foreach ($modules as $module) {
    drupal_install_schema($module);
360
  }
361

362 363
  // Load system theme data appropriately.
  system_theme_data();
364

365 366 367 368 369 370 371 372 373
  // Inserting uid 0 here confuses MySQL -- the next user might be created as
  // uid 2 which is not what we want. So we insert the first user here, the
  // anonymous user. uid is 1 here for now, but very soon it will be changed
  // to 0.
  db_query("INSERT INTO {users} (name, mail) VALUES('%s', '%s')", '', '');
  // We need some placeholders here as name and mail are uniques and data is
  // presumed to be a serialized array. Install will change uid 1 immediately
  // anyways. So we insert the superuser here, the uid is 2 here for now, but
  // very soon it will be changed to 1.
374
  db_query("INSERT INTO {users} (name, mail, created, status, data) VALUES('%s', '%s', %d, %d, '%s')", 'placeholder-for-uid-1', 'placeholder-for-uid-1', REQUEST_TIME, 1, serialize(array()));
375 376 377 378 379
  // This sets the above two users uid 0 (anonymous). We avoid an explicit 0
  // otherwise MySQL might insert the next auto_increment value.
  db_query("UPDATE {users} SET uid = uid - uid WHERE name = '%s'", '');
  // This sets uid 1 (superuser). We skip uid 2 but that's not a big problem.
  db_query("UPDATE {users} SET uid = 1 WHERE name = '%s'", 'placeholder-for-uid-1');
380

381
  // Built-in roles.
382 383
  db_query("INSERT INTO {role} (name) VALUES ('%s')", 'anonymous user');
  db_query("INSERT INTO {role} (name) VALUES ('%s')", 'authenticated user');
384

385 386 387 388 389 390 391 392
  // Anonymous role permissions.
  db_query("INSERT INTO {role_permission} (rid, permission) VALUES (%d, '%s')", 1, 'access content');

  // Authenticated role permissions.
  db_query("INSERT INTO {role_permission} (rid, permission) VALUES (%d, '%s')", 2, 'access comments');
  db_query("INSERT INTO {role_permission} (rid, permission) VALUES (%d, '%s')", 2, 'access content');
  db_query("INSERT INTO {role_permission} (rid, permission) VALUES (%d, '%s')", 2, 'post comments');
  db_query("INSERT INTO {role_permission} (rid, permission) VALUES (%d, '%s')", 2, 'post comments without approval');
393

394 395
  db_query("INSERT INTO {variable} (name, value) VALUES ('%s', '%s')", 'theme_default', 's:7:"garland";');
  db_query("UPDATE {system} SET status = %d WHERE type = '%s' AND name = '%s'", 1, 'theme', 'garland');
396 397 398
  db_query("INSERT INTO {blocks} (module, delta, theme, status, weight, region, pages, cache) VALUES ('%s', '%s', '%s', %d, %d, '%s', '%s', %d)", 'user', 'login', 'garland', 1, 0, 'left', '', -1);
  db_query("INSERT INTO {blocks} (module, delta, theme, status, weight, region, pages, cache) VALUES ('%s', '%s', '%s', %d, %d, '%s', '%s', %d)", 'user', 'navigation', 'garland', 1, 0, 'left', '', -1);
  db_query("INSERT INTO {blocks} (module, delta, theme, status, weight, region, pages, cache) VALUES ('%s', '%s', '%s', %d, %d, '%s', '%s', %d)", 'system', 'powered-by', 'garland', 1, 10, 'footer', '', -1);
399

400
  db_query("INSERT INTO {node_access} (nid, gid, realm, grant_view, grant_update, grant_delete) VALUES (%d, %d, '%s', %d, %d, %d)", 0, 0, 'all', 1, 0, 0);
401

402
  // Add input formats.
403 404
  db_query("INSERT INTO {filter_formats} (name, roles, cache) VALUES ('%s', '%s', %d)", 'Filtered HTML', ',1,2,', 1);
  db_query("INSERT INTO {filter_formats} (name, roles, cache) VALUES ('%s', '%s', %d)", 'Full HTML', '', 1);
405 406 407 408 409

  // Enable filters for each input format.

  // Filtered HTML:
  // URL filter.
410
  db_query("INSERT INTO {filters} (format, module, delta, weight) VALUES (%d, '%s', %d, %d)", 1, 'filter', 2, 0);
411
  // HTML filter.
412
  db_query("INSERT INTO {filters} (format, module, delta, weight) VALUES (%d, '%s', %d, %d)", 1, 'filter', 0, 1);
413
  // Line break filter.
414
  db_query("INSERT INTO {filters} (format, module, delta, weight) VALUES (%d, '%s', %d, %d)", 1, 'filter', 1, 2);
415
  // HTML corrector filter.
416
  db_query("INSERT INTO {filters} (format, module, delta, weight) VALUES (%d, '%s', %d, %d)", 1, 'filter', 3, 10);
417 418 419

  // Full HTML:
  // URL filter.
420
  db_query("INSERT INTO {filters} (format, module, delta, weight) VALUES (%d, '%s', %d, %d)", 2, 'filter', 2, 0);
421
  // Line break filter.
422
  db_query("INSERT INTO {filters} (format, module, delta, weight) VALUES (%d, '%s', %d, %d)", 2, 'filter', 1, 1);
423
  // HTML corrector filter.
424
  db_query("INSERT INTO {filters} (format, module, delta, weight) VALUES (%d, '%s', %d, %d)", 2, 'filter', 3, 10);
425

426
  db_query("INSERT INTO {variable} (name, value) VALUES ('%s','%s')", 'filter_html_1', 'i:1;');
427

428
  db_query("INSERT INTO {variable} (name, value) VALUES ('%s', '%s')", 'node_options_forum', 'a:1:{i:0;s:6:"status";}');
429

430
  $cron_key = serialize(md5(mt_rand()));
431

432
  db_query("INSERT INTO {variable} (name, value) VALUES ('%s', '%s')", 'cron_key', $cron_key);
433 434
}

435 436 437 438 439 440 441 442
/**
 * Implementation of hook_schema().
 */
function system_schema() {
  // NOTE: {variable} needs to be created before all other tables, as
  // some database drivers, e.g. Oracle and DB2, will require variable_get()
  // and variable_set() for overcoming some database specific limitations.
  $schema['variable'] = array(
443
    'description' => t('Named variable/value pairs created by Drupal core or any other module or theme. All variables are cached in memory at the start of every Drupal request so developers should not be careless about what is stored here.'),
444
    'fields' => array(
445 446 447 448 449
      'name' => array(
        'description' => t('The name of the variable.'),
        'type' => 'varchar',
        'length' => 128,
        'not null' => TRUE,
450 451
        'default' => '',
      ),
452 453 454 455
      'value' => array(
        'description' => t('The value of the variable.'),
        'type' => 'text',
        'not null' => TRUE,
456
        'size' => 'big',
457
      ),
458
    ),
459
    'primary key' => array('name'),
460
  );
461 462

  $schema['actions'] = array(
463
    'description' => t('Stores action information.'),
464
    'fields' => array(
465 466 467 468 469
      'aid' => array(
        'description' => t('Primary Key: Unique actions ID.'),
        'type' => 'varchar',
        'length' => 255,
        'not null' => TRUE,
470 471
        'default' => '0',
      ),
472 473 474 475 476
      'type' => array(
        'description' => t('The object that that action acts on (node, user, comment, system or custom types.)'),
        'type' => 'varchar',
        'length' => 32,
        'not null' => TRUE,
477 478
        'default' => '',
      ),
479 480 481 482 483
      'callback' => array(
        'description' => t('The callback function that executes when the action runs.'),
        'type' => 'varchar',
        'length' => 255,
        'not null' => TRUE,
484 485
        'default' => '',
      ),
486 487 488 489
      'parameters' => array(
        'description' => t('Parameters to be passed to the callback function.'),
        'type' => 'text',
        'not null' => TRUE,
490 491
        'size' => 'big',
      ),
492 493 494 495 496
      'description' => array(
        'description' => t('Description of the action.'),
        'type' => 'varchar',
        'length' => 255,
        'not null' => TRUE,
497
        'default' => '0',
498
      ),
499
    ),
500
    'primary key' => array('aid'),
501
  );
502 503

  $schema['actions_aid'] = array(
504
    'description' => t('Stores action IDs for non-default actions.'),
505
    'fields' => array(
506 507 508 509
      'aid' => array(
        'description' => t('Primary Key: Unique actions ID.'),
        'type' => 'serial',
        'unsigned' => TRUE,
510
        'not null' => TRUE,
511
      ),
512
    ),
513
    'primary key' => array('aid'),
514
  );
515 516

  $schema['batch'] = array(
517
    'description' => t('Stores details about batches (processes that run in multiple HTTP requests).'),
518
    'fields' => array(
519 520 521 522
      'bid' => array(
        'description' => t('Primary Key: Unique batch ID.'),
        'type' => 'serial',
        'unsigned' => TRUE,
523 524
        'not null' => TRUE,
      ),
525 526 527 528
      'token' => array(
        'description' => t("A string token generated against the current user's session id and the batch id, used to ensure that only the user who submitted the batch can effectively access it."),
        'type' => 'varchar',
        'length' => 64,
529 530
        'not null' => TRUE,
      ),
531 532 533
      'timestamp' => array(
        'description' => t('A Unix timestamp indicating when this batch was submitted for processing. Stale batches are purged at cron time.'),
        'type' => 'int',
534 535
        'not null' => TRUE,
      ),
536 537 538 539
      'batch' => array(
        'description' => t('A serialized array containing the processing data for the batch.'),
        'type' => 'text',
        'not null' => FALSE,
540
        'size' => 'big',
541
      ),
542
    ),
543
    'primary key' => array('bid'),
544 545 546 547
    'indexes' => array(
      'token' => array('token'),
    ),
  );
548

549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571
  $schema['blocked_ips'] = array(
    'description' => t('Stores blocked IP addresses.'),
    'fields' => array(
       'iid' => array(
        'description' => t('Primary Key: unique ID for IP addresses.'),
        'type' => 'serial',
        'unsigned' => TRUE,
        'not null' => TRUE,
      ),
      'ip' => array(
        'description' => t('IP address'),
        'type' => 'varchar',
        'length' => 32,
        'not null' => TRUE,
        'default' => '',
      ),
    ),
    'indexes' => array(
      'blocked_ip' => array('ip'),
    ),
    'primary key' => array('iid'),
  );

572
  $schema['cache'] = array(
573
    'description' => t('Generic cache table for caching things not separated out into their own tables. Contributed modules may also use this to store cached items.'),
574
    'fields' => array(
575 576 577 578 579
      'cid' => array(
        'description' => t('Primary Key: Unique cache ID.'),
        'type' => 'varchar',
        'length' => 255,
        'not null' => TRUE,
580 581
        'default' => '',
      ),
582 583 584 585
      'data' => array(
        'description' => t('A collection of data to cache.'),
        'type' => 'blob',
        'not null' => FALSE,
586 587
        'size' => 'big',
      ),
588 589 590 591
      'expire' => array(
        'description' => t('A Unix timestamp indicating when the cache entry should expire, or 0 for never.'),
        'type' => 'int',
        'not null' => TRUE,
592 593
        'default' => 0,
      ),
594 595 596 597
      'created' => array(
        'description' => t('A Unix timestamp indicating when the cache entry was created.'),
        'type' => 'int',
        'not null' => TRUE,
598 599
        'default' => 0,
      ),
600 601 602
      'headers' => array(
        'description' => t('Any custom HTTP headers to be added to cached data.'),
        'type' => 'text',
603 604
        'not null' => FALSE,
      ),
605 606 607 608 609
      'serialized' => array(
        'description' => t('A flag to indicate whether content is serialized (1) or not (0).'),
        'type' => 'int',
        'size' => 'small',
        'not null' => TRUE,
610
        'default' => 0,
611
      ),
612 613 614 615
    ),
    'indexes' => array(
      'expire' => array('expire'),
    ),
616
    'primary key' => array('cid'),
617
  );
618 619

  $schema['cache_form'] = $schema['cache'];
620
  $schema['cache_form']['description'] = t('Cache table for the form system to store recently built forms and their storage data, to be used in subsequent page requests.');
621
  $schema['cache_page'] = $schema['cache'];
622
  $schema['cache_page']['description'] = t('Cache table used to store compressed pages for anonymous users, if page caching is enabled.');
623
  $schema['cache_menu'] = $schema['cache'];
624
  $schema['cache_menu']['description'] = t('Cache table for the menu system to store router information as well as generated link trees for various menu/page/user combinations.');
625 626
  $schema['cache_registry'] = $schema['cache'];
  $schema['cache_registry']['description'] = t('Cache table for the code registry system to remember what code files need to be loaded on any given page.');
627 628

  $schema['files'] = array(
629
    'description' => t('Stores information for uploaded files.'),
630
    'fields' => array(
631
      'fid' => array(
632
        'description' => t('File ID.'),
633 634
        'type' => 'serial',
        'unsigned' => TRUE,
635 636
        'not null' => TRUE,
      ),
637 638 639 640 641
      'uid' => array(
        'description' => t('The {users}.uid of the user who is associated with the file.'),
        'type' => 'int',
        'unsigned' => TRUE,
        'not null' => TRUE,
642 643
        'default' => 0,
      ),
644
      'filename' => array(
645
        'description' => t('Name of the file with no path components. This may differ from the basename of the filepath if the file is renamed to avoid overwriting an existing file.'),
646 647 648
        'type' => 'varchar',
        'length' => 255,
        'not null' => TRUE,
649 650
        'default' => '',
      ),
651 652 653 654 655
      'filepath' => array(
        'description' => t('Path of the file relative to Drupal root.'),
        'type' => 'varchar',
        'length' => 255,
        'not null' => TRUE,
656 657
        'default' => '',
      ),
658
      'filemime' => array(
659
        'description' => t("The file's MIME type."),
660 661 662
        'type' => 'varchar',
        'length' => 255,
        'not null' => TRUE,
663 664
        'default' => '',
      ),
665 666 667 668 669
      'filesize' => array(
        'description' => t('The size of the file in bytes.'),
        'type' => 'int',
        'unsigned' => TRUE,
        'not null' => TRUE,
670 671
        'default' => 0,
      ),
672
      'status' => array(
673
        'description' => t('A bitmapped field indicating the status of the file the least sigifigant bit indicates temporary (1) or permanent (0). Temporary files older than DRUPAL_MAXIMUM_TEMP_FILE_AGE will be removed during a cron run.'),
674 675
        'type' => 'int',
        'not null' => TRUE,
676 677
        'default' => 0,
      ),
678 679 680 681 682
      'timestamp' => array(
        'description' => t('UNIX timestamp for when the file was added.'),
        'type' => 'int',
        'unsigned' => TRUE,
        'not null' => TRUE,
683
        'default' => 0,
684
      ),
685
    ),
686
    'indexes' => array(
687 688
      'uid' => array('uid'),
      'status' => array('status'),
689
      'timestamp' => array('timestamp'),
690
    ),
691
    'primary key' => array('fid'),
692
  );
693 694

  $schema['flood'] = array(
695
    'description' => t('Flood controls the threshold of events, such as the number of contact attempts.'),
696
    'fields' => array(
697 698 699
      'fid' => array(
        'description' => t('Unique flood event ID.'),
        'type' => 'serial',
700 701
        'not null' => TRUE,
      ),
702 703 704 705 706
      'event' => array(
        'description' => t('Name of event (e.g. contact).'),
        'type' => 'varchar',
        'length' => 64,
        'not null' => TRUE,
707 708
        'default' => '',
      ),
709 710 711 712 713
      'hostname' => array(
        'description' => t('Hostname of the visitor.'),
        'type' => 'varchar',
        'length' => 128,
        'not null' => TRUE,
714 715
        'default' => '',
      ),
716 717 718 719
      'timestamp' => array(
        'description' => t('Timestamp of the event.'),
        'type' => 'int',
        'not null' => TRUE,
720
        'default' => 0,
721
      ),
722
    ),
723
    'primary key' => array('fid'),
724 725 726
    'indexes' => array(
      'allow' => array('event', 'hostname', 'timestamp'),
    ),
727
  );
728 729

  $schema['history'] = array(
730
    'description' => t('A record of which {users} have read which {node}s.'),
731
    'fields' => array(
732 733 734 735
      'uid' => array(
        'description' => t('The {users}.uid that read the {node} nid.'),
        'type' => 'int',
        'not null' => TRUE,
736 737
        'default' => 0,
      ),
738 739 740 741
      'nid' => array(
        'description' => t('The {node}.nid that was read.'),
        'type' => 'int',
        'not null' => TRUE,
742 743
        'default' => 0,
      ),
744 745 746 747
      'timestamp' => array(
        'description' => t('The Unix timestamp at which the read occurred.'),
        'type' => 'int',
        'not null' => TRUE,
748
        'default' => 0,
749
      ),
750
    ),
751
    'primary key' => array('uid', 'nid'),
752 753 754
    'indexes' => array(
      'nid' => array('nid'),
    ),
755
  );
756
  $schema['menu_router'] = array(
757
    'description' => t('Maps paths to various callbacks (access, page and title)'),
758
    'fields' => array(
759 760 761 762 763
      'path' => array(
        'description' => t('Primary Key: the Drupal path this entry describes'),
        'type' => 'varchar',
        'length' => 255,
        'not null' => TRUE,
764 765
        'default' => '',
      ),
766 767
      'load_functions' => array(
        'description' => t('A serialized array of function names (like node_load) to be called to load an object corresponding to a part of the current path.'),
768
        'type' => 'text',
769
        'not null' => TRUE,
770
      ),
771
      'to_arg_functions' => array(
772
        'description' => t('A serialized array of function names (like user_uid_optional_to_arg) to be called to replace a part of the router path with another string.'),
773
        'type' => 'text',
774
        'not null' => TRUE,
775
      ),
776 777 778 779 780
      'access_callback' => array(
        'description' => t('The callback which determines the access to this router path. Defaults to user_access.'),
        'type' => 'varchar',
        'length' => 255,
        'not null' => TRUE,
781 782
        'default' => '',
      ),
783 784 785
      'access_arguments' => array(
        'description' => t('A serialized array of arguments for the access callback.'),
        'type' => 'text',
786 787
        'not null' => FALSE,
      ),
788 789 790 791 792
      'page_callback' => array(
        'description' => t('The name of the function that renders the page.'),
        'type' => 'varchar',
        'length' => 255,
        'not null' => TRUE,
793 794
        'default' => '',
      ),
795 796 797
      'page_arguments' => array(
        'description' => t('A serialized array of arguments for the page callback.'),
        'type' => 'text',
798 799
        'not null' => FALSE,
      ),
800 801 802 803
      'fit' => array(
        'description' => t('A numeric representation of how specific the path is.'),
        'type' => 'int',
        'not null' => TRUE,
804 805
        'default' => 0,
      ),
806 807 808 809 810
      'number_parts' => array(
        'description' => t('Number of parts in this router path.'),
        'type' => 'int',
        'not null' => TRUE,
        'default' => 0,
811 812
        'size' => 'small',
      ),
813 814 815 816 817
      'tab_parent' => array(
        'description' => t('Only for local tasks (tabs) - the router path of the parent page (which may also be a local task).'),
        'type' => 'varchar',
        'length' => 255,
        'not null' => TRUE,
818 819
        'default' => '',
      ),
820
      'tab_root' => array(
821
        'description' => t('Router path of the closest non-tab parent page. For pages that are not local tasks, this will be the same as the path.'),
822 823 824
        'type' => 'varchar',
        'length' => 255,
        'not null' => TRUE,
825 826
        'default' => '',
      ),
827 828 829 830 831
      'title' => array(
        'description' => t('The title for the current page, or the title for the tab if this is a local task.'),
        'type' => 'varchar',
        'length' => 255,
        'not null' => TRUE,
832 833
        'default' => '',
      ),
834 835 836 837 838
      'title_callback' => array(
        'description' => t('A function which will alter the title. Defaults to t()'),
        'type' => 'varchar',
        'length' => 255,
        'not null' => TRUE,
839 840
        'default' => '',
      ),
841
      'title_arguments' => array(
842
        'description' => t('A serialized array of arguments for the title callback. If empty, the title will be used as the sole argument for the title callback.'),
843 844 845
        'type' => 'varchar',
        'length' => 255,
        'not null' => TRUE,
846 847
        'default' => '',
      ),
848 849 850 851
      'type' => array(
        'description' => t('Numeric representation of the type of the menu item, like MENU_LOCAL_TASK.'),
        'type' => 'int',
        'not null' => TRUE,
852 853
        'default' => 0,
      ),
854 855 856 857 858
      'block_callback' => array(
        'description' => t('Name of a function used to render the block on the system administration page for this item.'),
        'type' => 'varchar',
        'length' => 255,
        'not null' => TRUE,
859 860
        'default' => '',
      ),
861 862 863
      'description' => array(
        'description' => t('A description of this item.'),
        'type' => 'text',
864 865
        'not null' => TRUE,
      ),
866 867 868 869 870
      'position' => array(
        'description' => t('The position of the block (left or right) on the system administration page for this item.'),
        'type' => 'varchar',
        'length' => 255,
        'not null' => TRUE,
871 872
        'default' => '',
      ),
873 874 875 876
      'weight' => array(
        'description' => t('Weight of the element. Lighter weights are higher up, heavier weights go down.'),
        'type' => 'int',
        'not null' => TRUE,
877 878 879
        'default' => 0,
      ),
    ),
880
    'indexes' => array(
881
      'fit' => array('fit'),
882 883
      'tab_parent' => array('tab_parent'),
    ),
884
    'primary key' => array('path'),
885
  );
886 887

  $schema['menu_links'] = array(
888
    'description' => t('Contains the individual links within a menu.'),
889
    'fields' => array(
890 891 892 893 894
     'menu_name' => array(
        'description' => t("The menu name. All links with the same menu name (such as 'navigation') are part of the same menu."),
        'type' => 'varchar',
        'length' => 32,
        'not null' => TRUE,
895 896
        'default' => '',
      ),
897 898 899 900
      'mlid' => array(
        'description' => t('The menu link ID (mlid) is the integer primary key.'),
        'type' => 'serial',
        'unsigned' => TRUE,
901 902
        'not null' => TRUE,
      ),
903 904 905 906 907
      'plid' => array(
        'description' => t('The parent link ID (plid) is the mlid of the link above in the hierarchy, or zero if the link is at the top level in its menu.'),
        'type' => 'int',
        'unsigned' => TRUE,
        'not null' => TRUE,
908 909
        'default' => 0,
      ),
910
      'link_path' => array(
911
        'description' => t('The Drupal path or external path this link points to.'),
912 913 914
        'type' => 'varchar',
        'length' => 255,
        'not null' => TRUE,
915 916
        'default' => '',
      ),
917 918 919 920 921
      'router_path' => array(
        'description' => t('For links corresponding to a Drupal path (external = 0), this connects the link to a {menu_router}.path for joins.'),
        'type' => 'varchar',
        'length' => 255,
        'not null' => TRUE,
922 923
        'default' => '',
      ),
924 925 926 927 928
      'link_title' => array(
      'description' => t('The text displayed for the link, which may be modified by a title callback stored in {menu_router}.'),
        'type' => 'varchar',
        'length' => 255,
        'not null' => TRUE,
929 930
        'default' => '',
      ),