diff --git a/.gitignore b/.gitignore index 78b63a5..140221c 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ src/Tools/Iiif/IiifHelper.php .idea/webform_strawberryfield.iml src/.DS_Store +.idea/runConfigurations/archipelago.xml diff --git a/composer.json b/composer.json index 461c6ae..298e794 100644 --- a/composer.json +++ b/composer.json @@ -14,6 +14,7 @@ ], "require": { "php": ">=7.2", + "ext-json": "*", "ml/json-ld": "^1.2", "mtdowling/jmespath.php":"^2.5", "strawberryfield/strawberryfield":"dev-1.0.0-RC3", diff --git a/src/Controller/AuthAutocompleteController.php b/src/Controller/AuthAutocompleteController.php index e0ddd0d..7651ef3 100644 --- a/src/Controller/AuthAutocompleteController.php +++ b/src/Controller/AuthAutocompleteController.php @@ -16,6 +16,7 @@ use GuzzleHttp\Client; use GuzzleHttp\Exception\ClientException; use GuzzleHttp\Exception\ServerException; +use GuzzleHttp\RequestOptions; use Drupal\Component\Utility\UrlHelper; use Drupal\Core\Url; use Drupal\Component\Datetime\TimeInterface; @@ -163,6 +164,9 @@ public function handleAutocomplete(Request $request, $auth_type, $vocab = 'subje if (is_string($csrf_token)) { $request_base = $request->getSchemeAndHttpHost().':'.$request->getPort(); $is_internal = $_SERVER['REQUEST_SCHEME'].'://'.$_SERVER['SERVER_ADDR'].':'.$_SERVER['SERVER_PORT'] == $request_base; + if (!$is_internal) { + $is_internal = $_SERVER['HTTP_HOST'] == $_SERVER['SERVER_NAME']; + } } if ($input) { @@ -173,10 +177,9 @@ public function handleAutocomplete(Request $request, $auth_type, $vocab = 'subje $cache_id = 'webform_strawberry:auth_lod:' . $cache_var; $cached = $this->cacheGet($cache_id); if ($cached) { - error_log('cached'); return new JsonResponse($cached->data); } - if ($this->currentUser->isAnonymous() && !$is_internal) { + if ($this->currentUser->isAnonymous() && !$is_internal) { sleep(1); } @@ -197,9 +200,15 @@ public function handleAutocomplete(Request $request, $auth_type, $vocab = 'subje case 'viaf': $results = $this->viaf($input); break; + case 'mesh': + $results = $this->mesh($input, $vocab, $rdftype); + break; + case 'snac': + $results = $this->snac($input, $vocab, $rdftype); + break; case 'europeana': if ($apikey) { - $results = $this->europeana($input, $vocab, $apikey); + $results = $this->europeana($input, $vocab, $apikey); } else { $this->messenger()->addError( @@ -273,7 +282,7 @@ protected function loc($input, $vocab, $rdftype) { ]; $path = $endpoint[$vocab]; - $input = urlencode($input); + $input = rawurlencode($input); if ($vocab == 'rdftype') { $urlindex = "/suggest/?q=" . $input . "&rdftype=" . $rdftype; @@ -327,7 +336,7 @@ protected function loc($input, $vocab, $rdftype) { * @return array */ protected function wikidata($input) { - $input = urlencode($input); + $input = rawurlencode($input); $urlindex = '&language=en&format=json&search=' . $input; $baseurl = 'https://www.wikidata.org/w/api.php?action=wbsearchentities'; $remoteUrl = $baseurl . $urlindex; @@ -602,7 +611,7 @@ protected function getty($input, $vocab = 'aat', $mode = 'fuzzy') { * @return array */ protected function viaf($input) { - $input = urlencode($input); + $input = rawurlencode($input); $urlindex = '&query=' . $input; $baseurl = 'https://viaf.org/viaf/AutoSuggest?'; $remoteUrl = $baseurl . $urlindex; @@ -612,12 +621,12 @@ protected function viaf($input) { $jsondata = []; $results = []; - $jsondata = json_decode($body, TRUE); + $jsondata = json_decode($body, TRUE) ?? []; $json_error = json_last_error(); if ($json_error == JSON_ERROR_NONE) { //WIKIdata will give is an success key will always return at least one, the query string if (count($jsondata) > 1) { - if (count($jsondata['result']) >= 1) { + if (isset($jsondata['result']) && is_array($jsondata['result']) && count($jsondata['result']) >= 1) { foreach ($jsondata['result'] as $key => $item) { $desc = (isset($item['nametype'])) ? '(' . $item['nametype'] . ')' : NULL; $results[] = [ @@ -681,7 +690,7 @@ protected function europeana($input, $vocab, string $apikey) { return $results; } - $input = urlencode($input); + $input = rawurlencode($input); $urlindex = "/suggest?text=" . $input . "&type=" . $vocab ."&wskey=". $apikey ; @@ -729,7 +738,7 @@ protected function europeana($input, $vocab, string $apikey) { } if (($vocab == 'agent') && isset($result['dateOfBirth'])) { - $desc[] = $result['dateOfBirth'] . '/' . $result['dateOfDeath'] ?? '?'; + $desc[] = $result['dateOfBirth'] . '/' . $result['dateOfDeath'] ?? '?'; } $desc = !empty($desc) ? ' (' . implode(', ', $desc) . ')' : NULL; @@ -761,6 +770,181 @@ protected function europeana($input, $vocab, string $apikey) { return []; } + /** + * @param $input + * The query + * @param $vocab + * The 'suggest' enabled endpoint at LoC + * + * @return array + */ + protected function snac($input, $vocab, $rdftype) { + //@TODO make the following allowed list a constant since we use it in + // \Drupal\webform_strawberryfield\Plugin\WebformElement\WebformLoC + if (!in_array($vocab, [ + 'Constellation', + 'rdftype', + ])) { + // Drop before tryin to hit non existing vocab + $this->messenger()->addError( + $this->t('@vocab for SNAC autocomplete is not in in our allowed list.', + [ + '@vocab' => $vocab, + ] + ) + ); + $results[] = [ + 'value' => NULL, + 'label' => "Wrong Vocabulary {$vocab} in SNAC Query", + ]; + return $results; + } + + + $input = urlencode($input); + + + $remoteUrl = "https://api.snaccooperative.org"; + $options = [ + 'body' => json_encode([ + "command" => "search", + "term" => $input, + "entity_type" => $rdftype != "thing" ? $rdftype : NULL, + "start" => 0, + "count" => 10, + "search_type" => "autocomplete", + ]), + RequestOptions::HEADERS => [ + 'Content-Type' => 'application/json' + ] + ]; + $body = $this->getRemoteJsonData($remoteUrl, $options, 'PUT'); + + $jsondata = []; + $results = []; + $jsondata = json_decode($body, TRUE); + $json_error = json_last_error(); + if ($json_error == JSON_ERROR_NONE) { + if (!empty($jsondata['results']) && ($jsondata['total'] ?? 0) >= 1) { + foreach ($jsondata['results'] as $key => $entry) { + $nameEntry = reset($entry['nameEntries']); + $results[] = [ + 'value' => $entry['ark'] ?? $entry['entityType']['uri'], + 'label' => $nameEntry['original'], + ]; + } + } + else { + $results[] = [ + 'value' => NULL, + 'label' => "Sorry no match from SNAC {$vocab}", + ]; + } + return $results; + } + $this->messenger()->addError( + $this->t('Looks like data fetched from @url is not in JSON format.
JSON says: @jsonerror
Please check your URL!', + [ + '@url' => $remoteUrl, + '@jsonerror' => $json_error, + ] + ) + ); + return []; + } + + + + /** + * @param $input + * The query + * @param $vocab + * The 'suggest' enabled endpoint at LoC + * + * @return array + */ + protected function mesh($input, $vocab, $rdftype) { + + //@TODO make the following allowed list a constant since we use it in + // \Drupal\webform_strawberryfield\Plugin\WebformElement\WebformMesh + if (!in_array($vocab, [ + 'descriptor', + 'term', + ])) { + // Drop before tryin to hit non existing vocab + $this->messenger()->addError( + $this->t('@vocab for MeSH autocomplete is not in in our allowed list.', + [ + '@vocab' => $vocab, + ] + ) + ); + $results[] = [ + 'value' => NULL, + 'label' => "Wrong Vocabulary {$vocab} in MeSH API Query", + ]; + return $results; + } + + // Here $rdftype acts as match + if (!in_array($rdftype, [ + 'startswith', + 'contains', + 'exact', + ])) { + // Drop before tryin to hit non existing vocab + $this->messenger()->addError( + $this->t('@rdftype Match type for MeSH autocomplete is not valid. It may be "exact","startswith" or "contains"', + [ + '@rdftype' => $rdftype, + ] + ) + ); + $results[] = [ + 'value' => NULL, + 'label' => "Wrong Match type for {$vocab} in MeSH API Query", + ]; + return $results; + } + + $input = rawurlencode($input); + $urlindex = "/mesh/lookup/{$vocab}?label=" . $input .'&limit=10&match=' . $rdftype; + $baseurl = 'https://id.nlm.nih.gov'; + $remoteUrl = $baseurl . $urlindex; + $options['headers'] = ['Accept' => 'application/json']; + $body = $this->getRemoteJsonData($remoteUrl, $options); + + $results = []; + $jsondata = json_decode($body, TRUE); + $json_error = json_last_error(); + if ($json_error == JSON_ERROR_NONE) { + if (count($jsondata) > 1) { + foreach ($jsondata as $entry) { + $results[] = [ + 'value' => $entry['resource'], + 'label' => $entry['label'], + ]; + } + } + else { + $results[] = [ + 'value' => NULL, + 'label' => "Sorry no match from MeSH for {$vocab}", + ]; + } + return $results; + } + $this->messenger()->addError( + $this->t('Looks like data fetched from @url is not in JSON format.
JSON says: @jsonerror
Please check your URL!', + [ + '@url' => $remoteUrl, + '@jsonerror' => $json_error, + ] + ) + ); + return []; + } + /** * @param $remoteUrl * @param $options @@ -768,7 +952,7 @@ protected function europeana($input, $vocab, string $apikey) { * @return string * A string that may be JSON (hopefully) */ - protected function getRemoteJsonData($remoteUrl, $options) { + protected function getRemoteJsonData($remoteUrl, $options, $method = 'GET') { // This is expensive, reason why we process and store in cache if (empty($remoteUrl)) { // No need to alarm. all good. If not URL just return. @@ -783,7 +967,18 @@ protected function getRemoteJsonData($remoteUrl, $options) { return NULL; } try { - $request = $this->httpClient->get($remoteUrl, $options); + if ($method == 'GET') { + $request = $this->httpClient->get($remoteUrl, $options); + } + elseif ($method == 'POST') { + $request = $this->httpClient->post($remoteUrl, $options); + } + elseif ($method == 'PUT') { + $request = $this->httpClient->put($remoteUrl, $options); + } + else { + return NULL; + } // Do not cache if things go bad. if ($request->getStatusCode() == '401') { $this->setNotAllowed(TRUE); @@ -816,7 +1011,7 @@ protected function getRemoteJsonData($remoteUrl, $options) { catch (ServerException $exception) { $this->useCaches = FALSE; $responseMessage = $exception->getMessage(); - $this->loggerFactory->get('webform_strawberryfield') + $this->getLogger('webform_strawberryfield') ->error('We tried to contact @url but we could not.
The Remote server says: @response.
Check your query', [ '@url' => $remoteUrl, diff --git a/src/Controller/StrawberryRunnerModalController.php b/src/Controller/StrawberryRunnerModalController.php index 8feb4dd..09fc623 100644 --- a/src/Controller/StrawberryRunnerModalController.php +++ b/src/Controller/StrawberryRunnerModalController.php @@ -138,17 +138,18 @@ public function openModalForm(WebformInterface $webform = NULL, Request $request // In case we are editing an existing entity, this one gets the // Strawberryfield value $alldata = $source_entity->get($field_name)->getValue(); - $fielddata['value'] = !empty($alldata) ? $alldata[$delta]['value']: "{}"; + $fielddata['value'] = $alldata[$delta]['value'] ?? "{}"; $entityid = $source_entity->id(); } - $stored_value = (isset($fielddata['value']) && !empty($fielddata['value'])) ? $fielddata['value'] : "{}"; + $stored_value = $fielddata['value'] ?? "{}"; $data_defaults = [ 'strawberry_field_widget_state_id' => $widgetid, // Can't remember why, but seems useful to pass around 'strawberry_field_widget_source_entity_uuid' => $source_uuid, 'strawberry_field_widget_source_entity_id' => $entityid, + 'strawberry_field_widget_autosave' => $entityid ? FALSE : TRUE, 'strawberry_field_stored_values' => json_decode($stored_value,true) ]; @@ -247,7 +248,7 @@ public function openModalForm(WebformInterface $webform = NULL, Request $request // We delete both, the session and the accumulated errors. /** @var \Drupal\Core\TempStore\PrivateTempStore $tempstore */ $tempstore = \Drupal::service('tempstore.private')->get('archipel'); - $tempstore->delete($clear_saved); + $tempstore->delete($clear_saved.'-draft'); $tempstore->delete($clear_saved.'-errors'); // Selector us built using the field name and the delta. $response->addCommand(new \Drupal\Core\Ajax\HtmlCommand('#' . $selector .' > .fieldset-wrapper', diff --git a/src/Element/WebformMesh.php b/src/Element/WebformMesh.php new file mode 100644 index 0000000..d50cf13 --- /dev/null +++ b/src/Element/WebformMesh.php @@ -0,0 +1,108 @@ + 'descriptor', + '#matchtype' => 'startswith' + ]; + return $info; + } + + /** + * {@inheritdoc} + */ + public static function getCompositeElements(array $element) { + $elements = []; + $vocab = 'descriptor'; + $matchtype = 'startswith'; + if (isset($element['#vocab'])) { + $vocab = $element['#vocab']; + } + if (isset($element['#matchtype'])) { + $matchtype = $element['#matchtype']; + } + + $class = '\Drupal\webform_strawberryfield\Element\WebformMesh'; + $elements['label'] = [ + '#type' => 'textfield', + '#title' => t('MeSH @vocab Label',['@vocab' => $vocab]), + '#autocomplete_route_name' => 'webform_strawberryfield.auth_autocomplete', + '#autocomplete_route_parameters' => ['auth_type' => 'mesh', 'vocab' => $vocab, 'rdftype'=> $matchtype ,'count' => 10], + '#attributes' => [ + 'data-source-strawberry-autocomplete-key' => 'label', + 'data-target-strawberry-autocomplete-key' => 'uri' + ], + ]; + + $elements['uri'] = [ + '#type' => 'url', + '#title' => t('MeSH URL'), + '#attributes' => ['data-strawberry-autocomplete-value' => TRUE] + ]; + $elements['label']['#process'][] = [$class, 'processAutocomplete']; + return $elements; + } + + /** + * {@inheritdoc} + */ + public static function processWebformComposite(&$element, FormStateInterface $form_state, &$complete_form) { + // @Disclaimer: This function is the worst and deceiving. Keeping it here + // So i never make this error again. Because of + // \Drupal\webform\Plugin\WebformElement\WebformCompositeBase::prepareMultipleWrapper + // Basically, in case of having multiple elements :: processWebformComposite + // is *never* called because it actually converts the 'WebformComposite' element into a + // \Drupal\webform\Element\WebformMultiple calling ::processWebformMultiple element + + $vocab = 'descriptor'; + $matchtype = 'startswith'; + + $element = parent::processWebformComposite($element, $form_state, $complete_form); + if (isset($element['#vocab'])) { + $vocab = $element['#vocab']; + } + if (isset($element['#matchtype'])) { + $matchtype = $element['#matchtype']; + } + + $element['label']["#autocomplete_route_parameters"] = + ['auth_type' => 'mesh', 'vocab' => $vocab, 'rdftype'=> $matchtype ,'count' => 10]; + + return $element; + } + + /** + * @param array $element + * @param \Drupal\Core\Form\FormStateInterface $form_state + * @param array $complete_form + * + * @return array + */ + public static function processAutocomplete(&$element, FormStateInterface $form_state, &$complete_form) { + $element = parent::processAutocomplete($element, $form_state, $complete_form); + $element['#attached']['library'][] = 'webform_strawberryfield/webform_strawberryfield.metadataauth.autocomplete'; + $element['#attached']['drupalSettings'] = [ + 'webform_strawberryfield_autocomplete' => [], + ]; + + $element['#attributes']['data-strawberry-autocomplete'] = 'mesh'; + return $element; + } + +} diff --git a/src/Element/WebformSnac.php b/src/Element/WebformSnac.php new file mode 100644 index 0000000..ea67079 --- /dev/null +++ b/src/Element/WebformSnac.php @@ -0,0 +1,114 @@ + 'Constellation', + '#rdftype' => 'thing' + ]; + return $info; + } + + /** + * {@inheritdoc} + */ + public static function getCompositeElements(array $element) { + + $elements = []; + $vocab = 'Constellation'; + $rdftype = 'thing'; + if (isset($element['#vocab'])) { + $vocab = $element['#vocab']; + } + if (($vocab == 'rdftype') && isset($element['#rdftype'])) { + $rdftype = trim($element['#rdftype']); + } + + $class = '\Drupal\webform_strawberryfield\Element\WebformLoC'; + $elements['label'] = [ + '#type' => 'textfield', + '#title' => t('Label'), + '#autocomplete_route_name' => 'webform_strawberryfield.auth_autocomplete', + '#autocomplete_route_parameters' => ['auth_type' => 'snac', 'vocab' => $vocab, 'rdftype'=> $rdftype ,'count' => 10], + '#attributes' => [ + 'data-source-strawberry-autocomplete-key' => 'label', + 'data-target-strawberry-autocomplete-key' => 'uri' + ], + + ]; + $elements['uri'] = [ + '#type' => 'url', + '#title' => t('Snac URL'), + '#attributes' => ['data-strawberry-autocomplete-value' => TRUE] + ]; + $elements['label']['#process'][] = [$class, 'processAutocomplete']; + return $elements; + } + + + + /** + * {@inheritdoc} + */ + public static function processWebformComposite(&$element, FormStateInterface $form_state, &$complete_form) { + // @Disclaimer: This function is the worst and deceiving. Keeping it here + // So i never make this error again. Because of + // \Drupal\webform\Plugin\WebformElement\WebformCompositeBase::prepareMultipleWrapper + // Basically, in case of having multiple elements :: processWebformComposite + // is *never* called because it actually converts the 'WebformComposite' element into a + // \Drupal\webform\Element\WebformMultiple calling ::processWebformMultiple element + // So basically whatever i do here gets skipped if multiple elements are allowed. + // Solution is acting here instead: + // \Drupal\webform_strawberryfield\Plugin\WebformElement\WebformLoC::prepareMultipleWrapper + $vocab = 'Constellation'; + $rdftype = 'thing'; + + $element = parent::processWebformComposite($element, $form_state, $complete_form); + if (isset($element['#vocab'])) { + $vocab = $element['#vocab']; + } + if (($vocab == 'rdftype') && isset($element['#rdftype'])) { + $rdftype = trim($element['#rdftype']); + } + $element['label']["#autocomplete_route_parameters"] = + ['auth_type' => 'snac', 'vocab' => $vocab, 'rdftype'=> $rdftype ,'count' => 10]; + + return $element; + } + + /** + * @param array $element + * @param \Drupal\Core\Form\FormStateInterface $form_state + * @param array $complete_form + * + * @return array + */ + public static function processAutocomplete(&$element, FormStateInterface $form_state, &$complete_form) { + $element = parent::processAutocomplete($element, $form_state, $complete_form); + $element['#attached']['library'][] = 'webform_strawberryfield/webform_strawberryfield.metadataauth.autocomplete'; + $element['#attached']['drupalSettings'] = [ + 'webform_strawberryfield_autocomplete' => [], + ]; + + $element['#attributes']['data-strawberry-autocomplete'] = 'Snac'; + return $element; + } + + +} diff --git a/src/EventSubscriber/WebformStrawberryfieldDeleteTmpStorage.php b/src/EventSubscriber/WebformStrawberryfieldDeleteTmpStorage.php index b50bc3f..d5a260d 100644 --- a/src/EventSubscriber/WebformStrawberryfieldDeleteTmpStorage.php +++ b/src/EventSubscriber/WebformStrawberryfieldDeleteTmpStorage.php @@ -111,8 +111,13 @@ public function onEntityInsert(StrawberryfieldCrudEvent $event) { foreach ($field->getValue() as $delta => $value) { $keyid = $this->getTempStoreKeyName($fieldname, $delta, ''); $tempstore->delete($keyid); - // Delete also any cached errors - $tempstore->delete($keyid.'-errors'); + // Delete also any cached errors and drafts if the autosave session + // Generated this ADO. If not (e.g Clone we will have marked that + // ADO's UUID as not autosave so we do not delete another ongoing session + if ($this->tempStoreFactory->get('archipel_autosave')->get($entity->uuid())) { + $tempstore->delete($keyid . '-errors'); + $tempstore->delete($keyid . '-draft'); + } } } diff --git a/src/Plugin/Field/FieldWidget/StrawberryFieldWebFormInlineWidget.php b/src/Plugin/Field/FieldWidget/StrawberryFieldWebFormInlineWidget.php index 035a152..d514896 100644 --- a/src/Plugin/Field/FieldWidget/StrawberryFieldWebFormInlineWidget.php +++ b/src/Plugin/Field/FieldWidget/StrawberryFieldWebFormInlineWidget.php @@ -12,11 +12,13 @@ use Drupal\Core\Ajax\AjaxResponse; use Drupal\Core\Ajax\HtmlCommand; use Drupal\Core\Entity\EntityMalformedException; +use Drupal\Core\Entity\Plugin\DataType\EntityAdapter; use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Field\WidgetBase; use Drupal\Core\Url; +use Drupal\node\Entity\Node; use Symfony\Component\DependencyInjection\ContainerInterface; use Drupal\webform\WebformInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; @@ -265,7 +267,6 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen 'strawberry_webform_inline', ]); $limit_validation_errors = $parents; - // We add 'data-drupal-selector' = 'strawberry_webform_widget' // To allow JS to react/jquery select on this. $element += [ @@ -292,24 +293,23 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen // Which means an abandoned Metadata Sessions somewhere // Someone saved/drafted 'metadata' during a form session and left for coffee // WE can reuse! - - if (($tempstore->getMetadata($tempstoreId) != NULL) && $items->getEntity() - ->isNew()) { + $default_value = $items->getFieldDefinition()->getDefaultValue($items->getEntity()); + $default_value = $default_value[$delta]['value'] ?? "{}"; + $autosave = ($items->getEntity()->isNew() && $savedvalue['value'] == $default_value); + // If the SBF in this entity has data and its different to the default for + // the field then we are sure it can not be auto saved (e.g when cloning) + // Not should we load a session. + if (($tempstore->getMetadata($tempstoreId.'-draft') != NULL) && $autosave) { $discard = $form_state->getUserInput()['_triggering_element_name'] ?? FALSE; $discard = $discard == 'webform_strawberryfield_discard_session' ?? FALSE; - - $json_string = $tempstore->get($tempstoreId); + $autosave = TRUE; + $json_string = $tempstore->get($tempstoreId.'-draft'); $json = json_decode($json_string, TRUE); $json_error = json_last_error(); if ($json_error == JSON_ERROR_NONE) { $savedvalue['value'] = $json_string; - $element['strawberry_webform_inline_message'] = [ - '#id' => 'ajax-value', - '#theme' => 'status_messages', - '#message_list' => [ - 'status' => [$this->t('We found and loaded a previous unfinished metadata session for you.')], - ], - ]; + // Let's use the time to see if draft was changed before or after this call + // Since this form rebuilds itself with any webform ajax interaction $webform_controller_url_clear = Url::fromRoute('webform_strawberryfield.modal_webform', [ @@ -320,9 +320,11 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen 'clear_saved' => $tempstoreId, ] ); + $element['strawberry_webform_discard_session'] = [ '#type' => 'link', '#title' => $this->t('Discard Session'), + '#description' => $this->t('We found and unfinished Metadata Session'), '#url' => $webform_controller_url_clear, '#attributes' => [ 'class' => [ @@ -333,9 +335,12 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen ], ], ]; + $element['#title'] = $element['#title'] . $this->t(' (Unfinished Metadata Session loaded)'); } } + $form_state->set('autosave', $autosave); + // If new this won't exist $stored_value = !empty($savedvalue['value']) ? $savedvalue['value'] : "{}"; @@ -343,6 +348,7 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen 'strawberry_field_widget_state_id' => $this_widget_id, 'strawberry_field_widget_source_entity_uuid' => $entity_uuid, 'strawberry_field_widget_source_entity_id' => $entity_id, + 'strawberry_field_widget_autosave' => $autosave, 'strawberry_field_stored_values' => json_decode($stored_value, TRUE), ]; @@ -426,7 +432,7 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen ], ]; } - if ($this->getSetting('hide_cancel') === FALSE || $this->getSetting('hide_cancel') == NULL) { + if (($this->getSetting('hide_cancel') === FALSE || $this->getSetting('hide_cancel') == NULL) && $autosave == FALSE ){ $webform_controller_url_close = Url::fromRoute('webform_strawberryfield.close_modal_webform', [ 'state' => "$entity_uuid:$this_field_name:$delta:$this_widget_id", @@ -538,6 +544,11 @@ public function validateWebform($element, FormStateInterface $form_state) { } $json_string = $tempstore->get($tempstoreId); + $autosave = $form_state->get('autosave'); + $tempstore_noautosave = \Drupal::service('tempstore.private')->get('archipel_autosave'); + $final_uuid = $form_state->getFormObject()->getEntity()->uuid(); + $tempstore_noautosave->set($final_uuid, $autosave); + $json = json_decode($json_string, TRUE); $json_error = json_last_error(); if ($json_error == JSON_ERROR_NONE) { diff --git a/src/Plugin/WebformElement/WebformMesh.php b/src/Plugin/WebformElement/WebformMesh.php new file mode 100644 index 0000000..fedf6d0 --- /dev/null +++ b/src/Plugin/WebformElement/WebformMesh.php @@ -0,0 +1,162 @@ + 'descriptor', + 'matchtype' => 'startswith', + ] + parent::defineDefaultBaseProperties(); + } + + public function getDefaultProperties() { + $properties = parent::getDefaultProperties() + [ + 'vocab' => 'descriptor', + 'matchtype' => 'startswith', + ]; + + return $properties; + } + + + + public function prepare( + array &$element, + WebformSubmissionInterface $webform_submission = NULL + ) { + + // @TODO explore this method to act on submitted data v/s element behavior + } + + /** + * Set multiple element wrapper. + * + * @param array $element + * An element. + */ + protected function prepareMultipleWrapper(array &$element) { + + parent::prepareMultipleWrapper($element); + + // Finally! + // This is the last chance we have to affect the render array + // This is where the original element type is also + // swapped by webform_multiple + // breaking all our #process callbacks. + $matchtype = trim($this->getElementProperty($element, 'matchtype')); + $matchtype = $matchtype?: $this->getDefaultProperty('matchtype'); + $vocab = $this->getElementProperty($element, 'vocab'); + $vocab = $vocab ?: $this->getDefaultProperty('vocab'); + if (isset($element['#element']['#webform_composite_elements']['label'])) { + $element['#element']['#webform_composite_elements']['label']["#autocomplete_route_parameters"] = + [ + 'auth_type' => 'mesh', + 'vocab' => $vocab, + 'rdftype' => $matchtype, + 'count' => 10 + ]; + } + // For some reason i can not understand, when multiples are using + // Tables, the #webform_composite_elements -> 'label' is not used... + if (isset($element["#multiple__header"]) && $element["#multiple__header"] == true) { + $element['#element']['label']["#autocomplete_route_parameters"] = + [ + 'auth_type' => 'mesh', + 'vocab' => $vocab, + 'rdftype' => $matchtype, + 'matchtype' => 'startswith', + 'count' => 10 + ]; + } + } + + + /** + * {@inheritdoc} + */ + public function getPluginLabel() { + return $this->elementManager->isExcluded('webform_metadata_mesh') ? $this->t('Medical Subject Heading MeSH') : parent::getPluginLabel(); + } + + /** + * {@inheritdoc} + */ + protected function formatHtmlItemValue(array $element, WebformSubmissionInterface $webform_submission, array $options = []) { + return $this->formatTextItemValue($element, $webform_submission, $options); + } + + /** + * {@inheritdoc} + */ + protected function formatTextItemValue(array $element, WebformSubmissionInterface $webform_submission, array $options = []) { + $value = $this->getValue($element, $webform_submission, $options); + + $lines = []; + if (!empty($value['uri'])) { + $lines[] = $value['uri']; + } + + if (!empty($value['label'])) { + $lines[] = $value['label']; + } + return $lines; + } + + /** + * {@inheritdoc} + */ + public function form(array $form, FormStateInterface $form_state) { + $form = parent::form($form, $form_state); + + $form['composite']['vocab'] = [ + '#type' => 'select', + '#options' => [ + 'descriptor' => 'Medical Subject Headings Descriptor (Subject Headings) API', + 'term' => 'Medical Subject Headings Term API', + ], + '#title' => $this->t("What MeSH Autocomplete API Type to use."), + '#description' => $this->t('See MeSH Subject HeadingsAPI'), + + ]; + $form['composite']['matchtype'] = [ + '#type' => 'select', + '#options' => [ + 'exact' => 'Exact, based on recommended Label. Will give you a single result or none.', + 'startswith' => 'Label Starts with.', + 'contains' => 'Label Contains.', + ], + '#title' => $this->t("What type of Match Query to perform"), + '#description' => $this->t('All match types return the same number of results. Exact matches only against the prefered label of the query.'), + ]; + return $form; + } + +} diff --git a/src/Plugin/WebformElement/WebformSnac.php b/src/Plugin/WebformElement/WebformSnac.php new file mode 100644 index 0000000..dffe00d --- /dev/null +++ b/src/Plugin/WebformElement/WebformSnac.php @@ -0,0 +1,174 @@ + 'Constellation', + 'rdftype' => 'thing', + ] + parent::defineDefaultBaseProperties(); + } + + public function getDefaultProperties() { + $properties = parent::getDefaultProperties() + [ + 'vocab' => 'Constellation', + 'rdftype' => 'thing', + ]; + + return $properties; + } + + + + public function prepare( + array &$element, + WebformSubmissionInterface $webform_submission = NULL + ) { + + // @TODO explore this method to act on submitted data v/s element behavior + } + + /** + * Set multiple element wrapper. + * + * @param array $element + * An element. + */ + protected function prepareMultipleWrapper(array &$element) { + + parent::prepareMultipleWrapper($element); + + // Finally! + // This is the last chance we have to affect the render array + // This is where the original element type is also + // swapped by webform_multiple + // breaking all our #process callbacks. + $vocab = 'Constellation'; + $rdftype = 'thing'; + $vocab = $this->getElementProperty($element, 'vocab'); + $vocab = $vocab ?: $this->getDefaultProperty($vocab); + if ($vocab == 'rdftype') { + $rdftype = trim($this->getElementProperty($element, 'rdftype')); + } + + $rdftype = $rdftype ?: $this->getDefaultProperty($rdftype); + // This seems to have been an old Webform module variation + // Keeping it here until sure its not gone for good + if (isset($element['#element']['#webform_composite_elements']['label'])) { + $element['#element']['#webform_composite_elements']['label']["#autocomplete_route_parameters"] = + [ + 'auth_type' => 'snac', + 'vocab' => $vocab, + 'rdftype' => $rdftype, + 'count' => 10 + ]; + } + // For some reason i can not understand, when multiples are using + // Tables, the #webform_composite_elements -> 'label' is not used... + if (isset($element["#multiple__header"]) && $element["#multiple__header"] == true) { + $element['#element']['label']["#autocomplete_route_parameters"] = + [ + 'auth_type' => 'snac', + 'vocab' => $vocab, + 'rdftype' => $rdftype, + 'count' => 10 + ]; + } + } + + + /** + * {@inheritdoc} + */ + public function getPluginLabel() { + return $this->elementManager->isExcluded('webform_metadata_snac') ? $this->t('SNAC Constellation Terms') : parent::getPluginLabel(); + } + + /** + * {@inheritdoc} + */ + protected function formatHtmlItemValue(array $element, WebformSubmissionInterface $webform_submission, array $options = []) { + return $this->formatTextItemValue($element, $webform_submission, $options); + } + + /** + * {@inheritdoc} + */ + protected function formatTextItemValue(array $element, WebformSubmissionInterface $webform_submission, array $options = []) { + $value = $this->getValue($element, $webform_submission, $options); + + $lines = []; + if (!empty($value['uri'])) { + $lines[] = $value['uri']; + } + + if (!empty($value['label'])) { + $lines[] = $value['label']; + } + return $lines; + } + + /** + * {@inheritdoc} + */ + public function form(array $form, FormStateInterface $form_state) { + $form = parent::form($form, $form_state); + + $form['composite']['vocab'] = [ + '#type' => 'select', + '#options' => [ + 'Constellation' => 'All Constellation Entity Types', + 'rdftype' => 'By specific Constellation Entity Types', + ], + '#title' => $this->t("What SNAC query type to use."), + '#description' => $this->t('Specific Entity Types can be: person, corporateBody or family'), + ]; + // Not sure if this has a sub authority and how that works/if suggest + $form['composite']['rdftype'] = [ + '#type' => 'select', + '#options' => [ + 'person' => 'person', + 'corporateBody' => 'corporateBody', + 'family' => 'family', + ], + '#title' => $this->t("What SNAC Entity type to use as filter"), + '#description' => $this->t('Can be one of: person, corporateBody or family'), + '#default_value' => 'person', + '#states' => [ + 'visible' => [ + ':input[name="properties[vocab]"]' => ['value' => 'rdftype'], + ], + ], + ]; + + return $form; + } + +} diff --git a/src/Plugin/WebformHandler/strawberryFieldharvester.php b/src/Plugin/WebformHandler/strawberryFieldharvester.php index ab99444..f58002d 100644 --- a/src/Plugin/WebformHandler/strawberryFieldharvester.php +++ b/src/Plugin/WebformHandler/strawberryFieldharvester.php @@ -557,14 +557,16 @@ public function preSave(WebformSubmissionInterface $webform_submission) { ); } try { + // Saves all what was done in the webform in our tempstore + // To be later retrieved by the Field Widget Submit $tempstore->set( $values["strawberry_field_widget_state_id"], $cleanvalues ); // Just in case we have stashed errors remove them $tempstore->delete($values['strawberry_field_widget_state_id'] . '-errors'); - - } catch (TempStoreException $e) { + } + catch (TempStoreException $e) { $this->messenger()->addError( $this->t( 'Sorry, we have issues writing metadata to your session storage. Please reload this form and/or contact your system admin.' @@ -609,8 +611,8 @@ public function validateForm( $values = $webform_submission->getData(); // So we can now unset cached errors on this step. Only if triggered by next here. // Wizard navigation has its own way. - if ((!isset($values['strawberry_field_widget_source_entity_id']) || - $values['strawberry_field_widget_source_entity_id'] === NULL) && + if ((!isset($values['strawberry_field_widget_autosave']) || + $values['strawberry_field_widget_autosave'] === TRUE) && isset($values['strawberry_field_widget_state_id'])) { if (isset($form_state->getTriggeringElement()['#name']) && $form_state->getTriggeringElement()['#name'] == 'op') { $current_page = $webform_submission->getCurrentPage(); @@ -666,10 +668,11 @@ public function submitForm( /* @var $tempstore \Drupal\Core\TempStore\PrivateTempStore */ $tempstore = \Drupal::service('tempstore.private')->get('archipel'); $tempstore->set( - $values['strawberry_field_widget_state_id'], + $values['strawberry_field_widget_state_id'].'-draft', $cleanvalues ); $form_state->set('in_draft', TRUE); + $form_state->set('draft_saved', TRUE); } catch (TempStoreException $e) { $this->messenger()->addError( $this->t( @@ -829,8 +832,8 @@ public function alterForm(array &$form, FormStateInterface $form_state, WebformS // Instead of validating previous invisible steps and setting errors here // we will block the submit button until all errors are cleared. - if ((!isset($values['strawberry_field_widget_source_entity_id']) || - $values['strawberry_field_widget_source_entity_id'] === NULL) && + if ((!isset($values['strawberry_field_widget_autosave']) || + $values['strawberry_field_widget_autosave'] === TRUE) && isset($values['strawberry_field_widget_state_id']) ) { $tempstore = \Drupal::service('tempstore.private')->get('archipel'); @@ -845,7 +848,9 @@ public function alterForm(array &$form, FormStateInterface $form_state, WebformS $can_not = FALSE; if (is_array($previous_errors)) { + $i = 0; foreach ($all_pages as $pagekey => $pagekeyinfo) { + $i++; if (($pagekeyinfo['#access'] === TRUE) && isset($previous_errors[$pagekey]) && is_array($previous_errors[$pagekey]) && @@ -854,8 +859,9 @@ public function alterForm(array &$form, FormStateInterface $form_state, WebformS $this->messenger() ->addWarning(t('You can not submit this form yet.'), FALSE); $this->messenger() - ->addWarning(t('Please check the @steps step for missing or incorrect fields', [ + ->addWarning(t('Please check the @steps step (@number) for missing or incorrect fields', [ '@steps' => $pagekeyinfo['#title'], + '@number' => $i, ])); } } @@ -864,8 +870,8 @@ public function alterForm(array &$form, FormStateInterface $form_state, WebformS } } } - } elseif ((!isset($values['strawberry_field_widget_source_entity_id']) || - $values['strawberry_field_widget_source_entity_id'] === NULL) && + } elseif ((!isset($values['strawberry_field_widget_autosave']) || + $values['strawberry_field_widget_autosave'] === TRUE) && isset($values['strawberry_field_widget_state_id']) && isset($form['pages']) ) { @@ -904,8 +910,8 @@ public function sbfDraftValidate(array &$form, FormStateInterface $form_state) { } $values = $this->getWebformSubmission()->getData(); // Only allow saving of drafts if the user is creating a new entity - if ((!isset($values['strawberry_field_widget_source_entity_id']) || - $values['strawberry_field_widget_source_entity_id'] === NULL) && + if ((!isset($values['strawberry_field_widget_autosave']) || + $values['strawberry_field_widget_autosave'] === TRUE) && isset($values['strawberry_field_widget_state_id']) ) { $this->setIsWidgetDriven(TRUE); @@ -927,7 +933,8 @@ public function sbfDraftValidate(array &$form, FormStateInterface $form_state) { $form_state->set('in_draft', TRUE); $form_state->set('draft_saved', TRUE); $this->getWebformSubmission()->validate(); - } catch (TempStoreException $e) { + } + catch (TempStoreException $e) { $this->messenger()->addError( $this->t( 'Sorry, we have issues writing metadata to your session storage. Please reload this form and/or contact your system admin.'