View source
<?php
use Drupal\acsf\AcsfInitHtaccessException;
use Drupal\acsf\AcsfSite;
use Drupal\user\Entity\Role;
use Drupal\user\Entity\User;
define('ACSF_HTACCESS_PATCH', 'RewriteCond %{REQUEST_URI} !/sites/g/apc_rebuild.php$');
define('ACSF_HTACCESS_PATCH_MARKER', 'RewriteRule "^(.+/.*|autoload)\\.php($|/)" - [F]');
define('ACSF_HTACCESS_PATCH_COMMENT', ' # ACSF requirement: allow access to apc_rebuild.php.');
define('ACSF_INIT_CODE_DELIMITER_START', '// ===== Added by acsf-init, please do not delete. Section start. =====');
define('ACSF_INIT_CODE_DELIMITER_END', '// ===== Added by acsf-init, please do not delete. Section end. =====');
function acsf_init_drush_command() {
return [
'acsf-init' => [
'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_ROOT,
'callback' => 'drush_acsf_init',
'description' => dt('Installs/updates the non-standard Drupal components for this repository to be compatible with Acquia Site Factory. This command will update in place, so there is no harm in running it multiple times.'),
'options' => [
'skip-default-settings' => dt('Do not edit the default settings.php file. Use this option when the edited default settings.php is causing issues in a local environment.'),
],
],
'acsf-init-verify' => [
'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_ROOT,
'callback' => 'drush_acsf_init_verify',
'description' => dt('Verifies that acsf-init was successfully run in the current version.'),
'options' => [
'skip-default-settings' => dt('Skip verifying the default settings.php file.'),
],
],
'acsf-connect-factory' => [
'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_FULL,
'callback' => 'drush_acsf_connect_factory',
'description' => dt('Connect a site to a factory by setting up the right variables in the database.'),
'aliases' => [
'acsf-cf',
],
'options' => [
'site-admin-mail' => [
'description' => dt('The email address of the Site Factory admin / Gardens admin user. This is typically the "Site Factory admin" user on the factory. These email addresses have to match in order for the initial OpenID connection to bind these accounts.'),
'required' => TRUE,
'example-value' => 'user3@example.com',
],
'site-owner-name' => [
'description' => dt('The name of the site owner.'),
'required' => TRUE,
'example-value' => 'John Smith',
],
'site-owner-mail' => [
'description' => dt('The email address of the site owner.'),
'required' => TRUE,
'example-value' => 'john.smith@example.com',
],
'site-owner-roles' => [
'description' => dt('A list of comma-separated roles (machine names) that should be granted to the site owner (optional).'),
'example-value' => 'editor, site manager',
],
],
'examples' => [
'drush acsf-connect-factory --site-admin-mail="user3@example.com" --site-owner-name="John Smith" --site-owner-mail="john.smith@example.com"' => dt('Connect the site to the factory and sets the owner to John Smith.'),
],
],
'acsf-uninstall' => [
'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_ROOT,
'callback' => 'drush_acsf_uninstall',
'description' => dt('Uninstalls components for this Drupal repository to be compatible with Acquia Site Factory.'),
],
];
}
function _acsf_init_include_exceptions() {
$path = dirname(__DIR__) . '/src';
$classes = [
'AcsfException',
'AcsfInitException',
'AcsfInitHtaccessException',
];
foreach ($classes as $class) {
require_once "{$path}/{$class}.php";
}
}
function drush_acsf_init_pre_acsf_init() {
_acsf_init_include_exceptions();
}
function drush_acsf_init_pre_acsf_init_verify() {
_acsf_init_include_exceptions();
}
function drush_acsf_init() {
$drupal_root = realpath(DRUPAL_ROOT);
if (basename($drupal_root) !== 'docroot') {
drush_set_error('INCORRECT DIRECTORY STRUCTURE', dt("Drupal must be installed in a subdirectory of your code repository, named 'docroot'."));
return;
}
$repo_root = dirname($drupal_root);
$skip_default_settings = drush_get_option('skip-default-settings');
drush_print(dt('Installing ACSF requirements.'));
foreach (acsf_init_get_required_dirs($repo_root) as $name => $dir) {
if ($skip_default_settings && $name == 'site env default') {
continue;
}
drush_print(dt('Creating directory for !name at !dir', [
'!name' => $name,
'!dir' => $dir,
]));
if (!file_exists($dir)) {
if (mkdir($dir, 0755, TRUE)) {
drush_log(dt('Success'), 'success');
}
else {
drush_log(dt('Error'), 'error');
}
}
else {
drush_log(dt('Already Exists'), 'ok');
}
}
$lib_path = sprintf('%s/lib', dirname(__FILE__));
foreach (acsf_init_get_required_files($repo_root) as $location) {
$file = $location['filename'];
$dest = sprintf('%s/%s', $location['dest'], $file);
if (isset($location['source']) && isset($location['dest'])) {
$source = sprintf('%s/%s/%s', $lib_path, $location['source'], $file);
drush_print(dt('Copying !file to !dest.', [
'!file' => $source,
'!dest' => $dest,
]));
if (file_exists($dest)) {
$confirm = drush_confirm(dt('Destination file exists, continue?'));
if ($confirm === FALSE) {
continue;
}
}
if (copy($source, $dest)) {
drush_log(dt('Copy Success: !file', [
'!file' => $file,
]), 'success');
}
else {
drush_log(dt('Copy Error: !file', [
'!file' => $file,
]), 'error');
}
if (!is_writable($dest)) {
if (!chmod($dest, 0666)) {
drush_log(dt('Chmod Error: !file', [
'!file' => $file,
]), 'error');
}
}
}
$mod = isset($location['mod']) ? $location['mod'] : FALSE;
if ($mod && chmod($dest, $mod)) {
drush_log(dt('Chmod Success: !file', [
'!file' => $file,
]), 'success');
}
elseif ($mod) {
drush_log(dt('Chmod Error: !file', [
'!file' => $file,
]), 'error');
}
}
try {
drush_acsf_init_patch_htaccess();
} catch (AcsfInitHtaccessException $e) {
drush_log($e
->getMessage(), 'error');
}
if (!$skip_default_settings) {
drush_print(dt('Updating the default settings.php file with the ACSF specific business logic.'));
$edit_allowed = TRUE;
$default_settings_php_path = $repo_root . '/docroot/sites/default/settings.php';
if (file_exists($default_settings_php_path)) {
$edit_allowed = drush_confirm(dt('Destination file exists, continue?'));
}
if ($edit_allowed !== FALSE) {
if (file_exists($default_settings_php_path) && !is_writable($default_settings_php_path)) {
if (!chmod($default_settings_php_path, 0666)) {
drush_log(dt('Chmod Error: !file', [
'!file' => $file,
]), 'error');
}
}
if (file_exists($default_settings_php_path)) {
$current_default_settings_php = file_get_contents($default_settings_php_path);
$legacy_default_settings_php = file_get_contents($lib_path . '/sites/default/acsf.legacy.default.settings.php');
if ($current_default_settings_php === $legacy_default_settings_php) {
acsf_init_default_settings_php_create($default_settings_php_path);
}
else {
acsf_init_default_settings_php_update($default_settings_php_path);
}
}
else {
acsf_init_default_settings_php_create($default_settings_php_path);
}
}
}
clearstatcache();
$result = drush_acsf_init_verify();
if ($result) {
drush_print(dt("Be sure to commit any changes to your repository before deploying. This includes files like sites/default/settings.php; you can use 'git status --ignored' to make sure no changes are inadvertantly ignored by a custom git configuration."));
}
}
function acsf_init_default_settings_php_create($default_settings_php_path) {
$result = file_put_contents($default_settings_php_path, "<?php\n\n" . acsf_init_default_settings_php_include_get() . "\n");
if ($result) {
drush_log(dt('File create success: sites/default/settings.php'), 'success');
}
else {
drush_log(dt('File create error: sites/default/settings.php'), 'error');
}
}
function acsf_init_default_settings_php_update($default_settings_php_path) {
$default_settings_php_contents = file_get_contents($default_settings_php_path);
if (!preg_match('/' . preg_quote(ACSF_INIT_CODE_DELIMITER_START, '/') . '.*?' . preg_quote(ACSF_INIT_CODE_DELIMITER_END, '/') . '/ms', $default_settings_php_contents)) {
drush_log(dt('ACSF include not detected in sites/default/settings.php.'), 'notice');
$default_settings_php_contents = preg_replace('/<\\?php/', "<?php\n\n" . acsf_init_default_settings_php_include_get() . "\n", $default_settings_php_contents, 1, $count);
if ($count === 0) {
drush_log(dt('Could not find <?php tag in sites/default/settings.php.'), 'error');
}
}
else {
drush_log(dt('ACSF include detected in sites/default/settings.php.'), 'notice');
$default_settings_php_contents = preg_replace('/' . preg_quote(ACSF_INIT_CODE_DELIMITER_START, '/') . '.*?' . preg_quote(ACSF_INIT_CODE_DELIMITER_END, '/') . '/ms', acsf_init_default_settings_php_include_get(), $default_settings_php_contents);
}
$result = file_put_contents($default_settings_php_path, $default_settings_php_contents);
if ($result) {
drush_log(dt('File edit success: sites/default/settings.php'), 'success');
}
else {
drush_log(dt('File edit error: sites/default/settings.php'), 'error');
}
}
function acsf_init_default_settings_php_include_get() {
$delimiter_start = ACSF_INIT_CODE_DELIMITER_START;
$delimiter_end = ACSF_INIT_CODE_DELIMITER_END;
return <<<INCLUDE
{<span class="php-variable">$delimiter_start</span>}
\$_acsf_infrastructure = include dirname(__FILE__) . '/acsf.settings.php';
if (\$_acsf_infrastructure === 'acsf-infrastructure') {
return;
}
{<span class="php-variable">$delimiter_end</span>}
INCLUDE;
}
function drush_acsf_init_verify() {
$drupal_root = realpath(DRUPAL_ROOT);
if (basename($drupal_root) !== 'docroot') {
return drush_set_error('INCORRECT DIRECTORY STRUCTURE', dt("Drupal must be installed in a subdirectory of your code repository, named 'docroot'."));
}
$repo_root = dirname($drupal_root);
$skip_default_settings = drush_get_option('skip-default-settings');
$lib_path = sprintf('%s/lib', dirname(__FILE__));
$error = FALSE;
foreach (acsf_init_get_required_files($repo_root) as $location) {
$file = $location['filename'];
$dest = sprintf('%s/%s', $location['dest'], $file);
if (isset($location['source']) && isset($location['dest'])) {
$source = sprintf('%s/%s/%s', $lib_path, $location['source'], $file);
if (!file_exists($dest)) {
$error = TRUE;
drush_set_error('FILE MISSING', dt('The file !file is missing.', [
'!file' => $file,
]));
}
elseif (md5_file($source) != md5_file($dest)) {
$error = TRUE;
drush_set_error('FILE OUT OF DATE', dt('The file !file is out of date.', [
'!file' => $file,
]));
}
}
if (file_exists($dest) && !empty($location['test_executable'])) {
$dest_permissions = fileperms($dest);
if (($dest_permissions & (0100 | 010)) != (0100 | 010)) {
$error = TRUE;
drush_set_error('INCORRECT FILE MODE', dt('The file !file is not executable. Make this file executable for the owner and group, then commit it again.', [
'!file' => $file,
]));
}
}
}
if (!drush_acsf_init_test_htaccess_is_patched()) {
$error = TRUE;
drush_set_error('HTACCESS UNPATCHED', dt('The .htaccess file has not been patched to allow access to apc_rebuild.php.'));
}
if (!$skip_default_settings) {
$acsf_business_logic = acsf_init_default_settings_php_include_get();
$acsf_business_logic_fragments = explode("\n", $acsf_business_logic);
$missing_piece = FALSE;
$default_settings_php_contents = file_get_contents($repo_root . '/docroot/sites/default/settings.php');
foreach ($acsf_business_logic_fragments as $line) {
if (strpos($default_settings_php_contents, $line) === FALSE) {
$missing_piece = TRUE;
break;
}
}
if ($missing_piece) {
$error = TRUE;
drush_set_error('FILE OUT OF DATE', dt('The default settings.php file is out of date.'));
}
}
if ($error) {
drush_print(dt('Please run drush acsf-init to correct these problems and commit the resulting code changes.'));
return FALSE;
}
else {
drush_log(dt('acsf-init required files ok'), 'success');
return TRUE;
}
}
function drush_acsf_connect_factory() {
$site_admin_mail = trim(drush_get_option('site-admin-mail'));
$site_owner_name = trim(drush_get_option('site-owner-name'));
$site_owner_mail = trim(drush_get_option('site-owner-mail'));
$site_owner_roles = drush_get_option_list('site-owner-roles');
$validator = \Drupal::service('email.validator');
if (!$validator
->isValid($site_admin_mail)) {
return drush_set_error(dt('The site-admin-mail value is not a valid email address.'));
}
if (!$validator
->isValid($site_owner_mail)) {
return drush_set_error(dt('The site-owner-mail value is not a valid email address.'));
}
$user_storage = \Drupal::entityTypeManager()
->getStorage('user');
$site_admin_mail_accounts = $user_storage
->loadByProperties([
'mail' => $site_admin_mail,
]);
$site_admin_mail_account = reset($site_admin_mail_accounts);
if ($site_admin_mail_account && $site_admin_mail_account
->id() > 1) {
return drush_set_error(dt('Unable to sync the admin account, the email address @mail is already used by the user account @uid.', [
'@mail' => $site_admin_mail,
'@uid' => $site_admin_mail_account
->id(),
]));
}
$site_owner_accounts = $user_storage
->loadByProperties([
'mail' => $site_owner_mail,
]);
$site_owner_account = reset($site_owner_accounts);
if ($site_owner_account && $site_owner_account
->getAccountName() !== $site_owner_name) {
return drush_set_error(dt('The site-owner-name value does not match the name of the user loaded by site-owner-mail.'));
}
if (!$site_owner_account) {
$site_owner_accounts = $user_storage
->loadByProperties([
'name' => $site_owner_name,
]);
$site_owner_account = reset($site_owner_accounts);
}
if (!$site_owner_account) {
if (!drush_confirm(dt('The site owner name or email address that you provided does not correspond to any account on the site. Do you want to create a new account?'))) {
return drush_user_abort();
}
}
drupal_flush_all_caches();
acsf_build_registry();
drush_log(dt('Cleared all caches.'), 'ok');
$admin_role_ids = \Drupal::EntityQuery('user_role')
->condition('is_admin', TRUE)
->execute();
$admin_account = User::load(1);
if (!$admin_account) {
$admin_account = User::create([
'uid' => 1,
]);
}
$admin_account
->addRole(reset($admin_role_ids));
$admin_account
->setLastLoginTime(1)
->setUsername('Site Factory admin')
->setEmail($site_admin_mail)
->setPassword(user_password())
->activate()
->save();
if (!$site_owner_account) {
$site_owner_account = User::create();
}
foreach ($site_owner_roles as $owner_role) {
if (Role::load($owner_role)) {
$site_owner_account
->addRole($owner_role);
}
else {
drush_log(dt('The role @role does not exist; not adding it to the site owner.', [
'@role' => $owner_role,
]), 'warning');
}
}
$site_owner_account
->addRole(reset($admin_role_ids));
$site_owner_account
->setLastLoginTime(1)
->setUsername($site_owner_name)
->setEmail($site_owner_mail)
->setPassword(user_password())
->activate()
->save();
drush_log(dt('Synched Site Factory admin and site owner accounts.'), 'ok');
\Drupal::service('acsf.variable_storage')
->delete('acsf_site_info');
$site = AcsfSite::load();
$site
->clean();
drush_acsf_site_sync();
drush_log(dt('Executed acsf-site-sync to gather site data from factory and reset all acsf variables.'), 'ok');
drupal_flush_all_caches();
drush_log(dt('Cleared all caches.'), 'ok');
\Drupal::service('acsf.theme_notification')
->sendNotification('site', 'create');
}
function drush_acsf_uninstall() {
drush_print('Removing ACSF requirements.');
$drupal_root = realpath(DRUPAL_ROOT);
$repo_root = dirname($drupal_root);
if (basename($drupal_root) !== 'docroot') {
$repo_root = $drupal_root;
}
foreach (acsf_init_get_required_files($repo_root) as $location) {
$file = $location['filename'];
$dest = sprintf('%s/%s', $location['dest'], $file);
if (isset($location['source']) && file_exists($dest)) {
$confirm = drush_confirm(dt('Delete !file?', [
'!file' => $dest,
]));
if ($confirm === FALSE) {
continue;
}
if (unlink($dest)) {
drush_log(dt('Success'), 'success');
}
else {
drush_log(dt('Error'), 'error');
}
}
}
if (file_exists($repo_root . '/docroot/sites/default/settings.php')) {
$default_settings_php_contents = file_get_contents($repo_root . '/docroot/sites/default/settings.php');
$default_settings_php_contents = preg_replace('/' . preg_quote(ACSF_INIT_CODE_DELIMITER_START, '/') . '.*?' . preg_quote(ACSF_INIT_CODE_DELIMITER_END, '/') . '/sm', '', $default_settings_php_contents);
file_put_contents($repo_root . '/docroot/sites/default/settings.php', $default_settings_php_contents);
}
}
function acsf_init_get_required_dirs($repo_root) {
return [
'cloud hooks' => sprintf('%s/hooks/common/post-db-copy', $repo_root),
'cloud hook deploy' => sprintf('%s/hooks/common/pre-web-activate', $repo_root),
'acquia hook dir' => sprintf('%s/hooks/acquia', $repo_root),
'cloud hook samples' => sprintf('%s/hooks/samples', $repo_root),
'site config logic' => sprintf('%s/sites/g', DRUPAL_ROOT),
'site env default' => sprintf('%s/sites/default', DRUPAL_ROOT),
];
}
function acsf_init_get_required_files($repo_root) {
return [
[
'filename' => 'README.md',
'source' => 'cloud_hooks',
'dest' => sprintf('%s/hooks', $repo_root),
],
[
'filename' => '000-acquia_required_scrub.php',
'source' => 'cloud_hooks/common/post-db-copy',
'dest' => sprintf('%s/hooks/common/post-db-copy', $repo_root),
'mod' => 0750,
'test_executable' => TRUE,
],
[
'filename' => '000-acquia-deployment.php',
'source' => 'cloud_hooks/common/pre-web-activate',
'dest' => sprintf('%s/hooks/common/pre-web-activate', $repo_root),
'mod' => 0750,
'test_executable' => TRUE,
],
[
'filename' => 'db_connect.php',
'source' => 'cloud_hooks/acquia',
'dest' => sprintf('%s/hooks/acquia', $repo_root),
],
[
'filename' => 'uri.php',
'source' => 'cloud_hooks/acquia',
'dest' => sprintf('%s/hooks/acquia', $repo_root),
],
[
'filename' => 'acquia-cloud-site-factory-post-db.sh',
'source' => 'cloud_hooks/samples',
'dest' => sprintf('%s/hooks/samples', $repo_root),
],
[
'filename' => 'hello-world.sh',
'source' => 'cloud_hooks/samples',
'dest' => sprintf('%s/hooks/samples', $repo_root),
],
[
'filename' => 'sites.php',
'source' => 'sites',
'dest' => sprintf('%s/sites', DRUPAL_ROOT),
],
[
'filename' => 'apc_rebuild.php',
'source' => 'sites/g',
'dest' => sprintf('%s/sites/g', DRUPAL_ROOT),
],
[
'filename' => '.gitignore',
'source' => 'sites/g',
'dest' => sprintf('%s/sites/g', DRUPAL_ROOT),
],
[
'filename' => 'services.yml',
'source' => 'sites/g',
'dest' => sprintf('%s/sites/g', DRUPAL_ROOT),
],
[
'filename' => 'settings.php',
'source' => 'sites/g',
'dest' => sprintf('%s/sites/g', DRUPAL_ROOT),
],
[
'filename' => 'SimpleRest.php',
'source' => 'sites/g',
'dest' => sprintf('%s/sites/g', DRUPAL_ROOT),
],
[
'filename' => 'sites.inc',
'source' => 'sites/g',
'dest' => sprintf('%s/sites/g', DRUPAL_ROOT),
],
[
'filename' => '.gitignore',
'source' => 'sites/default',
'dest' => sprintf('%s/sites/default', DRUPAL_ROOT),
],
[
'filename' => 'acsf.settings.php',
'source' => 'sites/default',
'dest' => sprintf('%s/sites/default', DRUPAL_ROOT),
],
];
}
function drush_acsf_init_patch_htaccess() {
if (drush_acsf_init_test_htaccess_is_patched()) {
return;
}
drush_print(dt('Patching .htaccess file.'));
$output_lines = [];
$output = '';
$fp = fopen(drush_acsf_init_get_htaccess_path(), 'r+');
$marker_found = FALSE;
while (($line = fgets($fp, 4096)) !== FALSE) {
$output_lines[] = $line;
if (strpos($line, ACSF_HTACCESS_PATCH_MARKER) !== FALSE) {
$marker_found = TRUE;
$marker_index = count($output_lines) - 1;
while ($marker_index > 0 && preg_match('/^\\s*#.*$/', $output_lines[$marker_index - 1])) {
$marker_index--;
}
if ($marker_index == 0) {
throw new AcsfInitHtaccessException('Reached the beginning of the file but was unable to find a place to insert the .htaccess patch. The .htaccess file can be manually handled to fix this error.');
}
array_splice($output_lines, $marker_index, 0, [
ACSF_HTACCESS_PATCH_COMMENT . "\n",
' ' . ACSF_HTACCESS_PATCH . "\n",
]);
$output = implode('', $output_lines);
break;
}
}
if (!$marker_found) {
throw new AcsfInitHtaccessException('Unable to locate the marker for patching the .htaccess file. This file will need manual patching to allow access to the apc_rebuild.php file.');
}
while (($line = fgets($fp, 4096)) !== FALSE) {
$output .= "{$line}";
}
file_put_contents(drush_acsf_init_get_htaccess_path(), $output);
if (drush_acsf_init_test_htaccess_is_patched()) {
drush_log(dt('Successfully patched .htaccess file.'), 'ok');
}
else {
drush_log(dt('Failed to patch .htaccess file.'), 'error');
}
}
function drush_acsf_init_test_htaccess_is_patched() {
if (!file_exists(drush_acsf_init_get_htaccess_path()) || !($content = file_get_contents(drush_acsf_init_get_htaccess_path()))) {
return TRUE;
}
return strpos($content, ACSF_HTACCESS_PATCH) !== FALSE;
}
function drush_acsf_init_get_htaccess_path() {
return DRUPAL_ROOT . '/.htaccess';
}