From 9677a99b1ea47306694b4be1dc953c59e2dd1f48 Mon Sep 17 00:00:00 2001 From: Diego Pino Navarro Date: Fri, 25 Mar 2022 18:13:29 -0300 Subject: [PATCH 1/4] Adds realtime ami_lod_reconcile() Twig function Uses one of these (there are a few more! but not documented yet...hehehe) 'loc;subjects;thing' => 'LoC subjects(LCSH)', 'loc;names;athing' => 'LoC Name Authority File (LCNAF)', 'loc;genreForms;thing' => 'LoC Genre/Form Terms (LCGFT)', 'loc;graphicMaterials;thing' => 'LoC Thesaurus of Graphic Materials (TGN)', 'loc;geographicAreas;thing' => 'LoC MARC List for Geographic Areas', 'loc;relators;thing' => 'LoC Relators Vocabulary (Roles)', 'loc;rdftype;CorporateName' => 'LoC MADS RDF by type: Corporate Name', 'loc;rdftype;PersonalName' => 'LoC MADS RDF by type: Personal Name', 'loc;rdftype;FmilyName' => 'LoC MADS RDF by type: Family Name', 'loc;rdftype;Topic' => 'LoC MADS RDF by type: Topic', 'loc;rdftype;GenreForm' => 'LoC MADS RDF by type: Genre Form', 'loc;rdftype;Geographic' => 'LoC MADS RDF by type: Geographic', 'loc;rdftype;Temporal' => 'LoC MADS RDF by type: Temporal', 'loc;rdftype;ExtraterrestrialArea' => 'LoC MADS RDF by type: Extraterrestrial Area', 'viaf;subjects;thing' => 'Viaf', 'getty;aat;fuzzy' => 'Getty aat Fuzzy', 'getty;aat;terms' => 'Getty aat Terms', 'getty;aat;exact' => 'Getty aat Exact Label Match', 'wikidata;subjects;thing' => 'Wikidata Q Items' To be called like this {{ ami_lod_reconcile('Central Park', 'loc;subjects;thing', 'en')|json_encode|raw }} Return will be either NULL (if none found) or an array in this form [{"uri":"http:\/\/id.loc.gov\/authorities\/subjects\/sh85021920","label":"Central Park (New York, N.Y.)"}] --- src/TwigExtension.php | 82 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 src/TwigExtension.php diff --git a/src/TwigExtension.php b/src/TwigExtension.php new file mode 100644 index 0000000..02ab95c --- /dev/null +++ b/src/TwigExtension.php @@ -0,0 +1,82 @@ + 'LoC subjects(LCSH)', + 'loc;names;athing' => 'LoC Name Authority File (LCNAF)', + 'loc;genreForms;thing' => 'LoC Genre/Form Terms (LCGFT)', + 'loc;graphicMaterials;thing' => 'LoC Thesaurus of Graphic Materials (TGN)', + 'loc;geographicAreas;thing' => 'LoC MARC List for Geographic Areas', + 'loc;relators;thing' => 'LoC Relators Vocabulary (Roles)', + 'loc;rdftype;CorporateName' => 'LoC MADS RDF by type: Corporate Name', + 'loc;rdftype;PersonalName' => 'LoC MADS RDF by type: Personal Name', + 'loc;rdftype;FmilyName' => 'LoC MADS RDF by type: Family Name', + 'loc;rdftype;Topic' => 'LoC MADS RDF by type: Topic', + 'loc;rdftype;GenreForm' => 'LoC MADS RDF by type: Genre Form', + 'loc;rdftype;Geographic' => 'LoC MADS RDF by type: Geographic', + 'loc;rdftype;Temporal' => 'LoC MADS RDF by type: Temporal', + 'loc;rdftype;ExtraterrestrialArea' => 'LoC MADS RDF by type: Extraterrestrial Area', + 'viaf;subjects;thing' => 'Viaf', + 'getty;aat;fuzzy' => 'Getty aat Fuzzy', + 'getty;aat;terms' => 'Getty aat Terms', + 'getty;aat;exact' => 'Getty aat Exact Label Match', + 'wikidata;subjects;thing' => 'Wikidata Q Items' + ]; + $label = trim($label); + try { + $domain = \Drupal::service('request_stack')->getCurrentRequest()->getSchemeAndHttpHost(); + $lod_route_argument_list = explode(";", $vocab); + $lod = \Drupal::service('ami.lod')->invokeLoDRoute($domain, + $label, $lod_route_argument_list[0], + $lod_route_argument_list[1], $lod_route_argument_list[2], $len ?? 'en', 1); + } + catch (\Exception $exception) { + $message = t('@exception_type thrown in @file:@line while querying for @entity_type entity ids matching "@label". Message: @response', + [ + '@exception_type' => get_class($exception), + '@file' => $exception->getFile(), + '@line' => $exception->getLine(), + '@label' => $label, + '@response' => $exception->getMessage(), + ]); + \Drupal::logger('ami')->warning($message); + return NULL; + } + + if (!empty($lod)) { + return $lod; + } + return NULL; + } +} From 56558aa765c02067f10434a76e88a18254c29125 Mon Sep 17 00:00:00 2001 From: Diego Pino Navarro Date: Fri, 25 Mar 2022 18:13:37 -0300 Subject: [PATCH 2/4] Update ami.services.yml --- ami.services.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ami.services.yml b/ami.services.yml index 1dd7925..1b55397 100644 --- a/ami.services.yml +++ b/ami.services.yml @@ -13,3 +13,7 @@ services: arguments: [ '@file_system', '@file.usage', '@entity_type.manager', '@stream_wrapper_manager', '@plugin.manager.archiver', '@config.factory', '@current_user', '@language_manager', '@transliteration', '@module_handler', '@logger.factory', '@strawberryfield.utility', '@http_client', '@keyvalue'] tags: - { name: backend_overridable } + ami.twig.TwigExtension: + class: Drupal\ami\TwigExtension + tags: + - { name: twig.extension } \ No newline at end of file From e741d4b82ed67d334cab9ba04ef9e8f79bb8fd5b Mon Sep 17 00:00:00 2001 From: Diego Pino Navarro Date: Fri, 25 Mar 2022 18:13:51 -0300 Subject: [PATCH 3/4] Small sanity fix in case there is no data --- src/AmiUtilityService.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AmiUtilityService.php b/src/AmiUtilityService.php index ba39f42..867b780 100644 --- a/src/AmiUtilityService.php +++ b/src/AmiUtilityService.php @@ -1011,7 +1011,7 @@ public function csv_read(File $file, int $offset = 0, int $count = 0, bool $alwa $highestRow = count($data); if ($always_include_header) { - $rowHeaders = $data[0]; + $rowHeaders = $data[0] ?? []; $rowHeaders_utf8 = array_map('stripslashes', $rowHeaders); $rowHeaders_utf8 = array_map('utf8_encode', $rowHeaders_utf8); $rowHeaders_utf8 = array_map('strtolower', $rowHeaders_utf8); From 32d5ee6a0f3a0d590fb1ffba9f03bdd9db16222b Mon Sep 17 00:00:00 2001 From: Diego Pino Navarro Date: Fri, 25 Mar 2022 18:14:19 -0300 Subject: [PATCH 4/4] Adds extra context (data_lod) to preview and is more explicit --- src/Controller/AmiRowAutocompleteHandler.php | 317 ++++++++++++------- 1 file changed, 194 insertions(+), 123 deletions(-) diff --git a/src/Controller/AmiRowAutocompleteHandler.php b/src/Controller/AmiRowAutocompleteHandler.php index 3cc205b..e6bc02d 100644 --- a/src/Controller/AmiRowAutocompleteHandler.php +++ b/src/Controller/AmiRowAutocompleteHandler.php @@ -138,10 +138,15 @@ public static function ajaxPreviewAmiSet($form, FormStateInterface $form_state) /** @var \Drupal\format_strawberryfield\MetadataDisplayInterface $entity */ $entity = $form_state->getFormObject()->getEntity(); + // Attach the library necessary for using the OpenOffCanvasDialogCommand and // set the attachments for this Ajax response. $form['#attached']['library'][] = 'core/drupal.dialog.off_canvas'; $form['#attached']['library'][] = 'codemirror_editor/editor'; + $form['#attached']['library'][] = 'core/jquery'; + $form['#attached']['library'][] = 'core/jquery.form'; + $form['#attached']['library'][] = 'core/drupal.dialog.ajax'; + $form['#attached']['library'][] = 'core/drupal.ajax'; $response->setAttachments($form['#attached']); $row = $form_state->getValues()['ado_amiset_row_context_preview'] ?? 1; $row = (int) $row; @@ -159,159 +164,227 @@ public static function ajaxPreviewAmiSet($form, FormStateInterface $form_state) } // Now get the row, if not passed we will get the first because we are // weird. - error_log(PHP_VERSION_ID); $csv_file_reference = $preview_ami_set->get('source_data')->getValue(); if (isset($csv_file_reference[0]['target_id'])) { /** @var \Drupal\file\Entity\File $file */ $file = \Drupal::entityTypeManager()->getStorage('file')->load( $csv_file_reference[0]['target_id'] ); - if (PHP_VERSION_ID < 80000) { + if (PHP_VERSION_ID < 81000) { //@TODO fgetcsv has a bug when called after a seek, offsets on 1 always. // We are trying to skip the header too (but get it) - $row = $row - 2; + // Tested on 80016 and error is still in place. + $row = $row - 2; + } + $file_data_all = NULL; + if ($file) { + $file_data_all = \Drupal::service('ami.utility') + ->csv_read($file, $row, 1, TRUE); } - $file_data_all = \Drupal::service('ami.utility') - ->csv_read($file, $row, 1, TRUE); - $jsondata = array_combine($file_data_all['headers'], reset($file_data_all['data'])); - $jsondata = \Drupal::service('ami.utility')->expandJson($jsondata); - // Check if render native is requested and get mimetype - $mimetype = $form_state->getValue('mimetype'); - $mimetype = !empty($mimetype) ? $mimetype[0]['value'] : 'text/html'; - $show_render_native = $form_state->getValue('render_native'); + if ($file_data_all !== NULL && !empty($file_data_all['data']) && is_array($file_data_all['data'])) { - // Set initial context. - $context = [ - 'node' => NULL, - 'iiif_server' => \Drupal::service('config.factory') - ->get('format_strawberryfield.iiif_settings') - ->get('pub_server_url'), - ]; + $jsondata = array_combine($file_data_all['headers'], + reset($file_data_all['data'])); + $jsondata = \Drupal::service('ami.utility')->expandJson($jsondata); + // Check if render native is requested and get mimetype + $mimetype = $form_state->getValue('mimetype'); + $mimetype = !empty($mimetype) ? $mimetype[0]['value'] : 'text/html'; + $show_render_native = $form_state->getValue('render_native'); - $context['data'] = $jsondata; + $context_lod = []; + $lod_mappings = \Drupal::service('ami.lod') + ->getKeyValueMappingsPerAmiSet($id); + if ($lod_mappings) { + foreach ($lod_mappings as $source_column => $destination) { + if (is_array($destination)) { + foreach ($destination as $pos_approach) { + $context_lod[$source_column][$pos_approach] = $context_lod[$source_column][$pos_approach] ?? []; + } + } - $output = []; - $output['json'] = [ - '#type' => 'details', - '#title' => t('JSON Data'), - '#open' => FALSE, - ]; - $output['json']['data'] = [ - '#type' => 'codemirror', - '#rows' => 60, - '#value' => json_encode($context['data'], JSON_PRETTY_PRINT), - '#codemirror' => [ - 'lineNumbers' => FALSE, - 'toolbar' => FALSE, - 'readOnly' => TRUE, - 'mode' => 'application/json', - ], - ]; + if (isset($jsondata[$source_column])) { + // sad here. Ok, this is a work around for our normally + // Strange CSV data structure + $data_to_clean['data'][0] = [$jsondata[$source_column]]; + $labels = \Drupal::service('ami.utility') + ->getDifferentValuesfromColumnSplit($data_to_clean, + 0); - // Try to Ensure we're using the twig from user's input instead of the entity's - // default. - try { - $input = $form_state->getUserInput(); - $entity->set('twig', $input['twig'][0], FALSE); - $render = $entity->renderNative($context); - if ($show_render_native) { - $message = ''; - switch ($mimetype) { - case 'application/ld+json': - case 'application/json': - json_decode((string) $render); - if (JSON_ERROR_NONE !== json_last_error()) { - throw new \Exception( - 'Error parsing JSON: ' . json_last_error_msg(), - 0, - NULL - ); + foreach ($labels as $label) { + $lod_for_label = \Drupal::service('ami.lod') + ->getKeyValuePerAmiSet($label, $id); + if (is_array($lod_for_label) && count($lod_for_label) > 0) { + foreach ($lod_for_label as $approach => $lod) { + if (isset($lod['lod'])) { + $context_lod[$source_column][$approach] = array_merge($context_lod[$source_column][$approach] ?? [], + $lod['lod']); + } + } + } } - break; - case 'text/html': - libxml_use_internal_errors(TRUE); - $dom = new \DOMDocument('1.0', 'UTF-8'); - if ($dom->loadHTML((string) $render)) { - if ($error = libxml_get_last_error()) { - libxml_clear_errors(); - $message = $error->message; + } + } + } + + + // Set initial context. + $context = [ + 'node' => NULL, + 'iiif_server' => \Drupal::service('config.factory') + ->get('format_strawberryfield.iiif_settings') + ->get('pub_server_url'), + ]; + + $context['data'] = $jsondata; + $context['data_lod'] = $context_lod; + $output = []; + $output['json'] = [ + '#type' => 'details', + '#title' => t('JSON Data'), + '#open' => FALSE, + ]; + $output['json']['data'] = [ + '#title' => t('Your row data. e.g {{ data.keyname }} :'), + '#type' => 'codemirror', + '#rows' => 60, + '#value' => json_encode($context['data'], JSON_PRETTY_PRINT), + '#codemirror' => [ + 'lineNumbers' => FALSE, + 'toolbar' => FALSE, + 'readOnly' => TRUE, + 'mode' => 'application/json', + ], + ]; + $output['json']['data_lod'] = [ + '#type' => 'codemirror', + '#title' => t('Reconciliated LoD for this row {{ data_lod.keyname.lod_endpoint_type }} :'), + '#rows' => 60, + '#value' => json_encode($context['data_lod'], JSON_PRETTY_PRINT), + '#codemirror' => [ + 'lineNumbers' => FALSE, + 'toolbar' => FALSE, + 'readOnly' => TRUE, + 'mode' => 'application/json', + ], + ]; + + // Try to Ensure we're using the twig from user's input instead of the entity's + // default. + try { + $input = $form_state->getUserInput(); + $entity->set('twig', $input['twig'][0], FALSE); + $render = $entity->renderNative($context); + if ($show_render_native) { + $message = ''; + switch ($mimetype) { + case 'application/ld+json': + case 'application/json': + json_decode((string) $render); + if (JSON_ERROR_NONE !== json_last_error()) { + throw new \Exception( + 'Error parsing JSON: ' . json_last_error_msg(), + 0, + NULL + ); } break; - } - else { - throw new \Exception( - 'Error parsing HTML', - 0, - NULL - ); - } - case 'application/xml': - libxml_use_internal_errors(TRUE); - try { - libxml_clear_errors(); - $dom = new \SimpleXMLElement((string) $render); - if ($error = libxml_get_last_error()) { - $message = $error->message; + case 'text/html': + libxml_use_internal_errors(TRUE); + $dom = new \DOMDocument('1.0', 'UTF-8'); + if ($dom->loadHTML((string) $render)) { + if ($error = libxml_get_last_error()) { + libxml_clear_errors(); + $message = $error->message; + } + break; } - } catch (\Exception $e) { - throw new \Exception( - "Error parsing XML: {$e->getMessage()}", - 0, - NULL - ); - } - break; + else { + throw new \Exception( + 'Error parsing HTML', + 0, + NULL + ); + } + case 'application/xml': + libxml_use_internal_errors(TRUE); + try { + libxml_clear_errors(); + $dom = new \SimpleXMLElement((string) $render); + if ($error = libxml_get_last_error()) { + $message = $error->message; + } + } catch (\Exception $e) { + throw new \Exception( + "Error parsing XML: {$e->getMessage()}", + 0, + NULL + ); + } + break; + } + } + if (!$show_render_native || ($show_render_native && $mimetype != 'text/html')) { + $output['preview'] = [ + '#type' => 'codemirror', + '#title' => t('Processed Output:'), + '#rows' => 60, + '#value' => $render, + '#codemirror' => [ + 'lineNumbers' => FALSE, + 'toolbar' => FALSE, + 'readOnly' => TRUE, + 'mode' => $mimetype, + ], + ]; + } + else { + $output['preview'] = [ + '#type' => 'details', + '#open' => TRUE, + '#title' => 'HTML Output', + 'messages' => [ + '#markup' => $message, + '#attributes' => [ + 'class' => ['error'], + ], + ], + 'render' => [ + '#markup' => $render, + ], + ]; + } + } catch (\Exception $exception) { + // Make the Message easier to read for the end user + if ($exception instanceof TwigError) { + $message = $exception->getRawMessage() . ' at line ' . $exception->getTemplateLine(); + } + else { + $message = $exception->getMessage(); } - } - if (!$show_render_native || ($show_render_native && $mimetype != 'text/html')) { - $output['preview'] = [ - '#type' => 'codemirror', - '#rows' => 60, - '#value' => $render, - '#codemirror' => [ - 'lineNumbers' => FALSE, - 'toolbar' => FALSE, - 'readOnly' => TRUE, - 'mode' => $mimetype, - ], - ]; - } - else { $output['preview'] = [ '#type' => 'details', '#open' => TRUE, - '#title' => 'HTML Output', - 'messages' => [ + '#title' => t('Syntax error'), + 'error' => [ '#markup' => $message, - '#attributes' => [ - 'class' => ['error'], - ], - ], - 'render' => [ - '#markup' => $render, - ], + ] ]; } - } catch (\Exception $exception) { - // Make the Message easier to read for the end user - if ($exception instanceof TwigError) { - $message = $exception->getRawMessage() . ' at line ' . $exception->getTemplateLine(); - } - else { - $message = $exception->getMessage(); - } - + $response->addCommand(new OpenOffCanvasDialogCommand(t('Preview'), + $output, ['width' => '50%'])); + } + else { $output['preview'] = [ '#type' => 'details', '#open' => TRUE, - '#title' => t('Syntax error'), + '#title' => !$file ? t('AMI Set has no CSV File'): t('AMI Set has no data for chosen row.'), 'error' => [ '#markup' => $message, ] ]; + $response->addCommand(new OpenOffCanvasDialogCommand(t('Preview'), + $output, ['width' => '50%'])); } - $response->addCommand(new OpenOffCanvasDialogCommand(t('Preview'), - $output, ['width' => '50%'])); } } // Always refresh the Preview Element too. @@ -338,8 +411,6 @@ public static function rowAjaxCallback($form, FormStateInterface $form_state) { if ($id) { $form['preview']['ado_amiset_row_context_preview']['#autocomplete_route_parameters'] = ['ami_set_entity' => $id]; } - //$form_state->getUserInput()['ado_amiset_preview'] == name (id) - //$form_state->getValues()['ado_amiset_preview'] == id \Drupal::messenger()->deleteByType(MessengerInterface::TYPE_STATUS); $response->addCommand(new ReplaceCommand('#metadata-preview-container', $form['preview'])); return $response;