Commit fcd26319 authored by Julian Pustkuchen's avatar Julian Pustkuchen
Browse files

Issue #3309682 by Anybody: Detect parent menu item from trail to ensure it...

Issue #3309682 by Anybody: Detect parent menu item from trail to ensure it also works without "real" children menu items
parent 0de1dea6
Loading
Loading
Loading
Loading
+105 −98
Original line number Diff line number Diff line
@@ -12,6 +12,9 @@ use Drupal\Core\Menu\MenuActiveTrail;
use Drupal\Core\Menu\MenuLinkManager;
use Drupal\menu_link_content\MenuLinkContentInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use \Drupal\media\Entity\Media;
use Drupal\Component\Uuid\Uuid;


/**
 * Provides a 'DROWL Header Slides Menu Slideshow Slide Block (Page Width)' Block.
@@ -24,6 +27,8 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
 */
class MenuSlideshowRefSlidesBlock extends BlockBase implements ContainerFactoryPluginInterface {

  const MENU_LINK_CONTENT_ENTITY_TYPE = 'menu_link_content';

  /**
   * The entity type manager object.
   *
@@ -98,144 +103,146 @@ class MenuSlideshowRefSlidesBlock extends BlockBase implements ContainerFactoryP
   * {@inheritdoc}
   */
  public function build() {
    $activeTrailMenuLinkEntity = $this->determineActiveTrailMenuLinkEntity();
    if (!empty($activeTrailMenuLinkEntity)) {
    $mediaSlideshowEntity = $this->determineActiveTrailMediaHeaderSlideEntity();
    if (!empty($mediaSlideshowEntity)) {
      /**
       * @var \Drupal\media_entity\Entity\Media $mediaSlideshowEntity
       * @var $mediaSlideshowEntity \Drupal\media\Entity\Media
       */
      $mediaSlideshowEntity = $this->determineMenuLinkMediaSlideshowEntity($activeTrailMenuLinkEntity);
      if (!empty($mediaSlideshowEntity) && $mediaSlideshowEntity->access('view')) {
        // Return the rendered media entity:
        $build = $this->entityTypeManager->getViewBuilder('media')->view($mediaSlideshowEntity);
        $build = \Drupal::entityTypeManager()->getViewBuilder('media')->view($mediaSlideshowEntity, $this->view_mode);
        return $build;
      }
    }
    return NULL;
    return null;
  }

  /**
   * {@inheritdoc}
   */
  public function getCacheContexts() {
    // Every new route this block will rebuild.
    return Cache::mergeContexts(parent::getCacheContexts(), ['route']);
    //Every new route this block will rebuild
    return Cache::mergeContexts(parent::getCacheContexts(), array('route'));
  }

  /**
   * Returns the Media slideshow entity from the given menu link.
   *
   * @param \Drupal\menu_link_content\MenuLinkContentInterface $menuLinkEntity
   *   The menu link entity.
   * Returns the currently active MenuLinkContent entity.
   *
   * @return \Drupal\media_entity\Entity\Media
   *   The media slideshow entity.
   * @return \Drupal\media\Entity\Media|null
   */
  protected function determineMenuLinkMediaSlideshowEntity(MenuLinkContentInterface $menuLinkEntity = NULL) {
    if (empty($menuLinkEntity)) {
      return NULL;
  protected function determineActiveTrailMediaHeaderSlideEntity(): ?Media {
    // Cache per site call:
    $menuActiveLinkEntity = &drupal_static(__FUNCTION__, null);
    if (isset($menuActiveLinkEntity)) {
      return $menuActiveLinkEntity ?: null;
    }
    if ($menuLinkEntity->hasField('field_slideshow_ref')) {

    $candidateMenuLinkIds = $this->determineActiveTrailsMenuLinkIds();
    $i = 0;
    foreach ($candidateMenuLinkIds as $key => $candidateMenuLinkId) {
      $menuLinkContentEntity = $this->getMenuLinkContentEntityFromUuidString($candidateMenuLinkId);
      if (!empty($menuLinkContentEntity) && $menuLinkContentEntity->hasField('field_slideshow_ref')) {
        /**
       * @var \Drupal\media_entity\Entity\Media $mediaSlideshowEntity
         * @var $mediaSlideshowEntity \Drupal\media\Entity\Media
         */
      $mediaSlideshowEntity = $menuLinkEntity->field_slideshow_ref->entity;
        $mediaSlideshowEntity = $menuLinkContentEntity->field_slideshow_ref->entity;
        if (!empty($mediaSlideshowEntity)) {
          // ONLY return the entity if it's the currently displayed
          // menu item or has field_slideshow_inherit enabled!
          // Other menu items are wrong!
          if ($i == 0) {
            // This is the menu item of the node itself
            return $mediaSlideshowEntity;
          } elseif ($menuLinkContentEntity->hasField('field_slideshow_inherit')) {
            // The parent menu item has a field_slideshow_inherit field.
            /**
             * @var $inheritFromParent boolean
             */
            $inheritFromParent = !empty($menuLinkContentEntity->get('field_slideshow_inherit')->value);
            if ($inheritFromParent) {
              // Inherit from parent is enabled. Return the parent as slideshow provider:
              return $mediaSlideshowEntity;
            }
          }
        }
      }
    // Has no own slideshow selected - check for parent inheritance:
    return $this->determineMenuLinkMediaSlideshowEntity($this->determineParentMenuLink($menuLinkEntity));
      $i++;
    }

    return null;
  }

  /**
   * Returns the parent menu item from the given $menuLinkEntity.
   * Returns an array of all active trail menu link UUIDs, starting
   * with the current node, up to front as last item, as they may contain
   * a header slideshow selection with field_slideshow_inherit enabled.
   *
   * @param \Drupal\menu_link_content\MenuLinkContentInterface $menuLinkEntity
   *   The menu link entity.
   * This allows to travel up the list, until such an entry is found or
   * we can be sure there's no such entry.
   *
   * @return \Drupal\menu_link_content\MenuLinkContentInterface
   *   The parent menu link entity.
   * @return array
   */
  protected function determineParentMenuLink(MenuLinkContentInterface $menuLinkEntity = NULL) {
    if (empty($menuLinkEntity)) {
      return NULL;
  protected function determineActiveTrailsMenuLinkIds(): array {
    $searchInMenus = \Drupal::config('drowl_header_slides.settings')->get('menus');
    $activeTrailIds = [];
    if (!empty($searchInMenus)) {
      foreach ($searchInMenus as $menu_id) {
        $activeTrailIds = $this->menuActiveTrail->getActiveTrailIds($menu_id);
        // Return the first found active trail.
        // @todo This should be improved, perhaps better merge candidates
        // from all selected menus? (But in 90% this is enough).
        if (!empty($activeTrailIds) && (count($activeTrailIds) > 1 || !empty(reset($activeTrailIds)))) {
          // IMPORTANT: $activeTrailIds[''] is also returned for empty results
          // with an empty array, so wo have to sort that out!

          // Check if the currently viewed entity has a menu item itself,
          // otherwise it will not appear in the trail, but we have to indicate
          // this with an empty first entry in the $activeTrailIds
          // to show we're returning parent entities.
          $menuActiveLink = $this->menuActiveTrail->getActiveLink($menu_id);
          if (empty($menuActiveLink)) {
            // Prepend this placeholder value to the results to count all
            // other results as parent items and upper methods can see that
            // there's no direct / own menu item returned:
            array_unshift($activeTrailIds, 'ONLY-PARENT-ITEMS-FOUND-PLACEHOLDER');
          }
    // Load the parent menu id:
    /**
     * @var string $parentMenuLinkEntityId
     */
    $parentMenuLinkEntityId = $menuLinkEntity->getParentId();
    if (!empty($parentMenuLinkEntityId)) {
      /**
       * @var \Drupal\menu_link_content\Plugin\Menu\MenuLinkContent
       */
      $parentMenuLinkManagerInstance = $this->menuLinkManager->createInstance($parentMenuLinkEntityId);
      /**
       * @var string $parentMenuLinkManagerInstanceDerivateId
       */
      $parentMenuLinkManagerInstanceDerivateId = $parentMenuLinkManagerInstance->getDerivativeId();
      // Load the parent menu_item entity.
      /**
       * @var \Drupal\menu_item_extras\Entity\MenuItemExtrasMenuLinkContent $parentMenuLinkEntity
       */
      $parentMenuLinkEntity = $this->entityRepository
        ->loadEntityByUuid('menu_link_content', $parentMenuLinkManagerInstanceDerivateId);
      if (!empty($parentMenuLinkEntity) && $parentMenuLinkEntity->hasField('field_slideshow_inherit')) {
        // The parent menu item has a field_slideshow_inherit field.
        /**
         * @var boolean $inheritFromParent
         */
        $inheritFromParent = !empty($parentMenuLinkEntity->get('field_slideshow_inherit')->value);
        if ($inheritFromParent) {
          // Inherit from parent is enabled. Return the parent as slideshow
          // provider:
          return $parentMenuLinkEntity;
          break;
        }
      }
    }
    return NULL;

    return $activeTrailIds;
  }

  /**
   * Returns the currently active MenuLinkContent entity.
   * Returns the menu_link_content entity from the given $uuidString.
   *
   * @return \Drupal\menu_link_content\MenuLinkContentInterface
   *   The currently active MenuLinkContent entity.
   * @param string $uuidString
   *   The UUID string, for example menu_link_content:6887e19d-4426-47e3-afa9
   * @return \Drupal\menu_link_content\MenuLinkContentInterface|null
   */
  protected function determineActiveTrailMenuLinkEntity() {
    // Cache per site call:
    $menuActiveLinkEntity = &drupal_static(__FUNCTION__, NULL);
    if (isset($menuActiveLinkEntity)) {
      return $menuActiveLinkEntity ?? NULL;
    }
    $config = $this->configFactory->get('drowl_header_slides.settings');
    $searchInMenus = $config->get('menus');
    $menuActiveLink = NULL;
    if (!empty($searchInMenus)) {
      foreach ($searchInMenus as $menu) {
        // Hint: Drupal treats the first menu with an active link
        // as the matching menu, whatever we do... Our
        // current workaround is to only use the selected menu(s).
        // It Would be better to sort the menus for evaluation and check
        // them in the configured order,
        // IF they contain a drowl_header_slide reference.
        $menuActiveLink = $this->menuActiveTrail->getActiveLink($menu);
        break;
      }
  private function getMenuLinkContentEntityFromUuidString(string $uuidString): ?MenuLinkContentInterface {
    $parts = explode(':', $uuidString);
    // Skip if one part is empty:
    if (empty($parts[0]) || empty($parts[1])) {
      return null;
    }
    if (empty($menuActiveLink)) {
      return NULL;
    $pluginId = $parts[0];
    $uuid = $parts[1];

    // Skip if the item has no valid UUID:
    if (!Uuid::isValid($uuid)) {
      return null;
    }
    $menuActiveLinkUuid = $menuActiveLink->getDerivativeId();
    if (empty($menuActiveLinkUuid)) {
      return NULL;

    if (is_a($pluginId[0], MenuLinkContentInterface::class)) {
      throw new \Exception('Entity of type menu_link_content expected, but was "' . $parts[0] . '"');
    }
    $menuActiveLinkEntity = $this->entityRepository
      ->loadEntityByUuid('menu_link_content', $menuActiveLinkUuid);

    $menuActiveLinkEntity = $this->entityRepository->loadEntityByUuid(self::MENU_LINK_CONTENT_ENTITY_TYPE, $parts[1]);
    if (!empty($menuActiveLinkEntity)) {
      return $menuActiveLinkEntity;
    }
    return NULL;
    return null;
  }

}