install.inc 28.7 KB
Newer Older
1
2
<?php

3
use Drupal\Core\Database\Database;
4
use Drupal\locale\Gettext;
5

6
7
8
/**
 * Indicates that a module has not been installed yet.
 */
9
const SCHEMA_UNINSTALLED = -1;
10
11
12
13

/**
 * Indicates that a module has been installed.
 */
14
const SCHEMA_INSTALLED = 0;
15

16
17
18
/**
 * Requirement severity -- Informational message only.
 */
19
const REQUIREMENT_INFO = -1;
20
21
22
23

/**
 * Requirement severity -- Requirement successfully met.
 */
24
const REQUIREMENT_OK = 0;
25
26
27
28

/**
 * Requirement severity -- Warning condition; proceed but flag warning.
 */
29
const REQUIREMENT_WARNING = 1;
30
31
32
33

/**
 * Requirement severity -- Error condition; abort installation.
 */
34
const REQUIREMENT_ERROR = 2;
35

36
37
38
/**
 * File permission check -- File exists.
 */
39
const FILE_EXIST = 1;
40
41
42
43

/**
 * File permission check -- File is readable.
 */
44
const FILE_READABLE = 2;
45
46
47
48

/**
 * File permission check -- File is writable.
 */
49
const FILE_WRITABLE = 4;
50
51
52
53

/**
 * File permission check -- File is executable.
 */
54
const FILE_EXECUTABLE = 8;
55
56
57
58

/**
 * File permission check -- File does not exist.
 */
59
const FILE_NOT_EXIST = 16;
60
61
62
63

/**
 * File permission check -- File is not readable.
 */
64
const FILE_NOT_READABLE = 32;
65
66
67
68

/**
 * File permission check -- File is not writable.
 */
69
const FILE_NOT_WRITABLE = 64;
70
71
72
73

/**
 * File permission check -- File is not executable.
 */
74
const FILE_NOT_EXECUTABLE = 128;
75

76
77
78
79
/**
 * Initialize the update system by loading all installed module's .install files.
 */
function drupal_load_updates() {
80
81
82
83
  foreach (drupal_get_installed_schema_version(NULL, FALSE, TRUE) as $module => $schema_version) {
    if ($schema_version > -1) {
      module_load_install($module);
    }
84
85
86
  }
}

87
/**
88
 * Loads the install profile, extracting its defined distribution name.
89
90
 *
 * @return
91
92
93
94
 *   The distribution name defined in the profile's .info file. Defaults to
 *   "Drupal" if none is explicitly provided by the install profile.
 *
 * @see install_profile_info()
95
 */
96
function drupal_install_profile_distribution_name() {
97
98
99
100
101
102
103
104
105
  // During installation, the profile information is stored in the global
  // installation state (it might not be saved anywhere yet).
  if (drupal_installation_attempted()) {
    global $install_state;
    return $install_state['profile_info']['distribution_name'];
  }
  // At all other times, we load the profile via standard methods.
  else {
    $profile = drupal_get_profile();
106
    $info = system_get_info('module', $profile);
107
108
    return $info['distribution_name'];
  }
109
110
111
112
113
114
115
116
117
118
119
}

/**
 * Auto detect the base_url with PHP predefined variables.
 *
 * @param $file
 *   The name of the file calling this function so we can strip it out of
 *   the URI when generating the base_url.
 * @return
 *   The auto-detected $base_url that should be configured in settings.php
 */
120
function drupal_detect_baseurl($file = 'core/install.php') {
121
122
  $proto = $_SERVER['HTTPS'] ? 'https://' : 'http://';
  $host = $_SERVER['SERVER_NAME'];
123
  $port = ($_SERVER['SERVER_PORT'] == 80 ? '' : ':' . $_SERVER['SERVER_PORT']);
124
  $dir = str_replace("/$file", '', $_SERVER['SCRIPT_NAME']);
125
126
127
128
129

  return "$proto$host$port$dir";
}

/**
130
 * Detect all supported databases that are compiled into PHP.
131
132
133
134
135
 *
 * @return
 *  An array of database types compiled into PHP.
 */
function drupal_detect_database_types() {
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
  $databases = drupal_get_database_types();

  foreach ($databases as $driver => $installer) {
    $databases[$driver] = $installer->name();
  }

  return $databases;
}

/**
 * Return all supported database installer objects that are compiled into PHP.
 *
 * @return
 *  An array of database installer objects compiled into PHP.
 */
function drupal_get_database_types() {
152
  $databases = array();
153
  $drivers = array();
154

155
  // We define a driver as a directory in /core/includes/database that in turn
156
  // contains a database.inc file. That allows us to drop in additional drivers
157
158
159
  // without modifying the installer.
  // Because we have no registry yet, we need to also include the install.inc
  // file for the driver explicitly.
160
  require_once DRUPAL_ROOT . '/core/includes/database.inc';
161
  foreach (file_scan_directory(DRUPAL_ROOT . '/core/lib/Drupal/Core/Database/Driver', '/^[a-z]*$/i', array('recurse' => FALSE)) as $file) {
162
    if (file_exists($file->uri . '/Install/Tasks.php')) {
163
164
      $drivers[$file->filename] = $file->uri;
    }
165
166
167
  }

  foreach ($drivers as $driver => $file) {
168
    $installer = db_installer_object($driver);
169
    if ($installer->installable()) {
170
      $databases[$driver] = $installer;
171
172
    }
  }
173

174
  // Usability: unconditionally put the MySQL driver on top.
175
176
177
178
179
  if (isset($databases['mysql'])) {
    $mysql_database = $databases['mysql'];
    unset($databases['mysql']);
    $databases = array('mysql' => $mysql_database) + $databases;
  }
180

181
182
183
  return $databases;
}

184
/**
185
 * Replace values in settings.php with values in the submitted array.
186
187
188
189
 *
 * @param $settings
 *   An array of settings that need to be updated.
 */
190
function drupal_rewrite_settings($settings = array()) {
191
  drupal_static_reset('conf_path');
192
  $settings_file = conf_path(FALSE) . '/settings.php';
193
194
195
196
197
198
199
200
201

  // Build list of setting names and insert the values into the global namespace.
  $keys = array();
  foreach ($settings as $setting => $data) {
    $GLOBALS[$setting] = $data['value'];
    $keys[] = $setting;
  }

  $buffer = NULL;
202
203
204
205
206
207
208
209
210
211
212
213
214
215
  $contents = file_get_contents(DRUPAL_ROOT . '/' . $settings_file);
  if ($contents !== FALSE) {
    // Step through each token in settings.php and replace any variables that
    // are in the passed-in array.
    $replacing_variable = FALSE;
    foreach (token_get_all($contents) as $token) {
      // Strip off the leading "$" before comparing the variable name.
      if (is_array($token) && $token[0] == T_VARIABLE && ($variable_name = substr($token[1], 1)) && in_array($variable_name, $keys)) {
        // Write the new value to settings.php in the following format:
        //    $[setting] = '[value]'; // [comment]
        $setting = $settings[$variable_name];
        $buffer .= '$' . $variable_name . ' = ' . var_export($setting['value'], TRUE) . ';';
        if (!empty($setting['comment'])) {
          $buffer .= ' // ' . $setting['comment'];
216
        }
217
218
        unset($settings[$variable_name]);
        $replacing_variable = TRUE;
219
      }
220
221
222
223
224
      else {
        // Write a regular token (that is not part of a variable we're
        // replacing) to settings.php directly.
        if (!$replacing_variable) {
          $buffer .= is_array($token) ? $token[1] : $token;
225
        }
226
227
228
229
        // When we hit a semicolon, we are done with the code that defines the
        // variable that is being replaced.
        if ($token == ';') {
          $replacing_variable = FALSE;
230
231
232
233
234
235
        }
      }
    }

    // Add required settings that were missing from settings.php.
    foreach ($settings as $setting => $data) {
236
      if (!empty($data['required'])) {
237
        $buffer .= "\$$setting = " . var_export($data['value'], TRUE) . ";\n";
238
239
240
      }
    }

241
242
    // Write the new settings file.
    if (file_put_contents(DRUPAL_ROOT . '/' . $settings_file, $buffer) === FALSE) {
243
      throw new Exception(st('Failed to modify %settings. Verify the file permissions.', array('%settings' => $settings_file)));
244
245
246
    }
  }
  else {
247
    throw new Exception(st('Failed to open %settings. Verify the file permissions.', array('%settings' => $settings_file)));
248
249
250
  }
}

251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
/**
 * Creates the config directory and ensures it is operational.
 *
 * @see install_settings_form_submit()
 * @see update_prepare_d8_bootstrap()
 */
function drupal_install_config_directory() {
  global $config_directory_name;

  // Add a randomized config directory name to settings.php, unless it was
  // manually defined in the existing already.
  if (!$config_directory_name) {
    $settings['config_directory_name'] = array(
      'value' => 'config_' . drupal_hash_base64(drupal_random_bytes(55)),
      'required' => TRUE,
    );
    // Rewrite settings.php, which also sets the value as global variable.
    drupal_rewrite_settings($settings);
  }

  // Ensure that the config directory exists or can be created, and is writable.
  if (!install_ensure_config_directory()) {
    // This should never fail, since if the config directory was specified in
    // settings.php it will have already been created and verified earlier, and
    // if it wasn't specified in settings.php, it is created here inside the
    // public files directory, which has already been verified to be writable
    // itself. But if it somehow fails anyway, the installation cannot proceed.
    // Bail out using a similar error message as in system_requirements().
    throw new Exception(st('The directory %directory could not be created or could not be made writable. 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, see the <a href="@handbook_url">online handbook</a>.', array(
      '%directory' => config_get_config_directory(),
      '@handbook_url' => 'http://drupal.org/server-permissions',
    )));
  }
}

/**
 * Ensures that the config directory exists and is writable, or can be made so.
 */
function install_ensure_config_directory() {
  // The config directory must be defined in settings.php.
  global $config_directory_name;
  if (empty($config_directory_name)) {
    return FALSE;
  }
  // The logic here is similar to that used by system_requirements() for other
  // directories that the installer creates.
  else {
    $config_directory = config_get_config_directory();
    return file_prepare_directory($config_directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
  }
}

303
/**
304
 * Verify an install profile for installation.
305
 *
306
307
 * @param $install_state
 *   An array of information about the current installation state.
Steven Wittens's avatar
Steven Wittens committed
308
309
 * @return
 *   The list of modules to install.
310
 */
311
function drupal_verify_profile($install_state) {
312
313
  include_once DRUPAL_ROOT . '/core/includes/file.inc';
  include_once DRUPAL_ROOT . '/core/includes/common.inc';
314

315
316
  $profile = $install_state['parameters']['profile'];
  $profile_file = $install_state['profiles'][$profile]->uri;
317
318

  if (!isset($profile) || !file_exists($profile_file)) {
319
    throw new Exception(install_no_profile_error());
320
  }
321
  $info = $install_state['profile_info'];
322

323
324
  // Get a list of modules that exist in Drupal's assorted subdirectories.
  $present_modules = array();
325
  foreach (drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.module$/', 'modules', 'name', 0) as $present_module) {
326
327
328
    $present_modules[] = $present_module->name;
  }

329
330
331
332
  // The install profile is also a module, which needs to be installed after all the other dependencies
  // have been installed.
  $present_modules[] = drupal_get_profile();

333
  // Verify that all of the profile's required modules are present.
334
  $missing_modules = array_diff($info['dependencies'], $present_modules);
335
336

  $requirements = array();
337

338
  if (count($missing_modules)) {
339
    $modules = array();
340
    foreach ($missing_modules as $module) {
341
      $modules[] = '<span class="admin-missing">' . drupal_ucfirst($module) . '</span>';
Steven Wittens's avatar
Steven Wittens committed
342
    }
343
344
345
346
    $requirements['required_modules'] = array(
      'title'       => st('Required modules'),
      'value'       => st('Required modules not found.'),
      'severity'    => REQUIREMENT_ERROR,
347
      'description' => st('The following modules are required but were not found. Move them into the appropriate modules subdirectory, such as <em>sites/all/modules</em>. Missing modules: !modules', array('!modules' => implode(', ', $modules))),
348
    );
Steven Wittens's avatar
Steven Wittens committed
349
  }
350
  return $requirements;
Steven Wittens's avatar
Steven Wittens committed
351
}
352

353
354
355
356
357
358
359
/**
 * Callback to install the system module.
 *
 * Separated from the installation of other modules so core system
 * functions can be made available while other modules are installed.
 */
function drupal_install_system() {
360
361
362
  // Create tables.
  drupal_install_schema('system');

363
  $system_path = drupal_get_path('module', 'system');
364
  require_once DRUPAL_ROOT . '/' . $system_path . '/system.install';
Steven Wittens's avatar
Steven Wittens committed
365
366
  $system_versions = drupal_get_schema_versions('system');
  $system_version = $system_versions ? max($system_versions) : SCHEMA_INSTALLED;
367
  db_insert('system')
368
    ->fields(array('filename', 'name', 'type', 'owner', 'status', 'schema_version', 'bootstrap'))
369
370
371
372
373
374
    ->values(array(
        'filename' => $system_path . '/system.module',
        'name' => 'system',
        'type' => 'module',
        'owner' => '',
        'status' => 1,
375
376
377
        'schema_version' => $system_version,
        'bootstrap' => 0,
      ))
378
    ->execute();
379
380
381

  // Clear out module list and hook implementation statics before calling
  // system_rebuild_theme_data().
382
  system_list_reset();
383
384
385
  module_list_reset();
  module_implements_reset();

386
  system_rebuild_module_data();
387
388
  system_rebuild_theme_data();

389
  config_install_default_config('module', 'system');
390
391

  module_invoke('system', 'install');
392
393
}

394
395
396
397
398
399
400
401
402
403
/**
 * Verify the state of the specified file.
 *
 * @param $file
 *   The file to check for.
 * @param $mask
 *   An optional bitmask created from various FILE_* constants.
 * @param $type
 *   The type of file. Can be file (default), dir, or link.
 * @return
404
 *   TRUE on success or FALSE on failure. A message is set for the latter.
405
406
407
408
409
410
411
412
413
 */
function drupal_verify_install_file($file, $mask = NULL, $type = 'file') {
  $return = TRUE;
  // Check for files that shouldn't be there.
  if (isset($mask) && ($mask & FILE_NOT_EXIST) && file_exists($file)) {
    return FALSE;
  }
  // Verify that the file is the type of file it is supposed to be.
  if (isset($type) && file_exists($file)) {
414
    $check = 'is_' . $type;
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
    if (!function_exists($check) || !$check($file)) {
      $return = FALSE;
    }
  }

  // Verify file permissions.
  if (isset($mask)) {
    $masks = array(FILE_EXIST, FILE_READABLE, FILE_WRITABLE, FILE_EXECUTABLE, FILE_NOT_READABLE, FILE_NOT_WRITABLE, FILE_NOT_EXECUTABLE);
    foreach ($masks as $current_mask) {
      if ($mask & $current_mask) {
        switch ($current_mask) {
          case FILE_EXIST:
            if (!file_exists($file)) {
              if ($type == 'dir') {
                drupal_install_mkdir($file, $mask);
              }
              if (!file_exists($file)) {
                $return = FALSE;
              }
            }
            break;
          case FILE_READABLE:
            if (!is_readable($file) && !drupal_install_fix_file($file, $mask)) {
              $return = FALSE;
            }
            break;
          case FILE_WRITABLE:
            if (!is_writable($file) && !drupal_install_fix_file($file, $mask)) {
              $return = FALSE;
            }
            break;
          case FILE_EXECUTABLE:
            if (!is_executable($file) && !drupal_install_fix_file($file, $mask)) {
              $return = FALSE;
            }
            break;
          case FILE_NOT_READABLE:
            if (is_readable($file) && !drupal_install_fix_file($file, $mask)) {
              $return = FALSE;
            }
            break;
          case FILE_NOT_WRITABLE:
            if (is_writable($file) && !drupal_install_fix_file($file, $mask)) {
              $return = FALSE;
            }
            break;
          case FILE_NOT_EXECUTABLE:
            if (is_executable($file) && !drupal_install_fix_file($file, $mask)) {
              $return = FALSE;
            }
            break;
        }
      }
    }
  }
  return $return;
}

/**
 * Create a directory with specified permissions.
 *
476
 * @param $file
477
 *  The name of the directory to create;
478
 * @param $mask
479
480
481
482
483
484
485
486
487
488
489
490
491
 *  The permissions of the directory to create.
 * @param $message
 *  (optional) Whether to output messages. Defaults to TRUE.
 * @return
 *  TRUE/FALSE whether or not the directory was successfully created.
 */
function drupal_install_mkdir($file, $mask, $message = TRUE) {
  $mod = 0;
  $masks = array(FILE_READABLE, FILE_WRITABLE, FILE_EXECUTABLE, FILE_NOT_READABLE, FILE_NOT_WRITABLE, FILE_NOT_EXECUTABLE);
  foreach ($masks as $m) {
    if ($mask & $m) {
      switch ($m) {
        case FILE_READABLE:
492
          $mod |= 0444;
493
494
          break;
        case FILE_WRITABLE:
495
          $mod |= 0222;
496
497
          break;
        case FILE_EXECUTABLE:
498
          $mod |= 0111;
499
500
501
502
503
          break;
      }
    }
  }

504
  if (@drupal_mkdir($file, $mod)) {
505
506
507
508
509
510
511
512
513
514
    return TRUE;
  }
  else {
    return FALSE;
  }
}

/**
 * Attempt to fix file permissions.
 *
515
516
517
518
519
520
521
522
523
 * The general approach here is that, because we do not know the security
 * setup of the webserver, we apply our permission changes to all three
 * digits of the file permission (i.e. user, group and all).
 *
 * To ensure that the values behave as expected (and numbers don't carry
 * from one digit to the next) we do the calculation on the octal value
 * using bitwise operations. This lets us remove, for example, 0222 from
 * 0700 and get the correct value of 0500.
 *
524
525
526
527
528
529
530
531
532
533
 * @param $file
 *  The name of the file with permissions to fix.
 * @param $mask
 *  The desired permissions for the file.
 * @param $message
 *  (optional) Whether to output messages. Defaults to TRUE.
 * @return
 *  TRUE/FALSE whether or not we were able to fix the file's permissions.
 */
function drupal_install_fix_file($file, $mask, $message = TRUE) {
534
535
536
537
538
  // If $file does not exist, fileperms() issues a PHP warning.
  if (!file_exists($file)) {
    return FALSE;
  }

539
  $mod = fileperms($file) & 0777;
540
  $masks = array(FILE_READABLE, FILE_WRITABLE, FILE_EXECUTABLE, FILE_NOT_READABLE, FILE_NOT_WRITABLE, FILE_NOT_EXECUTABLE);
541
542
543
544
545

  // FILE_READABLE, FILE_WRITABLE, and FILE_EXECUTABLE permission strings
  // can theoretically be 0400, 0200, and 0100 respectively, but to be safe
  // we set all three access types in case the administrator intends to
  // change the owner of settings.php after installation.
546
547
548
549
550
  foreach ($masks as $m) {
    if ($mask & $m) {
      switch ($m) {
        case FILE_READABLE:
          if (!is_readable($file)) {
551
            $mod |= 0444;
552
553
554
555
          }
          break;
        case FILE_WRITABLE:
          if (!is_writable($file)) {
556
            $mod |= 0222;
557
558
559
560
          }
          break;
        case FILE_EXECUTABLE:
          if (!is_executable($file)) {
561
            $mod |= 0111;
562
563
564
565
          }
          break;
        case FILE_NOT_READABLE:
          if (is_readable($file)) {
566
            $mod &= ~0444;
567
568
569
570
          }
          break;
        case FILE_NOT_WRITABLE:
          if (is_writable($file)) {
571
            $mod &= ~0222;
572
573
574
575
          }
          break;
        case FILE_NOT_EXECUTABLE:
          if (is_executable($file)) {
576
            $mod &= ~0111;
577
578
579
580
581
582
          }
          break;
      }
    }
  }

583
584
585
586
  // chmod() will work if the web server is running as owner of the file.
  // If PHP safe_mode is enabled the currently executing script must also
  // have the same owner.
  if (@chmod($file, $mod)) {
587
588
589
590
591
592
593
    return TRUE;
  }
  else {
    return FALSE;
  }
}

594
/**
595
 * Send the user to a different installer page.
596
597
 *
 * This issues an on-site HTTP redirect. Messages (and errors) are erased.
598
599
600
601
602
 *
 * @param $path
 *   An installer path.
 */
function install_goto($path) {
603
  global $base_url;
604
  include_once DRUPAL_ROOT . '/core/includes/common.inc';
605
  header('Location: ' . $base_url . '/' . $path);
606
  header('Cache-Control: no-cache'); // Not a permanent redirect.
607
  drupal_exit();
608
609
}

610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
/**
 * Returns the URL of the current script, with modified query parameters.
 *
 * This function can be called by low-level scripts (such as install.php and
 * update.php) and returns the URL of the current script. Existing query
 * parameters are preserved by default, but new ones can optionally be merged
 * in.
 *
 * This function is used when the script must maintain certain query parameters
 * over multiple page requests in order to work correctly. In such cases (for
 * example, update.php, which requires the 'continue=1' parameter to remain in
 * the URL throughout the update process if there are any requirement warnings
 * that need to be bypassed), using this function to generate the URL for links
 * to the next steps of the script ensures that the links will work correctly.
 *
 * @param $query
 *   (optional) An array of query parameters to merge in to the existing ones.
 *
 * @return
 *   The URL of the current script, with query parameters modified by the
 *   passed-in $query. The URL is not sanitized, so it still needs to be run
 *   through check_url() if it will be used as an HTML attribute value.
 *
 * @see drupal_requirements_url()
 */
function drupal_current_script_url($query = array()) {
  $uri = $_SERVER['SCRIPT_NAME'];
  $query = array_merge(drupal_get_query_parameters(), $query);
  if (!empty($query)) {
    $uri .= '?' . drupal_http_build_query($query);
  }
  return $uri;
}

/**
 * Returns a URL for proceeding to the next page after a requirements problem.
 *
 * This function can be called by low-level scripts (such as install.php and
 * update.php) and returns a URL that can be used to attempt to proceed to the
 * next step of the script.
 *
 * @param $severity
 *   The severity of the requirements problem, as returned by
 *   drupal_requirements_severity().
 *
 * @return
 *   A URL for attempting to proceed to the next step of the script. The URL is
 *   not sanitized, so it still needs to be run through check_url() if it will
 *   be used as an HTML attribute value.
 *
 * @see drupal_current_script_url()
 */
function drupal_requirements_url($severity) {
  $query = array();
  // If there are no errors, only warnings, append 'continue=1' to the URL so
  // the user can bypass this screen on the next page load.
  if ($severity == REQUIREMENT_WARNING) {
    $query['continue'] = 1;
  }
  return drupal_current_script_url($query);
}

672
/**
673
674
675
 * Functional equivalent of t(), used when some systems are not available.
 *
 * Used during the install process, when database, theme, and localization
676
 * system is possibly not yet available.
677
 *
678
679
680
681
 * Use t() if your code will never run during the Drupal installation phase.
 * Use st() if your code will only run during installation and never any other
 * time. Use get_t() if your code could run in either circumstance.
 *
682
 * @see t()
683
 * @see get_t()
684
 * @ingroup sanitization
685
 */
686
function st($string, array $args = array(), array $options = array()) {
687
  static $strings = NULL;
688
  global $install_state;
drumm's avatar
drumm committed
689

690
691
692
693
  if (empty($options['context'])) {
    $options['context'] = '';
  }

694
695
  if (!isset($strings)) {
    $strings = array();
696
    if (isset($install_state['parameters']['langcode'])) {
697
698
      // If the given langcode was selected, there should be at least one .po
      // file with its name in the pattern drupal-$version.$langcode.po.
699
      // This might or might not be the entire filename. It is also possible
700
      // that multiple files end with the same suffix, even if unlikely.
701
702
      $files = install_find_translation_files($install_state['parameters']['langcode']);
      if (!empty($files)) {
703
704
705
        // Register locale classes with the classloader. Locale module is not
        // yet enabled at this stage, so this is not happening automatically.
        drupal_classloader_register('locale', drupal_get_path('module', 'locale'));
706
        $strings = Gettext::filesToArray($install_state['parameters']['langcode'], $files);
707
      }
708
    }
drumm's avatar
drumm committed
709
710
  }

711
  require_once DRUPAL_ROOT . '/core/includes/theme.inc';
712
  // Transform arguments before inserting them
713
  foreach ($args as $key => $value) {
714
715
716
717
718
719
720
721
    switch ($key[0]) {
      // Escaped only
      case '@':
        $args[$key] = check_plain($value);
        break;
      // Escaped and placeholder
      case '%':
      default:
722
        $args[$key] = '<em>' . check_plain($value) . '</em>';
723
724
725
726
        break;
      // Pass-through
      case '!':
    }
727
  }
728
  return strtr((!empty($strings[$options['context']][$string]) ? $strings[$options['context']][$string] : $string), $args);
729
}
730
731

/**
732
 * Check an install profile's requirements.
733
 *
734
 * @param string $profile
735
 *   Name of install profile to check.
736
737
738
739
 * @param array $install_state
 *   The current state in the install process.
 *
 * @return array
740
 *   Array of the install profile's requirements.
741
 */
742
function drupal_check_profile($profile, array $install_state) {
743
  include_once DRUPAL_ROOT . '/core/includes/file.inc';
744

745
  $profile_file = $install_state['profiles'][$profile]->uri;
746
747

  if (!isset($profile) || !file_exists($profile_file)) {
748
    throw new Exception(install_no_profile_error());
749
750
  }

751
  $info = install_profile_info($profile);
752

753
  // Collect requirement testing results.
754
  $requirements = array();
755
756
757
  foreach ($info['dependencies'] as $module) {
    module_load_install($module);
    $function = $module . '_requirements';
758
759
    if (function_exists($function)) {
      $requirements = array_merge($requirements, $function('install'));
760
    }
761
762
763
764
765
766
  }
  return $requirements;
}

/**
 * Extract highest severity from requirements array.
767
768
 *
 * @param $requirements
769
 *   An array of requirements, in the same format as is returned by
770
771
772
 *   hook_requirements().
 * @return
 *   The highest severity in the array.
773
774
775
776
777
778
779
780
781
782
783
784
785
 */
function drupal_requirements_severity(&$requirements) {
  $severity = REQUIREMENT_OK;
  foreach ($requirements as $requirement) {
    if (isset($requirement['severity'])) {
      $severity = max($severity, $requirement['severity']);
    }
  }
  return $severity;
}

/**
 * Check a module's requirements.
786
787
788
789
790
 *
 * @param $module
 *   Machine name of module to check.
 * @return
 *   TRUE/FALSE depending on the requirements are in place.
791
792
 */
function drupal_check_module($module) {
793
794
  module_load_install($module);
  if (module_hook($module, 'requirements')) {
795
796
797
798
799
800
    // Check requirements
    $requirements = module_invoke($module, 'requirements', 'install');
    if (is_array($requirements) && drupal_requirements_severity($requirements) == REQUIREMENT_ERROR) {
      // Print any error messages
      foreach ($requirements as $requirement) {
        if (isset($requirement['severity']) && $requirement['severity'] == REQUIREMENT_ERROR) {
801
802
          $message = $requirement['description'];
          if (isset($requirement['value']) && $requirement['value']) {
803
            $message .= ' (' . t('Currently using !item !version', array('!item' => $requirement['title'], '!version' => $requirement['value'])) . ')';
804
805
          }
          drupal_set_message($message, 'error');
806
807
808
809
810
811
812
        }
      }
      return FALSE;
    }
  }
  return TRUE;
}
813
814
815

/**
 * Retrieve info about an install profile from its .info file.
816
 *
817
818
 * The information stored in a profile .info file is similar to that stored in
 * a normal Drupal module .info file. For example:
819
820
821
822
 * - name: The real name of the install profile for display purposes.
 * - description: A brief description of the profile.
 * - dependencies: An array of shortnames of other modules this install profile requires.
 *
823
824
825
826
827
828
 * Additional, less commonly-used information that can appear in a profile.info
 * file but not in a normal Drupal module .info file includes:
 * - distribution_name: The name of the Drupal distribution that is being
 *   installed, to be shown throughout the installation process. Defaults to
 *   'Drupal'.
 *
829
830
831
832
 * Note that this function does an expensive file system scan to get info file
 * information for dependencies. If you only need information from the info
 * file itself, use system_get_info().
 *
833
 * Example of .info file:
834
 * @code
835
836
 *    name = Minimal
 *    description = Start fresh, with only a few modules enabled.
837
838
 *    dependencies[] = block
 *    dependencies[] = dblog
839
 * @endcode
840
 *
841
 * @param $profile
842
 *   Name of profile.
843
844
 * @param $langcode
 *   Language code (if any).
845
 *
846
847
 * @return
 *   The info array.
848
 */
849
function install_profile_info($profile, $langcode = 'en') {
850
851
852
853
854
855
856
  $cache = &drupal_static(__FUNCTION__, array());

  if (!isset($cache[$profile])) {
    // Set defaults for module info.
    $defaults = array(
      'dependencies' => array(),
      'description' => '',
857
      'distribution_name' => 'Drupal',
858
      'version' => NULL,
859
      'hidden' => FALSE,
860
861
      'php' => DRUPAL_MINIMUM_PHP,
    );
862
863
864
    $profile_file = drupal_get_path('profile', $profile) . "/$profile.info";
    $info = drupal_parse_info_file($profile_file);
    $info += $defaults;
865
    $info['dependencies'] = array_unique(array_merge(
866
867
      drupal_required_modules(),
      $info['dependencies'],
868
      ($langcode != 'en' && !empty($langcode) ? array('locale') : array()))
869
    );
870
871
872
873
874

    // drupal_required_modules() includes the current profile as a dependency.
    // Since a module can't depend on itself we remove that element of the array.
    array_shift($info['dependencies']);

875
876
877
    $cache[$profile] = $info;
  }
  return $cache[$profile];
878
879
}

880
881
882
883
884
/**
 * Ensures the environment for a Drupal database on a predefined connection.
 *
 * This will run tasks that check that Drupal can perform all of the functions
 * on a database, that Drupal needs. Tasks include simple checks like CREATE
885
 * TABLE to database specific functions like stored procedures and client
886
887
888
 * encoding.
 */
function db_run_tasks($driver) {
889
  db_installer_object($driver)->runTasks();
890
  return TRUE;
891
}
892
893
894
895
896
897
898
899

/**
 * Returns a database installer object.
 *
 * @param $driver
 *   The name of the driver.
 */
function db_installer_object($driver) {
900
901
  // We cannot use Database::getConnection->getDriverClass() here, because
  // the connection object is not yet functional.
902
  $task_class = "Drupal\\Core\\Database\\Driver\\{$driver}\\Install\\Tasks";
903
904
  return new $task_class();
}