ww_publish-8.x-1.x-dev/src/Article.php

src/Article.php
<?php

namespace Drupal\ww_publish;

use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException;
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\Core\Entity\EntityStorageException;
use Drupal\Core\Utility\Error;
use Drupal\field\FieldConfigInterface;
use Drupal\node\Entity\Node;
use Drupal\node\NodeInterface;
use Drupal\ww_publish\Entity\SnsMessageEntityInterface;
use Drupal\ww_publish\Events\ArticleFieldsEvent;
use Drupal\ww_publish\Events\PreImportEvent;

class Article {

  /**
   * The SNS message.
   *
   * @var \Drupal\ww_publish\Message
   */
  protected $message;

  /**
   * Metadata of the article received from WoodWing Studio.
   *
   * @var \Drupal\ww_publish\Metadata
   */
  private $articleMetadata;

  /**
   * Article data received from WoodWing Studio.
   *
   * @var Object
   */
  private $articleData;

  /**
   * Configuration of the ww_publish module.
   *
   * @var \Drupal\Core\Config\Config
   */
  private $config;

  /**
   * A logger instance.
   *
   * @var \Psr\Log\LoggerInterface
   */
  private $logger;

  /**
   * Content type in which the article should be saved.
   *
   * @var \Drupal\node\Entity\NodeType
   */
  private $contentType;

  /**
   * WoodWing article ID.
   *
   * @var int
   */
  private $id;

  /**
   * WoodWing Studio article ID field.
   *
   * @var string
   */
  private $articleIdField;

  /**
   * Paragraph field.
   *
   * @var string
   */
  private $paragraphField;

  /**
   * The target node which will be created or updated.
   *
   * @var \Drupal\node\Entity\Node
   */
  private $node;

  /**
   * Constructor.
   *
   * @param \Drupal\ww_publish\Message $message
   *   SNS Message.
   * @param \Drupal\Core\Config\Config $config
   *   Configuration of the ww_publish module.
   * @param \Psr\Log\LoggerInterface $logger
   *   A logger instance.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  public function __construct(Message $message, $config, $logger) {
    $this->message = $message;
    $this->articleMetadata = $message->getArticleMetadata();
    $this->articleData = $message->getArticleData();
    $this->config = $config;
    $this->logger = $logger;
    $this->contentType = $this->getContentType();
    $this->paragraphField = $this->getContentTypeSetting('paragraph_field');
  }

  /**
   * Publish a new node or update an existing node.
   *
   * @return bool
   * @throws \Drupal\Core\Entity\EntityStorageException
   */
  public function publish(): int {
    if ($this->config->get('debug_mode'))
      $this->logger->debug('Publish!');

    $pre_import_event = $this->findRelatedNode();
    if ($pre_import_event->isSkip()) {
      $this->logger->notice('Import of @ww_id has been skipped.', [
        '@ww_id' => $this->id,
      ]);
      return SnsMessageEntityInterface::SKIPPED;
    }
    $this->node = $pre_import_event->getNode();
    if (!$this->node->isNew()) {
      return $this->update() ? SnsMessageEntityInterface::IMPORTED : SnsMessageEntityInterface::FAILED;
    }
    else {
      return $this->create() ? SnsMessageEntityInterface::IMPORTED : SnsMessageEntityInterface::FAILED;
    }
  }

  /**
   * Create a new node.
   *
   * @return bool
   * @throws \Drupal\Core\Entity\EntityStorageException|\Drupal\Core\TypedData\Exception\ReadOnlyException
   */
  private function create(): bool {
    try {
      $this->updateFields();

      // Test if during updating the fields a related node was created.
      $pre_import_event = $this->findRelatedNode();

      // Prevent creation of a duplicated node, when Amazon sends more request at the same time.
      if (!$pre_import_event->getNode() || $pre_import_event->getNode()->isNew()) {
        if ($this->config->get('debug_mode'))
          $this->logger->debug('Node to be created: <pre><code>@node</code></pre>', ['@node' => print_r($this->node->toArray(), TRUE)]);
        $this->node->save();
        $this->logger->notice('The following article has been created: @node_title (WW ID: @ww_id, Drupal ID: @node_id)', ['@node_title' => $this->node->getTitle(), '@ww_id' => $this->id, '@node_id' => $this->node->id()]);
      } else if ($pre_import_event->getNode() && !$pre_import_event->getNode()) {
        $this->logger->error('The node already exists. Probably has Amazon sent more requests at the same time.');
      } else {
        $this->logger->error('There are more than one nodes with the same WoodWing Studio article ID.');
      }
    }
    catch (EntityStorageException $e) {
      $variables = Error::decodeException($e);
      $this->logger->error('A node could not be created. @message in %function (line %line of %file).', $variables);
      return FALSE;
    }

    return TRUE;
  }

  /**
   * Update an existing node.
   *
   * @return bool
   * @throws \Drupal\Core\Entity\EntityStorageException
   */
  private function update(): bool {
    if ($this->node instanceof NodeInterface) {
      try {
        $this->updateFields();
        $this->node->setRevisionUserId($this->articleMetadata->getAuthor()->id());

        if ($this->config->get('debug_mode'))
          $this->logger->debug('Node to be updated: <pre><code>@node</code></pre>', ['@node' => print_r($this->node->toArray(), TRUE)]);

        $this->node->save();
        $this->logger->notice('The following article has been updated: @node_title (WW ID: @ww_id, Drupal ID: @node_id)', ['@node_title' => $this->node->getTitle(), '@ww_id' => $this->id, '@node_id' => $this->node->id()]);
      }
      catch (\Exception $e) {
        $variables = Error::decodeException($e);
        $this->logger->error('The node could not be updated. @message in %function (line %line of %file).', $variables);
        return FALSE;
      }
    }

    return TRUE;
  }

  /**
   * Update fields.
   *
   * @throws \Drupal\Core\TypedData\Exception\ReadOnlyException
   */
  private function updateFields() {
    // Define an empty error messages field.
    $messagesFieldName = $this->config->get('error_messages_field');
    $messagesField = [];
    // Update title and owner.
    $title = $this->getTitle();
    $this->node->set('title', $title);
    $this->node->setOwnerId($this->articleMetadata->getAuthor());

    $fields = $this->getContentTypeFields();
    foreach ($fields as $fieldName => $fieldConfig) {
      $contentStationIdentifier = $fieldConfig->getThirdPartySetting('ww_publish', 'content_station_identifier');
      if ($contentStationIdentifier) {
        $field = new Field($fieldConfig, $contentStationIdentifier, $this->message, $this->config, $this->logger);
        $articleData = $field->getArticleData($messagesField);
        if ($this->config->get('debug_mode'))
          $this->logger->debug($fieldName . ': <pre><code>@article_data</code></pre>', ['@article_data' => print_r($articleData, TRUE)]);
        $this->node->set($fieldName, $articleData);
      }
      if ($fieldName === $this->paragraphField) {
        $paragraphField = new ParagraphField($fieldConfig, $this->message, $this->articleData->data->content, $this->config, $this->logger);
        $this->node->set($fieldName, $paragraphField->getArticleData($messagesField));
      }
    }

    $articleFieldsEvent = new ArticleFieldsEvent($this->message, $this->node);
    \Drupal::service('event_dispatcher')->dispatch($articleFieldsEvent);

    if ($messagesFieldName) {
      \Drupal::service('account_switcher')->switchTo($this->node->getOwner());
      $violations = $this->node->validate();
      foreach ($violations as $violation) {
        $messagesField[] = $violation->getPropertyPath() . ': ' . $violation->getMessage();
      }

      // Also validate all paragraphs.
      if ($this->paragraphField) {
        foreach ($this->node->get($this->paragraphField) as $delta => $item) {
          $violations = $item->entity->validate();
          foreach ($violations as $violation) {
            $messagesField[] = $this->paragraphField . '.' . $delta . '.' . $violation->getPropertyPath() . ': ' . $violation->getMessage();
          }
        }
      }

      \Drupal::service('account_switcher')->switchBack();
    }

    if ($messagesFieldName)
      $this->node->get($messagesFieldName)->setValue($messagesField);
  }

  /**
   * Get content type fields.
   *
   * @return array
   */
  private function getContentTypeFields(): array {
    $contentType = $this->contentType->get('type');

    $entityFieldManager = \Drupal::service('entity_field.manager');
    $fields = [];

    if(!empty($contentType)) {
      $fields = array_filter(
        $entityFieldManager->getFieldDefinitions('node', $contentType),
        function ($field_definition) {
          return
            $field_definition instanceof FieldConfigInterface;
        }
      );
    }

    return $fields;
  }

  /**
   * Get the target content type.
   *
   * @return mixed
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  private function getContentType() {
    $contentTypeMachineName = $this->articleMetadata->getMetadataByIdentifier('ExtraMetaData', $this->config->get('content_type_field'));
    if ($contentTypeMachineName) {
      if ($this->config->get('debug_mode')) {
        $this->logger->debug('Target content type: <pre><code>@content_type_name</code></pre>', ['@content_type_name' => print_r($contentTypeMachineName, TRUE)]);
      }
      // Get all content types.
      try {
        $types = \Drupal::entityTypeManager()
          ->getStorage('node_type')
          ->loadMultiple();
      } catch (InvalidPluginDefinitionException $e) {
        $this->logger->error('Could not get content types.');
        return FALSE;
      } catch (PluginNotFoundException $e) {
        $this->logger->error('Could not get content types.');
        return FALSE;
      }

      // Test if the target content type exists in Drupal.
      if ($this->config->get('debug_mode'))
        $this->logger->debug('Existing content types: <pre><code>@types</code></pre>', ['@types' => print_r($types, TRUE)]);
      if ($types[$contentTypeMachineName]) {
        // TODO: Test if the content type is correctly configured.
        if ($this->config->get('debug_mode'))
          $this->logger->debug('The target content type exists in Drupal and is correctly configured.');

        return \Drupal::entityTypeManager()
          ->getStorage('node_type')
          ->load($contentTypeMachineName);

      } else {
        $this->logger->error('The content type does not exist in Drupal.');
        return FALSE;
      }
    } else {
      $this->logger->error('The content type field could not be found in the metadata.');
      return FALSE;
    }
  }

  /**
   * Get the ID of the WoodWing Studio article.
   *
   * @return int|false
   *   Either the woodwing article ID or FALSE if no ID could be found.
   */
  private function getId() {
    if ($id = $this->articleMetadata->getMetadataByIdentifier('BasicMetaData', 'ID')) {
      if ($this->config->get('debug_mode')) {
        $this->logger->debug('WoodWing Studio article ID: <pre><code>@id</code></pre>', ['@id' => print_r($id)]);
      }
      return (int) $id;
    } else {
      $this->logger->error('No article ID found in metadata.');
      return FALSE;
    }
  }

  /**
   * Get the title of the WoodWing Studio article.
   *
   * @return mixed
   * @throws \Drupal\Core\Entity\EntityStorageException
   */
  private function getTitle() {
    // Get the WoodWing Studio identifier for the title from content type configuration.
    $contentStationIdentifier = $this->contentType->getThirdPartySetting('ww_publish', 'content_station_identifier_title');

    $contentType = $this->contentType->get('type');

    $entityFieldManager = \Drupal::service('entity_field.manager');

    // Get field configuration for the field title.
    $fieldConfig = $entityFieldManager->getFieldDefinitions('node', $contentType)['title'];

    if ($contentStationIdentifier && $fieldConfig) {
      $field = new Field($fieldConfig, $contentStationIdentifier, $this->message, $this->config, $this->logger);
      $title = $field->getArticleData();

      if (\is_array($title) && !empty($title['value'])) {
        $title = $title['value'];
      }

      if (!$this->config->get('html_title')) {
        $title = strip_tags($title);
      }

      if ($this->config->get('debug_mode'))
        $this->logger->debug('Title: <pre><code>@title</code></pre>', ['@title' => print_r($title, TRUE)]);
      return $title;
    }

    $this->logger->error('No article title found.');
    return FALSE;
  }

  /**
   * Get the ww_publish setting from content type configuration.
   *
   * @param string $settingName
   *   Setting name, e.g.: 'article_id_field', 'paragraph_field'.
   * @param boolean $logMissing
   *   Log a notice if a setting is not set.
   *
   * @return mixed
   */
  private function getContentTypeSetting($settingName, $logMissing = TRUE) {
    $setting = $this->contentType->getThirdPartySetting('ww_publish', $settingName);

    if ($setting) {
      return $setting;
    }
    elseif ($logMissing) {
      $this->logger->notice('The content type setting @setting_name is not set.', ['@setting_name' => $settingName]);
    }
    return NULL;
  }

  /**
   * Find the related node.
   *
   * @return \Drupal\ww_publish\Events\PreImportEvent
   *   Returns the pre import event object. If a node is set than that should
   *   be updated, if not then an ew one should be created and if marked as skip
   *   then the import should be skipped.
   */
  private function findRelatedNode() {

    $pre_import_event = new PreImportEvent($this->message);

    try {

      $storyid = $this->articleMetadata->getMetadataByIdentifier('BasicMetaData', 'StoryId');
      $storyid_field = $this->getContentTypeSetting('article_storyid_field', FALSE);
      $id_field = $this->getContentTypeSetting('article_id_field', FALSE);

      if (!$storyid_field && !$id_field) {
        throw new \Exception('Either storyid or id field needs to be configured for content type ' . $this->contentType->id());
      }

      $storage = \Drupal::entityTypeManager()
        ->getStorage('node');

      // First, look up by StoryID if configured and one is available.
      $nodes = [];
      if ($storyid && $storyid_field) {

        $this->id = $storyid;
        $this->articleIdField = $storyid_field;
        $nodes = $storage->loadByProperties([$storyid_field => $this->id]);
      }
      // Fall back to the ID field if the story ID is either not present or
      // no field is configured for it.
      if ($id_field) {
        $id = $this->getId();
        $nodes = $storage->loadByProperties([$id_field => $id]);
        if (!$this->id) {
          $this->id = $id;
        }
      }

      // Test if the node exists.
      if (($node = reset($nodes)) && count($nodes) === 1) {
        // If a StoryID exists but the field is empty then this is
        // being upgraded from an ID reference, set the story id on the existing
        // node.
        if ($storyid && $storyid_field && $node->get($storyid_field)->value != $storyid) {
          $node->set($storyid_field, $storyid);
        }

        $pre_import_event->setNode($node);
      }
      elseif (count($nodes) > 1) {
        $this->logger->error('There is more than one node with the same WoodWing Studio article ID.');
        $pre_import_event->setSkip(TRUE);
      }
      else {
        // Create a new node with the correct ID field.
        $node = Node::create([
          'type' => $this->contentType->id(),
        ]);
        if ($storyid_field && $storyid) {
          $node->set($storyid_field, $storyid);
        }
        elseif ($id_field) {
          $node->set($id_field, $this->id);
        }
        $pre_import_event->setNode($node);
      }
    } catch (\Exception $e) {
      $this->logger->error($e->getMessage());
      $pre_import_event->setSkip(TRUE);
    }

    \Drupal::service('event_dispatcher')->dispatch($pre_import_event);
    return $pre_import_event;
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc