diff --git a/src/Plugin/QueueWorker/UWServiceUpdateDepth.php b/src/Plugin/QueueWorker/UWServiceUpdateDepth.php new file mode 100644 index 0000000000000000000000000000000000000000..254dbcd696137e571395e6560d8f7e6c6a0dc9d3 --- /dev/null +++ b/src/Plugin/QueueWorker/UWServiceUpdateDepth.php @@ -0,0 +1,95 @@ +<?php + +namespace Drupal\uw_ct_service\Plugin\QueueWorker; + +use Drupal\Core\Database\Connection; +use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\Plugin\ContainerFactoryPluginInterface; +use Drupal\Core\Queue\QueueWorkerBase; +use Drupal\taxonomy\TermStorageInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Updates taxonomy term depth from uw_ct_service module. + * + * @QueueWorker( + * id = "uw_ct_service_queue_worker", + * title = @Translation("UW Taxonomy term depth update"), + * cron = {"time" = 300} + * ) + */ +class UWServiceUpdateDepth extends QueueWorkerBase implements ContainerFactoryPluginInterface { + + /** + * Database. + * + * @var \Drupal\Core\Database\Connection + */ + protected Connection $database; + + /** + * Entity type manager. + * + * @var \Drupal\Core\Entity\EntityTypeManagerInterface + */ + protected EntityTypeManagerInterface $entityTypeManager; + + /** + * Term storage. + * + * @var \Drupal\taxonomy\TermStorageInterface + */ + protected TermStorageInterface $termStorage; + + /** + * Constructs a new LocaleTranslation object. + * + * @param array $configuration + * A configuration array containing information about the plugin instance. + * @param string $plugin_id + * The plugin_id for the plugin instance. + * @param array $plugin_definition + * The plugin implementation definition. + * @param \Drupal\Core\Database\Connection $database + * Database. + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager + * Entity type manager. + */ + public function __construct(array $configuration, $plugin_id, array $plugin_definition, Connection $database, EntityTypeManagerInterface $entity_type_manager) { + parent::__construct($configuration, $plugin_id, $plugin_definition); + + $this->database = $database; + $this->entityTypeManager = $entity_type_manager; + $this->termStorage = $this->entityTypeManager->getStorage('taxonomy_term'); + } + + /** + * {@inheritDoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('database'), + $container->get('entity_type.manager') + ); + } + + /** + * {@inheritDoc} + */ + public function processItem($data) { + if (($vid = $data['vid']) && (is_array($data['tids']) && $tids = $data['tids'])) { + + foreach ($tids as $tid) { + $depth = count($this->termStorage->loadAllParents($tid)); + + $this->database->update('taxonomy_term_field_data')->fields([ + 'depth_level' => $depth, + ])->condition('vid', $vid)->condition('tid', $tid)->execute(); + } + } + } + +} diff --git a/src/QueueManager/UWQueueManager.php b/src/QueueManager/UWQueueManager.php new file mode 100644 index 0000000000000000000000000000000000000000..45d46891ef467e1172d875c3f9b043ca4f2e3dd8 --- /dev/null +++ b/src/QueueManager/UWQueueManager.php @@ -0,0 +1,148 @@ +<?php + +namespace Drupal\uw_ct_service\QueueManager; + +use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\Queue\QueueFactory; +use Drupal\Core\Queue\QueueInterface; +use Drupal\Core\Queue\QueueWorkerManager; +use Drupal\Core\Queue\SuspendQueueException; +use Drupal\taxonomy\VocabularyInterface; + +/** + * UW Queue Manager class. + */ +class UWQueueManager { + + public const QUEUE_ID = 'uw_ct_service_queue'; + + public const BATCH_SIZE = 10; + + /** + * Vocabulary. + * + * @var \Drupal\taxonomy\VocabularyInterface|null + */ + protected ?VocabularyInterface $vocabulary = NULL; + + /** + * Vocabulary id. + * + * @var string|null + */ + protected ?string $vid = NULL; + /** + * Queue. + * + * @var \Drupal\Core\Queue\QueueInterface|null + */ + protected ?QueueInterface $queue = NULL; + + /** + * Default constructor. + * + * @param \Drupal\Core\Queue\QueueFactory $queue_factory + * Queue. + * @param \Drupal\Core\Queue\QueueWorkerManager $queue_worker_manager + * Queue worker manager. + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager + * Entity type manager service. + */ + public function __construct( + QueueFactory $queue_factory, + protected QueueWorkerManager $queue_worker_manager, + protected EntityTypeManagerInterface $entity_type_manager, + ) { + $this->queue = $queue_factory->get(self::QUEUE_ID); + $this->vocabulary = NULL; + $this->vid = NULL; + } + + /** + * Deletes queue. + */ + public function deleteQueue(): void { + $this->queue->deleteQueue(); + } + + /** + * Creates queue. + */ + public function createQueue(): void { + $this->deleteQueue(); + + /** @var \Drupal\taxonomy\TermStorageInterface $term_storage */ + $term_storage = $this->entity_type_manager->getStorage('taxonomy_term'); + + $all_terms = $term_storage->loadByProperties([ + 'vid' => $this->vid, + ]); + + $tids = []; + foreach ($all_terms as $term) { + if (count($tids) >= self::BATCH_SIZE) { + $this->queue->createItem([ + 'vid' => $this->vid, + 'tids' => $tids, + ]); + + $tids = []; + } + + $tids[] = $term->id(); + } + + // Last batch which may be less than the limit. + $this->queue->createItem([ + 'vid' => $this->vid, + 'tids' => $tids, + ]); + + } + + /** + * Process queued items. + */ + public function processQueue(): void { + if ($this->queue->numberOfItems() === 0) { + $this->createQueue(); + } + + $worker = $this->queue_worker_manager->createInstance('uw_ct_service_queue_worker'); + + while ($item = $this->queue->claimItem()) { + try { + $worker->processItem($item->data); + $this->queue->deleteItem($item); + } + catch (SuspendQueueException $e) { + $this->queue->releaseItem($item); + break; + } + catch (\Exception $e) { + $this->queue->deleteItem($item); + } + } + } + + /** + * Set vocabulary. + * + * @param \Drupal\taxonomy\VocabularyInterface $vocabulary + * Vocabulary. + */ + public function setVocabulary(VocabularyInterface $vocabulary): self { + // Check if vocabulary has nesting allowed. + // When max_depth is set to zero, there isn't any save handler on the form. + // So maybe checking this out is not necessary. + $max_depth = $vocabulary?->getThirdPartySetting('taxonomy_max_depth', 'max_depth'); + + if ($max_depth > 1) { + $this->vocabulary = $vocabulary; + $this->vid = $vocabulary->id(); + } + + return $this; + } + +} diff --git a/uw_ct_service.module b/uw_ct_service.module index 82e2e0be425efa8515a53140bd55686d0356d4d5..7101cefa847b4ed412c5545fcf2503e4aacb6ad5 100644 --- a/uw_ct_service.module +++ b/uw_ct_service.module @@ -5,8 +5,8 @@ * Provides configuration and settings for services. */ -use Drupal\Core\Link; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Link; use Drupal\Core\Url; use Drupal\taxonomy\Entity\Term; use Drupal\views\Plugin\views\query\QueryPluginBase; @@ -55,7 +55,7 @@ function _uw_ct_service_load_locations(): array { $locations = []; foreach ($locations_file as $file_location) { // Parse name into code and name. - list($location_code, $location_name) = explode('-', $file_location['name'], 2); + [$location_code, $location_name] = explode('-', $file_location['name'], 2); $location_code = trim($location_code); // Create array for this location. $location = [ @@ -238,7 +238,7 @@ function uw_ct_service_preprocess_views_view(&$variables) { */ function uw_ct_service_views_query_alter( ViewExecutable $view, - QueryPluginBase $query + QueryPluginBase $query, ) { if ($view->id() === 'uw_view_services') { @@ -483,3 +483,40 @@ function _uw_ct_service_get_child_terms_with_grandchild(): array { } return $tids_for_child_with_grandchild; } + +/** + * Implements hook_form_FORM_ID_alter(). + */ +function uw_ct_service_form_taxonomy_overview_terms_alter(array &$form, FormStateInterface $form_state, $form_id) { + + // Add the submission handler to update depths of vocabularies. + $form['#submit'][] = '_uw_ct_service_form_taxonomy_overview_terms_submit'; +} + +/** + * Additional form submit handler. + * + * @param array $form + * Term overview form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * Form state. + */ +function _uw_ct_service_form_taxonomy_overview_terms_submit(array $form, FormStateInterface $form_state) { + + /** @var \Drupal\taxonomy\VocabularyInterface $vocabulary */ + $vocabulary = \Drupal::routeMatch()->getParameter('taxonomy_vocabulary'); + + if (!$vocabulary) { + return; + } + + // Once issue with calculating depth_level is fixed in a contrib module + // taxonomy_term_depth this should be removed. What this code does is + // check if vocabulary has set depth limit, and recalculate depth for + // each term and the used database query to update depth_level to the + // correct value. Avoiding entity update, so that no update hook is fired. + // see: https://uwaterloo.atlassian.net/browse/ISTWCMS-7142 + /** @var \Drupal\uw_ct_service\QueueManager\UWQueueManager $qm */ + $qm = \Drupal::service('uw_ct_service.queue_manager'); + $qm->setVocabulary($vocabulary)->processQueue(); +} diff --git a/uw_ct_service.services.yml b/uw_ct_service.services.yml new file mode 100644 index 0000000000000000000000000000000000000000..2f46b4c074a9e74198904f33c3a0e1c22bba587f --- /dev/null +++ b/uw_ct_service.services.yml @@ -0,0 +1,4 @@ +services: + uw_ct_service.queue_manager: + class: Drupal\uw_ct_service\QueueManager\UWQueueManager + arguments: ['@queue', '@plugin.manager.queue_worker', '@entity_type.manager']