Newer
Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
<?php
namespace Drupal\Core\Command;
use Drupal\Component\Utility\Crypt;
use Drupal\Core\Database\ConnectionNotDefinedException;
use Drupal\Core\Database\Database;
use Drupal\Core\DrupalKernel;
use Drupal\Core\Extension\ExtensionDiscovery;
use Drupal\Core\Extension\InfoParserDynamic;
use Drupal\Core\Site\Settings;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
/**
* Installs a Drupal site for local testing/development.
*
* @internal
* This command makes no guarantee of an API for Drupal extensions.
*/
class InstallCommand extends Command {
/**
* The class loader.
*
* @var object
*/
protected $classLoader;
/**
* Constructs a new InstallCommand command.
*
* @param object $class_loader
* The class loader.
*/
public function __construct($class_loader) {
parent::__construct('install');
$this->classLoader = $class_loader;
}
/**
* {@inheritdoc}
*/
protected function configure() {
$this->setName('install')
->setDescription('Installs a Drupal demo site. This is not meant for production and might be too simple for custom development. It is a quick and easy way to get Drupal running.')
->addArgument('install-profile', InputArgument::OPTIONAL, 'Install profile to install the site in.')
->addOption('langcode', NULL, InputOption::VALUE_OPTIONAL, 'The language to install the site in.', 'en')
->addOption('site-name', NULL, InputOption::VALUE_OPTIONAL, 'Set the site name.', 'Drupal')
->addUsage('demo_umami --langcode fr')
->addUsage('standard --site-name QuickInstall');
parent::configure();
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output): int {
$io = new SymfonyStyle($input, $output);
if (!extension_loaded('pdo_sqlite')) {
$io->getErrorStyle()->error('You must have the pdo_sqlite PHP extension installed. See core/INSTALL.sqlite.txt for instructions.');
return 1;
}
// Change the directory to the Drupal root.
chdir(dirname(__DIR__, 5));
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
// Check whether there is already an installation.
if ($this->isDrupalInstalled()) {
// Do not fail if the site is already installed so this command can be
// chained with ServerCommand.
$output->writeln('<info>Drupal is already installed.</info> If you want to reinstall, remove sites/default/files and sites/default/settings.php.');
return 0;
}
$install_profile = $input->getArgument('install-profile');
if ($install_profile && !$this->validateProfile($install_profile, $io)) {
return 1;
}
if (!$install_profile) {
$install_profile = $this->selectProfile($io);
}
return $this->install($this->classLoader, $io, $install_profile, $input->getOption('langcode'), $this->getSitePath(), $input->getOption('site-name'));
}
/**
* Returns whether there is already an existing Drupal installation.
*
* @return bool
*/
protected function isDrupalInstalled() {
try {
$kernel = new DrupalKernel('prod', $this->classLoader, FALSE);
$kernel::bootEnvironment();
$kernel->setSitePath($this->getSitePath());
Settings::initialize($kernel->getAppRoot(), $kernel->getSitePath(), $this->classLoader);
$kernel->boot();
}
catch (ConnectionNotDefinedException $e) {
return FALSE;
}
return !empty(Database::getConnectionInfo());
}
/**
* Installs Drupal with specified installation profile.
*
* @param object $class_loader
* The class loader.
* @param \Symfony\Component\Console\Style\SymfonyStyle $io
* The Symfony output decorator.
* @param string $profile
* The installation profile to use.
* @param string $langcode
* The language to install the site in.
* @param string $site_path
* The path to install the site to, like 'sites/default'.
* @param string $site_name
* The site name.
*
* @throws \Exception
* Thrown when failing to create the $site_path directory or settings.php.
*
* @return int
* The command exit status.
*/
protected function install($class_loader, SymfonyStyle $io, $profile, $langcode, $site_path, $site_name) {
$sqliteDriverNamespace = 'Drupal\\sqlite\\Driver\\Database\\sqlite';
$password = Crypt::randomBytesBase64(12);
$parameters = [
'interactive' => FALSE,
'site_path' => $site_path,
'parameters' => [
'profile' => $profile,
'langcode' => $langcode,
],
'forms' => [
'install_settings_form' => [
'driver' => $sqliteDriverNamespace,
$sqliteDriverNamespace => [
'database' => $site_path . '/files/.sqlite',
],
],
'install_configure_form' => [
'site_name' => $site_name,
'site_mail' => 'drupal@localhost',
'account' => [
'name' => 'admin',
'mail' => 'admin@localhost',
'pass' => [
'pass1' => $password,
'pass2' => $password,
],
],
'enable_update_status_module' => TRUE,
catch
committed
// \Drupal\Core\Render\Element\Checkboxes::valueCallback() requires
// NULL instead of FALSE values for programmatic form submissions to
// disable a checkbox.
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
'enable_update_status_emails' => NULL,
],
],
];
// Create the directory and settings.php if not there so that the installer
// works.
if (!is_dir($site_path)) {
if ($io->isVerbose()) {
$io->writeln("Creating directory: $site_path");
}
if (!mkdir($site_path, 0775)) {
throw new \RuntimeException("Failed to create directory $site_path");
}
}
if (!file_exists("{$site_path}/settings.php")) {
if ($io->isVerbose()) {
$io->writeln("Creating file: {$site_path}/settings.php");
}
if (!copy('sites/default/default.settings.php', "{$site_path}/settings.php")) {
throw new \RuntimeException("Copying sites/default/default.settings.php to {$site_path}/settings.php failed.");
}
}
require_once 'core/includes/install.core.inc';
$progress_bar = $io->createProgressBar();
install_drupal($class_loader, $parameters, function ($install_state) use ($progress_bar) {
static $started = FALSE;
if (!$started) {
$started = TRUE;
// We've already done 1.
$progress_bar->setFormat("%current%/%max% [%bar%]\n%message%\n");
$progress_bar->setMessage(t('Installing @drupal', ['@drupal' => drupal_install_profile_distribution_name()]));
$tasks = install_tasks($install_state);
$progress_bar->start(count($tasks) + 1);
}
$tasks_to_perform = install_tasks_to_perform($install_state);
$task = current($tasks_to_perform);
if (isset($task['display_name'])) {
$progress_bar->setMessage($task['display_name']);
}
$progress_bar->advance();
});
$success_message = t('Congratulations, you installed @drupal!', [
'@drupal' => drupal_install_profile_distribution_name(),
'@name' => 'admin',
'@pass' => $password,
], ['langcode' => $langcode]);
$progress_bar->setMessage('<info>' . $success_message . '</info>');
$progress_bar->display();
$progress_bar->finish();
$io->writeln('<info>Username:</info> admin');
$io->writeln("<info>Password:</info> $password");
return 0;
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
}
/**
* Gets the site path.
*
* Defaults to 'sites/default'. For testing purposes this can be overridden
* using the DRUPAL_DEV_SITE_PATH environment variable.
*
* @return string
* The site path to use.
*/
protected function getSitePath() {
return getenv('DRUPAL_DEV_SITE_PATH') ?: 'sites/default';
}
/**
* Selects the install profile to use.
*
* @param \Symfony\Component\Console\Style\SymfonyStyle $io
* Symfony style output decorator.
*
* @return string
* The selected install profile.
*
* @see _install_select_profile()
* @see \Drupal\Core\Installer\Form\SelectProfileForm
*/
protected function selectProfile(SymfonyStyle $io) {
$profiles = $this->getProfiles();
// If there is a distribution there will be only one profile.
if (count($profiles) == 1) {
return key($profiles);
}
// Display alphabetically by human-readable name, but always put the core
// profiles first (if they are present in the filesystem).
natcasesort($profiles);
if (isset($profiles['minimal'])) {
// If the expert ("Minimal") core profile is present, put it in front of
// any non-core profiles rather than including it with them
// alphabetically, since the other profiles might be intended to group
// together in a particular way.
$profiles = ['minimal' => $profiles['minimal']] + $profiles;
}
if (isset($profiles['standard'])) {
// If the default ("Standard") core profile is present, put it at the very
// top of the list. This profile will have its radio button pre-selected,
// so we want it to always appear at the top.
$profiles = ['standard' => $profiles['standard']] + $profiles;
}
reset($profiles);
return $io->choice('Select an installation profile', $profiles, current($profiles));
}
/**
* Validates a user provided install profile.
*
* @param string $install_profile
* Install profile to validate.
* @param \Symfony\Component\Console\Style\SymfonyStyle $io
* Symfony style output decorator.
*
* @return bool
* TRUE if the profile is valid, FALSE if not.
*/
protected function validateProfile($install_profile, SymfonyStyle $io) {
// Allow people to install hidden and non-distribution profiles if they
// supply the argument.
$profiles = $this->getProfiles(TRUE, FALSE);
if (!isset($profiles[$install_profile])) {
$error_msg = sprintf("'%s' is not a valid install profile.", $install_profile);
$alternatives = [];
foreach (array_keys($profiles) as $profile_name) {
$lev = levenshtein($install_profile, $profile_name);
if ($lev <= strlen($profile_name) / 4 || str_contains($profile_name, $install_profile)) {
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
$alternatives[] = $profile_name;
}
}
if (!empty($alternatives)) {
$error_msg .= sprintf(" Did you mean '%s'?", implode("' or '", $alternatives));
}
$io->getErrorStyle()->error($error_msg);
return FALSE;
}
return TRUE;
}
/**
* Gets a list of profiles.
*
* @param bool $include_hidden
* (optional) Whether to include hidden profiles. Defaults to FALSE.
* @param bool $auto_select_distributions
* (optional) Whether to only return the first distribution found.
*
* @return string[]
* An array of profile descriptions keyed by the profile machine name.
*/
protected function getProfiles($include_hidden = FALSE, $auto_select_distributions = TRUE) {
// Build a list of all available profiles.
$listing = new ExtensionDiscovery(getcwd(), FALSE);
$listing->setProfileDirectories([]);
$profiles = [];
$info_parser = new InfoParserDynamic(getcwd());
foreach ($listing->scan('profile') as $profile) {
$details = $info_parser->parse($profile->getPathname());
// Don't show hidden profiles.
if (!$include_hidden && !empty($details['hidden'])) {
continue;
}
// Determine the name of the profile; default to the internal name if none
// is specified.
$name = $details['name'] ?? $profile->getName();
$description = $details['description'] ?? $name;
$profiles[$profile->getName()] = $description;
if ($auto_select_distributions && !empty($details['distribution'])) {
return [$profile->getName() => $description];
}
}
return $profiles;
}
}