diff --git a/web/modules/custom/bnf/src/Services/BnfImporter.php b/web/modules/custom/bnf/src/Services/BnfImporter.php index 138db8519..eae2043fa 100644 --- a/web/modules/custom/bnf/src/Services/BnfImporter.php +++ b/web/modules/custom/bnf/src/Services/BnfImporter.php @@ -4,12 +4,17 @@ use Drupal\bnf\BnfStateEnum; use Drupal\bnf\Exception\AlreadyExistsException; +use Drupal\Core\Entity\EntityFieldManagerInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\StringTranslation\TranslationInterface; +use Drupal\field\Entity\FieldConfig; +use Drupal\field\Entity\FieldStorageConfig; +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. @@ -20,20 +25,224 @@ */ class BnfImporter { + const ALLOWED_PARAGRAPHS = [ + 'ParagraphTextBody' => 'text_body', + 'ParagraphAccordion' => 'accordion', + ]; + + /** + * The BNF UUID of the content that we import. + */ + protected string $uuid; + /** * Constructor. */ public function __construct( protected ClientInterface $httpClient, + protected EntityFieldManagerInterface $entityFieldManager, protected EntityTypeManagerInterface $entityTypeManager, protected TranslationInterface $translation, protected LoggerInterface $logger, ) {} + /** + * Loading the columns of a field, that we use to ask GraphQL for data. + * + * E.g. a WYSIWYG field will have both a "value" and a "format" that we want + * to pull out. + * + * @return mixed[] + * Return an array of fields, along with their column keys. + */ + protected function getFieldColumns(string $entityType, string $bundle): array { + $values = []; + $fields = []; + $fieldDefinitions = $this->entityFieldManager->getFieldDefinitions($entityType, $bundle); + + foreach ($fieldDefinitions as $fieldKey => $fieldDefinition) { + if ($fieldDefinition instanceof FieldConfig) { + $fields[] = $fieldKey; + } + } + + foreach ($fields as $fieldKey) { + $field = $this->entityTypeManager->getStorage('field_storage_config')->load("$entityType.$fieldKey"); + + if ($field instanceof FieldStorageConfig) { + $values[$fieldKey] = array_keys($field->getColumns()); + } + } + + return $values; + } + + /** + * Builds the query used to get data from the source. + */ + public function getQuery(string $queryName): string { + // Start building the GraphQL query. + $query = <<uuid") { + title + paragraphs { + + GRAPHQL; + + // Loop through allowed paragraphs and add their structures. + foreach (self::ALLOWED_PARAGRAPHS as $graphBundle => $drupalBundle) { + $query .= <<getFieldColumns('paragraph', $drupalBundle); + foreach ($fieldColumns as $fieldKey => $columns) { + $fieldKey = $this->drupalFieldToGraphField($fieldKey); + + $columnsString = implode("\r\n ", $columns); + $query .= << $bundleName]; + + // Map fields dynamically. + foreach ($paragraphData as $key => $value) { + if ($key === '__typename') { + continue; + } + + // Assume Drupal uses field names like "field_{key}". + $drupalFieldName = $this->graphFieldToDrupalField($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->parseGraphParagraphs($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; + } + /** * 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 = @@ -48,18 +257,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', @@ -87,7 +286,10 @@ public function importNode(string $uuid, string $endpointUrl, string $nodeType = $nodeData = $data['data'][$queryName] ?? NULL; if (empty($nodeData)) { - $this->logger->error('Could not find any node data in GraphQL response.'); + $this->logger->error( + 'Could not find any node data in GraphQL response. @query', + ['@query' => $queryName] + ); throw new \Exception('Could not retrieve content values.'); } @@ -95,6 +297,7 @@ public function importNode(string $uuid, string $endpointUrl, string $nodeType = try { $nodeData['type'] = $nodeType; $nodeData['uuid'] = $uuid; + $nodeData['field_paragraphs'] = $this->getParagraphs($nodeData); $node = $nodeStorage->create($nodeData); $node->save();