From 917cdfb7b0cc12b64657fa908f91d635c50acf51 Mon Sep 17 00:00:00 2001 From: Alex Tkachev Date: Mon, 20 Nov 2023 19:36:51 +0400 Subject: [PATCH] chore: remove webform_jsonschema It will live on drupal.org --- .../drupal/webform_jsonschema/.gitignore | 1 - .../drupal/webform_jsonschema/CHANGELOG.md | 71 --- .../drupal/webform_jsonschema/README.md | 60 --- .../drupal/webform_jsonschema/composer.json | 25 -- .../drupal/webform_jsonschema/package.json | 19 - .../webform_jsonschema/src/Conditions.php | 348 -------------- .../src/Encoder/JsonEncoder.php | 31 -- .../src/JsonSchemaElementInterface.php | 52 --- .../resource/WebformJsonSchemaResource.php | 123 ----- .../src/Routing/EventSubscriber.php | 29 -- .../webform_jsonschema/src/Submission.php | 257 ----------- .../webform_jsonschema/src/Transformer.php | 423 ------------------ .../webform_jsonschema/src/WebformItem.php | 31 -- .../webform_jsonschema.api.php | 43 -- .../webform_jsonschema.info.yml | 8 - .../webform_jsonschema.services.yml | 21 - pnpm-lock.yaml | 6 - 17 files changed, 1548 deletions(-) delete mode 100644 packages/composer/drupal/webform_jsonschema/.gitignore delete mode 100644 packages/composer/drupal/webform_jsonschema/CHANGELOG.md delete mode 100644 packages/composer/drupal/webform_jsonschema/README.md delete mode 100644 packages/composer/drupal/webform_jsonschema/composer.json delete mode 100644 packages/composer/drupal/webform_jsonschema/package.json delete mode 100644 packages/composer/drupal/webform_jsonschema/src/Conditions.php delete mode 100644 packages/composer/drupal/webform_jsonschema/src/Encoder/JsonEncoder.php delete mode 100644 packages/composer/drupal/webform_jsonschema/src/JsonSchemaElementInterface.php delete mode 100644 packages/composer/drupal/webform_jsonschema/src/Plugin/rest/resource/WebformJsonSchemaResource.php delete mode 100644 packages/composer/drupal/webform_jsonschema/src/Routing/EventSubscriber.php delete mode 100644 packages/composer/drupal/webform_jsonschema/src/Submission.php delete mode 100644 packages/composer/drupal/webform_jsonschema/src/Transformer.php delete mode 100644 packages/composer/drupal/webform_jsonschema/src/WebformItem.php delete mode 100644 packages/composer/drupal/webform_jsonschema/webform_jsonschema.api.php delete mode 100644 packages/composer/drupal/webform_jsonschema/webform_jsonschema.info.yml delete mode 100644 packages/composer/drupal/webform_jsonschema/webform_jsonschema.services.yml diff --git a/packages/composer/drupal/webform_jsonschema/.gitignore b/packages/composer/drupal/webform_jsonschema/.gitignore deleted file mode 100644 index a09c56df5..000000000 --- a/packages/composer/drupal/webform_jsonschema/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/.idea diff --git a/packages/composer/drupal/webform_jsonschema/CHANGELOG.md b/packages/composer/drupal/webform_jsonschema/CHANGELOG.md deleted file mode 100644 index 98498c60d..000000000 --- a/packages/composer/drupal/webform_jsonschema/CHANGELOG.md +++ /dev/null @@ -1,71 +0,0 @@ -# Change Log - -All notable changes to this project will be documented in this file. See -[Conventional Commits](https://conventionalcommits.org) for commit guidelines. - -## [2.0.8](https://github.com/AmazeeLabs/silverback-mono/compare/@-drupal/webform_jsonschema@2.0.7...@-drupal/webform_jsonschema@2.0.8) (2023-08-24) - -**Note:** Version bump only for package @-drupal/webform_jsonschema - -# Change Log - -All notable changes to this project will be documented in this file. See -[Conventional Commits](https://conventionalcommits.org) for commit guidelines. - -## [2.0.7](https://github.com/AmazeeLabs/silverback-mono/compare/@-drupal/webform_jsonschema@2.0.6...@-drupal/webform_jsonschema@2.0.7) (2023-03-23) - -**Note:** Version bump only for package @-drupal/webform_jsonschema - -# Change Log - -All notable changes to this project will be documented in this file. See -[Conventional Commits](https://conventionalcommits.org) for commit guidelines. - -## [2.0.6](https://github.com/AmazeeLabs/silverback-mono/compare/@-drupal/webform_jsonschema@2.0.5...@-drupal/webform_jsonschema@2.0.6) (2023-01-30) - -**Note:** Version bump only for package @-drupal/webform_jsonschema - -# Change Log - -All notable changes to this project will be documented in this file. See -[Conventional Commits](https://conventionalcommits.org) for commit guidelines. - -## [2.0.5](https://github.com/AmazeeLabs/silverback-mono/compare/@-drupal/webform_jsonschema@2.0.4...@-drupal/webform_jsonschema@2.0.5) (2023-01-05) - -**Note:** Version bump only for package @-drupal/webform_jsonschema - -## [2.0.4](https://github.com/AmazeeLabs/silverback-mono/compare/@-drupal/webform_jsonschema@2.0.3...@-drupal/webform_jsonschema@2.0.4) (2021-12-27) - -**Note:** Version bump only for package @-drupal/webform_jsonschema - -## [2.0.3](https://github.com/AmazeeLabs/silverback-mono/compare/@-drupal/webform_jsonschema@2.0.2...@-drupal/webform_jsonschema@2.0.3) (2021-10-11) - -**Note:** Version bump only for package @-drupal/webform_jsonschema - -## [2.0.2](https://github.com/AmazeeLabs/silverback-mono/compare/@-drupal/webform_jsonschema@2.0.1...@-drupal/webform_jsonschema@2.0.2) (2021-08-17) - -**Note:** Version bump only for package @-drupal/webform_jsonschema - -## [2.0.1](https://github.com/AmazeeLabs/silverback-mono/compare/@-drupal/webform_jsonschema@1.0.6...@-drupal/webform_jsonschema@2.0.1) (2021-08-10) - -**Note:** Version bump only for package @-drupal/webform_jsonschema - -## [1.0.6](https://github.com/AmazeeLabs/silverback-mono/compare/@-drupal/webform_jsonschema@1.0.5...@-drupal/webform_jsonschema@1.0.6) (2021-08-09) - -**Note:** Version bump only for package @-drupal/webform_jsonschema - -## [1.0.5](https://github.com/AmazeeLabs/silverback-mono/compare/@-drupal/webform_jsonschema@1.0.4...@-drupal/webform_jsonschema@1.0.5) (2021-03-03) - -**Note:** Version bump only for package @-drupal/webform_jsonschema - -## [1.0.4](https://github.com/AmazeeLabs/silverback-mono/compare/@-drupal/webform_jsonschema@1.0.3...@-drupal/webform_jsonschema@1.0.4) (2021-02-11) - -**Note:** Version bump only for package @-drupal/webform_jsonschema - -## [1.0.3](https://github.com/AmazeeLabs/silverback-mono/compare/@-drupal/webform_jsonschema@1.0.1...@-drupal/webform_jsonschema@1.0.3) (2021-02-11) - -**Note:** Version bump only for package @-drupal/webform_jsonschema - -## 1.0.1 (2021-02-11) - -**Note:** Version bump only for package @-drupal/webform_jsonschema diff --git a/packages/composer/drupal/webform_jsonschema/README.md b/packages/composer/drupal/webform_jsonschema/README.md deleted file mode 100644 index 26cd20f0d..000000000 --- a/packages/composer/drupal/webform_jsonschema/README.md +++ /dev/null @@ -1,60 +0,0 @@ -# Webform JSON Schema - -☝️ Development happens in https://github.com/AmazeeLabs/silverback-mono/tree/development/packages/composer/drupal/webform_jsonschema - -A Drupal module that provides /webform_jsonschema/{webform_id} REST endpoint: - -- GET: returns JSON Schema + UI Schema + Form Data to be used with https://github.com/mozilla-services/react-jsonschema-form -- POST: handles the webform submission - -The REST module is not listed as a dependency. It needs to be enabled manually. - -## Known issues - -- Drupal messages are shown on drupal frontend on validation errors. -- Multivalue elements should use the following setup: - `Advanced => Multiple settings => Number of empty items => 0` - The default value for this option is `1` and it triggers a validation error for multivalue components because it adds an empty value on submission. - Maybe a warning should be added to the webform build form for this case. -- Bugs. - -## Tricks - -It's easy to override uiSchema: - -- edit a webform component -- go to Advanced tab -- add something like this to the "Custom properties" field: - -``` -webform_jsonschema: - uiSchema: - 'ui:widget': carSelector -``` - -- result: the `ui:widget` in the uiSchema will be overridden with `carSelector` for the component - -## Field conditions (dependencies) - -The module supports field conditions. But only a limited portion of them due to limitations of the JSON Schema: https://json-schema.org/understanding-json-schema/reference/object.html#dependencies -However `react-jsonschema-form` uses a bit extended dependencies allowing to depend not only on the field presence, but also on the field value: https://react-jsonschema-form.readthedocs.io/en/latest/dependencies/#dynamic - -Limitations, in comparison with the default Webform conditions (Drupal states): - -- only `Visible` and `Required` states are available -- one state can have only one trigger -- only `Filled` and `Value is` triggers are available -- `Value is` trigger works correctly only with enumerable fields -- the dependency field should be on the same level with the target field - -The webform_jsonschema module provides a way to apply the above limitations to the Webform UI. But the module does not apply them automatically, because it does not know which of the forms will be used with `react-jsonschema-form`. Here is an example of how to apply them: - -``` -/** - * Implements hook_form_FORM_ID_alter(). - */ -function MY_MOFULE_form_webform_ui_element_form_alter(array &$form, FormStateInterface $form_state) { - // Streamline Webform conditions UI on all webforms / webform elements. - Conditions::alterWebformUiElementForm($form, $form_state); -} -``` diff --git a/packages/composer/drupal/webform_jsonschema/composer.json b/packages/composer/drupal/webform_jsonschema/composer.json deleted file mode 100644 index 0e2346b2a..000000000 --- a/packages/composer/drupal/webform_jsonschema/composer.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "drupal/webform_jsonschema", - "type": "drupal-module", - "description": "Expose webforms as JSON Schema, UI Schema, and Form Data. Make webforms work with react-jsonschema-form.", - "keywords": [ - "Drupal" - ], - "license": "GPL-2.0+", - "homepage": "https://www.drupal.org/project/webform_jsonschema", - "minimum-stability": "dev", - "support": { - "issues": "https://www.drupal.org/project/issues/webform_jsonschema", - "source": "http://cgit.drupalcode.org/webform_jsonschema" - }, - "repositories": [ - { - "type": "composer", - "url": "https://packages.drupal.org/8" - } - ], - "require": { - "drupal/webform": "^5 || ^6" - }, - "version": "2.0.8" -} diff --git a/packages/composer/drupal/webform_jsonschema/package.json b/packages/composer/drupal/webform_jsonschema/package.json deleted file mode 100644 index 20e71e976..000000000 --- a/packages/composer/drupal/webform_jsonschema/package.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "@-drupal/webform_jsonschema", - "version": "2.0.8", - "description": "Makes it possible to use webforms with react-jsonschema-form NPM package.", - "publishConfig": { - "access": "public", - "registry": "http://127.0.0.1:4873", - "repository": "git@git.drupal.org:project/webform_jsonschema.git", - "branch": "2.x" - }, - "author": "Collective Mind of Amazee Labs", - "license": "ISC", - "devDependencies": { - "@amazeelabs/sync-composer-version": "workspace:*" - }, - "scripts": { - "version": "sync-composer-version" - } -} diff --git a/packages/composer/drupal/webform_jsonschema/src/Conditions.php b/packages/composer/drupal/webform_jsonschema/src/Conditions.php deleted file mode 100644 index e260268e4..000000000 --- a/packages/composer/drupal/webform_jsonschema/src/Conditions.php +++ /dev/null @@ -1,348 +0,0 @@ - $property) { - $item = $items[$key]; - if (!empty($item->element['#states']['required'])) { - $triggerArray = reset($item->element['#states']['required']); - $selector = key($item->element['#states']['required']); - $dependencyKey = self::getDependencyKey($selector); - $dependencySchema =& self::prepareDependencySchema($schema, $dependencyKey, $triggerArray); - // Move the "required" mark for this item from the top of the schema to - // the dependency field. - self::removeRequiredMark($schema, $key); - $dependencySchema['required'][] = $key; - } - if (!empty($item->element['#states']['visible'])) { - $triggerArray = reset($item->element['#states']['visible']); - $selector = key($item->element['#states']['visible']); - $dependencyKey = self::getDependencyKey($selector); - $dependencySchema =& self::prepareDependencySchema($schema, $dependencyKey, $triggerArray); - // Move the field definition to the dependency field. - $dependencySchema['properties'][$key] = $property; - unset($schema['properties'][$key]); - // Move the "required" mark for this item (if any) from the top of the - // schema to the dependency field. - if (self::removeRequiredMark($schema, $key)) { - $dependencySchema['required'][] = $key; - } - } - } - } - - /** - * Returns element key used in a states selector. - * - * @param string $selector - * - * @return string - * - * @throws \Exception - */ - protected static function getDependencyKey($selector) { - if (!preg_match('/\[name="([^"]+)"\]/', $selector, $matches)) { - throw new \Exception('Cannot parse states selector.'); - } - return $matches[1]; - } - - /** - * Removes the required mark from the JSON Schema. - * - * @param array $schema - * @param string $targetKey - * - * @return bool - * TRUE if the required mark was found and removed. FALSE otherwise. - */ - protected static function removeRequiredMark(&$schema, $targetKey) { - if (!empty($schema['required'])) { - $arrayKey = array_search($targetKey, $schema['required'], TRUE); - if ($arrayKey !== FALSE) { - unset($schema['required'][$arrayKey]); - if (empty($schema['required'])) { - unset($schema['required']); - } - else { - $schema['required'] = array_values($schema['required']); - } - return TRUE; - } - } - return FALSE; - } - - /** - * Prepares the dependency schema. - * - * @see https://react-jsonschema-form.readthedocs.io/en/latest/dependencies/ - * - * "Property dependencies" are not used, because the same result can be - * achieved with "Schema dependencies". - * - * @param array $schema - * @param string $dependencyKey - * @param array $triggerArray - * Only two types are supported: - * - ['filled' => TRUE] - * - ['value' => {mixed}] - * - * @return array - * A reference to a part of dependency schema which is already inserted into - * the given JSON Schema. - */ - protected static function &prepareDependencySchema(&$schema, $dependencyKey, $triggerArray) { - $value = reset($triggerArray); - $trigger = key($triggerArray); - - if (($schema['properties'][$dependencyKey]['type'] ?? NULL) === 'boolean' && $trigger === 'filled') { - // The issue with the checkboxes is: - // - JSON Schema dependencies react to the presence of the field value. - // - When a checkbox appears on a form, it's value is undefined. - // - When a checkbox is checked, it's value in true. - // - When checkbox is unchecked, it's value is false, but it is a defined - // value, so dependencies will trigger. - // Therefore, we use the dynamic dependencies for boolean values. - $trigger = 'value'; - $value = TRUE; - } - - // For the "Filled" trigger we can use conditional dependencies: - // https://react-jsonschema-form.readthedocs.io/en/latest/dependencies/#conditional - if ($trigger === 'filled') { - if (!isset($schema['dependencies'][$dependencyKey])) { - $schema['dependencies'][$dependencyKey] = []; - } - return $schema['dependencies'][$dependencyKey]; - } - - // For the "Value is" trigger we use dynamic dependencies: - // https://react-jsonschema-form.readthedocs.io/en/latest/dependencies/#dynamic - if ($trigger === 'value') { - if (!isset($schema['dependencies'][$dependencyKey]['oneOf'])) { - $schema['dependencies'][$dependencyKey]['oneOf'] = []; - } - - // It is important to provide all allowed values in the dependency. - // Otherwise the form can error in unpredictable ways. - $possibleValues = NULL; - if (isset($schema['properties'][$dependencyKey]['anyOf'][0]['enum'][0])) { - // Enumerable field. - $possibleValues = array_map(function ($definition) { - return $definition['enum'][0]; - }, $schema['properties'][$dependencyKey]['anyOf']); - } - if (($schema['properties'][$dependencyKey]['type'] ?? NULL) === 'boolean') { - // Boolean field. - $possibleValues = [TRUE, FALSE]; - } - if (!$possibleValues) { - \Drupal::logger('webform_jsonschema')->warning('Cannot detect possible values. Data:
@data
', [ - '@data' => Variable::export([ - '$schema' => $schema, - '$dependencyKey' => $dependencyKey, - '$triggerArray' => $triggerArray, - ]), - ]); - $possibleValues = [$value]; - } - - $existing = array_map(function($dependency) use ($dependencyKey) { - return $dependency['properties'][$dependencyKey]['enum'][0]; - }, $schema['dependencies'][$dependencyKey]['oneOf']); - $missing = array_diff($possibleValues, $existing); - foreach ($missing as $possibleValue) { - $schema['dependencies'][$dependencyKey]['oneOf'][]['properties'][$dependencyKey] = [ - 'enum' => [$possibleValue], - ]; - } - foreach ($schema['dependencies'][$dependencyKey]['oneOf'] as $key => $dependency) { - if ($dependency['properties'][$dependencyKey]['enum'][0] === $value) { - return $schema['dependencies'][$dependencyKey]['oneOf'][$key]; - } - } - } - - \Drupal::logger('webform_jsonschema')->warning('Cannot prepare dependency schema. Data:
@data
', [ - '@data' => Variable::export([ - '$schema' => $schema, - '$dependencyKey' => $dependencyKey, - '$triggerArray' => $triggerArray, - ]), - ]); - $nothing = []; - return $nothing; - } - - /** - * Alters the webform_ui_element_form form. - * - * Simplifies the Webform Conditions UI to allow only those use cases which - * could be used with react-jsonschema-form library. - * - * @param array $form - * @param \Drupal\Core\Form\FormStateInterface $form_state - */ - public static function alterWebformUiElementForm(array &$form, FormStateInterface $form_state) { - if (empty($form['properties']['conditional_logic'])) { - return; - } - $form['properties']['conditional_logic']['states_clear']['#access'] = FALSE; - $form['properties']['conditional_logic']['value_warning'] = [ - '#theme' => 'status_messages', - '#message_list' => [ - 'warning' => [ - t('Please note that "@value" trigger works with enumerable field only. E.g. with selects, radio buttons, etc.', [ - '@value' => t('Value is'), - ]), - ], - ], - ]; - $form['properties']['conditional_logic']['states']['#after_build'][] = [ - self::class, - 'webformElementStatesAfterBuild', - ]; - } - - /** - * After-build callback for the webform_element_states form element. - * - * @param array $element - * @param \Drupal\Core\Form\FormStateInterface $form_state - * - * @return array - */ - public static function webformElementStatesAfterBuild($element, FormStateInterface $form_state) { - /** @var \Drupal\webform_ui\Form\WebformUiElementFormInterface $formObject */ - $formObject = $form_state->getFormObject(); - /** @var \Drupal\webform\Entity\Webform $webform */ - $webform = $formObject->getWebform(); - /** @var \Drupal\webform_jsonschema\Transformer $transformer */ - $transformer = \Drupal::service('webform_jsonschema.transformer'); - $webformItems = $transformer->toItems($webform); - - // Get webform element keys from the same level as the currently processed - // webform element. - $parentKey = $form_state->getCompleteForm()['parent_key']['#value'] ?? NULL; - $elementKey = $form_state->getBuildInfo()['args'][1] ?? NULL; - if ($parentKey === NULL && $elementKey === NULL) { - // We add a new element on the root level. - $elementKeysFromSameLevel = array_keys($webformItems); - } - elseif ($parentKey !== NULL) { - // We add a new element. - $getByParent = function($webformItems, $parentKey) use (&$getByParent) { - foreach ($webformItems as $key => $webformItem) { - if ($key === $parentKey) { - return array_keys($webformItem->children); - } - $result = $getByParent($webformItem->children, $parentKey); - if ($result) { - return $result; - } - } - return []; - }; - $elementKeysFromSameLevel = $getByParent($webformItems, $parentKey); - } - elseif ($elementKey !== NULL) { - // We edit an existing element. - $getByTarget = function($webformItems, $targetKey) use (&$getByTarget) { - if (isset($webformItems[$targetKey])) { - return array_keys($webformItems); - } - foreach ($webformItems as $key => $webformItem) { - $result = $getByTarget($webformItem->children, $targetKey); - if ($result) { - return $result; - } - } - return []; - }; - $elementKeysFromSameLevel = $getByTarget($webformItems, $elementKey); - // Exclude the element itself. - $elementKeysFromSameLevel = array_diff($elementKeysFromSameLevel, [$elementKey]); - } - else { - // Should never happen. - \Drupal::logger('webform_jsonschema')->warning('Cannot detect elements from the same level. Data:
@data
', [ - '@data' => Variable::export([ - '$elementKey' => $elementKey, - '$parentKey' => $parentKey, - '$webform->id()' => $webform->id(), - ]), - ]); - $elementKeysFromSameLevel = []; - } - - // Adjust the form. - foreach (Element::children($element['states']) as $key) { - $row =& $element['states'][$key]; - $isFirstRow = isset($row['state']) && !isset($row['selector']); - $isSecondRow = !$isFirstRow; - if ($isFirstRow) { - // Limit states to Visible and Required. - $row['state']['#options'] = [ - '' => t('- Select- '), - 'visible' => t('Visible'), - 'required' => t('Required'), - ]; - // Allow only one condition per state. - $row['operator'] = [ - '#markup' => t('if the following is met:'), - // Fix a PHP notice which happens because "operator" is supposed to be - // a form element. - '#parents' => [], - ]; - $row['operations']['add']['#access'] = FALSE; - } - if ($isSecondRow) { - // Allow only one condition per state. - $row['operations']['add']['#access'] = FALSE; - $row['operations']['remove']['#access'] = FALSE; - // Dependency fields should be on the same level. - foreach (array_keys($row['selector']['#options']) as $selector) { - if (!$selector) { - continue; - } - $dependencyKey = self::getDependencyKey($selector); - if (!in_array($dependencyKey, $elementKeysFromSameLevel, TRUE)) { - unset($row['selector']['#options'][$selector]); - } - } - // Limit triggers to "Filled" and "Value is". - $row['condition']['trigger']['#options'] = array_intersect_key( - $row['condition']['trigger']['#options'], - array_fill_keys(['', 'filled', 'value'], NULL) - ); - } - unset($row); - } - $element['actions']['source']['#access'] = FALSE; - - return $element; - } - -} diff --git a/packages/composer/drupal/webform_jsonschema/src/Encoder/JsonEncoder.php b/packages/composer/drupal/webform_jsonschema/src/Encoder/JsonEncoder.php deleted file mode 100644 index 4cf5364ab..000000000 --- a/packages/composer/drupal/webform_jsonschema/src/Encoder/JsonEncoder.php +++ /dev/null @@ -1,31 +0,0 @@ -transformer = $transformer; - $this->submission = $submission; - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { - return new static( - $configuration, - $plugin_id, - $plugin_definition, - $container->getParameter('serializer.formats'), - $container->get('logger.factory')->get('rest'), - $container->get('webform_jsonschema.transformer'), - $container->get('webform_jsonschema.submission') - ); - } - - /** - * Returns JSON Schema + UI Schema + Form Data for a webform. - * - * @param string $webform_id - * Webform ID. - * - * @return \Drupal\rest\ResourceResponseInterface - */ - public function get($webform_id) { - // String translations, Drupal entity API calls and a lot of other things - // can emit cache metadata in the current render context. - // Prevent the leaked cache metadata exception by wrapping the execution in - // its own render context. - /** @var \Drupal\Core\Render\RendererInterface $renderer */ - $renderer = \Drupal::service('renderer'); - return $renderer->executeInRenderContext(new RenderContext(), function () use ($webform_id) { - if ($webform = Webform::load($webform_id)) { - /** @var \Drupal\Core\Access\CsrfTokenGenerator $token_generator */ - $token_generator = \Drupal::service('csrf_token'); - $response = new ResourceResponse([ - 'schema' => $this->transformer->toJsonSchema($webform), - 'ui' => $this->transformer->toUiSchema($webform), - /** - * The buttons cannot be part of the schema. They need to be added as - * children of the form element, so we put them under a separate key. - * The items should be mapped in a way similar to this: - * - * - *
- * {buttons.map(({ value }) => } - *
- *
- */ - 'buttons' => $this->transformer->toButtons($webform), - 'data' => [], - 'csrfToken' => $token_generator->get(\Drupal\Core\Access\CsrfRequestHeaderAccessCheck::TOKEN_KEY), - ]); - $response->addCacheableDependency($webform); - return $response; - } - throw new NotFoundHttpException(t('Cannot load webform.')); - }); - } - - /** - * Handles JSON submission. - */ - public function post($webform_id, $data) { - $result = $this->submission->submit($webform_id, $data); - return new \Drupal\rest\ModifiedResourceResponse($result, $result['saved'] ? 200 : 400); - } - -} diff --git a/packages/composer/drupal/webform_jsonschema/src/Routing/EventSubscriber.php b/packages/composer/drupal/webform_jsonschema/src/Routing/EventSubscriber.php deleted file mode 100644 index 102b54ec0..000000000 --- a/packages/composer/drupal/webform_jsonschema/src/Routing/EventSubscriber.php +++ /dev/null @@ -1,29 +0,0 @@ - $route) { - if ( - $route->getPath() === '/webform_jsonschema/{webform_id}' && - ($requirements = $route->getRequirements()) && - isset($requirements['_content_type_format']) - ) { - // Set content type to JSON on routes to make Drupal happy. - $route->setRequirement('_content_type_format', 'json'); - } - } - } - -} diff --git a/packages/composer/drupal/webform_jsonschema/src/Submission.php b/packages/composer/drupal/webform_jsonschema/src/Submission.php deleted file mode 100644 index 03f449547..000000000 --- a/packages/composer/drupal/webform_jsonschema/src/Submission.php +++ /dev/null @@ -1,257 +0,0 @@ -transformer = $transformer; - } - - /** - * Handles webform submission. - * - * @param string $webform_id - * Webform ID. - * @param array $data - * JSON data. - * - * @return array - * In case of a successful submission: - * [ - * 'saved' => TRUE, - * 'userMessages' => [{string}, ...], - * ] - * In case of a failed submission: - * [ - * 'saved' => FALSE, - * 'errors' => [ - * [ - * 'message' => {string}, - * 'path' => {data-path-as-array}, - * 'userMessage' => {string}, - * ], - * ... - * ], - * ] - */ - public function submit($webform_id, $data) { - $error_message = NULL; - try { - $webform = Webform::load($webform_id); - if (!$webform) { - return [ - 'saved' => FALSE, - 'errors' => [ - [ - 'message' => "Cannot load a webform with \"{$webform_id}\" ID.", - 'path' => [], - 'userMessage' => (string) t('Cannot find webform to submit.'), - ], - ], - ]; - } - $schema = $this->transformer->toJsonSchema($webform); - $data = self::flattenData($data, $schema); - $path_mapping = self::getPathMapping($schema); - $result = WebformSubmissionForm::submitFormValues([ - 'webform_id' => $webform_id, - 'data' => $data, - ]); - if (is_array($result)) { - $errors = []; - foreach ($result as $form_path => $error) { - $mapped_path = isset($path_mapping[$form_path]) - ? $path_mapping[$form_path] - : $form_path; - $path = self::fromFormPath($mapped_path, $schema); - $errors[] = [ - 'message' => "Webform error. Form path: \"{$form_path}\". Mapped path: \"{$mapped_path}\".", - 'path' => $path, - 'userMessage' => strip_tags((string) $error), - ]; - } - return [ - 'saved' => FALSE, - 'errors' => $errors, - ]; - } - elseif ($result instanceof WebformSubmissionInterface) { - /** @var \Drupal\webform\WebformMessageManagerInterface $message_manager */ - $message_manager = \Drupal::service('webform.message_manager'); - $message_manager->setWebformSubmission($result); - $message_array = $message_manager->build(WebformMessageManagerInterface::SUBMISSION_CONFIRMATION_MESSAGE); - $message = trim(strip_tags(\Drupal::service('renderer') - ->render($message_array))); - return [ - 'saved' => TRUE, - 'userMessages' => [ - $message, - ], - ]; - } - } - catch (\Exception $e) { - watchdog_exception('webform_jsonschema', $e); - $error_message = $e->getMessage(); - } - return [ - 'saved' => FALSE, - 'errors' => [ - [ - 'message' => $error_message ? $error_message : 'There was an error submitting webform.', - 'path' => [], - 'userMessage' => (string) t('There was an error submitting webform.'), - ], - ], - ]; - } - - /** - * Transforms Drupal's form path to JSON data path. - * - * It is not necessary that "foo][bar][baz" will become ["foo", "bar", "baz"]. - * Sometimes Drupal's form path can contain items that do not present in the - * JSON schema/data. Real world example: - * - form path: "multivalue_text][items][0][item]" - * - JSON path: ["multivalue_text", "0"] - * - * @param string $form_path - * @param array $schema - * - * @return array - */ - protected static function fromFormPath($form_path, $schema) { - $result = []; - foreach (explode('][', $form_path) as $key) { - if (isset($schema['properties'][$key])) { - $schema = $schema['properties'][$key]; - $result[] = $key; - continue; - } - elseif ($schema['type'] === 'array' && is_numeric($key)) { - $schema = $schema['items']; - $result[] = $key; - } - } - return $result; - } - - /** - * Returns path mapping for Webform error messages. - * - * @see \Drupal\webform_jsonschema\Submission::flattenData() to get the logic. - * - * @param array $schema - * @param array $json_path - * @param array $webform_path - * @param array $result - * - * @return array - * The mapping array. Keys are form paths, values are data paths. - */ - protected static function getPathMapping($schema, $json_path = [], $webform_path = [], &$result = NULL) { - if ($result === NULL) { - $result = []; - } - if (empty($schema['properties'])) { - return $result; - } - foreach ($schema['properties'] as $key => $property) { - if (!empty($schema['properties'][$key]['is_wrapper_element'])) { - array_push($json_path, $key); - if (empty($schema['properties'][$key]['is_composite_element'])) { - self::getPathMapping($schema['properties'][$key], $json_path, $webform_path, $result); - } - else { - array_push($webform_path, $key); - self::getPathMapping($schema['properties'][$key], $json_path, $webform_path, $result); - array_pop($webform_path); - } - array_pop($json_path); - } - else { - if ($json_path !== $webform_path) { - $result[implode('][', array_merge($webform_path, [$key]))] = implode('][', array_merge($json_path, [$key])); - } - } - } - return $result; - } - - /** - * Prepares JSON data for submission. - * - * Webform has two kinds of grouped elements: - * - composite elements (e.g. address) - * - field groups (e.g. fieldset) - * - * To represent form as JSON schema, we need to treat both kinds in the same - * way. For example: - * [ - * 'address' => [ - * 'street' => {string}, - * 'number' => {string}, - * ], - * 'name_fieldset' => [ - * 'first_name' => {string}, - * 'last_name' => {string}, - * ], - * ] - * This is required to render the form in the proper way on the frontend. - * - * But on submission Webform module expects that composite element children - * are nested on their parent, but field group children are not. Here is how - * the above example should be passed to - * WebformSubmissionForm::submitFormValues(): - * [ - * 'address' => [ - * 'street' => {string}, - * 'number' => {string}, - * ], - * 'first_name' => {string}, - * 'last_name' => {string}, - * ] - * - * @param $data - * @param $schema - * - * @return array - */ - protected static function flattenData($data, $schema) { - $result = []; - foreach ($data as $key => $value) { - if (!empty($schema['properties'][$key]['is_wrapper_element']) && is_array($value)) { - if (empty($schema['properties'][$key]['is_composite_element'])) { - $result += self::flattenData($value, $schema['properties'][$key]); - } - else { - $result[$key] = self::flattenData($value, $schema['properties'][$key]); - } - } - else { - $result[$key] = $value; - } - } - return $result; - } - -} diff --git a/packages/composer/drupal/webform_jsonschema/src/Transformer.php b/packages/composer/drupal/webform_jsonschema/src/Transformer.php deleted file mode 100644 index 75e24ccee..000000000 --- a/packages/composer/drupal/webform_jsonschema/src/Transformer.php +++ /dev/null @@ -1,423 +0,0 @@ -moduleHandler = $moduleHandler; - } - - /** - * Transforms a webform to JSON Schema. - * - * @param \Drupal\webform\Entity\Webform $webform - * - * @return array - */ - public function toJsonSchema(Webform $webform) { - $schema = [ - 'title' => $webform->label(), - ] + self::itemsToSchema($this->toItems($webform)); - $this->moduleHandler->alter( - 'webform_jsonschema_schema', $schema, $webform); - return $schema; - } - - /** - * Transforms a webform to UI Schema. - * - * @param \Drupal\webform\Entity\Webform $webform - * - * @return array - */ - public function toUiSchema(Webform $webform) { - $uiSchema = self::itemsToUiSchema($this->toItems($webform)); - - // Provide a general validation error message that can be displayed on the - // top of the form. - // Unfortunately, the standard error message from - // \Drupal\inline_form_errors\FormErrorHandler::displayErrorMessages is too - // hard to generate and use. So we go with a custom one. - $uiSchema['webform:generalValidationErrorMessage'] = (string) t('A form validation error occurred. Please check the values you have entered.'); - // And a one for a general submission error. - $uiSchema['webform:generalSubmissionErrorMessage'] = (string) t('There was an error submitting webform.'); - - $this->moduleHandler->alter( - 'webform_jsonschema_ui_schema', $uiSchema, $webform); - return $uiSchema; - } - - /** - * Extracts the button definitions from the UI schema. - * - * @param \Drupal\webform\Entity\Webform $webform - * - * @return array - */ - public function toButtons(Webform $webform) { - $buttons = self::itemsToButtons($this->toItems($webform)); - $this->moduleHandler->alter( - 'webform_jsonschema_buttons', $buttons, $webform); - return $buttons; - } - - /** - * Transforms a webform to WebformItem's. - * - * @param \Drupal\webform\Entity\Webform $webform - * - * @return \Drupal\webform_jsonschema\WebformItem[] - */ - public function toItems(Webform $webform) { - $elements = $webform->getElementsInitialized(); - return self::getStructureElements($elements); - } - - /** - * Creates a JSON Schema out of WebformItem's. - * - * @param \Drupal\webform_jsonschema\WebformItem[] $items - * - * @return array - */ - protected static function itemsToSchema($items) { - $schema = [ - 'type' => 'object', - ]; - - - foreach ($items as $key => $item) { - if (!empty($item->element['#required'])) { - $schema['required'][] = $key; - } - $properties = [ - 'title' => (string) $item->elementPlugin->getLabel($item->element), - ]; - if ($item->element['#type'] === 'container') { - $properties['title'] = ''; - } - if ($item->elementPlugin->isComposite()) { - $properties['is_composite_element'] = TRUE; - } - if ($item->children) { - $properties['is_wrapper_element'] = TRUE; - if ($item->elementPlugin instanceof JsonSchemaElementInterface) { - $item->elementPlugin->addJsonSchema($properties, $item->element); - } - $properties += self::itemsToSchema($item->children); - } - else { - $ignore = [ - 'value', - 'webform_element', // No idea what this is. - ]; - if (!isset($item->element['#type']) || in_array($item->element['#type'], $ignore, TRUE)) { - $properties = []; - } - elseif ($item->elementPlugin instanceof JsonSchemaElementInterface) { - $item->elementPlugin->addJsonSchema($properties, $item->element); - } - elseif ( - $item->element['#type'] === 'checkbox' || - $item->element['#type'] === 'webform_terms_of_service' - ) { - $properties['type'] = 'boolean'; - } - elseif ($item->element['#type'] === 'textarea') { - $properties['type'] = 'string'; - } - elseif ($item->element['#type'] === 'textfield') { - $properties['type'] = 'string'; - if (isset($item->element['#pattern'])) { - $properties['pattern'] = $item->element['#pattern']; - } - } - elseif ($item->element['#type'] === 'hidden') { - $properties['type'] = 'string'; - } - elseif ( - $item->element['#type'] === 'select' || - $item->element['#type'] === 'webform_buttons' || - $item->element['#type'] === 'checkboxes' || - $item->element['#type'] === 'radios' || - $item->element['#type'] === 'tableselect' || - $item->element['#type'] === 'webform_tableselect_sort' || - $item->element['#type'] === 'webform_table_sort' || - // These define an option widget with one special "Other..." item - // which allows to add a custom value in addition to the default - // options. This cannot be implemented with default - // react-jsonschema-form tools. Therefore we stick to the default - // options only. - $item->element['#type'] === 'webform_select_other' || - $item->element['#type'] === 'webform_checkboxes_other' || - $item->element['#type'] === 'webform_radios_other' || - $item->element['#type'] === 'webform_buttons_other' - ) { - // TODO: currently all option keys are assumed to be strings, but - // maybe other types need to be considered. - $properties['type'] = 'string'; - $properties['anyOf'] = array_map(function($key, $value) { - return [ - 'enum' => [ - (string) $key, - ], - 'title' => (string) $value, - ]; - }, array_keys($item->element['#options']), $item->element['#options']); - $properties['uniqueItems'] = TRUE; - } - elseif ($item->element['#type'] === 'number') { - $properties['type'] = 'number'; - if (isset($item->element['#min'])) { - $properties['minimum'] = $item->element['#min']; - } - if (isset($item->element['#max'])) { - $properties['maximum'] = (float) $item->element['#max']; - } - if (isset($item->element['#step'])) { - $properties['multipleOf'] = (float) $item->element['#step']; - } - } - elseif ($item->element['#type'] === 'email') { - $properties['type'] = 'string'; - $properties['format'] = 'email'; - } - elseif ($item->element['#type'] === 'url') { - $properties['type'] = 'string'; - $properties['format'] = 'uri'; - } - elseif ($item->element['#type'] === 'tel') { - $properties['type'] = 'string'; - } - elseif ($item->element['#type'] === 'datetime') { - $properties['type'] = 'string'; - $properties['format'] = 'date-time'; - } - elseif ($item->element['#type'] === 'date') { - $properties['type'] = 'string'; - $properties['format'] = 'date'; - } - elseif ($item->element['#type'] === 'webform_time') { - $properties['type'] = 'string'; - } - else { - // Not supported yet. - $properties = []; - } - } - if (!empty($properties)) { - if ($multivalue = $item->elementPlugin->hasMultipleValues($item->element)) { - $properties = [ - 'type' => 'array', - 'items' => $properties, - ]; - $top_properties = [ - 'title', - 'description', - 'uniqueItems', - ]; - foreach ($top_properties as $top_property) { - if (isset($properties['items'][$top_property])) { - $properties[$top_property] = $properties['items'][$top_property]; - unset($properties['items'][$top_property]); - } - } - if (is_numeric($multivalue)) { - $properties['maxItems'] = $multivalue; - } - if (!isset($properties['minItems']) && !empty($item->element['#required'])) { - $properties['minItems'] = 1; - } - } - $schema['properties'][$key] = $properties; - } - } - - Conditions::apply($schema, $items); - - return $schema; - } - - /** - * Transforms webform elements to WebformItem's. - * - * @param array $elements - * - * @return \Drupal\webform_jsonschema\WebformItem[] - */ - protected static function getStructureElements($elements) { - $element_manager = \Drupal::service('plugin.manager.webform.element'); - $items = []; - foreach ($elements as $key => $element) { - /** @var \Drupal\webform\Plugin\WebformElementInterface $element_plugin */ - $element_plugin = $element_manager->getElementInstance($element); - $item = new WebformItem(); - $item->element = $element; - $item->elementPlugin = $element_plugin; - if ($element_plugin->isComposite() && $element_plugin instanceof WebformCompositeBase) { - $children = $element_plugin->getInitializedCompositeElement($element); - $item->children = self::getStructureElements($children); - } - elseif ($children_keys = Element::children($element)) { - $children = array_intersect_key($element, array_flip($children_keys)); - $item->children = self::getStructureElements($children); - } - if (!isset($item->element['#access']) || $item->element['#access']) { - $items[$key] = $item; - } - } - return $items; - } - - /** - * Creates a UI Schema out of WebformItem's. - * - * @param \Drupal\webform_jsonschema\WebformItem[] $items - * - * @return array - */ - protected static function itemsToUiSchema(array $items) { - $ui_schema = []; - foreach ($items as $key => $item) { - $ui_schema[$key] = []; - - if ($item->element['#type'] === 'textarea') { - $ui_schema[$key]['ui:widget'] = 'textarea'; - } - elseif ($item->elementPlugin instanceof JsonSchemaElementInterface) { - $item->elementPlugin->addJsonSchemaUiSchema($ui_schema[$key], $item->element); - } - elseif ( - $item->element['#type'] === 'webform_buttons' || - $item->element['#type'] === 'checkboxes' || - $item->element['#type'] === 'webform_buttons_other' || - $item->element['#type'] === 'webform_checkboxes_other' - ) { - $ui_schema[$key]['ui:widget'] = 'checkboxes'; - } - elseif ( - $item->element['#type'] === 'radios' || - $item->element['#type'] === 'webform_radios_other' - ) { - $ui_schema[$key]['ui:widget'] = 'radio'; - } - elseif ($item->element['#type'] === 'webform_terms_of_service') { - $markupBuild = WebformHtmlEditor::checkMarkup($item->element['#terms_content']); - $ui_schema[$key]['ui:termsOfService'] = [ - 'title' => (string) $item->element['#_webform_terms_of_service_title'], - 'markup' => \Drupal::service('renderer')->render($markupBuild), - ]; - } - elseif ($item->element['#type'] === 'hidden') { - $ui_schema[$key]['ui:widget'] = 'hidden'; - } - - if ( - isset($item->element['#webform_jsonschema']['uiSchema']) && - is_array($item->element['#webform_jsonschema']['uiSchema']) - ) { - $ui_schema[$key] = NestedArray::mergeDeepArray([ - $ui_schema[$key], - $item->element['#webform_jsonschema']['uiSchema'], - ]); - } - - if ($item->children) { - $ui_schema[$key] += self::itemsToUiSchema($item->children); - } - - // Provide error messages to frontend so that they can be used with - // react-jsonschema-form's `transformErrors` option. See - // https://react-jsonschema-form.readthedocs.io/en/latest/validation/#custom-error-messages - // - // The errors keys are "Validation Keywords" from the "JSON Schema - // Validation" specification. See - // https://json-schema.org/latest/json-schema-validation.html#rfc.section.6 - // - // If an element does not provide a custom error message, use the standard - // one which would be used by Webform module or Drupal core. - if (!empty($item->element['#required'])) { - $message = !empty($item->element['#required_error']) - ? $item->element['#required_error'] - : t('@name field is required.', ['@name' => $item->element['#title']]); - $message = strip_tags((string) $message); - if ($item->elementPlugin->hasMultipleValues($item->element)) { - // For the required multivalue elements we use minItems=1 in - // Transformer::itemsToSchema. So on the frontend the `error.name` - // will be "minItems", but the actual meaning will be that the element - // is required. - $ui_schema[$key]['webform:validationErrorMessages']['minItems'] = $message; - } - else { - $ui_schema[$key]['webform:validationErrorMessages']['required'] = $message; - } - } - if (isset($item->element['#pattern'])) { - $message = !empty($item->element['#pattern_error']) - ? $item->element['#pattern_error'] - : t('%name field is not in the right format.', ['%name' => $item->element['#title']]); - $message = strip_tags((string) $message); - $ui_schema[$key]['webform:validationErrorMessages']['pattern'] = $message; - } - - if (empty($ui_schema[$key])) { - // Prevent empty array/object PHP-to-JSON conversion issues by removing - // empty items. - unset($ui_schema[$key]); - } - - // It is important to add the order since we use conditions in the schema. - $ui_schema['ui:order'][] = $key; - } - return $ui_schema; - } - - /** - * Creates an array of buttons out of the WebformItems. - * - * @param \Drupal\webform_jsonschema\WebformItem[] $items - * - * @return array - */ - protected static function itemsToButtons($items) { - $buttons = []; - foreach ($items as $key => $item) { - if ($item->elementPlugin instanceof JsonSchemaElementInterface) { - $item->elementPlugin->addJsonSchemaButtons($buttons, $item->element); - } - elseif ($item->element['#type'] == 'webform_actions') { - $buttons[] = [ - // Now we just have submit, but we might want to introduce other - // button later, e.g. reset. - 'type' => 'submit', - 'value' => !empty($item->element['#submit__label']) - ? $item->element['#submit__label'] - : $item->element['#title'], - ]; - } - } - return $buttons; - } - -} diff --git a/packages/composer/drupal/webform_jsonschema/src/WebformItem.php b/packages/composer/drupal/webform_jsonschema/src/WebformItem.php deleted file mode 100644 index 59d4cdefd..000000000 --- a/packages/composer/drupal/webform_jsonschema/src/WebformItem.php +++ /dev/null @@ -1,31 +0,0 @@ - 'submit', - 'value' => 'Save with this button instead', - ]; -} diff --git a/packages/composer/drupal/webform_jsonschema/webform_jsonschema.info.yml b/packages/composer/drupal/webform_jsonschema/webform_jsonschema.info.yml deleted file mode 100644 index afa8f7bc6..000000000 --- a/packages/composer/drupal/webform_jsonschema/webform_jsonschema.info.yml +++ /dev/null @@ -1,8 +0,0 @@ -name: Webform JSON Schema -type: module -description: 'Expose webforms as JSON Schema, UI Schema, and Form Data. Make webforms work with react-jsonschema-form.' -package: Webform -core: 8.x -core_version_requirement: ^8 || ^9 -dependencies: -- webform:webform diff --git a/packages/composer/drupal/webform_jsonschema/webform_jsonschema.services.yml b/packages/composer/drupal/webform_jsonschema/webform_jsonschema.services.yml deleted file mode 100644 index 123baa0bd..000000000 --- a/packages/composer/drupal/webform_jsonschema/webform_jsonschema.services.yml +++ /dev/null @@ -1,21 +0,0 @@ -services: - - serializer.encoder.webform_jsonschema: - class: Drupal\webform_jsonschema\Encoder\JsonEncoder - tags: - - { name: encoder, format: webform_jsonschema } - - webform_jsonschema.route_subscriber: - class: 'Drupal\webform_jsonschema\Routing\EventSubscriber' - tags: - - { name: 'event_subscriber' } - - webform_jsonschema.transformer: - class: 'Drupal\webform_jsonschema\Transformer' - arguments: - ['@module_handler'] - - webform_jsonschema.submission: - class: 'Drupal\webform_jsonschema\Submission' - arguments: - ['@webform_jsonschema.transformer'] diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c1d14127b..5537c6ff2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -433,12 +433,6 @@ importers: specifier: workspace:* version: link:../../../npm/@amazeelabs/sync-composer-version - packages/composer/drupal/webform_jsonschema: - devDependencies: - '@amazeelabs/sync-composer-version': - specifier: workspace:* - version: link:../../../npm/@amazeelabs/sync-composer-version - packages/npm/@amazeelabs/bridge: dependencies: react: