deploy_individual-8.x-1.x-dev/src/Form/IndividualPushConfirm.php
src/Form/IndividualPushConfirm.php
<?php
namespace Drupal\deploy_individual\Form;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\ConfirmFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
use Drupal\multiversion\MultiversionManagerInterface;
use Drupal\multiversion\Workspace\WorkspaceManagerInterface;
use Drupal\replication\Entity\ReplicationLogInterface;
use Drupal\replication\ReplicationTask\ReplicationTask;
use Drupal\user\PrivateTempStoreFactory;
use Drupal\user\UserStorageInterface;
use Drupal\workspace\Entity\Replication;
use Drupal\workspace\ReplicatorInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a confirmation form for deploying content.
*/
class IndividualPushConfirm extends ConfirmFormBase {
/**
* The depth to search for referenced entities.
*/
const MAX_REFERENCED_ENTITIES_DEPTH = 5;
/**
* The separator to create option values.
*/
const VALUE_SEPARATOR = '___';
/**
* The temp store factory.
*
* @var \Drupal\user\PrivateTempStoreFactory
*/
protected $tempStoreFactory;
/**
* The user storage.
*
* @var \Drupal\user\UserStorageInterface
*/
protected $userStorage;
/**
* The replicator manager.
*
* @var \Drupal\workspace\ReplicatorInterface
*/
protected $replicatorManager;
/**
* The workspace manager.
*
* @var \Drupal\multiversion\Workspace\WorkspaceManagerInterface
*/
protected $workspaceManager;
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The multiversion manager.
*
* @var \Drupal\multiversion\MultiversionManagerInterface
*/
protected $multiversionManager;
/**
* The source workspace pointer.
*
* @var \Drupal\workspace\WorkspacePointerInterface
*/
protected $source = NULL;
/**
* The target workspace pointer.
*
* @var \Drupal\workspace\WorkspacePointerInterface
*/
protected $target = NULL;
/**
* The replicable entity types.
*
* @var \Drupal\Core\Entity\EntityTypeInterface[]
*/
protected $replicableEntities = array();
/**
* Constructs a new IndividualPushConfirm.
*
* @param \Drupal\user\PrivateTempStoreFactory $temp_store_factory
* The temp store factory.
* @param \Drupal\user\UserStorageInterface $user_storage
* The user storage.
* @param \Drupal\workspace\ReplicatorInterface $replicator_manager
* The conflict tracker.
* @param \Drupal\multiversion\Workspace\WorkspaceManagerInterface $workspace_manager
* The conflict tracker.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Drupal\multiversion\MultiversionManagerInterface $multiversion_manager
* The multiversion manager.
*/
public function __construct(
PrivateTempStoreFactory $temp_store_factory,
UserStorageInterface $user_storage,
ReplicatorInterface $replicator_manager,
WorkspaceManagerInterface $workspace_manager,
EntityTypeManagerInterface $entity_type_manager,
MultiversionManagerInterface $multiversion_manager
) {
$this->tempStoreFactory = $temp_store_factory;
$this->userStorage = $user_storage;
$this->replicatorManager = $replicator_manager;
$this->workspaceManager = $workspace_manager;
$this->entityTypeManager = $entity_type_manager;
$this->multiversionManager = $multiversion_manager;
$this->replicableEntities = $multiversion_manager->getSupportedEntityTypes();
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('user.private_tempstore'),
$container->get('entity_type.manager')->getStorage('user'),
$container->get('workspace.replicator_manager'),
$container->get('workspace.manager'),
$container->get('entity_type.manager'),
$container->get('multiversion.manager')
);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'individual_push_confirm';
}
/**
* {@inheritdoc}
*/
public function getQuestion() {
return $this->t('Are you sure you want to deploy these entities?');
}
/**
* {@inheritdoc}
*/
public function getCancelUrl() {
return new Url('<front>');
}
/**
* {@inheritdoc}
*/
public function getConfirmText() {
return $this->t('Deploy entities');
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
// Retrieve the entities to be deployed from the temp store.
/** @var \Drupal\Core\Entity\EntityInterface[]|NULL $entities */
$entities = $this->tempStoreFactory
->get('deploy_individual_push_individual')
->get($this->currentUser()->id());
if (!$entities) {
return $this->redirect('<front>');
}
// Table to select entities.
$header = [
'label' => $this->t('Label'),
'type' => $this->t('Type'),
'bundle' => $this->t('Bundle'),
];
$options = array();
$default_value = array();
foreach ($entities as $entity) {
$this->buildEntityOptions($options, $entity);
$option_value = $this->formatOptionValue($entity, '');
$default_value[$option_value] = $option_value;
}
$form['entities'] = array(
'#type' => 'tableselect',
'#header' => $header,
'#options' => $options,
'#default_value' => $default_value,
'#empty' => $this->t('No entities to be pushed have been found.'),
);
$form = parent::buildForm($form, $form_state);
return $form;
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
$selected_entities = $form_state->getValue('entities');
$selected_entities = array_filter($selected_entities);
if (empty($selected_entities)) {
$form_state->setErrorByName('entities', $this->t('You must select at least one entity.'));
}
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
// Clear out the accounts from the temp store.
$current_user_id = $this->currentUser()->id();
$this->tempStoreFactory->get('deploy_individual_push_individual')->delete($current_user_id);
if ($form_state->getValue('confirm')) {
// Deploy selected entities.
$selected_entities = $form_state->getValue('entities');
$selected_entities = array_filter($selected_entities);
$organized_uuids = $this->extractUuids($selected_entities);
// Reload entities to put their label in the description of the
// Replication.
$prepared_entities = [];
foreach ($organized_uuids as $entity_type => $uuids) {
$entities = $this->entityTypeManager
->getStorage($entity_type)
->loadByProperties(array('uuid' => $uuids));
foreach ($entities as $entity) {
$prepared_entities[] = $entity;
}
}
$this->deploy($prepared_entities);
}
}
/**
* Helper function to deploy individual content.
*
* @param \Drupal\Core\Entity\EntityInterface[] $entities
* An array of entities.
*/
protected function deploy($entities) {
$task = new ReplicationTask();
$uuids = array();
// Limit name length to the max in the entity base fields definition (50).
$name = Unicode::truncate($this->t('Deploy @source to @target', [
'@source' => $this->getDefaultSource()->label(),
'@target' => $this->getDefaultTarget()->label(),
])->render(), 50);
$description = $this->formatPlural(
count($entities),
'Deployment of the following entity:',
'Deployment of the following entities:'
)
->render();
$description .= PHP_EOL;
foreach ($entities as $entity) {
$uuids[] = $entity->uuid();
$description .= $entity->label() . PHP_EOL;
}
$task->setParameter('uuids', $uuids);
$replication = Replication::create(array(
'name' => $name,
'description' => $description,
'source' => $this->getDefaultSource()->id(),
'target' => $this->getDefaultTarget()->id(),
));
try {
$response = $this->replicatorManager->replicate(
$this->getDefaultSource(),
$this->getDefaultTarget(),
$task
);
if (($response instanceof ReplicationLogInterface) && ($response->get('ok')->value == TRUE)) {
$replication->set('replicated', REQUEST_TIME)
->save();
drupal_set_message($this->t('Successful deployment.'));
}
else {
drupal_set_message($this->t('Deployment error. Check recent log messages for more details.'), 'error');
}
}
catch (\Exception $e) {
watchdog_exception('Deploy individual', $e);
drupal_set_message($e->getMessage(), 'error');
}
}
/**
* Helper function to return the default source.
*
* @return \Drupal\Core\Entity\EntityInterface|\Drupal\workspace\WorkspacePointerInterface|mixed
* A WorkspacePointer to the source.
*/
protected function getDefaultSource() {
if (!empty($this->source)) {
return $this->source;
}
/** @var \Drupal\multiversion\Entity\Workspace $workspace */
$workspace = $this->workspaceManager->getActiveWorkspace();
$workspace_pointers = $this->entityTypeManager
->getStorage('workspace_pointer')
->loadByProperties(['workspace_pointer' => $workspace->id()]);
return $this->source = reset($workspace_pointers);
}
/**
* Helper function to return the default target.
*
* @return \Drupal\workspace\WorkspacePointerInterface
* A WorkspacePointer to the target.
*/
protected function getDefaultTarget() {
if (!empty($this->target)) {
return $this->target;
}
/** @var \Drupal\multiversion\Entity\Workspace $workspace */
$workspace = $this->workspaceManager->getActiveWorkspace();
return $this->target = $workspace->get('upstream')->entity;
}
/**
* Helper function to check if an entity is replicable.
*
* @param EntityInterface $entity
* The entity to check the replicability.
*
* @return bool
* TRUE if the entity is replicable. FALSE otherwise
*/
protected function entityReplicable(EntityInterface $entity) {
foreach ($this->replicableEntities as $replicable_entity) {
$supported_entity_class = $replicable_entity->getOriginalClass();
if ($entity instanceof $supported_entity_class) {
return TRUE;
}
}
return FALSE;
}
/**
* Helper function to get replicable entities referenced from an entity.
*
* @param EntityInterface $entity
* The entity to get the replicable referenced entities from.
*
* @return \Drupal\Core\Entity\EntityInterface[]
* An array of replicable referenced entities.
*/
protected function getReplicableReferencedEntities(EntityInterface $entity) {
$replicable_referenced_entities = array();
$referenced_entities = $entity->referencedEntities();
foreach ($referenced_entities as $referenced_entity) {
if (($referenced_entity instanceof ContentEntityInterface) && $this->entityReplicable($referenced_entity)) {
$replicable_referenced_entities[] = $referenced_entity;
}
}
return $replicable_referenced_entities;
}
/**
* Extract entity UUID from string.
*
* Expected entry format: entity_type_id___(uuids___...)entity_uuid.
*
* @param string[] $selected_entities
* The selected entities UUID as string.
*
* @return string[]
* The selected UUID string.
*/
protected function extractUuids($selected_entities) {
$cleaned_selected_entities = array();
foreach ($selected_entities as $selected_entity) {
$uuids = explode($this::VALUE_SEPARATOR, $selected_entity);
$type = $uuids[0];
$uuid = end($uuids);
if (!isset($cleaned_selected_entities[$type])) {
$cleaned_selected_entities[$type] = [];
}
$cleaned_selected_entities[$type][$uuid] = $uuid;
}
return $cleaned_selected_entities;
}
/**
* Helper function to format the label of an entity for the option.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity to format the label.
* @param int $level
* The number of indentation div.
*
* @return \Drupal\Component\Render\FormattableMarkup
* A formatted label.
*/
protected function formatOptionLabel(EntityInterface $entity, $level = 0) {
$indentation = '';
for ($i = 1; $i <= $level; $i++) {
$indentation .= '<div class="indentation"> </div>';
}
return new FormattableMarkup($indentation . '@label', array(
'@label' => $this->t('@entity_label (id: @entity_id)', array(
'@entity_label' => $entity->label(),
'@entity_id' => $entity->id(),
)),
));
}
/**
* Helper function to format the value of an entity for the option.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity to format the value.
* @param string $referencing_uuids
* The referencing uuids. Format: uuid1___uuid2___...
*
* @return string
* A formatted value.
*/
protected function formatOptionValue(EntityInterface $entity, $referencing_uuids = '') {
return $entity->getEntityTypeId() . $this::VALUE_SEPARATOR . $referencing_uuids . $entity->uuid();
}
/**
* Helper function to generate option for an entity.
*
* @param array $options
* The array of options for the tableselect form type element.
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity to create the option on.
* @param string $referencing_uuids
* The referencing uuids. Format: uuid1___uuid2___...
* @param int $level
* The number of indentation div.
*/
protected function addEntityOption(array &$options, EntityInterface $entity, $referencing_uuids = '', $level = 0) {
$options[$this->formatOptionValue($entity, $referencing_uuids)] = array(
'label' => $this->formatOptionLabel($entity, $level),
'type' => $entity->getEntityType()->getLabel(),
'bundle' => $entity->bundle(),
);
}
/**
* Build the options of the table select recursively.
*
* @param array $options
* The array of options for the tableselect form type element.
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity to create the option on.
* @param string $referencing_uuids
* The referencing uuids. Format: uuid1___uuid2___...
* @param int $level
* The number of indentation div.
*/
protected function buildEntityOptions(array &$options, EntityInterface $entity, $referencing_uuids = '', $level = 0) {
if ($level > $this::MAX_REFERENCED_ENTITIES_DEPTH) {
return;
}
// The option for the current entity.
$this->addEntityOption($options, $entity, $referencing_uuids, $level);
// Build the options for the referenced entities.
foreach ($this->getReplicableReferencedEntities($entity) as $referenced_entity) {
$this->buildEntityOptions($options, $referenced_entity, $referencing_uuids . $entity->uuid() . $this::VALUE_SEPARATOR, $level + 1);
}
}
}
