You are here

ReplicationForm.php in Deploy - Content Staging 8

File

src/Entity/Form/ReplicationForm.php
View source
<?php

namespace Drupal\deploy\Entity\Form;

use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\CloseModalDialogCommand;
use Drupal\Core\Ajax\RedirectCommand;
use Drupal\Core\Entity\ContentEntityForm;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Markup;
use Drupal\Core\Url;
use Drupal\replication\Entity\ReplicationLogInterface;
use Drupal\workspace\Entity\Replication;
use Drupal\workspace\WorkspacePointerInterface;

/**
 * Form controller for Replication edit forms.
 *
 * @ingroup deploy
 */
class ReplicationForm extends ContentEntityForm {

  /**
   * A source object.
   *
   * @var \Drupal\workspace\WorkspacePointerInterface
   */
  protected $source = NULL;

  /**
   * A target object.
   *
   * @var \Drupal\workspace\WorkspacePointerInterface
   */
  protected $target = NULL;

  /**
   * Format the title of the Replication process.
   *
   * @return string
   *   The title formatted with source and target labels.
   */
  public function addTitle() {
    $this
      ->setEntity(Replication::create());
    if (!$this
      ->getDefaultSource() || !$this
      ->getDefaultTarget()) {
      return $this
        ->t('Error');
    }
    $target = $this
      ->getDefaultTarget();
    $remote_name = '';
    if (!empty($target->remote_database->value) && !empty($target->remote_pointer->entity)) {
      $target_workspace_name = $target->remote_database->value;
      $remote_name = $target->remote_pointer->entity
        ->label();
      $target_workspace_name = ucfirst($target_workspace_name);
      $message = 'Deploy changes from local @source workspace to @target workspace on @remote';
    }
    else {
      $target_workspace_name = $this
        ->getDefaultTarget()
        ->label();
      $message = 'Deploy changes from local @source workspace to @target workspace';
    }
    return $this
      ->t($message, [
      '@source' => $this
        ->getDefaultSource()
        ->label(),
      '@target' => $target_workspace_name,
      '@remote' => $remote_name,
    ]);
  }

  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state) {
    $input = $form_state
      ->getUserInput();
    $js = isset($input['_drupal_ajax']) ? TRUE : FALSE;
    $form = parent::buildForm($form, $form_state);
    $default_source = $this
      ->getDefaultSource();
    $default_target = $this
      ->getDefaultTarget();

    // For remote targets the remote workspace can't be loaded from workspace
    // pointer.
    $published = NULL;

    /** @var \Drupal\multiversion\Entity\WorkspaceInterface|NULL $target_workspace */
    if ($target_workspace = $default_target
      ->get('workspace_pointer')->entity) {
      $published = $target_workspace
        ->isPublished();
    }
    if (!$default_source || !$default_target || $published === FALSE) {
      $message = $this
        ->t('Source and target must be set, make sure your current workspace has an upstream. Go to <a href=":path">this page</a> to edit your workspaces.', [
        ':path' => Url::fromRoute('entity.workspace.collection')
          ->toString(),
      ]);
      if ($js) {
        return [
          '#markup' => $message,
        ];
      }
      $this
        ->messenger()
        ->addError($message);
      return [];
    }

    // @todo Move this to be injected.
    $this->conflictTracker = \Drupal::service('workspace.conflict_tracker');

    // Allow the user to not abort on conflicts.
    $source_workspace = $default_source
      ->getWorkspace();
    $conflicts = $this->conflictTracker
      ->useWorkspace($source_workspace)
      ->getAll();
    if ($conflicts) {
      $form['message'] = $this
        ->generateMessageRenderArray('error', $this
        ->t('There are <a href=":link">@count conflict(s) with the :target workspace</a>. Pushing changes to :target may result in unexpected behavior or data loss, and cannot be undone. Please proceed with caution.', [
        '@count' => count($conflicts),
        ':link' => Url::fromRoute('entity.workspace.conflicts', [
          'workspace' => $source_workspace
            ->id(),
        ])
          ->toString(),
        ':target' => $default_target
          ->label(),
      ]));
      $form['is_aborted_on_conflict'] = [
        '#type' => 'radios',
        '#title' => $this
          ->t('Abort if there are conflicts?'),
        '#default_value' => 'true',
        '#options' => [
          'true' => $this
            ->t('Yes, if conflicts are found do not replicate to upstream.'),
          'false' => $this
            ->t('No, go ahead and push any conflicts to the upstream.'),
        ],
        '#weight' => 0,
      ];
    }
    else {
      $form['message'] = $this
        ->generateMessageRenderArray('status', 'There are no conflicts.');
    }
    if (!$source_workspace
      ->isDefaultWorkspace()) {
      $form['archive'] = [
        '#type' => 'checkbox',
        '#title' => $this
          ->t('Archive workspace after deployment'),
        '#description' => $this
          ->t('The workspace will be archived only if the deployment finishes with success.'),
        '#default_value' => FALSE,
      ];
    }
    $form['source']['widget']['#default_value'] = [
      $default_source
        ->id(),
    ];
    if (empty($this->entity
      ->get('target')->target_id) && $default_target) {
      $form['target']['widget']['#default_value'] = [
        $default_target
          ->id(),
      ];
    }
    if (!$form['source']['#access'] && !$form['target']['#access']) {
      $form['actions']['submit']['#value'] = $this
        ->t('Deploy to @target', [
        '@target' => $default_target
          ->label(),
      ]);
    }
    else {
      $form['actions']['submit']['#value'] = $this
        ->t('Deploy');
    }
    $form['actions']['submit']['#ajax'] = [
      'callback' => [
        $this,
        'deploy',
      ],
      'event' => 'mousedown',
      'prevent' => 'click',
      'progress' => [
        'type' => 'throbber',
        'message' => 'Deploying',
      ],
    ];
    return $form;
  }

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

    // Pass the abort flag to the ReplicationManager using runtime-only state,
    // i.e. a static.
    // @see \Drupal\workspace\ReplicatorManager
    // @see \Drupal\workspace\Entity\Form\WorkspaceForm
    $is_aborted_on_conflict = !$form_state
      ->hasValue('is_aborted_on_conflict') || $form_state
      ->getValue('is_aborted_on_conflict') === 'true';
    drupal_static('workspace_is_aborted_on_conflict', $is_aborted_on_conflict);
    parent::save($form, $form_state);
    $input = $form_state
      ->getUserInput();
    $js = isset($input['_drupal_ajax']) ? TRUE : FALSE;
    try {
      $response = \Drupal::service('workspace.replicator_manager')
        ->replicate($this->entity
        ->get('source')->entity, $this->entity
        ->get('target')->entity, null, $this->entity);
      if ($response instanceof ReplicationLogInterface && $response
        ->get('ok')->value == TRUE) {
        $this->entity
          ->set('replicated', REQUEST_TIME)
          ->save();
        $this
          ->messenger()
          ->addMessage($this
          ->t('Deployment queued, refresh this page and check the status below. It might take a few minutes to complete.'));
        if ($form_state
          ->hasValue('archive') && $form_state
          ->getValue('archive') == TRUE) {
          $this->entity
            ->setArchiveSource()
            ->save();
        }
        if (!$js) {
          $form_state
            ->setRedirect('entity.replication.collection');
        }
      }
      else {
        $this
          ->messenger()
          ->addError('Deployment error. Check recent log messages for more details.', 'error');
      }
    } catch (\Exception $e) {
      watchdog_exception('Deploy', $e);
      $this
        ->messenger()
        ->addError($e
        ->getMessage(), 'error');
    }
    if (!$js) {
      $form_state
        ->setRedirect('entity.replication.collection');
    }
  }

  /**
   * Main method to return an Ajax Response.
   *
   * @return \Drupal\Core\Ajax\AjaxResponse
   *   AjaxResponse object.
   */
  public function deploy() {
    $response = new AjaxResponse();
    $response
      ->addCommand(new CloseModalDialogCommand());
    $response
      ->addCommand(new RedirectCommand(Url::fromRoute('entity.replication.collection')
      ->setAbsolute()
      ->toString()));
    return $response;
  }

  /**
   * Returns a source object.
   */
  protected function getDefaultSource() {
    if (!empty($this->source)) {
      return $this->source;
    }
    if (!empty($this->entity
      ->get('source')) && $this->entity
      ->get('source')->entity instanceof WorkspacePointerInterface) {
      return $this->source = $this->entity
        ->get('source')->entity;
    }

    /* @var \Drupal\multiversion\Entity\Workspace $workspace */
    $workspace = \Drupal::service('workspace.manager')
      ->getActiveWorkspace();
    $workspace_pointers = \Drupal::service('entity_type.manager')
      ->getStorage('workspace_pointer')
      ->loadByProperties([
      'workspace_pointer' => $workspace
        ->id(),
    ]);
    return $this->source = reset($workspace_pointers);
  }

  /**
   * Returns a target object.
   */
  protected function getDefaultTarget() {
    if (!empty($this->target)) {
      return $this->target;
    }
    if (!empty($this->entity
      ->get('target')) && $this->entity
      ->get('target')->entity instanceof WorkspacePointerInterface) {
      return $this->target = $this->entity
        ->get('target')->entity;
    }

    /* @var \Drupal\multiversion\Entity\Workspace $workspace */
    $workspace = \Drupal::service('workspace.manager')
      ->getActiveWorkspace();
    return $this->target = $workspace
      ->get('upstream')->entity;
  }

  /**
   * Generate a message render array with the given text.
   *
   * @param string $type
   *   The type of message: status, warning, or error.
   * @param string $message
   *   The message to create with.
   *
   * @return array
   *   The render array for a status message.
   *
   * @see \Drupal\Core\Render\Element\StatusMessages
   */
  protected function generateMessageRenderArray($type, $message) {
    return [
      '#theme' => 'status_messages',
      '#message_list' => [
        $type => [
          Markup::create($message),
        ],
      ],
    ];
  }

}

Classes

Namesort descending Description
ReplicationForm Form controller for Replication edit forms.