add drush command

This commit is contained in:
Robert Nasarek 2026-06-25 11:19:29 +02:00
parent 3310455a70
commit cb58638fe9
7 changed files with 360 additions and 82 deletions

View file

@ -0,0 +1,130 @@
<?php
namespace Drupal\dfg_3dviewer\Drush\Commands;
use Drupal\dfg_3dviewer\Service\Dfg3dViewerConfigApplier;
use Drush\Attributes as CLI;
use Drush\Commands\DrushCommands;
/**
* Drush commands for DFG 3D Viewer configuration.
*/
final class Dfg3dViewerCommands extends DrushCommands {
public function __construct(
protected Dfg3dViewerConfigApplier $configApplier,
) {
parent::__construct();
}
/**
* Applies DFG 3D Viewer settings (local dev preset by default).
*/
#[CLI\Command(name: 'dfg-3dviewer:configure', aliases: ['dfg3dv-config'])]
#[CLI\Option(name: 'main-url', description: 'Main URL')]
#[CLI\Option(name: 'basenamespace', description: 'Default base namespace')]
#[CLI\Option(name: 'metadata-url', description: 'Metadata URL')]
#[CLI\Option(name: 'json-export-base-url', description: 'JSON export base URL')]
#[CLI\Option(name: 'container', description: 'Container ID')]
#[CLI\Option(name: 'entitybundle', description: 'Entity bundle ID')]
#[CLI\Option(name: 'viewer-file-upload', description: 'Viewer file upload field ID')]
#[CLI\Option(name: 'viewer-file-name', description: 'Viewer file name field ID')]
#[CLI\Option(name: 'api-3d-file-field', description: 'API 3D file field')]
#[CLI\Option(name: 'image-generation', description: 'Image generation field ID')]
#[CLI\Option(name: 'field-df', description: 'Field DF')]
#[CLI\Option(name: 'export-viewer', description: 'Export viewer field')]
#[CLI\Option(name: 'export-viewer-url', description: 'Export viewer URL')]
#[CLI\Option(name: 'lightweight', description: 'Enable lightweight mode (0 or 1)')]
#[CLI\Option(name: 'scale-container-x', description: 'Scale container X')]
#[CLI\Option(name: 'scale-container-y', description: 'Scale container Y')]
#[CLI\Option(name: 'gallery-container', description: 'Gallery container element name')]
#[CLI\Option(name: 'gallery-image-class', description: 'Gallery image class name')]
#[CLI\Option(name: 'gallery-image-id', description: 'Gallery image ID name')]
#[CLI\Option(name: 'base-module-path', description: 'Path for the Viewer module')]
#[CLI\Option(name: 'entity-id-uri', description: 'Regex for entity ID')]
#[CLI\Option(name: 'view-entity-path', description: 'Path with navigate content')]
#[CLI\Option(name: 'attribute-id', description: 'WissKI attribute ID')]
#[CLI\Usage(name: 'drush dfg-3dviewer:configure', description: 'Apply the bundled local development preset')]
#[CLI\Usage(name: 'drush dfg-3dviewer:configure --main-url=https://example.test/', description: 'Apply preset with one override')]
public function configure(
array $options = [
'main-url' => self::OPT,
'basenamespace' => self::OPT,
'metadata-url' => self::OPT,
'json-export-base-url' => self::OPT,
'container' => self::OPT,
'entitybundle' => self::OPT,
'viewer-file-upload' => self::OPT,
'viewer-file-name' => self::OPT,
'api-3d-file-field' => self::OPT,
'image-generation' => self::OPT,
'field-df' => self::OPT,
'export-viewer' => self::OPT,
'export-viewer-url' => self::OPT,
'lightweight' => self::OPT,
'scale-container-x' => self::OPT,
'scale-container-y' => self::OPT,
'gallery-container' => self::OPT,
'gallery-image-class' => self::OPT,
'gallery-image-id' => self::OPT,
'base-module-path' => self::OPT,
'entity-id-uri' => self::OPT,
'view-entity-path' => self::OPT,
'attribute-id' => self::OPT,
],
): void {
$values = $this->configApplier->getDevPreset();
$option_map = [
'main-url' => 'dfg_3dviewer_main_url',
'basenamespace' => 'dfg_3dviewer_basenamespace',
'metadata-url' => 'dfg_3dviewer_metadata_url',
'json-export-base-url' => 'dfg_3dviewer_json_export_base_url',
'container' => 'dfg_3dviewer_container',
'entitybundle' => 'dfg_3dviewer_entitybundle',
'viewer-file-upload' => 'dfg_3dviewer_viewer_file_upload',
'viewer-file-name' => 'dfg_3dviewer_viewer_file_name',
'api-3d-file-field' => 'dfg_3dviewer_api_3d_file_field',
'image-generation' => 'dfg_3dviewer_image_generation',
'field-df' => 'dfg_3dviewer_field_df',
'export-viewer' => 'dfg_3dviewer_export_viewer',
'export-viewer-url' => 'dfg_3dviewer_export_viewer_url',
'lightweight' => 'dfg_3dviewer_lightweight',
'scale-container-x' => 'dfg_3dviewer_scale_container_x',
'scale-container-y' => 'dfg_3dviewer_scale_container_y',
'gallery-container' => 'dfg_3dviewer_gallery_container',
'gallery-image-class' => 'dfg_3dviewer_gallery_image_class',
'gallery-image-id' => 'dfg_3dviewer_gallery_image_id',
'base-module-path' => 'dfg_3dviewer_base_module_path',
'entity-id-uri' => 'dfg_3dviewer_entity_id_uri',
'view-entity-path' => 'dfg_3dviewer_view_entity_path',
'attribute-id' => 'dfg_3dviewer_attribute_id',
];
foreach ($option_map as $option => $key) {
if ($options[$option] !== NULL) {
$values[$key] = $this->normalizeOptionValue($key, $options[$option]);
}
}
$prepared = $this->configApplier->prepareValues($values);
$missing = $this->configApplier->validateRequired($prepared);
if ($missing !== []) {
throw new \InvalidArgumentException('Missing required settings: ' . implode(', ', $missing));
}
$this->configApplier->apply($values);
$this->logger()->success('DFG 3D Viewer settings saved.');
}
/**
* Converts CLI option values to the expected config types.
*/
protected function normalizeOptionValue(string $key, mixed $value): mixed {
if ($key === 'dfg_3dviewer_lightweight') {
return filter_var($value, FILTER_VALIDATE_BOOLEAN);
}
return (string) $value;
}
}

View file

@ -4,48 +4,25 @@ namespace Drupal\dfg_3dviewer\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\File\FileSystemInterface;
use Drupal\dfg_3dviewer\Service\Dfg3dViewerConfigApplier;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
*
*/
class DFG3dViewerConfigForm extends FormBase {
public function __construct(
protected Dfg3dViewerConfigApplier $configApplier,
) {}
/**
* Normalizes the viewer module path to a path-first value.
* {@inheritdoc}
*/
protected function normalizeBaseModulePath(string $value): string {
$value = trim($value);
if ($value === '') {
return $value;
}
if (preg_match('@^https?://@i', $value)) {
$parts = parse_url($value);
if (!empty($parts['path'])) {
$value = $parts['path'];
}
}
elseif (preg_match('@^/[^/]+\.[^/]+/.+@', $value)) {
$segments = array_values(array_filter(explode('/', $value), 'strlen'));
array_shift($segments);
$value = '/' . implode('/', $segments);
}
elseif (preg_match('@^[^/]+\.[^/]+/.+@', $value)) {
$segments = explode('/', $value);
array_shift($segments);
$value = '/' . implode('/', $segments);
}
$value = preg_replace('@/{2,}@', '/', $value);
if ($value !== '' && $value[0] !== '/') {
$value = '/' . $value;
}
return rtrim($value, '/');
public static function create(ContainerInterface $container) {
return new static(
$container->get('dfg_3dviewer.config_applier'),
);
}
/**
@ -61,7 +38,6 @@ class DFG3dViewerConfigForm extends FormBase {
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$settings = $this->configFactory()->getEditable('dfg_3dviewer.settings');
$default_config = \Drupal::config('dfg_3dviewer.settings');
$default_settings = [
'entity_bundle' => $default_config->get('dfg_3dviewer_entitybundle'),
@ -89,8 +65,6 @@ class DFG3dViewerConfigForm extends FormBase {
'export_viewer_url' => $default_config->get('dfg_3dviewer_export_viewer_url'),
];
$form['#dfg_3dviewer_settings'] = $settings;
$form['#attached']['library'][] = dfg_3dviewer_get_library();
dfg_3dviewer_attach_settings($form);
@ -299,20 +273,7 @@ class DFG3dViewerConfigForm extends FormBase {
public function validateForm(array &$form, FormStateInterface $form_state) {
if ($form_state->getValue('dfg_3dviewer_lightweight')) {
$optional_fields = [
'dfg_3dviewer_metadata_url',
'dfg_3dviewer_json_export_base_url',
'dfg_3dviewer_api_3d_file_field',
'dfg_3dviewer_image_generation',
'dfg_3dviewer_field_df',
'dfg_3dviewer_export_viewer',
'dfg_3dviewer_export_viewer_url',
'dfg_3dviewer_gallery_container',
'dfg_3dviewer_gallery_image_class',
'dfg_3dviewer_gallery_image_id',
];
foreach ($optional_fields as $field) {
foreach (Dfg3dViewerConfigApplier::LIGHTWEIGHT_OPTIONAL_KEYS as $field) {
$form_state->setValue($field, '');
}
}
@ -322,36 +283,7 @@ class DFG3dViewerConfigForm extends FormBase {
*
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$settings = $form['#dfg_3dviewer_settings'];
$new_vals = $form_state->getValues();
$normalized_base_module_path = $this->normalizeBaseModulePath((string) $new_vals['dfg_3dviewer_base_module_path']);
$settings->set('dfg_3dviewer_basenamespace', $new_vals['dfg_3dviewer_basenamespace']);
$settings->set('dfg_3dviewer_main_url', $new_vals['dfg_3dviewer_main_url']);
$settings->set('dfg_3dviewer_metadata_url', $new_vals['dfg_3dviewer_metadata_url']);
$settings->set('dfg_3dviewer_json_export_base_url', $new_vals['dfg_3dviewer_json_export_base_url']);
$settings->set('dfg_3dviewer_entitybundle', $new_vals['dfg_3dviewer_entitybundle']);
$settings->set('dfg_3dviewer_container', $new_vals['dfg_3dviewer_container']);
$settings->set('dfg_3dviewer_viewer_file_upload', $new_vals['dfg_3dviewer_viewer_file_upload']);
$settings->set('dfg_3dviewer_viewer_file_name', $new_vals['dfg_3dviewer_viewer_file_name']);
$settings->set('dfg_3dviewer_api_3d_file_field', $new_vals['dfg_3dviewer_api_3d_file_field']);
$settings->set('dfg_3dviewer_image_generation', $new_vals['dfg_3dviewer_image_generation']);
$settings->set('dfg_3dviewer_field_df', $new_vals['dfg_3dviewer_field_df']);
$settings->set('dfg_3dviewer_lightweight', $new_vals['dfg_3dviewer_lightweight']);
$settings->set('dfg_3dviewer_scale_container_x', $new_vals['dfg_3dviewer_scale_container_x']);
$settings->set('dfg_3dviewer_scale_container_y', $new_vals['dfg_3dviewer_scale_container_y']);
$settings->set('dfg_3dviewer_gallery_container', $new_vals['dfg_3dviewer_gallery_container']);
$settings->set('dfg_3dviewer_gallery_image_class', $new_vals['dfg_3dviewer_gallery_image_class']);
$settings->set('dfg_3dviewer_gallery_image_id', $new_vals['dfg_3dviewer_gallery_image_id']);
$settings->set('dfg_3dviewer_base_module_path', $normalized_base_module_path);
$settings->set('dfg_3dviewer_entity_id_uri', $new_vals['dfg_3dviewer_entity_id_uri']);
$settings->set('dfg_3dviewer_view_entity_path', $new_vals['dfg_3dviewer_view_entity_path']);
$settings->set('dfg_3dviewer_attribute_id', $new_vals['dfg_3dviewer_attribute_id']);
$settings->set('dfg_3dviewer_export_viewer', $new_vals['dfg_3dviewer_export_viewer']);
$settings->set('dfg_3dviewer_export_viewer_url', $new_vals['dfg_3dviewer_export_viewer_url']);
$settings->save();
$this->configApplier->apply($form_state->getValues());
$this->messenger()->addStatus($this->t('Changed DFG 3D Viewer settings successfully'));
$form_state->setRedirect('system.admin_config');

View file

@ -0,0 +1,198 @@
<?php
namespace Drupal\dfg_3dviewer\Service;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Config\ImmutableConfig;
/**
* Applies DFG 3D Viewer module settings to config storage.
*/
class Dfg3dViewerConfigApplier {
/**
* All config keys managed by the settings form and configure command.
*/
public const SETTING_KEYS = [
'dfg_3dviewer_main_url',
'dfg_3dviewer_basenamespace',
'dfg_3dviewer_metadata_url',
'dfg_3dviewer_json_export_base_url',
'dfg_3dviewer_container',
'dfg_3dviewer_entitybundle',
'dfg_3dviewer_viewer_file_upload',
'dfg_3dviewer_viewer_file_name',
'dfg_3dviewer_api_3d_file_field',
'dfg_3dviewer_image_generation',
'dfg_3dviewer_field_df',
'dfg_3dviewer_export_viewer',
'dfg_3dviewer_export_viewer_url',
'dfg_3dviewer_lightweight',
'dfg_3dviewer_scale_container_x',
'dfg_3dviewer_scale_container_y',
'dfg_3dviewer_gallery_container',
'dfg_3dviewer_gallery_image_class',
'dfg_3dviewer_gallery_image_id',
'dfg_3dviewer_base_module_path',
'dfg_3dviewer_entity_id_uri',
'dfg_3dviewer_view_entity_path',
'dfg_3dviewer_attribute_id',
];
/**
* Config keys that must be non-empty when saving settings.
*/
public const REQUIRED_KEYS = [
'dfg_3dviewer_main_url',
'dfg_3dviewer_container',
'dfg_3dviewer_entitybundle',
'dfg_3dviewer_viewer_file_upload',
'dfg_3dviewer_viewer_file_name',
'dfg_3dviewer_scale_container_x',
'dfg_3dviewer_scale_container_y',
'dfg_3dviewer_base_module_path',
'dfg_3dviewer_view_entity_path',
'dfg_3dviewer_attribute_id',
];
/**
* Keys cleared when lightweight mode is enabled.
*/
public const LIGHTWEIGHT_OPTIONAL_KEYS = [
'dfg_3dviewer_metadata_url',
'dfg_3dviewer_json_export_base_url',
'dfg_3dviewer_api_3d_file_field',
'dfg_3dviewer_image_generation',
'dfg_3dviewer_field_df',
'dfg_3dviewer_export_viewer',
'dfg_3dviewer_export_viewer_url',
'dfg_3dviewer_gallery_container',
'dfg_3dviewer_gallery_image_class',
'dfg_3dviewer_gallery_image_id',
];
public function __construct(
protected ConfigFactoryInterface $configFactory,
) {}
/**
* Returns the local development preset used by the configure command.
*/
public function getDevPreset(): array {
return [
'dfg_3dviewer_main_url' => 'http://dev.wisski.local/',
'dfg_3dviewer_basenamespace' => '',
'dfg_3dviewer_metadata_url' => '',
'dfg_3dviewer_json_export_base_url' => '',
'dfg_3dviewer_container' => 'DFG_3DViewer',
'dfg_3dviewer_entitybundle' => 'bd3d7baa74856d141bcff7b4193fa128',
'dfg_3dviewer_viewer_file_upload' => 'fbf95bddee5160d515b982b3fd2e05f7',
'dfg_3dviewer_viewer_file_name' => 'faa602a0be629324806aef22892cdbe5',
'dfg_3dviewer_api_3d_file_field' => '',
'dfg_3dviewer_image_generation' => '',
'dfg_3dviewer_field_df' => '',
'dfg_3dviewer_export_viewer' => '',
'dfg_3dviewer_export_viewer_url' => '',
'dfg_3dviewer_lightweight' => TRUE,
'dfg_3dviewer_scale_container_x' => '1',
'dfg_3dviewer_scale_container_y' => '1.4',
'dfg_3dviewer_gallery_container' => '',
'dfg_3dviewer_gallery_image_class' => '',
'dfg_3dviewer_gallery_image_id' => '',
'dfg_3dviewer_base_module_path' => '/libraries/dfg-3dviewer/assets',
'dfg_3dviewer_entity_id_uri' => '/wisski/navigate/(.*)/view',
'dfg_3dviewer_view_entity_path' => '/wisski/navigate/',
'dfg_3dviewer_attribute_id' => 'wisski_id',
];
}
/**
* Validates required settings and returns missing keys.
*
* @return string[]
* Missing required config keys.
*/
public function validateRequired(array $values): array {
$missing = [];
foreach (self::REQUIRED_KEYS as $key) {
if (!array_key_exists($key, $values) || trim((string) $values[$key]) === '') {
$missing[] = $key;
}
}
return $missing;
}
/**
* Saves settings to config, mirroring the admin form submit behavior.
*/
public function apply(array $values): ImmutableConfig {
$values = array_intersect_key($values, array_flip(self::SETTING_KEYS));
$values = $this->prepareValues($values);
$missing = $this->validateRequired($values);
if ($missing !== []) {
throw new \InvalidArgumentException('Missing required settings: ' . implode(', ', $missing));
}
$settings = $this->configFactory->getEditable('dfg_3dviewer.settings');
foreach ($values as $key => $value) {
$settings->set($key, $value);
}
$settings->save();
return $settings;
}
/**
* Normalizes values before persistence.
*/
public function prepareValues(array $values): array {
if (!empty($values['dfg_3dviewer_lightweight'])) {
foreach (self::LIGHTWEIGHT_OPTIONAL_KEYS as $key) {
$values[$key] = '';
}
}
if (isset($values['dfg_3dviewer_base_module_path'])) {
$values['dfg_3dviewer_base_module_path'] = $this->normalizeBaseModulePath((string) $values['dfg_3dviewer_base_module_path']);
}
return $values;
}
/**
* Normalizes the viewer module path to a path-first value.
*/
public function normalizeBaseModulePath(string $value): string {
$value = trim($value);
if ($value === '') {
return $value;
}
if (preg_match('@^https?://@i', $value)) {
$parts = parse_url($value);
if (!empty($parts['path'])) {
$value = $parts['path'];
}
}
elseif (preg_match('@^/[^/]+\.[^/]+/.+@', $value)) {
$segments = array_values(array_filter(explode('/', $value), 'strlen'));
array_shift($segments);
$value = '/' . implode('/', $segments);
}
elseif (preg_match('@^[^/]+\.[^/]+/.+@', $value)) {
$segments = explode('/', $value);
array_shift($segments);
$value = '/' . implode('/', $segments);
}
$value = preg_replace('@/{2,}@', '/', $value);
if ($value !== '' && $value[0] !== '/') {
$value = '/' . $value;
}
return rtrim($value, '/');
}
}