From 581cea0b1abb7317adef6b14779da5b8b1d044aa Mon Sep 17 00:00:00 2001 From: Seth Shaw Date: Fri, 4 Jan 2019 12:53:26 -0800 Subject: [PATCH 1/8] support multiple EDTF data types in JSON-LD - update normalized array hook to update data type for edtf fields - update default configurations to use edtf field --- controlled_access_terms.module | 50 ++- ...y.taxonomy_term.corporate_body.default.yml | 4 +- ...m_display.taxonomy_term.family.default.yml | 4 +- ...m_display.taxonomy_term.person.default.yml | 4 +- ...y.taxonomy_term.corporate_body.default.yml | 4 +- ...w_display.taxonomy_term.family.default.yml | 4 +- ...w_display.taxonomy_term.person.default.yml | 4 +- ...rm.corporate_body.field_cat_date_begin.yml | 2 +- ...term.corporate_body.field_cat_date_end.yml | 2 +- ...onomy_term.family.field_cat_date_begin.yml | 2 +- ...axonomy_term.family.field_cat_date_end.yml | 2 +- ...onomy_term.person.field_cat_date_begin.yml | 2 +- ...axonomy_term.person.field_cat_date_end.yml | 2 +- ...age.taxonomy_term.field_cat_date_begin.yml | 2 +- ...orage.taxonomy_term.field_cat_date_end.yml | 2 +- ...onomy_term.field_corp_dissolution_date.yml | 2 +- ...taxonomy_term.field_corp_founding_date.yml | 2 +- src/EDTFConverter.php | 53 ++- .../Field/FieldFormatter/EDTFFormatter.php | 321 ++++++++++++++++++ .../FieldType/ExtendedDateTimeFormat.php | 21 ++ src/Plugin/Field/FieldWidget/EDTFWidget.php | 248 ++++++++++++++ .../Field/FieldWidget/TextDateWidget.php | 106 ------ 22 files changed, 701 insertions(+), 142 deletions(-) create mode 100644 src/Plugin/Field/FieldFormatter/EDTFFormatter.php create mode 100644 src/Plugin/Field/FieldType/ExtendedDateTimeFormat.php create mode 100644 src/Plugin/Field/FieldWidget/EDTFWidget.php delete mode 100644 src/Plugin/Field/FieldWidget/TextDateWidget.php diff --git a/controlled_access_terms.module b/controlled_access_terms.module index c31673a..930d150 100644 --- a/controlled_access_terms.module +++ b/controlled_access_terms.module @@ -7,6 +7,7 @@ use Drupal\jsonld\Normalizer\NormalizerBase; use Drupal\Core\Entity\EntityInterface; +use Drupal\controlled_access_terms\EDTFConverter; /** * Implements hook_rdf_namespaces(). @@ -15,6 +16,7 @@ function controlled_access_terms_rdf_namespaces() { return [ 'wgs84_pos' => 'http://www.w3.org/2003/01/geo/wgs84_pos#', 'org' => 'https://www.w3.org/TR/vocab-org/#org:', + 'xs' => 'http://www.w3.org/2001/XMLSchema#', ]; } @@ -24,15 +26,45 @@ function controlled_access_terms_rdf_namespaces() { function controlled_access_terms_jsonld_alter_normalized_array(EntityInterface $entity, array &$normalized, array $context) { if (isset($normalized['@graph']) && is_array($normalized['@graph'])) { foreach ($entity->getFieldDefinitions() as $field => $field_definition) { - if (($field_definition->getType() == 'typed_relation') && (!empty($entity->get($field)->getValue()))) { - foreach ($entity->get($field)->getValue() as $value) { - $predicate = NormalizerBase::escapePrefix($value['rel_type'], $context['namespaces']); - $referenced_entity = \Drupal::entityTypeManager()->getStorage($field_definition->getSetting('target_type'))->load($value['target_id']); - // We are assuming the first graph is the one corresponding - // to the node/taxonomy_term we are modifying. - $normalized['@graph'][0][$predicate][] = [ - '@id' => $referenced_entity->toUrl('canonical', ['absolute' => TRUE])->setRouteParameter('_format', 'jsonld')->toString(), - ]; + if (!empty($entity->get($field)->getValue())) { + if ($field_definition->getType() == 'typed_relation') { + foreach ($entity->get($field)->getValue() as $value) { + $predicate = NormalizerBase::escapePrefix($value['rel_type'], $context['namespaces']); + $referenced_entity = \Drupal::entityTypeManager()->getStorage($field_definition->getSetting('target_type'))->load($value['target_id']); + // We are assuming the first graph is the one corresponding + // to the node/taxonomy_term we are modifying. + $normalized['@graph'][0][$predicate][] = [ + '@id' => $referenced_entity->toUrl('canonical', ['absolute' => TRUE])->setRouteParameter('_format', 'jsonld')->toString(), + ]; + } + } + elseif ($field_definition->getType() == 'edtf') { + // Get the predicate to look in. + foreach ($context['current_entity_rdf_mapping']->get('fieldMappings')[$field]['properties'] as $predicate) { + // Find the predicate value that needs updating. + $predicate_normalized = NormalizerBase::escapePrefix($predicate, $context['namespaces']); + foreach ($normalized['@graph'][0][$predicate_normalized] as $index => $value) { + // Clean the date of ranges, uncertainty, and approximations. + // Have to remap the array from '@value' to 'value'. + $normalized_date = EDTFConverter::dateIso8601Value(['value' => $value['@value']]); + + // Determine which type to use. + $date_type = NormalizerBase::escapePrefix('xs:date', $context['namespaces']); + switch (count(explode('-', $normalized_date))) { + case 1: + $date_type = NormalizerBase::escapePrefix('xs:gYear', $context['namespaces']); + break; + + case 2: + $date_type = NormalizerBase::escapePrefix('xs:gYearMonth', $context['namespaces']); + break; + } + $normalized['@graph'][0][$predicate_normalized][] = [ + '@value' => $normalized_date, + '@type' => $date_type, + ]; + } + } } } } diff --git a/modules/controlled_access_terms_default_configuration/config/install/core.entity_form_display.taxonomy_term.corporate_body.default.yml b/modules/controlled_access_terms_default_configuration/config/install/core.entity_form_display.taxonomy_term.corporate_body.default.yml index cf81ea8..f9f59b4 100644 --- a/modules/controlled_access_terms_default_configuration/config/install/core.entity_form_display.taxonomy_term.corporate_body.default.yml +++ b/modules/controlled_access_terms_default_configuration/config/install/core.entity_form_display.taxonomy_term.corporate_body.default.yml @@ -40,7 +40,7 @@ content: strict_dates: false intervals: false third_party_settings: { } - type: text_edtf + type: edtf_default region: content field_cat_date_end: weight: 5 @@ -48,7 +48,7 @@ content: strict_dates: false intervals: false third_party_settings: { } - type: text_edtf + type: edtf_default region: content field_corp_alt_name: weight: 6 diff --git a/modules/controlled_access_terms_default_configuration/config/install/core.entity_form_display.taxonomy_term.family.default.yml b/modules/controlled_access_terms_default_configuration/config/install/core.entity_form_display.taxonomy_term.family.default.yml index caead34..c5ff0a9 100644 --- a/modules/controlled_access_terms_default_configuration/config/install/core.entity_form_display.taxonomy_term.family.default.yml +++ b/modules/controlled_access_terms_default_configuration/config/install/core.entity_form_display.taxonomy_term.family.default.yml @@ -38,7 +38,7 @@ content: strict_dates: false intervals: false third_party_settings: { } - type: text_edtf + type: edtf_default region: content field_cat_date_end: weight: 9 @@ -46,7 +46,7 @@ content: strict_dates: false intervals: false third_party_settings: { } - type: text_edtf + type: edtf_default region: content field_relationships: weight: 123 diff --git a/modules/controlled_access_terms_default_configuration/config/install/core.entity_form_display.taxonomy_term.person.default.yml b/modules/controlled_access_terms_default_configuration/config/install/core.entity_form_display.taxonomy_term.person.default.yml index 9d7b8b9..82689d7 100644 --- a/modules/controlled_access_terms_default_configuration/config/install/core.entity_form_display.taxonomy_term.person.default.yml +++ b/modules/controlled_access_terms_default_configuration/config/install/core.entity_form_display.taxonomy_term.person.default.yml @@ -42,7 +42,7 @@ content: strict_dates: false intervals: false third_party_settings: { } - type: text_edtf + type: edtf_default region: content field_cat_date_end: weight: 27 @@ -50,7 +50,7 @@ content: strict_dates: false intervals: false third_party_settings: { } - type: text_edtf + type: edtf_default region: content field_person_alternate_names: weight: 3 diff --git a/modules/controlled_access_terms_default_configuration/config/install/core.entity_view_display.taxonomy_term.corporate_body.default.yml b/modules/controlled_access_terms_default_configuration/config/install/core.entity_view_display.taxonomy_term.corporate_body.default.yml index 3e49437..3133cd5 100644 --- a/modules/controlled_access_terms_default_configuration/config/install/core.entity_view_display.taxonomy_term.corporate_body.default.yml +++ b/modules/controlled_access_terms_default_configuration/config/install/core.entity_view_display.taxonomy_term.corporate_body.default.yml @@ -47,7 +47,7 @@ content: day_format: dd season_hemisphere: north third_party_settings: { } - type: text_edtf_human + type: edtf_default region: content field_cat_date_end: weight: 3 @@ -59,7 +59,7 @@ content: day_format: dd season_hemisphere: north third_party_settings: { } - type: text_edtf_human + type: edtf_default region: content field_corp_alt_name: weight: 4 diff --git a/modules/controlled_access_terms_default_configuration/config/install/core.entity_view_display.taxonomy_term.family.default.yml b/modules/controlled_access_terms_default_configuration/config/install/core.entity_view_display.taxonomy_term.family.default.yml index 05f66fd..05d4f07 100644 --- a/modules/controlled_access_terms_default_configuration/config/install/core.entity_view_display.taxonomy_term.family.default.yml +++ b/modules/controlled_access_terms_default_configuration/config/install/core.entity_view_display.taxonomy_term.family.default.yml @@ -44,7 +44,7 @@ content: day_format: dd season_hemisphere: north third_party_settings: { } - type: text_edtf_human + type: edtf_default region: content field_cat_date_end: weight: 9 @@ -56,7 +56,7 @@ content: day_format: dd season_hemisphere: north third_party_settings: { } - type: text_edtf_human + type: edtf_default region: content field_relationships: weight: 11 diff --git a/modules/controlled_access_terms_default_configuration/config/install/core.entity_view_display.taxonomy_term.person.default.yml b/modules/controlled_access_terms_default_configuration/config/install/core.entity_view_display.taxonomy_term.person.default.yml index c53f09e..5f7245f 100644 --- a/modules/controlled_access_terms_default_configuration/config/install/core.entity_view_display.taxonomy_term.person.default.yml +++ b/modules/controlled_access_terms_default_configuration/config/install/core.entity_view_display.taxonomy_term.person.default.yml @@ -47,7 +47,7 @@ content: day_format: dd season_hemisphere: north third_party_settings: { } - type: text_edtf_human + type: edtf_default region: content field_cat_date_end: weight: 7 @@ -59,7 +59,7 @@ content: day_format: dd season_hemisphere: north third_party_settings: { } - type: text_edtf_human + type: edtf_default region: content field_person_alternate_names: weight: 3 diff --git a/modules/controlled_access_terms_default_configuration/config/install/field.field.taxonomy_term.corporate_body.field_cat_date_begin.yml b/modules/controlled_access_terms_default_configuration/config/install/field.field.taxonomy_term.corporate_body.field_cat_date_begin.yml index 556e6ce..723c616 100644 --- a/modules/controlled_access_terms_default_configuration/config/install/field.field.taxonomy_term.corporate_body.field_cat_date_begin.yml +++ b/modules/controlled_access_terms_default_configuration/config/install/field.field.taxonomy_term.corporate_body.field_cat_date_begin.yml @@ -15,4 +15,4 @@ translatable: false default_value: { } default_value_callback: '' settings: { } -field_type: string +field_type: edtf diff --git a/modules/controlled_access_terms_default_configuration/config/install/field.field.taxonomy_term.corporate_body.field_cat_date_end.yml b/modules/controlled_access_terms_default_configuration/config/install/field.field.taxonomy_term.corporate_body.field_cat_date_end.yml index f9cfe7c..cb67890 100644 --- a/modules/controlled_access_terms_default_configuration/config/install/field.field.taxonomy_term.corporate_body.field_cat_date_end.yml +++ b/modules/controlled_access_terms_default_configuration/config/install/field.field.taxonomy_term.corporate_body.field_cat_date_end.yml @@ -15,4 +15,4 @@ translatable: true default_value: { } default_value_callback: '' settings: { } -field_type: string +field_type: edtf diff --git a/modules/controlled_access_terms_default_configuration/config/install/field.field.taxonomy_term.family.field_cat_date_begin.yml b/modules/controlled_access_terms_default_configuration/config/install/field.field.taxonomy_term.family.field_cat_date_begin.yml index 2428ea4..58ab96f 100644 --- a/modules/controlled_access_terms_default_configuration/config/install/field.field.taxonomy_term.family.field_cat_date_begin.yml +++ b/modules/controlled_access_terms_default_configuration/config/install/field.field.taxonomy_term.family.field_cat_date_begin.yml @@ -15,4 +15,4 @@ translatable: false default_value: { } default_value_callback: '' settings: { } -field_type: string +field_type: edtf diff --git a/modules/controlled_access_terms_default_configuration/config/install/field.field.taxonomy_term.family.field_cat_date_end.yml b/modules/controlled_access_terms_default_configuration/config/install/field.field.taxonomy_term.family.field_cat_date_end.yml index b92fa70..2db627d 100644 --- a/modules/controlled_access_terms_default_configuration/config/install/field.field.taxonomy_term.family.field_cat_date_end.yml +++ b/modules/controlled_access_terms_default_configuration/config/install/field.field.taxonomy_term.family.field_cat_date_end.yml @@ -15,4 +15,4 @@ translatable: true default_value: { } default_value_callback: '' settings: { } -field_type: string +field_type: edtf diff --git a/modules/controlled_access_terms_default_configuration/config/install/field.field.taxonomy_term.person.field_cat_date_begin.yml b/modules/controlled_access_terms_default_configuration/config/install/field.field.taxonomy_term.person.field_cat_date_begin.yml index 3f576b7..eb1064c 100644 --- a/modules/controlled_access_terms_default_configuration/config/install/field.field.taxonomy_term.person.field_cat_date_begin.yml +++ b/modules/controlled_access_terms_default_configuration/config/install/field.field.taxonomy_term.person.field_cat_date_begin.yml @@ -15,4 +15,4 @@ translatable: false default_value: { } default_value_callback: '' settings: { } -field_type: string +field_type: edtf diff --git a/modules/controlled_access_terms_default_configuration/config/install/field.field.taxonomy_term.person.field_cat_date_end.yml b/modules/controlled_access_terms_default_configuration/config/install/field.field.taxonomy_term.person.field_cat_date_end.yml index 99a3201..ca4e79f 100644 --- a/modules/controlled_access_terms_default_configuration/config/install/field.field.taxonomy_term.person.field_cat_date_end.yml +++ b/modules/controlled_access_terms_default_configuration/config/install/field.field.taxonomy_term.person.field_cat_date_end.yml @@ -15,4 +15,4 @@ translatable: false default_value: { } default_value_callback: '' settings: { } -field_type: string +field_type: edtf diff --git a/modules/controlled_access_terms_default_configuration/config/install/field.storage.taxonomy_term.field_cat_date_begin.yml b/modules/controlled_access_terms_default_configuration/config/install/field.storage.taxonomy_term.field_cat_date_begin.yml index 751d041..d1545e5 100644 --- a/modules/controlled_access_terms_default_configuration/config/install/field.storage.taxonomy_term.field_cat_date_begin.yml +++ b/modules/controlled_access_terms_default_configuration/config/install/field.storage.taxonomy_term.field_cat_date_begin.yml @@ -6,7 +6,7 @@ dependencies: id: taxonomy_term.field_cat_date_begin field_name: field_cat_date_begin entity_type: taxonomy_term -type: string +type: edtf settings: max_length: 60 is_ascii: false diff --git a/modules/controlled_access_terms_default_configuration/config/install/field.storage.taxonomy_term.field_cat_date_end.yml b/modules/controlled_access_terms_default_configuration/config/install/field.storage.taxonomy_term.field_cat_date_end.yml index 4191c39..76cb22f 100644 --- a/modules/controlled_access_terms_default_configuration/config/install/field.storage.taxonomy_term.field_cat_date_end.yml +++ b/modules/controlled_access_terms_default_configuration/config/install/field.storage.taxonomy_term.field_cat_date_end.yml @@ -6,7 +6,7 @@ dependencies: id: taxonomy_term.field_cat_date_end field_name: field_cat_date_end entity_type: taxonomy_term -type: string +type: edtf settings: max_length: 60 is_ascii: false diff --git a/modules/controlled_access_terms_default_configuration/config/install/field.storage.taxonomy_term.field_corp_dissolution_date.yml b/modules/controlled_access_terms_default_configuration/config/install/field.storage.taxonomy_term.field_corp_dissolution_date.yml index 3eb84d0..ad8129f 100644 --- a/modules/controlled_access_terms_default_configuration/config/install/field.storage.taxonomy_term.field_corp_dissolution_date.yml +++ b/modules/controlled_access_terms_default_configuration/config/install/field.storage.taxonomy_term.field_corp_dissolution_date.yml @@ -6,7 +6,7 @@ dependencies: id: taxonomy_term.field_corp_dissolution_date field_name: field_corp_dissolution_date entity_type: taxonomy_term -type: string +type: edtf settings: max_length: 60 is_ascii: false diff --git a/modules/controlled_access_terms_default_configuration/config/install/field.storage.taxonomy_term.field_corp_founding_date.yml b/modules/controlled_access_terms_default_configuration/config/install/field.storage.taxonomy_term.field_corp_founding_date.yml index 55798c6..8f3a707 100644 --- a/modules/controlled_access_terms_default_configuration/config/install/field.storage.taxonomy_term.field_corp_founding_date.yml +++ b/modules/controlled_access_terms_default_configuration/config/install/field.storage.taxonomy_term.field_corp_founding_date.yml @@ -6,7 +6,7 @@ dependencies: id: taxonomy_term.field_corp_founding_date field_name: field_corp_founding_date entity_type: taxonomy_term -type: string +type: edtf settings: max_length: 60 is_ascii: false diff --git a/src/EDTFConverter.php b/src/EDTFConverter.php index a1fd726..d3a91d1 100644 --- a/src/EDTFConverter.php +++ b/src/EDTFConverter.php @@ -54,7 +54,7 @@ class EDTFConverter extends CommonDataConverter { * @return string * Returns the ISO 8601 timestamp. */ - public static function dateIso8601Value(array $data) { + public static function datetimeIso8601Value($data) { $date = explode('/', $data['value'])[0]; // Strip approximations/uncertainty. @@ -67,13 +67,56 @@ public static function dateIso8601Value(array $data) { $date = str_replace('u', '0', $date); // Seasons map. - list($year, $month, $day) = explode('-', $date, 3); + return EDTFConverter::seasonsMap($date) . 'T00:00:00'; + + } + + /** + * Converts an EDTF text field into an ISO 8601 timestamp string. + * + * It assumes the earliest valid date for approximations and intervals. + * + * @param array $data + * The array containing the 'value' element. + * + * @return string + * Returns the ISO 8601 date. + */ + public static function dateIso8601Value($data) { + $date = explode('/', $data['value'])[0]; + + // Strip approximations/uncertainty. + $date = str_replace(['?', '~'], '', $date); + + // Remove unspecified. + // Month/day. + $date = str_replace('-uu', '', $date); + // Zero-Year in decade/century. + $date = str_replace('u', '0', $date); + + // Seasons map. + return EDTFConverter::seasonsMap($date); + + } + + /** + * Converts a numeric season into a numeric month. + * + * @param string $date + * The date string to convert. + * + * @return string + * Returns the ISO 8601 date with the correct month. + */ + protected static function seasonsMap(string $date) { + $date_parts[] = explode('-', $date, 3); // Digit Seasons. - if (in_array($month, ['21', '22', '23', '24'])) { + if ((count($date_parts) > 1) && + in_array($date_parts[1], ['21', '22', '23', '24'])) { // TODO: Make hemisphere seasons configurable. $season_mapping = $seasonMapNorth; - $month = $season_mapping[$month]; - $date = implode('-', array_filter([$year, $month, $day])); + $date_parts[1] = $season_mapping[$date_parts[1]]; + $date = implode('-', array_filter($date_parts)); } return $date; diff --git a/src/Plugin/Field/FieldFormatter/EDTFFormatter.php b/src/Plugin/Field/FieldFormatter/EDTFFormatter.php new file mode 100644 index 0000000..06a7612 --- /dev/null +++ b/src/Plugin/Field/FieldFormatter/EDTFFormatter.php @@ -0,0 +1,321 @@ + ['mmm' => 'Jan', 'mmmm' => 'January'], + '02' => ['mmm' => 'Feb', 'mmmm' => 'February'], + '03' => ['mmm' => 'Mar', 'mmmm' => 'March'], + '04' => ['mmm' => 'Apr', 'mmmm' => 'April'], + '05' => ['mmm' => 'May', 'mmmm' => 'May'], + '06' => ['mmm' => 'Jun', 'mmmm' => 'June'], + '07' => ['mmm' => 'Jul', 'mmmm' => 'July'], + '08' => ['mmm' => 'Aug', 'mmmm' => 'August'], + '09' => ['mmm' => 'Sep', 'mmmm' => 'September'], + '10' => ['mmm' => 'Oct', 'mmmm' => 'October'], + '11' => ['mmm' => 'Nov', 'mmmm' => 'November'], + '12' => ['mmm' => 'Dec', 'mmmm' => 'December'], + '21' => ['mmm' => 'Spr', 'mmmm' => 'Spring'], + '22' => ['mmm' => 'Sum', 'mmmm' => 'Summer'], + '23' => ['mmm' => 'Aut', 'mmmm' => 'Autumn'], + '24' => ['mmm' => 'Win', 'mmmm' => 'Winter'], + ]; + + /** + * Various delimiters. + * + * @var array + */ + private $DELIMITERS = [ + 'dash' => '-', + 'stroke' => '/', + 'period' => '.', + 'space' => ' ', + ]; + + /** + * Northern hemisphere season map. + * + * @var array + */ + private $seasonMapNorth = [ + // Spring => March. + '21' => '03', + // Summer => June. + '22' => '06', + // Autumn => September. + '23' => '09', + // Winter => December. + '24' => '12', + ]; + + /** + * Southern hemisphere season map. + * + * @var array + */ + private $seasonMapSouth = [ + // Spring => September. + '21' => '03', + // Summer => December. + '22' => '06', + // Autumn => March. + '23' => '09', + // Winter => June. + '24' => '12', + ]; + + /** + * {@inheritdoc} + */ + public static function defaultSettings() { + return [ + // ISO 8601 bias. + 'date_separator' => 'dash', + // ISO 8601 bias. + 'date_order' => 'big_endian', + // ISO 8601 bias. + 'month_format' => 'mm', + // ISO 8601 bias. + 'day_format' => 'dd', + // Northern bias, sorry. + 'season_hemisphere' => 'north', + ] + parent::defaultSettings(); + } + + /** + * {@inheritdoc} + */ + public function settingsForm(array $form, FormStateInterface $form_state) { + $form['date_separator'] = [ + '#title' => t('Date Separator'), + '#type' => 'select', + '#description' => "Select the separator between date elements.", + '#default_value' => $this->getSetting('date_separator'), + '#options' => [ + 'dash' => t("Dash '-'"), + 'stroke' => t("Stroke '\'"), + 'period' => t("Period '.'"), + 'space' => t("Space ' '"), + ], + ]; + $form['date_order'] = [ + '#title' => t('Date Order'), + '#type' => 'select', + '#description' => "Select the separator between date elements.", + '#default_value' => $this->getSetting('date_order'), + '#options' => [ + 'big_endian' => t('Big-endian (year, month, day)'), + 'little_endian' => t('Little-endian (day, month, year)'), + 'middle_endian' => t('Middle-endian (month, day, year)'), + ], + ]; + $form['month_format'] = [ + '#title' => t('Month Format'), + '#type' => 'select', + '#default_value' => $this->getSetting('month_format'), + '#options' => [ + 'mm' => t('two-digit month, e.g. 04'), + 'm' => t('one-digit month for months below 10, e.g. 4'), + 'mmm' => t('three-letter abbreviation for month, Apr'), + 'mmmm' => t('month spelled out in full, e.g. April'), + ], + ]; + $form['day_format'] = [ + '#title' => t('Day Format'), + '#type' => 'select', + '#default_value' => $this->getSetting('day_format'), + '#options' => [ + 'dd' => t('two-digit day of the month, e.g. 02'), + 'd' => t('one-digit day of the month for days below 10, e.g. 2'), + ], + ]; + $form['season_hemisphere'] = [ + '#title' => t('Hemisphere Seasons'), + '#type' => 'select', + '#default_value' => $this->getSetting('season_hemisphere'), + '#description' => t("Seasons don't have digit months so we map them + to their respective equinox and solstice months. + Select a hemisphere to use for the mapping."), + '#options' => [ + 'north' => t('Northern Hemisphere'), + 'south' => t('Southern Hemisphere'), + ], + ]; + return $form; + } + + /** + * {@inheritdoc} + */ + public function settingsSummary() { + $summary = []; + $example_date = $this->formatDate('1996-04-22'); + $summary[] = t('Date Format Example: @date', ['@date' => $example_date]); + return $summary; + } + + /** + * {@inheritdoc} + */ + public function viewElements(FieldItemListInterface $items, $langcode) { + $element = []; + + foreach ($items as $delta => $item) { + // Interval. + list($begin, $end) = explode('/', $item->value); + + $formatted_begin = $this->formatDate($begin); + + // End either empty or valid extended interval values (5.2.3.) + if (empty($end)) { + $element[$delta] = ['#markup' => $formatted_begin]; + } + elseif ($end === 'unknown' || $end === 'open') { + $element[$delta] = [ + '#markup' => t('@begin to @end', [ + '@begin' => $formatted_begin, + '@end' => $end, + ]), + ]; + } + else { + $formatted_end = $this->formatDate($end); + $element[$delta] = [ + '#markup' => t('@begin to @end', [ + '@begin' => $formatted_begin, + '@end' => $formatted_end, + ]), + ]; + } + + } + return $element; + } + + /** + * Create a date format string. + * + * @param string $edtf_text + * The date to format. + * + * @return string + * The date in EDTF format. + */ + protected function formatDate($edtf_text) { + $settings = $this->getSettings(); + $cleaned_datetime = $edtf_text; + // TODO: Time? + $qualifiers_format = '%s'; + // Uncertainty. + if (!(strpos($edtf_text, '~') === FALSE)) { + $qualifiers_format = t('approximately'); + $qualifiers_format .= ' %s'; + } + if (!(strpos($edtf_text, '?') === FALSE)) { + $qualifiers_format = '%s '; + $qualifiers_format .= t('(uncertain)'); + } + $cleaned_datetime = str_replace(['?', '~'], '', $cleaned_datetime); + + list($year, $month, $day) = explode('-', $cleaned_datetime, 3); + + // Which unspecified, if any? + $which_unspecified = ''; + if (!(strpos($year, 'uu') === FALSE)) { + $which_unspecified = t('decade'); + } + if (!(strpos($year, 'u') === FALSE)) { + $which_unspecified = t('year'); + } + if (!(strpos($month, 'u') === FALSE)) { + $which_unspecified = t('month'); + // No partial months. + $month = ''; + } + if (!(strpos($day, 'u') === FALSE)) { + $which_unspecified = t('day'); + // No partial days. + $day = ''; + } + // Add unspecified formatting if needed. + if (!empty($which_unspecified)) { + $qualifiers_format = t('an unspecified @part in', ['@part' => $which_unspecified]) . ' ' . $qualifiers_format; + } + + // Clean-up unspecified year/decade. + if (!(strpos($year, 'u') === FALSE)) { + $year = str_replace('u', '0', $year); + $year = t("the @year's", ['@year' => $year]); + } + + // Format the month. + if (!empty($month)) { + // IF 'mm', do nothing, it is already in this format. + if ($settings['month_format'] === 'mmm' || $settings['month_format'] === 'mmmm') { + $month = $this->MONTHS[$month][$settings['month_format']]; + } + // Digit Seasons. + elseif (in_array($month, ['21', '22', '23', '24'])) { + $season_mapping = ($settings['season_hemisphere'] === 'north' ? $this->seasonMapNorth : $this->seasonMapSouth); + $month = $season_mapping[$month]; + } + + if ($settings['month_format'] === 'm') { + $month = ltrim($month, ' 0'); + } + } + + // Format the day. + if (!empty($day)) { + if ($settings['day_format'] === 'd') { + $day = ltrim($day, ' 0'); + } + } + + // Put the parts back together + // Big Endian by default. + $parts_in_order = [$year, $month, $day]; + + if ($settings['date_order'] === 'little_endian') { + $parts_in_order = [$day, $month, $year]; + } + elseif ($settings['date_order'] === 'middle_endian') { + $parts_in_order = [$month, $day, $year]; + } // Big Endian by default + + if ($settings['date_order'] === 'middle_endian' && !preg_match('/\d/', $month) && !empty(array_filter([$month, $day]))) { + $cleaned_datetime = "$month $day, $year"; + } + else { + $cleaned_datetime = implode($this->DELIMITERS[$settings['date_separator']], array_filter($parts_in_order)); + } + + return sprintf($qualifiers_format, $cleaned_datetime); + } + +} diff --git a/src/Plugin/Field/FieldType/ExtendedDateTimeFormat.php b/src/Plugin/Field/FieldType/ExtendedDateTimeFormat.php new file mode 100644 index 0000000..900e4d2 --- /dev/null +++ b/src/Plugin/Field/FieldType/ExtendedDateTimeFormat.php @@ -0,0 +1,21 @@ + FALSE, + 'intervals' => FALSE, + ] + parent::defaultSettings(); + } + + /** + * {@inheritdoc} + */ + public function settingsForm(array $form, FormStateInterface $form_state) { + $description_string = $this->t( + 'Negative dates, and the level 1 features unspecified dates, + extended years, and seasons + are not supported with strict date checking.' + ); + $description_string .= '
'; + $description_string .= $this->t( + 'Uncertain/Approximate dates will have their markers removed before + checking. (For example, "1984~?" will be checked as "1984".)' + ); + $element = parent::settingsForm($form, $form_state); + $element['description'] = [ + '#type' => 'markup', + '#prefix' => '
', + '#suffix' => '
', + '#markup' => $this->t('See Library of Congress EDTF Draft Submission for details on formatting options.', ['@locedtf' => 'http://www.loc.gov/standards/datetime/pre-submission.html']), + ]; + $element['strict_dates'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Ensure provided date values are valid.'), + '#description' => $description_string, + '#default_value' => $this->getSetting('strict_dates'), + ]; + $element['intervals'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Permit date intervals.'), + '#default_value' => $this->getSetting('intervals'), + ]; + return $element; + } + + /** + * {@inheritdoc} + */ + public function settingsSummary() { + $summary = []; + + if ($this->getSetting('strict_dates')) { + $summary[] = t('Strict dates enabled'); + } + if ($this->getSetting('intervals')) { + $summary[] = t('Date intervals permitted'); + } + else { + $summary[] = t('Date intervals are not permitted'); + } + + return $summary; + } + + /** + * {@inheritdoc} + */ + public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) { + $element['value'] = $element + [ + '#type' => 'textfield', + '#default_value' => isset($items[$delta]->value) ? $items[$delta]->value : NULL, + '#placeholder' => $this + ->getSetting('placeholder'), + '#element_validate' => [ + [$this, 'validate'], + ], + ]; + return $element; + } + + /** + * Validate date format compliance. + */ + public function validate($element, FormStateInterface $form_state) { + // Accept a blank (empty) value. + $value = $element['#value']; + if (strlen($value) == 0) { + $form_state->setValueForElement($element, ''); + return; + } + + // Intervals. + if ($this->getSetting('intervals')) { + if (strpos($value, 'T') !== FALSE) { + $form_state->setError($element, t("Date intervals cannot include times.")); + } + + list($begin, $end) = explode('/', $value); + // Begin. + $error_message = $this->dateValidation($begin); + if ($error_message) { + $form_state->setError($element, $error_message); + } + // End either empty or valid extended interval values (5.2.3.) + if (empty($end) || $end === 'unknown' || $end === 'open') { + return; + } + $error_message = $this->dateValidation($end); + if ($error_message) { + $form_state->setError($element, $error_message); + } + } + else { + $error_message = $this->dateValidation($value); + if ($error_message) { + $form_state->setError($element, $error_message); + } + } + } + + /** + * Validate a date. + * + * @param string $datetime_str + * The datetime string. + * + * @return bool|string + * False if valid or a string explaining the reason for invalidation. + */ + protected function dateValidation($datetime_str) { + + list($date, $time) = explode('T', $datetime_str); + + $date = trim($date); + $extended_year = (strpos($date, 'y') === 0 ? TRUE : FALSE); + if ($extended_year && $this->getSetting('strict_dates')) { + return "Extended years (5.2.4.) are not supported with the 'strict dates' option enabled."; + } + // Uncertainty characters on the end are valid Level 1 features (5.2.1.), + // pull them off to make checking the rest easier. + $date = rtrim($date, '?~'); + + // Negative year? That is fine, but remove it + // and the extended year indicator before exploding the date. + $date = ltrim($date, 'y-'); + + // Now to check the parts. + list($year, $month, $day) = explode('-', $date, 3); + + // Year. + if (!preg_match('/^\d\d(\d\d|\du|uu)$/', $year) && !$extended_year) { + return "The year '$year' is invalid. Please enter a four-digit year."; + } + elseif ($extended_year && !preg_match('/^\d{5,}$/', $year)) { + return "Invalid extended year. Please enter at least a four-digit year."; + } + $strict_pattern = 'Y'; + + // Month. + if (!empty($month) && !preg_match('/^(\d\d|\du|uu)$/', $month)) { + return "The month '$month' is invalid. Please enter a two-digit month."; + } + if (!empty($month)) { + if (strpos($year, 'u') !== FALSE && strpos($month, 'u') === FALSE) { + return "The month must either be blank or unspecified when the year is unspecified."; + } + $strict_pattern = 'Y-m'; + } + + // Day. + if (!empty($day) && !preg_match('/^(\d\d|\du|uu)$/', $day)) { + return "The day '$day' is invalid. Please enter a two-digit day."; + } + if (!empty($day)) { + if (strpos($month, 'u') !== FALSE && strpos($day, 'u') === FALSE) { + return "The day must either be blank or unspecified when the month is unspecified."; + } + $strict_pattern = 'Y-m-d'; + } + + // Time. + if (strpos($datetime_str, 'T') !== FALSE && empty($time)) { + return "Time not provided with time seperator (T)."; + } + + if ($time) { + if (!preg_match('/^-?(\d{4})(-\d{2}){2}T\d{2}(:\d{2}){2}(Z|(\+|-)\d{2}:\d{2})?$/', $datetime_str, $matches)) { + return "The date/time '$datetime_str' is invalid. See EDTF 1.0, 5.1.2."; + } + drupal_set_message(print_r($matches, TRUE)); + $strict_pattern = 'Y-m-d\TH:i:s'; + if (count($matches) > 4) { + if ($matches[4] === 'Z') { + $strict_pattern .= '\Z'; + } + else { + $strict_pattern .= 'P'; + } + } + } + + if ($this->getSetting('strict_dates')) { + // Clean the date/time string to ensure it parses correctly. + $cleaned_datetime = str_replace('u', '1', $datetime_str); + $datetime_obj = DateTime::createFromFormat('!' . $strict_pattern, $cleaned_datetime); + $errors = DateTime::getLastErrors(); + if (!$datetime_obj || + !empty($errors['warning_count']) || + // DateTime will create valid dates from Y-m without warning, + // so validate we still have what it was given. + !($cleaned_datetime === $datetime_obj->format($strict_pattern)) + ) { + return "Strictly speaking, the date (and/or time) '$datetime_str' is invalid."; + } + + } + + return FALSE; + } + +} diff --git a/src/Plugin/Field/FieldWidget/TextDateWidget.php b/src/Plugin/Field/FieldWidget/TextDateWidget.php deleted file mode 100644 index fe1ab5e..0000000 --- a/src/Plugin/Field/FieldWidget/TextDateWidget.php +++ /dev/null @@ -1,106 +0,0 @@ - 'Y-m-d', - 'strict_dates' => TRUE, - ] + parent::defaultSettings(); - } - - /** - * {@inheritdoc} - */ - public function settingsForm(array $form, FormStateInterface $form_state) { - $element = parent::settingsForm($form, $form_state); - $element['date_format'] = [ - '#type' => 'textfield', - '#title' => $this->t('PHP DateTime Format String.'), - '#description' => $this->t('See PHP DateTime Documentation for details.', ['@phpdate' => 'http://php.net/manual/en/datetime.createfromformat.php']), - '#default_value' => $this->getSetting('date_format'), - ]; - $element['strict_dates'] = [ - '#type' => 'checkbox', - '#title' => $this->t('Ensure date values are valid.'), - '#default_value' => $this->getSetting('strict_dates'), - ]; - return $element; - } - - /** - * {@inheritdoc} - */ - public function settingsSummary() { - $summary = []; - - $summary[] = t('Date Format: @format', ['@format' => $this->getSetting('date_format')]); - - if ($this->getSetting('strict_dates')) { - $summary[] = t('Strict dates enabled'); - } - - return $summary; - } - - /** - * {@inheritdoc} - */ - public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) { - $element['value'] = $element + [ - '#type' => 'textfield', - '#default_value' => isset($items[$delta]->value) ? $items[$delta]->value : NULL, - '#placeholder' => $this - ->getSetting('placeholder'), - '#element_validate' => [ - [$this, 'validate'], - ], - ]; - return $element; - } - - /** - * Validate date format compliance. - */ - public function validate($element, FormStateInterface $form_state) { - $value = $element['#value']; - if (strlen($value) == 0) { - $form_state->setValueForElement($element, ''); - return; - } - $date_format = $this->getSetting('date_format'); - $date = DateTime::createFromFormat($date_format, $value); - if (!$date) { - $form_state->setError($element, t("Date must match the pattern @format", - ['@format' => $date_format])); - } - $errors = DateTime::getLastErrors(); - if ($this->getSetting('strict_dates') && !empty($errors['warning_count'])) { - $form_state->setError($element, t('Strictly speaking, the date "@value" is invalid!', - ['@value' => $value])); - } - } - -} From b369931756db1bd2750270f0f6c202be9b654187 Mon Sep 17 00:00:00 2001 From: Seth Shaw Date: Fri, 4 Jan 2019 13:34:30 -0800 Subject: [PATCH 2/8] add type hints --- src/EDTFConverter.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/EDTFConverter.php b/src/EDTFConverter.php index d3a91d1..85a278e 100644 --- a/src/EDTFConverter.php +++ b/src/EDTFConverter.php @@ -54,7 +54,7 @@ class EDTFConverter extends CommonDataConverter { * @return string * Returns the ISO 8601 timestamp. */ - public static function datetimeIso8601Value($data) { + public static function datetimeIso8601Value(array $data) { $date = explode('/', $data['value'])[0]; // Strip approximations/uncertainty. @@ -82,7 +82,7 @@ public static function datetimeIso8601Value($data) { * @return string * Returns the ISO 8601 date. */ - public static function dateIso8601Value($data) { + public static function dateIso8601Value(array $data) { $date = explode('/', $data['value'])[0]; // Strip approximations/uncertainty. From 66f4f9db0deeb8d1b1aefe6f4cbda760e6b5d170 Mon Sep 17 00:00:00 2001 From: Seth Shaw Date: Fri, 4 Jan 2019 14:43:42 -0800 Subject: [PATCH 3/8] make phpcpd happy --- .../FieldFormatter/TextEDTFHumanFormatter.php | 303 +----------------- .../Field/FieldWidget/TextEDTFWidget.php | 227 +------------ 2 files changed, 2 insertions(+), 528 deletions(-) diff --git a/src/Plugin/Field/FieldFormatter/TextEDTFHumanFormatter.php b/src/Plugin/Field/FieldFormatter/TextEDTFHumanFormatter.php index f6a4f5b..c7d396b 100644 --- a/src/Plugin/Field/FieldFormatter/TextEDTFHumanFormatter.php +++ b/src/Plugin/Field/FieldFormatter/TextEDTFHumanFormatter.php @@ -2,10 +2,6 @@ namespace Drupal\controlled_access_terms\Plugin\Field\FieldFormatter; -use Drupal\Core\Field\FormatterBase; -use Drupal\Core\Field\FieldItemListInterface; -use Drupal\Core\Form\FormStateInterface; - /** * Plugin implementation of the 'TextEDTFHumanFormatter'. * @@ -19,303 +15,6 @@ * } * ) */ -class TextEDTFHumanFormatter extends FormatterBase { - - /** - * Month/Season to text map. - * - * @var array - */ - private $MONTHS = [ - '01' => ['mmm' => 'Jan', 'mmmm' => 'January'], - '02' => ['mmm' => 'Feb', 'mmmm' => 'February'], - '03' => ['mmm' => 'Mar', 'mmmm' => 'March'], - '04' => ['mmm' => 'Apr', 'mmmm' => 'April'], - '05' => ['mmm' => 'May', 'mmmm' => 'May'], - '06' => ['mmm' => 'Jun', 'mmmm' => 'June'], - '07' => ['mmm' => 'Jul', 'mmmm' => 'July'], - '08' => ['mmm' => 'Aug', 'mmmm' => 'August'], - '09' => ['mmm' => 'Sep', 'mmmm' => 'September'], - '10' => ['mmm' => 'Oct', 'mmmm' => 'October'], - '11' => ['mmm' => 'Nov', 'mmmm' => 'November'], - '12' => ['mmm' => 'Dec', 'mmmm' => 'December'], - '21' => ['mmm' => 'Spr', 'mmmm' => 'Spring'], - '22' => ['mmm' => 'Sum', 'mmmm' => 'Summer'], - '23' => ['mmm' => 'Aut', 'mmmm' => 'Autumn'], - '24' => ['mmm' => 'Win', 'mmmm' => 'Winter'], - ]; - - /** - * Various delimiters. - * - * @var array - */ - private $DELIMITERS = [ - 'dash' => '-', - 'stroke' => '/', - 'period' => '.', - 'space' => ' ', - ]; - - /** - * Northern hemisphere season map. - * - * @var array - */ - private $seasonMapNorth = [ - // Spring => March. - '21' => '03', - // Summer => June. - '22' => '06', - // Autumn => September. - '23' => '09', - // Winter => December. - '24' => '12', - ]; - - /** - * Southern hemisphere season map. - * - * @var array - */ - private $seasonMapSouth = [ - // Spring => September. - '21' => '03', - // Summer => December. - '22' => '06', - // Autumn => March. - '23' => '09', - // Winter => June. - '24' => '12', - ]; - - /** - * {@inheritdoc} - */ - public static function defaultSettings() { - return [ - // ISO 8601 bias. - 'date_separator' => 'dash', - // ISO 8601 bias. - 'date_order' => 'big_endian', - // ISO 8601 bias. - 'month_format' => 'mm', - // ISO 8601 bias. - 'day_format' => 'dd', - // Northern bias, sorry. - 'season_hemisphere' => 'north', - ] + parent::defaultSettings(); - } - - /** - * {@inheritdoc} - */ - public function settingsForm(array $form, FormStateInterface $form_state) { - $form['date_separator'] = [ - '#title' => t('Date Separator'), - '#type' => 'select', - '#description' => "Select the separator between date elements.", - '#default_value' => $this->getSetting('date_separator'), - '#options' => [ - 'dash' => t("Dash '-'"), - 'stroke' => t("Stroke '\'"), - 'period' => t("Period '.'"), - 'space' => t("Space ' '"), - ], - ]; - $form['date_order'] = [ - '#title' => t('Date Order'), - '#type' => 'select', - '#description' => "Select the separator between date elements.", - '#default_value' => $this->getSetting('date_order'), - '#options' => [ - 'big_endian' => t('Big-endian (year, month, day)'), - 'little_endian' => t('Little-endian (day, month, year)'), - 'middle_endian' => t('Middle-endian (month, day, year)'), - ], - ]; - $form['month_format'] = [ - '#title' => t('Month Format'), - '#type' => 'select', - '#default_value' => $this->getSetting('month_format'), - '#options' => [ - 'mm' => t('two-digit month, e.g. 04'), - 'm' => t('one-digit month for months below 10, e.g. 4'), - 'mmm' => t('three-letter abbreviation for month, Apr'), - 'mmmm' => t('month spelled out in full, e.g. April'), - ], - ]; - $form['day_format'] = [ - '#title' => t('Day Format'), - '#type' => 'select', - '#default_value' => $this->getSetting('day_format'), - '#options' => [ - 'dd' => t('two-digit day of the month, e.g. 02'), - 'd' => t('one-digit day of the month for days below 10, e.g. 2'), - ], - ]; - $form['season_hemisphere'] = [ - '#title' => t('Hemisphere Seasons'), - '#type' => 'select', - '#default_value' => $this->getSetting('season_hemisphere'), - '#description' => t("Seasons don't have digit months so we map them - to their respective equinox and solstice months. - Select a hemisphere to use for the mapping."), - '#options' => [ - 'north' => t('Northern Hemisphere'), - 'south' => t('Southern Hemisphere'), - ], - ]; - return $form; - } - - /** - * {@inheritdoc} - */ - public function settingsSummary() { - $summary = []; - $example_date = $this->formatDate('1996-04-22'); - $summary[] = t('Date Format Example: @date', ['@date' => $example_date]); - return $summary; - } - - /** - * {@inheritdoc} - */ - public function viewElements(FieldItemListInterface $items, $langcode) { - $element = []; - - foreach ($items as $delta => $item) { - // Interval. - list($begin, $end) = explode('/', $item->value); - - $formatted_begin = $this->formatDate($begin); - - // End either empty or valid extended interval values (5.2.3.) - if (empty($end)) { - $element[$delta] = ['#markup' => $formatted_begin]; - } - elseif ($end === 'unknown' || $end === 'open') { - $element[$delta] = [ - '#markup' => t('@begin to @end', [ - '@begin' => $formatted_begin, - '@end' => $end, - ]), - ]; - } - else { - $formatted_end = $this->formatDate($end); - $element[$delta] = [ - '#markup' => t('@begin to @end', [ - '@begin' => $formatted_begin, - '@end' => $formatted_end, - ]), - ]; - } - - } - return $element; - } - - /** - * Create a date format string. - * - * @param string $edtf_text - * The date to format. - * - * @return string - * The date in EDTF format. - */ - protected function formatDate($edtf_text) { - $settings = $this->getSettings(); - $cleaned_datetime = $edtf_text; - // TODO: Time? - $qualifiers_format = '%s'; - // Uncertainty. - if (!(strpos($edtf_text, '~') === FALSE)) { - $qualifiers_format = t('approximately'); - $qualifiers_format .= ' %s'; - } - if (!(strpos($edtf_text, '?') === FALSE)) { - $qualifiers_format = '%s '; - $qualifiers_format .= t('(uncertain)'); - } - $cleaned_datetime = str_replace(['?', '~'], '', $cleaned_datetime); - - list($year, $month, $day) = explode('-', $cleaned_datetime, 3); - - // Which unspecified, if any? - $which_unspecified = ''; - if (!(strpos($year, 'uu') === FALSE)) { - $which_unspecified = t('decade'); - } - if (!(strpos($year, 'u') === FALSE)) { - $which_unspecified = t('year'); - } - if (!(strpos($month, 'u') === FALSE)) { - $which_unspecified = t('month'); - // No partial months. - $month = ''; - } - if (!(strpos($day, 'u') === FALSE)) { - $which_unspecified = t('day'); - // No partial days. - $day = ''; - } - // Add unspecified formatting if needed. - if (!empty($which_unspecified)) { - $qualifiers_format = t('an unspecified @part in', ['@part' => $which_unspecified]) . ' ' . $qualifiers_format; - } - - // Clean-up unspecified year/decade. - if (!(strpos($year, 'u') === FALSE)) { - $year = str_replace('u', '0', $year); - $year = t("the @year's", ['@year' => $year]); - } - - // Format the month. - if (!empty($month)) { - // IF 'mm', do nothing, it is already in this format. - if ($settings['month_format'] === 'mmm' || $settings['month_format'] === 'mmmm') { - $month = $this->MONTHS[$month][$settings['month_format']]; - } - // Digit Seasons. - elseif (in_array($month, ['21', '22', '23', '24'])) { - $season_mapping = ($settings['season_hemisphere'] === 'north' ? $this->seasonMapNorth : $this->seasonMapSouth); - $month = $season_mapping[$month]; - } - - if ($settings['month_format'] === 'm') { - $month = ltrim($month, ' 0'); - } - } - - // Format the day. - if (!empty($day)) { - if ($settings['day_format'] === 'd') { - $day = ltrim($day, ' 0'); - } - } - - // Put the parts back together - // Big Endian by default. - $parts_in_order = [$year, $month, $day]; - - if ($settings['date_order'] === 'little_endian') { - $parts_in_order = [$day, $month, $year]; - } - elseif ($settings['date_order'] === 'middle_endian') { - $parts_in_order = [$month, $day, $year]; - } // Big Endian by default - - if ($settings['date_order'] === 'middle_endian' && !preg_match('/\d/', $month) && !empty(array_filter([$month, $day]))) { - $cleaned_datetime = "$month $day, $year"; - } - else { - $cleaned_datetime = implode($this->DELIMITERS[$settings['date_separator']], array_filter($parts_in_order)); - } - - return sprintf($qualifiers_format, $cleaned_datetime); - } +class TextEDTFHumanFormatter extends EDTFFormatter { } diff --git a/src/Plugin/Field/FieldWidget/TextEDTFWidget.php b/src/Plugin/Field/FieldWidget/TextEDTFWidget.php index 905f9f9..5e1d9de 100644 --- a/src/Plugin/Field/FieldWidget/TextEDTFWidget.php +++ b/src/Plugin/Field/FieldWidget/TextEDTFWidget.php @@ -2,11 +2,6 @@ namespace Drupal\controlled_access_terms\Plugin\Field\FieldWidget; -use Datetime; -use Drupal\Core\Field\FieldItemListInterface; -use Drupal\Core\Field\WidgetBase; -use Drupal\Core\Form\FormStateInterface; - /** * Plugin implementation of the 'text_edtf' widget. * @@ -23,226 +18,6 @@ * } * ) */ -class TextEDTFWidget extends WidgetBase { - - /** - * {@inheritdoc} - */ - public static function defaultSettings() { - return [ - 'strict_dates' => FALSE, - 'intervals' => FALSE, - ] + parent::defaultSettings(); - } - - /** - * {@inheritdoc} - */ - public function settingsForm(array $form, FormStateInterface $form_state) { - $description_string = $this->t( - 'Negative dates, and the level 1 features unspecified dates, - extended years, and seasons - are not supported with strict date checking.' - ); - $description_string .= '
'; - $description_string .= $this->t( - 'Uncertain/Approximate dates will have their markers removed before - checking. (For example, "1984~?" will be checked as "1984".)' - ); - $element = parent::settingsForm($form, $form_state); - $element['description'] = [ - '#type' => 'markup', - '#prefix' => '
', - '#suffix' => '
', - '#markup' => $this->t('See Library of Congress EDTF Draft Submission for details on formatting options.', ['@locedtf' => 'http://www.loc.gov/standards/datetime/pre-submission.html']), - ]; - $element['strict_dates'] = [ - '#type' => 'checkbox', - '#title' => $this->t('Ensure provided date values are valid.'), - '#description' => $description_string, - '#default_value' => $this->getSetting('strict_dates'), - ]; - $element['intervals'] = [ - '#type' => 'checkbox', - '#title' => $this->t('Permit date intervals.'), - '#default_value' => $this->getSetting('intervals'), - ]; - return $element; - } - - /** - * {@inheritdoc} - */ - public function settingsSummary() { - $summary = []; - - if ($this->getSetting('strict_dates')) { - $summary[] = t('Strict dates enabled'); - } - if ($this->getSetting('intervals')) { - $summary[] = t('Date intervals permitted'); - } - else { - $summary[] = t('Date intervals are not permitted'); - } - - return $summary; - } - - /** - * {@inheritdoc} - */ - public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) { - $element['value'] = $element + [ - '#type' => 'textfield', - '#default_value' => isset($items[$delta]->value) ? $items[$delta]->value : NULL, - '#placeholder' => $this - ->getSetting('placeholder'), - '#element_validate' => [ - [$this, 'validate'], - ], - ]; - return $element; - } - - /** - * Validate date format compliance. - */ - public function validate($element, FormStateInterface $form_state) { - // Accept a blank (empty) value. - $value = $element['#value']; - if (strlen($value) == 0) { - $form_state->setValueForElement($element, ''); - return; - } - - // Intervals. - if ($this->getSetting('intervals')) { - if (strpos($value, 'T') !== FALSE) { - $form_state->setError($element, t("Date intervals cannot include times.")); - } - - list($begin, $end) = explode('/', $value); - // Begin. - $error_message = $this->dateValidation($begin); - if ($error_message) { - $form_state->setError($element, $error_message); - } - // End either empty or valid extended interval values (5.2.3.) - if (empty($end) || $end === 'unknown' || $end === 'open') { - return; - } - $error_message = $this->dateValidation($end); - if ($error_message) { - $form_state->setError($element, $error_message); - } - } - else { - $error_message = $this->dateValidation($value); - if ($error_message) { - $form_state->setError($element, $error_message); - } - } - } - - /** - * Validate a date. - * - * @param string $datetime_str - * The datetime string. - * - * @return bool|string - * False if valid or a string explaining the reason for invalidation. - */ - protected function dateValidation($datetime_str) { - - list($date, $time) = explode('T', $datetime_str); - - $date = trim($date); - $extended_year = (strpos($date, 'y') === 0 ? TRUE : FALSE); - if ($extended_year && $this->getSetting('strict_dates')) { - return "Extended years (5.2.4.) are not supported with the 'strict dates' option enabled."; - } - // Uncertainty characters on the end are valid Level 1 features (5.2.1.), - // pull them off to make checking the rest easier. - $date = rtrim($date, '?~'); - - // Negative year? That is fine, but remove it - // and the extended year indicator before exploding the date. - $date = ltrim($date, 'y-'); - - // Now to check the parts. - list($year, $month, $day) = explode('-', $date, 3); - - // Year. - if (!preg_match('/^\d\d(\d\d|\du|uu)$/', $year) && !$extended_year) { - return "The year '$year' is invalid. Please enter a four-digit year."; - } - elseif ($extended_year && !preg_match('/^\d{5,}$/', $year)) { - return "Invalid extended year. Please enter at least a four-digit year."; - } - $strict_pattern = 'Y'; - - // Month. - if (!empty($month) && !preg_match('/^(\d\d|\du|uu)$/', $month)) { - return "The month '$month' is invalid. Please enter a two-digit month."; - } - if (!empty($month)) { - if (strpos($year, 'u') !== FALSE && strpos($month, 'u') === FALSE) { - return "The month must either be blank or unspecified when the year is unspecified."; - } - $strict_pattern = 'Y-m'; - } - - // Day. - if (!empty($day) && !preg_match('/^(\d\d|\du|uu)$/', $day)) { - return "The day '$day' is invalid. Please enter a two-digit day."; - } - if (!empty($day)) { - if (strpos($month, 'u') !== FALSE && strpos($day, 'u') === FALSE) { - return "The day must either be blank or unspecified when the month is unspecified."; - } - $strict_pattern = 'Y-m-d'; - } - - // Time. - if (strpos($datetime_str, 'T') !== FALSE && empty($time)) { - return "Time not provided with time seperator (T)."; - } - - if ($time) { - if (!preg_match('/^-?(\d{4})(-\d{2}){2}T\d{2}(:\d{2}){2}(Z|(\+|-)\d{2}:\d{2})?$/', $datetime_str, $matches)) { - return "The date/time '$datetime_str' is invalid. See EDTF 1.0, 5.1.2."; - } - drupal_set_message(print_r($matches, TRUE)); - $strict_pattern = 'Y-m-d\TH:i:s'; - if (count($matches) > 4) { - if ($matches[4] === 'Z') { - $strict_pattern .= '\Z'; - } - else { - $strict_pattern .= 'P'; - } - } - } - - if ($this->getSetting('strict_dates')) { - // Clean the date/time string to ensure it parses correctly. - $cleaned_datetime = str_replace('u', '1', $datetime_str); - $datetime_obj = DateTime::createFromFormat('!' . $strict_pattern, $cleaned_datetime); - $errors = DateTime::getLastErrors(); - if (!$datetime_obj || - !empty($errors['warning_count']) || - // DateTime will create valid dates from Y-m without warning, - // so validate we still have what it was given. - !($cleaned_datetime === $datetime_obj->format($strict_pattern)) - ) { - return "Strictly speaking, the date (and/or time) '$datetime_str' is invalid."; - } - - } - - return FALSE; - } +class TextEDTFWidget extends EDTFWidget { } From 7c29cb597a0dddba33690d26334525cb999e6167 Mon Sep 17 00:00:00 2001 From: Seth Shaw Date: Thu, 24 Jan 2019 09:53:55 -0800 Subject: [PATCH 4/8] Add update hook also remove unused storage config --- controlled_access_terms.module | 66 +++++++++++++++++++ ...onomy_term.field_corp_dissolution_date.yml | 20 ------ ...taxonomy_term.field_corp_founding_date.yml | 20 ------ 3 files changed, 66 insertions(+), 40 deletions(-) delete mode 100644 modules/controlled_access_terms_default_configuration/config/install/field.storage.taxonomy_term.field_corp_dissolution_date.yml delete mode 100644 modules/controlled_access_terms_default_configuration/config/install/field.storage.taxonomy_term.field_corp_founding_date.yml diff --git a/controlled_access_terms.module b/controlled_access_terms.module index 930d150..74da4e0 100644 --- a/controlled_access_terms.module +++ b/controlled_access_terms.module @@ -70,3 +70,69 @@ function controlled_access_terms_jsonld_alter_normalized_array(EntityInterface $ } } } + +/** + * Change fields using the EDTF Widget to the new EDTF Field Type. + */ +function controlled_access_terms_update_8002() { + // Ensure the new EDTF plugins can be found. + \Drupal::service('plugin.manager.field.field_type')->clearCachedDefinitions(); + + // Find all the fields using the text_edtf widget via form configs. + $config_factory = \Drupal::configFactory(); + foreach ($config_factory->listAll('core.entity_form_display.') as $entity_form_display_config_name) { + $entity_form_display = $config_factory->getEditable($entity_form_display_config_name); + $fields = $entity_form_display->get('content'); + foreach ($fields as $field_name => $field_settings) { + if (isset($field_settings['type']) && $field_settings['type'] === 'text_edtf') { + + // Update this form setting. + $entity_form_display->set("content.$field_name.type", 'edtf_default'); + + // Update the field setting. + if (!$fields = \Drupal::entityManager()->getStorage('field_config')->loadByProperties(['field_name' => $field_name])) { + continue; + } + else { + foreach ($fields as $field) { + $new_field = $field->toArray(); + $new_field['field_type'] = 'edtf'; + $new_field = FieldConfig::create($new_field); + $new_field->original = $new_field; + $new_field->enforceIsNew(FALSE); + $new_field->save(); + } + } + + // Update the field storage setting. + if (!$field_storage_configs = \Drupal::entityManager()->getStorage('field_storage_config')->loadByProperties(['field_name' => $field_name])) { + continue; + } + else { + foreach ($field_storage_configs as $field_storage) { + $new_field_storage = $field_storage->toArray(); + $new_field_storage['type'] = 'edtf'; + $new_field_storage = FieldStorageConfig::create($new_field_storage); + $new_field_storage->original = $new_field_storage; + $new_field_storage->enforceIsNew(FALSE); + $new_field_storage->save(); + } + } + } + } + $entity_form_display->save(TRUE); + } + + // Find display configs. + foreach ($config_factory->listAll('core.entity_view_display.') as $entity_view_display_config_name) { + $entity_view_display = $config_factory->getEditable($entity_view_display_config_name); + $fields = $entity_view_display->get('content'); + foreach ($fields as $field_name => $field_settings) { + if (isset($field_settings['type']) && ($field_settings['type'] === 'text_edtf_human' || $field_settings['type'] === 'text_edtf_iso8601')) { + // Update this view setting. + $entity_view_display->set("content.$field_name.type", 'edtf_default'); + } + } + $entity_view_display->save(TRUE); + } +} diff --git a/modules/controlled_access_terms_default_configuration/config/install/field.storage.taxonomy_term.field_corp_dissolution_date.yml b/modules/controlled_access_terms_default_configuration/config/install/field.storage.taxonomy_term.field_corp_dissolution_date.yml deleted file mode 100644 index ad8129f..0000000 --- a/modules/controlled_access_terms_default_configuration/config/install/field.storage.taxonomy_term.field_corp_dissolution_date.yml +++ /dev/null @@ -1,20 +0,0 @@ -langcode: en -status: true -dependencies: - module: - - taxonomy -id: taxonomy_term.field_corp_dissolution_date -field_name: field_corp_dissolution_date -entity_type: taxonomy_term -type: edtf -settings: - max_length: 60 - is_ascii: false - case_sensitive: false -module: core -locked: false -cardinality: 1 -translatable: true -indexes: { } -persist_with_no_fields: false -custom_storage: false diff --git a/modules/controlled_access_terms_default_configuration/config/install/field.storage.taxonomy_term.field_corp_founding_date.yml b/modules/controlled_access_terms_default_configuration/config/install/field.storage.taxonomy_term.field_corp_founding_date.yml deleted file mode 100644 index 8f3a707..0000000 --- a/modules/controlled_access_terms_default_configuration/config/install/field.storage.taxonomy_term.field_corp_founding_date.yml +++ /dev/null @@ -1,20 +0,0 @@ -langcode: en -status: true -dependencies: - module: - - taxonomy -id: taxonomy_term.field_corp_founding_date -field_name: field_corp_founding_date -entity_type: taxonomy_term -type: edtf -settings: - max_length: 60 - is_ascii: false - case_sensitive: false -module: core -locked: false -cardinality: 1 -translatable: true -indexes: { } -persist_with_no_fields: false -custom_storage: false From 812ceb5e7a5ae91b85d2e66e087afac8330625c6 Mon Sep 17 00:00:00 2001 From: Seth Shaw Date: Thu, 24 Jan 2019 09:55:31 -0800 Subject: [PATCH 5/8] simplify labels --- src/Plugin/Field/FieldFormatter/EDTFFormatter.php | 2 +- src/Plugin/Field/FieldWidget/EDTFWidget.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Plugin/Field/FieldFormatter/EDTFFormatter.php b/src/Plugin/Field/FieldFormatter/EDTFFormatter.php index 06a7612..0720c36 100644 --- a/src/Plugin/Field/FieldFormatter/EDTFFormatter.php +++ b/src/Plugin/Field/FieldFormatter/EDTFFormatter.php @@ -13,7 +13,7 @@ * * @FieldFormatter( * id = "edtf_default", - * label = @Translation("EDTF (L1) for Humans"), + * label = @Translation("Default EDTF formatter"), * field_types = { * "edtf" * } diff --git a/src/Plugin/Field/FieldWidget/EDTFWidget.php b/src/Plugin/Field/FieldWidget/EDTFWidget.php index 44a66bb..8d59cca 100644 --- a/src/Plugin/Field/FieldWidget/EDTFWidget.php +++ b/src/Plugin/Field/FieldWidget/EDTFWidget.php @@ -17,7 +17,7 @@ * * @FieldWidget( * id = "edtf_default", - * label = @Translation("Extended Date Time Format, Level 1"), + * label = @Translation("Default EDTF widget"), * field_types = { * "edtf" * } From 385bbd92ff826424d9337d3550e37e3f9199e626 Mon Sep 17 00:00:00 2001 From: Seth Shaw Date: Thu, 24 Jan 2019 10:02:16 -0800 Subject: [PATCH 6/8] remove unused widget and formatters --- .../FieldFormatter/TextEDTFHumanFormatter.php | 20 --- .../Field/FieldFormatter/TextEDTFiso8601.php | 138 ------------------ .../Field/FieldWidget/TextEDTFWidget.php | 23 --- 3 files changed, 181 deletions(-) delete mode 100644 src/Plugin/Field/FieldFormatter/TextEDTFHumanFormatter.php delete mode 100644 src/Plugin/Field/FieldFormatter/TextEDTFiso8601.php delete mode 100644 src/Plugin/Field/FieldWidget/TextEDTFWidget.php diff --git a/src/Plugin/Field/FieldFormatter/TextEDTFHumanFormatter.php b/src/Plugin/Field/FieldFormatter/TextEDTFHumanFormatter.php deleted file mode 100644 index c7d396b..0000000 --- a/src/Plugin/Field/FieldFormatter/TextEDTFHumanFormatter.php +++ /dev/null @@ -1,20 +0,0 @@ - March. - '21' => '03', - // Summer => June. - '22' => '06', - // Autumn => September. - '23' => '09', - // Winter => December. - '24' => '12', - ]; - - /** - * Southern hemisphere season map. - * - * @var array - */ - private $seasonMapSouth = [ - // Spring => September. - '21' => '03', - // Summer => December. - '22' => '06', - // Autumn => March. - '23' => '09', - // Winter => June. - '24' => '12', - ]; - - /** - * {@inheritdoc} - */ - public static function defaultSettings() { - return [ - // Northern bias, sorry. - 'season_hemisphere' => 'north', - ] + parent::defaultSettings(); - } - - /** - * {@inheritdoc} - */ - public function settingsForm(array $form, FormStateInterface $form_state) { - $form['season_hemisphere'] = [ - '#title' => t('Hemisphere Seasons'), - '#type' => 'select', - '#default_value' => $this->getSetting('season_hemisphere'), - '#description' => t("Seasons aren't currently supported by iso 8601. - We map them to their respective equinox and - solstice months. Select a hemisphere to use for - the mapping."), - '#options' => [ - 'north' => t('Northern Hemisphere'), - 'south' => t('Southern Hemisphere'), - ], - ]; - return $form; - } - - /** - * {@inheritdoc} - */ - public function settingsSummary() { - $summary = []; - if ($this->getSetting('strict_dates') === 'south') { - $summary[] = t('Seasons mapped to the southern hemisphere.'); - } - return $summary; - } - - /** - * {@inheritdoc} - */ - public function viewElements(FieldItemListInterface $items, $langcode) { - $element = []; - $settings = $this->getSettings(); - - foreach ($items as $delta => $item) { - // Interval. - list($begin, $end) = explode('/', $item->value); - // End is currently ignored. - // Strip approximations/uncertainty. - $begin = str_replace(['?', '~'], '', $begin); - - // Replace unspecified. - // Month/day. - $begin = str_replace('-uu', '-01', $begin); - // Zero-Year in decade/century. - $begin = str_replace('u', '0', $begin); - - // Seasons map. - list($year, $month, $day) = explode('-', $begin, 3); - // Digit Seasons. - if (in_array($month, ['21', '22', '23', '24'])) { - $season_mapping = ($settings['season_hemisphere'] === 'north' ? $this->seasonMapNorth : $this->seasonMapSouth); - $month = $season_mapping[$month]; - $begin = implode('-', array_filter([$year, $month, $day])); - } - - $element[$delta] = ['#markup' => $begin]; - } - return $element; - } - -} diff --git a/src/Plugin/Field/FieldWidget/TextEDTFWidget.php b/src/Plugin/Field/FieldWidget/TextEDTFWidget.php deleted file mode 100644 index 5e1d9de..0000000 --- a/src/Plugin/Field/FieldWidget/TextEDTFWidget.php +++ /dev/null @@ -1,23 +0,0 @@ - Date: Thu, 24 Jan 2019 10:19:52 -0800 Subject: [PATCH 7/8] include note on EDTF --- README.md | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 3dc7987..9a3cf06 100644 --- a/README.md +++ b/README.md @@ -22,14 +22,16 @@ greatly appreciated. ## Content Types Below is a list of the (at least partially) implemented content types with -their fields. *Emphasized fields* were originally planned but proved infeasible -given the structure of Drupal's RDF module and the Islandora JSON-LD module. +their fields. The fields with "EDTF L1" accept and display dates corresponding +to the Library of Congress [2012 Extended Date/Time Format Specification](http://www.loc.gov/standards/datetime/pre-submission.html) +_Level 1_. [EDTF was incorporated in ISO 8601-2019 with some modifications](http://www.loc.gov/standards/datetime/edtf.html) +which will be supported in a future update. - Corporate Body - Preferred Name (Title) - Alternate Name - - Founding Date (EDTF v.1) - - Dissolution Date (EDTF v.1) + - Founding Date (EDTF L1) + - Dissolution Date (EDTF L1) - Parent Organization - Authorities - Description @@ -49,14 +51,14 @@ given the structure of Drupal's RDF module and the Islandora JSON-LD module. - Display Label (Title) - Authorities - Relation - - Date Begin (EDTF v.1) - - Date End (EDTF v.1) + - Date Begin (EDTF L1) + - Date End (EDTF L1) - Person - Title/Display Name - Alternate Name - Preferred Name - - Birth Date (EDTF v.1) - - Death Date (EDTF v.1) + - Birth Date (EDTF L1) + - Death Date (EDTF L1) - Relation - Authorities - Description From fc6e2e297a497f80f68c6141c7a4c561a23a48ab Mon Sep 17 00:00:00 2001 From: Seth Shaw Date: Wed, 30 Jan 2019 12:13:04 -0800 Subject: [PATCH 8/8] include missing use statements --- controlled_access_terms.module | 2 ++ 1 file changed, 2 insertions(+) diff --git a/controlled_access_terms.module b/controlled_access_terms.module index 74da4e0..a5fb060 100644 --- a/controlled_access_terms.module +++ b/controlled_access_terms.module @@ -8,6 +8,8 @@ use Drupal\jsonld\Normalizer\NormalizerBase; use Drupal\Core\Entity\EntityInterface; use Drupal\controlled_access_terms\EDTFConverter; +use Drupal\field\Entity\FieldStorageConfig; +use Drupal\field\Entity\FieldConfig; /** * Implements hook_rdf_namespaces().