acquia_commercemanager-8.x-1.122/modules/acm_promotion/src/AcmPromotionsManager.php

modules/acm_promotion/src/AcmPromotionsManager.php
<?php

namespace Drupal\acm_promotion;

use Drupal\acm\Connector\APIWrapperInterface;
use Drupal\acm_sku\Entity\SKU;
use Drupal\acm_sku\Entity\SKUInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\acm\I18nHelper;
use Drupal\Core\Database\Connection;
use Drupal\Core\Entity\EntityRepositoryInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\Core\Queue\QueueFactory;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\node\NodeInterface;

/**
 * Class AcmPromotionsManager.
 */
class AcmPromotionsManager {

  use StringTranslationTrait;

  /**
   * Language Manager service.
   *
   * @var \Drupal\Core\Language\LanguageManagerInterface
   */
  protected $languageManager;

  /**
   * Sku Entity storage.
   *
   * @var \Drupal\Core\Entity\EntityStorageInterface
   */
  protected $skuStorage;

  /**
   * Entity Repository service.
   *
   * @var \Drupal\Core\Entity\EntityRepositoryInterface
   */
  protected $entityRepository;

  /**
   * Node Entity Storage.
   *
   * @var \Drupal\node\NodeStorageInterface
   */
  private $nodeStorage;

  /**
   * The api wrapper.
   *
   * @var \Drupal\acm\Connector\APIWrapperInterface
   */
  protected $apiWrapper;

  /**
   * Queue Factory service.
   *
   * @var \Drupal\Core\Queue\QueueFactory
   */
  protected $queue;

  /**
   * Config factory service.
   *
   * @var \Drupal\Core\Config\ConfigFactoryInterface
   */
  protected $configFactory;

  /**
   * Database connection service.
   *
   * @var \Drupal\Core\Database\Connection
   */
  protected $connection;

  /**
   * Entity manager.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $entityTypeManager;

  /**
   * Module handler.
   *
   * @var \Drupal\Core\Extension\ModuleHandlerInterface
   */
  protected $moduleHandler;

  /**
   * Logger channel.
   *
   * @var \Drupal\Core\Logger\LoggerChannelInterface
   */
  protected $logger;

  /**
   * I18n Helper.
   *
   * @var \Drupal\acm\I18nHelper
   */
  private $i18nHelper;

  /**
   * Constructs a new AcmPromotionsManager object.
   *
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   EntityTypeManager object.
   * @param \Drupal\acm\Connector\APIWrapperInterface $api_wrapper
   *   The api wrapper.
   * @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger_factory
   *   LoggerFactory object.
   * @param \Drupal\Core\Language\LanguageManagerInterface $languageManager
   *   Language Manager service.
   * @param \Drupal\Core\Entity\EntityRepositoryInterface $entityRepository
   *   Entity Repository service.
   * @param \Drupal\Core\Queue\QueueFactory $queue
   *   Queue factory service.
   * @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
   *   Config factory service.
   * @param \Drupal\Core\Database\Connection $connection
   *   Database connection service.
   * @param \Drupal\acm\I18nHelper $i18n_helper
   *   I18nHelper object.
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
   *   Module handler.
   */
  public function __construct(EntityTypeManagerInterface $entity_type_manager,
                              APIWrapperInterface $api_wrapper,
                              LoggerChannelFactoryInterface $logger_factory,
                              LanguageManagerInterface $languageManager,
                              EntityRepositoryInterface $entityRepository,
                              QueueFactory $queue,
                              ConfigFactoryInterface $configFactory,
                              Connection $connection,
                              I18nHelper $i18n_helper,
                              ModuleHandlerInterface $module_handler) {
    $this->entityTypeManager = $entity_type_manager;
    $this->nodeStorage = $entity_type_manager->getStorage('node');
    $this->skuStorage = $entity_type_manager->getStorage('acm_sku');
    $this->apiWrapper = $api_wrapper;
    $this->logger = $logger_factory->get('acm_promotion');
    $this->languageManager = $languageManager;
    $this->entityRepository = $entityRepository;
    $this->queue = $queue;
    $this->configFactory = $configFactory;
    $this->connection = $connection;
    $this->i18nHelper = $i18n_helper;
    $this->moduleHandler = $module_handler;
  }

  /**
   * Synchronize promotions through the API.
   *
   * @param mixed $types
   *   The type of promotion to synchronize.
   */
  public function syncPromotions($types = ['category', 'cart']) {
    $types = is_array($types) ? $types : [$types];
    $ids = [];
    $fetched_promotions = [];

    foreach ($types as $type) {
      $promotions = $this->apiWrapper->getPromotions($type);

      foreach ($promotions as $key => $promotion) {
        // Add type to $promotion array, to be saved later.
        $promotion['promotion_type'] = $type;
        $fetched_promotions[] = $promotion;
        $ids[] = $promotion['rule_id'];
      }
    }

    if (!empty($fetched_promotions)) {
      $this->processPromotions($fetched_promotions);
    }

    // Delete promotions, which are not part of API response.
    if (!empty($ids)) {
      $this->deletePromotions($types, $ids);
    }
  }

  /**
   * Delete Promotion nodes, not part of API Response.
   *
   * @param array $types
   *   Promotions types to delete.
   * @param array $validIDs
   *   Valid Rule ID's from API.
   */
  protected function deletePromotions(array $types, array $validIDs = []) {
    $query = $this->nodeStorage->getQuery();
    $query->condition('type', 'acm_promotion');
    $query->condition('field_acm_promotion_type', $types, 'IN');

    if ($validIDs) {
      $query->condition('field_acm_promotion_rule_id', $validIDs, 'NOT IN');
    }

    $nids = $query->execute();

    foreach ($nids as $nid) {
      /* @var $node \Drupal\node\NodeInterface */
      $node = $this->nodeStorage->load($nid);
      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(),
        ]);
      }
    }
  }

  /**
   * Helper function to fetch promotion node given rule id.
   *
   * @param int $rule_id
   *   Rule id of the promotion to load.
   * @param string $rule_type
   *   Rule type of the promotion to load.
   *
   * @return \Drupal\node\NodeInterface|null
   *   Return node if a promotion found associated with the rule id else Null.
   */
  public function getPromotionByRuleId($rule_id, $rule_type) {
    $query = $this->nodeStorage->getQuery();
    $query->condition('type', 'acm_promotion');
    $query->condition('field_acm_promotion_rule_id', $rule_id);
    $query->condition('field_acm_promotion_type', $rule_type);
    $nids = $query->execute();

    if (empty($nids)) {
      return NULL;
    }
    else {
      // Log a message for admin to check errors in data.
      if (count($nids) > 1) {
        $this->logger->critical('Multiple nodes found for rule id @rule_id', ['@rule_id' => $rule_id]);
        return NULL;
      }

      // We only load the first node.
      /* @var $node \Drupal\node\NodeInterface */
      $node = $this->nodeStorage->load(reset($nids));
      $langcode = $this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_CONTENT)->getId();
      // Get the promotion with language fallback, if it did not have a
      // translation for $langcode.
      $node = $this->entityRepository->getTranslationFromContext($node, $langcode);
      return $node;
    }
  }

  /**
   * Helper function to get skus attached with a promotion.
   *
   * @param \Drupal\node\NodeInterface $promotion
   *   Promotion node for which we need to find skus.
   *
   * @return array
   *   Array of sku objects attached with the promotion.
   */
  public function getSkusForPromotion(NodeInterface $promotion) {
    $query = $this->connection->select('acm_sku__field_acm_sku_promotions', 'fasp');
    if ($this->entityTypeManager->getDefinition('acm_sku')->isTranslatable()) {
      $query->join('acm_sku_field_data', 'asfd', 'asfd.id = fasp.entity_id');
    }
    else {
      $query->join('acm_sku', 'asfd', 'asfd.id = fasp.entity_id');
    }
    $query->condition('fasp.field_acm_sku_promotions_target_id', $promotion->id());
    $query->fields('asfd', ['id', 'sku']);
    $query->distinct();
    $skus = $query->execute()->fetchAllKeyed(0, 1);

    return $skus;
  }

  /**
   * Helper function to create Promotion node from connector response.
   *
   * @param array $promotion
   *   Promotion response from Connector.
   * @param \Drupal\node\NodeInterface $promotion_node
   *   Promotion node in case we need to update Promotion.
   *
   * @return \Drupal\Core\Entity\EntityInterface
   *   Promotion node.
   *
   * @throws \Drupal\Core\TypedData\Exception\ReadOnlyException
   *   Exception thrown when trying to write or set ready-only data.
   * @throws \Exception
   *   Generic exception.
   */
  public function syncPromotionWithConnectorResponse(array $promotion, NodeInterface $promotion_node = NULL) {
    if (!$promotion_node) {
      $promotion_node = $this->nodeStorage->create([
        'type' => 'acm_promotion',
      ]);
    }

    $config = $this->configFactory->get('acm.connector');
    $text_format = $config->get('text_format') ?: 'rich_text';
    $promotions_labels = $promotion['labels'];
    $promotion_label_languages = [];
    $site_default_langcode = $this->languageManager->getDefaultLanguage()->getId();

    $promotions_label = reset($promotion['labels']);
    foreach ($promotions_labels as $promotion_label) {
      $promotion_label_language = $this->i18nHelper->getLangcodeFromStoreId($promotion_label['store_id']);

      // Magento might have stores that what we don't support.
      if (empty($promotion_label_language)) {
        continue;
      }

      $promotion_label_languages[$promotion_label_language] = $promotion_label['store_label'];
    }

    $promotion_node->get('title')->setValue($promotion['name']);

    // Set the description.
    $promotion_node->get('field_acm_promotion_description')->setValue(['value' => $promotion['description'], 'format' => $text_format]);

    // Set promotion rule_id.
    $promotion_node->get('field_acm_promotion_rule_id')->setValue($promotion['rule_id']);

    // Set the status.
    $promotion_node->setPublished((bool) $promotion['status']);

    // Store everything as serialized string in DB.
    $promotion_node->get('field_acm_promotion_data')->setValue(serialize($promotion));

    // Set the Promotion type.
    $promotion_node->get('field_acm_promotion_type')->setValue($promotion['promotion_type']);

    // Set the Promotion label.
    if (isset($promotion_label_languages[$site_default_langcode])) {
      $promotion_node->get('field_acm_promotion_label')->setValue($promotion_label_languages[$site_default_langcode]);
    }

    // Set promotion coupon code.
    $promotion_node->get('field_coupon_code')->setValue($promotion['coupon_code']);

    // Set the Promotion skus.
    $skus = [];
    foreach ($promotion['products'] as $product) {
      $sku = SKU::loadFromSku($product['product_sku']);
      if ($sku instanceof SKUInterface) {
        $skus[] = $product['product_sku'];
      }
    }
    $promotion_node->get('field_skus')->setValue($skus);

    // Set promotion type to percent & discount value depending on the promotion
    // being imported.
    if (($promotion['type'] === 'NO_COUPON') && isset($promotion['action']) && ($promotion['action'] === 'by_percent')) {
      $promotion_node->get('field_acm_promotion_disc_type')->setValue('percentage');
      $promotion_node->get('field_acm_promotion_discount')->setValue($promotion['discount']);
    }

    // Check promotion action type & store in Drupal.
    if (!empty($promotion['action'])) {
      $promotion_node->get('field_acm_promotion_action')->setValue($promotion['action']);
    }

    // Invoke the alter hook to allow modules to update the node from API data.
    $this->moduleHandler->alter('acm_promotion_promotion_node', $promotion_node, $promotion);

    $status = $promotion_node->save();
    // Create promotion translations based on the language codes available in
    // promotion labels.
    foreach ($promotion_label_languages as $langcode => $promotion_label_language) {
      if ($langcode !== $site_default_langcode) {
        if ($promotion_node->hasTranslation($langcode)) {
          $promotion_node->removeTranslation($langcode);
        }

        $node_translation = $promotion_node->addTranslation($langcode, $promotion_node->toArray());

        $node_translation->get('field_acm_promotion_label')->setValue($promotion_label_languages[$langcode]);
        $node_translation->save();
      }
    }

    if ($status) {
      return $promotion_node;
    }
    else {
      $this->logger->critical('Error occured while creating Promotion node for rule id: @rule_id.', ['@rule_id' => $promotion['rule_id']]);
      return NULL;
    }
  }

  /**
   * Helper function to process Promotions obtained from connector.
   *
   * @param array $promotions
   *   List of promotions to sync.
   *
   * @return array
   *   Messages around attach & detach queues.
   */
  public function processPromotions(array $promotions = []) {
    $output = [];
    $acm_promotion_attach_batch_size = $this->configFactory
      ->get('acm_promotion.settings')
      ->get('promotion_attach_batch_size');

    $promotion_detach_queue = $this->queue->get('acm_promotion_detach_queue');
    $promotion_attach_queue = $this->queue->get('acm_promotion_attach_queue');

    // Clear any outstanding items in queue before starting promotion import to
    // avoid duplicate queues.
    $promotion_detach_queue->deleteQueue();
    $promotion_attach_queue->deleteQueue();

    foreach ($promotions as $promotion) {
      $fetched_promotion_skus = [];
      $fetched_promotion_sku_attach_data = [];

      // Extract list of sku text attached with the promotion passed.
      $products = $promotion['products'];
      foreach ($products as $product) {
        if (!in_array($product['product_sku'], array_keys($fetched_promotion_skus))) {
          $fetched_promotion_skus[$product['product_sku']] = $product['product_sku'];

          $fetched_promotion_sku_attach_data[$product['product_sku']] = [
            'sku' => $product['product_sku'],
          ];

          if (($promotion['promotion_type'] === 'category') && isset($product['final_price'])) {
            $fetched_promotion_sku_attach_data[$product['product_sku']]['final_price'] = $product['final_price'];
          }
        }
      }

      // Check if this promotion exists in Drupal.
      // Assuming rule_id is unique across a promotion type.
      $promotion_node = $this->getPromotionByRuleId($promotion['rule_id'], $promotion['promotion_type']);

      // If promotion exists, we update the related skus & final price.
      if ($promotion_node) {
        $promotion_nid = $promotion_node->id();
        // Update promotion metadata.
        $this->syncPromotionWithConnectorResponse($promotion, $promotion_node);
        $attached_promotion_skus = $this->getSkusForPromotion($promotion_node);
        $detach_promotion_skus = [];

        // Get list of skus for which promotions should be detached.
        if (!empty($attached_promotion_skus)) {
          $detach_promotion_skus = array_diff($attached_promotion_skus, $fetched_promotion_skus);
        }

        // Create a queue for removing promotions from skus.
        if (!empty($detach_promotion_skus)) {
          $chunks = array_chunk($detach_promotion_skus, $acm_promotion_attach_batch_size);
          foreach ($chunks as $chunk) {
            $data['promotion'] = $promotion_nid;
            $data['skus'] = $chunk;
            $promotion_detach_queue->createItem($data);
            $output['detached_message'] = t(
              'Skus @skus queued up to detach promotion rule: @rule_id',
              [
                '@skus' => implode(',', $data['skus']),
                '@rule_id' => $promotion['rule_id'],
              ]
            );
          }
        }
      }
      else {
        // Create promotions node using Metadata from Promotions Object.
        $promotion_node = $this->syncPromotionWithConnectorResponse($promotion);
      }

      // Attach promotions to skus.
      if ($promotion_node && (!empty($fetched_promotion_sku_attach_data))) {
        $data['promotion'] = $promotion_node->id();
        $chunks = array_chunk($fetched_promotion_sku_attach_data, $acm_promotion_attach_batch_size);
        foreach ($chunks as $chunk) {
          $data['skus'] = $chunk;
          $promotion_attach_queue->createItem($data);
          $output['attached_message'] = t(
            'Skus @skus queued up to attach promotion rule: @rule_id',
            [
              '@skus' => implode(',', array_keys($fetched_promotion_sku_attach_data)),
              '@rule_id' => $promotion['rule_id'],
            ]
          );
        }
      }

      $this->logger->notice($this->t('Promotion `@node` having rule_id:@rule_id created or updated successfully with @attach items in attach queue and @detach items in detach queue.', [
        '@node' => $promotion_node->getTitle(),
        '@rule_id' => $promotion['rule_id'],
        '@attach' => !empty($fetched_promotion_sku_attach_data) ? count($fetched_promotion_sku_attach_data) : 0,
        '@detach' => !empty($detach_promotion_skus) ? count($detach_promotion_skus) : 0,
      ]));
    }

    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();
      }
    }
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc