You are here

class EntityMatcher in Linkit 8.5

Same name and namespace in other branches
  1. 8.4 src/Plugin/Linkit/Matcher/EntityMatcher.php \Drupal\linkit\Plugin\Linkit\Matcher\EntityMatcher

Provides default linkit matchers for all entity types.

Plugin annotation


@Matcher(
  id = "entity",
  label = @Translation("Entity"),
  deriver = "\Drupal\linkit\Plugin\Derivative\EntityMatcherDeriver"
)

Hierarchy

Expanded class hierarchy of EntityMatcher

File

src/Plugin/Linkit/Matcher/EntityMatcher.php, line 35

Namespace

Drupal\linkit\Plugin\Linkit\Matcher
View source
class EntityMatcher extends ConfigurableMatcherBase {
  use MatcherTokensTrait;

  /**
   * The default limit for matches.
   */
  const DEFAULT_LIMIT = 100;

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

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

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

  /**
   * The entity repository.
   *
   * @var \Drupal\Core\Entity\EntityRepositoryInterface
   */
  protected $entityRepository;

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

  /**
   * The current user.
   *
   * @var \Drupal\Core\Session\AccountInterface
   */
  protected $currentUser;

  /**
   * The target entity type ID.
   *
   * @var string
   */
  protected $targetType;

  /**
   * The substitution manager.
   *
   * @var \Drupal\linkit\SubstitutionManagerInterface
   */
  protected $substitutionManager;

  /**
   * {@inheritdoc}
   */
  public function __construct(array $configuration, $plugin_id, $plugin_definition, Connection $database, EntityTypeManagerInterface $entity_type_manager, EntityTypeBundleInfoInterface $entity_type_bundle_info, EntityRepositoryInterface $entity_repository, ModuleHandlerInterface $module_handler, AccountInterface $current_user, SubstitutionManagerInterface $substitution_manager) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
    if (empty($plugin_definition['target_entity'])) {
      throw new \InvalidArgumentException("Missing required 'target_entity' property for a matcher.");
    }
    $this->database = $database;
    $this->entityTypeManager = $entity_type_manager;
    $this->entityTypeBundleInfo = $entity_type_bundle_info;
    $this->entityRepository = $entity_repository;
    $this->moduleHandler = $module_handler;
    $this->currentUser = $current_user;
    $this->targetType = $plugin_definition['target_entity'];
    $this->substitutionManager = $substitution_manager;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static($configuration, $plugin_id, $plugin_definition, $container
      ->get('database'), $container
      ->get('entity_type.manager'), $container
      ->get('entity_type.bundle.info'), $container
      ->get('entity.repository'), $container
      ->get('module_handler'), $container
      ->get('current_user'), $container
      ->get('plugin.manager.linkit.substitution'));
  }

  /**
   * {@inheritdoc}
   */
  public function getSummary() {
    $summery = parent::getSummary();
    $entity_type = $this->entityTypeManager
      ->getDefinition($this->targetType);
    $metadata = $this->configuration['metadata'];
    if (!empty($metadata)) {
      $summery[] = $this
        ->t('Metadata: @metadata', [
        '@metadata' => $metadata,
      ]);
    }
    if ($entity_type
      ->hasKey('bundle')) {
      $has_bundle_filter = !empty($this->configuration['bundles']);
      $bundles = [];
      if ($has_bundle_filter) {
        $bundles_info = $this->entityTypeBundleInfo
          ->getBundleInfo($this->targetType);
        foreach ($this->configuration['bundles'] as $bundle) {
          $bundles[] = $bundles_info[$bundle]['label'];
        }
      }
      $summery[] = $this
        ->t('Bundle filter: @bundle_filter', [
        '@bundle_filter' => $has_bundle_filter ? implode(', ', $bundles) : $this
          ->t('None'),
      ]);
      $summery[] = $this
        ->t('Group by bundle: @bundle_grouping', [
        '@bundle_grouping' => $this->configuration['group_by_bundle'] ? $this
          ->t('Yes') : $this
          ->t('No'),
      ]);
      if (!empty($this->configuration['limit'])) {
        $summery[] = $this
          ->t('Limit: @limit', [
          '@limit' => $this->configuration['limit'],
        ]);
      }
    }
    return $summery;
  }

  /**
   * {@inheritdoc}
   */
  public function defaultConfiguration() {
    return [
      'metadata' => '',
      'bundles' => [],
      'group_by_bundle' => FALSE,
      'substitution_type' => SubstitutionManagerInterface::DEFAULT_SUBSTITUTION,
      'limit' => static::DEFAULT_LIMIT,
    ] + parent::defaultConfiguration();
  }

  /**
   * {@inheritdoc}
   */
  public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
    $entity_type = $this->entityTypeManager
      ->getDefinition($this->targetType);
    $form['metadata'] = [
      '#type' => 'details',
      '#title' => $this
        ->t('Suggestion metadata'),
      '#open' => TRUE,
      '#weight' => -100,
    ];
    $form['metadata']['metadata'] = [
      '#title' => $this
        ->t('Metadata'),
      '#type' => 'textfield',
      '#default_value' => $this->configuration['metadata'],
      '#description' => $this
        ->t('Metadata is shown together with each suggestion in the suggestion list.'),
      '#size' => 120,
      '#maxlength' => 255,
      '#weight' => 0,
    ];
    $this
      ->insertTokenList($form, [
      $this->targetType,
    ]);

    // Filter the possible bundles to use if the entity has bundles.
    if ($entity_type
      ->hasKey('bundle')) {
      $bundle_options = [];
      foreach ($this->entityTypeBundleInfo
        ->getBundleInfo($this->targetType) as $bundle_name => $bundle_info) {
        $bundle_options[$bundle_name] = $bundle_info['label'];
      }
      $form['bundle_restrictions'] = [
        '#type' => 'details',
        '#title' => $this
          ->t('Bundle restrictions'),
        '#open' => TRUE,
        '#weight' => -90,
      ];
      $form['bundle_restrictions']['bundles'] = [
        '#type' => 'checkboxes',
        '#title' => $this
          ->t('Restrict suggestions to the selected bundles'),
        '#options' => $bundle_options,
        '#default_value' => $this->configuration['bundles'],
        '#description' => $this
          ->t('If none of the checkboxes is checked, all bundles are allowed.'),
        '#element_validate' => [
          [
            get_class($this),
            'elementValidateFilter',
          ],
        ],
      ];
      $form['bundle_grouping'] = [
        '#type' => 'details',
        '#title' => $this
          ->t('Bundle grouping'),
        '#open' => TRUE,
      ];

      // Group the suggestions by bundle.
      $form['bundle_grouping']['group_by_bundle'] = [
        '#type' => 'checkbox',
        '#title' => $this
          ->t('Group by bundle'),
        '#default_value' => $this->configuration['group_by_bundle'],
        '#description' => $this
          ->t('Group suggestions by their bundle.'),
      ];
    }
    $substitution_options = $this->substitutionManager
      ->getApplicablePluginsOptionList($this->targetType);
    $form['substitution'] = [
      '#type' => 'details',
      '#title' => $this
        ->t('URL substitution'),
      '#open' => TRUE,
      '#weight' => 100,
      '#access' => count($substitution_options) !== 1,
    ];
    $form['substitution']['substitution_type'] = [
      '#title' => $this
        ->t('Substitution Type'),
      '#type' => 'select',
      '#default_value' => $this->configuration['substitution_type'],
      '#options' => $substitution_options,
      '#description' => $this
        ->t('Configure how the selected entity should be transformed into a URL for insertion.'),
    ];
    $form['limit'] = [
      '#type' => 'details',
      '#title' => $this
        ->t('Limit'),
      '#open' => TRUE,
    ];
    $form['limit']['limit'] = [
      '#type' => 'select',
      '#options' => [
        0 => $this
          ->t('Unlimited'),
        20 => 20,
        50 => 50,
        100 => 100,
        200 => 200,
      ],
      '#title' => $this
        ->t('Limit search results'),
      '#description' => $this
        ->t('Limit the amount of results displayed when searching.'),
      '#default_value' => $this->configuration['limit'],
    ];
    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
  }

  /**
   * {@inheritdoc}
   */
  public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
    $this->configuration['metadata'] = $form_state
      ->getValue('metadata');
    $this->configuration['bundles'] = $form_state
      ->getValue('bundles');
    $this->configuration['group_by_bundle'] = $form_state
      ->getValue('group_by_bundle');
    $this->configuration['substitution_type'] = $form_state
      ->getValue('substitution_type');
    $this->configuration['limit'] = $form_state
      ->getValue('limit');
  }

  /**
   * Form element validation handler; Filters the #value property of an element.
   */
  public static function elementValidateFilter(&$element, FormStateInterface $form_state) {
    $element['#value'] = array_filter($element['#value']);
    $form_state
      ->setValueForElement($element, $element['#value']);
  }

  /**
   * {@inheritdoc}
   */
  public function execute($string) {
    $suggestions = new SuggestionCollection();
    $query = $this
      ->buildEntityQuery($string);
    $query_result = $query
      ->execute();
    $url_results = $this
      ->findEntityIdByUrl($string);
    $result = array_merge($query_result, $url_results);

    // If no results, return an empty suggestion collection.
    if (empty($result)) {
      return $suggestions;
    }
    $entities = $this->entityTypeManager
      ->getStorage($this->targetType)
      ->loadMultiple($result);
    foreach ($entities as $entity) {

      // Check the access against the defined entity access handler.

      /** @var \Drupal\Core\Access\AccessResultInterface $access */
      $access = $entity
        ->access('view', $this->currentUser, TRUE);
      if (!$access
        ->isAllowed()) {
        continue;
      }
      $entity = $this->entityRepository
        ->getTranslationFromContext($entity);
      $suggestion = $this
        ->createSuggestion($entity);
      $suggestions
        ->addSuggestion($suggestion);
    }
    return $suggestions;
  }

  /**
   * Builds an EntityQuery to get entities.
   *
   * @param string $search_string
   *   Text to match the label against.
   *
   * @return \Drupal\Core\Entity\Query\QueryInterface
   *   The EntityQuery object with the basic conditions and sorting applied to
   *   it.
   */
  protected function buildEntityQuery($search_string) {
    $search_string = $this->database
      ->escapeLike($search_string);
    $entity_type = $this->entityTypeManager
      ->getDefinition($this->targetType);
    $query = $this->entityTypeManager
      ->getStorage($this->targetType)
      ->getQuery();
    $label_key = $entity_type
      ->getKey('label');
    if ($label_key) {

      // For configuration entities, the condition needs to be CONTAINS as
      // the matcher does not support LIKE.
      if ($entity_type instanceof ConfigEntityTypeInterface) {
        $query
          ->condition($label_key, $search_string, 'CONTAINS');
      }
      else {
        $query
          ->condition($label_key, '%' . $search_string . '%', 'LIKE');
      }
      $query
        ->sort($label_key, 'ASC');
    }

    // Bundle check.
    if (!empty($this->configuration['bundles']) && ($bundle_key = $entity_type
      ->getKey('bundle'))) {
      $query
        ->condition($bundle_key, $this->configuration['bundles'], 'IN');
    }
    if ($this->configuration['limit']) {
      $query
        ->range(0, $this->configuration['limit']);
    }
    $this
      ->addQueryTags($query);
    return $query;
  }

  /**
   * Adds query tags to the query.
   *
   * @param \Drupal\Core\Entity\Query\QueryInterface $query
   *   A query to add tags to.
   */
  protected function addQueryTags(QueryInterface $query) {

    // Add tags to let other modules alter the query.
    $query
      ->addTag('linkit_entity_autocomplete');
    $query
      ->addTag('linkit_entity_' . $this->targetType . '_autocomplete');

    // Add access tag for the query.
    $query
      ->addTag('entity_access');
    $query
      ->addTag($this->targetType . '_access');
  }

  /**
   * Creates a suggestion.
   *
   * @param \Drupal\Core\Entity\EntityInterface $entity
   *   The matched entity.
   *
   * @return \Drupal\linkit\Suggestion\EntitySuggestion
   *   A suggestion object with populated entity data.
   */
  protected function createSuggestion(EntityInterface $entity) {
    $suggestion = new EntitySuggestion();
    $suggestion
      ->setLabel($this
      ->buildLabel($entity))
      ->setGroup($this
      ->buildGroup($entity))
      ->setDescription($this
      ->buildDescription($entity))
      ->setEntityUuid($entity
      ->uuid())
      ->setEntityTypeId($entity
      ->getEntityTypeId())
      ->setSubstitutionId($this->configuration['substitution_type'])
      ->setPath($this
      ->buildPath($entity));
    return $suggestion;
  }

  /**
   * Builds the label string used in the suggestion.
   *
   * @param \Drupal\Core\Entity\EntityInterface $entity
   *   The matched entity.
   *
   * @return string
   *   The label for this entity.
   */
  protected function buildLabel(EntityInterface $entity) {
    return Html::escape($entity
      ->label());
  }

  /**
   * Builds the metadata string used in the suggestion.
   *
   * @param \Drupal\Core\Entity\EntityInterface $entity
   *   The matched entity.
   *
   * @return string
   *   The metadata for this entity.
   */
  protected function buildDescription(EntityInterface $entity) {
    $description = \Drupal::token()
      ->replace($this->configuration['metadata'], [
      $this->targetType => $entity,
    ], [
      'clear' => TRUE,
    ]);
    return LinkitXss::descriptionFilter($description);
  }

  /**
   * Builds the group string used in the suggestion.
   *
   * @param \Drupal\Core\Entity\EntityInterface $entity
   *   The matched entity.
   *
   * @return string
   *   The match group for this entity.
   */
  protected function buildGroup(EntityInterface $entity) {
    $group = $entity
      ->getEntityType()
      ->getLabel();

    // If the entities by this entity should be grouped by bundle, get the
    // name and append it to the group.
    if ($this->configuration['group_by_bundle']) {
      $bundles = $this->entityTypeBundleInfo
        ->getBundleInfo($entity
        ->getEntityTypeId());
      $bundle_label = $bundles[$entity
        ->bundle()]['label'];
      $group .= ' - ' . $bundle_label;
    }
    return $group;
  }

  /**
   * Builds the path string used in the suggestion.
   *
   * @param \Drupal\Core\Entity\EntityInterface $entity
   *   The matched entity.
   *
   * @return string
   *   The path for this entity.
   */
  protected function buildPath(EntityInterface $entity) {
    $path = $entity
      ->toUrl('canonical', [
      'path_processing' => FALSE,
    ])
      ->toString();

    // For media entities, check if standalone URLs are allowed. If not, then
    // strip '/edit' from the end of the canonical URL returned
    // by $entity->toUrl().
    if ($entity
      ->getEntityTypeId() == 'media') {
      $standalone_url = \Drupal::config('media.settings')
        ->get('standalone_url');
      if (!$standalone_url) {

        // Strip "/edit".
        $path = substr($path, 0, -5);
      }
    }
    return $path;
  }

  /**
   * Finds entity id from the given input.
   *
   * @param string $user_input
   *   The string to url parse.
   *
   * @return array
   *   An array with an entity id if the input can be parsed as an internal url
   *   and a match is found, otherwise an empty array.
   */
  protected function findEntityIdByUrl($user_input) {
    $result = [];
    try {
      $params = Url::fromUserInput($user_input)
        ->getRouteParameters();
      if (key($params) === $this->targetType) {
        $result = [
          end($params),
        ];
      }
    } catch (Exception $e) {

      // Do nothing.
    }
    return $result;
  }

}

Members

Namesort descending Modifiers Type Description Overrides
DependencySerializationTrait::$_entityStorages protected property An array of entity type IDs keyed by the property name of their storages.
DependencySerializationTrait::$_serviceIds protected property An array of service IDs keyed by property name used for serialization.
DependencySerializationTrait::__sleep public function 1
DependencySerializationTrait::__wakeup public function 2
EntityMatcher::$currentUser protected property The current user.
EntityMatcher::$database protected property The database connection.
EntityMatcher::$entityRepository protected property The entity repository.
EntityMatcher::$entityTypeBundleInfo protected property The entity type bundle info.
EntityMatcher::$entityTypeManager protected property The entity type manager.
EntityMatcher::$moduleHandler protected property The module handler service.
EntityMatcher::$substitutionManager protected property The substitution manager.
EntityMatcher::$targetType protected property The target entity type ID.
EntityMatcher::addQueryTags protected function Adds query tags to the query.
EntityMatcher::buildConfigurationForm public function Form constructor. Overrides PluginFormInterface::buildConfigurationForm 4
EntityMatcher::buildDescription protected function Builds the metadata string used in the suggestion. 2
EntityMatcher::buildEntityQuery protected function Builds an EntityQuery to get entities. 4
EntityMatcher::buildGroup protected function Builds the group string used in the suggestion.
EntityMatcher::buildLabel protected function Builds the label string used in the suggestion.
EntityMatcher::buildPath protected function Builds the path string used in the suggestion. 1
EntityMatcher::create public static function Creates an instance of the plugin. Overrides MatcherBase::create
EntityMatcher::createSuggestion protected function Creates a suggestion.
EntityMatcher::defaultConfiguration public function Gets default configuration for this plugin. Overrides MatcherBase::defaultConfiguration 3
EntityMatcher::DEFAULT_LIMIT constant The default limit for matches.
EntityMatcher::elementValidateFilter public static function Form element validation handler; Filters the #value property of an element.
EntityMatcher::execute public function Executes the matcher. Overrides MatcherInterface::execute
EntityMatcher::findEntityIdByUrl protected function Finds entity id from the given input.
EntityMatcher::getSummary public function Returns the summarized configuration of the matcher. Overrides MatcherBase::getSummary 3
EntityMatcher::submitConfigurationForm public function Form submission handler. Overrides PluginFormInterface::submitConfigurationForm 3
EntityMatcher::validateConfigurationForm public function Form validation handler. Overrides PluginFormInterface::validateConfigurationForm
EntityMatcher::__construct public function Constructs a \Drupal\Component\Plugin\PluginBase object. Overrides MatcherBase::__construct
MatcherBase::$uuid protected property The matcher ID.
MatcherBase::$weight protected property The weight of the matcher compared to others in a matcher collection.
MatcherBase::calculateDependencies public function Calculates dependencies for the configured plugin. Overrides DependentPluginInterface::calculateDependencies 5
MatcherBase::getConfiguration public function Gets this plugin's configuration. Overrides ConfigurableInterface::getConfiguration
MatcherBase::getLabel public function Returns the matcher label. Overrides MatcherInterface::getLabel
MatcherBase::getUuid public function Returns the unique ID representing the matcher. Overrides MatcherInterface::getUuid
MatcherBase::getWeight public function Returns the weight of the matcher. Overrides MatcherInterface::getWeight
MatcherBase::setConfiguration public function Sets the configuration for this plugin instance. Overrides ConfigurableInterface::setConfiguration
MatcherBase::setWeight public function Sets the weight for the matcher. Overrides MatcherInterface::setWeight
MatcherTokensTrait::getAvailableTokens public function Gets all available tokens.
MatcherTokensTrait::insertTokenList public function Inserts a form element with a list of available tokens.
MessengerTrait::$messenger protected property The messenger. 29
MessengerTrait::messenger public function Gets the messenger. 29
MessengerTrait::setMessenger public function Sets the messenger.
PluginBase::$configuration protected property Configuration information passed into the plugin. 1
PluginBase::$pluginDefinition protected property The plugin implementation definition. 1
PluginBase::$pluginId protected property The plugin_id.
PluginBase::DERIVATIVE_SEPARATOR constant A string which is used to separate base plugin IDs from the derivative ID.
PluginBase::getBaseId public function Gets the base_plugin_id of the plugin instance. Overrides DerivativeInspectionInterface::getBaseId
PluginBase::getDerivativeId public function Gets the derivative_id of the plugin instance. Overrides DerivativeInspectionInterface::getDerivativeId
PluginBase::getPluginDefinition public function Gets the definition of the plugin implementation. Overrides PluginInspectionInterface::getPluginDefinition 3
PluginBase::getPluginId public function Gets the plugin_id of the plugin instance. Overrides PluginInspectionInterface::getPluginId
PluginBase::isConfigurable public function Determines if the plugin is configurable.
StringTranslationTrait::$stringTranslation protected property The string translation service. 1
StringTranslationTrait::formatPlural protected function Formats a string containing a count of items.
StringTranslationTrait::getNumberOfPlurals protected function Returns the number of plurals supported by a given language.
StringTranslationTrait::getStringTranslation protected function Gets the string translation service.
StringTranslationTrait::setStringTranslation public function Sets the string translation service to use. 2
StringTranslationTrait::t protected function Translates a string to the current language or to a given language.