image_to_media_swapper-2.x-dev/src/Form/SecuritySettingsForm.php
src/Form/SecuritySettingsForm.php
<?php
declare(strict_types=1);
namespace Drupal\image_to_media_swapper\Form;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Config\TypedConfigManagerInterface;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\image_to_media_swapper\SwapperService;
/**
* Configure security settings for Image to Media Swapper module.
*/
final class SecuritySettingsForm extends ConfigFormBase {
/**
* The swapper service.
*/
protected SwapperService $swapperService;
/**
* SecuritySettingsForm constructor.
*/
public function __construct(
ConfigFactoryInterface $configFactory,
TypedConfigManagerInterface $typedConfigManager,
SwapperService $swapperService,
) {
parent::__construct($configFactory, $typedConfigManager);
$this->swapperService = $swapperService;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container): self {
/** @var \Drupal\Core\Config\ConfigFactoryInterface $configFactory */
$configFactory = $container->get('config.factory');
/** @var \Drupal\Core\Config\TypedConfigManagerInterface $typedConfigManager */
$typedConfigManager = $container->get('config.typed');
/** @var \Drupal\image_to_media_swapper\SwapperService $swapperService */
$swapperService = $container->get('image_to_media_swapper.service');
return new self(
$configFactory,
$typedConfigManager,
$swapperService,
);
}
/**
* Config settings.
*
* @var string
*/
const SETTINGS = 'image_to_media_swapper.security_settings';
/**
* {@inheritdoc}
*/
public function getFormId(): string {
return 'image_to_media_swapper_security_settings';
}
/**
* {@inheritdoc}
*/
protected function getEditableConfigNames(): array {
return [
SecuritySettingsForm::SETTINGS,
];
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state): array {
$config = $this->config(self::SETTINGS);
$form['remote_downloads'] = [
'#type' => 'details',
'#title' => $this->t('Remote File Downloads'),
'#open' => TRUE,
];
$form['remote_downloads']['enable_remote_downloads'] = [
'#type' => 'checkbox',
'#title' => $this->t('Enable remote file downloads'),
'#description' => $this->t('Allow downloading files from remote URLs. Disable for maximum security.'),
'#default_value' => $config->get('enable_remote_downloads') ?? TRUE,
];
$form['remote_downloads']['max_file_size'] = [
'#type' => 'number',
'#title' => $this->t('Maximum file size (MB)'),
'#description' => $this->t('Maximum file size allowed for remote downloads in megabytes.'),
'#default_value' => $config->get('max_file_size') ?? 10,
'#min' => 1,
'#max' => 100,
'#step' => 1,
'#states' => [
'visible' => [
':input[name="enable_remote_downloads"]' => ['checked' => TRUE],
],
],
];
$form['remote_downloads']['download_timeout'] = [
'#type' => 'number',
'#title' => $this->t('Download timeout (seconds)'),
'#description' => $this->t('Maximum time to wait for a remote file download.'),
'#default_value' => $config->get('download_timeout') ?? 30,
'#min' => 5,
'#max' => 300,
'#step' => 5,
'#states' => [
'visible' => [
':input[name="enable_remote_downloads"]' => ['checked' => TRUE],
],
],
];
$form['allowed_domains'] = [
'#type' => 'details',
'#title' => $this->t('Allowed Domains'),
'#open' => TRUE,
];
$form['allowed_domains']['restrict_domains'] = [
'#type' => 'checkbox',
'#title' => $this->t('Restrict allowed domains'),
'#description' => $this->t('Only allow downloads from specified domains. Leave unchecked to allow all domains (not recommended).'),
'#default_value' => $config->get('restrict_domains') ?? FALSE,
];
$form['allowed_domains']['allowed_domains_list'] = [
'#type' => 'textarea',
'#title' => $this->t('Allowed domains'),
'#description' => $this->t('Enter one domain per line. Use wildcards for subdomains (e.g., *.example.com). Examples:<br/>example.com<br/>*.example.org<br/>cdn.mysite.com'),
'#default_value' => $config->get('allowed_domains_list') ?? '',
'#rows' => 8,
'#states' => [
'visible' => [
':input[name="restrict_domains"]' => ['checked' => TRUE],
],
'required' => [
':input[name="restrict_domains"]' => ['checked' => TRUE],
],
],
];
$form['file_types'] = [
'#type' => 'details',
'#title' => $this->t('File Type Restrictions'),
'#open' => TRUE,
];
$form['file_types']['allowed_extensions'] = [
'#type' => 'textarea',
'#title' => $this->t('Allowed file extensions'),
'#description' => $this->t('Enter allowed file extensions, separated by spaces. Examples: jpg png gif pdf.'),
'#default_value' => $this->getAllowedExtensions(),
'#rows' => 4,
'#required' => TRUE,
];
$form['file_types']['allowed_extensions_description'] = [
'#markup' => $this->t('The available file extensions for all media types are: <code>@extensions</code>', [
'@extensions' => $this->arrayToString($this->swapperService->getAvailableExtensions()),
]),
];
$form['security'] = [
'#type' => 'details',
'#title' => $this->t('Security Options'),
'#open' => TRUE,
];
$form['security']['disable_batch_processing'] = [
'#type' => 'checkbox',
'#title' => $this->t('Disable batch processing'),
'#description' => $this->t('Completely disable the batch file-to-media swapper functionality. This will prevent all users from accessing the batch processing feature regardless of permissions.'),
'#default_value' => $config->get('disable_batch_processing') ?? TRUE,
];
$form['security']['block_private_ips'] = [
'#type' => 'checkbox',
'#title' => $this->t('Block private IP addresses'),
'#description' => $this->t('Prevent downloads from private/internal IP addresses (recommended for security).'),
'#default_value' => $config->get('block_private_ips') ?? TRUE,
];
$form['security']['require_https'] = [
'#type' => 'checkbox',
'#title' => $this->t('Require HTTPS'),
'#description' => $this->t('Only allow downloads from HTTPS URLs (recommended for security).'),
'#default_value' => $config->get('require_https') ?? FALSE,
];
$form['security']['max_redirects'] = [
'#type' => 'number',
'#title' => $this->t('Maximum redirects'),
'#description' => $this->t('Maximum number of redirects to follow when downloading files.'),
'#default_value' => $config->get('max_redirects') ?? 3,
'#min' => 0,
'#max' => 10,
'#step' => 1,
];
return parent::buildForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state): void {
parent::validateForm($form, $form_state);
// Validate allowed domains format.
if ($form_state->getValue('restrict_domains')) {
$domains = $form_state->getValue('allowed_domains_list');
if (empty(trim($domains))) {
$form_state->setErrorByName('allowed_domains_list', $this->t('You must specify at least one allowed domain when domain restriction is enabled.'));
}
else {
$this->validateDomainsList($domains, $form_state);
}
}
// Validate file extensions.
$extensions = $form_state->getValue('allowed_extensions');
if (!empty($extensions)) {
$this->validateExtensionsList($extensions, $form_state);
}
}
/**
* Validates the domains list format.
*/
private function validateDomainsList(string $domains, FormStateInterface $form_state): void {
$domainLines = array_filter(array_map('trim', preg_split('/[\r\n]+/', $domains)));
foreach ($domainLines as $line => $domain) {
// Basic domain validation (allowing wildcards).
if (!preg_match('/^(\*\.)?[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?)*$/', $domain)) {
$form_state->setErrorByName('allowed_domains_list', $this->t('Invalid domain format on line @line: @domain', [
'@line' => $line + 1,
'@domain' => $domain,
]));
}
}
}
/**
* Validates the extensions list format.
*/
private function validateExtensionsList(string $extensions, FormStateInterface $form_state): void {
// Parse both comma-separated and line-separated formats.
$extensionList = $this->parseListInput($extensions);
foreach ($extensionList as $ext) {
if (!preg_match('/^[a-zA-Z0-9]+$/', $ext)) {
$form_state->setErrorByName('allowed_extensions', $this->t('Invalid file extension: @ext. Extensions should only contain letters and numbers.', [
'@ext' => $ext,
]));
}
}
}
/**
* Parses comma-separated or line-separated input.
*/
private function parseListInput(string $input): array {
return explode(' ', $input,);
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state): void {
$config = $this->config(self::SETTINGS);
// Remote download settings.
$config->set('enable_remote_downloads', $form_state->getValue('enable_remote_downloads'));
$config->set('max_file_size', $form_state->getValue('max_file_size'));
$config->set('download_timeout', $form_state->getValue('download_timeout'));
// Domain restrictions.
$config->set('restrict_domains', $form_state->getValue('restrict_domains'));
$config->set('allowed_domains_list', $form_state->getValue('allowed_domains_list'));
// Parse and store domains as array.
if ($form_state->getValue('restrict_domains')) {
$domains = array_filter(array_map('trim', preg_split('/[\r\n]+/', $form_state->getValue('allowed_domains_list'))));
$config->set('allowed_domains', $domains);
}
else {
$config->set('allowed_domains', []);
}
// File type restrictions.
$config->set('allowed_extensions', $this->parseListInput($form_state->getValue('allowed_extensions')));
// Security options.
$config->set('disable_batch_processing', $form_state->getValue('disable_batch_processing'));
$config->set('block_private_ips', $form_state->getValue('block_private_ips'));
$config->set('require_https', $form_state->getValue('require_https'));
$config->set('max_redirects', $form_state->getValue('max_redirects'));
$config->save();
parent::submitForm($form, $form_state);
}
/**
* Converts an associative array to textarea format.
*/
private function arrayToString(array $array): string {
return implode(' ', $array);
}
/**
* Gets the allowed file extensions from configuration.
*
* @return string
* * Returns a string of allowed file extensions, from config or site logic.
*/
private function getAllowedExtensions(): string {
if (empty($extensions)) {
$extensions = $this->swapperService->getAvailableExtensions();
}
return $this->arrayToString($extensions);
}
}
