Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ISSUE-181 and ISSUE-182: Better date but also data/pre-check for compatibility against Webform #184

Merged
merged 6 commits into from
Jan 30, 2025
Merged
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
3 changes: 3 additions & 0 deletions js/modernizr-custom.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

134 changes: 117 additions & 17 deletions src/Controller/StrawberryRunnerModalController.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@
/**
* StrawberryRunnerModalController class.
*/
class StrawberryRunnerModalController extends ControllerBase
{
class StrawberryRunnerModalController extends ControllerBase {

/**
* Callback for opening the modal form.
Expand Down Expand Up @@ -171,23 +170,27 @@ function array_is_list(array $arr)
}
}
$data['data'] = $data_defaults + json_decode($stored_value,true);
// In case the saved data is "single valued" for a key
// But the corresponding webform element is not
// we cast to it multi valued so it can be read/updated
/* @var \Drupal\webform\WebformInterface $webform */
$webform_elements = $webform->getElementsInitializedFlattenedAndHasValue();
$webform_elements_clean = $webform->getElementsDecodedAndFlattened();
$elements_in_data = array_intersect_key($webform_elements, $data['data']);
if (is_array($elements_in_data) && count($elements_in_data)>0) {
foreach($elements_in_data as $key => $elements_in_datum) {
if (isset($elements_in_datum['#webform_multiple']) &&
$elements_in_datum['#webform_multiple']!== FALSE) {
$data['data'][$key] = (array) $data['data'][$key];
if (!array_is_list($data['data'][$key])) {
$data['data'][$key] = [ $data['data'][$key] ];
}
}
}
// In case the saved data is "single valued" for a key
// But the corresponding webform element is not
// we cast to it multi valued so it can be read/updated
// If the element itself does not allow multiple, is not a composite and we are passing an indexed array
// we need to re-write data (take the first) to avoid a Render array error in Drupal 10.3
// But also tell the user this form is not safe to use.
if (is_array($elements_in_data) && count($elements_in_data) > 0) {
$error_elements_why = [];
$error_elements = StrawberryRunnerModalController::validateDataAgainstWebformElements($elements_in_data, $data, $error_elements_why);
}
foreach ($error_elements as $key => $value) {
$element = $webform_elements_clean[$key];
$element['#disabled'] = TRUE;
$element['#required'] = FALSE;
$webform->setElementProperties($key, $element);
}
$data['data']['strawberry_field_invalid_elements'] = $error_elements;
}

$confirmation_message = $webform->getSetting('confirmation_message', FALSE);
Expand All @@ -198,8 +201,6 @@ function array_is_list(array $arr)
// And also we need to reset some defaults here
// @see \Drupal\webform\Entity\Webform::getDefaultSettings
// @TODO autofill needs to be a setting that is respected
// But Kerri thought this could get in our way
// Need to thing about this.
// @TODO research option of using WebformInterface::CONFIRMATION_NONE
// @SEE https://www.drupal.org/node/2996780
// Does not work right now.
Expand Down Expand Up @@ -322,5 +323,104 @@ public function closeModalForm(Request $request)

}

/**
* @param $elements_in_data
* The Flattened Elements that intersect keys present in data.
* @param $data
* The JSON data as an Array coming from an ADO
* @return array
* Empty if no errors, if not associative array with keys of elements that are not compatible with data
* holding the original data so we can restore it on persistence (when saving).
*/
public static function validateDataAgainstWebformElements(array $elements_in_data, array &$data, array &$error_elements_why): array {
// In case the saved data is "single valued" for a key
// But the corresponding webform element is not
// we cast to it multi valued so it can be read/updated
// If the element itself does not allow multiple, is not a composite and we are passing an indexed array
// we need to re-write data (take the first) to avoid a Render array error in Drupal 10.3
// But also tell the user this form is not safe to use.
$error_elements = [];
foreach ($elements_in_data as $key => $elements_in_datum) {
if (isset($elements_in_datum['#webform_multiple']) &&
$elements_in_datum['#webform_multiple'] !== FALSE) {
//@TODO should we log this operation for admins?
$data['data'][$key] = (array) $data['data'][$key];
if (!array_is_list($data['data'][$key])) {
// means we just made a composite element a composite element! So make it a list
// And check also if the Source can be read/edited by the form element.
if ($elements_in_datum['#webform_composite_elements'] ?? NULL) {
$current_source_subkeys = array_keys($data['data'][$key]);
$element_subkeys = array_keys($elements_in_datum['#webform_composite_elements']);
// OK if element has more/less. Not OK if the source data has other/more.
if (count(array_diff($current_source_subkeys, $element_subkeys))) {
$diff = array_diff($current_source_subkeys, $element_subkeys);
$error_elements_why[$key] = t('@key contains @property not available for <em>@element_name</em>' , [
'@key' => $key,
'@property' => (count($diff) > 1 ? "properties " : "property ") . implode (",", $diff),
'@element_name' => $elements_in_datum['#title'] ?? $key,
]);
$error_elements[$key] = $data['data'][$key];
}
else {
$data['data'][$key] = [$data['data'][$key]];
}
} else {
// NO need to count here since this was originally an Object already.
$data['data'][$key] = [$data['data'][$key]];
}
}
else {
// count the values. The Element count might be lower than the source data
if (count($data['data'][$key]) > (int) $elements_in_datum['#webform_multiple']) {
$error_elements_why[$key] = t('@key contains @count which is larger than the multiple values limit of @max for <em>@element_name</em>' , [
'@key' => $key,
'@count' => count($data['data'][$key]) . " entries ",
'@max' => (int) $elements_in_datum['#webform_multiple'],
'@element_name' => $elements_in_datum['#title'] ?? $key,
]);
$error_elements[$key] = $data['data'][$key];
}
}
}
else {
// Not a multiple element. So check what we are getting here.
if (is_array($data['data'][$key]) && !empty($data['data'][$key])) {
if (array_is_list($data['data'][$key])) {
// Make an exception for "one" count and "entity_autocomplete"
if ($elements_in_datum['#webform_plugin_id'] == "entity_autocomplete" && count($data['data'][$key]) == 1) {
// Do nothing. We accept this bc the element actually can load a single entry array.
}
else {
// Multiple entries for a single valued element. Bad.
$error_elements_why[$key] = t('@key contains multiple values but <em>@element_name</em> is configured for a single one', [
'@key' => $key,
'@element_name' => $elements_in_datum['#title'] ?? $key,
]);
$error_elements[$key] = $data['data'][$key];
}
}
else {
// The data is an object.
if ($elements_in_datum['#webform_composite_elements'] ?? NULL) {
$current_source_subkeys = array_keys($data['data'][$key]);
$element_subkeys = array_keys($elements_in_datum['#webform_composite_elements']);
// OK if element has more/less. Not OK if the source data has other/more.
if (count(array_diff($current_source_subkeys, $element_subkeys))) {
$diff = array_diff($current_source_subkeys, $element_subkeys);
$error_elements_why[$key] = t('@key contains @property not available for <em>@element_name</em>' , [
'@key' => $key,
'@property' => (count($diff) > 1 ? "properties " : "property ") . implode (",", $diff),
'@element_name' => $elements_in_datum['#title'] ?? $key,
]);
$error_elements[$key] = $data['data'][$key];
}
}
}
}
}
}
return $error_elements;
}


}
61 changes: 50 additions & 11 deletions src/Element/WebformMetadataDate.php
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@ public static function processWebformMetadataDate(&$element, FormStateInterface
if (isset($element['#default_value']) && !empty($element['#default_value'])) {
if (is_array($element['#default_value'])) {
// This is the default. When stored value is present
$type = isset($element['#default_value']['date_type']) ? $element['#default_value']['date_type'] : $type;
$type = $element['#default_value']['date_type'] ?? $type;
$type = trim($type);
$date_from_value = isset($element['#default_value']['date_from']) ? $element['#default_value']['date_from'] : $date_from_value;
$date_to_value = isset($element['#default_value']['date_to']) ? $element['#default_value']['date_to'] : $date_to_value;
$date_free_value = isset($element['#default_value']['date_free']) ? $element['#default_value']['date_free'] : $date_free_value;
Expand Down Expand Up @@ -134,27 +135,55 @@ public static function processWebformMetadataDate(&$element, FormStateInterface
$date_free_value = $element['#default_value'];
}
}
if ($type == 'date_edtf') {
$date_free_value = $element['#default_value'];
}
}
}


$date_types = [
'date_point' => 'Point Date',
'date_range' => 'Date Range',
'date_free' => 'Freeform Date',
'date_edtf' => 'Date EDTF',
];
if (!in_array($type, array_keys($date_types))) {
$type = 'date_free';
}
if(!empty($element['#edtf_validateme'])) {
$date_free_msg = t('EDTF formatted date');
unset($date_types['date_free']);
if ($type =='date_free') {
$type ='date_edtf';
}
}
if(!empty($element['#edtf_only'])) {
$date_types = [
'date_edtf' => 'Date EDTF',
];
}

if(!empty($element['#edtf_validateme']) || $type == 'date_edtf') {
$date_free_msg = t('See the <a href="https://www.loc.gov/standards/datetime/" target="_blank">LoC EDTF Specification </a> for formatting requirements.');
$date_free_title = t('EDTF date');
}
else {
$date_free_msg = t('Free form date (e.g Circa Summer of 1977)');
$date_free_title = t('Free form date');
}

/* if ($type == 'date_edtf') {
$date_types = [
'date_edtf' => 'Date EDTF',
];
} */

// The date formatting/options
$element['date_type'] = [
'#type' => 'radios',
'#title' => '',
'#default_value' => $type,
'#options' => [
'date_point' => t('Date'),
'date_range' => t('Date Range'),
'date_free' => $date_free_msg,
]
'#default_value' => trim($type),
'#options' => $date_types
];
/* Just and idea? Since we need modal labels.
// We may make the labels simply HTML and then deal with them via JS?
Expand Down Expand Up @@ -263,6 +292,7 @@ public static function processWebformMetadataDate(&$element, FormStateInterface
$element['date_to']['#attributes']['type'] = 'date';

$element['date_free']['#title'] = $date_free_title;
$element['date_free']['#description'] = $date_free_msg;
$element['date_free']['#attributes']['class'][] = 'webform-date-free';
$element['date_free']['#default_value'] = $date_free_value;
$element['date_free']['#type'] = 'textfield';
Expand All @@ -274,16 +304,20 @@ public static function processWebformMetadataDate(&$element, FormStateInterface
[':input[name="' . $name_prefix . '[date_type]"]' => ['value' => 'date_point']],
'or',
[':input[name="' . $name_prefix . '[date_type]"]' => ['value' => 'date_free']],
'or',
[':input[name="' . $name_prefix . '[date_type]"]' => ['value' => 'date_edtf']],
]
];

$element['date_from']['#states'] = [
'invisible' => [
':input[name="' . $name_prefix . '[date_type]"]' => ['value' => 'date_free'],
[':input[name="' . $name_prefix . '[date_type]"]' => ['value' => 'date_free']],
'or',
[':input[name="' . $name_prefix . '[date_type]"]' => ['value' => 'date_edtf']],
],
];

if (!isset($element['#showfreeformalways']) || (isset($element['#showfreeformalways']) && $element['#showfreeformalways'] == FALSE )) {
if (!isset($element['#showfreeformalways']) || (isset($element['#showfreeformalways']) && $element['#showfreeformalways'] == FALSE ) || $type == 'date_edtf') {
$element['date_free']['#states'] = [
'invisible' => [
[':input[name="' . $name_prefix . '[date_type]"]' => ['value' => 'date_point']],
Expand Down Expand Up @@ -354,20 +388,25 @@ public static function validateMetadataDates(&$element, FormStateInterface $form
if (!empty($element['#value'])) {
$value = $form_state->getValue($element['#parents'], []);
$filtered_value = array_filter($value);

// Empty elements here will carry at least the date_type making them
// not empty. So deal with that by unsetting the value completely in
// that case.
if (count($filtered_value) == 1 && isset($filtered_value['date_type'])) {
$element['#value'] = [];
} else {
if (in_array($value['date_type'], ['date_edtf', 'date_free'])) {
unset($value['date_from']);
unset($value['date_to']);
}
$element['#value'] = $value;
}
$form_state->setValueForElement($element, $element['#value']);
}
}

// Perform edtf validation on freeform date if so configured.
if(!empty($metadatadate_element['#edtf_validateme']) && !empty($element['#value']['date_free'])) {
if((!empty($metadatadate_element['#edtf_validateme']) || ($element['#value']['date_type'] == 'date_edtf')) && !empty($element['#value']['date_free'])) {
$validator = EdtfFactory::newValidator();
if (!$validator->isValidEdtf($element['#value']['date_free'])) {
$form_state->setError($element['date_free'],
Expand Down
Loading