You are here

class EntityRenderHandler in Acquia Lift Connector 8.4

Class EntityRenderHandler.

@package Drupal\acquia_lift_publisher\EventSubscriber\Cdf

Hierarchy

Expanded class hierarchy of EntityRenderHandler

1 file declares its use of EntityRenderHandler
EntityRenderHandlerTest.php in modules/acquia_lift_publisher/tests/src/Kernel/EventSubscriber/Cdf/EntityRenderHandlerTest.php
1 string reference to 'EntityRenderHandler'
acquia_lift_publisher.services.yml in modules/acquia_lift_publisher/acquia_lift_publisher.services.yml
modules/acquia_lift_publisher/acquia_lift_publisher.services.yml
1 service uses EntityRenderHandler
acquia_lift.service.entity_render.cdf.handler in modules/acquia_lift_publisher/acquia_lift_publisher.services.yml
Drupal\acquia_lift_publisher\EventSubscriber\Cdf\EntityRenderHandler

File

modules/acquia_lift_publisher/src/EventSubscriber/Cdf/EntityRenderHandler.php, line 32

Namespace

Drupal\acquia_lift_publisher\EventSubscriber\Cdf
View source
class EntityRenderHandler implements EventSubscriberInterface {
  use ContentPublishingSettingsTrait;

  /**
   * The account switcher.
   *
   * @var \Drupal\Core\Session\AccountSwitcherInterface
   */
  protected $accountSwitcher;

  /**
   * The origin uuid.
   *
   * @var string
   */
  protected $origin;

  /**
   * The renderer.
   *
   * @var \Drupal\Core\Render\RendererInterface
   */
  protected $renderer;

  /**
   * The entity type manager.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $entityTypeManager;

  /**
   * The Block Manager.
   *
   * @var \Drupal\Core\Block\BlockManagerInterface
   */
  protected $blockManager;

  /**
   * The uuid generator.
   *
   * @var \Drupal\Component\Uuid\UuidInterface
   */
  protected $uuidGenerator;

  /**
   * The rendered user.
   *
   * @var \Drupal\Core\Session\UserSession
   */
  protected $renderUser;

  /**
   * The storage.
   *
   * @var array
   */
  protected $storage = [];

  /**
   * The client factory.
   *
   * @var \Drupal\acquia_contenthub\Client\ClientFactory
   */
  protected $clientFactory;

  /**
   * The language default service.
   *
   * @var \Drupal\Core\Language\LanguageDefault
   */
  protected $languageDefault;

  /**
   * Translation manager.
   *
   * @var \Drupal\Core\StringTranslation\TranslationManager
   */
  protected $translationManager;

  /**
   * EntityRenderHandler constructor.
   *
   * @param \Drupal\Core\Session\AccountSwitcherInterface $account_switcher
   *   The account switcher.
   * @param \Drupal\Core\Config\ImmutableConfig $config
   *   The Acquia Lift publishing configuration object.
   * @param \Drupal\Core\Render\RendererInterface $renderer
   *   The renderer.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager.
   * @param \Drupal\Core\Block\BlockManagerInterface $block_manager
   *   The block manager.
   * @param \Drupal\Component\Uuid\UuidInterface $uuid_generator
   *   The UUID generator.
   * @param \Drupal\acquia_contenthub\Client\ClientFactory $client_factory
   *   The client factory.
   */
  public function __construct(AccountSwitcherInterface $account_switcher, ImmutableConfig $config, RendererInterface $renderer, EntityTypeManagerInterface $entity_type_manager, BlockManagerInterface $block_manager, UuidInterface $uuid_generator, ClientFactory $client_factory, LanguageDefault $language_default, TranslationManager $translation_manager) {
    $this->accountSwitcher = $account_switcher;
    $this->publisherSettings = $config;
    $this->clientFactory = $client_factory;
    $this->origin = $client_factory
      ->getSettings()
      ->getUuid();
    $this->renderer = $renderer;
    $this->entityTypeManager = $entity_type_manager;
    $this->blockManager = $block_manager;
    $this->uuidGenerator = $uuid_generator;
    $this->languageDefault = $language_default;
    $this->translationManager = $translation_manager;
  }

  /**
   * {@inheritdoc}
   */
  public static function getSubscribedEvents() {
    $events[AcquiaContentHubEvents::CREATE_CDF_OBJECT][] = [
      'onCreateCdf',
      100,
    ];
    return $events;
  }

  /**
   * Actions on create CDF.
   *
   * @param \Drupal\acquia_contenthub\Event\CreateCdfEntityEvent $event
   *   The create CDF entity event.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  public function onCreateCdf(CreateCdfEntityEvent $event) {
    $entity = $event
      ->getEntity();
    if (!$entity instanceof ContentEntityInterface) {

      // @todo we should support config entity rendering too.
      return;
    }
    if ($view_modes = $this
      ->getEntityViewModesSettingValue($entity)) {
      $client = $this->clientFactory
        ->getClient();
      if (!is_object($client)) {
        return;
      }
      $document = $client
        ->getEntities([
        $entity
          ->uuid(),
      ]);
      $remote_entity = $document
        ->hasEntity($entity
        ->uuid()) ? $document
        ->getCDFEntity($entity
        ->uuid()) : FALSE;
      $default_lang = $this->languageDefault
        ->get();
      foreach (array_keys($view_modes) as $view_mode) {

        // The preview image field setting is saved along side the view modes.
        // Don't process it as one.
        if ($view_mode == 'acquia_lift_preview_image') {
          continue;
        }
        foreach ($entity
          ->getTranslationLanguages() as $language) {
          $translation = $entity
            ->getTranslation($language
            ->getId());
          if ($remote_entity instanceof CDFObject) {
            $uuid = $this
              ->getRenderUuid($remote_entity, $view_mode, $language
              ->getId());
          }
          else {
            $uuid = $this->uuidGenerator
              ->generate();
          }
          $cdf = new CDFObject('rendered_entity', $uuid, date('c'), date('c'), $this->origin);
          $this->languageDefault
            ->set($language);
          $this->translationManager
            ->setDefaultLangcode($language
            ->getId());
          $standard_languages = LanguageManager::getStandardLanguageList();
          if (isset($standard_languages[$language
            ->getId()][1])) {
            $language_native_label = $standard_languages[$language
              ->getId()][1];
          }
          else {
            $language_native_label = $language
              ->getName();
          }

          // Getting fake user account to give as context to the normalization.
          $account = $this
            ->getRenderUser();

          // Checking for entity access permission to this particular account.
          $entity_access = $translation
            ->access('view', $account, TRUE);

          // Switch to render account.
          $this->accountSwitcher
            ->switchTo($account);
          $elements = [];
          if ($entity_access
            ->isAllowed()) {
            $elements = $this
              ->getViewModeMinimalHtml($translation, $view_mode);
          }
          $html = $this->renderer
            ->renderPlain($elements);

          // Restore user account.
          $this->accountSwitcher
            ->switchBack();
          $metadata['data'] = base64_encode($html);
          $cdf
            ->addAttribute('content', CDFAttribute::TYPE_STRING, trim(preg_replace('/\\s+/', ' ', str_replace("\n", ' ', strip_tags($html)))));
          $cdf
            ->addAttribute('source_entity', CDFAttribute::TYPE_STRING, $translation
            ->uuid());
          $cdf
            ->addAttribute('label', CDFAttribute::TYPE_ARRAY_STRING, $translation
            ->label(), $translation
            ->language()
            ->getId());
          $cdf
            ->addAttribute('language', CDFAttribute::TYPE_STRING, $language
            ->getId());
          $cdf
            ->addAttribute('language_label', CDFAttribute::TYPE_STRING, $language_native_label);
          $cdf
            ->addAttribute('view_mode', CDFAttribute::TYPE_STRING, $view_mode);
          $cdf
            ->addAttribute('view_mode_label', CDFAttribute::TYPE_STRING, $view_mode);
          $preview_src = $this
            ->buildPreviewImageAttributeSource($view_modes, 'acquia_lift_preview_image', 'acquia_lift_publisher_preview_image', $translation);
          if (!empty($preview_src)) {
            $cdf
              ->addAttribute('preview_image', CDFAttribute::TYPE_STRING, $preview_src);
          }
          $cdf
            ->setMetadata($metadata);
          $event
            ->addCdf($cdf);
        }
      }
      $this->languageDefault
        ->set($default_lang);
      $this->translationManager
        ->setDefaultLangcode($default_lang
        ->getId());
    }
  }

  /**
   * Build a preview image attribute source.
   *
   * @param $view_modes
   *   View modes available to check.
   * @param $preview_view_mode
   *   View mode that represents the preview image.
   * @param $style
   *   Image style for the preview image.
   * @param $translation
   *   Translation to use to build the source.
   *
   * @return string|null
   *   The source string returned or nothing.
   */
  protected function buildPreviewImageAttributeSource($view_modes, $preview_view_mode, $style, $translation) {

    // Does the view mode exist?
    if (empty($view_modes[$preview_view_mode])) {
      return NULL;
    }

    // Can we load this view mode?
    $preview_image = $translation->{$view_modes[$preview_view_mode]}
      ->first();
    if (empty($preview_image)) {
      return NULL;
    }

    // Can we get a source url for the image style using the view mode?
    $src = ImageStyle::load($style)
      ->buildUrl($preview_image->entity
      ->getFileUri());
    if (empty($src)) {
      return NULL;
    }
    return $src;
  }

  /**
   * Get rendered user.
   *
   * @return \Drupal\acquia_contenthub\Session\ContentHubUserSession|\Drupal\Core\Session\UserSession
   *   The rendered user.
   */
  protected function getRenderUser() {
    if (!$this->renderUser) {
      $this->renderUser = new ContentHubUserSession($this->publisherSettings
        ->get('render_role'));
    }
    return $this->renderUser;
  }

  /**
   * Get view mode minimal HTML.
   *
   * @param \Drupal\Core\Entity\ContentEntityInterface $object
   *   The content entity object.
   * @param string $view_mode
   *   The view mode identifier.
   *
   * @return array
   *   The view mode minimal HTML.
   */
  protected function getViewModeMinimalHtml(ContentEntityInterface $object, $view_mode) {

    // Render View Mode.
    $entity_type_id = $object
      ->getEntityTypeId();

    // @todo allow different entity types to specify how to do this.
    if ($entity_type_id === 'block_content') {
      $build = $this
        ->getBlockMinimalBuildArray($object, $view_mode);
    }
    else {
      $build = $this
        ->getViewMode($object, $view_mode);
    }
    return $build;
  }

  /**
   * Renders block using BlockViewBuilder.
   *
   * @param \Drupal\Core\Entity\ContentEntityInterface $object
   *   The Content Entity Object.
   * @param string $view_mode
   *   The request view mode identifier.
   *
   * @return array
   *   Render array for the block.
   */
  protected function getBlockMinimalBuildArray(ContentEntityInterface $object, $view_mode) {

    /** @var \Drupal\block_content\Plugin\Block\BlockContentBlock $block */
    $block = $this->blockManager
      ->createInstance('block_content:' . $object
      ->uuid());
    $build = [
      '#theme' => 'block',
      '#attributes' => [],
      '#contextual_links' => [],
      '#weight' => 0,
      '#configuration' => $block
        ->getConfiguration(),
      '#plugin_id' => $block
        ->getPluginId(),
      '#base_plugin_id' => $block
        ->getBaseId(),
      '#derivative_plugin_id' => $block
        ->getDerivativeId(),
    ];

    // Label controlled by the block__block_content__acquia_contenthub template
    // (hidden by default). Override the template in your theme to render a
    // block label.
    if ($build['#configuration']['label'] === '') {
      $build['#configuration']['label'] = $object
        ->label();
    }

    // Block entity itself doesn't have configuration.
    $block
      ->setConfigurationValue('view_mode', $view_mode);
    $build['#configuration']['view_mode'] = $view_mode;

    // See \Drupal\block\BlockViewBuilder::preRender() for reference.
    $content = $this
      ->getViewMode($object, $view_mode);
    if ($content !== NULL && !Element::isEmpty($content)) {
      foreach ([
        '#attributes',
        '#contextual_links',
      ] as $property) {
        if (isset($content[$property])) {
          $build[$property] += $content[$property];
          unset($content[$property]);
        }
      }
    }
    $build['content'] = $content;
    return $build;
  }

  /**
   * Returns the applicable render array.
   *
   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
   *   The renderable entity.
   * @param string $view_mode
   *   The view mode to render in.
   *
   * @return array
   *   The render array.
   */
  protected function getViewMode(ContentEntityInterface $entity, string $view_mode) : array {
    return $this->entityTypeManager
      ->getViewBuilder($entity
      ->getEntityTypeId())
      ->view($entity, $view_mode, $entity
      ->language()
      ->getId());
  }

  /**
   * Get rendered content UUID for given source entity view mode and language.
   *
   * A render UUID will be created if one does not already exist in Content Hub
   * for the provided view mode and language.
   *
   * @param \Acquia\ContentHubClient\CDF\CDFObject $source_entity_cdf
   *   The source entity CDF.
   * @param string $view_mode
   *   The view mode identifier.
   * @param string $langcode
   *   The language code.
   *
   * @return mixed
   *   The UUID.
   */
  protected function getRenderUuid(CDFObject $source_entity_cdf, $view_mode, $langcode) {
    $source_entity_uuid = $source_entity_cdf
      ->getUuid();
    if ($this
      ->isStorageHit($source_entity_uuid, $langcode, $view_mode)) {
      return $this
        ->getStorageItem($source_entity_uuid, $langcode, $view_mode);
    }

    // Warm up storage.
    $this
      ->getAllRenderUuids($source_entity_uuid);
    if (!$this
      ->isStorageHit($source_entity_uuid, $langcode, $view_mode)) {
      $this
        ->setStorageItem($source_entity_uuid, $langcode, $view_mode, $this->uuidGenerator
        ->generate());
    }
    return $this
      ->getStorageItem($source_entity_uuid, $langcode, $view_mode);
  }

  /**
   * Get all rendered content UUIDs for a given source entity.
   *
   * @param string $source_entity_uuid
   *   The source entity CDF.
   *
   * @return mixed
   *   The UUIDs.
   */
  protected function getAllRenderUuids($source_entity_uuid) {
    if (!$this
      ->storageIsAlreadyWarmedUp($source_entity_uuid)) {
      $response = $this->clientFactory
        ->getClient()
        ->listEntities([
        'type' => 'rendered_entity',
        'origin' => $this->origin,
        'fields' => 'language,view_mode,source_entity',
        'filters' => [
          'source_entity' => $source_entity_uuid,
        ],
      ]);
      if (TRUE === $response['success'] && !empty($response['data'])) {
        $this
          ->storageWarmUp($response['data'], $source_entity_uuid);
      }
    }
    return $this
      ->getStorageAllItems($source_entity_uuid);
  }

  /**
   * Get attribute value.
   *
   * @param array $attribute
   *   The attribute.
   * @param string $key
   *   The key.
   * @param string|null $default
   *   The default.
   *
   * @return mixed|null
   *   The und value or the default value.
   */
  protected function getAttributeValue(array $attribute, string $key, ?string $default = NULL) {
    return $attribute[$key][LanguageInterface::LANGCODE_NOT_SPECIFIED] ?? $default;
  }

  /**
   * Check if the storage is a hit.
   *
   * @param string $uuid
   *   The UUID.
   * @param string $langcode
   *   The language code.
   * @param string $view_mode
   *   The view mode identifier.
   *
   * @return bool
   *   TRUE if storage hit; FALSE otherwise.
   */
  protected function isStorageHit($uuid, $langcode, $view_mode) {
    return isset($this->storage[$uuid][$langcode][$view_mode]);
  }

  /**
   * Get storage item.
   *
   * @param string $uuid
   *   The UUID.
   * @param string $langcode
   *   The language code.
   * @param string $view_mode
   *   The view mode identifier.
   *
   * @return mixed
   *   The value.
   */
  protected function getStorageItem($uuid, $langcode, $view_mode) {
    return $this->storage[$uuid][$langcode][$view_mode];
  }

  /**
   * Get all items from storage regardless of langcodes and view modes.
   *
   * @param string $uuid
   *   The UUID.
   *
   * @return mixed
   *   The value.
   */
  protected function getStorageAllItems($uuid) {
    return $this->storage[$uuid] ?? [];
  }

  /**
   * Set storage item.
   *
   * @param string $uuid
   *   The UUID.
   * @param string $langcode
   *   The language code.
   * @param string $view_mode
   *   The view mode identifier.
   * @param mixed $value
   *   The value.
   */
  protected function setStorageItem($uuid, $langcode, $view_mode, $value) {
    $this->storage[$uuid][$langcode][$view_mode] = $value;
  }

  /**
   * Check if the storage is already warmed up.
   *
   * @param string $uuid
   *   The UUID.
   *
   * @return bool
   *   TRUE if the storage is already warmed up; FALSE otherwise.
   */
  protected function storageIsAlreadyWarmedUp($uuid) {
    return isset($this->storage[$uuid]);
  }

  /**
   * Storage warm up action.
   *
   * @param array $data
   *   The array of data.
   * @param string $source_uuid
   *   The source UUID.
   */
  protected function storageWarmUp(array $data, $source_uuid) {
    foreach ($data as $entity_info) {

      // Ensure we discard any result that does not match exactly the source_uuid.
      if ($this
        ->getAttributeValue($entity_info['attributes'], 'source_entity') === $source_uuid) {
        $this
          ->setStorageItem($source_uuid, $this
          ->getAttributeValue($entity_info['attributes'], 'language'), $this
          ->getAttributeValue($entity_info['attributes'], 'view_mode'), $entity_info['uuid']);
      }
    }
  }

}

Members

Namesort descending Modifiers Type Description Overrides
ContentPublishingSettingsTrait::$publisherSettings private property Acquia lift publisher configuration object.
ContentPublishingSettingsTrait::getEntityViewModesSettingValue protected function Returns the value of the entity view modes setting.
EntityRenderHandler::$accountSwitcher protected property The account switcher.
EntityRenderHandler::$blockManager protected property The Block Manager.
EntityRenderHandler::$clientFactory protected property The client factory.
EntityRenderHandler::$entityTypeManager protected property The entity type manager.
EntityRenderHandler::$languageDefault protected property The language default service.
EntityRenderHandler::$origin protected property The origin uuid.
EntityRenderHandler::$renderer protected property The renderer.
EntityRenderHandler::$renderUser protected property The rendered user.
EntityRenderHandler::$storage protected property The storage.
EntityRenderHandler::$translationManager protected property Translation manager.
EntityRenderHandler::$uuidGenerator protected property The uuid generator.
EntityRenderHandler::buildPreviewImageAttributeSource protected function Build a preview image attribute source.
EntityRenderHandler::getAllRenderUuids protected function Get all rendered content UUIDs for a given source entity.
EntityRenderHandler::getAttributeValue protected function Get attribute value.
EntityRenderHandler::getBlockMinimalBuildArray protected function Renders block using BlockViewBuilder.
EntityRenderHandler::getRenderUser protected function Get rendered user.
EntityRenderHandler::getRenderUuid protected function Get rendered content UUID for given source entity view mode and language.
EntityRenderHandler::getStorageAllItems protected function Get all items from storage regardless of langcodes and view modes.
EntityRenderHandler::getStorageItem protected function Get storage item.
EntityRenderHandler::getSubscribedEvents public static function Returns an array of event names this subscriber wants to listen to.
EntityRenderHandler::getViewMode protected function Returns the applicable render array.
EntityRenderHandler::getViewModeMinimalHtml protected function Get view mode minimal HTML.
EntityRenderHandler::isStorageHit protected function Check if the storage is a hit.
EntityRenderHandler::onCreateCdf public function Actions on create CDF.
EntityRenderHandler::setStorageItem protected function Set storage item.
EntityRenderHandler::storageIsAlreadyWarmedUp protected function Check if the storage is already warmed up.
EntityRenderHandler::storageWarmUp protected function Storage warm up action.
EntityRenderHandler::__construct public function EntityRenderHandler constructor.