You are here

EntityField.php in Entity Field Condition 2.0.x

File

src/Plugin/Condition/EntityField.php
View source
<?php

namespace Drupal\entity_field_condition\Plugin\Condition;

use Drupal\Core\Render\Element;
use Drupal\Core\Form\SubformState;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Condition\ConditionPluginBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Field\FieldTypePluginManagerInterface;
use Drupal\entity_field_condition\Contracts\EntityFieldCompareTypeInterface;
use Drupal\entity_field_condition\Contracts\EntityFieldCompareTypeManagerInterface;

/**
 * Define the entity field condition base class.
 *
 * @Condition(
 *   id = "entity_field",
 *   label = @Translation("Entity Field"),
 *   deriver = "Drupal\entity_field_condition\Plugin\Deriver\EntityFieldConditionDeriver"
 * )
 */
class EntityField extends ConditionPluginBase implements ContainerFactoryPluginInterface {

  /**
   * Drupal\Core\Entity\EntityTypeManagerInterface definition.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $entityTypeManager;

  /**
   * Drupal\Core\Entity\EntityFieldManagerInterface definition.
   *
   * @var \Drupal\Core\Entity\EntityFieldManagerInterface
   */
  protected $entityFieldManager;

  /**
   * Drupal\Core\Field\FieldTypePluginManagerInterface definition.
   *
   * @var \Drupal\Core\Field\FieldTypePluginManagerInterface
   */
  protected $fieldTypePluginManager;

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

  /**
   * The entity field compare type manager.
   *
   * @var \Drupal\entity_field_condition\Contracts\EntityFieldCompareTypeManagerInterface
   */
  protected $entityFieldCompareTypeManager;

  /**
   * The entity field condition constructor.
   *
   * @param array $configuration
   *   The plugin configuration, i.e. an array with configuration values keyed
   *   by configuration option name. The special key 'context' may be used to
   *   initialize the defined contexts by setting it to an array of context
   *   values keyed by context names.
   * @param string $plugin_id
   *   The plugin_id for the plugin instance.
   * @param mixed $plugin_definition
   *   The plugin implementation definition.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager interface.
   * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
   *   The entity field manager interface.
   * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info
   *   The entity type bundle info.
   * @param \Drupal\Core\Field\FieldTypePluginManagerInterface $field_type_plugin_manager
   *   The field type plugin manager interface.
   * @param \Drupal\entity_field_condition\Contracts\EntityFieldCompareTypeManagerInterface $entity_field_compare_type_manager
   *   The field compare type manager.
   */
  public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, EntityFieldManagerInterface $entity_field_manager, EntityTypeBundleInfoInterface $entity_type_bundle_info, FieldTypePluginManagerInterface $field_type_plugin_manager, EntityFieldCompareTypeManagerInterface $entity_field_compare_type_manager) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
    $this->entityTypeManager = $entity_type_manager;
    $this->entityFieldManager = $entity_field_manager;
    $this->entityTypeBundleInfo = $entity_type_bundle_info;
    $this->fieldTypePluginManager = $field_type_plugin_manager;
    $this->entityFieldCompareTypeManager = $entity_field_compare_type_manager;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static($configuration, $plugin_id, $plugin_definition, $container
      ->get('entity_type.manager'), $container
      ->get('entity_field.manager'), $container
      ->get('entity_type.bundle.info'), $container
      ->get('plugin.manager.field.field_type'), $container
      ->get('plugin.manager.entity_field_condition.compare_type'));
  }

  /**
   * {@inheritdoc}
   */
  public function defaultConfiguration() : array {
    return [
      'entity' => [
        'bundle' => NULL,
        'field' => NULL,
        'compare_type' => NULL,
        'compare_configuration' => [],
      ],
    ] + parent::defaultConfiguration();
  }

  /**
   * {@inheritdoc}
   */
  public function buildConfigurationForm(array $form, FormStateInterface $form_state) : array {
    $parents = $form['#parents'] ?? $this
      ->resolveFormParents($form_state);
    $wrapper_id = $this
      ->buildWrapperId();
    $form['entity'] = [
      '#tree' => TRUE,
      '#type' => 'container',
      '#prefix' => "<div id='{$wrapper_id}'>",
      '#suffix' => '</div>',
    ];
    $entity_bundle = $this
      ->getFormStateValue(array_merge($parents, [
      'entity',
      'bundle',
    ]), $form_state, $this
      ->getEntityBundleType());
    $form['entity']['bundle'] = [
      '#type' => 'select',
      '#title' => $this
        ->t('Entity Type'),
      '#options' => $this
        ->getEntityContextDataEntityBundleOptions(),
      '#empty_option' => $this
        ->t('- None -'),
      '#default_value' => $entity_bundle,
      '#ajax' => [
        'event' => 'change',
        'method' => 'replace',
        'callback' => [
          $this,
          'ajaxCallback',
        ],
        'wrapper' => $wrapper_id,
        'progress' => [
          'type' => 'throbber',
          'message' => $this
            ->t('Loading fields...'),
        ],
      ],
    ];
    if (isset($entity_bundle) && !empty($entity_bundle)) {
      $form['entity']['field'] = [
        '#type' => 'select',
        '#title' => $this
          ->t('Field'),
        '#required' => TRUE,
        '#options' => $this
          ->getEntityBundleFieldOptions($entity_bundle),
        '#default_value' => $this
          ->getEntityFieldName(),
      ];
      $compare_type_id = $this
        ->getFormStateValue(array_merge($parents, [
        'entity',
        'compare_type',
      ]), $form_state, $this
        ->getConfigurationEntity()['compare_type']);
      $compare_type_options = $this->entityFieldCompareTypeManager
        ->getDefinitionOptions();
      $form['entity']['compare_type'] = [
        '#type' => 'select',
        '#title' => $this
          ->t('Compare Type'),
        '#required' => TRUE,
        '#options' => $compare_type_options,
        '#empty_option' => $this
          ->t('- None -'),
        '#default_value' => $compare_type_id,
        '#ajax' => [
          'event' => 'change',
          'method' => 'replace',
          'callback' => [
            $this,
            'ajaxCallback',
          ],
          'wrapper' => $wrapper_id,
          'progress' => [
            'type' => 'throbber',
            'message' => $this
              ->t('Loading compare type...'),
          ],
        ],
      ];
      if (isset($compare_type_id, $compare_type_options[$compare_type_id])) {
        $form['entity']['compare_configuration'] = [
          '#type' => 'fieldset',
          '#title' => $this
            ->t('Compare Configuration'),
          '#tree' => TRUE,
        ];
        try {

          /** @var \Drupal\entity_field_condition\Plugin\EntityFieldCondition\CompareType\EntityFieldCompareTypePluginBase $instance */
          $instance = $this->entityFieldCompareTypeManager
            ->createInstance($compare_type_id, $this
            ->getCompareTypeConfigurations());
          if ($instance instanceof EntityFieldCompareTypeInterface) {
            $subform = [
              '#parents' => array_merge($parents, [
                'entity',
                'compare_configuration',
              ]),
            ];
            $form['entity']['compare_configuration'] += $instance
              ->buildConfigurationForm($subform, SubformState::createForSubform($subform, $form, $form_state));
          }
        } catch (\RuntimeException $exception) {
          watchdog_exception('entity_field_condition', $exception);
        }
        if (count(Element::children($form['entity']['compare_configuration'])) === 0) {
          unset($form['entity']['compare_configuration']);
        }
      }
    }
    return parent::buildConfigurationForm($form, $form_state);
  }

  /**
   * Build a unique wrapper HTML ID.
   *
   * @return string
   *   A valid HTML ID.
   */
  protected function buildWrapperId() : string {
    return Html::getId(implode('-', [
      'entity-field-condition',
      $this
        ->getPluginId(),
    ]));
  }

  /**
   * The entity context data entity bundle options.
   *
   * @return array
   *   An array of entity bundle types.
   *
   * @throws \Drupal\Component\Plugin\Exception\PluginException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  protected function getEntityContextDataEntityBundleOptions() : array {
    $options = [];
    foreach ($this
      ->getEntityContextDataEntityBundleTypes() as $type => $info) {
      if (!isset($info['label'])) {
        continue;
      }
      $options[$type] = $info['label'];
    }
    return $options;
  }

  /**
   * Get the entity field options.
   *
   * @param string $bundle
   *   The node type machine name.
   *
   * @return array
   *   An array of entity field options.
   *
   * @throws \Drupal\Component\Plugin\Exception\PluginException
   */
  protected function getEntityBundleFieldOptions(string $bundle) : array {
    if (empty($bundle)) {
      return [];
    }
    $options = [];
    $field_types = $this->fieldTypePluginManager
      ->getDefinitions();
    foreach ($this
      ->getEntityContextDataEntityFieldDefinitions($bundle) as $definition) {
      $type = $definition
        ->getType();
      if (!isset($field_types[$type])) {
        continue;
      }

      /** @var \Drupal\Core\StringTranslation\TranslatableMarkup $category */
      $category = $field_types[$type]['label'];
      $options[$category
        ->render()][$definition
        ->getName()] = $definition
        ->getLabel();
    }
    return $options;
  }

  /**
   * The form Ajax callback.
   *
   * @param array $form
   *   An associative array containing the structure of the form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current state of the form.
   *
   * @return array
   *   An array of the form elements.
   */
  public function ajaxCallback(array $form, FormStateInterface $form_state) : array {
    $button = $form_state
      ->getTriggeringElement();
    return NestedArray::getValue($form, array_splice($button['#array_parents'], 0, -1));
  }

  /**
   * {@inheritdoc}
   */
  public function validateConfigurationForm(array &$form, FormStateInterface $form_state) : void {
    if ($compare_type_id = $form_state
      ->getValue([
      'entity',
      'compare_type',
    ])) {
      try {

        /** @var \Drupal\entity_field_condition\Plugin\EntityFieldCondition\CompareType\EntityFieldCompareTypePluginBase $instance */
        $instance = $this->entityFieldCompareTypeManager
          ->createInstance($compare_type_id, $this
          ->getCompareTypeConfigurations());
        $parents = $form['#parents'] ?? [];
        if ($instance instanceof EntityFieldCompareTypeInterface) {
          $subform = [
            '#parents' => array_merge($parents, [
              'entity',
              'compare_configuration',
            ]),
          ];
          $instance
            ->validateConfigurationForm($subform, SubformState::createForSubform($subform, $form, $form_state));
        }
      } catch (\RuntimeException $exception) {
        watchdog_exception('entity_field_condition', $exception);
      }
    }
  }

  /**
   * {@inheritdoc}
   */
  public function submitConfigurationForm(array &$form, FormStateInterface $form_state) : void {
    parent::submitConfigurationForm($form, $form_state);
    $values = $form_state
      ->cleanValues()
      ->getValues();
    if (!isset($values['entity']['bundle']) || empty($values['entity']['bundle'])) {
      $values = [];
    }
    if ($compare_type_id = $form_state
      ->getValue([
      'entity',
      'compare_type',
    ])) {
      try {

        /** @var \Drupal\entity_field_condition\Plugin\EntityFieldCondition\CompareType\EntityFieldCompareTypePluginBase $instance */
        $instance = $this->entityFieldCompareTypeManager
          ->createInstance($compare_type_id, $this
          ->getCompareTypeConfigurations());
        $parents = $form['#parents'] ?? [];
        if ($instance instanceof EntityFieldCompareTypeInterface) {
          $subform_parents = [
            'entity',
            'compare_configuration',
          ];
          $subform = [
            '#parents' => array_merge($parents, $subform_parents),
          ];
          $instance
            ->submitConfigurationForm($subform, SubformState::createForSubform($subform, $form, $form_state));
          NestedArray::setValue($values, $subform_parents, $instance
            ->getConfiguration());
        }
      } catch (\RuntimeException $exception) {
        watchdog_exception('entity_field_condition', $exception);
      }
    }
    $this
      ->setConfiguration($values);
  }

  /**
   * {@inheritdoc}
   */
  public function evaluate() : bool {
    $verdict = FALSE;
    $field_name = $this
      ->getEntityFieldName();
    $compare_type = $this
      ->getEntityFieldCompareType();
    if (isset($field_name, $compare_type) && $this
      ->hasEntityContext()) {
      $verdict = $compare_type
        ->evaluate($this
        ->getContextValue('entity'), $field_name);
    }
    return $this
      ->isNegated() ? !$verdict : $verdict;
  }

  /**
   * {@inheritdoc}
   */
  public function summary() : ?TranslatableMarkup {
    $entity_field = $this
      ->getEntityFieldName();
    $entity_bundle = $this
      ->getEntityBundleType();
    if (isset($entity_field, $entity_bundle)) {
      $entity_definition = $this
        ->getEntityContextDataEntityDefinition();
      return $this
        ->t('The @entity_type with bundle of @entity_bundle has a condition for @field.', [
        '@entity_type' => $entity_definition
          ->getLabel(),
        '@entity_bundle' => $entity_bundle,
        '@field' => $entity_field,
      ]);
    }
    return NULL;
  }

  /**
   * Get the form state value.
   *
   * @param mixed $key
   *   The value key.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state instance.
   * @param mixed|null $default
   *   The default value.
   * @param bool $checkEmpty
   *   Check if the value is empty.
   *
   * @return mixed
   *   The value for the property.
   */
  protected function getFormStateValue($key, FormStateInterface $form_state, $default = NULL, bool $checkEmpty = FALSE) {
    $key = !is_array($key) ? [
      $key,
    ] : $key;
    $inputs = [
      $form_state
        ->getValues(),
      $form_state
        ->getUserInput(),
    ];
    foreach ($inputs as $input) {
      $key_exist = FALSE;
      $value = NestedArray::getValue($input, $key, $key_exist);
      if ($key_exist) {
        if ($checkEmpty === TRUE && empty($value)) {
          continue;
        }
        return $value;
      }
    }
    return $default;
  }

  /**
   * Determine if the entity context exist.
   *
   * @throws \Drupal\Component\Plugin\Exception\PluginException
   */
  protected function hasEntityContext() : bool {
    return $this
      ->getContextValue('entity') instanceof ContentEntityInterface;
  }

  /**
   * Get the configuration entity.
   *
   * @return array
   *   An array of the configuration entity settings.
   */
  protected function getConfigurationEntity() : array {
    return $this
      ->getConfiguration()['entity'] ?? [];
  }

  /**
   * Get entity field name.
   *
   * @return string|null
   *   The entity field machine name.
   */
  protected function getEntityFieldName() : ?string {
    return $this
      ->getConfigurationEntity()['field'] ?? NULL;
  }

  /**
   * Get entity bundle type.
   *
   * @return string|null
   *   The entity bundle type.
   */
  protected function getEntityBundleType() : ?string {
    return $this
      ->getConfigurationEntity()['bundle'] ?? NULL;
  }

  /**
   * Get compare type configurations.
   *
   * @return array
   *   An array of compare type configurations.
   */
  protected function getCompareTypeConfigurations() : array {
    return $this
      ->getConfigurationEntity()['compare_configuration'] ?? [];
  }

  /**
   * Resolve form parents array.
   *
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state object.
   *
   * @return array
   *   An array of the form parents.
   */
  protected function resolveFormParents(FormStateInterface $form_state) : array {
    $parents = [];
    if ($form_object = $form_state
      ->getFormObject()) {
      switch (get_class($form_object)) {
        case 'Drupal\\block\\BlockForm':
          $parents = [
            'visibility',
            $this
              ->getPluginId(),
          ];
          break;
        case 'Drupal\\block_visibility_groups\\Form\\ConditionAddForm':
          $parents = [
            'condition',
          ];
          break;
      }
    }
    return $parents;
  }

  /**
   * Get entity field compare type instance.
   *
   * @return \Drupal\entity_field_condition\Contracts\EntityFieldCompareTypeInterface|null
   *   The entity field compare type instance.
   *
   * @throws \Drupal\Component\Plugin\Exception\PluginException
   */
  protected function getEntityFieldCompareType() : ?EntityFieldCompareTypeInterface {
    $compare_type_id = $this
      ->getConfigurationEntity()['compare_type'] ?? NULL;
    if (!isset($compare_type_id)) {
      return NULL;
    }
    return $this->entityFieldCompareTypeManager
      ->createInstance($compare_type_id, $this
      ->getCompareTypeConfigurations());
  }

  /**
   * Get entity context data entity type ID.
   *
   * @throws \Drupal\Component\Plugin\Exception\PluginException
   */
  protected function getEntityContextDataEntityTypeId() : ?string {
    $context_definition = $this
      ->getContextDefinition('entity');
    if (!isset($context_definition)) {
      return NULL;
    }

    /** @var \Drupal\Core\Entity\TypedData\EntityDataDefinition $data_definition */
    $data_definition = $context_definition
      ->getDataDefinition();
    return $data_definition
      ->getEntityTypeId();
  }

  /**
   * Get entity context data entity definition.
   *
   * @return \Drupal\Core\Entity\EntityTypeInterface
   *   The entity definition object.
   *
   * @throws \Drupal\Component\Plugin\Exception\PluginException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  protected function getEntityContextDataEntityDefinition() : EntityTypeInterface {
    return $this->entityTypeManager
      ->getDefinition($this
      ->getEntityContextDataEntityTypeId());
  }

  /**
   * Get entity context data entity field definitions.
   *
   * @param string $bundle
   *   The entity bundle type name.
   *
   * @return \Drupal\Core\Field\FieldDefinitionInterface[]
   *   An array of the field definitions.
   *
   * @throws \Drupal\Component\Plugin\Exception\PluginException
   */
  protected function getEntityContextDataEntityFieldDefinitions(string $bundle) : array {
    return $this->entityFieldManager
      ->getFieldDefinitions($this
      ->getEntityContextDataEntityTypeId(), $bundle);
  }

  /**
   * Get the entity context data entity bundle types.
   *
   * @return array
   *   An array of bundle types.
   *
   * @throws \Drupal\Component\Plugin\Exception\PluginException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  protected function getEntityContextDataEntityBundleTypes() : array {
    return $this->entityTypeBundleInfo
      ->getBundleInfo($this
      ->getEntityContextDataEntityTypeId());
  }

}

Classes

Namesort descending Description
EntityField Define the entity field condition base class.