delivery-8.x-1.x-dev/src/Form/DeliveryItemResolveForm.php

src/Form/DeliveryItemResolveForm.php
<?php

namespace Drupal\delivery\Form;

use Drupal\Component\Utility\NestedArray;
use Drupal\conflict\ConflictResolver\ConflictResolverManagerInterface;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\Entity\EntityFormDisplay;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\Core\Entity\EntityRepositoryInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\ConfirmFormBase;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\TypedData\TranslatableInterface;
use Drupal\delivery\DeliveryInterface;
use Drupal\delivery\DeliveryService;
use Drupal\delivery\Entity\Delivery;
use Drupal\delivery\Entity\DeliveryItem;
use Drupal\workspaces\WorkspaceManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\ParameterBag;

/**
 * Class DeliveryItemResolveForm
 *
 * @package Drupal\delivery\Form
 */
class DeliveryItemResolveForm extends FormBase {

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

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

  /**
   * @var \Drupal\delivery\DeliveryService
   */
  protected $deliveryService;

  /**
   * @var \Drupal\Core\Messenger\MessengerInterface
   *  The messenger service.
   */
  protected $messenger;

  /**
   * @var \Drupal\Core\Render\Renderer
   */
  protected $renderer;

  /**
   * @var \Drupal\conflict\ConflictResolver\ConflictResolverManagerInterface
   */
  protected $conflictResolverManager;

  /**
   * @var \Drupal\workspaces\WorkspaceManagerInterface
   */
  protected $workspaceManager;

  /**
   * @var \Drupal\delivery\Entity\DeliveryItem
   */
  protected $deliveryItem;

  /**
   * @var \Drupal\Core\Entity\ContentEntityInterface
   */
  protected $sourceEntity;

  /**
   * @var \Drupal\Core\Entity\ContentEntityInterface
   */
  protected $targetEntity;

  /**
   * @var \Drupal\Core\Entity\ContentEntityInterface
   */
  protected $parentEntity;

  /**
   * @var \Drupal\Core\Entity\ContentEntityInterface
   */
  protected $resultEntity;

  /**
   * @var \Drupal\Core\Language\LanguageManagerInterface
   */
  protected $languageManager;

  /**
   * DeliveryPushConfirmFom constructor.
   *
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   * @param \Drupal\Core\Messenger\MessengerInterface $messenger
   * @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository
   * @param \Drupal\delivery\DeliveryService $deliveryService
   * @param \Drupal\Core\Render\RendererInterface $renderer
   * @param \Drupal\conflict\ConflictResolver\ConflictResolverManagerInterface $conflictResolverManager
   * @param \Drupal\workspaces\WorkspaceManagerInterface $workspaceManager
   * @param \Drupal\Core\Language\LanguageManagerInterface $languageManager
   */
  public function __construct(
    EntityTypeManagerInterface $entity_type_manager,
    MessengerInterface $messenger,
    EntityRepositoryInterface $entity_repository,
    DeliveryService $deliveryService,
    RendererInterface $renderer,
    ConflictResolverManagerInterface $conflictResolverManager,
    WorkspaceManagerInterface $workspaceManager,
    LanguageManagerInterface $languageManager
  ) {
    $this->deliveryService = $deliveryService;
    $this->entityTypeManager = $entity_type_manager;
    $this->messenger = $messenger;
    $this->entityRepository = $entity_repository;
    $this->renderer = $renderer;
    $this->conflictResolverManager = $conflictResolverManager;
    $this->workspaceManager = $workspaceManager;
    $this->languageManager = $languageManager;
  }

  public function access(AccountInterface $account, DeliveryItem $delivery_item) {
    if (isset($delivery_item->resolution->value)) {
      return AccessResult::forbidden();
    }
    // TODO: Restrict access to conflict resolution based on target permnissions.
    return AccessResult::allowed();
//    $this->sourceEntity = $this->entityTypeManager
//      ->getStorage($delivery_item->getTargetType())
//      ->loadRevision($delivery_item->getSourceRevision());
//    return $this->sourceEntity->access('edit', $account, TRUE);
  }

  public function title(DeliveryItem $delivery_item) {
    /** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */
    $storage = $this->entityTypeManager->getStorage($delivery_item->getTargetType());
    /** @var \Drupal\Core\Entity\ContentEntityInterface $this->sourceEntity */
    $sourceEntity = $storage->loadRevision($delivery_item->getSourceRevision());
    return $this->t('Resolve conflict. <a href="@href" target="_blank">@label</a>', [
      '@href' => $sourceEntity->toUrl()->toString(),
      '@label' => $sourceEntity->label(),
    ]);
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('entity_type.manager'),
      $container->get('messenger'),
      $container->get('entity.repository'),
      $container->get('delivery.service'),
      $container->get('renderer'),
      $container->get('conflict_resolver.manager'),
      $container->get('workspaces.manager'),
      $container->get('language_manager')
    );
  }

  /**
   * {@inheritdoc}
   */
  public function getFormId() {
    return 'delivery_push_changes';
  }

  protected function getTranslation(TranslatableInterface $entity, $langcode) {
    return $entity->hasTranslation($langcode)
      ? $entity->getTranslation($langcode)
      : $entity->addTranslation($langcode);
  }

  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state, DeliveryItem $delivery_item = NULL) {
    $form['#attached']['library'][] = 'delivery/conflict-resolution';
    $this->deliveryItem = $delivery_item;
    /** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */
    $storage = $this->entityTypeManager->getStorage($delivery_item->getTargetType());
    /** @var \Drupal\Core\Entity\ContentEntityInterface $this->sourceEntity */
    $this->sourceEntity = $storage->loadRevision($delivery_item->getSourceRevision());
    $this->sourceEntity = $this->sourceEntity;
    /** @var \Drupal\Core\Entity\ContentEntityInterface $this->targetEntity */
    $this->targetEntity = $storage->loadRevision($this->deliveryService->getActiveRevision($delivery_item));

    $this->resultEntity = $storage->createRevision($this->sourceEntity);

    $entityType = $this->entityTypeManager->getDefinition($this->deliveryItem->getTargetType());
    $revisionParentField = $entityType->getRevisionMetadataKey('revision_parent');
    $revisionMergeParentField = $entityType->getRevisionMetadataKey('revision_merge_parent');
    $revisionField = $entityType->getKey('revision');

    $this->resultEntity->{$revisionMergeParentField}->target_revision_id = $this->deliveryItem->getSourceRevision();
    $this->resultEntity->{$revisionParentField}->target_revision_id = $this->targetEntity->{$revisionField}->value;
    $this->resultEntity->workspace = $this->deliveryItem->getTargetWorkspace();

    /** @var \Drupal\revision_tree\EntityRevisionTreeHandlerInterface $revisionTreeHandler */
    $revisionTreeHandler = $this->entityTypeManager->getHandler($this->sourceEntity->getEntityTypeId(), 'revision_tree');
    $parentEntityRevision = $revisionTreeHandler->getLowestCommonAncestorId($this->sourceEntity->getRevisionId(), $this->targetEntity->getRevisionId(), $delivery_item->getTargetId());

    /** @var \Drupal\Core\Entity\ContentEntityInterface $parentEntity */
    $parentEntity = $storage->loadRevision($parentEntityRevision);
    $this->parentEntity = $parentEntity;

    $targetWorkspace = $this->entityTypeManager->getStorage('workspace')
      ->load($delivery_item->getTargetWorkspace());
    $sourceWorkspace = $this->entityTypeManager->getStorage('workspace')
      ->load($delivery_item->getSourceWorkspace());

    $targetPrimaryLanguage = (!empty($targetWorkspace->primary_language) && $targetWorkspace->primary_language->value) ?: $this->languageManager->getDefaultLanguage()->getId();
    $targetLanguages = [$targetPrimaryLanguage];
    if (!empty($targetWorkspace->secondary_languages)) {
      foreach ($targetWorkspace->secondary_languages as $secondaryLanguage) {
        $targetLanguages[] = $secondaryLanguage->value;
      }
    }

    $viewDisplay = EntityViewDisplay::collectRenderDisplay($this->sourceEntity, 'merge');
    $formDisplay = EntityFormDisplay::collectRenderDisplay($this->sourceEntity, 'merge');

    $form['languages'] = array(
      '#type' => 'vertical_tabs',
    );

    $hadConflicts = FALSE;

    if ($this->sourceEntity->isTranslatable()) {
      foreach ($this->sourceEntity->getTranslationLanguages() as $language) {
        $languageId = $language->getId();

        $sourceTranslation = $this->getTranslation($this->sourceEntity, $languageId);
        $resultTranslation = $this->getTranslation($this->resultEntity, $languageId);
        $parentTranslation = $this->getTranslation($this->parentEntity, $languageId);
        $targetTranslation = $this->getTranslation($this->targetEntity, $languageId);

        $context = new ParameterBag();
        $context->set('supported_languages', $targetLanguages);

        $conflicts = $this->conflictResolverManager->resolveConflicts(
          $targetTranslation,
          $sourceTranslation,
          $parentTranslation,
          $resultTranslation,
          $context
        );

        $hadConflicts = $hadConflicts || count($conflicts) > 0;

        // If the current language is not the target workspace primary language,
        // ignore all conflicts in non-translatable fields.
        if ($languageId !== $targetPrimaryLanguage) {
          foreach (array_keys($conflicts) as $prop) {
            if(!$sourceTranslation->get($prop)->getFieldDefinition()->isTranslatable()) {
              unset($conflicts[$prop]);
            }
          }
        }

        if ($conflicts) {
          $sourceBuild = $viewDisplay->build($sourceTranslation);
          $targetBuild = $viewDisplay->build($targetTranslation);
          $customForm = [];
          $formDisplay->buildForm($resultTranslation, $customForm, $form_state);

          $form[$languageId] = [
            '#type' => 'details',
            '#title' => $language->getName(),
            '#group' => 'languages',
          ];

          foreach (array_keys($conflicts) as $property) {
            if (!($viewDisplay->getComponent($property) || $formDisplay->getComponent($property))) {
              continue;
            }

            $form[$languageId][$property] = [
              '#type' => 'details',
              '#attributes' => [
                'class' => ['delivery-merge-property'],
              ],
              '#open' => TRUE,
              '#title' => $this->sourceEntity->get($property)->getFieldDefinition()->getLabel(),
              'selection' => [
                '#prefix' => '<div class="delivery-merge-options">',
                '#suffix' => '</div>',
                '#required' => TRUE,
                '#parents' => ['languages', $languageId, $property],
                '#options' => [],
                '#default_value' => '__source__',
              ],
              'preview' => [],
            ];

            if ($viewDisplay->getComponent($property)) {
              $form[$languageId][$property]['selection']['#type'] = 'radios';
              $form[$languageId][$property]['selection']['#options'] = [
                '__source__' => $sourceWorkspace->label(),
                '__target__' => $targetWorkspace->label(),
              ];
              $form[$languageId][$property]['preview'] = [
                'source' => [
                  '#prefix' => '<div class="delivery-merge-source">',
                  '#suffix' => '</div>',
                  'build' => $sourceBuild[$property]
                ],
                'target' => [
                  '#prefix' => '<div class="delivery-merge-target">',
                  '#suffix' => '</div>',
                  'build' => $targetBuild[$property]
                ],
              ];
              if ($formDisplay->getComponent($property)) {
                $formElement = $customForm[$property];
                $formElement['widget']['#parents'] = ['custom', $languageId, $property];
                $form[$languageId][$property]['selection']['#options']['__custom__'] = $this->t('Custom');
                $form[$languageId][$property]['preview']['custom'] = [
                  '#prefix' => '<div class="delivery-merge-custom">',
                  '#suffix' => '</div>',
                  'build' => $formElement,
                ];
              }
            }
            else if ($formDisplay->getComponent($property)) {
              $formElement = $customForm[$property];
              $formElement['widget']['#parents'] = ['custom', $languageId, $property];
              // Add a language attribute to each editor widget.
              foreach ($formElement['widget'] as $key => $widget) {
                if (!is_numeric($key)) {
                  continue;
                }
                if (empty($formElement['widget'][$key]['html']['#attributes'])) {
                  $formElement['widget'][$key]['html']['#attributes'] = [];
                }
                $formElement['widget'][$key]['html']['#attributes']['data-lang'] = $languageId;
              }
              $form[$languageId][$property]['selection']['#type'] = 'value';
              $form[$languageId][$property]['selection']['#value'] = '__custom__';
              $form[$languageId][$property]['preview'] = [
                'custom' => [
                  '#prefix' => '<div class="delivery-merge-custom">',
                  '#suffix' => '</div>',
                  'build' => $formElement,
                ]
              ];
            }
          }
        }
        else {
          // TODO: Display preview of the automatic merge.
        }
      }
    }

    $args = [
      ':source' => $sourceWorkspace->label(),
      ':target' => $targetWorkspace->label(),
      ':title' => $parentEntity->label(),
    ];

    $buttons = [
      DeliveryItem::STATUS_NEW => $this->t('Add to :target', $args),
      DeliveryItem::STATUS_MODIFIED_BY_SOURCE => $this->t('Apply changes to :target', $args),
      DeliveryItem::STATUS_CONFLICT => $this->t('Conflict'),
      DeliveryItem::STATUS_CONFLICT_AUTO => $this->t('Apply changes to :target', $args),
      DeliveryItem::STATUS_DELETED => $this->t('Mark as resolved'),
      DeliveryItem::STATUS_DELETED_BY_SOURCE => $this->t('Delete from :target', $args),
      DeliveryItem::STATUS_RESTORED_BY_SOURCE => $this->t('Restore to :target', $args),
    ];

    $messages = [
      DeliveryItem::STATUS_NEW => $this->t(':source added ":title". Also add it to :target?', $args),
      DeliveryItem::STATUS_MODIFIED_BY_SOURCE => $this->t('":title" was modified in :source. Apply changes to :target?', $args),
      DeliveryItem::STATUS_CONFLICT_AUTO => $this->t('The conflict in ":title" could be resolved automatically. Apply changes to :target?', $args),
      DeliveryItem::STATUS_DELETED => $this->t('":title" has been deleted from both :source and :target. Mark this as resolved?', $args),
      DeliveryItem::STATUS_DELETED_BY_SOURCE => $this->t('":title" was deleted from :source. Also delete it from :target?', $args),
      DeliveryItem::STATUS_RESTORED_BY_SOURCE => $this->t(':source has restored ":title". Also restore it to :target?', $args),
    ];

    if (!$hadConflicts) {
      $form['message'] = [
        '#markup' => '<p><em>' . $messages[$delivery_item->getStatus()['status']] . "</em></p>",
      ];
    }

    $form['submit'] = [
      '#type' => 'submit',
      '#value' => $hadConflicts ? $this->t('Resolve conflicts') : $buttons[$delivery_item->getStatus()['status']],
      '#button_type' => 'primary',
    ];

    $form_state->set('workspace_safe', TRUE);
    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function validateForm(array &$form, FormStateInterface $form_state) {
    $formDisplay = EntityFormDisplay::collectRenderDisplay($this->resultEntity, 'merge');
    /** @var \Drupal\Core\Field\WidgetPluginManager $widgetPluginManager */
    $widgetPluginManager = \Drupal::service('plugin.manager.field.widget');
    /** @var \Drupal\Core\Entity\EntityFieldManagerInterface $entityFieldManager */
    $entityFieldManager = \Drupal::service('entity_field.manager');

    $targetWorkspace = $this->entityTypeManager->getStorage('workspace')
      ->load($this->deliveryItem->getTargetWorkspace());

    $targetLanguages = [];
    if (!empty($targetWorkspace->primary_language)) {
      $targetPrimaryLanguage = $targetWorkspace->primary_language->value;
      $targetLanguages = [$targetPrimaryLanguage];
    }
    if (!empty($targetWorkspace->secondary_languages)) {
      foreach ($targetWorkspace->secondary_languages as $secondaryLanguage) {
        $targetLanguages[] = $secondaryLanguage->value;
      }
    }

    // Copy resolutions of non-translatable fields from primary language to
    // other languages.
    $allResolutions = $form_state->getValue('languages');
    foreach ($allResolutions[$targetPrimaryLanguage] as $key => $value) {
      if ($this->resultEntity->get($key)->getFieldDefinition()->isTranslatable()) {
        continue;
      }
      foreach (array_keys($allResolutions) as $lang) {
        if ($lang !== $targetPrimaryLanguage) {
          $allResolutions[$lang][$key] = $value;
        }
      }
    }

    // Copy manual merges of non-translatable fields from primary language to
    // other languages.
    $allCustomValues = $form_state->getValue('custom');
    foreach ($allCustomValues[$targetPrimaryLanguage] as $key => $value) {
      if ($this->resultEntity->get($key)->getFieldDefinition()->isTranslatable()) {
        continue;
      }
      foreach (array_keys($allCustomValues) as $lang) {
        if ($lang !== $targetPrimaryLanguage) {
          $allCustomValues[$lang][$key] = $value;
        }
      }
    }

    foreach ($this->resultEntity->getTranslationLanguages() as $language) {
      $context = new ParameterBag();
      $context->set('supported_languages', $targetLanguages);
      $context->set('resolution_form_result', $allResolutions[$language->getId()]);
      $customValues = $allCustomValues[$language->getId()];

      foreach ($customValues as $field => $input) {
        $component = $formDisplay->getComponent($field);
        $entityType = $this->sourceEntity->getEntityType();
        $bundle = $this->sourceEntity->bundle();
        $definitions = $entityFieldManager->getFieldDefinitions($entityType->id(), $bundle);
        /** @var \Drupal\Core\Field\WidgetInterface $widget */
        $widget = $widgetPluginManager->getInstance([
          'field_definition' => $definitions[$field],
          'form_mode' => 'merge',
          // No need to prepare, defaults have been merged in setComponent().
          'prepare' => FALSE,
          'configuration' => $component,
        ]);
        $customValues[$field] = $widget->massageFormValues($input, $form, $form_state);
      }
      $context->set('resolution_custom_values', $customValues);
      /** @var \Drupal\Core\Entity\ContentEntityInterface $resultTranslation */
      $resultTranslation = $this->getTranslation($this->resultEntity, $language->getId());
      $this->conflictResolverManager->resolveConflicts(
        $this->getTranslation($this->targetEntity, $language->getId()),
        $this->getTranslation($this->sourceEntity, $language->getId()),
        $this->getTranslation($this->parentEntity, $language->getId()),
        $resultTranslation,
        $context
      );
      if (in_array($language->getId(), $targetLanguages)) {
        $violations = $resultTranslation->validate();
        foreach ($violations as $violation) {
          // Ignore all violations that can not be resolved in this form.
          if (isset($form[$language->getId()][$violation->getPropertyPath()])) {
            $form_state->setError($form, $violation->getMessage());
          }
        }
      }
    }
  }

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state) {
    $this->workspaceManager->executeInWorkspace($this->deliveryItem->getTargetWorkspace(), function () use ($form, $form_state) {
      $this->resultEntity->setSyncing(TRUE);
      $this->resultEntity->save();
      $this->deliveryItem->result_revision = $this->resultEntity->getRevisionId();
    });
    // TODO: actually compare entities.
    $this->deliveryItem->resolution = DeliveryItem::RESOLUTION_MERGE;
    $this->deliveryItem->save();

    $this->messenger->addStatus($this->t('The changes have been imported.'));
  }
}

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

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