inline_image_saver-1.0.x-dev/src/Form/InlineImageSaverSettingsForm.php
src/Form/InlineImageSaverSettingsForm.php
<?php
declare(strict_types=1);
namespace Drupal\inline_image_saver\Form;
use Drupal\Component\Plugin\PluginManagerInterface;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Config\TypedConfigManagerInterface;
use Drupal\Core\DependencyInjection\AutowireTrait;
use Drupal\Core\Entity\SynchronizableInterface;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\ConfigTarget;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Form\RedundantEditableConfigNamesTrait;
use Drupal\Core\Render\Element\Checkboxes;
use Drupal\filter\FilterFormatInterface;
use Drupal\inline_image_saver\InlineImageMimeInterface;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
/**
* Configures inline_image_saver.settings for this site.
*/
class InlineImageSaverSettingsForm extends ConfigFormBase {
use AutowireTrait;
use RedundantEditableConfigNamesTrait;
/**
* The module config name.
*
* @var string
*/
public const CONFIG_NAME = 'inline_image_saver.settings';
public function __construct(
ConfigFactoryInterface $configFactory,
TypedConfigManagerInterface $typedConfigManager,
protected InlineImageMimeInterface $inlineImageMime,
#[Autowire('@plugin.manager.filter')]
protected PluginManagerInterface $filterPluginManager,
) {
parent::__construct($configFactory, $typedConfigManager);
}
/**
* {@inheritdoc}
*/
public function getFormId(): string {
return 'inline_image_saver_settings_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state): array {
$form['processable_formats'] = [
'#type' => 'checkboxes',
'#title' => $this->t('Processable formats'),
'#options' => array_map(fn (FilterFormatInterface $format) => $format->label(), filter_formats()),
'#description' => $this->t('Select which text formats should be processed. If none are selected, all available formats will be processed.'),
'#config_target' => new ConfigTarget(
static::CONFIG_NAME,
'processable_formats',
toConfig: Checkboxes::getCheckedCheckboxes(...),
),
];
$form['validation'] = [
'#type' => 'fieldset',
'#title' => $this->t('Image validation'),
];
$form['validation']['enable_validation'] = [
'#type' => 'checkbox',
'#title' => $this->t('Enable image validation'),
'#description' => $this->t('Validates inline images in text fields and prevents use of external images (not stored on your site).'),
'#config_target' => static::CONFIG_NAME . ':enable_validation',
];
$form['validation']['settings'] = [
'#type' => 'container',
'#states' => [
'visible' => [
':input[name="enable_validation"]' => ['checked' => TRUE],
],
],
];
$form['validation']['settings']['allow_if_downloadable'] = [
'#type' => 'checkbox',
'#title' => $this->t('Allow downloadable external images'),
'#description' => $this->t('Only applies if image downloading is enabled below. Skips validation for external images that can be downloaded and stored locally. These images will be saved and replace the original source.'),
'#states' => [
'enabled' => [
':input[name="enable_download"]' => ['checked' => TRUE],
],
],
'#config_target' => static::CONFIG_NAME . ':validation_settings.allow_if_downloadable',
];
$form['validation']['settings']['allow_data_uri'] = [
'#type' => 'checkbox',
'#title' => $this->t('Allow data URI images'),
'#description' => $this->t('Allows inline base64-encoded images like <code><img src="data:image/..."></code> to pass validation.'),
'#config_target' => static::CONFIG_NAME . ':validation_settings.allow_data_uri',
];
$form['validation']['settings']['check_file_exists'] = [
'#type' => 'checkbox',
'#title' => $this->t('Check image file exists'),
'#description' => $this->t('Additionally verifies that the referenced image file exists on the file system. Improves validation reliability but may slow down performance. By default, only entity references via <code>data-entity-*</code> attributes are checked.'),
'#config_target' => static::CONFIG_NAME . ':validation_settings.check_file_exists',
];
$form['validation']['settings']['check_file_mime'] = [
'#type' => 'checkbox',
'#title' => $this->t('Check image file MIME type'),
'#description' => $this->t('Check if the image file has a valid and supported MIME type.'),
'#config_target' => static::CONFIG_NAME . ':validation_settings.check_file_mime',
];
$this->disableIfNoMimeResolver($form['validation']['settings']['check_file_mime']);
$form['validation']['settings']['validate_url'] = [
'#type' => 'checkbox',
'#title' => $this->t('Validate image URL'),
'#description' => $this->t('Validates that the image URL matches the expected file entity URL (host and path only). Usually unnecessary - "@editor_file_reference" filter handles this automatically.', [
'@editor_file_reference' => $this->filterPluginManager->getDefinition('editor_file_reference')['title'],
]),
'#config_target' => static::CONFIG_NAME . ':validation_settings.validate_url',
];
$form['validation']['settings']['validate_url_query'] = [
'#type' => 'checkbox',
'#title' => $this->t('Include query parameters in URL validation'),
'#description' => $this->t('Also checks query parameters in image URLs. May cause issues with dynamic parameters such as tokens from the <a href=":module_url">Private File Token</a> module. Not recommended for most sites.', [
':module_url' => 'https://www.drupal.org/project/private_file_token',
]),
'#states' => [
'enabled' => [
':input[name="validate_url"]' => ['checked' => TRUE],
],
],
'#config_target' => static::CONFIG_NAME . ':validation_settings.validate_url_query',
];
$form['download'] = [
'#type' => 'fieldset',
'#title' => $this->t('Image download'),
];
$form['download']['enable_download'] = [
'#type' => 'checkbox',
'#title' => $this->t('Download external images'),
'#description' => $this->t('Automatically downloads external images when saving an entity to prevent broken image links. The downloaded image replaces the original external source.'),
'#config_target' => static::CONFIG_NAME . ':enable_download',
];
$this->disableIfNoMimeResolver($form['download']['enable_download']);
$form['download']['settings'] = [
'#type' => 'container',
'#access' => !$form['download']['enable_download']['#disabled'],
'#states' => [
'visible' => [
':input[name="enable_download"]' => ['checked' => TRUE],
],
],
];
$form['download']['settings']['prefer_reuse_files'] = [
'#type' => 'checkbox',
'#title' => $this->t('Prefer reusing existing files'),
'#description' => $this->t('If enabled, the module searches for an existing file entity with identical content (based on file hash) and reuses it instead of saving a new one. Reduces duplication and saves disk space. Supports <a href=":module_url">File Hash</a> module for improved matching.', [
':module_url' => 'https://www.drupal.org/project/filehash',
]),
'#config_target' => static::CONFIG_NAME . ':prefer_reuse_files',
];
$form['replace'] = [
'#type' => 'fieldset',
'#title' => $this->t('Replace broken images'),
];
$form['replace']['enable_replace'] = [
'#type' => 'checkbox',
'#title' => $this->t('Replace broken images'),
'#description' => $this->t('Replaces broken inline images with fallback markup. An image is considered broken if it does not exist on the file system and cannot be downloaded.'),
'#config_target' => static::CONFIG_NAME . ':enable_replace',
];
$form['replace']['settings'] = [
'#type' => 'container',
'#states' => [
'visible' => [
':input[name="enable_replace"]' => ['checked' => TRUE],
],
],
];
$form['replace']['settings']['fallback_markup'] = [
'#type' => 'textarea',
'#title' => $this->t('Fallback markup'),
'#placeholder' => $this->t('[image not found: @src]'),
'#description' => $this->t('Available placeholders: <code>@src</code>, <code>@alt</code> and other image attributes. You may include the following allowed HTML tags: <code>@tags</code>', [
'@tags' => '<' . implode('> <', Xss::getAdminTagList()) . '>',
]),
'#config_target' => new ConfigTarget(
static::CONFIG_NAME,
'fallback_markup',
toConfig: Xss::filterAdmin(...),
),
];
$form['revision_group'] = [
'#type' => 'fieldset',
'#title' => $this->t('Create new revision'),
];
$form['revision_group']['create_revision'] = [
'#type' => 'checkbox',
'#title' => $this->t('Create revision'),
'#description' => $this->t('Creates a new revision if an external image is replaced with a downloaded image or fallback markup.'),
'#config_target' => static::CONFIG_NAME . ':create_revision',
];
$form['revision_group']['settings'] = [
'#type' => 'container',
'#states' => [
'visible' => [
':input[name="create_revision"]' => ['checked' => TRUE],
],
],
];
$form['revision_group']['settings']['revision_log'] = [
'#type' => 'textarea',
'#title' => $this->t('Revision log message'),
'#placeholder' => $this->t('External images have been replaced.'),
'#config_target' => static::CONFIG_NAME . ':revision_log',
];
$form['skip_on_sync'] = [
'#type' => 'checkbox',
'#title' => $this->t('Skip processing on sync'),
'#description' => $this->t('Skips processing images when an entity is being synchronized programmatically. Useful for batch updates. See <code>@interface</code>.', [
'@interface' => SynchronizableInterface::class,
]),
'#config_target' => static::CONFIG_NAME . ':skip_on_sync',
];
return parent::buildForm($form, $form_state);
}
/**
* Disables the element if MIME type detection is not supported.
*
* @param array $element
* The form element to be modified.
*/
protected function disableIfNoMimeResolver(array &$element): void {
if ($this->inlineImageMime->isSupported($reason)) {
$element['#disabled'] = FALSE;
return;
}
if ($description = &$element['#description']) {
$description = "<s>$description</s><br>";
}
$description = $this->t('MIME type detection is not available.');
if ($reason) {
$description .= " $reason";
}
$element['#disabled'] = TRUE;
}
}
