Skip to content
Draft
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
10 changes: 10 additions & 0 deletions modules/json_form_widget/css/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/* Target any fieldset with the error class */
fieldset.error {
border-width: var(--input--error-border-size) !important;
border-color: var(--input--error-border-color) !important;
}

/* Target any fieldset legend when it has an error */
fieldset.error > legend > span.fieldset__label {
color: var(--input--error-color) !important;
}
4 changes: 4 additions & 0 deletions modules/json_form_widget/json_form_widget.libraries.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
style:
css:
theme:
css/style.css: {}
10 changes: 10 additions & 0 deletions modules/json_form_widget/json_form_widget.module
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,19 @@ function json_form_widget_form_alter(&$form, FormStateInterface $form_state, $fo
}
if ($form_state->get('has_json_form_widget')) {
$form['actions']['submit']['#submit'][] = [UploadOrLink::class, 'submit'];

// Add a validation handler.
$form['#validate'][] = '_json_form_widget_post_constraint_validation';
}
}

/**
* Validation handler.
*/
function _json_form_widget_post_constraint_validation(array &$form, FormStateInterface $form_state) {
\Drupal::service('json_form.form_post_validate')->onPostConstraintValidate($form, $form_state);
}

/**
* Implements hook_entity_delete().
*
Expand Down
2 changes: 2 additions & 0 deletions modules/json_form_widget/json_form_widget.services.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,5 @@ services:
parent: logger.channel_base
arguments:
- 'json_form_widget'
json_form.form_post_validate:
class: \Drupal\json_form_widget\FormPostValidate
46 changes: 46 additions & 0 deletions modules/json_form_widget/src/FormPostValidate.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

namespace Drupal\json_form_widget;

use Drupal\Core\Form\FormStateInterface;
use Drupal\Component\Utility\NestedArray;

/**
* Post validation.
*/
class FormPostValidate {

/**
* Handles the post-constraint validation event.
*
* @param array $form
* The form array.
* @param \Drupal\core\Form\FormStateInterface $form_state
* The form state.
*/
public function onPostConstraintValidate(array &$form, FormStateInterface $form_state) {
if (empty($errors = $form_state->getErrors())) {
return;
}

$form_state->clearErrors();

$form['#attached']['library'][] = 'json_form_widget/style';

foreach ($errors as $error) {
$message = $error->__toString();
$field_name_json = $error->getArguments()['json_field_pointer'];
$field_name = json_decode($field_name_json, TRUE);

$full_path = ['field_json_metadata', 'widget', 0, 'value'];
$full_path = array_merge($full_path, $field_name);

$element = &NestedArray::getValue($form, $full_path, $key_exists);

if ($key_exists && is_array($element)) {
$form_state->setError($element, $message);
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,41 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
// Attempt to build the form.
$json_form = $this->builder->getJsonForm($default_data, $form_state);
if ($json_form) {
return ['value' => $json_form];
// Add entity_builders callback - runs AFTER entity validation
//$element['#element_validate'][] = [$this, 'lateValidationHandler'];
$element['value'] = $json_form;
return $element;
}
}

/**
* Late validation handler.
*/
public static function lateValidationHandler(array &$form, FormStateInterface $form_state) {
if ($form_state->hasAnyErrors()) {
return;
}

// Get the entity being validated
$form_object = $form_state->getFormObject();
if (method_exists($form_object, 'getEntity')) {
$entity = $form_object->getEntity();

// Manually validate the entity constraints
$violations = $entity->validate();

// If there are violations, process them with your service
if (count($violations) > 0) {
$field_name = $form_state->get('json_form_widget_field');
$element = $form[$field_name]['widget'][0] ?? null;

if ($element) {
\Drupal::service('json_form.form_post_validate')->onPostConstraintValidate($element, $form, $form_state);
}
}
}
}

/**
* {@inheritdoc}
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ private function doValidate(string $schema_id, $item): array {
$this->validMetadataFactory->get($item->value, $schema_id);
}
catch (ValidationException $e) {
$errors = $this->getValidationErrorsMessages($e->getResult()->getErrors());
$errors = $e->getResult()->getErrors();
}
catch (InvalidArgumentException $e) {
$errors[] = $e->getMessage();
Expand All @@ -116,31 +116,86 @@ private function doValidate(string $schema_id, $item): array {
}

/**
* Presents errors.
*
* @param array $errors
* Validation errors array.
*
* @return array
* Presented errors array.
* Add Violations with field context.
*/
private function getValidationErrorsMessages(array $errors): array {
$presented = $this->presenter->present(...$errors);
return array_map(
function ($presented_error) {
return $presented_error->message();
},
$presented
);
private function addViolations($errors) {
foreach ($errors as $error) {
// Extract field information from error pointer
$field_name = $this->extractFieldFromPointer($error);
$message = is_array($error) ? $error['message'] : $this->presenter->present($error)[0]->message();

// Add violation with field context
$violation = $this->context->buildViolation($message);

// Store field information in the violation for later use
if ($field_name) {
$violation->setParameter('json_field_pointer', json_encode($field_name));
}

$violation->addViolation();
}
}

/**
* Add Violations.
* Extract field name from JSON Schema error pointer.
*/
private function addViolations($errors) {
foreach ($errors as $error) {
$this->context->addViolation($error);
private function extractFieldFromPointer($error) {
if (!is_object($error)) {
return null;
}

// Handle required field errors - field name is in keywordArgs['missing']
if (method_exists($error, 'keywordArgs')) {
$keywordArgs = $error->keywordArgs();
if (isset($keywordArgs['missing'])) {
return [$keywordArgs['missing']];
}
}

// Handle other validation errors - field name is in dataPointer
if (method_exists($error, 'dataPointer')) {
$pointer = $error->dataPointer();
if (is_array($pointer) && !empty($pointer)) {
// If dataPointer contains just one index, return it as an array
if (count($pointer) === 1) {
return $pointer;
}

// For array fields with multiple indices, we need to add the field name both before and after numeric indices
$processed_pointer = [];

foreach ($pointer as $index => $part) {
// If this is the first part and it's a field name, add it twice
if ($index === 0 && !is_numeric($part)) {
$processed_pointer[] = $part; // First occurrence
$processed_pointer[] = $part; // Second occurrence for array structure
}
// If this is a numeric index, add it and then add the field name after
elseif (is_numeric($part)) {
$processed_pointer[] = $part;
// Find the field name (first non-numeric part)
$field_name = null;
foreach ($pointer as $p) {
if (!is_numeric($p)) {
$field_name = $p;
break;
}
}
if ($field_name) {
$processed_pointer[] = $field_name;
}
}
// For other parts (like 'privateEmail'), just add them
else {
$processed_pointer[] = $part;
}
}

return $processed_pointer;
}
}

return null;
}

}
78 changes: 76 additions & 2 deletions schema/collections/dataset.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"identifier",
"accessLevel",
"modified",
"keyword"
"keyword",
"creators"
],
"properties": {
"@type": {
Expand All @@ -22,7 +23,80 @@
"title": "Title",
"description": "Human-readable name of the asset. Should be in plain English and include sufficient detail to facilitate search and discovery.",
"type": "string",
"minLength": 1
"minLength": 10
},
"number": {
"title": "Number",
"description": "Human-readable name of the asset. Should be in plain English and include sufficient detail to facilitate search and discovery.",
"type": "integer",
"minimum": 10
},
"creators": {
"title": "Author Information",
"type": "array",
"items": {
"title": "Author",
"type": "object",
"properties": {
"givenName": {
"title": "First Name",
"type": "string"
},
"middleName": {
"title": "Middle Name",
"type": "string"
},
"familyName": {
"title": "Last Name",
"type": "string"
},
"privateEmail": {
"title": "Email",
"format": "email",
"type": "string",
"minLength": 1
}
},
"required": [
"givenName",
"familyName",
"privateEmail"
]
},
"minItems": 2
},
"creators-not-required": {
"title": "Author Information NOT",
"type": "array",
"items": {
"title": "Author",
"type": "object",
"properties": {
"givenName": {
"title": "First Name",
"type": "string"
},
"middleName": {
"title": "Middle Name",
"type": "string"
},
"familyName": {
"title": "Last Name",
"type": "string"
},
"privateEmail": {
"title": "Email",
"format": "email",
"type": "string",
"minLength": 1
}
},
"required": [
"givenName",
"familyName",
"privateEmail"
]
}
},
"identifier": {
"title": "Unique Identifier",
Expand Down