cms_content_sync-3.0.x-dev/src/Form/PushChangesConfirm.php
src/Form/PushChangesConfirm.php
<?php
namespace Drupal\cms_content_sync\Form;
use Drupal\cms_content_sync\Entity\EntityStatus;
use Drupal\cms_content_sync\Entity\Flow;
use Drupal\cms_content_sync\EntityStatusProxy;
use Drupal\cms_content_sync\Plugin\Type\EntityHandlerPluginManager;
use Drupal\cms_content_sync\PushIntent;
use Drupal\cms_content_sync\SyncCoreInterface\SyncCoreFactory;
use Drupal\cms_content_sync\SyncIntent;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityChangedInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeManager;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Entity\TranslatableInterface;
use Drupal\Core\Form\ConfirmFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Link;
use Drupal\Core\TempStore\PrivateTempStoreFactory;
use Drupal\Core\Url;
use EdgeBox\SyncCore\Interfaces\ISyncCore;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
/**
* Provides a node deletion confirmation form.
*
* @internal
*/
class PushChangesConfirm extends ConfirmFormBase {
/**
* The tempstore factory.
*
* @var \Drupal\Core\TempStore\PrivateTempStoreFactory
*/
protected $tempStoreFactory;
/**
* The node storage.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $storage;
/**
* The Flow to use for pushing, if any.
*
* @var null|Flow
*/
protected $flow;
/**
* The entities in their default language along with all the translations to be pushed.
*
* @var array
*/
protected $tree;
/**
* Whether any of the selected entities has additional translations, meaning
* we show a more complex table.
*
* @var bool
*/
protected $hasTranslations = FALSE;
/**
* Constructs a PushChangesConfirm form object.
*
* @param \Drupal\Core\TempStore\PrivateTempStoreFactory $temp_store_factory
* The tempstore factory.
* @param \Drupal\Core\Entity\EntityTypeManager $manager
* The entity manager.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
*/
public function __construct(PrivateTempStoreFactory $temp_store_factory, EntityTypeManager $manager) {
$this->tempStoreFactory = $temp_store_factory;
$this->storage = $manager->getStorage('node');
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('tempstore.private'),
$container->get('entity_type.manager')
);
}
/**
*
*/
protected function loadEntity(array $entity_serialized) {
if ($entity_serialized['deleted']) {
return NULL;
}
$result = \Drupal::entityTypeManager()->getStorage($entity_serialized['type'])->load($entity_serialized['id']);
if ($result instanceof TranslatableInterface && $result->language()->getId() !== $entity_serialized['language']) {
return $result->getTranslation($entity_serialized['language']);
}
return $result;
}
/**
*
*/
protected function loadEntities(array $entities_serialized) {
$result = [];
foreach ($entities_serialized as $entity_serialized) {
$entity = $this->loadEntity($entity_serialized);
if ($entity) {
$result[] = $entity;
}
}
return $result;
}
/**
*
*/
protected function getFlowForEntity(EntityInterface $entity) {
if ($this->flow) {
if (!$this->flow->getController()->canPushEntity($entity, PushIntent::PUSH_ANY)) {
return NULL;
}
return $this->flow;
}
$candidates = PushIntent::getFlowsForEntity($entity, PushIntent::PUSH_FORCED, SyncIntent::ACTION_CREATE);
if (!count($candidates)) {
return NULL;
}
return reset($candidates);
}
/**
*
*/
protected function serializeEntity(EntityInterface $entity) {
$flow = $this->getFlowForEntity($entity);
$controller = $flow->getController();
$allowed_languages = $controller->getAllowedLanguages();
return [
'type' => $entity->getEntityTypeId(),
'id' => $entity->id(),
'label' => $entity->label(),
'language' => $entity->language()->getId(),
'language_name' => $entity->language()->getName(),
'translatable' => $entity instanceof TranslatableInterface,
'language_allowed' => !$allowed_languages || in_array($entity->language()->getId(), $allowed_languages),
'changed' => $entity instanceof EntityChangedInterface ? $entity->getChangedTime() : NULL,
'last_push' => EntityStatus::getLastPushForEntity($entity, TRUE),
'new_references' => $flow ? $this->hasNewReferences($entity, $flow) : FALSE,
'deleted' => FALSE,
'prevented' => _cms_content_sync_prevent_entity_export($entity),
];
}
/**
*
*/
protected function serializeDeletedTranslation(EntityInterface $entity, string $language, ?int $deleted_at) {
return [
'type' => $entity->getEntityTypeId(),
'id' => $entity->id(),
'label' => '',
'language' => $language,
'language_name' => $language,
'translatable' => TRUE,
'changed' => $deleted_at,
'last_push' => EntityStatus::getLastPushForEntity($entity, TRUE),
'new_references' => FALSE,
'deleted' => TRUE,
'prevented' => FALSE,
];
}
/**
*
*/
protected function initValues(FormStateInterface $form_state, array $entities, ?string $flow_id, ?bool $deleted_translation) {
if ($this->tree) {
return;
}
// TODO: Provide by feature flag; allow to overwrite by query parameter for recovery.
$delete_missing_translations = TRUE;
if ($form_state->get('tree')) {
$this->tree = $form_state->get('tree');
$flow_id = $form_state->get('flow_id');
}
else {
$this->tree = [];
foreach ($entities as $entity) {
foreach ($this->tree as $item) {
if ($item['root']['id'] === $entity->id()) {
continue 2;
}
}
$root_entity = $entity instanceof TranslatableInterface ? $entity->getUntranslated() : $entity;
if (empty($flow_id)) {
$flow = $this->getFlowForEntity($root_entity);
if (!empty($flow)) {
$flow_id = $flow->id();
}
if (empty($flow_id)) {
continue;
}
}
$item = [
'root' => $this->serializeEntity($root_entity),
'translations' => [],
'root_selected' => !$deleted_translation && in_array($root_entity, $entities, TRUE),
'root_required' => !EntityStatus::wasEntityPushedOrPulled($root_entity),
'translations_selected' => [],
'pushed_translations' => $entity instanceof TranslatableInterface
? SyncCoreFactory::getSyncCoreV2()->getUsedLanguages($entity->getEntityTypeId(), $entity->bundle(), EntityHandlerPluginManager::getSharedId($entity))
: NULL,
];
if ($root_entity instanceof TranslatableInterface) {
if (count($root_entity->getTranslationLanguages()) > 1) {
$languages = $root_entity->getTranslationLanguages();
$pushing_languages = [];
foreach ($entities as $pushing_entity) {
if ($pushing_entity->getEntityTypeId() === $entity->getEntityTypeId() && $pushing_entity->id() === $entity->id()) {
$pushing_languages[] = $pushing_entity->language()->getId();
}
}
if (in_array($root_entity->language()->getId(), $pushing_languages)) {
$item['root_selected'] = TRUE;
}
foreach ($languages as $id => $language) {
$translation = $root_entity->getTranslation($id);
// Don't show unpublished translation for manual flows.
if (!$translation->isPublished()
&& Flow::getAll()[$flow_id]->getController()->getEntityTypeConfig($root_entity->getEntityTypeId(), $root_entity->bundle())['export'] === PushIntent::PUSH_MANUALLY
&& Flow::getAll()[$flow_id]->getController()->getEntityTypeConfig($root_entity->getEntityTypeId(), $root_entity->bundle())['handler_settings']['ignore_unpublished']
&& empty(EntityStatus::getLastPushForEntity($translation, TRUE))) {
continue;
}
$translation_serialized = $this->serializeEntity($translation);
if ($id !== $root_entity->language()->getId()) {
if (!$deleted_translation && in_array($id, $pushing_languages)) {
$item['translations_selected'][] = $this->getEntityLanguageId($translation_serialized);
}
}
$item['translations'][$id] = $translation_serialized;
}
}
$status = EntityStatus::getInfosForEntity($entity->getEntityTypeId(), $entity->uuid(), $flow_id ? ['flow' => $flow_id] : NULL);
if (!count($status)) {
$status = NULL;
}
elseif (count($status) === 1) {
$status = $status[0];
}
else {
$status = new EntityStatusProxy($status);
}
$deleted_languages = $status ? $status->translationDeletionRequiresPush() : NULL;
if ($delete_missing_translations && !empty($item['pushed_translations'])) {
foreach ($item['pushed_translations'] as $language) {
if (!$root_entity->hasTranslation($language)) {
if (!$deleted_languages) {
$deleted_languages = [];
}
$deleted_languages[] = $language;
}
}
}
if ($deleted_languages) {
foreach ($deleted_languages as $id) {
if ($root_entity->hasTranslation($id)) {
continue;
}
$translation_serialized = $this->serializeDeletedTranslation($entity, $id, $status->getTranslationDeletedAt($id));
$item['translations'][$id] = $translation_serialized;
if ($deleted_translation) {
$item['translations_selected'][] = $this->getEntityLanguageId($translation_serialized);
}
}
}
}
$this->tree[] = $item;
}
$form_state->set('tree', $this->tree);
$form_state->set('flow_id', $flow_id);
}
$this->flow = $flow_id ? Flow::getAll()[$flow_id] : NULL;
foreach ($this->tree as $tree_item) {
if (!empty($tree_item['translations'])) {
$this->hasTranslations = TRUE;
}
}
}
/**
*
*/
protected function hasNewReferences(EntityInterface $entity, Flow $flow) {
static $tested = [];
if (isset($tested[$entity->getEntityTypeId()][$entity->id()])) {
return $tested[$entity->getEntityTypeId()][$entity->id()] === 2;
}
// Avoid circular references. 1=checking right now.
$tested[$entity->getEntityTypeId()][$entity->id()] = 1;
if (!$flow->getController()->canPushEntity($entity, PushIntent::PUSH_ANY)) {
// Entity not supported => no new references found. 0=none.
$tested[$entity->getEntityTypeId()][$entity->id()] = 0;
return FALSE;
}
if ($entity instanceof TranslatableInterface) {
$entity = $entity->getUntranslated();
}
if ($entity instanceof FieldableEntityInterface) {
foreach ($entity->getFields(FALSE) as $field) {
$definition = $field->getFieldDefinition();
if ($definition->isTranslatable()) {
continue;
}
if (!in_array($definition->getType(), [
'entity_reference',
'entity_reference_revisions',
'cohesion_entity_reference_revisions',
])) {
continue;
}
foreach ($field as $item) {
$reference = $item->get('entity');
if (!$reference) {
continue;
}
$adapter = $reference->getTarget();
if (!$adapter) {
continue;
}
$referenced_entity = $adapter->getValue();
if (!$referenced_entity || !($referenced_entity instanceof ContentEntityInterface)) {
continue;
}
// Exclude e.g. user references.
if (!$flow->getController()->canPushEntity($referenced_entity, PushIntent::PUSH_AS_DEPENDENCY)) {
continue;
}
if (!EntityStatus::wasEntityPushedOrPulled($referenced_entity, TRUE)) {
// New reference found. 2=new found.
$tested[$entity->getEntityTypeId()][$entity->id()] = 2;
return TRUE;
}
if ($this->hasNewReferences($referenced_entity, $flow)) {
// New reference found, nested. 2=new found.
$tested[$entity->getEntityTypeId()][$entity->id()] = 2;
return TRUE;
}
}
}
}
// No new references found. 0=none.
$tested[$entity->getEntityTypeId()][$entity->id()] = 0;
return FALSE;
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'node_cms_content_sync_push_changes_confirm';
}
/**
* {@inheritdoc}
*/
public function getQuestion() {
return 'Please confirm pushing';
}
/**
* {@inheritdoc}
*/
public function getCancelUrl() {
return new Url('system.admin_content');
}
/**
* {@inheritdoc}
*/
public function getConfirmText() {
return _cms_content_sync_push_label();
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state, mixed $entities = NULL, ?string $flow_id = NULL, ?bool $deleted_translation = NULL) {
if (empty($entities)) {
return new RedirectResponse($this->getCancelUrl()->setAbsolute()->toString());
}
if (is_string($entities)) {
$entities = explode(',', $entities);
}
foreach ($entities as $index => $entity) {
if (is_string($entity)) {
$parts = explode(':', $entity);
$entity = \Drupal::entityTypeManager()->getStorage($parts[0])->load($parts[1]);
if (!empty($parts[2]) && $entity instanceof TranslatableInterface && $entity->language()->getId() !== $parts[2]) {
$entity = $entity->getTranslation($parts[2]);
}
$entities[$index] = $entity;
}
}
$this->initValues($form_state, $entities, $flow_id, $deleted_translation);
if (empty($this->tree)) {
return new RedirectResponse($this->getCancelUrl()->setAbsolute()->toString());
}
$show_order = !!$form_state->getValue('show-order');
if ($show_order) {
\Drupal::messenger()->addWarning($this->t('When defining a push order, the content is pushed and pulled one after another. This is useful when:<ol><li>The publishing is time-sensitive and specific content should be published first.</li><li>You have references to new content that you want to be resolved immediately.</li></ol><br/><strong>But if any push or pull fails, the push/pull of every content item after it will fail, too! The total time to update content is also considerably longer.</strong>'));
}
$can_select_translations = PushIntent::canHandleTranslationsIndependently();
$items = [
'#type' => 'table',
'#attributes' => [
'id' => 'content-sync--push-changes-confirm',
],
/*'#header' => [
$show_order ? $this->t('Order') : '',
$this->t('Title'),
],*/
'#tabledrag' => $show_order
? [[
'action' => 'order',
'relationship' => 'sibling',
'group' => 'draggable-weight',
]
]
: [],
];
foreach ($this->tree as $weight => $tree_item) {
$entity_serialized = $tree_item['root'];
$pushed_translations = $tree_item['pushed_translations'];
$root_selected = $tree_item['root_selected'];
if ($show_order) {
$items[$entity_serialized['id']]['#attributes']['class'] = ['draggable'];
$items[$entity_serialized['id']]['#weight'] = $weight;
}
if ($show_order) {
$items[$entity_serialized['id']]['order-handle'] = [
'#markup' => '',
];
$items[$entity_serialized['id']]['order'] = [
'#type' => 'weight',
'#title' => t('Weight'),
'#title_display' => 'invisible',
'#default_value' => $weight,
'#attributes' => [
'class' => [
'draggable-weight',
],
],
];
}
if ($entity_serialized['translatable'] && $this->hasTranslations) {
$items[$entity_serialized['id']]['content'] = [
'title' => [
'#markup' => $entity_serialized['label'],
'#prefix' => '<h1>',
'#suffix' => '</h1>',
],
];
$disabled = $tree_item['root_required'] || !$can_select_translations || $entity_serialized['prevented'] || !$entity_serialized['language_allowed'];
if (!$disabled) {
if ($entity_serialized['new_references']) {
$items[$entity_serialized['id']]['content']['warning'] = [
'#prefix' => '<div class="messages messages--warning">',
'#markup' => $this->t('This item contains new nested and untranslatable content like paragraphs or media items. When you push any translation, the root language will also have to be updated on the target site(s) to include the new content. The new content elements are set to unpublished by default but it may still affect your content. Please consider publishing the default translation to avoid any potential inconsistencies.'),
'#suffix' => '</div>'
];
}
}
else {
if ($entity_serialized['prevented']) {
$items[$entity_serialized['id']]['content']['warning'] = [
'#prefix' => '<div class="messages messages--warning">',
'#markup' => $this->t('This item has been pulled from another site and can\'t be pushed back.'),
'#suffix' => '</div>'
];
}
}
$translations = [
'#type' => 'table',
'#header' => [
$this->t('Push'),
$this->t('Language'),
$this->t('Updated'),
$this->t('Last pushed version'),
$this->t('Notes'),
$this->t('Title'),
],
'#attributes' => [
'class' => [
'pushable-translations',
],
],
];
$last_push = !$pushed_translations || in_array($entity_serialized['language'], $pushed_translations)
? $entity_serialized['last_push']
: NULL;
$changed_time = $entity_serialized['changed'];
$id = $entity_serialized['language'];
$has_changed = !$last_push || !$changed_time || $changed_time > $last_push;
$default_value = ($root_selected || $tree_item['root_required'] || !$can_select_translations) && !$entity_serialized['prevented'] && $entity_serialized['language_allowed'];
$translations[$id]['#attributes']['class'] = [
$last_push ? ($has_changed ? 'entity-changed' : 'entity-unchanged') : 'entity-new',
$default_value ? 'default-checked' : 'default-unchecked',
];
$translations[$id]['push'] = [
'#type' => 'checkbox',
'#default_value' => $default_value,
'#disabled' => $disabled,
'#title_display' => 'invisible',
'#title' => $this->t('Push'),
'#attributes' => [
'title' => $tree_item['root_required'] ? $this->t('The default language of a content must always be pushed initially as it cannot be changed later; this is a technical requirement from Drupal.') : (!$can_select_translations ? $this->t('Please enable individual translation handling to use this (see below).') : ''),
],
];
$translations[$id]['language'] = [
'#markup' => $entity_serialized['language_name'],
];
$translations[$id]['changed'] = $changed_time ? [
'#markup' => \Drupal::service('date.formatter')->format($changed_time),
] : [
'#markup' => '',
];
$translations[$id]['pushed'] = $last_push ? [
'#markup' => \Drupal::service('date.formatter')->format($last_push),
] : [
'#prefix' => '<em>',
'#suffix' => '<em>',
'#markup' => $this->t('New'),
];
$translations[$id]['notes'] = [
'#markup' => ($tree_item['root_required'] ? '<em>' . $this->t('default') . '</em>, <strong>' . $this->t('required') . '</strong>' : '<em>' . $this->t('default') . '</em>') . ($has_changed ? '' : ', <em>' . $this->t('unchanged') . '</em>') . ($entity_serialized['language_allowed'] ? '' : ', <em>' . $this->t("language can't be pushed") . '</em>'),
];
$translations[$id]['title'] = [
'#markup' => $entity_serialized['label'],
];
foreach ($tree_item['translations'] as $translation_serialized) {
$translation_id = $translation_serialized['language'];
if ($translation_id === $id) {
continue;
}
if ($translation_serialized['deleted']) {
$entity = $this->loadEntity($entity_serialized);
$flow = $this->getFlowForEntity($entity);
if (!$flow->getController()->canPushEntity($entity, PushIntent::PUSH_MANUALLY, SyncIntent::ACTION_DELETE)) {
continue;
}
}
$changed_time = $translation_serialized['changed'];
$last_push = !$pushed_translations || in_array($translation_serialized['language'], $pushed_translations)
? $translation_serialized['last_push']
: NULL;
$has_changed = !$last_push || !$changed_time || $changed_time > $last_push;
$default_value = (in_array($this->getEntityLanguageId($translation_serialized), $tree_item['translations_selected']) || !$can_select_translations) && !$translation_serialized['prevented'] && $translation_serialized['language_allowed'];
$translations[$translation_id]['#attributes']['class'] = [
$last_push ? ($has_changed ? 'entity-changed' : 'entity-unchanged') : 'entity-new',
$default_value ? 'default-checked' : 'default-unchecked',
];
$translations[$translation_id]['push'] = [
'#type' => 'checkbox',
'#default_value' => $default_value,
'#disabled' => !$can_select_translations || $translation_serialized['prevented'] || !$translation_serialized['language_allowed'],
'#title' => $this->t('Push'),
'#title_display' => 'invisible',
'#attributes' => [
'class' => [
'push-select',
],
],
];
$translations[$translation_id]['language'] = [
'#markup' => $translation_serialized['language_name'],
];
$translations[$translation_id]['changed'] = $changed_time ? [
'#markup' => \Drupal::service('date.formatter')->format($changed_time),
] : [
'#markup' => '',
];
$translations[$translation_id]['pushed'] = $last_push ? [
'#markup' => \Drupal::service('date.formatter')->format($last_push),
] : [
'#prefix' => '<em>',
'#suffix' => '<em>',
'#markup' => $this->t('New'),
];
$notes = $has_changed ? ($translation_serialized['deleted'] ? '<em>' . ($changed_time ? $this->t('deleted') : $this->t('missing')) . '</em>' : '') : '<em>' . $this->t('unchanged') . '</em>';
if (!$translation_serialized['language_allowed']) {
$notes .= ($notes ? ', ' : '') . '<em>' . $this->t("language can't be pushed") . '</em>';
}
$translations[$translation_id]['notes'] = [
'#markup' => $notes,
];
$translations[$translation_id]['title'] = [
'#markup' => $translation_serialized['label'],
];
}
if (count($translations) > 1 && $can_select_translations) {
$translations['actions']['actions']['#wrapper_attributes']['colspan'] = 6;
$translations['actions']['actions']['#wrapper_attributes']['class'] = ['entity-actions'];
$translations['actions']['actions']['#markup'] = $this->t('Select') . ': <a href="#" class="select-all-action">' . $this->t('all') . '</a> | <a href="#" class="select-new-action">' . $this->t('new') . '</a> | <a href="#" class="select-new-changed-action">' . $this->t('new and changed') . '</a> | <a href="#" class="select-changed-action">' . $this->t('changed') . '</a> | <a href="#" class="select-none-action">' . $this->t('none') . '</a> | <a href="#" class="select-restore-action">' . $this->t('restore') . '</a>';
}
$items[$entity_serialized['id']]['content']['translations'] = $translations;
}
else {
$items[$entity_serialized['id']]['content'] = [
'push' => [
'#type' => 'checkbox',
'#default_value' => ($root_selected || $tree_item['root_required']) && !$entity_serialized['prevented'],
'#disabled' => $tree_item['root_required'] || $entity_serialized['prevented'],
'#title' => $entity_serialized['label'],
],
'#prefix' => '<h1>',
'#suffix' => '</h1>',
];
}
}
$form['entities'] = $items;
$client = SyncCoreFactory::getSyncCoreV2();
$site_priority = (int) $client->getSitePriority();
if ($site_priority && $site_priority > ISyncCore::PRIORITY_NORMAL) {
if ($site_priority === ISyncCore::PRIORITY_HIGHLY_CRITICAL) {
$priorities = [
-3 => $this->t('Regular'),
-2 => $this->t('Important'),
-1 => $this->t('Critical'),
0 => $this->t('Highly Critical'),
];
}
elseif ($site_priority === ISyncCore::PRIORITY_CRITICAL) {
$priorities = [
-2 => $this->t('Regular'),
-1 => $this->t('Important'),
0 => $this->t('Critical'),
1 => $this->t('Highly Critical'),
];
}
elseif ($site_priority === ISyncCore::PRIORITY_HIGH) {
$priorities = [
-1 => $this->t('Regular'),
0 => $this->t('Important'),
1 => $this->t('Critical'),
];
}
// Fallback in case the module becomes outdated.
else {
$priorities = [
0 => $site_priority . '',
];
}
$form['priority'] = [
'#type' => 'radios',
'#default_value' => 0,
'#options' => $priorities,
'#title' => $this->t('Priority'),
'#description' => $this->t('Prioritize important updates. The more important, the faster the update is processed by the service. More important updates are retried faster and more often if they fail but cost more to run. The highest priority "Highly Critical" is only available during critical publishing events and ignores throttling and avoids Drupal caching issues during high-traffic events.'),
];
}
$form = parent::buildForm($form, $form_state);
if ($show_order) {
$form['push-in-order'] = [
'#type' => 'hidden',
'#value' => 1,
];
}
elseif (count($this->tree) > 1) {
$form['actions']['show-order'] = [
'#type' => 'submit',
'#value' => $this->t('Select order'),
'#submit' => ['::showOrder'],
'#name' => 'show-order',
];
}
$form['actions']['submit']['#name'] = 'confirm';
// Swap order.
$cancel = $form['actions']['cancel'];
unset($form['actions']['cancel']);
$form['actions']['cancel'] = $cancel;
$form['#attached']['library'][] = 'cms_content_sync/push-changes-confirm-form';
return $form;
}
/**
*
*/
public function showOrder(array &$form, FormStateInterface $form_state) {
$form_state->set('show-order', TRUE)->setRebuild(TRUE);
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$trigger = $form_state->getTriggeringElement()['#name'];
if ($trigger === 'confirm') {
$ordered = !!$form_state->getValue('push-in-order');
if ($ordered) {
usort($this->tree, function ($a, $b) use (&$form_state) {
$weight_a = $form_state->getValue(['entities', $a['root']['id'], 'order']);
$weight_b = $form_state->getValue(['entities', $b['root']['id'], 'order']);
return $weight_a - $weight_b;
});
}
$translation_groups_per_flow = [];
foreach ($this->tree as $item) {
$entity_serialized = $item['root'];
$entity = $this->loadEntity($entity_serialized);
$flow = $this->getFlowForEntity($entity);
if (!$flow) {
continue;
}
if (!($entity instanceof TranslatableInterface) || !$this->hasTranslations) {
$selected = !!$form_state->getValue(['entities', $entity->id(), 'content', 'push']);
if ($selected) {
$translation_groups_per_flow[$flow->id]['update'][] = [$entity];
}
}
else {
$updated_translations = [];
$translations = empty($item['translations']) ? [$entity] : $this->loadEntities($item['translations']);
if (!in_array($entity, $translations, TRUE)) {
$translations[] = $entity;
}
foreach ($translations as $translation) {
$translation_id = $translation->language()->getId();
$selected = !!$form_state->getValue(['entities', $translation->id(), 'content', 'translations', $translation_id, 'push']);
if ($selected) {
$updated_translations[] = $translation;
}
}
if (!empty($updated_translations)) {
$translation_groups_per_flow[$flow->id]['update'][] = $updated_translations;
}
$deleted_translations = [];
foreach ($item['translations'] ?? [] as $translation_serialized) {
if ($translation_serialized['deleted']) {
$selected = !!$form_state->getValue(['entities', $entity->id(), 'content', 'translations', $translation_serialized['language'], 'push']);
if ($selected) {
$deleted_translations[] = $translation_serialized['language'];
}
}
}
if (!empty($deleted_translations)) {
array_unshift($deleted_translations, $entity);
$translation_groups_per_flow[$flow->id]['delete'][] = $deleted_translations;
}
}
}
$priority = $form_state->getValue('priority') ?? NULL;
if ($priority !== NULL) {
$client = SyncCoreFactory::getSyncCoreV2();
$site_priority = $client->getSitePriority();
$priority = (int) $priority + $site_priority;
if ($priority < 0) {
$priority = 0;
}
elseif ($priority > ISyncCore::PRIORITY_HIGHLY_CRITICAL) {
$priority = ISyncCore::PRIORITY_HIGHLY_CRITICAL;
}
}
foreach ($translation_groups_per_flow as $flow_id => $translation_groups) {
$flow = Flow::getAll()[$flow_id];
PushIntent::pushEntitiesFromUi($translation_groups['update'] ?? [], PushIntent::PUSH_MANUALLY, SyncIntent::ACTION_CREATE, $flow, $ordered, $priority, $translation_groups['delete'] ?? []);
}
$this->tempStoreFactory->get('node_cms_content_sync_push_changes_confirm')->delete('entities');
$this->tempStoreFactory->get('node_cms_content_sync_push_changes_confirm')->delete('flow_id');
$form_state->setRedirect('system.admin_content');
}
}
/**
*
*/
public function getDescription() {
$can_select_translations = PushIntent::canHandleTranslationsIndependently();
if ($can_select_translations) {
return $this->t('This action cannot be undone.');
}
elseif (Url::fromRoute('cms_content_sync.advanced')->access(\Drupal::currentUser())) {
return $this->t('This action cannot be undone. If you want to publish translations individually, please enable both the <em>Individual requests per translation</em> and the <em>Skip unchanged translations</em> settings in the @advanced_settings_tab tab of the module.', [
'@advanced_settings_tab' => Link::createFromRoute(t('Advanced settings tab'), 'cms_content_sync.advanced')->toString(),
]);
}
else {
return $this->t('This action cannot be undone. If you want to publish translations individually, please ask your site builder to enable the option at the Advanced settings tab of the module.');
}
}
/**
*
*/
protected function getEntityLanguageId(array $entity_serialized) {
if ($entity_serialized['translatable']) {
return $entity_serialized['type'] . ":" . $entity_serialized['id'] . ":" . $entity_serialized['language'];
}
return $entity_serialized['type'] . ":" . $entity_serialized['id'];
}
}
