From c073ca81546d41b39c9fc34d108b6f8e887bd8eb Mon Sep 17 00:00:00 2001 From: Benjamin Rasmussen Date: Tue, 17 Dec 2024 14:31:54 +0100 Subject: [PATCH] Update BnfImporter to also import text paragraphs. This feels a bit dirty, but for now, until we've cleared how to deal with GraphQL schemas/codegen, it'll do. Only text_body paragraphs are imported. --- .../custom/bnf/src/Services/BnfImporter.php | 145 ++++++++++++++++-- 1 file changed, 133 insertions(+), 12 deletions(-) diff --git a/web/modules/custom/bnf/src/Services/BnfImporter.php b/web/modules/custom/bnf/src/Services/BnfImporter.php index f836d5437..3f18f5e68 100644 --- a/web/modules/custom/bnf/src/Services/BnfImporter.php +++ b/web/modules/custom/bnf/src/Services/BnfImporter.php @@ -5,10 +5,12 @@ use Drupal\bnf\Exception\AlreadyExistsException; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\StringTranslation\TranslationInterface; +use Drupal\paragraphs\ParagraphInterface; use GuzzleHttp\ClientInterface; use Psr\Log\LoggerInterface; use function Safe\json_decode; use function Safe\parse_url; +use function Safe\preg_replace; /** * Service related to importing content from an external source. @@ -21,6 +23,15 @@ class BnfImporter { const UUID_FIELD = 'bnf_uuid'; + const ALLOWED_PARAGRAPHS = [ + 'text_body', + ]; + + /** + * The BNF UUID of the content that we import. + */ + protected string $uuid; + /** * Constructor. */ @@ -31,10 +42,129 @@ public function __construct( protected LoggerInterface $logger, ) {} + /** + * Building the query we use to get data from source. + */ + protected function getQuery(string $queryName): string { + // Example of GraphQL query: "nodeArticle". + // For now, we only support the title of the nodes. + return <<uuid") { + title + paragraphs { + ... on ParagraphTextBody { + __typename + body { + format, + value + } + } + } + } + } + GRAPHQL; + + } + + /** + * Parses paragraphs from GraphQL node data into Drupal-compatible structures. + * + * @param mixed[] $nodeData + * The GraphQL node data containing paragraphs. + * + * @return mixed[] + * Array of paragraph values, that we can use to create paragraph entities. + */ + protected function parseParagraphs(array $nodeData) { + $parsedParagraphs = []; + + // Ensure paragraphs exist in the GraphQL response. + if (empty($nodeData['paragraphs'])) { + return $parsedParagraphs; + } + + foreach ($nodeData['paragraphs'] as $paragraphData) { + $type = $paragraphData['__typename'] ?? ''; + + // Convert typename to Drupal paragraph bundle name. + $bundleName = $this->graphqlTypeToBundle($type); + + if (!in_array($bundleName, self::ALLOWED_PARAGRAPHS)) { + continue; + } + + $paragraph = ['type' => $bundleName]; + + // Map fields dynamically. + foreach ($paragraphData as $key => $value) { + if ($key === '__typename') { + continue; + } + + // Assume Drupal uses field names like "field_{key}". + $drupalFieldName = 'field_' . $key; + $paragraph[$drupalFieldName] = $value; + } + + $parsedParagraphs[] = $paragraph; + } + + return $parsedParagraphs; + } + + /** + * Creating the paragraphs, that we will add to the nodes. + * + * @param mixed[] $nodeData + * The GraphQL node data containing paragraphs. + * + * @return \Drupal\paragraphs\ParagraphInterface[] + * The paragraph entities. + */ + protected function getParagraphs(array $nodeData): array { + $parsedParagraphs = $this->parseParagraphs($nodeData); + $storage = $this->entityTypeManager->getStorage('paragraph'); + $paragraphs = []; + foreach ($parsedParagraphs as $paragraphData) { + $paragraph = $storage->create($paragraphData); + + if ($paragraph instanceof ParagraphInterface) { + $paragraph->save(); + $paragraphs[] = $paragraph; + } + } + + return $paragraphs; + } + + /** + * Converts a GraphQL typename to a Drupal paragraph bundle name. + * + * @param string $typeName + * The GraphQL typename (e.g., ParagraphTextBody). + * + * @return string + * The Drupal paragraph bundle name (e.g., text_body). + */ + protected function graphqlTypeToBundle(string $typeName): string { + // Removing 'Paragraph' prefix. + $typeName = preg_replace('/^Paragraph/', '', $typeName); + + // Converting CamelCase to snake_case. + $pattern = '/(?<=\\w)(?=[A-Z])|(?<=[a-z])(?=[0-9])/'; + $typeName = preg_replace($pattern, '_', $typeName); + + return strtolower($typeName); + } + /** * Importing a node from a GraphQL source endpoint. */ public function importNode(string $uuid, string $endpointUrl, string $nodeType = 'article'): void { + $this->uuid = $uuid; + $queryName = 'node' . ucfirst($nodeType); + $nodeStorage = $this->entityTypeManager->getStorage('node'); $existingNodes = @@ -49,18 +179,6 @@ public function importNode(string $uuid, string $endpointUrl, string $nodeType = throw new AlreadyExistsException('Cannot import node - already exists.'); } - // Example of GraphQL query: "nodeArticle". - $queryName = 'node' . ucfirst($nodeType); - - // For now, we only support the title of the nodes. - $query = <<getQuery($queryName); + $response = $this->httpClient->request('post', $endpointUrl, [ 'headers' => [ 'Content-Type' => 'application/json', @@ -96,6 +216,7 @@ public function importNode(string $uuid, string $endpointUrl, string $nodeType = try { $nodeData['type'] = $nodeType; $nodeData[self::UUID_FIELD] = $uuid; + $nodeData['field_paragraphs'] = $this->getParagraphs($nodeData); $node = $nodeStorage->create($nodeData); $node->save();