Chris@17: classLoader = $class_loader; Chris@17: } Chris@17: Chris@17: /** Chris@17: * {@inheritdoc} Chris@17: */ Chris@17: protected function configure() { Chris@17: $this->setName('install') Chris@17: ->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.') Chris@17: ->addArgument('install-profile', InputArgument::OPTIONAL, 'Install profile to install the site in.') Chris@17: ->addOption('langcode', NULL, InputOption::VALUE_OPTIONAL, 'The language to install the site in.', 'en') Chris@17: ->addOption('site-name', NULL, InputOption::VALUE_OPTIONAL, 'Set the site name.', 'Drupal') Chris@17: ->addUsage('demo_umami --langcode fr') Chris@17: ->addUsage('standard --site-name QuickInstall'); Chris@17: Chris@17: parent::configure(); Chris@17: } Chris@17: Chris@17: /** Chris@17: * {@inheritdoc} Chris@17: */ Chris@17: protected function execute(InputInterface $input, OutputInterface $output) { Chris@17: $io = new SymfonyStyle($input, $output); Chris@17: if (!extension_loaded('pdo_sqlite')) { Chris@17: $io->getErrorStyle()->error('You must have the pdo_sqlite PHP extension installed. See core/INSTALL.sqlite.txt for instructions.'); Chris@17: return 1; Chris@17: } Chris@17: Chris@17: // Change the directory to the Drupal root. Chris@17: chdir(dirname(dirname(dirname(dirname(dirname(__DIR__)))))); Chris@17: Chris@17: // Check whether there is already an installation. Chris@17: if ($this->isDrupalInstalled()) { Chris@17: // Do not fail if the site is already installed so this command can be Chris@17: // chained with ServerCommand. Chris@17: $output->writeln('Drupal is already installed. If you want to reinstall, remove sites/default/files and sites/default/settings.php.'); Chris@17: return 0; Chris@17: } Chris@17: Chris@17: $install_profile = $input->getArgument('install-profile'); Chris@17: if ($install_profile && !$this->validateProfile($install_profile, $io)) { Chris@17: return 1; Chris@17: } Chris@17: if (!$install_profile) { Chris@17: $install_profile = $this->selectProfile($io); Chris@17: } Chris@17: Chris@17: return $this->install($this->classLoader, $io, $install_profile, $input->getOption('langcode'), $this->getSitePath(), $input->getOption('site-name')); Chris@17: } Chris@17: Chris@17: /** Chris@17: * Returns whether there is already an existing Drupal installation. Chris@17: * Chris@17: * @return bool Chris@17: */ Chris@17: protected function isDrupalInstalled() { Chris@17: try { Chris@17: $kernel = new DrupalKernel('prod', $this->classLoader, FALSE); Chris@17: $kernel::bootEnvironment(); Chris@17: $kernel->setSitePath($this->getSitePath()); Chris@17: Settings::initialize($kernel->getAppRoot(), $kernel->getSitePath(), $this->classLoader); Chris@17: $kernel->boot(); Chris@17: } Chris@17: catch (ConnectionNotDefinedException $e) { Chris@17: return FALSE; Chris@17: } Chris@17: return !empty(Database::getConnectionInfo()); Chris@17: } Chris@17: Chris@17: /** Chris@17: * Installs Drupal with specified installation profile. Chris@17: * Chris@17: * @param object $class_loader Chris@17: * The class loader. Chris@17: * @param \Symfony\Component\Console\Style\SymfonyStyle $io Chris@17: * The Symfony output decorator. Chris@17: * @param string $profile Chris@17: * The installation profile to use. Chris@17: * @param string $langcode Chris@17: * The language to install the site in. Chris@17: * @param string $site_path Chris@17: * The path to install the site to, like 'sites/default'. Chris@17: * @param string $site_name Chris@17: * The site name. Chris@17: * Chris@17: * @throws \Exception Chris@17: * Thrown when failing to create the $site_path directory or settings.php. Chris@17: */ Chris@17: protected function install($class_loader, SymfonyStyle $io, $profile, $langcode, $site_path, $site_name) { Chris@17: $password = Crypt::randomBytesBase64(12); Chris@17: $parameters = [ Chris@17: 'interactive' => FALSE, Chris@17: 'site_path' => $site_path, Chris@17: 'parameters' => [ Chris@17: 'profile' => $profile, Chris@17: 'langcode' => $langcode, Chris@17: ], Chris@17: 'forms' => [ Chris@17: 'install_settings_form' => [ Chris@17: 'driver' => 'sqlite', Chris@17: 'sqlite' => [ Chris@17: 'database' => $site_path . '/files/.sqlite', Chris@17: ], Chris@17: ], Chris@17: 'install_configure_form' => [ Chris@17: 'site_name' => $site_name, Chris@17: 'site_mail' => 'drupal@localhost', Chris@17: 'account' => [ Chris@17: 'name' => 'admin', Chris@17: 'mail' => 'admin@localhost', Chris@17: 'pass' => [ Chris@17: 'pass1' => $password, Chris@17: 'pass2' => $password, Chris@17: ], Chris@17: ], Chris@17: 'enable_update_status_module' => TRUE, Chris@17: // form_type_checkboxes_value() requires NULL instead of FALSE values Chris@17: // for programmatic form submissions to disable a checkbox. Chris@17: 'enable_update_status_emails' => NULL, Chris@17: ], Chris@17: ], Chris@17: ]; Chris@17: Chris@17: // Create the directory and settings.php if not there so that the installer Chris@17: // works. Chris@17: if (!is_dir($site_path)) { Chris@17: if ($io->isVerbose()) { Chris@17: $io->writeln("Creating directory: $site_path"); Chris@17: } Chris@17: if (!mkdir($site_path, 0775)) { Chris@17: throw new \RuntimeException("Failed to create directory $site_path"); Chris@17: } Chris@17: } Chris@17: if (!file_exists("{$site_path}/settings.php")) { Chris@17: if ($io->isVerbose()) { Chris@17: $io->writeln("Creating file: {$site_path}/settings.php"); Chris@17: } Chris@17: if (!copy('sites/default/default.settings.php', "{$site_path}/settings.php")) { Chris@17: throw new \RuntimeException("Copying sites/default/default.settings.php to {$site_path}/settings.php failed."); Chris@17: } Chris@17: } Chris@17: Chris@17: require_once 'core/includes/install.core.inc'; Chris@17: Chris@17: $progress_bar = $io->createProgressBar(); Chris@17: install_drupal($class_loader, $parameters, function ($install_state) use ($progress_bar) { Chris@17: static $started = FALSE; Chris@17: if (!$started) { Chris@17: $started = TRUE; Chris@17: // We've already done 1. Chris@17: $progress_bar->setFormat("%current%/%max% [%bar%]\n%message%\n"); Chris@17: $progress_bar->setMessage(t('Installing @drupal', ['@drupal' => drupal_install_profile_distribution_name()])); Chris@17: $tasks = install_tasks($install_state); Chris@17: $progress_bar->start(count($tasks) + 1); Chris@17: } Chris@17: $tasks_to_perform = install_tasks_to_perform($install_state); Chris@17: $task = current($tasks_to_perform); Chris@17: if (isset($task['display_name'])) { Chris@17: $progress_bar->setMessage($task['display_name']); Chris@17: } Chris@17: $progress_bar->advance(); Chris@17: }); Chris@17: $success_message = t('Congratulations, you installed @drupal!', [ Chris@17: '@drupal' => drupal_install_profile_distribution_name(), Chris@17: '@name' => 'admin', Chris@17: '@pass' => $password, Chris@17: ], ['langcode' => $langcode]); Chris@17: $progress_bar->setMessage('' . $success_message . ''); Chris@17: $progress_bar->display(); Chris@17: $progress_bar->finish(); Chris@17: $io->writeln('Username: admin'); Chris@17: $io->writeln("Password: $password"); Chris@17: } Chris@17: Chris@17: /** Chris@17: * Gets the site path. Chris@17: * Chris@17: * Defaults to 'sites/default'. For testing purposes this can be overridden Chris@17: * using the DRUPAL_DEV_SITE_PATH environment variable. Chris@17: * Chris@17: * @return string Chris@17: * The site path to use. Chris@17: */ Chris@17: protected function getSitePath() { Chris@17: return getenv('DRUPAL_DEV_SITE_PATH') ?: 'sites/default'; Chris@17: } Chris@17: Chris@17: /** Chris@17: * Selects the install profile to use. Chris@17: * Chris@17: * @param \Symfony\Component\Console\Style\SymfonyStyle $io Chris@17: * Symfony style output decorator. Chris@17: * Chris@17: * @return string Chris@17: * The selected install profile. Chris@17: * Chris@17: * @see _install_select_profile() Chris@17: * @see \Drupal\Core\Installer\Form\SelectProfileForm Chris@17: */ Chris@17: protected function selectProfile(SymfonyStyle $io) { Chris@17: $profiles = $this->getProfiles(); Chris@17: Chris@17: // If there is a distribution there will be only one profile. Chris@17: if (count($profiles) == 1) { Chris@17: return key($profiles); Chris@17: } Chris@17: // Display alphabetically by human-readable name, but always put the core Chris@17: // profiles first (if they are present in the filesystem). Chris@17: natcasesort($profiles); Chris@17: if (isset($profiles['minimal'])) { Chris@17: // If the expert ("Minimal") core profile is present, put it in front of Chris@17: // any non-core profiles rather than including it with them Chris@17: // alphabetically, since the other profiles might be intended to group Chris@17: // together in a particular way. Chris@17: $profiles = ['minimal' => $profiles['minimal']] + $profiles; Chris@17: } Chris@17: if (isset($profiles['standard'])) { Chris@17: // If the default ("Standard") core profile is present, put it at the very Chris@17: // top of the list. This profile will have its radio button pre-selected, Chris@17: // so we want it to always appear at the top. Chris@17: $profiles = ['standard' => $profiles['standard']] + $profiles; Chris@17: } Chris@17: reset($profiles); Chris@17: return $io->choice('Select an installation profile', $profiles, current($profiles)); Chris@17: } Chris@17: Chris@17: /** Chris@17: * Validates a user provided install profile. Chris@17: * Chris@17: * @param string $install_profile Chris@17: * Install profile to validate. Chris@17: * @param \Symfony\Component\Console\Style\SymfonyStyle $io Chris@17: * Symfony style output decorator. Chris@17: * Chris@17: * @return bool Chris@17: * TRUE if the profile is valid, FALSE if not. Chris@17: */ Chris@17: protected function validateProfile($install_profile, SymfonyStyle $io) { Chris@17: // Allow people to install hidden and non-distribution profiles if they Chris@17: // supply the argument. Chris@17: $profiles = $this->getProfiles(TRUE, FALSE); Chris@17: if (!isset($profiles[$install_profile])) { Chris@17: $error_msg = sprintf("'%s' is not a valid install profile.", $install_profile); Chris@17: $alternatives = []; Chris@17: foreach (array_keys($profiles) as $profile_name) { Chris@17: $lev = levenshtein($install_profile, $profile_name); Chris@17: if ($lev <= strlen($profile_name) / 4 || FALSE !== strpos($profile_name, $install_profile)) { Chris@17: $alternatives[] = $profile_name; Chris@17: } Chris@17: } Chris@17: if (!empty($alternatives)) { Chris@17: $error_msg .= sprintf(" Did you mean '%s'?", implode("' or '", $alternatives)); Chris@17: } Chris@17: $io->getErrorStyle()->error($error_msg); Chris@17: return FALSE; Chris@17: } Chris@17: return TRUE; Chris@17: } Chris@17: Chris@17: /** Chris@17: * Gets a list of profiles. Chris@17: * Chris@17: * @param bool $include_hidden Chris@17: * (optional) Whether to include hidden profiles. Defaults to FALSE. Chris@17: * @param bool $auto_select_distributions Chris@17: * (optional) Whether to only return the first distribution found. Chris@17: * Chris@17: * @return string[] Chris@17: * An array of profile descriptions keyed by the profile machine name. Chris@17: */ Chris@17: protected function getProfiles($include_hidden = FALSE, $auto_select_distributions = TRUE) { Chris@17: // Build a list of all available profiles. Chris@17: $listing = new ExtensionDiscovery(getcwd(), FALSE); Chris@17: $listing->setProfileDirectories([]); Chris@17: $profiles = []; Chris@17: $info_parser = new InfoParserDynamic(); Chris@17: foreach ($listing->scan('profile') as $profile) { Chris@17: $details = $info_parser->parse($profile->getPathname()); Chris@17: // Don't show hidden profiles. Chris@17: if (!$include_hidden && !empty($details['hidden'])) { Chris@17: continue; Chris@17: } Chris@17: // Determine the name of the profile; default to the internal name if none Chris@17: // is specified. Chris@17: $name = isset($details['name']) ? $details['name'] : $profile->getName(); Chris@17: $description = isset($details['description']) ? $details['description'] : $name; Chris@17: $profiles[$profile->getName()] = $description; Chris@17: Chris@17: if ($auto_select_distributions && !empty($details['distribution'])) { Chris@17: return [$profile->getName() => $description]; Chris@17: } Chris@17: } Chris@17: return $profiles; Chris@17: } Chris@17: Chris@17: }