You are here

UsageEventSubscriber.php in Bynder 8.3

File

modules/bynder_usage/src/EventSubscriber/UsageEventSubscriber.php
View source
<?php

namespace Drupal\bynder_usage\EventSubscriber;

use Drupal\bynder\BynderApiInterface;
use Drupal\bynder\Plugin\media\Source\Bynder;
use Drupal\bynder_usage\Exception\UnableToAddUsageException;
use Drupal\bynder_usage\Exception\UnableToDeleteUsageException;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Url;
use Drupal\entity_usage\Events\EntityUsageEvent;
use Drupal\entity_usage\Events\Events;
use Drupal\media\MediaInterface;
use Drupal\paragraphs\ParagraphInterface;
use GuzzleHttp\Exception\RequestException;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RequestStack;

/**
 * Listens for the usage events from Entity Usage module.
 */
class UsageEventSubscriber implements EventSubscriberInterface {

  /**
   * Bynder api service.
   *
   * @var \Drupal\bynder\BynderApiInterface
   *   Bynder api service.
   */
  protected $bynderApi;

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

  /**
   * The request stack.
   *
   * @var \Symfony\Component\HttpFoundation\RequestStack
   */
  protected $requestStack;

  /**
   * UsageEventSubscriber constructor.
   *
   * @param \Drupal\bynder\BynderApiInterface $bynder_api_service
   *   Bynder api service.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager.
   * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
   *   The request stack.
   */
  public function __construct(BynderApiInterface $bynder_api_service, EntityTypeManagerInterface $entity_type_manager, RequestStack $request_stack) {
    $this->bynderApi = $bynder_api_service;
    $this->entityTypeManager = $entity_type_manager;
    $this->requestStack = $request_stack;
  }

  /**
   * {@inheritdoc}
   */
  public static function getSubscribedEvents() {
    $events[Events::USAGE_REGISTER][] = [
      'onUsageRegister',
    ];
    $events[Events::DELETE_BY_SOURCE_ENTITY][] = [
      'onDeleteBySourceEntity',
    ];
    $events[Events::DELETE_BY_TARGET_ENTITY][] = [
      'onDeleteByTargetEntity',
    ];
    return $events;
  }

  /**
   * Returns the remote media ID.
   *
   * @param \Drupal\media\MediaInterface $media
   *   The media to get the remote ID for.
   *
   * @return mixed|null
   *   The remote media ID or NULL if not found.
   */
  protected function getRemoteMediaId(MediaInterface $media) {
    $source_plugin = $media
      ->getSource();
    if (!$source_plugin instanceof Bynder) {
      return NULL;
    }
    return $source_plugin
      ->getSourceFieldValue($media);
  }

  /**
   * Returns the canonical URL for the given entity.
   *
   * @param \Drupal\Core\Entity\EntityInterface $entity
   *   The source entity.
   *
   * @return \Drupal\Core\Url|null
   *   The absolute URL of the given entity.
   */
  protected function getEntityUrl(EntityInterface $entity) {

    // If the entity is a paragraph, attempt to recursively load the parent.
    while ($entity && $entity instanceof ParagraphInterface) {
      $entity = $entity
        ->getParentEntity();
    }

    // If the entity exists and has a canonical link template, get the URI.
    return $entity && $entity
      ->hasLinkTemplate('canonical') ? $entity
      ->toUrl('canonical')
      ->setOption('path_processing', FALSE)
      ->setAbsolute() : NULL;
  }

  /**
   * Auxiliary function to get media information for asset usage operations.
   *
   * @param \Drupal\entity_usage\Events\EntityUsageEvent $event
   *
   * @return array|null
   */
  private function getUsageEventMediainformation(EntityUsageEvent $event) {
    if ($event
      ->getTargetEntityType() !== 'media') {
      return NULL;
    }

    /** @var \Drupal\media\MediaInterface $media */
    $media = $this->entityTypeManager
      ->getStorage('media')
      ->load($event
      ->getTargetEntityId());
    if (!isset($media)) {
      return NULL;
    }
    $source_plugin = $media
      ->getSource();
    if (!$source_plugin instanceof Bynder) {
      return NULL;
    }
    $url = NULL;
    if ($source_id = $event
      ->getSourceEntityId()) {
      if ($entity = $this->entityTypeManager
        ->getStorage($event
        ->getSourceEntityType())
        ->load($source_id)) {
        $url = $this
          ->getEntityUrl($entity);
      }
    }
    if ($url) {
      return [
        'mediaId' => $source_plugin
          ->getSourceFieldValue($media),
        'url' => $url,
      ];
    }
  }

  /**
   * Returns whether the given remote ID and URI have a remote usage.
   *
   * @param string $remote_id
   *   The remote ID.
   * @param string $uri
   *   The URI this asset was used on.
   *
   * @return bool
   *   TRUE if there is a remote usage for the ID and URI. Otherwise, FALSE.
   */
  protected function hasRemoteUsageByUri($remote_id, $uri) {
    $usages = $this->bynderApi
      ->getAssetUsages($remote_id);

    // No remote usages.
    if (empty($usages)) {
      return FALSE;
    }
    foreach ($usages as $usage) {

      // There is a remote usage on the given URI.
      if ($usage['uri'] === $uri) {
        return TRUE;
      }
    }
    return FALSE;
  }

  /**
   * Triggers when a usage is registered (create or update) for a Bynder asset.
   *
   * @param \Drupal\entity_usage\Events\EntityUsageEvent $event
   *   The event to process.
   */
  public function onUsageRegister(EntityUsageEvent $event) {
    $mediaInfo = $this
      ->getUsageEventMediainformation($event);
    if (isset($mediaInfo)) {
      try {

        // Add a usage only once if the count is positive and there are no
        // usages registered on this URI.
        if ($event
          ->getCount() > 0) {
          $usage_url = $mediaInfo['url'];
          if (!$this
            ->hasRemoteUsageByUri($mediaInfo['mediaId'], $usage_url
            ->toString())) {
            $this->bynderApi
              ->addAssetUsage($mediaInfo['mediaId'], $usage_url, date(DATE_ISO8601, \Drupal::time()
              ->getRequestTime()), 'Added asset by user ' . \Drupal::currentUser()
              ->getAccountName() . '.');
          }
        }
        else {
          $this->bynderApi
            ->removeAssetUsage($mediaInfo['mediaId'], $mediaInfo['url']
            ->toString());
        }
      } catch (RequestException $e) {
        watchdog_exception('bynder', $e);
        (new UnableToAddUsageException($e
          ->getMessage()))
          ->logException()
          ->displayMessage();
      }
    }
  }

  /**
   * Triggers when the source entity is deleted.
   *
   * @param \Drupal\entity_usage\Events\EntityUsageEvent $event
   *   The event to process.
   */
  public function onDeleteBySourceEntity(EntityUsageEvent $event) {

    // Skip if a non-default revision or translation is deleted.
    if ($event
      ->getSourceEntityRevisionId() || $event
      ->getSourceEntityLangcode()) {
      return;
    }
    $storage = $this->entityTypeManager
      ->getStorage($event
      ->getSourceEntityType());
    $entity = $storage
      ->load($event
      ->getSourceEntityId());
    if (!$entity) {
      return;
    }
    $usage_url = $this
      ->getEntityUrl($entity);
    if (!$usage_url) {
      return;
    }

    // At this point, the entity usages are already deleted so we can't query
    // the entity usage table to find the relevant media usages. Instead, loop
    // over the references of the source entity and delete all usages of the
    // Bynder media assets on this URI.
    foreach ($entity
      ->referencedEntities() as $referenced_entity) {
      if ($referenced_entity instanceof MediaInterface && ($remote_id = $this
        ->getRemoteMediaId($referenced_entity))) {
        try {
          $this->bynderApi
            ->removeAssetUsage($remote_id, $usage_url
            ->toString());
        } catch (RequestException $e) {
          watchdog_exception('bynder', $e);
          (new UnableToDeleteUsageException($e
            ->getMessage()))
            ->logException()
            ->displayMessage();
        }
      }
    }
  }

  /**
   * Triggers if the target (media) entity is deleted. Remove all Bynder usages.
   *
   * @param \Drupal\entity_usage\Events\EntityUsageEvent $event
   *   The entity usage event.
   */
  public function onDeleteByTargetEntity(EntityUsageEvent $event) {
    if ($event
      ->getTargetEntityType() === 'media') {

      /** @var \Drupal\media\MediaInterface $media */
      $media = $this->entityTypeManager
        ->getStorage('media')
        ->load($event
        ->getTargetEntityId());
      if ($media) {
        $remote_id = $this
          ->getRemoteMediaId($media);
        if ($remote_id) {
          try {

            // The Bynder media entity is deleted from the system. Delete all
            // remote usages for this asset.
            $usages = $this->bynderApi
              ->getAssetUsages($remote_id);
            $base_url = Url::fromUri('base:/')
              ->setAbsolute()
              ->toString();
            foreach ($usages as $usage) {

              // If the usage appears to be on this site remove all the usages.
              if (strpos($usage['uri'], $base_url) === 0) {
                $this->bynderApi
                  ->removeAssetUsage($remote_id, $usage['uri']);
              }
            }
          } catch (RequestException $e) {
            watchdog_exception('bynder', $e);
            (new UnableToDeleteUsageException($e
              ->getMessage()))
              ->logException()
              ->displayMessage();
          }
        }
      }
    }
  }

}

Classes

Namesort descending Description
UsageEventSubscriber Listens for the usage events from Entity Usage module.