diff --git a/modules/acm_promotion/acm_promotion.module b/modules/acm_promotion/acm_promotion.module index 380aa07..c22c9e5 100644 --- a/modules/acm_promotion/acm_promotion.module +++ b/modules/acm_promotion/acm_promotion.module @@ -8,6 +8,7 @@ * connector. */ +use Drupal\acm_sku\Entity\SKU; use Drupal\Core\Entity\Display\EntityViewDisplayInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Url; @@ -114,6 +115,27 @@ function acm_promotion_node_view(array &$build, Node $node, EntityViewDisplayInt ]; } +/** + * Implements hook_ENTITY_TYPE_delete(). + */ +function acm_promotion_node_delete(NodeInterface $node) { + if ($node->bundle() == 'acm_promotion') { + /* @var \Drupal\acm_promotion\AcmPromotionsManager $promotion_manager */ + $promotion_manager = \Drupal::service('acm_promotion.promotions_manager'); + // Get SKUs attached to the promotion node. + $attached_promotion_skus = $promotion_manager->getSkusForPromotion($node); + + if (!empty($attached_promotion_skus)) { + foreach ($attached_promotion_skus as $sku) { + if (($sku_entity = SKU::loadFromSku($sku)) && + ($sku_entity instanceof SKU)) { + $promotion_manager->removeOrphanPromotionFromSku($sku_entity, $node->id()); + } + } + } + } +} + /** * Implements hook_form_alter(). */ diff --git a/modules/acm_promotion/src/AcmPromotionQueueBase.php b/modules/acm_promotion/src/AcmPromotionQueueBase.php index 3ce5f40..0ed1395 100644 --- a/modules/acm_promotion/src/AcmPromotionQueueBase.php +++ b/modules/acm_promotion/src/AcmPromotionQueueBase.php @@ -23,6 +23,13 @@ abstract class AcmPromotionQueueBase extends QueueWorkerBase implements Containe */ protected $ingestApiWrapper; + /** + * Promotion manager. + * + * @var \Drupal\acm_promotion\AcqPromotionsManager + */ + protected $promotionManager; + /** * LoggerChannelInterface object. * @@ -48,6 +55,8 @@ abstract class AcmPromotionQueueBase extends QueueWorkerBase implements Containe * Plugin definition. * @param \Drupal\acm\Connector\IngestAPIWrapper $ingestApiWrapper * IngestAPIWrapper Service object. + * @param \Drupal\acm_promotion\AcqPromotionsManager $promotionManager + * Promotion manager. * @param \Drupal\Core\Logger\LoggerChannelFactory $loggerFactory * Logger service. * @param \Drupal\Core\Cache\CacheTagsInvalidatorInterface $tag_invalidate @@ -57,10 +66,12 @@ public function __construct(array $configuration, $plugin_id, $plugin_definition, IngestAPIWrapper $ingestApiWrapper, + AcqPromotionsManager $promotionManager, LoggerChannelFactory $loggerFactory, CacheTagsInvalidatorInterface $tag_invalidate) { parent::__construct($configuration, $plugin_id, $plugin_definition); $this->ingestApiWrapper = $ingestApiWrapper; + $this->promotionManager = $promotionManager; $this->logger = $loggerFactory->get('acm_sku'); $this->tagInvalidate = $tag_invalidate; } @@ -86,6 +97,7 @@ public static function create(ContainerInterface $container, array $configuratio $plugin_id, $plugin_definition, $container->get('acm.ingest_api'), + $container->get('acm_promotion.promotions_manager'), $container->get('logger.factory'), $container->get('cache_tags.invalidator') ); diff --git a/modules/acm_promotion/src/AcmPromotionsManager.php b/modules/acm_promotion/src/AcmPromotionsManager.php index 843192e..97c6384 100644 --- a/modules/acm_promotion/src/AcmPromotionsManager.php +++ b/modules/acm_promotion/src/AcmPromotionsManager.php @@ -153,19 +153,19 @@ public function syncPromotions($types = ['category', 'cart']) { $this->processPromotions($fetched_promotions); } - // Unpublish promotions, which are not part of API response. + // Delete promotions, which are not part of API response. if (!empty($ids)) { $this->unpublishPromotions($ids); } } /** - * Unpublish Promotion nodes, not part of API Response. + * Delete Promotion nodes, not part of API Response. * * @param array $validIDs * Valid Rule ID's from API. */ - protected function unpublishPromotions(array $validIDs = []) { + protected function deletePromotions(array $validIDs = []) { $query = $this->nodeStorage->getQuery(); $query->condition('type', 'acm_promotion'); @@ -175,37 +175,15 @@ protected function unpublishPromotions(array $validIDs = []) { $nids = $query->execute(); - $acm_promotion_attach_batch_size = $this->configFactory - ->get('acm_promotion.settings') - ->get('promotion_attach_batch_size'); - foreach ($nids as $nid) { /* @var $node \Drupal\node\NodeInterface */ $node = $this->nodeStorage->load($nid); - $node->setPublished(NodeInterface::NOT_PUBLISHED); - $node->save(); - - // Unpublish all translations of the current node as well. - $translation_languages = $node->getTranslationLanguages(); - - foreach ($translation_languages as $langcode => $lang_obj) { - $node_translation = $node->getTranslation($langcode); - $node_translation->setPublished(Node::NOT_PUBLISHED); - $node_translation->save(); - } - - // Detach promotion from all skus. - $attached_promotion_skus = $this->getSkusForPromotion($node); - - if (!empty($attached_promotion_skus)) { - $chunks = array_chunk($attached_promotion_skus, $acm_promotion_attach_batch_size); - foreach ($chunks as $chunk) { - $data['skus'] = $chunk; - $data['promotion'] = $nid; - $data['promotion_type'] = $node->get('field_acm_promotion_type')->getString(); - $acm_promotion_detach_queue = $this->queue->get('acm_promotion_detach_queue'); - $acm_promotion_detach_queue->createItem($data); - } + if ($node instanceof NodeInterface) { + $node->delete(); + $this->logger->notice('Deleted orphan promotion node @promotion having rule_id:@rule_id.', [ + '@promotion' => $node->label(), + '@rule_id' => $node->get('field_acm_promotion_rule_id')->first()->getString(), + ]); } } } @@ -493,4 +471,31 @@ public function processPromotions(array $promotions = []) { return $output; } + /** + * Removes the given promotion from SKU entity. + * + * @param \Drupal\acm_sku\Entity\SKU $sku + * SKU Entity. + * @param int $nid + * Promotion node id. + */ + public function removeOrphanPromotionFromSku(SKU $sku, int $nid) { + $promotion_detach_item[] = ['target_id' => $nid]; + $sku_promotions = $sku->get('field_acm_sku_promotions')->getValue(); + $sku_promotions = array_udiff($sku_promotions, $promotion_detach_item, function ($array1, $array2) { + return $array1['target_id'] - $array2['target_id']; + }); + $sku->get('field_acm_sku_promotions')->setValue($sku_promotions); + $sku->save(); + // Update Sku Translations. + $translation_languages = $sku->getTranslationLanguages(TRUE); + if (!empty($translation_languages)) { + foreach ($translation_languages as $langcode => $language) { + $sku_entity_translation = $sku->getTranslation($langcode); + $sku_entity_translation->get('field_acm_sku_promotions')->setValue($sku_promotions); + $sku_entity_translation->save(); + } + } + } + } diff --git a/modules/acm_promotion/src/Plugin/QueueWorker/AcmPromotionDetachQueue.php b/modules/acm_promotion/src/Plugin/QueueWorker/AcmPromotionDetachQueue.php index 087bb03..e2139f2 100644 --- a/modules/acm_promotion/src/Plugin/QueueWorker/AcmPromotionDetachQueue.php +++ b/modules/acm_promotion/src/Plugin/QueueWorker/AcmPromotionDetachQueue.php @@ -44,34 +44,11 @@ class AcmPromotionDetachQueue extends AcmPromotionQueueBase { public function processItem($data) { $skus = $data['skus']; $promotion_nid = $data['promotion']; - $promotion_type = $data['promotion_type']; - $promotion_detach_item[] = ['target_id' => $promotion_nid]; - $invalidate_tags = ['node:' . $promotion_nid]; foreach ($skus as $sku) { // Check if the SKU added to queue is available before processing. if (($sku_entity = SKU::loadFromSku($sku)) && ($sku_entity instanceof SKU)) { - $sku_promotions = $sku_entity->get('field_acm_sku_promotions')->getValue(); - - $sku_promotions = array_udiff($sku_promotions, $promotion_detach_item, function ($array1, $array2) { - return $array1['target_id'] - $array2['target_id']; - }); - - $sku_entity->get('field_acm_sku_promotions')->setValue($sku_promotions); - $sku_entity->save(); - - // Update Sku Translations. - $translation_languages = $sku_entity->getTranslationLanguages(TRUE); - - if (!empty($translation_languages)) { - foreach ($translation_languages as $langcode => $language) { - $sku_entity_translation = $sku_entity->getTranslation($langcode); - $sku_entity_translation->get('field_acm_sku_promotions')->setValue($sku_promotions); - $sku_entity_translation->save(); - } - } - - $invalidate_tags[] = 'acm_sku:' . $sku_entity->id(); + $this->promotionManager->removeOrphanPromotionFromSku($sku_entity, $promotion_nid); } else { $unprocessed_skus[] = $sku; diff --git a/modules/acm_sku/src/AcquiaCommerce/SKUPluginBase.php b/modules/acm_sku/src/AcquiaCommerce/SKUPluginBase.php index 6d9a38f..42c402d 100644 --- a/modules/acm_sku/src/AcquiaCommerce/SKUPluginBase.php +++ b/modules/acm_sku/src/AcquiaCommerce/SKUPluginBase.php @@ -199,18 +199,52 @@ public function cartName(SKU $sku, array $cart, $asString = FALSE) { * @throws \Exception */ public function getParentSku(SKU $sku) { + $static = &drupal_static(__FUNCTION__, []); + + $langcode = $sku->language()->getId(); + $sku_string = $sku->getSku(); + + if (isset($static[$langcode], $static[$langcode][$sku_string])) { + return $static[$langcode][$sku_string]; + } + + // Initialise with empty value. + $static[$langcode][$sku_string] = NULL; + $query = $this->connection->select('acm_sku_field_data', 'acm_sku'); $query->addField('acm_sku', 'sku'); $query->join('acm_sku__field_configured_skus', 'child_sku', 'acm_sku.id = child_sku.entity_id'); - $query->condition('child_sku.field_configured_skus_value', $sku->getSku()); + $query->condition('child_sku.field_configured_skus_value', $sku_string); - $parent_sku = $query->execute()->fetchField(); + $parent_skus = array_keys($query->execute()->fetchAllAssoc('sku')); - if (empty($parent_sku)) { + if (empty($parent_skus)) { return NULL; } - return SKU::loadFromSku($parent_sku, $sku->language()->getId()); + if (count($parent_skus) > 1) { + \Drupal::logger('acm_sku')->warning( + 'Multiple parents found for SKU: @sku, parents: @parents', + [ + '@parents' => implode(',', $parent_skus), + '@sku' => $sku_string, + ] + ); + } + + foreach ($parent_skus as $parent_sku) { + $parent = SKU::loadFromSku($parent_sku, $langcode); + if ($parent instanceof SKU) { + $node = $this->getDisplayNode($parent, FALSE, FALSE); + + if ($node instanceof Node) { + $static[$langcode][$sku_string] = $parent; + break; + } + } + } + + return $static[$langcode][$sku_string]; } /** @@ -239,6 +273,7 @@ public function getDisplayNode(SKUInterface $sku, $check_parent = TRUE, $create_ $query = \Drupal::entityQuery('node') ->condition('type', $product_node_type) ->condition($sku_field_name, $sku->getSKU()) + ->addTag('get_display_node_for_sku') ->range(0, 1); $result = $query->execute(); diff --git a/modules/acm_sku/src/Plugin/AcquiaCommerce/SKUType/Configurable.php b/modules/acm_sku/src/Plugin/AcquiaCommerce/SKUType/Configurable.php index 83acad8..a951da4 100644 --- a/modules/acm_sku/src/Plugin/AcquiaCommerce/SKUType/Configurable.php +++ b/modules/acm_sku/src/Plugin/AcquiaCommerce/SKUType/Configurable.php @@ -635,7 +635,7 @@ public function cartName(SKU $sku, array $cart, $asString = FALSE) { ); $label_parts = []; - foreach ($configurables as $configurable) { + foreach ($configurables ?? [] as $configurable) { $key = $configurable['code']; $attribute_value = $this->getAttributeValue($sku, $key); $label = $configurable['label']; diff --git a/modules/acm_sku/src/Plugin/Field/FieldFormatter/SKUFieldFormatter.php b/modules/acm_sku/src/Plugin/Field/FieldFormatter/SKUFieldFormatter.php index 47805ec..583b64f 100644 --- a/modules/acm_sku/src/Plugin/Field/FieldFormatter/SKUFieldFormatter.php +++ b/modules/acm_sku/src/Plugin/Field/FieldFormatter/SKUFieldFormatter.php @@ -96,33 +96,7 @@ public function viewElements(FieldItemListInterface $items, $langcode) { * The textual output generated. */ protected function viewValue(FieldItemInterface $item) { - $sku = $item->value; - - $query = \Drupal::entityQuery('acm_sku') - ->condition('sku', $sku); - - $ids = $query->execute(); - - if (empty($ids)) { - \Drupal::logger('acm_sku') - ->notice(t('No SKU found for @sku.', ['@sku' => $sku])); - return ''; - } - elseif (count($ids) > 1) { - \Drupal::logger('acm_sku') - ->notice(t('More than one SKU found for @sku. Using first.', ['@sku' => $sku])); - } - - $sku_id = reset($ids); - $sku = SKU::load($sku_id); - - $langcode = \Drupal::languageManager()->getCurrentLanguage()->getId(); - - if ($sku->hasTranslation($langcode)) { - $sku = $sku->getTranslation($langcode); - } - - return $sku; + return SKU::loadFromSku($item->value); } } diff --git a/modules/acm_sku/src/Plugin/facets/processor/HideTaxonomyNotInMenu.php b/modules/acm_sku/src/Plugin/facets/processor/HideTaxonomyNotInMenu.php index 60dc9a4..f235a9a 100644 --- a/modules/acm_sku/src/Plugin/facets/processor/HideTaxonomyNotInMenu.php +++ b/modules/acm_sku/src/Plugin/facets/processor/HideTaxonomyNotInMenu.php @@ -1,6 +1,6 @@ languageManager->getCurrentLanguage()->getId(); + $ids = []; /** @var \Drupal\facets\Result\ResultInterface $result */ @@ -133,6 +135,10 @@ public function build(FacetInterface $facet, array $results) { foreach ($results as $i => $result) { $term = isset($entities[$ids[$i]]) ? $entities[$ids[$i]] : NULL; + if (($term instanceof TermInterface) && $term->hasTranslation($langcode)) { + $term = $term->getTranslation($langcode); + } + // Display the term if included in menu and status is enabled. if (($term instanceof TermInterface) && ($term->bundle() == $this->connectorConfig->get('category_vid')) && diff --git a/modules/acm_sku/src/ProductManager.php b/modules/acm_sku/src/ProductManager.php index 7e85472..a929171 100644 --- a/modules/acm_sku/src/ProductManager.php +++ b/modules/acm_sku/src/ProductManager.php @@ -748,10 +748,25 @@ public function processSku(array $product, $langcode) { if ($node = $plugin->getDisplayNode($sku, FALSE, FALSE)) { // Delete the node if it is linked to this SKU only. $node->delete(); + $this->logger->info('Deleted node for SKU @sku for @langcode.', [ + '@sku' => $sku->getSku(), + '@langcode' => $langcode, + ]); + } + else { + $this->logger->info('Node for SKU @sku for @langcode not found for deletion.', [ + '@sku' => $sku->getSku(), + '@langcode' => $langcode, + ]); } } catch (\Exception $e) { // Not doing anything, we might not have node for the sku. + $this->logger->info('Error while deleting node for the SKU @sku for @langcode. Message:@message', [ + '@sku' => $sku->getSku(), + '@langcode' => $langcode, + '@message' => $e->getMessage(), + ]); } // Delete the SKU. diff --git a/modules/acm_sku/src/ProductOptionsManager.php b/modules/acm_sku/src/ProductOptionsManager.php index ae1a3a3..438db8c 100644 --- a/modules/acm_sku/src/ProductOptionsManager.php +++ b/modules/acm_sku/src/ProductOptionsManager.php @@ -128,6 +128,19 @@ public function loadProductOptionByOptionId($attribute_code, $option_id, $langco * Taxonomy term weight == attribute option sort order. */ protected function createProductOption($langcode, $option_id, $option_value, $attribute_id, $attribute_code, $weight) { + if (strlen($option_value) == 0) { + $this->logger->warning('Got empty value while syncing production options: @data', [ + '@data' => json_encode([ + 'langcode' => $langcode, + 'option_id' => $option_id, + 'attribute_id' => $attribute_id, + 'attribute_code' => $attribute_code, + ]), + ]); + + return NULL; + } + // Update the term if already available. if ($term = $this->loadProductOptionByOptionId($attribute_code, $option_id, NULL, FALSE)) { $save_term = FALSE;