sites_group_overrides-1.x-dev/src/FormDecorator/EntityOverrideAbilityClues.php

src/FormDecorator/EntityOverrideAbilityClues.php
<?php

declare(strict_types=1);

namespace Drupal\sites_group_overrides\FormDecorator;

use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
use Drupal\Core\Entity\ContentEntityFormInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Render\Markup;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\form_decorator\ContentEntityFormDecoratorBase;
use Drupal\group\Entity\GroupInterface;
use Drupal\group\Entity\GroupRelationshipInterface;
use Drupal\sites\ContextProvider\CurrentSiteContextInterface;
use Drupal\sites_group_overrides\SitesGroupOverridesServiceInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

/**
 * Implementation for all ContentEntityFormInterface forms.
 *
 * @FormDecorator()
 */
final class EntityOverrideAbilityClues extends ContentEntityFormDecoratorBase implements ContainerFactoryPluginInterface {

  use StringTranslationTrait;
  use DependencySerializationTrait;

  /**
   * Constructs a EntityOverrideAbilityClues form decorator.
   *
   * @param array $configuration
   *   A configuration array containing information about the plugin instance.
   * @param string $plugin_id
   *   The plugin ID for the plugin instance.
   * @param array $plugin_definition
   *   The plugin definition.
   * @param \Drupal\sites_group_overrides\SitesGroupOverridesServiceInterface $sitesGroupOverridesService
   *   The sites group override service.
   * @param \Symfony\Contracts\EventDispatcher\EventDispatcherInterface $eventDispatcher
   *   The event dispatcher.
   * @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
   *   A config factory for retrieving required config objects.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
   *   The entity type manager.
   * @param \Drupal\sites\ContextProvider\CurrentSiteContextInterface $currentSite
   *   The current site context.
   */
  public function __construct(
    array $configuration,
    $plugin_id,
    $plugin_definition,
    protected SitesGroupOverridesServiceInterface $sitesGroupOverridesService,
    protected EventDispatcherInterface $eventDispatcher,
    protected ConfigFactoryInterface $configFactory,
    protected EntityTypeManagerInterface $entityTypeManager,
    protected CurrentSiteContextInterface $currentSite,
  ) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, $configuration, $plugin_id, $plugin_definition) {
    return new self(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $container->get('sites_group_overrides.service'),
      $container->get('event_dispatcher'),
      $container->get('config.factory'),
      $container->get('entity_type.manager'),
      $container->get('sites.current_site'),
    );
  }

  /**
   * {@inheritdoc}
   */
  public function applies(): bool {
    if (!$this->inner instanceof ContentEntityFormInterface) {
      return FALSE;
    }
    // Do not modify group form - groups are never overridable.
    if ($this->getEntity() instanceof GroupInterface) {
      return FALSE;
    }
    if ($this->getEntity() instanceof GroupRelationshipInterface) {
      return FALSE;
    }
    if (!$this->currentSite->getSiteFromContext()) {
      return FALSE;
    }
    // For whatever reason some entites use the 'default' form operation (Term)
    // And other have the 'edit' form opertaion (Nodes).
    if (!in_array($this->inner->getOperation(), ['edit', 'default'])) {
      return FALSE;
    }
    // Don't do this on create forms. They use 'default' as their operation.
    if ($this->inner->getEntity()->isNew()) {
      return FALSE;
    }

    // Decide by setting.
    $apply_to_non_overideable_entities = $this->configFactory->get('sites_group_overrides.settings')->get('apply_to_non_overideable_entities');
    $entity = $this->getEntity();
    \Drupal::moduleHandler()->alter('sites_group_overrides_non_overideable_entities', $apply_to_non_overideable_entities, $entity);
    if ($apply_to_non_overideable_entities) {
      return TRUE;
    }

    $relationship = $this->sitesGroupOverridesService->getRelationship($this->getEntity());
    if (!$relationship instanceof GroupRelationshipInterface) {
      return FALSE;
    }
    if (empty($this->sitesGroupOverridesService->getSynchronizableFields($relationship, $this->getEntity()))) {
      return FALSE;
    }

    return TRUE;
  }

  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state, ...$args) {
    $form = parent::buildForm($form, $form_state, ...$args);
    $form['#process'][] = [$this, 'entityFormSharedElements'];

    return $form;
  }

  /**
   * Process callback: determines which elements get clue in the form.
   *
   * @see \Drupal\content_translation\ContentTranslationHandler::entityFormAlter()
   */
  public function entityFormSharedElements($element, FormStateInterface $form_state, $form) {
    static $ignored_types;

    // @todo Find a more reliable way to determine if a form element concerns a
    //   multilingual value.
    if (!isset($ignored_types)) {
      $ignored_types = array_flip(['actions', 'value', 'hidden', 'vertical_tabs', 'token', 'details', 'link']);
    }

    /** @var \Drupal\Core\Entity\ContentEntityForm $form_object */
    $form_object = $form_state->getFormObject();
    /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
    $entity = $form_object->getEntity();
    $hide_untranslatable_fields = $entity->isDefaultTranslationAffectedOnly() && !$entity->isDefaultTranslation();
    $translation_form = $form_state->get(['content_translation', 'translation_form']);
    $display_warning = FALSE;

    // We use field definitions to identify untranslatable field widgets to be
    // hidden. Fields that are not involved in translation changes checks should
    // not be affected by this logic (the "revision_log" field, for instance).
    // $field_definitions = array_diff_key($entity->getFieldDefinitions(), array_flip($this->getFieldsToSkipFromTranslationChangesCheck($entity)));
    $fields = $field_definitions = [];
    if ($relationship = $this->sitesGroupOverridesService->getRelationship($this->getEntity())) {
      $field_definitions = $relationship->getFieldDefinitions();
      $fields = $this->sitesGroupOverridesService->getSynchronizableFields($relationship, $this->getEntity());
      // Reload relationship to have empty values for non-overriden fields.
      /** @var \Drupal\group\Entity\GroupRelationshipInterface $relationship */
      $relationship = $this->entityTypeManager->getStorage('group_relationship')->loadUnchanged($relationship->id());
      try {
        $relationship = $relationship->getTranslation($this->getEntity()->language()->getId());
      }
      catch (\InvalidArgumentException $e) {
        return $element;
      }

    }

    foreach (Element::children($element) as $key) {
      $is_overridden = $is_overridable = FALSE;
      if (!isset($element[$key]['#type'])) {
        $this->entityFormSharedElements($element[$key], $form_state, $form);
      }
      else {
        // Ignore non-widget form elements.
        if (isset($ignored_types[$element[$key]['#type']])) {
          continue;
        }
        if (!empty($element[$key]['widget'][0]['#supports_sites'])) {
          continue;
        }
        // Elements are considered to be non multilingual by default.
        // if (array_key_exists($key, $fields)) {
        //   $this->addOverrideStatus($key, $element[$key]);
        // }
        // If we are displaying a multilingual entity form we need to provide
        // translatability clues, otherwise the non-multilingual form elements
        // should be hidden.
        if (!$translation_form) {
          if (array_key_exists($key, $fields)) {
            $is_overridable = TRUE;
            if ($relationship instanceof GroupRelationshipInterface) {
              $target_field_name = $this->sitesGroupOverridesService->getTargetFieldName($relationship, $key);
              $is_overridden = $relationship->hasField($target_field_name) && !$relationship->get($target_field_name)->isEmpty();
            }
          }
          $this->addOverrideAbilityClue($element[$key], $is_overridable, $is_overridden, $key, $key);
          // Hide widgets for untranslatable fields.
          if ($hide_untranslatable_fields && isset($field_definitions[$key])) {
            $element[$key]['#access'] = FALSE;
            $display_warning = TRUE;
          }
        }
        else {
          $element[$key]['#access'] = FALSE;
        }
      }
    }

    if ($display_warning && !$form_state->isSubmitted() && !$form_state->isRebuilding()) {
      $url = $entity->getUntranslated()->toUrl('edit-form')->toString();
      $this->messenger()->addWarning($this->t('Fields that apply to all languages are hidden to avoid conflicting changes. <a href=":url">Edit them on the original language form</a>.', [':url' => $url]));
    }

    return $element;
  }

  /**
   * Adds a clue about the form element override-ability.
   *
   * If the given element does not have a #title attribute, the function is
   * recursively applied to child elements.
   *
   * @param array $element
   *   A form element array.
   * @param bool $is_overridable
   *   TRUE, if the field is overideable.
   * @param bool $is_overridden
   *   TRUE if the field is overridden.
   * @param mixed $key
   *   The key of the element.
   * @param mixed $field_name
   *   The field name.
   *
   * @see \Drupal\content_translation\ContentTranslationHandler::addTranslatabilityClue
   */
  protected function addOverrideAbilityClue(array &$element, bool $is_overridable, bool $is_overridden, $key, $field_name) {
    static $suffix, $fapi_title_elements;
    $apply = FALSE;

    // Elements which can have a #title attribute according to FAPI Reference.
    $text = $this->t('all sites');
    if ($is_overridable) {
      $text = $this->t('Original value');
      if ($is_overridden) {
        $text = $this->t('Overridden');
      }
    }
    $suffix = ' <span class="override-entity-all-sites sites-group-overrides-clue--' . $field_name . '">(' . $text . ')</span>';
    if (!isset($fapi_title_elements)) {
      $fapi_title_elements = array_flip([
        'checkbox',
        'checkboxes',
        'date',
        'details',
        'fieldset',
        'file',
        'item',
        'password',
        'password_confirm',
        'radio',
        'radios',
        'select',
        'text_format',
        'textarea',
        'textfield',
        'weight',
      ]);
    }

    // If ($is_overridable) {
    // Use an after build to set classes - some elemets e.g. textareas are not here yet.
    // @todo avoid using ->get()
    // $main_property = $this->getEntity()->get($field_name)->getFieldDefinition()->getFieldStorageDefinition()->getMainPropertyName();
    // $element['#main_property'] = $main_property;
    // $element['#key'] = $key;
    // $element['#field_name'] = $field_name;
    // $element['#after_build'][] = [static::class, 'afterBuild'];
    // }
    // Update #title attribute for all elements that are allowed to have a
    // #title attribute according to the Form API Reference. The reason for this
    // check is because some elements have a #title attribute even though it is
    // not rendered; for instance, field containers.
    if (isset($element['#type']) && isset($fapi_title_elements[$element['#type']]) && isset($element['#title'])) {
      $apply = TRUE;
    }
    elseif (isset($element['#title']) && !empty($element["#cardinality_multiple"])) {
      $apply = TRUE;
    }
    // If the current element does not have a (valid) title, try child elements.
    elseif ($children = Element::children($element)) {
      foreach ($children as $delta) {
        $this->addOverrideAbilityClue($element[$delta], $is_overridable, $is_overridden, $delta, $field_name);
      }
    }
    // If there are no children, fall back to the current #title attribute if it
    // exists.
    elseif (isset($element['#title'])) {
      $apply = TRUE;
    }

    if ($apply) {
      // Using ['#markup' => ...] ends in a fatal some times -> use Markup::create.
      $element['#title'] = Markup::create($element['#title'] . $suffix);
      if (!$is_overridable) {
        $config = $this->configFactory->get('sites_group_overrides.settings');
        if ($config->get('disable_source_field_on_override')) {
          $element['#disabled'] = TRUE;
        }
        if ($config->get('hide_source_field_on_override')) {
          $element['#access'] = FALSE;
        }
      }
    }
  }

  /**
   * Adds the drupal settings the JS.
   *
   * @param string $field_name
   *   The field name to add the settings for.
   * @param array $element
   *   The element of the field.
   */
  protected function addOverrideStatus(string $field_name, array &$element) {
    // @todo Avoid using ->get(..)
    $original_entity = $this->entityTypeManager->getStorage($this->getEntity()->getEntityTypeId())->loadUnchanged($this->getEntity()->id());
    $unsupported = ['metatag', 'entity_reference_revisions', 'link'];
    if (in_array($original_entity->get($field_name)->getFieldDefinition()->getType(), $unsupported)) {
      return;
    }
    $main_property = $original_entity->get($field_name)->getFieldDefinition()->getFieldStorageDefinition()->getMainPropertyName();
    $element['#attached']['drupalSettings']['sitesGroupOverrides'][$field_name] = [
      'original_value' => $original_entity->get($field_name)->{$main_property} ?? '',
      'label_selector' => '.sites-group-overrides-clue--' . $field_name,
      'value_selector' => '.sites-group-overrides-value--' . $field_name,
      'class' => 'sites-group-overrides-clue--' . $field_name,
    ];
    $element['#attached']['library'][] = 'sites_group_overrides/overide_status';
  }

  /**
   * Add classes to find label and value for all overideable fields.
   */
  public static function afterBuild(array $element, FormStateInterface $form_state, $main_property = NULL, $key = NULL, $field_name = NULL) {
    if (is_null($key)) {
      $key = $element['#key'];
    }
    if (is_null($main_property)) {
      $main_property = $element['#main_property'];
    }
    if (is_null($field_name)) {
      $field_name = $element['#field_name'];
    }

    if ($key == $main_property) {
      $element['#attributes']['class'][] = 'sites-group-overrides-value--' . $field_name;
    }
    elseif ($children = Element::children($element)) {
      foreach ($children as $delta) {
        $element[$delta] = self::afterBuild($element[$delta], $form_state, $main_property, $delta, $field_name);
      }
    }

    return $element;
  }

}

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

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