taxonomy_overview-1.0.1/src/Form/TagsOverviewTermMergeForm.php
src/Form/TagsOverviewTermMergeForm.php
<?php
namespace Drupal\taxonomy_overview\Form;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
use Drupal\node\Entity\Node;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
/**
* Form to merge multiple taxonomy terms into a single one.
*/
class TagsOverviewTermMergeForm extends FormBase {
/**
* The term IDs to merge.
*
* @var int[]
*/
protected array $termIds = [];
/**
* The entity type manager service.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected EntityTypeManagerInterface $entityTypeManager;
/**
* The taxonomy term storage.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $termStorage;
/**
* Constructs a new TagsOverviewTermMergeForm object.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager) {
$this->entityTypeManager = $entity_type_manager;
$this->termStorage = $entity_type_manager->getStorage('taxonomy_term');
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity_type.manager')
);
}
/**
* {@inheritdoc}
*/
public function getFormId(): string {
return 'term_variation_merge_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state): array {
$termIdsQuery = $this->getRequest()->query->get('term_ids', '');
$this->termIds = array_filter(explode(',', $termIdsQuery));
$terms = $this->entityTypeManager->getStorage('taxonomy_term')->loadMultiple($this->termIds);
if (count($terms) < 1) {
return ['#markup' => $this->t('At least 2 terms are required to merge.')];
}
$form['#attached']['library'][] = 'taxonomy_overview/taxonomy_overview.form';
$form['fieldset0'] = [
'#type' => 'fieldset',
'#title' => $this->t('You are about to merge the following terms'),
];
$form['fieldset0']['description2'] = [
'#markup' => $this->t('The unselected terms will be merged into the chosen one.'),
];
$form['fieldset0']['fieldset1'] = [
'#type' => 'fieldset',
];
$form['fieldset0']['fieldset1']['targetTid'] = [
'#type' => 'radios',
'#title' => $this->t('Select the term to keep'),
'#options' => array_map(fn($t) => $t->label(), $terms),
'#required' => TRUE,
];
$form['fieldset0']['fieldset1']['removeAfter'] = [
'#type' => 'checkbox',
'#title' => $this->t('Remove terms after merge?'),
'#required' => FALSE,
];
$form['fieldset0']['undo'] = [
'#type' => 'checkbox',
'#title' => $this->t('I agree that this operation cannot be undone!'),
'#required' => TRUE,
];
$form['fieldset0']['actions'] = [
'#type' => 'actions',
];
$form['fieldset0']['actions']['submit'] = [
'#type' => 'submit',
'#value' => $this->t('Merge Terms'),
'#button_type' => 'primary',
'#states' => [
'enabled' => [
':input[name="undo"]' => ['checked' => TRUE],
],
],
];
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state): void {
$vocabulary = $this->getRouteMatch()->getParameter('taxonomy_vocabulary');
$form_values = $form_state->getValues();
$removeTerms = $form_values['removeAfter'];
$targetTid = $form_values['targetTid'];
$tidsToReplace = $this->termIds;
unset($tidsToReplace[array_search($targetTid, $tidsToReplace)]);
$tidsNames = [];
if ($tidsToReplace) {
foreach ($tidsToReplace as $t) {
$term = $this->termStorage->load($t);
if ($term) {
$tidsNames[] = $term->label() . ' (tid: ' . $t . ')';
}
}
}
$entitiesFields = $this->getFieldsEntityReference($vocabulary);
$operations = [];
foreach ($entitiesFields as $entityType => $bundles) {
if ($entityType === 'node') {
foreach ($bundles as $fields) {
foreach ($fields as $field) {
$query = $this->entityTypeManager->getStorage('node')->getQuery()
->accessCheck(FALSE)
->condition($field, $tidsToReplace, 'IN');
$nids = $query->execute();
foreach ($nids as $nid) {
$operations[] = [
[
self::class,
'processNodeMerge',
], [
$nid,
$targetTid,
$tidsToReplace,
$field,
$vocabulary,
],
];
}
}
}
}
elseif ($entityType === 'paragraph') {
foreach ($bundles as $fields) {
foreach ($fields as $field) {
$paragraphIds = $this->entityTypeManager->getStorage('paragraph')->getQuery()
->accessCheck(FALSE)
->condition('status', 1)
->condition($field . '.target_id', $tidsToReplace, 'IN')
->latestRevision()
->execute();
foreach ($paragraphIds as $revisionId => $pid) {
$operations[] = [
[self::class, 'processParagraphMerge'], [
$pid, $revisionId, $targetTid, $tidsToReplace, $field, $vocabulary,
],
];
}
}
}
}
}
if ($removeTerms) {
foreach ($tidsToReplace as $tid) {
$operations[] = [[self::class, 'processCleanTerms'], [$tid, $vocabulary]];
}
}
if (empty($operations)) {
// No operations were created, redirect back with a message.
if (empty($operations)) {
$msg = $this->t('No fields use these terms: @terms, so there is nothing to merge. If you want to proceed and remove the similar terms, check the `Remove terms after merge` option.', [
'@terms' => implode(', ', $tidsNames),
]);
$this->messenger()->addMessage($msg, 'warning');
return;
}
// Merge the two values into the term_ids parameter.
$termIdsParam = implode(',', [$removeTerms, $targetTid]);
// Generate the URL for redirection.
$url = $this->urlGenerator->generateFromRoute('taxonomy_overview.group_similar.merge_form', [
'taxonomy_vocabulary' => $vocabulary,
'term_ids' => $termIdsParam,
]);
// Redirect user.
$response = new RedirectResponse($url);
$response->send();
return;
}
$batch = [
'title' => $this->t('Merging taxonomy terms'),
'operations' => $operations,
'finished' => [self::class, 'batchFinished'],
];
batch_set($batch);
}
/**
* Process the merging of terms in a node's field.
*/
public static function processNodeMerge($nid, $target_tid, $tids_to_replace, $field_name, $vocabulary, &$context) {
$node = Node::load($nid);
if (!$node) {
$message = t('Node with nid %nid not found.', ['%nid' => $nid]);
$context['sandbox'][] = (string) $message;
return;
}
$translations = $node->getTranslationLanguages();
foreach ($translations as $language) {
$langcode = $language->getId();
if ($node->hasTranslation($langcode)) {
$node = $node->getTranslation($langcode);
$tags = $node->get($field_name)->getValue();
$new_tags = [];
foreach ($tags as $key => $tag) {
$tid = $tag['target_id'];
if (in_array($tid, $tids_to_replace)) {
$tid = $target_tid;
}
$new_tags[$key] = ['target_id' => $tid];
}
$node->set($field_name, array_values($new_tags));
$node->save();
}
}
$message = t('Processed node @nid: merged terms into @target_tid in field @field_name.', [
'@nid' => $nid,
'@target_tid' => $target_tid,
'@field_name' => $field_name,
]);
$context['message'] = $message;
$context['sandbox'][] = (string) $message;
$context['results'][] = $message;
$context['results']['vocabulary'] = $vocabulary;
}
/**
* Process the deletion of a term.
*/
public static function processCleanTerms($tid, $vocabulary, &$context) {
$term = \Drupal::entityTypeManager()->getStorage('taxonomy_term')->load($tid);
if ($term) {
$label = $term->label();
$term->delete();
$message = t('Deleted term "@label" (tid: @tid)', ['@label' => $label, '@tid' => $tid]);
$context['message'] = $message;
$context['sandbox'][] = (string) $message;
$context['results'][] = $message;
$context['results']['vocabulary'] = $vocabulary;
}
}
/**
* Process the merging of terms in a paragraph's field.
*/
public static function processParagraphMerge($paragraph_id, $revision_id, $target_tid, $tids_to_replace, $field_name, $vocabulary, &$context) {
$entity_type_manager = \Drupal::entityTypeManager();
$paragraph_revision = $entity_type_manager->getStorage('paragraph')->loadRevision($revision_id);
$translations = $paragraph_revision->getTranslationLanguages();
foreach ($translations as $language) {
$langcode = $language->getId();
if ($paragraph_revision->hasTranslation($langcode)) {
$paragraph_revision = $paragraph_revision->getTranslation($langcode);
$values = $paragraph_revision->get($field_name)->getValue();
$new_values = [];
foreach ($values as $key => $value) {
$tid = $value['target_id'];
if (in_array($tid, $tids_to_replace)) {
$tid = $target_tid;
}
$new_values[$key] = ['target_id' => $tid];
}
$paragraph_revision->set($field_name, $new_values);
$paragraph_revision->save();
}
}
$message = t(
'Paragraph @pid on revision @rid field @field updated with merged terms.',
[
'@pid' => $paragraph_id,
'@rid' => $revision_id,
'@field' => $field_name,
]
);
$context['message'] = $message;
$context['sandbox'][] = (string) $message;
$context['results'][] = $message;
$context['results']['vocabulary'] = $vocabulary;
}
/**
* Batch finished callback.
*/
public static function batchFinished($success, $results, $operations, $context) {
$messenger = \Drupal::messenger();
// Get the vocabulary from sandbox.
$vid = $results['vocabulary'] ?? NULL;
if ($success) {
unset($results['vocabulary']);
foreach ($results as $message) {
$messenger->addStatus($message);
}
}
else {
$messenger->addError(t('An error occurred during the term merge process.'));
}
// Redirect to your custom route.
$url = Url::fromRoute('taxonomy_overview.group_similar', [
'taxonomy_vocabulary' => $vid,
]);
return new RedirectResponse($url->toString());
}
/**
* Return all fields that reference the given vocabulary.
*
* @param string $vocabulary
* The vocabulary machine name.
*
* @return array
* Array of entity_type => bundle => fields.
*/
protected function getFieldsEntityReference(string $vocabulary): array {
$arrData = [];
$fieldStorageConfigs = $this->entityTypeManager->getStorage('field_storage_config')->loadMultiple();
foreach ($fieldStorageConfigs as $fieldStorageConfig) {
if (in_array($fieldStorageConfig->getType(), ['entity_reference', 'entity_reference_revisions'])) {
$settings = $fieldStorageConfig->getSettings();
if (($settings['target_type'] ?? '') === 'taxonomy_term') {
$fieldConfigs = $this->entityTypeManager->getStorage('field_config')
->loadByProperties(['field_name' => $fieldStorageConfig->getName()]);
foreach ($fieldConfigs as $fieldConfig) {
$handlerSettings = $fieldConfig->getSetting('handler_settings') ?? [];
if (isset($handlerSettings['target_bundles'][$vocabulary])) {
$entityType = $fieldConfig->getTargetEntityTypeId();
$bundle = $fieldConfig->getTargetBundle();
$arrData[$entityType][$bundle][] = $fieldConfig->getName();
}
}
}
}
}
return $arrData;
}
}
