Skip to content
Snippets Groups Projects
Commit 33f429d6 authored by Vladimir Roudakov's avatar Vladimir Roudakov Committed by Aurelian Zaha
Browse files

Issue #3220993: Option to merge terms (Term Merge integration)

parent 8147c45b
No related branches found
No related tags found
1 merge request!20Issue #3220993: Option to merge terms (Term Merge integration)
......@@ -88,18 +88,24 @@
},
source: source,
select: function (event, data) {
// We update the the form inputs on every checkbox state change as
// We update the form inputs on every checkbox state change as
// ajax events might require the latest state.
data.tree.generateFormElements(name + '[]');
// If no item is selected then disable delete button.
if (data.tree.getSelectedNodes().length < 1) {
document.getElementById("edit-delete").disabled = true;
document.getElementById("edit-merge").disabled = true;
} else {
let $deleteButton = document.getElementById("edit-delete");
let $deleteButton = document.getElementById("edit-delete"),
$mergeButton = document.getElementById("edit-merge");
$deleteButton.disabled = false;
$mergeButton.disabled = false;
if ($deleteButton.classList.contains('is-disabled')) {
$deleteButton.classList.remove('is-disabled');
}
if ($mergeButton.classList.contains('is-disabled')) {
$mergeButton.classList.remove('is-disabled');
}
}
},
focus: function (event, data) {
......
<?php
declare(strict_types = 1);
namespace Drupal\taxonomy_manager\Form;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Form\FormBuilderInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\TempStore\PrivateTempStoreFactory;
use Drupal\Core\Url;
use Drupal\taxonomy\VocabularyInterface;
use Drupal\term_merge\Form\MergeTerms;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Form for merging the selected terms into a target term.
*/
class MergeTermsForm extends MergeTerms {
/**
* The module handler service.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected ModuleHandlerInterface $moduleHandler;
/**
* Constructs a MergeTermsForm object.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
* The entity manager service.
* @param \Drupal\Core\TempStore\PrivateTempStoreFactory $tempStoreFactory
* The private temporary storage factory service.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $moduleHandler
* The module handler.
*/
public function __construct(EntityTypeManagerInterface $entityTypeManager, PrivateTempStoreFactory $tempStoreFactory, ModuleHandlerInterface $moduleHandler) {
parent::__construct($entityTypeManager, $tempStoreFactory);
$this->moduleHandler = $moduleHandler;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity_type.manager'),
$container->get('tempstore.private'),
$container->get('module_handler')
);
}
/**
* Form constructor.
*
* @param array $form
* Form render array.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* Form state.
* @param \Drupal\taxonomy\VocabularyInterface|null $taxonomy_vocabulary
* Selected terms.
* @param array $selected_terms
* Selected terms.
*
* @return array
* Return render array of form or nothing if term_merge module not exists.
*/
public function buildForm(array $form, FormStateInterface $form_state, ?VocabularyInterface $taxonomy_vocabulary = NULL, array $selected_terms = []): array {
$form = parent::buildForm($form, $form_state, $taxonomy_vocabulary);
// We still need a private storage.
$form_state->disableCache();
$this->vocabulary = $taxonomy_vocabulary;
$form['description'] = [
'#type' => 'markup',
'#markup' => $this->t('Select terms to merge'),
'#weight' => -1,
];
$form['terms']['#ajax'] = [
'url' => Url::fromRoute(
'taxonomy_manager.admin_vocabulary.merge_terms',
[
'taxonomy_vocabulary' => $taxonomy_vocabulary->id(),
],
),
'options' => [
'query' => [
FormBuilderInterface::AJAX_FORM_REQUEST => TRUE,
],
],
'event' => 'change',
'callback' => '::rebuildExistingTermsOptions',
'wrapper' => 'existing-terms',
'disable-refocus' => FALSE,
];
$selected_terms = !empty($selected_terms) ? $selected_terms : (
!empty($form_state->getUserInput()['terms']) ? $form_state->getUserInput()['terms'] : []
);
$form['terms']['#default_value'] = $selected_terms;
// Optionally add all child terms to the target term,
// that means that child terms will get new parent.
$form['add_child_terms_to_target'] = [
'#type' => 'checkbox',
'#title' => $this->t('Add child terms to target'),
'#description' => $this->t('Allow all sub children to be merged into the target term otherwise child terms gets a new parent.'),
];
$form['terms_to_synonym'] = [
'#type' => 'checkbox',
'#title' => $this->t('Add source term as synonym'),
'#description' => $this->t('Selected terms titles will get moved to the target term.'),
];
$form['description']['#markup'] = $this->t('Enter a new term or select an existing term to merge into.');
$form['new'] = [
'#type' => 'textfield',
'#title' => $this->t('New term'),
];
$form['existing'] = [
'#type' => 'entity_autocomplete',
'#title' => $this->t('Existing term'),
'#target_type' => 'taxonomy_term',
'#selection_handler' => 'default:filter_existing_terms',
'#selection_settings' => [
'target_bundles' => [$taxonomy_vocabulary->id()],
],
'#empty_option' => $this->t('Select an existing term'),
'#prefix' => '<div id="existing-terms">',
'#suffix' => '</div>',
];
if (!empty($selected_terms)) {
$form['existing']['#selection_settings']['filter'] = ['tid' => $selected_terms];
}
return $form;
}
/**
* Rebuild existing element with new data.
*
* @param array $form
* Form render array.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* Form state.
*
* @return array
* Returns existing terms element with new data.
*/
public function rebuildExistingTermsOptions(array $form, FormStateInterface $form_state): array {
return $form['existing'];
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
parent::validateForm($form, $form_state);
$new = !empty($form_state->getValue('new'));
$existing = !empty($form_state->getValue('existing'));
if ($new !== $existing) {
return;
}
$form_state->setErrorByName('existing', $this->t('You must either select an existing term or enter a new term.'));
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$term_store = $this->tempStoreFactory->get('term_merge');
$selectedTerms = $form_state->getValue('terms');
if (!empty($selectedTerms)) {
$term_store->set('terms', $selectedTerms);
}
if (!empty($form_state->getValue('new'))) {
$term_store->set('target', $form_state->getValue('new'));
}
if (!empty($form_state->getValue('existing'))) {
$term = $this->termStorage->load($form_state->getValue('existing'));
$term_store->set('target', $term);
}
if (!empty($form_state->getValue('add_child_terms_to_target')) || $form_state->hasValue('add_child_terms_to_target')) {
$term_store->set('merge_children', $form_state->getValue('add_child_terms_to_target'));
}
if (!empty($form_state->getValue('terms_to_synonym')) || $form_state->hasValue('terms_to_synonym')) {
$term_store->set('terms_to_synonym', $form_state->getValue('terms_to_synonym'));
}
$routeName = 'entity.taxonomy_vocabulary.merge_confirm';
$routeParameters['taxonomy_vocabulary'] = $this->vocabulary->id();
$form_state->setRedirect($routeName, $routeParameters);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'taxonomy_manager_merge_form';
}
}
......@@ -15,6 +15,7 @@ use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Path\CurrentPathStack;
use Drupal\Core\Render\Element;
use Drupal\Core\Routing\UrlGeneratorInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Url;
use Drupal\taxonomy\VocabularyInterface;
use Drupal\taxonomy_manager\TaxonomyManagerHelper;
......@@ -74,6 +75,13 @@ class TaxonomyManagerForm extends FormBase {
*/
protected $taxonomyManagerHelper;
/**
* The current user object.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $currentUser;
/**
* Constructs a new TaxonomyManagerForm object.
*
......@@ -91,8 +99,10 @@ class TaxonomyManagerForm extends FormBase {
* The url generator service.
* @param \Drupal\taxonomy_manager\TaxonomyManagerHelper $taxonomy_manager_helper
* The taxonomy messenger helper.
* @param \Drupal\Core\Session\AccountInterface $current_user
* The current user.
*/
public function __construct(ConfigFactoryInterface $config_factory, FormBuilderInterface $form_builder, EntityFormBuilderInterface $entity_form_builder, EntityTypeManagerInterface $entity_type_manager, CurrentPathStack $current_path, UrlGeneratorInterface $url_generator, TaxonomyManagerHelper $taxonomy_manager_helper) {
public function __construct(ConfigFactoryInterface $config_factory, FormBuilderInterface $form_builder, EntityFormBuilderInterface $entity_form_builder, EntityTypeManagerInterface $entity_type_manager, CurrentPathStack $current_path, UrlGeneratorInterface $url_generator, TaxonomyManagerHelper $taxonomy_manager_helper, AccountInterface $current_user) {
$this->configFactory = $config_factory;
$this->formBuilder = $form_builder;
$this->entityFormBuilder = $entity_form_builder;
......@@ -100,6 +110,7 @@ class TaxonomyManagerForm extends FormBase {
$this->currentPath = $current_path;
$this->urlGenerator = $url_generator;
$this->taxonomyManagerHelper = $taxonomy_manager_helper;
$this->currentUser = $current_user;
}
/**
......@@ -113,7 +124,8 @@ class TaxonomyManagerForm extends FormBase {
$container->get('entity_type.manager'),
$container->get('path.current'),
$container->get('url_generator'),
$container->get('taxonomy_manager.helper')
$container->get('taxonomy_manager.helper'),
$container->get('current_user')
);
}
......@@ -215,6 +227,19 @@ class TaxonomyManagerForm extends FormBase {
],
];
$form['toolbar']['merge'] = [
'#type' => 'submit',
'#name' => 'merge',
'#value' => $this->t('Merge terms'),
'#ajax' => [
'callback' => '::mergeTermsFormCallback',
],
'#attributes' => [
'disabled' => TRUE,
],
'#access' => $this->currentUser->hasPermission('edit terms in ' . $taxonomy_vocabulary->id()),
];
$form['toolbar']['miniexport'] = [
'#type' => 'submit',
'#name' => 'export',
......@@ -366,6 +391,13 @@ class TaxonomyManagerForm extends FormBase {
return $this->modalHelper($form_state, 'Drupal\taxonomy_manager\Form\ExportTermsMiniForm', 'taxonomy_manager.admin_vocabulary.exportlist', $this->t('Export terms (CSV)'));
}
/**
* AJAX callback handler for merge terms to a target term.
*/
public function mergeTermsFormCallback($form, FormStateInterface $form_state) {
return $this->modalHelper($form_state, MergeTermsForm::class, 'taxonomy_manager.admin_vocabulary.merge_terms', $this->t('Merge terms'));
}
/**
* AJAX callback handler for the term data form.
*/
......
<?php
declare(strict_types = 1);
namespace Drupal\taxonomy_manager\Plugin\EntityReferenceSelection;
use Drupal\taxonomy\Plugin\EntityReferenceSelection\TermSelection;
/**
* Provides an additional filtering to exclude source terms.
*
* @EntityReferenceSelection(
* id = "default:filter_existing_terms",
* label = @Translation("Taxonomy term limit selection"),
* entity_types = {"taxonomy_term"},
* group = "default",
* weight = 1
* )
*/
class FilterExistingTermsSelection extends TermSelection {
/**
* {@inheritdoc}
*/
protected function buildEntityQuery($match = NULL, $match_operator = 'CONTAINS') {
$query = parent::buildEntityQuery($match, $match_operator);
$handler_settings = $this->configuration;
if (!isset($handler_settings['filter'])) {
return $query;
}
$filter_settings = $handler_settings['filter'];
foreach ($filter_settings as $field_name => $value) {
$query->condition($field_name, $value, is_array($value) ? 'NOT IN' : '<>');
}
return $query;
}
}
......@@ -4,6 +4,7 @@ core_version_requirement: ^9.3 || ^10
dependencies:
- drupal:taxonomy
- jquery_ui:jquery_ui
- term_merge:term_merge
package: Taxonomy
type: module
configure: taxonomy_manager.settings
......@@ -76,4 +76,12 @@ taxonomy_manager.admin_vocabulary.move:
_form: '\Drupal\taxonomy_manager\Form\MoveTermsForm'
_title: 'Move terms'
requirements:
_taxonomy_manager_access_check: 'TRUE'
_entity_create_access: 'taxonomy_term:{taxonomy_vocabulary}'
taxonomy_manager.admin_vocabulary.merge_terms:
path: '/admin/structure/taxonomy_manager/voc/{taxonomy_vocabulary}/mergeterms'
defaults:
_form: '\Drupal\taxonomy_manager\Form\MergeTermsForm'
_title: 'Merge terms'
requirements:
_entity_create_access: 'taxonomy_term:{taxonomy_vocabulary}'
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment