-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1853 from danskernesdigitalebibliotek/bnf-send
Integrate BNF Article Import/Export Functionality. DDFHER-164
- Loading branch information
Showing
22 changed files
with
630 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
# Bibliotekernes Nationale Formidling | ||
|
||
Bibliotekernes Nationale Formidling (henceforth "BNF") is a national | ||
team handling sharing of content for libraries. The `bnf_server` and | ||
`bnf_client` modules support their work. | ||
|
||
## Server module | ||
|
||
The server module is enabled on the main BNF site, which acts as a hub | ||
for content sharing. The BNF team uses this site to create and edit | ||
content provided for the libraries. | ||
|
||
## Client module | ||
|
||
The client module is enabled on library sites and handles pushing and | ||
fetching content to/from the BNF site. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
name: 'BNF Common' | ||
type: module | ||
description: 'Common code between BNF (Bibliotekernes Nationale Formidling) client and server modules' | ||
package: 'DPL - BNF' | ||
core_version_requirement: ^10 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
<?php | ||
|
||
use Drupal\bnf\BnfStateEnum; | ||
use Drupal\Core\Entity\EntityTypeInterface; | ||
use Drupal\Core\Field\BaseFieldDefinition; | ||
|
||
/** | ||
* Implements hook_entity_base_field_info(). | ||
* | ||
* Creating our custom programmatic fields. | ||
* | ||
* @return \Drupal\Core\Field\FieldDefinitionInterface[] | ||
* The field definitions. | ||
*/ | ||
function bnf_entity_base_field_info(EntityTypeInterface $entity_type): array { | ||
$fields = []; | ||
|
||
// Create new fields for node bundle. | ||
if ($entity_type->id() === 'node') { | ||
$fields[BnfStateEnum::FIELD_NAME] = BaseFieldDefinition::create('list_integer') | ||
->setName(BnfStateEnum::FIELD_NAME) | ||
->setLabel(t('BNF State')) | ||
->setDescription(t('The BNF state of the entity, defining if it was imported, exported, or neither.')) | ||
->setSetting('allowed_values_function', 'bnf_get_bnf_state_allowed_values') | ||
->setDefaultValue(BnfStateEnum::Undefined->value); | ||
} | ||
|
||
return $fields; | ||
} | ||
|
||
/** | ||
* Provides allowed values for the BNF State field. | ||
* | ||
* @return string[] | ||
* The enum values of BnfStateEnum. | ||
*/ | ||
function bnf_get_bnf_state_allowed_values(): array { | ||
$values = []; | ||
foreach (BnfStateEnum::cases() as $case) { | ||
$values[$case->value] = $case->name; | ||
} | ||
return $values; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
--- | ||
services: | ||
bnf.importer: | ||
class: Drupal\bnf\Services\BnfImporter | ||
autowire: true | ||
arguments: | ||
$logger: '@logger.channel.bnf' | ||
|
||
logger.channel.bnf: | ||
parent: logger.channel_base | ||
arguments: | ||
- 'bnf' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
name: 'BNF Client' | ||
type: module | ||
description: 'BNF (Bibliotekernes Nationale Formidling) client, for sharing content with BNF' | ||
package: 'DPL - BNF' | ||
core_version_requirement: ^10 | ||
dependencies: | ||
- bnf:bnf |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
<?php | ||
|
||
use Drupal\bnf\Exception\AlreadyExistsException; | ||
use Drupal\bnf_client\Services\BnfExporter; | ||
use Drupal\Core\Form\FormStateInterface; | ||
use Drupal\drupal_typed\DrupalTyped; | ||
|
||
/** | ||
* Implements hook_form_FORM_ID_alter(). | ||
* | ||
* Altering the node form, and adding an option to export the node to BNF. | ||
* If checked, a custom form submit handler will take care of the rest. | ||
* | ||
* @see bnf_client_form_node_form_submit() | ||
*/ | ||
function bnf_client_form_node_form_alter(array &$form, FormStateInterface $form_state, string $form_id): void { | ||
$current_user = \Drupal::currentUser(); | ||
|
||
if (!$current_user->hasPermission('bnf client export nodes')) { | ||
return; | ||
} | ||
|
||
if (empty($form['actions']['submit'])) { | ||
\Drupal::logger('bnf_client')->error('Could not find submit button - cannot show BNF export flow.'); | ||
return; | ||
} | ||
|
||
// When adding the submit handler here, it happens after ::save() and any | ||
// validation. | ||
$form['actions']['submit']['#submit'][] = 'bnf_client_form_node_form_submit'; | ||
|
||
// Let's hide the publishing button when BNF is checked, as the value will | ||
// be ignored and the node will be published regardless. | ||
$form['status']['#states']['invisible'] = | ||
[':input[name="bnf"]' => ['checked' => TRUE]]; | ||
|
||
$form['bnf'] = [ | ||
'#type' => 'checkbox', | ||
'#title' => t('Publish and submit to BNF'), | ||
'#description' => t('Please make sure that all content and media as part of this article is OK to be used by other libraries.'), | ||
'#default_value' => FALSE, | ||
]; | ||
} | ||
|
||
/** | ||
* A custom form submit handler, that publishes node, and "exports" to BNF. | ||
*/ | ||
function bnf_client_form_node_form_submit(array $form, FormStateInterface $form_state): void { | ||
|
||
if (empty($form_state->getValue('bnf'))) { | ||
return; | ||
} | ||
|
||
try { | ||
/** @var \Drupal\Core\Entity\EntityFormInterface $form_object */ | ||
$form_object = $form_state->getFormObject(); | ||
|
||
/** @var \Drupal\node\NodeInterface $node */ | ||
$node = $form_object->getEntity(); | ||
$node->setPublished(); | ||
$node->save(); | ||
} | ||
catch (\Exception $e) { | ||
\Drupal::logger('bnf_client')->error('Could not publish node as part of BNF export. @message', ['@message' => $e->getMessage()]); | ||
|
||
\Drupal::messenger()->addError( | ||
t("Could not publish node - will not send to BNF.", [], ['context' => 'BNF']) | ||
); | ||
} | ||
|
||
try { | ||
$service = DrupalTyped::service(BnfExporter::class, 'bnf_client.exporter'); | ||
$service->exportNode($node); | ||
|
||
\Drupal::messenger()->addStatus( | ||
t("Content has been published and sent to BNF.", [], ['context' => 'BNF']) | ||
); | ||
} | ||
catch (\Throwable $e) { | ||
\Drupal::logger('bnf_client')->error('Could not export node to BNF. @message', ['@message' => $e->getMessage()]); | ||
|
||
\Drupal::messenger()->addError(t('Could not export node to BNF.', [], ['context' => 'BNF'])); | ||
|
||
if ($e instanceof AlreadyExistsException) { | ||
\Drupal::messenger()->addError(t('Node already has been exported to BNF.', [], ['context' => 'BNF'])); | ||
} | ||
|
||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
bnf client export nodes: | ||
title: 'Can export nodes to BNF' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
--- | ||
services: | ||
bnf_client.exporter: | ||
class: Drupal\bnf_client\Services\BnfExporter | ||
autowire: true | ||
arguments: | ||
$logger: '@logger.channel.bnf' |
112 changes: 112 additions & 0 deletions
112
web/modules/custom/bnf/bnf_client/src/Services/BnfExporter.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
<?php | ||
|
||
namespace Drupal\bnf_client\Services; | ||
|
||
use Drupal\bnf\BnfStateEnum; | ||
use Drupal\bnf\Exception\AlreadyExistsException; | ||
use Drupal\Core\Routing\UrlGeneratorInterface; | ||
use Drupal\Core\StringTranslation\TranslationInterface; | ||
use Drupal\node\NodeInterface; | ||
use GuzzleHttp\ClientInterface; | ||
use Psr\Log\LoggerInterface; | ||
use function Safe\json_decode; | ||
use function Safe\parse_url; | ||
|
||
/** | ||
* Service, related to exporting our content to BNF. | ||
* | ||
* We send an Import Request to BNF using GraphQL, along with information | ||
* about the content and where they can access it using our GraphQL endpoint. | ||
*/ | ||
class BnfExporter { | ||
|
||
/** | ||
* Constructor. | ||
*/ | ||
public function __construct( | ||
protected ClientInterface $httpClient, | ||
protected UrlGeneratorInterface $urlGenerator, | ||
protected TranslationInterface $translation, | ||
protected LoggerInterface $logger, | ||
) {} | ||
|
||
/** | ||
* Requesting BNF server to import the supplied node. | ||
*/ | ||
public function exportNode(NodeInterface $node): void { | ||
// generateFromRoute returns a string if we do not pass TRUE as the | ||
// fourth argument. | ||
/** @var string $callbackUrl */ | ||
$callbackUrl = $this->urlGenerator->generateFromRoute( | ||
'graphql.query.graphql_compose_server', | ||
[], | ||
['absolute' => TRUE] | ||
); | ||
|
||
$uuid = $node->uuid(); | ||
|
||
$mutation = <<<GRAPHQL | ||
mutation { | ||
importRequest(uuid: "$uuid", callbackUrl: "$callbackUrl") { | ||
status | ||
message | ||
} | ||
} | ||
GRAPHQL; | ||
|
||
try { | ||
$bnfServer = (string) getenv('BNF_SERVER_GRAPHQL_ENDPOINT'); | ||
|
||
if (!filter_var($bnfServer, FILTER_VALIDATE_URL)) { | ||
throw new \InvalidArgumentException('The provided BNF server URL is not valid.'); | ||
} | ||
|
||
$parsedUrl = parse_url($bnfServer); | ||
$scheme = $parsedUrl['scheme'] ?? NULL; | ||
|
||
if ($scheme !== 'https') { | ||
throw new \InvalidArgumentException('The BNF server URL must use HTTPS.'); | ||
} | ||
|
||
$response = $this->httpClient->request('post', $bnfServer, [ | ||
'headers' => [ | ||
'Content-Type' => 'application/json', | ||
], | ||
'auth' => [getenv('GRAPHQL_USER_NAME'), getenv('GRAPHQL_USER_PASSWORD')], | ||
'json' => [ | ||
'query' => $mutation, | ||
], | ||
]); | ||
|
||
$data = json_decode($response->getBody()->getContents(), TRUE); | ||
} | ||
catch (\Exception $e) { | ||
$this->logger->error( | ||
'Failed at exporting node to BNF server. @message', | ||
['@message' => $e->getMessage()]); | ||
|
||
throw new \Exception('Could not export node to BNF.'); | ||
} | ||
|
||
$status = $data['data']['importRequest']['status'] ?? NULL; | ||
|
||
if ($status !== 'success') { | ||
$message = $data['data']['importRequest']['message'] ?? NULL; | ||
|
||
$this->logger->error( | ||
'Failed at exporting node to BNF server. @message', | ||
['@message' => $message]); | ||
|
||
if ($status === 'duplicate') { | ||
throw new AlreadyExistsException(); | ||
} | ||
|
||
throw new \Exception($message); | ||
} | ||
|
||
$node->set(BnfStateEnum::FIELD_NAME, BnfStateEnum::Exported->value); | ||
$node->save(); | ||
|
||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
name: 'BNF Server' | ||
type: module | ||
description: 'BNF (Bibliotekernes Nationale Formidling) server' | ||
package: 'DPL - BNF' | ||
core_version_requirement: ^10 | ||
dependencies: | ||
- bnf:bnf |
1 change: 1 addition & 0 deletions
1
web/modules/custom/bnf/bnf_server/graphql/import_request_extension.base.graphqls
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
# Base file, necessary for GraphQL module to load our extension. |
8 changes: 8 additions & 0 deletions
8
web/modules/custom/bnf/bnf_server/graphql/import_request_extension.extension.graphqls
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
extend type Mutation { | ||
importRequest(uuid: String!, callbackUrl: String!): ImportRequestResponse | ||
} | ||
|
||
type ImportRequestResponse { | ||
status: String | ||
message: String | ||
} |
Oops, something went wrong.