Backup of db before drupal security update
[yaffs-website] / web / core / includes / install.core.inc
1 <?php
2
3 /**
4  * @file
5  * API functions for installing Drupal.
6  */
7
8 use Drupal\Component\Utility\UrlHelper;
9 use Drupal\Core\DrupalKernel;
10 use Drupal\Core\Database\Database;
11 use Drupal\Core\Database\DatabaseExceptionWrapper;
12 use Drupal\Core\Form\FormState;
13 use Drupal\Core\Installer\Exception\AlreadyInstalledException;
14 use Drupal\Core\Installer\Exception\InstallerException;
15 use Drupal\Core\Installer\Exception\InstallProfileMismatchException;
16 use Drupal\Core\Installer\Exception\NoProfilesException;
17 use Drupal\Core\Installer\InstallerKernel;
18 use Drupal\Core\Language\Language;
19 use Drupal\Core\Language\LanguageManager;
20 use Drupal\Core\Logger\LoggerChannelFactory;
21 use Drupal\Core\Site\Settings;
22 use Drupal\Core\StringTranslation\Translator\FileTranslation;
23 use Drupal\Core\StackMiddleware\ReverseProxyMiddleware;
24 use Drupal\Core\StreamWrapper\PublicStream;
25 use Drupal\Core\Extension\ExtensionDiscovery;
26 use Drupal\Core\DependencyInjection\ContainerBuilder;
27 use Drupal\Core\Url;
28 use Drupal\language\Entity\ConfigurableLanguage;
29 use Symfony\Cmf\Component\Routing\RouteObjectInterface;
30 use Symfony\Component\DependencyInjection\Reference;
31 use Symfony\Component\HttpFoundation\Request;
32 use Symfony\Component\HttpFoundation\Response;
33 use Symfony\Component\Routing\Route;
34 use Drupal\user\Entity\User;
35 use GuzzleHttp\Exception\RequestException;
36
37 /**
38  * Do not run the task during the current installation request.
39  *
40  * This can be used to skip running an installation task when certain
41  * conditions are met, even though the task may still show on the list of
42  * installation tasks presented to the user. For example, the Drupal installer
43  * uses this flag to skip over the database configuration form when valid
44  * database connection information is already available from settings.php. It
45  * also uses this flag to skip language import tasks when the installation is
46  * being performed in English.
47  */
48 const INSTALL_TASK_SKIP = 1;
49
50 /**
51  * Run the task on each installation request that reaches it.
52  *
53  * This is primarily used by the Drupal installer for bootstrap-related tasks.
54  */
55 const INSTALL_TASK_RUN_IF_REACHED = 2;
56
57 /**
58  * Run the task on each installation request until the database is set up.
59  *
60  * This is the default method for running tasks and should be used for most
61  * tasks that occur after the database is set up; these tasks will then run
62  * once and be marked complete once they are successfully finished. For
63  * example, the Drupal installer uses this flag for the batch installation of
64  * modules on the new site, and also for the configuration form that collects
65  * basic site information and sets up the site maintenance account.
66  */
67 const INSTALL_TASK_RUN_IF_NOT_COMPLETED = 3;
68
69 /**
70  * Installs Drupal either interactively or via an array of passed-in settings.
71  *
72  * The Drupal installation happens in a series of steps, which may be spread
73  * out over multiple page requests. Each request begins by trying to determine
74  * the last completed installation step (also known as a "task"), if one is
75  * available from a previous request. Control is then passed to the task
76  * handler, which processes the remaining tasks that need to be run until (a)
77  * an error is thrown, (b) a new page needs to be displayed, or (c) the
78  * installation finishes (whichever happens first).
79  *
80  * @param $class_loader
81  *   The class loader. Normally Composer's ClassLoader, as included by the
82  *   front controller, but may also be decorated; e.g.,
83  *   \Symfony\Component\ClassLoader\ApcClassLoader.
84  * @param $settings
85  *   An optional array of installation settings. Leave this empty for a normal,
86  *   interactive, browser-based installation intended to occur over multiple
87  *   page requests. Alternatively, if an array of settings is passed in, the
88  *   installer will attempt to use it to perform the installation in a single
89  *   page request (optimized for the command line) and not send any output
90  *   intended for the web browser. See install_state_defaults() for a list of
91  *   elements that are allowed to appear in this array.
92  *
93  * @see install_state_defaults()
94  */
95 function install_drupal($class_loader, $settings = []) {
96   // Support the old way of calling this function with just a settings array.
97   // @todo Remove this when Drush is updated in the Drupal testing
98   //   infrastructure in https://www.drupal.org/node/2389243
99   if (is_array($class_loader) && $settings === []) {
100     $settings = $class_loader;
101     $class_loader = require __DIR__ . '/../../autoload.php';
102   }
103
104   global $install_state;
105   // Initialize the installation state with the settings that were passed in,
106   // as well as a boolean indicating whether or not this is an interactive
107   // installation.
108   $interactive = empty($settings);
109   $install_state = $settings + ['interactive' => $interactive] + install_state_defaults();
110
111   try {
112     // Begin the page request. This adds information about the current state of
113     // the Drupal installation to the passed-in array.
114     install_begin_request($class_loader, $install_state);
115     // Based on the installation state, run the remaining tasks for this page
116     // request, and collect any output.
117     $output = install_run_tasks($install_state);
118   }
119   catch (InstallerException $e) {
120     // In the non-interactive installer, exceptions are always thrown directly.
121     if (!$install_state['interactive']) {
122       throw $e;
123     }
124     $output = [
125       '#title' => $e->getTitle(),
126       '#markup' => $e->getMessage(),
127     ];
128   }
129
130   // After execution, all tasks might be complete, in which case
131   // $install_state['installation_finished'] is TRUE. In case the last task
132   // has been processed, remove the global $install_state, so other code can
133   // reliably check whether it is running during the installer.
134   // @see drupal_installation_attempted()
135   $state = $install_state;
136   if (!empty($install_state['installation_finished'])) {
137     unset($GLOBALS['install_state']);
138   }
139
140   // All available tasks for this page request are now complete. Interactive
141   // installations can send output to the browser or redirect the user to the
142   // next page.
143   if ($state['interactive']) {
144     // If a session has been initiated in this request, make sure to save it.
145     if ($session = \Drupal::request()->getSession()) {
146       $session->save();
147     }
148     if ($state['parameters_changed']) {
149       // Redirect to the correct page if the URL parameters have changed.
150       install_goto(install_redirect_url($state));
151     }
152     elseif (isset($output)) {
153       // Display a page only if some output is available. Otherwise it is
154       // possible that we are printing a JSON page and theme output should
155       // not be shown.
156       install_display_output($output, $state);
157     }
158     elseif ($state['installation_finished']) {
159       // Redirect to the newly installed site.
160       install_goto('');
161     }
162   }
163 }
164
165 /**
166  * Returns an array of default settings for the global installation state.
167  *
168  * The installation state is initialized with these settings at the beginning
169  * of each page request. They may evolve during the page request, but they are
170  * initialized again once the next request begins.
171  *
172  * Non-interactive Drupal installations can override some of these default
173  * settings by passing in an array to the installation script, most notably
174  * 'parameters' (which contains one-time parameters such as 'profile' and
175  * 'langcode' that are normally passed in via the URL) and 'forms' (which can
176  * be used to programmatically submit forms during the installation; the keys
177  * of each element indicate the name of the installation task that the form
178  * submission is for, and the values are used as the $form_state->getValues()
179  * array that is passed on to the form submission via
180  * \Drupal::formBuilder()->submitForm()).
181  *
182  * @see \Drupal\Core\Form\FormBuilderInterface::submitForm()
183  */
184 function install_state_defaults() {
185   $defaults = [
186     // The current task being processed.
187     'active_task' => NULL,
188     // The last task that was completed during the previous installation
189     // request.
190     'completed_task' => NULL,
191     // TRUE when there are valid config directories.
192     'config_verified' => FALSE,
193     // TRUE when there is a valid database connection.
194     'database_verified' => FALSE,
195     // TRUE if database is empty & ready to install.
196     'database_ready' => FALSE,
197     // TRUE when a valid settings.php exists (containing both database
198     // connection information and config directory names).
199     'settings_verified' => FALSE,
200     // TRUE when the base system has been installed and is ready to operate.
201     'base_system_verified' => FALSE,
202     // Whether a translation file for the selected language will be downloaded
203     // from the translation server.
204     'download_translation' => FALSE,
205     // An array of forms to be programmatically submitted during the
206     // installation. The keys of each element indicate the name of the
207     // installation task that the form submission is for, and the values are
208     // used as the $form_state->getValues() array that is passed on to the form
209     // submission via \Drupal::formBuilder()->submitForm().
210     'forms' => [],
211     // This becomes TRUE only at the end of the installation process, after
212     // all available tasks have been completed and Drupal is fully installed.
213     // It is used by the installer to store correct information in the database
214     // about the completed installation, as well as to inform theme functions
215     // that all tasks are finished (so that the task list can be displayed
216     // correctly).
217     'installation_finished' => FALSE,
218     // Whether or not this installation is interactive. By default this will
219     // be set to FALSE if settings are passed in to install_drupal().
220     'interactive' => TRUE,
221     // An array of parameters for the installation, pre-populated by the URL
222     // or by the settings passed in to install_drupal(). This is primarily
223     // used to store 'profile' (the name of the chosen installation profile)
224     // and 'langcode' (the code of the chosen installation language), since
225     // these settings need to persist from page request to page request before
226     // the database is available for storage.
227     'parameters' => [],
228     // Whether or not the parameters have changed during the current page
229     // request. For interactive installations, this will trigger a page
230     // redirect.
231     'parameters_changed' => FALSE,
232     // An array of information about the chosen installation profile. This will
233     // be filled in based on the profile's .info.yml file.
234     'profile_info' => [],
235     // An array of available installation profiles.
236     'profiles' => [],
237     // The name of the theme to use during installation.
238     'theme' => 'seven',
239     // The server URL where the interface translation files can be downloaded.
240     // Tokens in the pattern will be replaced by appropriate values for the
241     // required translation file.
242     'server_pattern' => 'http://ftp.drupal.org/files/translations/%core/%project/%project-%version.%language.po',
243     // Installation tasks can set this to TRUE to force the page request to
244     // end (even if there is no themable output), in the case of an interactive
245     // installation. This is needed only rarely; for example, it would be used
246     // by an installation task that prints JSON output rather than returning a
247     // themed page. The most common example of this is during batch processing,
248     // but the Drupal installer automatically takes care of setting this
249     // parameter properly in that case, so that individual installation tasks
250     // which implement the batch API do not need to set it themselves.
251     'stop_page_request' => FALSE,
252     // Installation tasks can set this to TRUE to indicate that the task should
253     // be run again, even if it normally wouldn't be. This can be used, for
254     // example, if a single task needs to be spread out over multiple page
255     // requests, or if it needs to perform some validation before allowing
256     // itself to be marked complete. The most common examples of this are batch
257     // processing and form submissions, but the Drupal installer automatically
258     // takes care of setting this parameter properly in those cases, so that
259     // individual installation tasks which implement the batch API or form API
260     // do not need to set it themselves.
261     'task_not_complete' => FALSE,
262     // A list of installation tasks which have already been performed during
263     // the current page request.
264     'tasks_performed' => [],
265     // An array of translation files URIs available for the installation. Keyed
266     // by the translation language code.
267     'translations' => [],
268   ];
269   return $defaults;
270 }
271
272 /**
273  * Begins an installation request, modifying the installation state as needed.
274  *
275  * This function performs commands that must run at the beginning of every page
276  * request. It throws an exception if the installation should not proceed.
277  *
278  * @param $class_loader
279  *   The class loader. Normally Composer's ClassLoader, as included by the
280  *   front controller, but may also be decorated; e.g.,
281  *   \Symfony\Component\ClassLoader\ApcClassLoader.
282  * @param $install_state
283  *   An array of information about the current installation state. This is
284  *   modified with information gleaned from the beginning of the page request.
285  */
286 function install_begin_request($class_loader, &$install_state) {
287   $request = Request::createFromGlobals();
288
289   // Add any installation parameters passed in via the URL.
290   if ($install_state['interactive']) {
291     $install_state['parameters'] += $request->query->all();
292   }
293
294   // Validate certain core settings that are used throughout the installation.
295   if (!empty($install_state['parameters']['profile'])) {
296     $install_state['parameters']['profile'] = preg_replace('/[^a-zA-Z_0-9]/', '', $install_state['parameters']['profile']);
297   }
298   if (!empty($install_state['parameters']['langcode'])) {
299     $install_state['parameters']['langcode'] = preg_replace('/[^a-zA-Z_0-9\-]/', '', $install_state['parameters']['langcode']);
300   }
301
302   // Allow command line scripts to override server variables used by Drupal.
303   require_once __DIR__ . '/bootstrap.inc';
304
305   // Before having installed the system module and being able to do a module
306   // rebuild, prime the drupal_get_filename() static cache with the module's
307   // exact location.
308   // @todo Remove as part of https://www.drupal.org/node/2186491
309   drupal_get_filename('module', 'system', 'core/modules/system/system.info.yml');
310
311   // If the hash salt leaks, it becomes possible to forge a valid testing user
312   // agent, install a new copy of Drupal, and take over the original site.
313   // The user agent header is used to pass a database prefix in the request when
314   // running tests. However, for security reasons, it is imperative that no
315   // installation be permitted using such a prefix.
316   $user_agent = $request->cookies->get('SIMPLETEST_USER_AGENT') ?: $request->server->get('HTTP_USER_AGENT');
317   if ($install_state['interactive'] && strpos($user_agent, 'simpletest') !== FALSE && !drupal_valid_test_ua()) {
318     header($request->server->get('SERVER_PROTOCOL') . ' 403 Forbidden');
319     exit;
320   }
321
322   $site_path = DrupalKernel::findSitePath($request, FALSE);
323   Settings::initialize(dirname(dirname(__DIR__)), $site_path, $class_loader);
324
325   // Ensure that procedural dependencies are loaded as early as possible,
326   // since the error/exception handlers depend on them.
327   require_once __DIR__ . '/../modules/system/system.install';
328   require_once __DIR__ . '/common.inc';
329   require_once __DIR__ . '/file.inc';
330   require_once __DIR__ . '/install.inc';
331   require_once __DIR__ . '/schema.inc';
332   require_once __DIR__ . '/database.inc';
333   require_once __DIR__ . '/form.inc';
334   require_once __DIR__ . '/batch.inc';
335
336   // Load module basics (needed for hook invokes).
337   include_once __DIR__ . '/module.inc';
338   require_once __DIR__ . '/entity.inc';
339
340   // Create a minimal mocked container to support calls to t() in the pre-kernel
341   // base system verification code paths below. The strings are not actually
342   // used or output for these calls.
343   // @todo Separate API level checks from UI-facing error messages.
344   $container = new ContainerBuilder();
345   $container->setParameter('language.default_values', Language::$defaultValues);
346   $container
347     ->register('language.default', 'Drupal\Core\Language\LanguageDefault')
348     ->addArgument('%language.default_values%');
349   $container
350     ->register('string_translation', 'Drupal\Core\StringTranslation\TranslationManager')
351     ->addArgument(new Reference('language.default'));
352
353   // Register the stream wrapper manager.
354   $container
355     ->register('stream_wrapper_manager', 'Drupal\Core\StreamWrapper\StreamWrapperManager')
356     ->addMethodCall('setContainer', [new Reference('service_container')]);
357   $container
358     ->register('file_system', 'Drupal\Core\File\FileSystem')
359     ->addArgument(new Reference('stream_wrapper_manager'))
360     ->addArgument(Settings::getInstance())
361     ->addArgument((new LoggerChannelFactory())->get('file'));
362
363   \Drupal::setContainer($container);
364
365   // Determine whether base system services are ready to operate.
366   try {
367     $sync_directory = config_get_config_directory(CONFIG_SYNC_DIRECTORY);
368     $install_state['config_verified'] = file_exists($sync_directory);
369   }
370   catch (Exception $e) {
371     $install_state['config_verified'] = FALSE;
372   }
373   $install_state['database_verified'] = install_verify_database_settings($site_path);
374   // A valid settings.php has database settings and a hash_salt value. Other
375   // settings like config_directories will be checked by system_requirements().
376   $install_state['settings_verified'] = $install_state['database_verified'] && (bool) Settings::get('hash_salt', FALSE);
377
378   // Install factory tables only after checking the database.
379   if ($install_state['database_verified'] && $install_state['database_ready']) {
380     $container
381       ->register('path.matcher', 'Drupal\Core\Path\PathMatcher')
382       ->addArgument(new Reference('config.factory'));
383   }
384
385   if ($install_state['settings_verified']) {
386     try {
387       $system_schema = system_schema();
388       end($system_schema);
389       $table = key($system_schema);
390       $install_state['base_system_verified'] = Database::getConnection()->schema()->tableExists($table);
391     }
392     catch (DatabaseExceptionWrapper $e) {
393       // The last defined table of the base system_schema() does not exist yet.
394       // $install_state['base_system_verified'] defaults to FALSE, so the code
395       // following below will use the minimal installer service container.
396       // As soon as the base system is verified here, the installer operates in
397       // a full and regular Drupal environment, without any kind of exceptions.
398     }
399   }
400
401   // Replace services with in-memory and null implementations. This kernel is
402   // replaced with a regular one in drupal_install_system().
403   if (!$install_state['base_system_verified']) {
404     $environment = 'install';
405     $GLOBALS['conf']['container_service_providers']['InstallerServiceProvider'] = 'Drupal\Core\Installer\InstallerServiceProvider';
406   }
407   else {
408     $environment = 'prod';
409   }
410
411   // Only allow dumping the container once the hash salt has been created.
412   $kernel = InstallerKernel::createFromRequest($request, $class_loader, $environment, (bool) Settings::get('hash_salt', FALSE));
413   $kernel->setSitePath($site_path);
414   $kernel->boot();
415   $container = $kernel->getContainer();
416   // If Drupal is being installed behind a proxy, configure the request.
417   ReverseProxyMiddleware::setSettingsOnRequest($request, Settings::getInstance());
418
419   // Register the file translation service.
420   if (isset($GLOBALS['config']['locale.settings']['translation']['path'])) {
421     $directory = $GLOBALS['config']['locale.settings']['translation']['path'];
422   }
423   else {
424     $directory = $site_path . '/files/translations';
425   }
426   $container->set('string_translator.file_translation', new FileTranslation($directory));
427   $container->get('string_translation')
428     ->addTranslator($container->get('string_translator.file_translation'));
429
430   // Add list of all available profiles to the installation state.
431   $listing = new ExtensionDiscovery($container->get('app.root'));
432   $listing->setProfileDirectories([]);
433   $install_state['profiles'] += $listing->scan('profile');
434
435   // Prime drupal_get_filename()'s static cache.
436   foreach ($install_state['profiles'] as $name => $profile) {
437     drupal_get_filename('profile', $name, $profile->getPathname());
438   }
439
440   if ($profile = _install_select_profile($install_state)) {
441     $install_state['parameters']['profile'] = $profile;
442     install_load_profile($install_state);
443     if (isset($install_state['profile_info']['distribution']['install']['theme'])) {
444       $install_state['theme'] = $install_state['profile_info']['distribution']['install']['theme'];
445     }
446   }
447
448   // Use the language from the profile configuration, if available, to override
449   // the language previously set in the parameters.
450   if (isset($install_state['profile_info']['distribution']['langcode'])) {
451     $install_state['parameters']['langcode'] = $install_state['profile_info']['distribution']['langcode'];
452   }
453
454   // Set the default language to the selected language, if any.
455   if (isset($install_state['parameters']['langcode'])) {
456     $default_language = new Language(['id' => $install_state['parameters']['langcode']]);
457     $container->get('language.default')->set($default_language);
458     \Drupal::translation()->setDefaultLangcode($install_state['parameters']['langcode']);
459   }
460
461   // Override the module list with a minimal set of modules.
462   $module_handler = \Drupal::moduleHandler();
463   if (!$module_handler->moduleExists('system')) {
464     $module_handler->addModule('system', 'core/modules/system');
465   }
466   if ($profile && !$module_handler->moduleExists($profile)) {
467     $module_handler->addProfile($profile, $install_state['profiles'][$profile]->getPath());
468   }
469
470   // Load all modules and perform request related initialization.
471   $kernel->preHandle($request);
472
473   // Initialize a route on this legacy request similar to
474   // \Drupal\Core\DrupalKernel::prepareLegacyRequest() since normal routing
475   // will not happen.
476   $request->attributes->set(RouteObjectInterface::ROUTE_OBJECT, new Route('<none>'));
477   $request->attributes->set(RouteObjectInterface::ROUTE_NAME, '<none>');
478
479   // Prepare for themed output. We need to run this at the beginning of the
480   // page request to avoid a different theme accidentally getting set. (We also
481   // need to run it even in the case of command-line installations, to prevent
482   // any code in the installer that happens to initialize the theme system from
483   // accessing the database before it is set up yet.)
484   drupal_maintenance_theme();
485
486   if ($install_state['database_verified']) {
487     // Verify the last completed task in the database, if there is one.
488     $task = install_verify_completed_task();
489   }
490   else {
491     $task = NULL;
492
493     // Do not install over a configured settings.php.
494     if (Database::getConnectionInfo()) {
495       throw new AlreadyInstalledException($container->get('string_translation'));
496     }
497   }
498
499   // Ensure that the active configuration is empty before installation starts.
500   if ($install_state['config_verified'] && empty($task)) {
501     if (count($kernel->getConfigStorage()->listAll())) {
502       $task = NULL;
503       throw new AlreadyInstalledException($container->get('string_translation'));
504     }
505   }
506
507   // Modify the installation state as appropriate.
508   $install_state['completed_task'] = $task;
509 }
510
511 /**
512  * Runs all tasks for the current installation request.
513  *
514  * In the case of an interactive installation, all tasks will be attempted
515  * until one is reached that has output which needs to be displayed to the
516  * user, or until a page redirect is required. Otherwise, tasks will be
517  * attempted until the installation is finished.
518  *
519  * @param $install_state
520  *   An array of information about the current installation state. This is
521  *   passed along to each task, so it can be modified if necessary.
522  *
523  * @return
524  *   HTML output from the last completed task.
525  */
526 function install_run_tasks(&$install_state) {
527   do {
528     // Obtain a list of tasks to perform. The list of tasks itself can be
529     // dynamic (e.g., some might be defined by the installation profile,
530     // which is not necessarily known until the earlier tasks have run),
531     // so we regenerate the remaining tasks based on the installation state,
532     // each time through the loop.
533     $tasks_to_perform = install_tasks_to_perform($install_state);
534     // Run the first task on the list.
535     reset($tasks_to_perform);
536     $task_name = key($tasks_to_perform);
537     $task = array_shift($tasks_to_perform);
538     $install_state['active_task'] = $task_name;
539     $original_parameters = $install_state['parameters'];
540     $output = install_run_task($task, $install_state);
541     // Ensure the maintenance theme is initialized. If the install task has
542     // rebuilt the container the active theme will not be set. This can occur if
543     // the task has installed a module.
544     drupal_maintenance_theme();
545
546     $install_state['parameters_changed'] = ($install_state['parameters'] != $original_parameters);
547     // Store this task as having been performed during the current request,
548     // and save it to the database as completed, if we need to and if the
549     // database is in a state that allows us to do so. Also mark the
550     // installation as 'done' when we have run out of tasks.
551     if (!$install_state['task_not_complete']) {
552       $install_state['tasks_performed'][] = $task_name;
553       $install_state['installation_finished'] = empty($tasks_to_perform);
554       if ($task['run'] == INSTALL_TASK_RUN_IF_NOT_COMPLETED || $install_state['installation_finished']) {
555         \Drupal::state()->set('install_task', $install_state['installation_finished'] ? 'done' : $task_name);
556       }
557     }
558     // Stop when there are no tasks left. In the case of an interactive
559     // installation, also stop if we have some output to send to the browser,
560     // the URL parameters have changed, or an end to the page request was
561     // specifically called for.
562     $finished = empty($tasks_to_perform) || ($install_state['interactive'] && (isset($output) || $install_state['parameters_changed'] || $install_state['stop_page_request']));
563   } while (!$finished);
564   return $output;
565 }
566
567 /**
568  * Runs an individual installation task.
569  *
570  * @param $task
571  *   An array of information about the task to be run as returned by
572  *   hook_install_tasks().
573  * @param $install_state
574  *   An array of information about the current installation state. This is
575  *   passed in by reference so that it can be modified by the task.
576  *
577  * @return
578  *   The output of the task function, if there is any.
579  */
580 function install_run_task($task, &$install_state) {
581   $function = $task['function'];
582
583   if ($task['type'] == 'form') {
584     return install_get_form($function, $install_state);
585   }
586   elseif ($task['type'] == 'batch') {
587     // Start a new batch based on the task function, if one is not running
588     // already.
589     $current_batch = \Drupal::state()->get('install_current_batch');
590     if (!$install_state['interactive'] || !$current_batch) {
591       $batches = $function($install_state);
592       if (empty($batches)) {
593         // If the task did some processing and decided no batch was necessary,
594         // there is nothing more to do here.
595         return;
596       }
597       // Create a one item list of batches if only one batch was provided.
598       if (isset($batches['operations'])) {
599         $batches = [$batches];
600       }
601       foreach ($batches as $batch) {
602         batch_set($batch);
603         // For interactive batches, we need to store the fact that this batch
604         // task is currently running. Otherwise, we need to make sure the batch
605         // will complete in one page request.
606         if ($install_state['interactive']) {
607           \Drupal::state()->set('install_current_batch', $function);
608         }
609         else {
610           $batch =& batch_get();
611           $batch['progressive'] = FALSE;
612         }
613       }
614       // Process the batch. For progressive batches, this will redirect.
615       // Otherwise, the batch will complete.
616       // Disable the default script for the URL and clone the object, as
617       // batch_process() will add additional options to the batch URL.
618       $url = Url::fromUri('base:install.php', ['query' => $install_state['parameters'], 'script' => '']);
619       $response = batch_process($url, clone $url);
620       if ($response instanceof Response) {
621         if ($session = \Drupal::request()->getSession()) {
622           $session->save();
623         }
624         // Send the response.
625         $response->send();
626         exit;
627       }
628     }
629     // If we are in the middle of processing this batch, keep sending back
630     // any output from the batch process, until the task is complete.
631     elseif ($current_batch == $function) {
632       $output = _batch_page(\Drupal::request());
633       // Because Batch API now returns a JSON response for intermediary steps,
634       // but the installer doesn't handle Response objects yet, just send the
635       // output here and emulate the old model.
636       // @todo Replace this when we refactor the installer to use a request-
637       //   response workflow.
638       if ($output instanceof Response) {
639         $output->send();
640         $output = NULL;
641       }
642       // The task is complete when we try to access the batch page and receive
643       // FALSE in return, since this means we are at a URL where we are no
644       // longer requesting a batch ID.
645       if ($output === FALSE) {
646         // Return nothing so the next task will run in the same request.
647         \Drupal::state()->delete('install_current_batch');
648         return;
649       }
650       else {
651         // We need to force the page request to end if the task is not
652         // complete, since the batch API sometimes prints JSON output
653         // rather than returning a themed page.
654         $install_state['task_not_complete'] = $install_state['stop_page_request'] = TRUE;
655         return $output;
656       }
657     }
658   }
659
660   else {
661     // For normal tasks, just return the function result, whatever it is.
662     return $function($install_state);
663   }
664 }
665
666 /**
667  * Returns a list of tasks to perform during the current installation request.
668  *
669  * Note that the list of tasks can change based on the installation state as
670  * the page request evolves (for example, if an installation profile hasn't
671  * been selected yet, we don't yet know which profile tasks need to be run).
672  *
673  * @param $install_state
674  *   An array of information about the current installation state.
675  *
676  * @return
677  *   A list of tasks to be performed, with associated metadata.
678  */
679 function install_tasks_to_perform($install_state) {
680   // Start with a list of all currently available tasks.
681   $tasks = install_tasks($install_state);
682   foreach ($tasks as $name => $task) {
683     // Remove any tasks that were already performed or that never should run.
684     // Also, if we started this page request with an indication of the last
685     // task that was completed, skip that task and all those that come before
686     // it, unless they are marked as always needing to run.
687     if ($task['run'] == INSTALL_TASK_SKIP || in_array($name, $install_state['tasks_performed']) || (!empty($install_state['completed_task']) && empty($completed_task_found) && $task['run'] != INSTALL_TASK_RUN_IF_REACHED)) {
688       unset($tasks[$name]);
689     }
690     if (!empty($install_state['completed_task']) && $name == $install_state['completed_task']) {
691       $completed_task_found = TRUE;
692     }
693   }
694   return $tasks;
695 }
696
697 /**
698  * Returns a list of all tasks the installer currently knows about.
699  *
700  * This function will return tasks regardless of whether or not they are
701  * intended to run on the current page request. However, the list can change
702  * based on the installation state (for example, if an installation profile
703  * hasn't been selected yet, we don't yet know which profile tasks will be
704  * available).
705  *
706  * You can override this using hook_install_tasks() or
707  * hook_install_tasks_alter().
708  *
709  * @param $install_state
710  *   An array of information about the current installation state.
711  *
712  * @return
713  *   A list of tasks, with associated metadata as returned by
714  *   hook_install_tasks().
715  */
716 function install_tasks($install_state) {
717   // Determine whether a translation file must be imported during the
718   // 'install_import_translations' task. Import when a non-English language is
719   // available and selected. Also we will need translations even if the
720   // installer language is English but there are other languages on the system.
721   $needs_translations = (count($install_state['translations']) > 1 && !empty($install_state['parameters']['langcode']) && $install_state['parameters']['langcode'] != 'en') || \Drupal::languageManager()->isMultilingual();
722   // Determine whether a translation file must be downloaded during the
723   // 'install_download_translation' task. Download when a non-English language
724   // is selected, but no translation is yet in the translations directory.
725   $needs_download = isset($install_state['parameters']['langcode']) && !isset($install_state['translations'][$install_state['parameters']['langcode']]) && $install_state['parameters']['langcode'] != 'en';
726
727   // Start with the core installation tasks that run before handing control
728   // to the installation profile.
729   $tasks = [
730     'install_select_language' => [
731       'display_name' => t('Choose language'),
732       'run' => INSTALL_TASK_RUN_IF_REACHED,
733     ],
734     'install_download_translation' => [
735       'run' => $needs_download ? INSTALL_TASK_RUN_IF_REACHED : INSTALL_TASK_SKIP,
736     ],
737     'install_select_profile' => [
738       'display_name' => t('Choose profile'),
739       'display' => empty($install_state['profile_info']['distribution']['name']) && count($install_state['profiles']) != 1,
740       'run' => INSTALL_TASK_RUN_IF_REACHED,
741     ],
742     'install_load_profile' => [
743       'run' => INSTALL_TASK_RUN_IF_REACHED,
744     ],
745     'install_verify_requirements' => [
746       'display_name' => t('Verify requirements'),
747     ],
748     'install_settings_form' => [
749       'display_name' => t('Set up database'),
750       'type' => 'form',
751       // Even though the form only allows the user to enter database settings,
752       // we still need to display it if settings.php is invalid in any way,
753       // since the form submit handler is where settings.php is rewritten.
754       'run' => $install_state['settings_verified'] ? INSTALL_TASK_SKIP : INSTALL_TASK_RUN_IF_NOT_COMPLETED,
755       'function' => 'Drupal\Core\Installer\Form\SiteSettingsForm',
756     ],
757     'install_write_profile' => [
758     ],
759     'install_verify_database_ready' => [
760       'run' => $install_state['database_ready'] ? INSTALL_TASK_SKIP : INSTALL_TASK_RUN_IF_NOT_COMPLETED,
761     ],
762     'install_base_system' => [
763       'run' => $install_state['base_system_verified'] ? INSTALL_TASK_SKIP : INSTALL_TASK_RUN_IF_NOT_COMPLETED,
764     ],
765     // All tasks below are executed in a regular, full Drupal environment.
766     'install_bootstrap_full' => [
767       'run' => INSTALL_TASK_RUN_IF_REACHED,
768     ],
769     'install_profile_modules' => [
770       'display_name' => t('Install site'),
771       'type' => 'batch',
772     ],
773     'install_profile_themes' => [
774     ],
775     'install_install_profile' => [
776     ],
777     'install_import_translations' => [
778       'display_name' => t('Set up translations'),
779       'display' => $needs_translations,
780       'type' => 'batch',
781       'run' => $needs_translations ? INSTALL_TASK_RUN_IF_NOT_COMPLETED : INSTALL_TASK_SKIP,
782     ],
783     'install_configure_form' => [
784       'display_name' => t('Configure site'),
785       'type' => 'form',
786       'function' => 'Drupal\Core\Installer\Form\SiteConfigureForm',
787     ],
788   ];
789
790   // Now add any tasks defined by the installation profile.
791   if (!empty($install_state['parameters']['profile'])) {
792     // Load the profile install file, because it is not always loaded when
793     // hook_install_tasks() is invoked (e.g. batch processing).
794     $profile = $install_state['parameters']['profile'];
795     $profile_install_file = $install_state['profiles'][$profile]->getPath() . '/' . $profile . '.install';
796     if (file_exists($profile_install_file)) {
797       include_once \Drupal::root() . '/' . $profile_install_file;
798     }
799     $function = $install_state['parameters']['profile'] . '_install_tasks';
800     if (function_exists($function)) {
801       $result = $function($install_state);
802       if (is_array($result)) {
803         $tasks += $result;
804       }
805     }
806   }
807
808   // Finish by adding the remaining core tasks.
809   $tasks += [
810     'install_finish_translations' => [
811       'display_name' => t('Finish translations'),
812       'display' => $needs_translations,
813       'type' => 'batch',
814       'run' => $needs_translations ? INSTALL_TASK_RUN_IF_NOT_COMPLETED : INSTALL_TASK_SKIP,
815     ],
816     'install_finished' => [
817     ],
818   ];
819
820   // Allow the installation profile to modify the full list of tasks.
821   if (!empty($install_state['parameters']['profile'])) {
822     $profile = $install_state['parameters']['profile'];
823     if ($install_state['profiles'][$profile]->load()) {
824       $function = $install_state['parameters']['profile'] . '_install_tasks_alter';
825       if (function_exists($function)) {
826         $function($tasks, $install_state);
827       }
828     }
829   }
830
831   // Fill in default parameters for each task before returning the list.
832   foreach ($tasks as $task_name => &$task) {
833     $task += [
834       'display_name' => NULL,
835       'display' => !empty($task['display_name']),
836       'type' => 'normal',
837       'run' => INSTALL_TASK_RUN_IF_NOT_COMPLETED,
838       'function' => $task_name,
839     ];
840   }
841   return $tasks;
842 }
843
844 /**
845  * Returns a list of tasks that should be displayed to the end user.
846  *
847  * The output of this function is a list suitable for sending to
848  * maintenance-task-list.html.twig.
849  *
850  * @param $install_state
851  *   An array of information about the current installation state.
852  *
853  * @return
854  *   A list of tasks, with keys equal to the machine-readable task name and
855  *   values equal to the name that should be displayed.
856  *
857  * @see maintenance-task-list.html.twig
858  */
859 function install_tasks_to_display($install_state) {
860   $displayed_tasks = [];
861   foreach (install_tasks($install_state) as $name => $task) {
862     if ($task['display']) {
863       $displayed_tasks[$name] = $task['display_name'];
864     }
865   }
866   return $displayed_tasks;
867 }
868
869 /**
870  * Builds and processes a form for the installer environment.
871  *
872  * Ensures that FormBuilder does not redirect after submitting a form, since the
873  * installer uses a custom step/flow logic via install_run_tasks().
874  *
875  * @param string|array $form_id
876  *   The form ID to build and process.
877  * @param array $install_state
878  *   The current state of the installation.
879  *
880  * @return array|null
881  *   A render array containing the form to render, or NULL in case the form was
882  *   successfully submitted.
883  *
884  * @throws \Drupal\Core\Installer\Exception\InstallerException
885  */
886 function install_get_form($form_id, array &$install_state) {
887   // Ensure the form will not redirect, since install_run_tasks() uses a custom
888   // redirection logic.
889   $form_state = (new FormState())
890     ->addBuildInfo('args', [&$install_state])
891     ->disableRedirect();
892   $form_builder = \Drupal::formBuilder();
893   if ($install_state['interactive']) {
894     $form = $form_builder->buildForm($form_id, $form_state);
895     // If the form submission was not successful, the form needs to be rendered,
896     // which means the task is not complete yet.
897     if (!$form_state->isExecuted()) {
898       $install_state['task_not_complete'] = TRUE;
899       return $form;
900     }
901   }
902   else {
903     // For non-interactive installs, submit the form programmatically with the
904     // values taken from the installation state.
905     $install_form_id = $form_builder->getFormId($form_id, $form_state);
906     if (!empty($install_state['forms'][$install_form_id])) {
907       $form_state->setValues($install_state['forms'][$install_form_id]);
908     }
909     $form_builder->submitForm($form_id, $form_state);
910
911     // Throw an exception in case of any form validation error.
912     if ($errors = $form_state->getErrors()) {
913       throw new InstallerException(implode("\n", $errors));
914     }
915   }
916 }
917
918 /**
919  * Returns the URL that should be redirected to during an installation request.
920  *
921  * The output of this function is suitable for sending to install_goto().
922  *
923  * @param $install_state
924  *   An array of information about the current installation state.
925  *
926  * @return
927  *   The URL to redirect to.
928  *
929  * @see install_full_redirect_url()
930  */
931 function install_redirect_url($install_state) {
932   return 'core/install.php?' . UrlHelper::buildQuery($install_state['parameters']);
933 }
934
935 /**
936  * Returns the complete URL redirected to during an installation request.
937  *
938  * @param $install_state
939  *   An array of information about the current installation state.
940  *
941  * @return
942  *   The complete URL to redirect to.
943  *
944  * @see install_redirect_url()
945  */
946 function install_full_redirect_url($install_state) {
947   global $base_url;
948   return $base_url . '/' . install_redirect_url($install_state);
949 }
950
951 /**
952  * Displays themed installer output and ends the page request.
953  *
954  * Installation tasks should use #title to set the desired page
955  * title, but otherwise this function takes care of theming the overall page
956  * output during every step of the installation.
957  *
958  * @param $output
959  *   The content to display on the main part of the page.
960  * @param $install_state
961  *   An array of information about the current installation state.
962  */
963 function install_display_output($output, $install_state) {
964   // Ensure the maintenance theme is initialized.
965   // The regular initialization call in install_begin_request() may not be
966   // reached in case of an early installer error.
967   drupal_maintenance_theme();
968
969   // Prevent install.php from being indexed when installed in a sub folder.
970   // robots.txt rules are not read if the site is within domain.com/subfolder
971   // resulting in /subfolder/install.php being found through search engines.
972   // When settings.php is writeable this can be used via an external database
973   // leading a malicious user to gain php access to the server.
974   $noindex_meta_tag = [
975     '#tag' => 'meta',
976     '#attributes' => [
977       'name' => 'robots',
978       'content' => 'noindex, nofollow',
979     ],
980   ];
981   $output['#attached']['html_head'][] = [$noindex_meta_tag, 'install_meta_robots'];
982
983   // Only show the task list if there is an active task; otherwise, the page
984   // request has ended before tasks have even been started, so there is nothing
985   // meaningful to show.
986   $regions = [];
987   if (isset($install_state['active_task'])) {
988     // Let the theming function know when every step of the installation has
989     // been completed.
990     $active_task = $install_state['installation_finished'] ? NULL : $install_state['active_task'];
991     $task_list = [
992       '#theme' => 'maintenance_task_list',
993       '#items' => install_tasks_to_display($install_state),
994       '#active' => $active_task,
995     ];
996     $regions['sidebar_first'] = $task_list;
997   }
998
999   $bare_html_page_renderer = \Drupal::service('bare_html_page_renderer');
1000   $response = $bare_html_page_renderer->renderBarePage($output, $output['#title'], 'install_page', $regions);
1001   $default_headers = [
1002     'Expires' => 'Sun, 19 Nov 1978 05:00:00 GMT',
1003     'Last-Modified' => gmdate(DATE_RFC1123, REQUEST_TIME),
1004     'Cache-Control' => 'no-cache, must-revalidate',
1005     'ETag' => '"' . REQUEST_TIME . '"',
1006   ];
1007   $response->headers->add($default_headers);
1008   $response->send();
1009   exit;
1010 }
1011
1012 /**
1013  * Verifies the requirements for installing Drupal.
1014  *
1015  * @param $install_state
1016  *   An array of information about the current installation state.
1017  *
1018  * @return
1019  *   A themed status report, or an exception if there are requirement errors.
1020  */
1021 function install_verify_requirements(&$install_state) {
1022   // Check the installation requirements for Drupal and this profile.
1023   $requirements = install_check_requirements($install_state);
1024
1025   // Verify existence of all required modules.
1026   $requirements += drupal_verify_profile($install_state);
1027
1028   return install_display_requirements($install_state, $requirements);
1029 }
1030
1031 /**
1032  * Installation task; install the base functionality Drupal needs to bootstrap.
1033  *
1034  * @param $install_state
1035  *   An array of information about the current installation state.
1036  */
1037 function install_base_system(&$install_state) {
1038   // Install system.module.
1039   drupal_install_system($install_state);
1040
1041   // Prevent the installer from using the system temporary directory after the
1042   // system module has been installed.
1043   if (drupal_valid_test_ua()) {
1044     // While the temporary directory could be preset/enforced in settings.php
1045     // like the public files directory, some tests expect it to be configurable
1046     // in the UI. If declared in settings.php, they would no longer be
1047     // configurable. The temporary directory needs to match what is set in each
1048     // test types ::prepareEnvironment() step.
1049     $temporary_directory = dirname(PublicStream::basePath()) . '/temp';
1050     file_prepare_directory($temporary_directory, FILE_MODIFY_PERMISSIONS | FILE_CREATE_DIRECTORY);
1051     \Drupal::configFactory()->getEditable('system.file')
1052       ->set('path.temporary', $temporary_directory)
1053       ->save();
1054   }
1055
1056   // Call file_ensure_htaccess() to ensure that all of Drupal's standard
1057   // directories (e.g., the public files directory and config directory) have
1058   // appropriate .htaccess files. These directories will have already been
1059   // created by this point in the installer, since Drupal creates them during
1060   // the install_verify_requirements() task. Note that we cannot call
1061   // file_ensure_access() any earlier than this, since it relies on
1062   // system.module in order to work.
1063   file_ensure_htaccess();
1064
1065   // Prime the drupal_get_filename() static cache with the user module's
1066   // exact location.
1067   // @todo Remove as part of https://www.drupal.org/node/2186491
1068   drupal_get_filename('module', 'user', 'core/modules/user/user.info.yml');
1069
1070   // Enable the user module so that sessions can be recorded during the
1071   // upcoming bootstrap step.
1072   \Drupal::service('module_installer')->install(['user'], FALSE);
1073
1074   // Save the list of other modules to install for the upcoming tasks.
1075   // State can be set to the database now that system.module is installed.
1076   $modules = $install_state['profile_info']['dependencies'];
1077
1078   \Drupal::state()->set('install_profile_modules', array_diff($modules, ['system']));
1079   $install_state['base_system_verified'] = TRUE;
1080 }
1081
1082 /**
1083  * Verifies and returns the last installation task that was completed.
1084  *
1085  * @return
1086  *   The last completed task, if there is one. An exception is thrown if Drupal
1087  *   is already installed.
1088  */
1089 function install_verify_completed_task() {
1090   try {
1091     $task = \Drupal::state()->get('install_task');
1092   }
1093   // Do not trigger an error if the database query fails, since the database
1094   // might not be set up yet.
1095   catch (\Exception $e) {
1096   }
1097   if (isset($task)) {
1098     if ($task == 'done') {
1099       throw new AlreadyInstalledException(\Drupal::service('string_translation'));
1100     }
1101     return $task;
1102   }
1103 }
1104
1105 /**
1106  * Verifies that settings.php specifies a valid database connection.
1107  *
1108  * @param string $site_path
1109  *   The site path.
1110  *
1111  * @return bool
1112  *   TRUE if there are no database errors.
1113  */
1114 function install_verify_database_settings($site_path) {
1115   if ($database = Database::getConnectionInfo()) {
1116     $database = $database['default'];
1117     $settings_file = './' . $site_path . '/settings.php';
1118     $errors = install_database_errors($database, $settings_file);
1119     if (empty($errors)) {
1120       return TRUE;
1121     }
1122   }
1123   return FALSE;
1124 }
1125
1126 /**
1127  * Verify that the database is ready (no existing Drupal installation).
1128  */
1129 function install_verify_database_ready() {
1130   $system_schema = system_schema();
1131   end($system_schema);
1132   $table = key($system_schema);
1133
1134   if ($database = Database::getConnectionInfo()) {
1135     if (Database::getConnection()->schema()->tableExists($table)) {
1136       throw new AlreadyInstalledException(\Drupal::service('string_translation'));
1137     }
1138   }
1139 }
1140
1141 /**
1142  * Checks a database connection and returns any errors.
1143  */
1144 function install_database_errors($database, $settings_file) {
1145   $errors = [];
1146
1147   // Check database type.
1148   $database_types = drupal_get_database_types();
1149   $driver = $database['driver'];
1150   if (!isset($database_types[$driver])) {
1151     $errors['driver'] = t("In your %settings_file file you have configured @drupal to use a %driver server, however your PHP installation currently does not support this database type.", ['%settings_file' => $settings_file, '@drupal' => drupal_install_profile_distribution_name(), '%driver' => $driver]);
1152   }
1153   else {
1154     // Run driver specific validation
1155     $errors += $database_types[$driver]->validateDatabaseSettings($database);
1156     if (!empty($errors)) {
1157       // No point to try further.
1158       return $errors;
1159     }
1160     // Run tasks associated with the database type. Any errors are caught in the
1161     // calling function.
1162     Database::addConnectionInfo('default', 'default', $database);
1163
1164     $errors = db_installer_object($driver)->runTasks();
1165   }
1166   return $errors;
1167 }
1168
1169 /**
1170  * Selects which profile to install.
1171  *
1172  * @param $install_state
1173  *   An array of information about the current installation state. The chosen
1174  *   profile will be added here, if it was not already selected previously, as
1175  *   will a list of all available profiles.
1176  *
1177  * @return
1178  *   For interactive installations, a form allowing the profile to be selected,
1179  *   if the user has a choice that needs to be made. Otherwise, an exception is
1180  *   thrown if a profile cannot be chosen automatically.
1181  */
1182 function install_select_profile(&$install_state) {
1183   if (empty($install_state['parameters']['profile'])) {
1184     // If there are no profiles at all, installation cannot proceed.
1185     if (empty($install_state['profiles'])) {
1186       throw new NoProfilesException(\Drupal::service('string_translation'));
1187     }
1188     // Try to automatically select a profile.
1189     if ($profile = _install_select_profile($install_state)) {
1190       $install_state['parameters']['profile'] = $profile;
1191     }
1192     else {
1193       // The non-interactive installer requires a profile parameter.
1194       if (!$install_state['interactive']) {
1195         throw new InstallerException(t('Missing profile parameter.'));
1196       }
1197       // Otherwise, display a form to select a profile.
1198       return install_get_form('Drupal\Core\Installer\Form\SelectProfileForm', $install_state);
1199     }
1200   }
1201 }
1202
1203 /**
1204  * Determines the installation profile to use in the installer.
1205  *
1206  * A profile will be selected in the following order of conditions:
1207  * - Only one profile is available.
1208  * - A specific profile name is requested in installation parameters:
1209  *   - For interactive installations via request query parameters.
1210  *   - For non-interactive installations via install_drupal() settings.
1211  * - A discovered profile that is a distribution. If multiple profiles are
1212  *   distributions, then the first discovered profile will be selected.
1213  * - Only one visible profile is available.
1214  *
1215  * @param array $install_state
1216  *   The current installer state, containing a 'profiles' key, which is an
1217  *   associative array of profiles with the machine-readable names as keys.
1218  *
1219  * @return
1220  *   The machine-readable name of the selected profile or NULL if no profile was
1221  *   selected.
1222  */
1223 function _install_select_profile(&$install_state) {
1224   // Don't need to choose profile if only one available.
1225   if (count($install_state['profiles']) == 1) {
1226     return key($install_state['profiles']);
1227   }
1228   if (!empty($install_state['parameters']['profile'])) {
1229     $profile = $install_state['parameters']['profile'];
1230     if (isset($install_state['profiles'][$profile])) {
1231       return $profile;
1232     }
1233   }
1234   // Check for a distribution profile.
1235   foreach ($install_state['profiles'] as $profile) {
1236     $profile_info = install_profile_info($profile->getName());
1237     if (!empty($profile_info['distribution'])) {
1238       return $profile->getName();
1239     }
1240   }
1241
1242   // Get all visible (not hidden) profiles.
1243   $visible_profiles = array_filter($install_state['profiles'], function ($profile) {
1244     $profile_info = install_profile_info($profile->getName());
1245     return !isset($profile_info['hidden']) || !$profile_info['hidden'];
1246   });
1247
1248   if (count($visible_profiles) == 1) {
1249     return (key($visible_profiles));
1250   }
1251 }
1252
1253 /**
1254  * Finds all .po files that are useful to the installer.
1255  *
1256  * @return
1257  *   An associative array of file URIs keyed by language code. URIs as
1258  *   returned by file_scan_directory().
1259  *
1260  * @see file_scan_directory()
1261  */
1262 function install_find_translations() {
1263   $translations = [];
1264   $files = \Drupal::service('string_translator.file_translation')->findTranslationFiles();
1265   // English does not need a translation file.
1266   array_unshift($files, (object) ['name' => 'en']);
1267   foreach ($files as $uri => $file) {
1268     // Strip off the file name component before the language code.
1269     $langcode = preg_replace('!^(.+\.)?([^\.]+)$!', '\2', $file->name);
1270     // Language codes cannot exceed 12 characters to fit into the {language}
1271     // table.
1272     if (strlen($langcode) <= 12) {
1273       $translations[$langcode] = $uri;
1274     }
1275   }
1276   return $translations;
1277 }
1278
1279 /**
1280  * Selects which language to use during installation.
1281  *
1282  * @param $install_state
1283  *   An array of information about the current installation state. The chosen
1284  *   langcode will be added here, if it was not already selected previously, as
1285  *   will a list of all available languages.
1286  *
1287  * @return
1288  *   For interactive installations, a form or other page output allowing the
1289  *   language to be selected or providing information about language selection,
1290  *   if a language has not been chosen. Otherwise, an exception is thrown if a
1291  *   language cannot be chosen automatically.
1292  */
1293 function install_select_language(&$install_state) {
1294   // Find all available translation files.
1295   $files = install_find_translations();
1296   $install_state['translations'] += $files;
1297
1298   // If a valid language code is set, continue with the next installation step.
1299   // When translations from the localization server are used, any language code
1300   // is accepted because the standard language list is kept in sync with the
1301   // languages available at http://localize.drupal.org.
1302   // When files from the translation directory are used, we only accept
1303   // languages for which a file is available.
1304   if (!empty($install_state['parameters']['langcode'])) {
1305     $standard_languages = LanguageManager::getStandardLanguageList();
1306     $langcode = $install_state['parameters']['langcode'];
1307     if ($langcode == 'en' || isset($files[$langcode]) || isset($standard_languages[$langcode])) {
1308       $install_state['parameters']['langcode'] = $langcode;
1309       return;
1310     }
1311   }
1312
1313   if (empty($install_state['parameters']['langcode'])) {
1314     // If we are performing an interactive installation, we display a form to
1315     // select a right language. If no translation files were found in the
1316     // translations directory, the form shows a list of standard languages. If
1317     // translation files were found the form shows a select list of the
1318     // corresponding languages to choose from.
1319     if ($install_state['interactive']) {
1320       return install_get_form('Drupal\Core\Installer\Form\SelectLanguageForm', $install_state);
1321     }
1322     // If we are performing a non-interactive installation. If only one language
1323     // (English) is available, assume the user knows what he is doing. Otherwise
1324     // throw an error.
1325     else {
1326       if (count($files) == 1) {
1327         $install_state['parameters']['langcode'] = current(array_keys($files));
1328         return;
1329       }
1330       else {
1331         throw new InstallerException(t('You must select a language to continue the installation.'));
1332       }
1333     }
1334   }
1335 }
1336
1337 /**
1338  * Download a translation file for the selected language.
1339  *
1340  * @param array $install_state
1341  *   An array of information about the current installation state.
1342  *
1343  * @return string
1344  *   A themed status report, or an exception if there are requirement errors.
1345  *   Upon successful download the page is reloaded and no output is returned.
1346  */
1347 function install_download_translation(&$install_state) {
1348   // Check whether all conditions are met to download. Download the translation
1349   // if possible.
1350   $requirements = install_check_translations($install_state['parameters']['langcode'], $install_state['server_pattern']);
1351   if ($output = install_display_requirements($install_state, $requirements)) {
1352     return $output;
1353   }
1354
1355   // The download was successful, reload the page in the new language.
1356   $install_state['translations'][$install_state['parameters']['langcode']] = TRUE;
1357   if ($install_state['interactive']) {
1358     install_goto(install_redirect_url($install_state));
1359   }
1360 }
1361
1362 /**
1363  * Attempts to get a file using a HTTP request and to store it locally.
1364  *
1365  * @param string $uri
1366  *   The URI of the file to grab.
1367  * @param string $destination
1368  *   Stream wrapper URI specifying where the file should be placed. If a
1369  *   directory path is provided, the file is saved into that directory under its
1370  *   original name. If the path contains a filename as well, that one will be
1371  *   used instead.
1372  *
1373  * @return bool
1374  *   TRUE on success, FALSE on failure.
1375  */
1376 function install_retrieve_file($uri, $destination) {
1377   $parsed_url = parse_url($uri);
1378   if (is_dir(drupal_realpath($destination))) {
1379     // Prevent URIs with triple slashes when gluing parts together.
1380     $path = str_replace('///', '//', "$destination/") . drupal_basename($parsed_url['path']);
1381   }
1382   else {
1383     $path = $destination;
1384   }
1385
1386   try {
1387     $response = \Drupal::httpClient()->get($uri, ['headers' => ['Accept' => 'text/plain']]);
1388     $data = (string) $response->getBody();
1389     if (empty($data)) {
1390       return FALSE;
1391     }
1392   }
1393   catch (RequestException $e) {
1394     return FALSE;
1395   }
1396   return file_put_contents($path, $data) !== FALSE;
1397 }
1398
1399 /**
1400  * Checks if the localization server can be contacted.
1401  *
1402  * @param string $uri
1403  *   The URI to contact.
1404  *
1405  * @return string
1406  *   TRUE if the URI was contacted successfully, FALSE if not.
1407  */
1408 function install_check_localization_server($uri) {
1409   try {
1410     \Drupal::httpClient()->head($uri);
1411     return TRUE;
1412   }
1413   catch (RequestException $e) {
1414     return FALSE;
1415   }
1416 }
1417
1418 /**
1419  * Extracts version information from a drupal core version string.
1420  *
1421  * @param string $version
1422  *   Version info string (e.g., 8.0.0, 8.1.0, 8.0.0-dev, 8.0.0-unstable1,
1423  *   8.0.0-alpha2, 8.0.0-beta3, and 8.0.0-rc4).
1424  *
1425  * @return array
1426  *   Associative array of version info:
1427  *   - major: Major version (e.g., "8").
1428  *   - minor: Minor version (e.g., "0").
1429  *   - patch: Patch version (e.g., "0").
1430  *   - extra: Extra version info (e.g., "alpha2").
1431  *   - extra_text: The text part of "extra" (e.g., "alpha").
1432  *   - extra_number: The number part of "extra" (e.g., "2").
1433  */
1434 function _install_get_version_info($version) {
1435   preg_match('/
1436     (
1437       (?P<major>[0-9]+)    # Major release number.
1438       \.          # .
1439       (?P<minor>[0-9]+)    # Minor release number.
1440       \.          # .
1441       (?P<patch>[0-9]+)    # Patch release number.
1442     )             #
1443     (             #
1444       -           # - separator for "extra" version information.
1445       (?P<extra>   #
1446         (?P<extra_text>[a-z]+)  # Release extra text (e.g., "alpha").
1447         (?P<extra_number>[0-9]*)  # Release extra number (no separator between text and number).
1448       )           #
1449       |           # OR no "extra" information.
1450     )
1451     /sx', $version, $matches);
1452
1453   return $matches;
1454 }
1455
1456 /**
1457  * Loads information about the chosen profile during installation.
1458  *
1459  * @param $install_state
1460  *   An array of information about the current installation state. The loaded
1461  *   profile information will be added here.
1462  */
1463 function install_load_profile(&$install_state) {
1464   $profile = $install_state['parameters']['profile'];
1465   $install_state['profiles'][$profile]->load();
1466   $install_state['profile_info'] = install_profile_info($profile, isset($install_state['parameters']['langcode']) ? $install_state['parameters']['langcode'] : 'en');
1467 }
1468
1469 /**
1470  * Performs a full bootstrap of Drupal during installation.
1471  */
1472 function install_bootstrap_full() {
1473   // Store the session on the request object and start it.
1474   /** @var \Symfony\Component\HttpFoundation\Session\SessionInterface $session */
1475   $session = \Drupal::service('session');
1476   \Drupal::request()->setSession($session);
1477   $session->start();
1478 }
1479
1480 /**
1481  * Installs required modules via a batch process.
1482  *
1483  * @param $install_state
1484  *   An array of information about the current installation state.
1485  *
1486  * @return
1487  *   The batch definition.
1488  */
1489 function install_profile_modules(&$install_state) {
1490   // We need to manually trigger the installation of core-provided entity types,
1491   // as those will not be handled by the module installer.
1492   install_core_entity_type_definitions();
1493
1494   $modules = \Drupal::state()->get('install_profile_modules') ?: [];
1495   $files = system_rebuild_module_data();
1496   \Drupal::state()->delete('install_profile_modules');
1497
1498   // Always install required modules first. Respect the dependencies between
1499   // the modules.
1500   $required = [];
1501   $non_required = [];
1502
1503   // Add modules that other modules depend on.
1504   foreach ($modules as $module) {
1505     if ($files[$module]->requires) {
1506       $modules = array_merge($modules, array_keys($files[$module]->requires));
1507     }
1508   }
1509   $modules = array_unique($modules);
1510   foreach ($modules as $module) {
1511     if (!empty($files[$module]->info['required'])) {
1512       $required[$module] = $files[$module]->sort;
1513     }
1514     else {
1515       $non_required[$module] = $files[$module]->sort;
1516     }
1517   }
1518   arsort($required);
1519   arsort($non_required);
1520
1521   $operations = [];
1522   foreach ($required + $non_required as $module => $weight) {
1523     $operations[] = ['_install_module_batch', [$module, $files[$module]->info['name']]];
1524   }
1525   $batch = [
1526     'operations' => $operations,
1527     'title' => t('Installing @drupal', ['@drupal' => drupal_install_profile_distribution_name()]),
1528     'error_message' => t('The installation has encountered an error.'),
1529   ];
1530   return $batch;
1531 }
1532
1533 /**
1534  * Installs entity type definitions provided by core.
1535  */
1536 function install_core_entity_type_definitions() {
1537   $update_manager = \Drupal::entityDefinitionUpdateManager();
1538   foreach (\Drupal::entityManager()->getDefinitions() as $entity_type) {
1539     if ($entity_type->getProvider() == 'core') {
1540       $update_manager->installEntityType($entity_type);
1541     }
1542   }
1543 }
1544
1545 /**
1546  * Installs themes.
1547  *
1548  * This does not use a batch, since installing themes is faster than modules and
1549  * because an installation profile typically installs 1-3 themes only (default
1550  * theme, base theme, admin theme).
1551  *
1552  * @param $install_state
1553  *   An array of information about the current installation state.
1554  */
1555 function install_profile_themes(&$install_state) {
1556   // Install the themes specified by the installation profile.
1557   $themes = $install_state['profile_info']['themes'];
1558   \Drupal::service('theme_handler')->install($themes);
1559
1560   // Ensure that the install profile's theme is used.
1561   // @see _drupal_maintenance_theme()
1562   \Drupal::theme()->resetActiveTheme();
1563 }
1564
1565 /**
1566  * Installs the install profile.
1567  *
1568  * @param $install_state
1569  *   An array of information about the current installation state.
1570  */
1571 function install_install_profile(&$install_state) {
1572   \Drupal::service('module_installer')->install([drupal_get_profile()], FALSE);
1573   // Install all available optional config. During installation the module order
1574   // is determined by dependencies. If there are no dependencies between modules
1575   // then the order in which they are installed is dependent on random factors
1576   // like PHP version. Optional configuration therefore might or might not be
1577   // created depending on this order. Ensuring that we have installed all of the
1578   // optional configuration whose dependencies can be met at this point removes
1579   // any disparities that this creates.
1580   \Drupal::service('config.installer')->installOptionalConfig();
1581
1582   // Ensure that the install profile's theme is used.
1583   // @see _drupal_maintenance_theme()
1584   \Drupal::theme()->resetActiveTheme();
1585 }
1586
1587 /**
1588  * Prepares the system for import and downloads additional translations.
1589  *
1590  * @param $install_state
1591  *   An array of information about the current installation state.
1592  *
1593  * @return
1594  *   The batch definition, if there are language files to download.
1595  */
1596 function install_download_additional_translations_operations(&$install_state) {
1597   \Drupal::moduleHandler()->loadInclude('locale', 'bulk.inc');
1598
1599   $langcode = $install_state['parameters']['langcode'];
1600   if (!($language = ConfigurableLanguage::load($langcode))) {
1601     // Create the language if not already shipped with a profile.
1602     $language = ConfigurableLanguage::createFromLangcode($langcode);
1603   }
1604   $language->save();
1605
1606   // If a non-English language was selected, change the default language and
1607   // remove English.
1608   if ($langcode != 'en') {
1609     \Drupal::configFactory()->getEditable('system.site')
1610       ->set('langcode', $langcode)
1611       ->set('default_langcode', $langcode)
1612       ->save();
1613     \Drupal::service('language.default')->set($language);
1614     if (empty($install_state['profile_info']['keep_english'])) {
1615       entity_delete_multiple('configurable_language', ['en']);
1616     }
1617   }
1618
1619   // If there is more than one language or the single one is not English, we
1620   // should download/import translations.
1621   $languages = \Drupal::languageManager()->getLanguages();
1622   $operations = [];
1623   foreach ($languages as $langcode => $language) {
1624     // The installer language was already downloaded. Check downloads for the
1625     // other languages if any. Ignore any download errors here, since we
1626     // are in the middle of an install process and there is no way back. We
1627     // will not import what we cannot download.
1628     if ($langcode != 'en' && $langcode != $install_state['parameters']['langcode']) {
1629       $operations[] = ['install_check_translations', [$langcode, $install_state['server_pattern']]];
1630     }
1631   }
1632   return $operations;
1633 }
1634
1635 /**
1636  * Imports languages via a batch process during installation.
1637  *
1638  * @param $install_state
1639  *   An array of information about the current installation state.
1640  *
1641  * @return
1642  *   The batch definition, if there are language files to import.
1643  */
1644 function install_import_translations(&$install_state) {
1645   \Drupal::moduleHandler()->loadInclude('locale', 'translation.inc');
1646
1647   // If there is more than one language or the single one is not English, we
1648   // should import translations.
1649   $operations = install_download_additional_translations_operations($install_state);
1650   $languages = \Drupal::languageManager()->getLanguages();
1651   if (count($languages) > 1 || !isset($languages['en'])) {
1652     $operations[] = ['_install_prepare_import', [array_keys($languages), $install_state['server_pattern']]];
1653
1654     // Set up a batch to import translations for drupal core. Translation import
1655     // for contrib modules happens in install_import_translations_remaining.
1656     foreach ($languages as $language) {
1657       if (locale_translation_use_remote_source()) {
1658         $operations[] = ['locale_translation_batch_fetch_download', ['drupal', $language->getId()]];
1659       }
1660       $operations[] = ['locale_translation_batch_fetch_import', ['drupal', $language->getId(), []]];
1661     }
1662
1663     module_load_include('fetch.inc', 'locale');
1664     $batch = [
1665       'operations' => $operations,
1666       'title' => t('Updating translations.'),
1667       'progress_message' => '',
1668       'error_message' => t('Error importing translation files'),
1669       'finished' => 'locale_translation_batch_fetch_finished',
1670       'file' => drupal_get_path('module', 'locale') . '/locale.batch.inc',
1671     ];
1672     return $batch;
1673   }
1674 }
1675
1676 /**
1677  * Tells the translation import process that Drupal core is installed.
1678  *
1679  * @param array $langcodes
1680  *   Language codes used for the translations.
1681  * @param string $server_pattern
1682  *   Server access pattern (to replace language code, version number, etc. in).
1683  */
1684 function _install_prepare_import($langcodes, $server_pattern) {
1685   \Drupal::moduleHandler()->loadInclude('locale', 'bulk.inc');
1686   $matches = [];
1687
1688   foreach ($langcodes as $langcode) {
1689     // Get the translation files located in the translations directory.
1690     $files = locale_translate_get_interface_translation_files(['drupal'], [$langcode]);
1691     // Pick the first file which matches the language, if any.
1692     $file = reset($files);
1693     if (is_object($file)) {
1694       $filename = $file->filename;
1695       preg_match('/drupal-([0-9a-z\.-]+)\.' . $langcode . '\.po/', $filename, $matches);
1696       // Get the version information.
1697       if ($version = $matches[1]) {
1698         $info = _install_get_version_info($version);
1699         // Picking the first file does not necessarily result in the right file. So
1700         // we check if at least the major version number is available.
1701         if ($info['major']) {
1702           $core = $info['major'] . '.x';
1703           $data = [
1704             'name' => 'drupal',
1705             'project_type' => 'module',
1706             'core' => $core,
1707             'version' => $version,
1708             'server_pattern' => $server_pattern,
1709             'status' => 1,
1710           ];
1711           \Drupal::service('locale.project')->set($data['name'], $data);
1712           module_load_include('compare.inc', 'locale');
1713           locale_translation_check_projects_local(['drupal'], [$langcode]);
1714         }
1715       }
1716     }
1717   }
1718 }
1719
1720 /**
1721  * Finishes importing files at end of installation.
1722  *
1723  * If other projects besides Drupal core have been installed, their translation
1724  * will be imported here.
1725  *
1726  * @param $install_state
1727  *   An array of information about the current installation state.
1728  *
1729  * @return array
1730  *   An array of batch definitions.
1731  */
1732 function install_finish_translations(&$install_state) {
1733   \Drupal::moduleHandler()->loadInclude('locale', 'fetch.inc');
1734   \Drupal::moduleHandler()->loadInclude('locale', 'compare.inc');
1735   \Drupal::moduleHandler()->loadInclude('locale', 'bulk.inc');
1736
1737   // Build a fresh list of installed projects. When more projects than core are
1738   // installed, their translations will be downloaded (if required) and imported
1739   // using a batch.
1740   $projects = locale_translation_build_projects();
1741   $languages = \Drupal::languageManager()->getLanguages();
1742   $batches = [];
1743   if (count($projects) > 1) {
1744     $options = _locale_translation_default_update_options();
1745     if ($batch = locale_translation_batch_update_build([], array_keys($languages), $options)) {
1746       $batches[] = $batch;
1747     }
1748   }
1749
1750   // Creates configuration translations.
1751   $batches[] = locale_config_batch_update_components([], array_keys($languages));
1752   return $batches;
1753 }
1754
1755 /**
1756  * Performs final installation steps and displays a 'finished' page.
1757  *
1758  * @param $install_state
1759  *   An array of information about the current installation state.
1760  *
1761  * @return
1762  *   A message informing the user that the installation is complete.
1763  */
1764 function install_finished(&$install_state) {
1765   $profile = drupal_get_profile();
1766
1767   // Installation profiles are always loaded last.
1768   module_set_weight($profile, 1000);
1769
1770   // Build the router once after installing all modules.
1771   // This would normally happen upon KernelEvents::TERMINATE, but since the
1772   // installer does not use an HttpKernel, that event is never triggered.
1773   \Drupal::service('router.builder')->rebuild();
1774
1775   // Run cron to populate update status tables (if available) so that users
1776   // will be warned if they've installed an out of date Drupal version.
1777   // Will also trigger indexing of profile-supplied content or feeds.
1778   \Drupal::service('cron')->run();
1779
1780   if ($install_state['interactive']) {
1781     // Load current user and perform final login tasks.
1782     // This has to be done after drupal_flush_all_caches()
1783     // to avoid session regeneration.
1784     $account = User::load(1);
1785     user_login_finalize($account);
1786   }
1787
1788   $success_message = t('Congratulations, you installed @drupal!', [
1789     '@drupal' => drupal_install_profile_distribution_name(),
1790   ]);
1791   drupal_set_message($success_message);
1792 }
1793
1794 /**
1795  * Implements callback_batch_operation().
1796  *
1797  * Performs batch installation of modules.
1798  */
1799 function _install_module_batch($module, $module_name, &$context) {
1800   \Drupal::service('module_installer')->install([$module], FALSE);
1801   $context['results'][] = $module;
1802   $context['message'] = t('Installed %module module.', ['%module' => $module_name]);
1803 }
1804
1805 /**
1806  * Checks installation requirements and reports any errors.
1807  *
1808  * @param string $langcode
1809  *   Language code to check for download.
1810  * @param string $server_pattern
1811  *   Server access pattern (to replace language code, version number, etc. in).
1812  *
1813  * @return array
1814  *   Requirements compliance array. If the translation was downloaded
1815  *   successfully then an empty array is returned. Otherwise the requirements
1816  *   error with detailed information.
1817  */
1818 function install_check_translations($langcode, $server_pattern) {
1819   $requirements = [];
1820
1821   $readable = FALSE;
1822   $writable = FALSE;
1823   // @todo: Make this configurable.
1824   $site_path = \Drupal::service('site.path');
1825   $files_directory = $site_path . '/files';
1826   $translations_directory = $site_path . '/files/translations';
1827   $translations_directory_exists = FALSE;
1828   $online = FALSE;
1829
1830   // First attempt to create or make writable the files directory.
1831   file_prepare_directory($files_directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
1832   // Then, attempt to create or make writable the translations directory.
1833   file_prepare_directory($translations_directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
1834
1835   // Get values so the requirements errors can be specific.
1836   if (drupal_verify_install_file($translations_directory, FILE_EXIST, 'dir')) {
1837     $readable = is_readable($translations_directory);
1838     $writable = is_writable($translations_directory);
1839     $translations_directory_exists = TRUE;
1840   }
1841
1842   // The file already exists, no need to attempt to download.
1843   if ($existing_file = glob($translations_directory . '/drupal-*.' . $langcode . '.po')) {
1844     return;
1845   }
1846
1847   // Build URL for the translation file and the translation server.
1848   $variables = [
1849     '%project' => 'drupal',
1850     '%version' => \Drupal::VERSION,
1851     '%core' => \Drupal::CORE_COMPATIBILITY,
1852     '%language' => $langcode,
1853   ];
1854   $translation_url = strtr($server_pattern, $variables);
1855
1856   $elements = parse_url($translation_url);
1857   $server_url = $elements['scheme'] . '://' . $elements['host'];
1858
1859   // Build the language name for display.
1860   $languages = LanguageManager::getStandardLanguageList();
1861   $language = isset($languages[$langcode]) ? $languages[$langcode][0] : $langcode;
1862
1863   // Check if any of the desired translation files are available or if the
1864   // translation server can be reached. In other words, check if we are online
1865   // and have an internet connection.
1866   if ($translation_available = install_check_localization_server($translation_url)) {
1867     $online = TRUE;
1868   }
1869   if (!$translation_available) {
1870     if (install_check_localization_server($server_url)) {
1871       $online = TRUE;
1872     }
1873   }
1874
1875   // If the translations directory does not exists, throw an error.
1876   if (!$translations_directory_exists) {
1877     $requirements['translations directory exists'] = [
1878       'title'       => t('Translations directory'),
1879       'value'       => t('The translations directory does not exist.'),
1880       'severity'    => REQUIREMENT_ERROR,
1881       'description' => t('The installer requires that you create a translations directory as part of the installation process. Create the directory %translations_directory . More details about installing Drupal are available in <a href=":install_txt">INSTALL.txt</a>.', ['%translations_directory' => $translations_directory, ':install_txt' => base_path() . 'core/INSTALL.txt']),
1882     ];
1883   }
1884   else {
1885     $requirements['translations directory exists'] = [
1886       'title'       => t('Translations directory'),
1887       'value'       => t('The directory %translations_directory exists.', ['%translations_directory' => $translations_directory]),
1888     ];
1889     // If the translations directory is not readable, throw an error.
1890     if (!$readable) {
1891       $requirements['translations directory readable'] = [
1892         'title'       => t('Translations directory'),
1893         'value'       => t('The translations directory is not readable.'),
1894         'severity'    => REQUIREMENT_ERROR,
1895         'description' => t('The installer requires read permissions to %translations_directory at all times. The <a href=":handbook_url">webhosting issues</a> documentation section offers help on this and other topics.', ['%translations_directory' => $translations_directory, ':handbook_url' => 'https://www.drupal.org/server-permissions']),
1896       ];
1897     }
1898     // If translations directory is not writable, throw an error.
1899     if (!$writable) {
1900       $requirements['translations directory writable'] = [
1901         'title'       => t('Translations directory'),
1902         'value'       => t('The translations directory is not writable.'),
1903         'severity'    => REQUIREMENT_ERROR,
1904         'description' => t('The installer requires write permissions to %translations_directory during the installation process. The <a href=":handbook_url">webhosting issues</a> documentation section offers help on this and other topics.', ['%translations_directory' => $translations_directory, ':handbook_url' => 'https://www.drupal.org/server-permissions']),
1905       ];
1906     }
1907     else {
1908       $requirements['translations directory writable'] = [
1909         'title'       => t('Translations directory'),
1910         'value'       => t('The translations directory is writable.'),
1911       ];
1912     }
1913   }
1914
1915   // If the translations server can not be contacted, throw an error.
1916   if (!$online) {
1917     $requirements['online'] = [
1918       'title'       => t('Internet'),
1919       'value'       => t('The translation server is offline.'),
1920       'severity'    => REQUIREMENT_ERROR,
1921       'description' => t('The installer requires to contact the translation server to download a translation file. Check your internet connection and verify that your website can reach the translation server at <a href=":server_url">@server_url</a>.', [':server_url' => $server_url, '@server_url' => $server_url]),
1922     ];
1923   }
1924   else {
1925     $requirements['online'] = [
1926       'title'       => t('Internet'),
1927       'value'       => t('The translation server is online.'),
1928     ];
1929     // If translation file is not found at the translation server, throw an
1930     // error.
1931     if (!$translation_available) {
1932       $requirements['translation available'] = [
1933         'title'       => t('Translation'),
1934         'value'       => t('The %language translation is not available.', ['%language' => $language]),
1935         'severity'    => REQUIREMENT_ERROR,
1936         'description' => t('The %language translation file is not available at the translation server. <a href=":url">Choose a different language</a> or select English and translate your website later.', ['%language' => $language, ':url' => $_SERVER['SCRIPT_NAME']]),
1937       ];
1938     }
1939     else {
1940       $requirements['translation available'] = [
1941         'title'       => t('Translation'),
1942         'value'       => t('The %language translation is available.', ['%language' => $language]),
1943       ];
1944     }
1945   }
1946
1947   if ($translations_directory_exists && $readable && $writable && $translation_available) {
1948     $translation_downloaded = install_retrieve_file($translation_url, $translations_directory);
1949
1950     if (!$translation_downloaded) {
1951       $requirements['translation downloaded'] = [
1952         'title'       => t('Translation'),
1953         'value'       => t('The %language translation could not be downloaded.', ['%language' => $language]),
1954         'severity'    => REQUIREMENT_ERROR,
1955         'description' => t('The %language translation file could not be downloaded. <a href=":url">Choose a different language</a> or select English and translate your website later.', ['%language' => $language, ':url' => $_SERVER['SCRIPT_NAME']]),
1956       ];
1957     }
1958   }
1959
1960   return $requirements;
1961 }
1962
1963 /**
1964  * Checks installation requirements and reports any errors.
1965  */
1966 function install_check_requirements($install_state) {
1967   $profile = $install_state['parameters']['profile'];
1968
1969   // Check the profile requirements.
1970   $requirements = drupal_check_profile($profile);
1971
1972   if ($install_state['settings_verified']) {
1973     return $requirements;
1974   }
1975
1976   // If Drupal is not set up already, we need to try to create the default
1977   // settings and services files.
1978   $default_files = [];
1979   $default_files['settings.php'] = [
1980     'file' => 'settings.php',
1981     'file_default' => 'default.settings.php',
1982     'title_default' => t('Default settings file'),
1983     'description_default' => t('The default settings file does not exist.'),
1984     'title' => t('Settings file'),
1985   ];
1986
1987   foreach ($default_files as $default_file_info) {
1988     $readable = FALSE;
1989     $writable = FALSE;
1990     $site_path = './' . \Drupal::service('site.path');
1991     $file = $site_path . "/{$default_file_info['file']}";
1992     $default_file = "./sites/default/{$default_file_info['file_default']}";
1993     $exists = FALSE;
1994     // Verify that the directory exists.
1995     if (drupal_verify_install_file($site_path, FILE_EXIST, 'dir')) {
1996       if (drupal_verify_install_file($file, FILE_EXIST)) {
1997         // If it does, make sure it is writable.
1998         $readable = drupal_verify_install_file($file, FILE_READABLE);
1999         $writable = drupal_verify_install_file($file, FILE_WRITABLE);
2000         $exists = TRUE;
2001       }
2002     }
2003
2004     // If the default $default_file does not exist, or is not readable,
2005     // report an error.
2006     if (!drupal_verify_install_file($default_file, FILE_EXIST | FILE_READABLE)) {
2007       $requirements["default $file file exists"] = [
2008         'title' => $default_file_info['title_default'],
2009         'value' => $default_file_info['description_default'],
2010         'severity' => REQUIREMENT_ERROR,
2011         'description' => t('The @drupal installer requires that the %default-file file not be modified in any way from the original download.', [
2012             '@drupal' => drupal_install_profile_distribution_name(),
2013             '%default-file' => $default_file
2014           ]),
2015       ];
2016     }
2017     // Otherwise, if $file does not exist yet, we can try to copy
2018     // $default_file to create it.
2019     elseif (!$exists) {
2020       $copied = drupal_verify_install_file($site_path, FILE_EXIST | FILE_WRITABLE, 'dir') && @copy($default_file, $file);
2021       if ($copied) {
2022         // If the new $file file has the same owner as $default_file this means
2023         // $default_file is owned by the webserver user. This is an inherent
2024         // security weakness because it allows a malicious webserver process to
2025         // append arbitrary PHP code and then execute it. However, it is also a
2026         // common configuration on shared hosting, and there is nothing Drupal
2027         // can do to prevent it. In this situation, having $file also owned by
2028         // the webserver does not introduce any additional security risk, so we
2029         // keep the file in place. Additionally, this situation also occurs when
2030         // the test runner is being run be different user than the webserver.
2031         if (fileowner($default_file) === fileowner($file) || DRUPAL_TEST_IN_CHILD_SITE) {
2032           $readable = drupal_verify_install_file($file, FILE_READABLE);
2033           $writable = drupal_verify_install_file($file, FILE_WRITABLE);
2034           $exists = TRUE;
2035         }
2036         // If $file and $default_file have different owners, this probably means
2037         // the server is set up "securely" (with the webserver running as its
2038         // own user, distinct from the user who owns all the Drupal PHP files),
2039         // although with either a group or world writable sites directory.
2040         // Keeping $file owned by the webserver would therefore introduce a
2041         // security risk. It would also cause a usability problem, since site
2042         // owners who do not have root access to the file system would be unable
2043         // to edit their settings file later on. We therefore must delete the
2044         // file we just created and force the administrator to log on to the
2045         // server and create it manually.
2046         else {
2047           $deleted = @drupal_unlink($file);
2048           // We expect deleting the file to be successful (since we just
2049           // created it ourselves above), but if it fails somehow, we set a
2050           // variable so we can display a one-time error message to the
2051           // administrator at the bottom of the requirements list. We also try
2052           // to make the file writable, to eliminate any conflicting error
2053           // messages in the requirements list.
2054           $exists = !$deleted;
2055           if ($exists) {
2056             $settings_file_ownership_error = TRUE;
2057             $readable = drupal_verify_install_file($file, FILE_READABLE);
2058             $writable = drupal_verify_install_file($file, FILE_WRITABLE);
2059           }
2060         }
2061       }
2062     }
2063
2064     // If the $file does not exist, throw an error.
2065     if (!$exists) {
2066       $requirements["$file file exists"] = [
2067         'title' => $default_file_info['title'],
2068         'value' => t('The %file does not exist.', ['%file' => $default_file_info['title']]),
2069         'severity' => REQUIREMENT_ERROR,
2070         'description' => t('The @drupal installer requires that you create a %file as part of the installation process. Copy the %default_file file to %file. More details about installing Drupal are available in <a href=":install_txt">INSTALL.txt</a>.', [
2071             '@drupal' => drupal_install_profile_distribution_name(),
2072             '%file' => $file,
2073             '%default_file' => $default_file,
2074             ':install_txt' => base_path() . 'core/INSTALL.txt'
2075           ]),
2076       ];
2077     }
2078     else {
2079       $requirements["$file file exists"] = [
2080         'title' => $default_file_info['title'],
2081         'value' => t('The %file exists.', ['%file' => $file]),
2082       ];
2083       // If the $file is not readable, throw an error.
2084       if (!$readable) {
2085         $requirements["$file file readable"] = [
2086           'title' => $default_file_info['title'],
2087           'value' => t('The %file is not readable.', ['%file' => $default_file_info['title']]),
2088           'severity' => REQUIREMENT_ERROR,
2089           'description' => t('@drupal requires read permissions to %file at all times. The <a href=":handbook_url">webhosting issues</a> documentation section offers help on this and other topics.', [
2090               '@drupal' => drupal_install_profile_distribution_name(),
2091               '%file' => $file,
2092               ':handbook_url' => 'https://www.drupal.org/server-permissions'
2093             ]),
2094         ];
2095       }
2096       // If the $file is not writable, throw an error.
2097       if (!$writable) {
2098         $requirements["$file file writeable"] = [
2099           'title' => $default_file_info['title'],
2100           'value' => t('The %file is not writable.', ['%file' => $default_file_info['title']]),
2101           'severity' => REQUIREMENT_ERROR,
2102           'description' => t('The @drupal installer requires write permissions to %file during the installation process. The <a href=":handbook_url">webhosting issues</a> documentation section offers help on this and other topics.', [
2103               '@drupal' => drupal_install_profile_distribution_name(),
2104               '%file' => $file,
2105               ':handbook_url' => 'https://www.drupal.org/server-permissions'
2106             ]),
2107         ];
2108       }
2109       else {
2110         $requirements["$file file"] = [
2111           'title' => $default_file_info['title'],
2112           'value' => t('The @file is writable.', ['@file' => $default_file_info['title']]),
2113         ];
2114       }
2115       if (!empty($settings_file_ownership_error)) {
2116         $requirements["$file file ownership"] = [
2117           'title' => $default_file_info['title'],
2118           'value' => t('The @file is owned by the web server.', ['@file' => $default_file_info['title']]),
2119           'severity' => REQUIREMENT_ERROR,
2120           'description' => t('The @drupal installer failed to create a %file file with proper file ownership. Log on to your web server, remove the existing %file file, and create a new one by copying the %default_file file to %file. More details about installing Drupal are available in <a href=":install_txt">INSTALL.txt</a>. The <a href=":handbook_url">webhosting issues</a> documentation section offers help on this and other topics.', [
2121               '@drupal' => drupal_install_profile_distribution_name(),
2122               '%file' => $file,
2123               '%default_file' => $default_file,
2124               ':install_txt' => base_path() . 'core/INSTALL.txt',
2125               ':handbook_url' => 'https://www.drupal.org/server-permissions'
2126             ]),
2127         ];
2128       }
2129     }
2130   }
2131   return $requirements;
2132 }
2133
2134 /**
2135  * Displays installation requirements.
2136  *
2137  * @param array $install_state
2138  *   An array of information about the current installation state.
2139  * @param array $requirements
2140  *   An array of requirements, in the same format as is returned by
2141  *   hook_requirements().
2142  *
2143  * @return
2144  *   A themed status report, or an exception if there are requirement errors.
2145  *   If there are only requirement warnings, a themed status report is shown
2146  *   initially, but the user is allowed to bypass it by providing 'continue=1'
2147  *   in the URL. Otherwise, no output is returned, so that the next task can be
2148  *   run in the same page request.
2149  *
2150  * @throws \Drupal\Core\Installer\Exception\InstallerException
2151  */
2152 function install_display_requirements($install_state, $requirements) {
2153   // Check the severity of the requirements reported.
2154   $severity = drupal_requirements_severity($requirements);
2155
2156   // If there are errors, always display them. If there are only warnings, skip
2157   // them if the user has provided a URL parameter acknowledging the warnings
2158   // and indicating a desire to continue anyway. See drupal_requirements_url().
2159   if ($severity == REQUIREMENT_ERROR || ($severity == REQUIREMENT_WARNING && empty($install_state['parameters']['continue']))) {
2160     if ($install_state['interactive']) {
2161       $build['report']['#type'] = 'status_report';
2162       $build['report']['#requirements'] = $requirements;
2163       if ($severity == REQUIREMENT_WARNING) {
2164         $build['#title'] = t('Requirements review');
2165         $build['#suffix'] = t('Check the messages and <a href=":retry">retry</a>, or you may choose to <a href=":cont">continue anyway</a>.', [':retry' => drupal_requirements_url(REQUIREMENT_ERROR), ':cont' => drupal_requirements_url($severity)]);
2166       }
2167       else {
2168         $build['#title'] = t('Requirements problem');
2169         $build['#suffix'] = t('Check the messages and <a href=":url">try again</a>.', [':url' => drupal_requirements_url($severity)]);
2170       }
2171       return $build;
2172     }
2173     else {
2174       // Throw an exception showing any unmet requirements.
2175       $failures = [];
2176       foreach ($requirements as $requirement) {
2177         // Skip warnings altogether for non-interactive installations; these
2178         // proceed in a single request so there is no good opportunity (and no
2179         // good method) to warn the user anyway.
2180         if (isset($requirement['severity']) && $requirement['severity'] == REQUIREMENT_ERROR) {
2181           $failures[] = $requirement['title'] . ': ' . $requirement['value'] . "\n\n" . $requirement['description'];
2182         }
2183       }
2184       if (!empty($failures)) {
2185         throw new InstallerException(implode("\n\n", $failures));
2186       }
2187     }
2188   }
2189 }
2190
2191 /**
2192  * Installation task; writes profile to settings.php if possible.
2193  *
2194  * @param array $install_state
2195  *   An array of information about the current installation state.
2196  *
2197  * @see _install_select_profile()
2198  *
2199  * @throws \Drupal\Core\Installer\Exception\InstallProfileMismatchException
2200  *
2201  * @deprecated in Drupal 8.3.0 and will be removed before Drupal 9.0.0. The
2202  *    install profile is written to core.extension.
2203  */
2204 function install_write_profile($install_state) {
2205   // Only need to write to settings.php if it is possible. The primary storage
2206   // for the install profile is the core.extension configuration.
2207   $settings_path = \Drupal::service('site.path') . '/settings.php';
2208   if (is_writable($settings_path)) {
2209     // Remember the profile which was used.
2210     $settings['settings']['install_profile'] = (object) [
2211       'value' => $install_state['parameters']['profile'],
2212       'required' => TRUE,
2213     ];
2214     drupal_rewrite_settings($settings);
2215   }
2216   elseif (($settings_profile = Settings::get('install_profile')) && $settings_profile !== $install_state['parameters']['profile']) {
2217     throw new InstallProfileMismatchException($install_state['parameters']['profile'], $settings_profile, $settings_path, \Drupal::translation());
2218   }
2219 }