acquia_commercemanager-8.x-1.122/modules/acm_sku/src/CategoryManager.php

modules/acm_sku/src/CategoryManager.php
<?php

namespace Drupal\acm_sku;

use Drupal\acm\Connector\APIWrapper;
use Drupal\acm\I18nHelper;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Logger\LoggerChannelFactory;
use Drupal\acm\Connector\ClientFactory;
use Drupal\Core\Database\Connection;
use Drupal\Core\Extension\ModuleHandlerInterface;

/**
 * Provides a service for category data to taxonomy synchronization.
 *
 * @ingroup acm_sku
 */
class CategoryManager implements CategoryManagerInterface {

  /**
   * Taxonomy Term Entity Storage.
   *
   * @var \Drupal\taxonomy\TermStorageInterface
   */
  private $termStorage;

  /**
   * Taxonomy Vocabulary Entity Storage.
   *
   * @var \Drupal\taxonomy\VocabularyStorageInterface
   */
  private $vocabStorage;

  /**
   * Taxonomy Vocabulary Entity to Sync.
   *
   * @var \Drupal\taxonomy\VocabularyInterface
   */
  private $vocabulary;

  /**
   * Result (create / update / failed) counts.
   *
   * @var array
   */
  private $results;

  /**
   * API Wrapper object.
   *
   * @var \Drupal\acm\Connector\APIWrapper
   */
  private $apiWrapper;

  /**
   * Instance of I18nHelper service.
   *
   * @var \Drupal\acm\I18nHelper
   */
  private $i18nHelper;

  /**
   * Instance of logger.
   *
   * @var \Drupal\Core\Logger\LoggerChannelInterface
   */
  private $logger;

  /**
   * Module Handler service.
   *
   * @var \Drupal\Core\Extension\ModuleHandlerInterface
   */
  private $modulehandler;

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

  /**
   * Constructor.
   *
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   EntityTypeManager object.
   * @param \Drupal\acm\Connector\ClientFactory $client_factory
   *   ClientFactory object.
   * @param \Drupal\acm\Connector\APIWrapper $api_wrapper
   *   API Wrapper object.
   * @param \Drupal\Core\Logger\LoggerChannelFactory $logger_factory
   *   LoggerFactory object.
   * @param \Drupal\acm\I18nHelper $i18nHelper
   *   Instance of I18nHelper service.
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $moduleHandler
   *   Module handler service.
   * @param \Drupal\Core\Database\Connection $connection
   *   Database connection.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   */
  public function __construct(EntityTypeManagerInterface $entity_type_manager,
                              ClientFactory $client_factory,
                              APIWrapper $api_wrapper,
                              LoggerChannelFactory $logger_factory,
                              I18nHelper $i18nHelper,
                              ModuleHandlerInterface $moduleHandler,
                              Connection $connection) {
    $this->termStorage = $entity_type_manager->getStorage('taxonomy_term');
    $this->vocabStorage = $entity_type_manager->getStorage('taxonomy_vocabulary');
    $this->clientFactory = $client_factory;
    $this->apiWrapper = $api_wrapper;
    $this->logger = $logger_factory->get('acm_sku');
    $this->i18nHelper = $i18nHelper;
    $this->modulehandler = $moduleHandler;
    $this->connection = $connection;
  }

  /**
   * {@inheritdoc}
   */
  public function synchronizeTree($vocabulary, $remoteRoot = NULL) {
    $this->resetResults();
    $this->loadVocabulary($vocabulary);

    $config = \Drupal::config('acm.connector');
    $debug = $config->get('debug');
    $debug_dir = $config->get('debug_dir');

    foreach ($this->i18nHelper->getStoreLanguageMapping() as $langcode => $store_id) {
      if ($store_id) {
        // Load Connector Category data.
        $categories[] = $this->loadCategoryData($store_id);

        if ($debug && !empty($debug_dir)) {
          // Export category data into file.
          $filename = $debug_dir . '/categories_' . $langcode . '.data';
          $fp = fopen($filename, 'w');
          fwrite($fp, var_export($categories, 1));
          fclose($fp);
        }

        // Recurse the category tree and create / update nodes.
        $this->syncCategory($categories, NULL, $store_id);
      }
    }

    return ($this->results);
  }

  /**
   * Synchronize categories in offline mode, i.e. not connected to connector.
   *
   * @param string $vocabulary
   *   Vocabulary machine name.
   * @param array $categories
   *   Category tree to import.
   * @param string $storeId
   *   Store ID.
   *
   * @throws \Drupal\Core\TypedData\Exception\ReadOnlyException
   *   If the data is read-only.
   *
   * @return array
   *   Array summarising updates.
   */
  public function synchronizeTreeOffline($vocabulary, array $categories, $storeId = '') {
    $this->resetResults();
    $this->loadVocabulary($vocabulary);

    // Recurse the category tree and create / update nodes.
    $this->syncCategory($categories, NULL, $storeId);

    return ($this->results);
  }

  /**
   * {@inheritdoc}
   */
  public function synchronizeCategory($vocabulary, array $categories, $storeId = '') {
    $this->resetResults();
    $this->loadVocabulary($vocabulary);

    // Load parent of current category.
    $parent = 0;
    $query = $this->termStorage->getQuery()
      ->condition('field_commerce_id', $categories['parent_id'])
      ->condition('vid', $this->vocabulary->id());
    $tids = $query->execute();

    // If any found, load object of Term.
    if (count($tids)) {
      $tid = array_shift($tids);
      $parent = $this->termStorage->load($tid);
      $parent = ($parent && $parent->id()) ? $parent : 0;
    }

    // Recurse the category tree and create / update nodes.
    $this->syncCategory([$categories], $parent, $storeId);

    return ($this->results);
  }

  /**
   * LoadCategoryData.
   *
   * Load the commerce backend category data from Connector.
   *
   * @param int $store_id
   *   Store id for which we should get categories.
   *
   * @return array
   *   Array of categories.
   */
  private function loadCategoryData($store_id) {
    $this->apiWrapper->updateStoreContext($store_id);
    return $this->apiWrapper->getCategories();
  }

  /**
   * LoadVocabulary.
   *
   * Load a taxonomy vocabulary from a vid.
   *
   * @param string $vocabulary
   *   Vocabulary VID.
   */
  private function loadVocabulary($vocabulary) {
    if (!strlen($vocabulary)) {
      throw new \InvalidArgumentException('CategoryManager requires a taxonomy vocabulary machine name.');
    }

    $vocab = $this->vocabStorage->load($vocabulary);

    if (!$vocab || !$vocab->id()) {
      throw new \InvalidArgumentException(sprintf(
        'CategoryManager unable to locate vocabulary %s.',
        $vocabulary
      ));
    }

    $this->vocabulary = $vocab;

  }

  /**
   * ResetResults.
   *
   * Reset the results counters.
   */
  private function resetResults() {
    $this->results = [
      'created' => [],
      'updated' => [],
      'failed' => [],
    ];
  }

  /**
   * SyncCategory.
   *
   * Recursive category synchronization and saving.
   *
   * @param array $categories
   *   Children Categories.
   * @param array|null $parent
   *   Parent Category.
   * @param string $storeId
   *   Store ID.
   *
   * @throws \Drupal\Core\TypedData\Exception\ReadOnlyException
   *   If the data is read-only.
   */
  private function syncCategory(array $categories, $parent = NULL, $storeId = '') {
    /** @var \Drupal\Core\Lock\PersistentDatabaseLockBackend $lock */
    $lock = \Drupal::service('lock.persistent');

    // Remove top level item (Default Category) from the categories, if its set
    // in configuration and category is with no parent.
    $filter_root_category = \Drupal::config('acm.connector')
      ->get('filter_root_category');
    if ($filter_root_category && $parent === NULL) {
      $categories = $categories[0]['children'];
    }

    // Get langcode for v2 connector.
    $langcode = '';
    if (!empty($storeId)) {
      $langcode = $this->i18nHelper->getLangcodeFromStoreId($storeId);
    }

    foreach ($categories as $category) {
      if (empty($category['category_id']) || empty($category['name'])) {
        $this->logger->error('Invalid / missing category ID or name.');
        $this->results['failed'][] = $category['category_id'] ?? '-1';
        continue;
      }

      // For v1 connector we are going to get store_id from each product,
      // because we are not sending X-ACM-UUID header.
      if (empty($langcode)) {
        $langcode = $this->i18nHelper->getLangcodeFromStoreId($category['store_id']);
      }

      // If langcode is still empty at this point, we probably don't support
      // this store. This is because we are sending all data for all stores.
      if (empty($langcode)) {
        continue;
      }

      $lock_key = 'syncCategory' . $category['category_id'];
      // Acquire lock to ensure parallel processes are executed one by one.
      do {
        $lock_acquired = $lock->acquire($lock_key);
        // Sleep for half a second before trying again.
        if (!$lock_acquired) {
          usleep(500000);
        }
      } while (!$lock_acquired);

      // Always initialise $parent_data (especially after recursion)
      // (Forces only one parent allowed per category branch)
      $parent_data = [];
      $parent_data[] = ($parent) ? $parent->id() : 0;

      $position = (isset($category['position'])) ? (int) $category['position'] : 1;

      // Load existing term (if found).
      $query = $this->termStorage->getQuery();
      $group = $query->andConditionGroup()
        ->condition('field_commerce_id', $category['category_id'])
        ->condition('vid', $this->vocabulary->id());
      $query->condition($group);

      $tids = $query->execute();

      if (count($tids) > 1) {
        $this->logger->error('Multiple terms found for category id @cid', ['@cid' => $category['category_id']]);
      }

      // Always use the first term and continue.
      if (count($tids) > 0) {
        $this->logger->info('Updating category term @name [@id]', [
          '@name' => $category['name'],
          '@id' => $category['category_id'],
        ]);

        // Load and update the term entity.
        /** @var \Drupal\taxonomy\Entity\Term $term */
        $term = $this->termStorage->load(array_shift($tids));

        if (!$term->hasTranslation($langcode)) {
          $term = $term->addTranslation($langcode);

          // We doing this because when the translation of node is created by
          // addTranslation(), pathauto alias is not created for the translated
          // version.
          // @see https://www.drupal.org/project/pathauto/issues/2995829.
          if ($this->modulehandler->moduleExists('pathauto')) {
            $term->path->pathauto = 1;
          }

          $term->get('field_commerce_id')->setValue($category['category_id']);
        }
        else {
          $term = $term->getTranslation($langcode);
        }

        $term->setName($category['name']);
        $term->parent = $parent_data;
        $term->weight->value = $position;

        // Break child relationships.
        $children = $this->termStorage->loadChildren($term->id(), $this->vocabulary->id());
        if (count($children)) {
          $child_ids = array_map(function ($child) use ($category) {
            // If term having commerce id, means its sync from magento and
            // thus we process. Term not having commerce id means its created
            // only on Drupal and thus we skip processing.
            if ($commerce_id = $child->get('field_commerce_id')->first()) {
              // We check if the child exists in the response get from magento.
              foreach ($category['children'] as $sync_cat_child) {
                if ($commerce_id->getString() == $sync_cat_child['category_id']) {
                  return $child->id();
                }
              }
            }
          }, $children);

          $this->termStorage->deleteTermHierarchy($child_ids);
        }

        $this->results['updated'][] = $category['category_id'];

      }
      else {
        // Create the term entity.
        $this->logger->info('Creating category term @name [@id]',
          ['@name' => $category['name'], '@id' => $category['category_id']]
        );

        $term = $this->termStorage->create([
          'vid' => $this->vocabulary->id(),
          'name' => $category['name'],
          'field_commerce_id' => $category['category_id'],
          'parent' => $parent_data,
          'weight' => $position,
          'langcode' => $langcode,
        ]);

        $this->results['created'][] = $category['category_id'];
      }

      // Store status of category.
      $term->get('field_commerce_status')->setValue((int) $category['is_active']);

      $term->get('field_category_include_menu')->setValue($category['in_menu']);
      $term->get('description')->setValue($category['description']);

      // @TODO: Refactor \Drupal::config into DI.
      $config = \Drupal::config('acm.connector');
      $text_format = $config->get('text_format') ?: 'rich_text';
      $term->setFormat($text_format);

      // Invoke the alter hook to allow all modules to update the term.
      \Drupal::moduleHandler()->alter('acq_sku_commerce_category', $term, $category, $parent);

      try {
        $term->save();
        // Release the lock.
        $lock->release($lock_key);
        $lock_key = NULL;
      }
      catch (\Exception $e) {
        $this->logger->warning('Failed saving category term @name (ID @id)',
          ['@name' => $category['name'], '@id' => $category['category_id']]
        );
        // Release the lock.
        $lock->release($lock_key);
        $lock_key = NULL;
        continue;
      }

      // Recurse to children categories.
      $childCats = (isset($category['children'])) ? $category['children'] : [];
      $this->syncCategory($childCats, $term, $storeId);
    }
  }

  /**
   * {@inheritdoc}
   */
  public function getOrphanCategories(array $sync_categories) {
    // Get all category terms with commerce id.
    $query = $this->connection->select('taxonomy_term_field_data', 'ttd');
    $query->fields('ttd', ['tid', 'name']);
    $query->leftJoin('taxonomy_term__field_commerce_id', 'tcid', 'ttd.tid=tcid.entity_id');
    $query->fields('tcid', ['field_commerce_id_value']);
    $query->condition('ttd.vid', 'acq_product_category');
    $result = $query->execute()->fetchAllAssoc('tid', \PDO::FETCH_ASSOC);

    $affected_terms = array_unique(array_merge($sync_categories['created'], $sync_categories['updated']));
    // Filter terms which are not in sync response.
    return $result = array_filter($result, function ($val) use ($affected_terms) {
      return !in_array($val['field_commerce_id_value'], $affected_terms);
    });
  }

}

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

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