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