diff --git a/README.md b/README.md index 772e558..f359b4c 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,13 @@ drush en dfg_3dviewer -y drush cr ``` -Configure at `/admin/config/dfg_3dviewer`. Settings are passed to the browser as `drupalSettings.dfg_3dviewer`. +Configure at `/admin/config/dfg_3dviewer`, or apply the bundled local development preset: + +```bash +drush dfg-3dviewer:configure +``` + +Override individual values with options, for example `--main-url=https://example.test/`. Settings are passed to the browser as `drupalSettings.dfg_3dviewer`. ## Package contents diff --git a/dfg_3dviewer.services.yml b/dfg_3dviewer.services.yml index 07c0c24..7a996e4 100644 --- a/dfg_3dviewer.services.yml +++ b/dfg_3dviewer.services.yml @@ -1,4 +1,8 @@ services: + dfg_3dviewer.config_applier: + class: Drupal\dfg_3dviewer\Service\Dfg3dViewerConfigApplier + arguments: + - '@config.factory' dfg_3dviewer.model_format_manager: class: Drupal\dfg_3dviewer\Service\ModelFormatManager dfg_3dviewer.convert_process: diff --git a/drush.services.yml b/drush.services.yml new file mode 100644 index 0000000..d5e78f9 --- /dev/null +++ b/drush.services.yml @@ -0,0 +1,7 @@ +services: + dfg_3dviewer.commands: + class: Drupal\dfg_3dviewer\Drush\Commands\Dfg3dViewerCommands + arguments: + - '@dfg_3dviewer.config_applier' + tags: + - { name: drush.command } diff --git a/scripts/pack-drupal-module.js b/scripts/pack-drupal-module.js index 5dedf75..fd37734 100644 --- a/scripts/pack-drupal-module.js +++ b/scripts/pack-drupal-module.js @@ -18,6 +18,7 @@ const moduleFiles = [ 'dfg_3dviewer.routing.yml', 'dfg_3dviewer.services.yml', 'dfg_3dviewer.translation.yml', + 'drush.services.yml', 'README.md', ]; diff --git a/src/Drush/Commands/Dfg3dViewerCommands.php b/src/Drush/Commands/Dfg3dViewerCommands.php new file mode 100644 index 0000000..2447480 --- /dev/null +++ b/src/Drush/Commands/Dfg3dViewerCommands.php @@ -0,0 +1,130 @@ + 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; + } + +} diff --git a/src/Form/DFG3dViewerConfigForm.php b/src/Form/DFG3dViewerConfigForm.php index 2327c19..1fa7ad7 100644 --- a/src/Form/DFG3dViewerConfigForm.php +++ b/src/Form/DFG3dViewerConfigForm.php @@ -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'); diff --git a/src/Service/Dfg3dViewerConfigApplier.php b/src/Service/Dfg3dViewerConfigApplier.php new file mode 100644 index 0000000..e88ec2c --- /dev/null +++ b/src/Service/Dfg3dViewerConfigApplier.php @@ -0,0 +1,198 @@ + '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, '/'); + } + +}