Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[multisite:new] Sets up files for a new multisite installation #23 #2335

Merged
merged 15 commits into from Jun 2, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions config/services/multisite.yml
Expand Up @@ -3,3 +3,7 @@ services:
class: Drupal\Console\Command\Multisite\DebugCommand
tags:
- { name: console.command }
multisite_new:
class: Drupal\Console\Command\Multisite\NewCommand
tags:
- { name: console.command }
24 changes: 24 additions & 0 deletions config/translations/en/multisite.new.yml
@@ -0,0 +1,24 @@
description: "Sets up the files for a new multisite install."
help: "The <info>multisite:new</info> command assists in setting up new multisite installs by creating the needed subdirectory and files, and can optionally copy an existing 'default' installation."
arguments:
sites-subdir: "Name of directory under 'sites' which should be created."
options:
site-uri: "Site URI to add to sites.php."
copy-install: "Copies existing site from the default install."
errors:
subdir-empty: "You must specify a multisite subdirectory to create."
subdir-exists: "The sites/%s directory already exists."
default-missing: "The sites/default directory is missing."
mkdir-fail: "Unable to create sites/%s. Please check the sites directory permissions and try again."
sites-invalid: "The sites.php file located is either not readable or not a file."
sites-missing: "No sites.php or example.sites.php to copy from."
sites-other: "A problem was encountered when attempting to write sites.php"
file-missing: "The file '%s' was not found for copying."
copy-fail: "Unable to copy %s to %s. Please check the permissions and try again."
write-fail: "Unable to write to the file %s. Please check the file permissions and try again."
chmod-fail: "Unable to change permissions on the file %s. Please ensure that the permissions on that file are correct."
warnings:
missing-files: "No sites/default/files directory found. The files directory will need to be created by hand."
messages:
copy-install: "The default install was successfully copied to sites/%s."
fresh-site: "The new multisite structure was successfully created at sites/%s and is ready for installation."
2 changes: 2 additions & 0 deletions config/translations/en/site.install.yml
Expand Up @@ -23,3 +23,5 @@ messages:
installed: 'Your Drupal 8 installation was completed successfully'
using-current-database: 'Using %s database with name %s and user %s'
already-installed: 'Drupal is already installed, try dropping your database using database:drop and then run site:install again'
sites-backup: 'The sites.php file has temporarily been renamed to backup.sites.php while Drupal installs.'
sites-restore: 'The backup of sites.php has been been restored to sites.php.'
298 changes: 298 additions & 0 deletions src/Command/Multisite/NewCommand.php
@@ -0,0 +1,298 @@
<?php

/**
* @file
* Contains \Drupal\AppConsole\Command\Multisite\NewCommand.
*/

namespace Drupal\Console\Command\Multisite;

use Drupal\Console\Command\Shared\CommandTrait;
use Drupal\Console\Style\DrupalStyle;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Filesystem\Exception\IOExceptionInterface;
use Symfony\Component\Filesystem\Filesystem;

/**
* Class MultisiteNewCommand
* @package Drupal\Console\Command\Multisite
*/
class NewCommand extends Command
{
use CommandTrait;

/**
* @var Symfony\Component\Filesystem\Filesystem;
*/
protected $fs;

/**
* @var string
*/
protected $root = '';

/**
* @var string
*/
protected $subdir = '';


/**
* @{@inheritdoc}
*/
public function configure()
{
$this
->setName('multisite:new')
->setDescription($this->trans('commands.multisite.new.description'))
->setHelp($this->trans('commands.multisite.new.help'))
->addArgument(
'sites-subdir',
InputOption::VALUE_REQUIRED,
$this->trans('commands.multisite.new.arguments.sites-subdir')
)
->addOption(
'site-uri',
'',
InputOption::VALUE_OPTIONAL,
$this->trans('commands.multisite.new.options.site-uri')
)
->addOption(
'copy-install',
'',
InputOption::VALUE_NONE,
$this->trans('commands.multisite.new.options.copy-install')
);
}

/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$output = new DrupalStyle($input, $output);
$this->subdir = $input->getArgument('sites-subdir');

if (empty($this->subdir)) {
$output->error($this->trans('commands.multisite.new.errors.subdir-empty'));
return;
}

$this->fs = new Filesystem();
$this->root = $this->get('site')->getRoot();

if ($this->fs->exists($this->root . '/sites/' . $this->subdir)) {
$output->error(
sprintf(
$this->trans('commands.multisite.new.errors.subdir-exists'),
$this->subdir
)
);
return;
}

if (!$this->fs->exists($this->root . '/sites/default')) {
$output->error($this->trans('commands.multisite.new.errors.default-missing'));
return;
}

try {
$this->fs->mkdir($this->root . '/sites/' . $this->subdir, 0755);
} catch (IOExceptionInterface $e) {
$output->error(
sprintf(
$this->trans('commands.multisite.new.errors.mkdir-fail'),
$this->subdir
)
);
return;
}

if ($uri = $input->getOption('site-uri')) {
try {
$this->addToSitesFile($output, $uri);
} catch (\Exception $e) {
$output->error($e->getMessage());
return;
}
}

if ($input->getOption('copy-install')) {
$this->copyExistingInstall($output);
return;
}

$this->createFreshSite($output);
}

/**
* Adds line to sites.php that is needed for the new site to be recognized.
*
* @param DrupalStyle $output
* @param string $uri
*/
protected function addToSitesFile(DrupalStyle $output, $uri)
{
if ($this->fs->exists($this->root . '/sites/sites.php')) {
$sites_is_dir = is_dir($this->root . '/sites/sites.php');
$sites_readable = is_readable($this->root . '/sites/sites.php');
if ($sites_is_dur || !$sites_is_readable) {
throw new \Exception($this->trans('commands.multisite.new.errors.sites-invalid'));
}
$sites_file_contents = file_get_contents($this->root . '/sites/sites.php');
}
elseif ($this->fs->exists($this->root . '/sites/example.sites.php')) {
$sites_file_contents = file_get_contents($this->root . '/sites/example.sites.php');
$sites_file_contents .= "\n\$sites = [];";
}
else {
throw new \Exception($this->trans('commands.multisite.new.errors.sites-missing'));
}

$sites_file_contents .= "\n\$sites['$uri'] = '$this->subdir';";

try {
$this->fs->dumpFile($this->root . '/sites/sites.php', $sites_file_contents);
$this->fs->chmod($this->root . '/sites/sites.php', 0640);
} catch (IOExceptionInterface $e) {
$output->error('commands.multisite.new.errors.sites-other');
}
}

/**
* Copies detected default install alters settings.php to fit the new directory.
*
* @param DrupalStyle $output
*/
protected function copyExistingInstall(DrupalStyle $output)
{
if (!$this->fs->exists($this->root . '/sites/default/settings.php')) {
$output->error(
sprintf(
$this->trans('commands.multisite.new.errors.file-missing'),
'sites/default/settings.php'
)
);
return;
}

if ($this->fs->exists($this->root . '/sites/default/files')) {
try {
$this->fs->mirror(
$this->root . '/sites/default/files',
$this->root . '/sites/' . $this->subdir . '/files'
);
} catch (IOExceptionInterface $e) {
$output->error(
sprintf(
$this->trans('commands.multisite.new.errors.copy-fail'),
'sites/default/files',
'sites/' . $this->subdir . '/files'
)
);
return;
}
}
else {
$output->warning($this->trans('commands.multisite.new.warnings.missing-files'));
}

$settings = file_get_contents($this->root . '/sites/default/settings.php');
$settings = str_replace('sites/default', 'sites/' . $this->subdir, $settings);

try {
$this->fs->dumpFile(
$this->root . '/sites/' . $this->subdir . '/settings.php',
$settings
);
} catch (IOExceptionInterface $e) {
$output->error(
sprintf(
$this->trans('commands.multisite.new.errors.write-fail'),
'sites/' . $this->subdir . '/settings.php'
)
);
return;
}

$this->chmodSettings($output);

$output->success(
sprintf(
$this->trans('commands.multisite.new.messages.copy-install'),
$this->subdir
)
);
}

/**
* Creates site folder with clean settings.php file.
*
* @param DrupalStyle $output
*/
protected function createFreshSite(DrupalStyle $output)
{
if ($this->fs->exists($this->root . '/sites/default/default.settings.php')) {
try {
$this->fs->copy(
$this->root . '/sites/default/default.settings.php',
$this->root . '/sites/' . $this->subdir . '/settings.php'
);
} catch (IOExceptionInterface $e) {
$output->error(
sprintf(
$this->trans('commands.multisite.new.errors.copy-fail'),
$this->root . '/sites/default/default.settings.php',
$this->root . '/sites/' . $this->subdir . '/settings.php'
)
);
return;
}
}
else {
$output->error(
sprintf(
$this->trans('commands.multisite.new.errors.file-missing'),
'sites/default/default.settings.php'
)
);
return;
}

$this->chmodSettings($output);

$output->success(
sprintf(
$this->trans('commands.multisite.new.messages.fresh-site'),
$this->subdir
)
);
}

/**
* Changes permissions of settings.php to 640.
*
* The copy will have 444 permissions by default, which makes it readable by
* anyone. Also, Drupal likes being able to write to it during, for example,
* a fresh install.
*
* @param DrupalStyle $output
*/
protected function chmodSettings(DrupalStyle $output)
{
try {
$this->fs->chmod($this->root . '/sites/' . $this->subdir . '/settings.php', 0640);
} catch (IOExceptionInterface $e) {
$output->error(
sprintf(
$this->trans('commands.multisite.new.errors.chmod-fail'),
$this->root . '/sites/' . $this->subdir . '/settings.php'
)
);
}
}

}
42 changes: 42 additions & 0 deletions src/Command/Site/InstallCommand.php
Expand Up @@ -318,12 +318,16 @@ protected function execute(InputInterface $input, OutputInterface $output)
);
}

$this->backupSitesFile($output);

try {
$this->runInstaller($output, $input, $database);
} catch (Exception $e) {
$output->error($e->getMessage());
return;
}

$this->restoreSitesFile($output);
}

protected function getProfiles()
Expand Down Expand Up @@ -354,6 +358,44 @@ protected function getDefaultLanguage()
return $config->get('application.language');
}

/**
* Backs up sites.php to backup.sites.php (if needed).
*
* This is needed because of a bug with install_drupal() that causes the
* install files to be placed directly under /sites instead of the
* appropriate subdir when run from a script and a sites.php file exists.
*
* @param DrupalStyle $output
*/
protected function backupSitesFile(DrupalStyle $output)
{
$root = $this->get('site')->getRoot();

if (!file_exists($root . '/sites/sites.php')) {
return;
}

rename($root . '/sites/sites.php', $root . '/sites/backup.sites.php');
$output->info($this->trans('commands.site.install.messages.sites-backup'));
}

/**
* Restores backup.sites.php to sites.php (if needed).
*
* @param DrupalStyle $output
*/
protected function restoreSitesFile(DrupalStyle $output)
{
$root = $this->get('site')->getRoot();

if (!file_exists($root . '/sites/backup.sites.php')) {
return;
}

rename($root . '/sites/backup.sites.php', $root . '/sites/sites.php');
$output->info($this->trans('commands.site.install.messages.sites-restore'));
}

protected function runInstaller(
DrupalStyle $output,
InputInterface $input,
Expand Down