Skip to content
This repository has been archived by the owner on Sep 30, 2024. It is now read-only.

[SDPAP-7155] webform validation #77

Open
wants to merge 7 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 153 additions & 0 deletions modules/tide_webform_jsonapi/src/TideResource/AddWebform.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
<?php

namespace Drupal\tide_webform_jsonapi\TideResource;

use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\jsonapi\JsonApiResource\ErrorCollection;
use Drupal\jsonapi\JsonApiResource\JsonApiDocumentTopLevel;
use Drupal\jsonapi\JsonApiResource\LinkCollection;
use Drupal\jsonapi\JsonApiResource\NullIncludedData;
use Drupal\jsonapi\JsonApiResource\ResourceObject;
use Drupal\jsonapi\JsonApiResource\ResourceObjectData;
use Drupal\jsonapi\ResourceResponse;
use Drupal\jsonapi_resources\Resource\EntityQueryResourceBase;
use Drupal\tide_webform_jsonapi\TideWebformJsonapiHelper;
use Drupal\webform\Entity\Webform;
use Drupal\webform\WebformSubmissionForm;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\Routing\Route;
use Drupal\jsonapi\ResourceType\ResourceTypeRepository;
use Drupal\jsonapi\Controller\EntityResource;

/**
* Processes a request to create a submission.
*
* @package Drupal\jsonapi_resources_test\Resource
*/
final class AddWebform extends EntityQueryResourceBase implements ContainerInjectionInterface {

/**
* The ResourceTypeRepository.
*
* @var \Drupal\jsonapi\ResourceType\ResourceTypeRepository
*/
protected $tideWebformJsonapiHelper;

/**
* The ResourceTypeRepository.
*
* @var \Drupal\jsonapi\ResourceType\ResourceTypeRepository
*/
protected $resourceTypeRepository;

/**
* The EntityResource controller.
*
* @var \Drupal\jsonapi\Controller\EntityResource
*/
protected $resource;

/**
* {@inheritdoc}
*/
public function __construct(TideWebformJsonapiHelper $tide_webform_jsonapi_helper, ResourceTypeRepository $resource_type_repository, EntityResource $resource) {
$this->tideWebformJsonapiHelper = $tide_webform_jsonapi_helper;
$this->resourceTypeRepository = $resource_type_repository;
$this->resource = $resource;
}

/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('tide_webform_jsonapi.helper'),
$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 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);

// Massage, organise and verify the data.
$original_elements = $webform->getElementsDecodedAndFlattened();
$supported_validations = $this->tideWebformJsonapiHelper->getSupportedValidateElements();
$results = $this->tideWebformJsonapiHelper->webformValidateSettingsExtractor($supported_validations, $original_elements);
$new_array = [];
foreach ($results as $key => $r) {
$new_array[$key] = $this->tideWebformJsonapiHelper->attachValidateSettingsToPayload($r);
}
$errors = $this->tideWebformJsonapiHelper->validatePayload($entity->getData(), $new_array, $original_elements);
// 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.
// Saving implies triggering the webform handlers.
$entity->save();
$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');
}

}
122 changes: 122 additions & 0 deletions modules/tide_webform_jsonapi/src/TideWebformJsonapiHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
<?php

namespace Drupal\tide_webform_jsonapi;

use Respect\Validation\Validator as v;

/**
* Provides helper functions for tide_webform_jsonapi.
*/
class TideWebformJsonapiHelper {

/**
* 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.
*/
public function getSupportedValidateElements() {
return [
'#required',
'#required_error',
'#pattern',
'#pattern_error',
];
}

/**
* Attached supported validator to Payload.
*/
public 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.
*/
public function validatePayload(array $payload, array $massaged_validates_array, array $original_elements) {
$results = [];
// 1st step, check if all required field are submitted.
foreach ($massaged_validates_array as $key => $item) {
if (isset($item['required'])) {
if (!isset($payload[$key])) {
$payload[$key] = "";
}
}
}
// 2nd step, check if it passes the validation.
foreach ($payload as $id => $value) {
if (array_key_exists($id, $massaged_validates_array)) {
if (!empty($this->generateErrorString($value, $massaged_validates_array[$id], $original_elements[$id]['#title']))) {
$results[$id] = $this->generateErrorString($value, $massaged_validates_array[$id], $original_elements[$id]['#title']);
}
}
}
// 3rd step, check if the field contains correct email or phone numbers.
foreach ($original_elements as $field_id => $detail) {
if (!isset($results[$field_id]) && isset($payload[$field_id])) {
if ($detail['#type'] === 'email') {
if (v::email()->validate($payload[$field_id]) === FALSE) {
$results[$field_id] = ['Please provide a valid email address.'];
}
}
if ($detail['#type'] === 'tel') {
if (v::phone('AU')->validate($payload[$field_id]) === FALSE) {
$results[$field_id] = ['Please provide a valid Phone number.'];
}
}
}
}
return $results;
}

/**
* Generates error messages.
*/
public function generateErrorString($value, array $arr, $title) {
$res = [];
foreach ($arr as $k => $v) {
if (call_user_func('tide_webform_jsonapi_' . $k . '_validate', $value, $v, $title) !== TRUE) {
$res[] = call_user_func('tide_webform_jsonapi_' . $k . '_validate', $value, $v, $title);
}
}
return $res;
}

}
Loading