From 1331175c86309f2f51f599a69853b48e92db7883 Mon Sep 17 00:00:00 2001 From: Vincent Gao Date: Mon, 3 Apr 2023 17:57:55 +1000 Subject: [PATCH] [SDPAP-7155] webform validation --- .../src/Resource/AddWebform.php | 229 ++++++++++++++++++ .../tide_webform_jsonapi.info.yml | 6 + .../tide_webform_jsonapi.module | 32 +++ .../tide_webform_jsonapi.routing.yml | 11 + 4 files changed, 278 insertions(+) create mode 100644 modules/tide_webform_jsonapi/src/Resource/AddWebform.php create mode 100644 modules/tide_webform_jsonapi/tide_webform_jsonapi.info.yml create mode 100644 modules/tide_webform_jsonapi/tide_webform_jsonapi.module create mode 100644 modules/tide_webform_jsonapi/tide_webform_jsonapi.routing.yml diff --git a/modules/tide_webform_jsonapi/src/Resource/AddWebform.php b/modules/tide_webform_jsonapi/src/Resource/AddWebform.php new file mode 100644 index 0000000..652b4b3 --- /dev/null +++ b/modules/tide_webform_jsonapi/src/Resource/AddWebform.php @@ -0,0 +1,229 @@ +resourceTypeRepository = $resource_type_repository; + $this->resource = $resource; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('jsonapi.resource_type.repository'), + $container->get('jsonapi.entity_resource') + ); + } + + /** + * Process the resource request. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * The request. + * @param \Drupal\webform\Entity\Webform $webform + * The webform entity. + * + * @return \Drupal\jsonapi\ResourceResponse + * The response. + * + * @throws \Symfony\Component\HttpKernel\Exception\ConflictHttpException + * Thrown when the entity to be created already exists. + * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException + * Thrown if the storage handler couldn't be loaded. + * @throws \Drupal\Core\Entity\EntityStorageException + * Thrown if the entity could not be saved. + */ + public function process(Request $request, Webform $webform): ResourceResponse { + // Purely business logic part to get the webform_submission entity. + $resource_type = $this->resourceTypeRepository->get( + 'webform_submission', + $webform->id() + ); + $reflectedMethod = new \ReflectionMethod( + $this->resource, + 'deserialize' + ); + $reflectedMethod->setAccessible(TRUE); + /** @var \Drupal\webform\Entity\WebformSubmission $entity */ + $entity = $reflectedMethod->invoke( + $this->resource, + $resource_type, + $request, + JsonApiDocumentTopLevel::class + ); + static::validate($entity); + // It implies triggering the webform handlers. + $entity->save(); + // Massage, organise and verify the data. + $original_elements = $webform->getElementsDecodedAndFlattened(); + $supported_validations = $this->getSupportedValidateElements(); + $results = $this->webformValidateSettingsExtractor($supported_validations, $original_elements); + $new_array = []; + foreach ($results as $key => $r) { + $new_array[$key] = $this->attachValidateSettingsToPayload($r); + } + $errors = $this->validatePayload($entity->getData(), $new_array); + // Let webform core checks words and characters. + $internal_errors = WebformSubmissionForm::validateWebformSubmission($entity); + // Prepare error messages. + if (!empty($internal_errors)) { + foreach ($internal_errors as $key => $message) { + $errors[$key][] = $message->__toString(); + } + } + $errors_collection = []; + if (!empty($errors)) { + foreach ($errors as $field_id => $details) { + foreach ($details as $item) { + $errors_collection[] = new HttpException(422, $field_id . '|' . $item); + } + } + $errs = new ErrorCollection($errors_collection); + $document = new JsonApiDocumentTopLevel($errs, new NullIncludedData(), new LinkCollection([])); + return new ResourceResponse($document, 422); + } + + // Return 201 if no errors. + $resource_object = ResourceObject::createFromEntity($resource_type, $entity); + $primary_data = new ResourceObjectData([$resource_object], 1); + return $this->createJsonapiResponse($primary_data, $request, 201); + } + + /** + * {@inheritdoc} + */ + public function getRouteResourceTypes(Route $route, string $route_name): array { + return $this->getResourceTypesByEntityTypeId('webform_submission'); + } + + /** + * Extracts validate settings from the webform settings. + */ + public function webformValidateSettingsExtractor(array $supported_validation, array $original_elements): array { + $res = []; + // Iterate through webform values. + foreach ($original_elements as $w_key => $items) { + // Iterate through supported validation keys and their values. + foreach ($supported_validation as $key => $value) { + // If the current value is an array, check for a match with the key. + if (is_array($value)) { + if (array_key_exists($key, $items)) { + $res[$w_key][$key] = $items[$key]; + } + } + else { + // If the value exists in the items and is not an array, + // store it in the result. + if (array_key_exists($value, $items)) { + $res[$w_key][$value] = $items[$value]; + } + } + } + } + return $res; + } + + /** + * Supported validators. + */ + private function getSupportedValidateElements() { + return [ + '#required', + '#required_error', + '#pattern', + '#pattern_error', + ]; + } + + /** + * Attached supported validator to Payload. + */ + private function attachValidateSettingsToPayload(array $payload) { + $output = []; + $mapping = [ + '#required' => 'required', + '#required_error' => 'required', + '#pattern' => 'pattern', + '#pattern_error' => 'pattern', + ]; + foreach ($payload as $key => $value) { + if (isset($mapping[$key])) { + $output[$mapping[$key]][] = $value; + } + } + return $output; + } + + /** + * Verifies the payload. + */ + private function validatePayload(array $payload, array $massaged_validates_array) { + $results = []; + foreach ($payload as $id => $value) { + if (array_key_exists($id, $massaged_validates_array)) { + if (!empty($this->generateErrorString($value, $massaged_validates_array[$id]))) { + $results[$id] = $this->generateErrorString($value, $massaged_validates_array[$id]); + } + } + } + return $results; + } + + /** + * Generates error messages. + */ + private function generateErrorString($value, array $arr) { + $res = []; + foreach ($arr as $k => $v) { + if (call_user_func('tide_webform_jsonapi_' . $k . '_validate', $value, $v) !== TRUE) { + $res[] = call_user_func('tide_webform_jsonapi_' . $k . '_validate', $value, $v); + } + } + return $res; + } + +} diff --git a/modules/tide_webform_jsonapi/tide_webform_jsonapi.info.yml b/modules/tide_webform_jsonapi/tide_webform_jsonapi.info.yml new file mode 100644 index 0000000..b256ceb --- /dev/null +++ b/modules/tide_webform_jsonapi/tide_webform_jsonapi.info.yml @@ -0,0 +1,6 @@ +name: 'JSON:API Tide_webform' +description: "This module let's you validate payload for webform." +core_version_requirement: ^9 || ^10 +type: module +dependencies: + - drupal:jsonapi_resources diff --git a/modules/tide_webform_jsonapi/tide_webform_jsonapi.module b/modules/tide_webform_jsonapi/tide_webform_jsonapi.module new file mode 100644 index 0000000..6fea1e4 --- /dev/null +++ b/modules/tide_webform_jsonapi/tide_webform_jsonapi.module @@ -0,0 +1,32 @@ + 1) { + return $arr[1]; + } + return 'The field is mandatory.'; + } + return TRUE; +} + +/** + * Callback for required validate. + */ +function tide_webform_jsonapi_pattern_validate($value, $arr) { + if (preg_match($arr[0], $value) != 1) { + if (count($arr) > 1) { + return $arr[1]; + } + return 'The value does not match the criteria.'; + } + return TRUE; +} diff --git a/modules/tide_webform_jsonapi/tide_webform_jsonapi.routing.yml b/modules/tide_webform_jsonapi/tide_webform_jsonapi.routing.yml new file mode 100644 index 0000000..c972f9e --- /dev/null +++ b/modules/tide_webform_jsonapi/tide_webform_jsonapi.routing.yml @@ -0,0 +1,11 @@ +tide_webform_jsonapi.add_webform: + path: '/%jsonapi%/webform/{webform}/add' + methods: ['POST'] + defaults: + _jsonapi_resource: Drupal\tide_webform_jsonapi\Resource\AddWebform + requirements: + _permission: 'access content' + options: + parameters: + webform: + type: 'entity:webform' \ No newline at end of file