config_preview_deploy-1.0.0-alpha3/src/Access/PreviewEnvironmentAccess.php
src/Access/PreviewEnvironmentAccess.php
<?php
declare(strict_types=1);
namespace Drupal\config_preview_deploy\Access;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Access\AccessResultInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Url;
/**
* Access controller for preview environment features.
*/
class PreviewEnvironmentAccess {
/**
* The config factory service.
*/
protected ConfigFactoryInterface $configFactory;
/**
* Constructs a PreviewEnvironmentAccess object.
*/
public function __construct(ConfigFactoryInterface $configFactory) {
$this->configFactory = $configFactory;
}
/**
* Checks access for deploy form.
*
* @param \Drupal\Core\Session\AccountInterface $account
* The user account.
*
* @return \Drupal\Core\Access\AccessResultInterface
* The access result.
*/
public function deployFormAccess(AccountInterface $account): AccessResultInterface {
$result = $this->isProduction()
? AccessResult::forbidden('Deploy form not available on production environments.')
: AccessResult::allowedIfHasPermission($account, 'deploy config from preview');
// Add cache dependency on config that affects production detection.
return $result->addCacheableDependency($this->configFactory->get('config_preview_deploy.settings'));
}
/**
* Checks access for rebase form.
*
* @param \Drupal\Core\Session\AccountInterface $account
* The user account.
*
* @return \Drupal\Core\Access\AccessResultInterface
* The access result.
*/
public function rebaseFormAccess(AccountInterface $account): AccessResultInterface {
$result = $this->isProduction()
? AccessResult::forbidden('Rebase form not available on production environments.')
: AccessResult::allowedIfHasPermission($account, 'deploy config from preview');
// Add cache dependency on config that affects production detection.
return $result->addCacheableDependency($this->configFactory->get('config_preview_deploy.settings'));
}
/**
* Checks access for download diff.
*
* @param \Drupal\Core\Session\AccountInterface $account
* The user account.
*
* @return \Drupal\Core\Access\AccessResultInterface
* The access result.
*/
public function downloadDiffAccess(AccountInterface $account): AccessResultInterface {
$result = $this->isProduction()
? AccessResult::forbidden('Download diff not available on production environments.')
: AccessResult::allowedIfHasPermission($account, 'deploy config from preview');
// Add cache dependency on config that affects production detection.
return $result->addCacheableDependency($this->configFactory->get('config_preview_deploy.settings'));
}
/**
* Checks access for changes tab.
*
* @param \Drupal\Core\Session\AccountInterface $account
* The user account.
*
* @return \Drupal\Core\Access\AccessResultInterface
* The access result.
*/
public function changesAccess(AccountInterface $account): AccessResultInterface {
$result = $this->isProduction()
? AccessResult::forbidden('Changes tab not available on production environments.')
: AccessResult::allowedIfHasPermission($account, 'deploy config from preview');
// Add cache dependency on config that affects production detection.
return $result->addCacheableDependency($this->configFactory->get('config_preview_deploy.settings'));
}
/**
* Checks if the current environment is production.
*
* @return bool
* TRUE if this is a production environment.
*/
public function isProduction(): bool {
// Check environment variable first.
$envVar = getenv('DRUPAL_CONFIG_PREVIEW_DEPLOY_IS_PRODUCTION');
if ($envVar && filter_var($envVar, FILTER_VALIDATE_BOOLEAN)) {
return TRUE;
}
// Check if current domain matches configured production URL.
$config = $this->configFactory->get('config_preview_deploy.settings');
$productionUrl = $config->get('production_url');
if (empty($productionUrl)) {
return FALSE;
}
// Get current site's base URL using admin path to avoid frontend domain
// overrides.
$currentUrl = Url::fromRoute('system.admin', [], ['absolute' => TRUE])->toString();
// Extract base URL (scheme + host + port + base path) from both URLs.
$currentBase = $this->extractBaseUrl($currentUrl);
$productionBase = $this->extractBaseUrl($productionUrl);
return $currentBase === $productionBase;
}
/**
* Extract base URL from a full URL.
*
* This extracts scheme, host, and port only, ignoring paths.
* This handles reverse proxy setups where the internal hostname
* may differ from the external URL.
*
* @param string $url
* The full URL to extract base from.
*
* @return string
* The base URL (scheme://host[:port]).
*/
protected function extractBaseUrl(string $url): string {
$parsed = parse_url($url);
if (!$parsed || !isset($parsed['scheme']) || !isset($parsed['host'])) {
return '';
}
$base = $parsed['scheme'] . '://' . $parsed['host'];
// Include port if it's not the default for the scheme.
if (isset($parsed['port'])) {
$defaultPorts = ['http' => 80, 'https' => 443];
if (!isset($defaultPorts[$parsed['scheme']]) || $parsed['port'] !== $defaultPorts[$parsed['scheme']]) {
$base .= ':' . $parsed['port'];
}
}
return $base;
}
}
