diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e53965..52caa83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,17 @@ Updates should follow the [Keep a CHANGELOG](https://keepachangelog.com/) princi ## [Unreleased][unreleased] +## [0.1.10] - 2022-01-04 + +### Changed + +- Disallowed access to `default` site in a multi-site install by default. +- Added support for `$database` variable alteration. +- Added support for `FILE_PUBLIC_PATH` environment variable. +- Added support for `FILE_PRIVATE_PATH` environment variable. +- Added support for `FILE_TEMP_PATH` environment variable. +- Added support for `CONFIG_SYNC_PATH` environment variable. + ## [0.1.9] - 2021-12-29 ### Fixed @@ -70,7 +81,8 @@ Updates should follow the [Keep a CHANGELOG](https://keepachangelog.com/) princi **Initial release!** -[unreleased]: https://github.com/unleashedtech/dotenv-drupal/compare/0.1.9...main +[unreleased]: https://github.com/unleashedtech/dotenv-drupal/compare/0.1.10...main +[0.1.10]: https://github.com/unleashedtech/dotenv-drupal/compare/0.1.9...0.1.10 [0.1.9]: https://github.com/unleashedtech/dotenv-drupal/compare/0.1.8...0.1.9 [0.1.8]: https://github.com/unleashedtech/dotenv-drupal/compare/0.1.7...0.1.8 [0.1.7]: https://github.com/unleashedtech/dotenv-drupal/compare/0.1.6...0.1.7 diff --git a/README.md b/README.md index 9c5834b..a844de3 100644 --- a/README.md +++ b/README.md @@ -28,13 +28,24 @@ $databases = $dotenv->getDatabases(); $settings = $dotenv->getSettings(); ``` -##### Multi-Site Drupal -Many multi-site installations leave the `default` site unused. If this is the -case, you can use the `default` settings file for base configuration in the -`settings.php` file for each site. +##### Conditional Logic +If conditional logic is required for a given site, such logic is still supported. +This package will auto-load various `settings.{{environment}}.php`, +`config.{{environment}}.php` or `databases.{{environment}}.php` files, if they exist. +For instance, if you need to set a database prefix for staging, you can create +`databases.staging.php`: -In your relevant environment file(s), leave the "database" (e.g. "path") -empty to allow automatic database name selection based on the site name. +```php +setDatabaseName('foo'); require __DIR__ . '/../default/settings.php'; ``` -If conditional logic is required for a given site, such logic is still supported. -This package will auto-load various `settings.{{environment}}.php` or -`config.{{environment}}.php` files, if they exist. +###### Using the Multi-Site Default Site +If you need to use the `default` site as part of your multi-site install, +you can allow it by calling the `DotEnv::setMultiSiteDefaultSiteAllowed` method +in `default/settings.php`: + +```php +setMultiSiteDefaultSiteAllowed(); +$config = $dotenv->getConfig(); +$databases = $dotenv->getDatabases(); +$settings = $dotenv->getSettings(); +``` ###### Sites Files This package also provides functionality to configure Drupal's `$sites` variable @@ -86,6 +108,10 @@ For production environments, environment variables should ideally be defined via configuration. * [DATABASE_URL](#database_url) +* [FILE_PUBLIC_PATH](#file_public_path) +* [FILE_PRIVATE_PATH](#file_private_path) +* [FILE_TEMP_PATH](#file_temp_path) +* [CONFIG_SYNC_PATH](#config_sync_path) * [DOMAINS](#domains) * [SITES](#sites) * [SOLR_URL](#solr_url) @@ -110,6 +136,36 @@ For multi-site installations, do _not_ specify a database name in the `DATABASE_ DATABASE_URL=mysql://foo:bar@host:3306 ``` +#### FILE_PUBLIC_PATH +Allows you to override the default `$settings['file_public_path']` value: + +```dotenv +FILE_PUBLIC_PATH=sites/all/files +``` + +Drupal expects this path to be _relative_ to `DRUPAL_ROOT`. + +#### FILE_PRIVATE_PATH +Allows you to override the default `$settings['file_private_path']` value: + +```dotenv +FILE_PRIVATE_PATH=/private +``` + +#### FILE_TEMP_PATH +Allows you to override the default `$settings['file_temp_path']` value: + +```dotenv +FILE_TEMP_PATH=/tmp +``` + +#### CONFIG_SYNC_PATH +Allows you to override the default `$settings['config_sync_path']` value: + +```dotenv +CONFIG_SYNC_PATH=/sync +``` + #### DOMAINS A CSV list of domains used by the given environment: diff --git a/src/Dotenv.php b/src/Dotenv.php index 40f04ec..46b3e42 100644 --- a/src/Dotenv.php +++ b/src/Dotenv.php @@ -20,6 +20,11 @@ class Dotenv */ private string $siteName = 'default'; + /** + * @var bool Whether the default site is allowed in a multi-site configuration. + */ + private bool $isMultiSiteDefaultSiteAllowed = FALSE; + /** * The class constructor. */ @@ -71,19 +76,33 @@ public function getEnvironmentName(): string } /** - * Decorates the given data with data of the same type defined in PHP files. + * Alters the given data with data of the same type defined in PHP files. * * @param $data + * The data to alter. * @param $type + * The type of data being altered (e.g. settings, config, databases). */ - private function decorate(&$data, $type): void + private function alter(&$data, $type): void { $$type = &$data; - $file = DRUPAL_ROOT . '/sites/' . $this->getSiteName() . '/' . + + // Allow alteration via the `default` directory. + $file = DRUPAL_ROOT . '/sites/default/' . $type . '.' . $this->getEnvironmentName() . '.php'; if (file_exists($file)) { include $file; } + + // Allow alteration via non-`default` directories. + $siteName = $this->getSiteName(); + if ($siteName !== 'default') { + $file = DRUPAL_ROOT . '/sites/' . $siteName . '/' . + $type . '.' . $this->getEnvironmentName() . '.php'; + if (file_exists($file)) { + include $file; + } + } } /** @@ -139,14 +158,14 @@ public function getConfig(): array ]; break; } - $this->decorate($config, 'config'); + $this->alter($config, 'config'); return $config; } public function getDatabases(): array { $db_url = parse_url($_SERVER['DATABASE_URL']); - return [ + $databases = [ 'default' => [ 'default' => @@ -162,22 +181,47 @@ public function getDatabases(): array ], ], ]; + $this->alter($databases, 'databases'); + return $databases; } - public function getDatabaseName(): ?string + public function getDatabaseName(): string { if (isset($this->databaseName)) { return $this->databaseName; } $result = parse_url($_SERVER['DATABASE_URL'], PHP_URL_PATH); - return (FALSE === $result || '/' === $result) ? $this->getSiteName() : substr($result, 1); + if (NULL === $result || trim($result) === '/') { + // Multi-site configuration detected. Use the site name. + $result = $this->getSiteName(); + if ($result === 'default' && !$this->isMultiSiteDefaultSiteAllowed()) { + header("HTTP/1.1 401 Unauthorized"); + die('Unauthorized'); + } + } else { + $result = substr($result, 1); + } + if (NULL === $result || preg_replace('/[^a-z0-9_]/', '', $result) === '') { + throw new \UnexpectedValueException('Database name could not be computed.'); + } + return $result; } - function setDatabaseName(string $database) + public function setDatabaseName(string $database): void { $this->databaseName = $database; } + public function isMultiSiteDefaultSiteAllowed(): bool + { + return $this->isMultiSiteDefaultSiteAllowed; + } + + public function setMultiSiteDefaultSiteAllowed(bool $allowed = TRUE): void + { + $this->isMultiSiteDefaultSiteAllowed = $allowed; + } + public function getSettings(): array { $envName = $this->getEnvironmentName(); @@ -192,6 +236,7 @@ public function getSettings(): array $settings['config_sync_directory'] = $this->getConfigSyncPath(); $settings['file_public_path'] = $this->getPublicFilePath(); $settings['file_private_path'] = $this->getPrivateFilePath(); + $settings['file_temp_path'] = $this->getTemporaryFilePath(); if (isset($_SERVER['HASH_SALT'])) { $settings['hash_salt'] = $_SERVER['HASH_SALT']; } @@ -221,7 +266,7 @@ public function getSettings(): array $this->getAppPath() . '/sites/' . $envName . '.services.yml', ]; } - $this->decorate($settings, 'settings'); + $this->alter($settings, 'settings'); return $settings; } @@ -253,19 +298,24 @@ public function getProjectPath(): string return dirname(DRUPAL_ROOT, 1); } + public function getPublicFilePath(): string + { + return $_SERVER['FILE_PUBLIC_PATH'] ?? 'sites/' . $this->getSiteName() . '/files'; + } + public function getPrivateFilePath(): string { - return dirname(DRUPAL_ROOT, 1); + return $_SERVER['FILE_PRIVATE_PATH'] ?? $this->getProjectPath() . '/drupal/private_files'; } - public function getPublicFilePath(): string + public function getTemporaryFilePath(): string { - return 'sites/' . $this->getSiteName() . '/files'; + return $_SERVER['FILE_TEMP_PATH'] ?? $this->getProjectPath() . '/drupal/temporary_files'; } public function getConfigSyncPath(): string { - return $this->getProjectPath() . '/drupal/config/sync'; + return $_SERVER['CONFIG_SYNC_PATH'] ?? $this->getProjectPath() . '/drupal/config/sync'; } }