wayfinding-2.1.x-dev/src/Query.php

src/Query.php
<?php

namespace Drupal\wayfinding;

use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException;
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Config\ImmutableConfig;
use Drupal\Core\Database\Connection;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Entity\EntityTypeManager;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\Url;
use Drupal\file\Entity\File;
use Drupal\wayfinding\Entity\Wayfinding;
use Drupal\wayfinding\Event\Libraries;
use Drupal\wayfinding\Event\QueryEvent;
use Drupal\wayfinding\Plugin\views\style\Master;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\RequestStack;

/**
 * Wayfinding query service.
 */
class Query {

  use StringTranslationTrait;

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

  /**
   * The entity type and bundle info service.
   *
   * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
   */
  protected EntityTypeBundleInfoInterface $entityTypeBundleInfo;

  /**
   * The entity field manager.
   *
   * @var \Drupal\Core\Entity\EntityFieldManagerInterface
   */
  protected EntityFieldManagerInterface $entityFieldManager;

  /**
   * The database connection.
   *
   * @var \Drupal\Core\Database\Connection
   */
  protected Connection $connection;

  /**
   * The configuration.
   *
   * @var \Drupal\Core\Config\ImmutableConfig
   */
  protected ImmutableConfig $config;

  /**
   * The event dispatcher.
   *
   * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
   */
  protected EventDispatcherInterface $eventDispatcher;

  /**
   * The module handler.
   *
   * @var \Drupal\Core\Extension\ModuleHandlerInterface
   */
  protected ModuleHandlerInterface $moduleHandler;

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

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

  /**
   * The list of source entities.
   *
   * @var \Drupal\Core\Entity\ContentEntityInterface[]|null
   */
  protected ?array $sources = NULL;

  /**
   * Constructs an Entity update service.
   *
   * @param \Drupal\Core\Entity\EntityTypeManager $entity_type_manager
   *   The entity type manager.
   * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info
   *   The entity type and bundle info service.
   * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
   *   The entity field manager.
   * @param \Drupal\Core\Database\Connection $connection
   *   The database connection.
   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
   *   The config factory.
   * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
   *   The event dispatcher.
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
   *   The module handler.
   * @param \Drupal\Core\Render\RendererInterface $renderer
   *   The core renderer.
   * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
   *   The request stack.
   */
  public function __construct(EntityTypeManager $entity_type_manager, EntityTypeBundleInfoInterface $entity_type_bundle_info, EntityFieldManagerInterface $entity_field_manager, Connection $connection, ConfigFactoryInterface $config_factory, EventDispatcherInterface $event_dispatcher, ModuleHandlerInterface $module_handler, RendererInterface $renderer, RequestStack $request_stack) {
    $this->entityTypeManager = $entity_type_manager;
    $this->entityTypeBundleInfo = $entity_type_bundle_info;
    $this->entityFieldManager = $entity_field_manager;
    $this->connection = $connection;
    $this->config = $config_factory->get('wayfinding.settings');
    $this->eventDispatcher = $event_dispatcher;
    $this->moduleHandler = $module_handler;
    $this->renderer = $renderer;
    $this->requestStack = $request_stack;
  }

  /**
   * Get the media entity for the device-specific background.
   *
   * @param float $perspective
   *   The perspective.
   * @param string $type
   *   The type.
   * @param int $id
   *   The ID.
   *
   * @return \Drupal\Core\Entity\ContentEntityInterface|null
   *   The media entity if available, NULL otherwise.
   */
  public function getMedia(float $perspective, string $type = 'user', int $id = 1): ?ContentEntityInterface {
    try {
      /** @var \Drupal\Core\Entity\ContentEntityInterface[] $entities */
      $entities = $this->entityTypeManager->getStorage('media')
        ->loadByProperties([
          'field_perspective' => $perspective,
          'field_destination' => [
            'target_id' => $id,
          ],
        ]);
      foreach ($entities as $entity) {
        if ($entity->get('field_destination')->getValue()[0]['target_type'] === $type) {
          return $entity;
        }
      }
    }
    catch (InvalidPluginDefinitionException | PluginNotFoundException) {
      // Deliberately ignored.
    }
    return NULL;
  }

  /**
   * Build the rendered svg image.
   *
   * @param float $perspective
   *   The perspective.
   * @param string $type
   *   The type.
   * @param int $id
   *   The ID.
   * @param bool $source
   *   TRUE, if the SVG source code is required, FALSE otherwise.
   *
   * @return array|string
   *   The SVG source code or the render array of an image.
   */
  public function getRenderedMedia(float $perspective, string $type = 'user', int $id = 1, bool $source = FALSE): array|string {
    if ($media = $this->getMedia($perspective, $type, $id)) {
      if ($source && $file = File::load($media->get('field_svg_image')->getValue()[0]['target_id'])) {
        $content = file_get_contents($file->getFileUri());
        if ($pos = strpos($content, '<svg')) {
          $content = substr($content, $pos);
        }
        return $content;
      }
      return $this->entityTypeManager->getViewBuilder($media->getEntityTypeId())
        ->view($media, 'wayfinding');
    }
    return [];
  }

  /**
   * Build the rendered pin svg image.
   *
   * @param bool $removeSvgWrapper
   *   TRUE, if the SVG wrapper should be removed so that the code can be
   *   embedded into another SVG image.
   *
   * @return string
   *   The PIN code.
   */
  public function getRenderedPin(bool $removeSvgWrapper = FALSE): string {
    $content = file_get_contents($this->config->get('pin'));
    if ($pos = strpos($content, '<svg')) {
      $content = substr($content, $pos);
    }
    if ($removeSvgWrapper) {
      $pos = strpos($content, '>');
      $content = substr($content, $pos + 1);
      $content = str_replace('</svg>', '', $content);
    }
    return $content;
  }

  /**
   * Query the database for existing perspectives.
   *
   * @return array
   *   The list of existing perspectives.
   */
  public function getPerspectives(): array {
    $perspectives = ['0.00' => 0.00];
    if ($this->moduleHandler->moduleExists('wayfinding_digital_signage')) {
      /* @noinspection NullPointerExceptionInspection */
      $perspectives += $this->connection
        ->query('# noinspection SqlResolve
select distinct(perspective) from {digital_signage_device_field_data} where perspective is not null and perspective > 0')
        ->fetchAllKeyed(0, 0);
    }
    return $perspectives;
  }

  /**
   * Helper function to prepare source entities once.
   */
  private function prepareSource(): void {
    if ($this->sources === NULL) {
      $this->sources = [];
      foreach (Master::getDestinations() as $item) {
        try {
          /** @var \Drupal\Core\Entity\ContentEntityInterface $destination */
          $destination = $this->entityTypeManager->getStorage($item['target_type'])->load($item['target_id']);
          foreach ($this->getSources($destination) as $source) {
            if (!in_array($item['target_id'], $this->sources[$source->getEntityTypeId()][$source->id()][$item['target_type']] ?? [], TRUE)) {
              $this->sources[$source->getEntityTypeId()][$source->id()][$item['target_type']][] = $item['target_id'];
            }
          }
        }
        catch (InvalidPluginDefinitionException | PluginNotFoundException) {
          // Deliberately no handling.
        }
      }
    }
  }

  /**
   * Get the ID of the destination entity for a given source entity ID.
   *
   * @param array $srcid
   *   The source entity ID.
   *
   * @return array
   *   The list of destination entity IDs.
   */
  public function getDestinationsForId(array $srcid): array {
    $this->prepareSource();
    $destinations = [];
    foreach ($this->sources[$srcid['target_type']][$srcid['target_id']] ?? [] as $type => $ids) {
      foreach ($ids as $id) {
        $destinations[] = [
          'target_type' => $type,
          'target_id' => $id,
        ];
      }
    }
    return $destinations;
  }

  /**
   * Get the Drupal javascript settings.
   *
   * @param float $perspective
   *   The perspective.
   * @param float $lat
   *   The latitude.
   * @param float $lng
   *   The longitude.
   *
   * @return array
   *   The settings.
   */
  public function getSettings(float $perspective, float $lat = 0, float $lng = 0): array {
    if (($media = $this->getMedia($perspective)) && $positions = $media->get('field_top_left')->getValue()) {
      $position = $positions[0];
    }
    else {
      $position = [
        'lat' => 0,
        'lng' => 0,
      ];
    }
    return [
      'origin' => trim(Url::fromUserInput('/', [
        'absolute' => TRUE,
      ])->toString(TRUE)->getGeneratedUrl(), '/'),
      'widgeturl' => trim(Url::fromRoute('wayfinding.widgets', [], [
        'absolute' => TRUE,
      ])->toString(TRUE)->getGeneratedUrl(), '/'),
      'pinDynamicPosition' => $this->config->get('pin dynamic position'),
      'resetTimeout' => $this->config->get('reset timeout'),
      'perspective' => $perspective,
      'location' => $this->config->get('location'),
      'topleft' => [
        'lat' => $position['lat'],
        'lng' => $position['lng'],
      ],
      'position' => [
        'lat' => $lat,
        'lng' => $lng,
      ],
    ];
  }

  /**
   * Tbd.
   *
   * @param bool $inline
   *   Tbd.
   * @param float $perspective
   *   Tbd.
   * @param float $lat
   *   Tbd.
   * @param float $lng
   *   Tbd.
   * @param int $did
   *   Tbd.
   * @param string $eid
   *   Tbd.
   *
   * @return array
   *   Tbd.
   */
  public function build(bool $inline, float $perspective, float $lat = 0, float $lng = 0, int $did = 0, string $eid = 'undefined'): array {
    $event = new Libraries();
    $event->addLibrary('wayfinding/general');
    $this->eventDispatcher->dispatch($event, WayfindingEvents::LIBRARIES);
    $attached = [
      'drupalSettings' => [
        'wayfinding' => $this->getSettings($perspective, $lat, $lng),
      ],
      'library' => $event->getLibraries(),
    ];
    if ($did === 0) {
      $attached['library'][] = 'wayfinding/nodevice';
    }
    $output = [
      '#theme' => $inline ? 'wayfinding_inline' : 'wayfinding',
      '#view' => views_embed_view('wayfinding', 'wayfinding_wrapper_1', $perspective, $did),
      '#blocks' => [],
      '#did' => $did,
      '#eid' => $eid,
      '#attached' => $inline ? $attached : [],
    ];
    return $inline ? $output : [
      '#markup' => $this->renderer->renderInIsolation($output),
      '#attached' => $attached + [
        'html_response_attachment_placeholders' => [
          'styles' => '<styles/>',
          'scripts' => '<scripts/>',
          'scripts_bottom' => '<scripts_bottom/>',
        ],
        'placeholders' => [],
      ],
    ];
  }

  /**
   * Get the source entities for a given destination.
   *
   * @param \Drupal\Core\Entity\ContentEntityInterface $destination
   *   The destination.
   *
   * @return \Drupal\Core\Entity\ContentEntityInterface[]
   *   The list of source entities.
   */
  protected function getSources(ContentEntityInterface $destination): array {
    $sources = [];
    $sourceTypes = $this->config->get('types.sources');

    // Get all sources referenced by the destination.
    foreach ($this->entityFieldManager->getFieldDefinitions($destination->getEntityTypeId(), $destination->bundle()) as $field) {
      if ($field->getName() !== 'digital_signage' &&
        !str_starts_with($field->getName(), 'revision_') &&
        ($field->getType() === 'entity_reference' || $field->getType() === 'dynamic_entity_reference')) {
        $valid = FALSE;
        if ($field->getType() === 'entity_reference') {
          $valid = isset($sourceTypes[$field->getSetting('target_type')]);
        }
        elseif ($field->getType() === 'dynamic_entity_reference') {
          foreach ($field->getSetting('entity_type_ids') as $type) {
            if (isset($sourceTypes[$type])) {
              $valid = TRUE;
              break;
            }
          }
        }
        if ($valid) {
          foreach ($destination->get($field->getName())->getValue() as $item) {
            $target_type = $item['target_type'] ?? $field->getSetting('target_type');
            $this->addSource($sources, $target_type, $item['target_id']);
          }
        }
      }
    }

    // Get all sources referencing this destination.
    foreach ($sourceTypes as $sourceType) {
      foreach (array_keys($this->entityTypeBundleInfo->getBundleInfo($sourceType)) as $bundle) {
        foreach ($this->entityFieldManager->getFieldDefinitions($sourceType, $bundle) as $field) {
          if ($field->getName() !== 'digital_signage' &&
            !str_starts_with($field->getName(), 'revision_') &&
            ($field->getType() === 'entity_reference' || $field->getType() === 'dynamic_entity_reference')) {
            $valid = FALSE;
            if ($field->getType() === 'entity_reference') {
              $valid = $field->getSetting('target_type') === $destination->getEntityTypeId();
            }
            elseif ($field->getType() === 'dynamic_entity_reference') {
              foreach ($field->getSetting('entity_type_ids') as $type) {
                if ($type === $destination->getEntityTypeId()) {
                  $valid = TRUE;
                  break;
                }
              }
            }
            if ($valid) {
              try {
                $ids = $this->entityTypeManager->getStorage($sourceType)
                  ->getQuery()
                  ->accessCheck(FALSE)
                  ->condition($field->getName(), $destination->id())
                  ->execute();
                foreach ($ids as $id) {
                  $this->addSource($sources, $sourceType, (int) $id);
                }
              }
              catch (InvalidPluginDefinitionException | PluginNotFoundException) {
                // Deliberately ignored.
              }
            }
          }
        }
      }
    }

    $event = new QueryEvent($destination, $sources);
    $this->eventDispatcher->dispatch($event, WayfindingEvents::QUERY);
    return array_merge($sources, $event->getSources());
  }

  /**
   * Add a source entity to the list of sources.
   *
   * @param array $sources
   *   The list of existing sources.
   * @param string $type
   *   The type of the new source.
   * @param int $id
   *   The ID of the new source.
   */
  protected function addSource(array &$sources, string $type, int $id): void {
    try {
      /** @var \Drupal\Core\Entity\ContentEntityInterface|null $source */
      $source = $this->entityTypeManager->getStorage($type)->load($id);
      if ($source) {
        $value = $source->get('wayfinding')->getValue();
        if ($value) {
          /** @var \Drupal\wayfinding\Entity\Wayfinding|null $wayfinding */
          $wayfinding = Wayfinding::load($value[0]['target_id']);
          if ($wayfinding && $wayfinding->isEnabled()) {
            $sources[] = $source;
          }
        }
      }
    }
    catch (InvalidPluginDefinitionException | PluginNotFoundException) {
      // Deliberately ignored.
    }
  }

  /**
   * Get the popup destination.
   *
   * @return string
   *   The popup destination.
   */
  public function getPopupDestination(): string {
    return $this->config->get('popup destination');
  }

  /**
   * Get the popup content.
   *
   * @return string
   *   The popup content.
   */
  public function getPopupContent(): string {
    if (!$this->config->get('enable popups') || $this->requestStack->getCurrentRequest()->get('no-popup-content', FALSE)) {
      return '';
    }
    $this->prepareSource();
    $output = '';
    foreach ($this->sources as $entity_type => $sources) {
      foreach ($sources as $entity_id => $destinations) {
        $output .= $this->buildPopupContent($entity_type, (int) $entity_id);
      }
    }
    return $output;
  }

  /**
   * Helper function to build popup content.
   *
   * @param string $entityType
   *   The entity type.
   * @param int $entityId
   *   The entity ID.
   *
   * @return string
   *   The rendered content for the given entity.
   */
  private function buildPopupContent(string $entityType, int $entityId): string {
    try {
      $entity = $this->entityTypeManager->getStorage($entityType)->load($entityId);
      if ($entity) {
        $output = $this->entityTypeManager->getViewBuilder($entityType)->view($entity, 'wayfinding');
        $srcid = 'wayfinding-src-id-' . $entity->getEntityTypeId() . '-' . $entity->id();
        return '<div class="popup-content ' . $srcid . '">' . $this->renderer->renderInIsolation($output) . '</div>';
      }
    }
    catch (InvalidPluginDefinitionException | PluginNotFoundException) {
      // Deliberately ignored.
    }
    return '';
  }

}

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

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