acquia_commercemanager-8.x-1.122/modules/acm_sku/src/ProductOptionsManager.php
modules/acm_sku/src/ProductOptionsManager.php
<?php namespace Drupal\acm_sku; use Drupal\acm\Connector\APIWrapperInterface; use Drupal\acm\I18nHelper; use Drupal\Core\Database\Connection; use Drupal\Core\Entity\EntityStorageException; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Logger\LoggerChannelFactoryInterface; /** * Provides a service for product options data to taxonomy synchronization. * * @ingroup acm_sku */ class ProductOptionsManager implements ProductOptionsManagerInterface { /** * Connector Agent Category Data API Endpoint. * * @const CONDUCTOR_API_CATEGORY */ const PRODUCT_OPTIONS_VOCABULARY = 'sku_product_option'; /** * Taxonomy Term Entity Storage. * * @var \Drupal\taxonomy\TermStorageInterface */ private $termStorage; /** * API Wrapper object. * * @var \Drupal\acm\Connector\APIWrapperInterface */ private $apiWrapper; /** * Instance of I18nHelper service. * * @var \Drupal\acm\I18nHelper */ private $i18nHelper; /** * Instance of LoggerChannelInterface. * * @var \Drupal\Core\Logger\LoggerChannelInterface */ private $logger; /** * Database connection service. * * @var \Drupal\Core\Database\Connection */ private $connection; /** * Constructor. * * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager * EntityTypeManager object. * @param \Drupal\acm\Connector\APIWrapperInterface $api_wrapper * ApiWrapper object. * @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger_factory * LoggerFactory object. * @param \Drupal\acm\I18nHelper $i18nHelper * Instance of I18nHelper service. * @param \Drupal\Core\Database\Connection $connection * Database connection service. * * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException */ public function __construct(EntityTypeManagerInterface $entity_type_manager, APIWrapperInterface $api_wrapper, LoggerChannelFactoryInterface $logger_factory, I18nHelper $i18nHelper, Connection $connection) { $this->termStorage = $entity_type_manager->getStorage('taxonomy_term'); $this->apiWrapper = $api_wrapper; $this->logger = $logger_factory->get('acm_sku'); $this->i18nHelper = $i18nHelper; $this->connection = $connection; } /** * {@inheritdoc} */ public function loadProductOptionByOptionId($attribute_code, $option_id, $langcode, $log_error = TRUE) { $query = $this->termStorage->getQuery(); $query->condition('field_sku_option_id', $option_id); $query->condition('field_sku_attribute_code', $attribute_code); $query->condition('vid', self::PRODUCT_OPTIONS_VOCABULARY); $tids = $query->execute(); // We won't log no term found error during sync. if (count($tids) === 0) { if ($log_error) { $this->logger->error('No term found for option_id: @option_id having attribute_code @attribute_code.', [ '@option_id' => $option_id, '@attribute_code' => $attribute_code, ]); } return NULL; } elseif (count($tids) > 1) { $this->logger->critical('Multiple terms found for option_id: @option_id having attribute_code @attribute_code.', [ '@option_id' => $option_id, '@attribute_code' => $attribute_code, ]); } // We use the first term and continue even if we have multiple terms. $tid = array_shift($tids); /** @var \Drupal\taxonomy\Entity\Term $term */ $term = $this->termStorage->load($tid); if ($langcode && $term->hasTranslation($langcode)) { $term = $term->getTranslation($langcode); } return $term; } /** * {@inheritdoc} */ public function createProductOptionWrapper($langcode, $option_id, $option_value, $attribute_id, $attribute_code, $weight) { return $this->createProductOption(); } /** * Create product option if not available or update the name. * * @param string $langcode * Lang code. * @param int $option_id * Option id. * @param string $option_value * Value (term name). * @param int $attribute_id * Attribute id. * @param string $attribute_code * Attribute code. * @param int $weight * Taxonomy term weight == attribute option sort order. * * @return \Drupal\taxonomy\Entity\Term|null * Term object or null. */ 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; // Save term even if weight changes. if ($term->getWeight() != $weight) { $save_term = TRUE; } if ($term->hasTranslation($langcode)) { $term = $term->getTranslation($langcode); // We won't allow editing name here, if required it must be done from // Magento. if ($term->getName() != $option_value) { $term->setName($option_value); $save_term = TRUE; } } else { $term = $term->addTranslation($langcode, []); $term->setName($option_value); $save_term = TRUE; } if ($save_term) { $term->setWeight($weight); $term->save(); } } else { $term = $this->termStorage->create([ 'vid' => self::PRODUCT_OPTIONS_VOCABULARY, 'langcode' => $langcode, 'name' => $option_value, 'weight' => $weight, 'field_sku_option_id' => $option_id, 'field_sku_attribute_id' => $attribute_id, 'field_sku_attribute_code' => $attribute_code, ]); try { $term->save(); } catch (EntityStorageException $exception) { $this->logger->critical('Product option "@option" wasn\'t saved. Try again later please.', ['@option' => $option_value]); } } return $term; } /** * {@inheritdoc} */ public function synchronizeProductOptions() { $options_available = []; foreach ($this->i18nHelper->getStoreLanguageMapping() as $langcode => $store_id) { $this->apiWrapper->updateStoreContext($store_id); $option_sets = $this->apiWrapper->getProductOptions(); $weight = 0; foreach ($option_sets as $options) { foreach ($options['options'] as $key => $value) { $this->createProductOption($langcode, $key, $value, $options['attribute_id'], $options['attribute_code'], $weight++); $options_available[$options['attribute_code']][$options['attribute_id']] = $options['attribute_id']; } } } if ($options_available) { $this->deleteUnavailableOptions($options_available); } } /** * {@inheritdoc} */ public function deleteUnavailableOptions(array $synced_options) { // Cleanup queries can be done only for one language. $query = $this->connection->select('taxonomy_term__field_sku_attribute_code', 'ttfsac'); $query->addExpression('count(entity_id)', 'cnt'); $query->condition('field_sku_attribute_code_value', array_keys($synced_options), 'IN'); $result = $query->execute()->fetchAllKeyed(0, 0); $options_in_db = reset($result); $synced_option_ids = []; foreach ($synced_options as $attribute_code => $options) { $synced_option_ids = array_merge($synced_option_ids, $options); } // Do nothing if count of option ids synced and in DB match. if (count($synced_option_ids) === $options_in_db) { return; } $query = $this->termStorage->getQuery(); $query->condition('field_sku_option_id', $synced_option_ids, 'NOT IN'); $query->condition('field_sku_attribute_code', array_keys($synced_options), 'IN'); $query->condition('vid', self::PRODUCT_OPTIONS_VOCABULARY); $tids = $query->execute(); if ($tids) { foreach (array_chunk($tids, 50) as $ids) { $this->termStorage->resetCache(); try { $entities = $this->termStorage->loadMultiple($ids); $this->termStorage->delete($entities); } catch (\Exception $e) { $this->logger->error(t('Error occurred while deleting options that are no longer available. Error: @message', [ '@message' => $e->getMessage(), ])); } } } } }