commercetools-8.x-1.2-alpha1/modules/commercetools_content/src/Plugin/Block/CommercetoolsContentCategoriesListBlock.php

modules/commercetools_content/src/Plugin/Block/CommercetoolsContentCategoriesListBlock.php
<?php

namespace Drupal\commercetools_content\Plugin\Block;

use Drupal\commercetools_content\CommercetoolsAjaxTrait;
use Drupal\commercetools_content\Service\CommercetoolsAjaxHelper;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Template\Attribute;
use Drupal\Core\Url;
use Drupal\commercetools\Plugin\Block\CommercetoolsCategoriesListBlockBase;
use Drupal\commercetools_content\Form\ContentSettingsForm;
use Drupal\commercetools_content\Service\CommercetoolsContentComponents;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Provides a Categories List block.
 *
 * @Block(
 *   id = "commercetools_content_categories_list",
 *   admin_label = @Translation("Categories"),
 * )
 */
class CommercetoolsContentCategoriesListBlock extends CommercetoolsCategoriesListBlockBase {

  use CommercetoolsAjaxTrait;

  /**
   * The commercetools content component service.
   *
   * @var \Drupal\commercetools_content\Service\CommercetoolsContentComponents
   */
  protected $contentComponents;

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): self {
    $instance = parent::create(...func_get_args());
    $instance->contentComponents = $container->get('commercetools_content.content_components');
    return $instance;
  }

  /**
   * {@inheritdoc}
   */
  public function defaultConfiguration(): array {
    return parent::defaultConfiguration() + [
      'use_ajax' => TRUE,
      CommercetoolsAjaxHelper::COMMERCETOOLS_SYSTEM_BLOCK_FORCE_UPDATE_CONFIG => FALSE,
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function getBlockConfigKeys(): array {
    $keys = parent::getBlockConfigKeys();
    $keys[] = CommercetoolsAjaxHelper::COMMERCETOOLS_SYSTEM_BLOCK_FORCE_UPDATE_CONFIG;
    return $keys;
  }

  /**
   * {@inheritdoc}
   */
  public function buildSafe(): array {
    $build = [];
    $displayStyle = $this->configuration['display_style'];

    $treeDepth = $this->configuration['max_level'];
    $index = $this->configuration['product_list_index'];
    $queryParams = $this->contentComponents->getRequestQueryParams();
    $paramName = CommercetoolsContentComponents::getParamNameByIndex('category', $index);
    $paramNameValue = $queryParams[$paramName] ?? NULL;
    $categoriesTree = $this->getCategoriesTree($paramNameValue);

    if (!$categoriesTree) {
      return $build;
    }

    $queryParams = $this->cleanupDynamicQueryParamsByIndex($queryParams, $index);

    if ($this->configuration['initial_level'] > 1) {
      $currentLevel = $paramNameValue
        ? $this->findCategoryLevel($categoriesTree, $paramNameValue, 2)
        : 1;
      if ($this->configuration['initial_level'] > $currentLevel) {
        return $build;
      }
    }

    $maxDepthTree = $displayStyle === 'cards' ? 1 : 100;

    if (!$this->configuration['display_from_current'] && $this->configuration['parent_category']) {
      $categoriesTree = $this->getCategorySubtree($categoriesTree, $this->configuration['parent_category'], $maxDepthTree)['children'];
    }

    if (!$categoriesTree) {
      return $build;
    }

    if ($displayStyle === 'list') {
      // Prevent re-ordering by Drupal\Core\Render\Element::children().
      $build['#sorted'] = TRUE;
      // Add the theme wrapper for outer markup and allow theme overrides.
      $build['#theme'] = 'menu__' . $this->getPluginId();
      $build['#items'] = $this->buildListItems(
        $categoriesTree,
        $index,
        $queryParams,
        $treeDepth,
      );
      $build['#attributes']['class'][] = 'ct-categories-list-block';
    }
    elseif ($displayStyle === 'cards') {
      if ($this->configuration['display_from_current']) {
        $categoriesTree = $paramNameValue
          ? $this->getCategorySubtree($categoriesTree, $paramNameValue)['children']
          : $categoriesTree;
      }
      $items = [];
      foreach ($categoriesTree as $category) {
        $queryParams[$paramName] = $category['id'];
        $items[] = [
          'title' => $category['name'],
          'url' => $this->getLink()->setOption('query', $queryParams),
        ];
      }
      $build = [
        '#theme' => 'commercetools_categories_cards_block',
        '#items' => $items,
        '#cards_columns' => $this->configuration['cards_columns'],
      ];
    }

    // Add ajax class by default.
    $this->configuration['use_ajax'] = TRUE;

    return $build;
  }

  /**
   * Recursively search for $id and return its depth (0-based), or null.
   */
  protected function findCategoryLevel(array $items, string $id, int $currentDepth = 1): ?int {
    foreach ($items as $node) {
      if ($node['id'] === $id) {
        return $currentDepth;
      }
      if (!empty($node['children'])) {
        $childDepth = $this->findCategoryLevel($node['children'], $id, $currentDepth + 1);
        if ($childDepth !== NULL) {
          return $childDepth;
        }
      }
    }
    return NULL;
  }

  /**
   * Find and return a subtree rooted at $id, limited to $maxDepth levels.
   */
  protected function getCategorySubtree(array $items, string $id, int $maxDepth = 1): ?array {
    // A small recursive helper to trim any node's children to $d levels.
    $prune = function (array $nodes, int $d) use (&$prune): array {
      if ($d <= 0) {
        // No deeper levels at all.
        return [];
      }
      $out = [];
      foreach ($nodes as $node) {
        $node['children'] = $prune($node['children'] ?? [], $d - 1);
        $out[] = $node;
      }
      return $out;
    };

    // Depth-first search.
    foreach ($items as $node) {
      if ($node['id'] === $id) {
        // Found the target: prune its children to $maxDepth levels and return.
        $node['children'] = $prune($node['children'] ?? [], $maxDepth);
        return $node;
      }
      if (!empty($node['children'])) {
        if ($sub = $this->getCategorySubtree($node['children'], $id, $maxDepth)) {
          return $sub;
        }
      }
    }
    return NULL;
  }

  /**
   * Builds the #items property for a categories tree.
   *
   * @param array $list
   *   The data structure representing the categories tree.
   * @param int $index
   *   The component index.
   * @param array $queryParams
   *   The current URL query parameters.
   * @param int $treeDepth
   *   The maximum allowed categories tree depth.
   * @param int $currentDepth
   *   The current level. Optional.
   *
   * @return array
   *   A value to use for the #items property to render a system menu.
   */
  protected function buildListItems(array $list, int $index, array $queryParams, int $treeDepth, int $currentDepth = 1): array {
    $items = [];

    $categoryParameterName = CommercetoolsContentComponents::getParamNameByIndex('category', $index);
    $baseLink = $this->getLink();
    foreach ($list as $data) {
      $link = clone $baseLink;
      $queryParams[$categoryParameterName] = $data['id'];
      $link->setOption('query', $queryParams);
      $element = [
        'is_active' => !empty($data['is_active']),
        'in_active_trail' => !empty($data['in_active_trail']),
        'attributes' => new Attribute(),
        'title' => $data['name'],
        'url' => $link,
        'below' => [],
      ];

      if ($data['children'] && (!empty($data['in_active_trail']) || !$treeDepth || $currentDepth < $treeDepth)) {
        $element['below'] = $this->buildListItems(
          $data['children'],
          $index,
          $queryParams,
          $treeDepth,
          $currentDepth + 1,
        );
      }

      $element['is_expanded'] = !empty($element['below']);
      $element['is_collapsed'] = empty($element['below']) && !empty($data['children']);

      $items[$data['id']] = $element;
    }

    return $items;
  }

  /**
   * Add option to force update system main content block.
   *
   * @param array $form
   *   Form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   Form state.
   *
   * @return array
   *   Form.
   */
  public function blockForm($form, FormStateInterface $form_state): array {
    $form = parent::blockForm($form, $form_state);
    $form += $this->getFormElements($this->configuration);
    return $form;
  }

  /**
   * Provides a URL instance for category link.
   *
   * @return \Drupal\Core\Url
   *   The URL instance.
   */
  protected function getLink(): Url {
    return empty($this->configuration['target_page']) ?
      Url::fromRoute('<current>') :
      Url::fromUserInput($this->configuration['target_page']);
  }

  /**
   * {@inheritdoc}
   */
  public function getCacheTags(): array {
    $cacheTags = parent::getCacheTags();
    return Cache::mergeTags($cacheTags, [
      'config:' . ContentSettingsForm::CONFIGURATION_NAME,
    ]);
  }

  /**
   * Filters query parameters, removing only variants.attributes.* filters.
   *
   * @param array $queryParams
   *   The original query parameters.
   * @param int $index
   *   The component index.
   *
   * @return array
   *   The filtered query parameters.
   */
  protected function cleanupDynamicQueryParamsByIndex(array $queryParams, int $index): array {
    // Remove pagination to reset to the first page when changing category.
    if (isset($queryParams['page'])) {
      unset($queryParams['page']);
    }

    // If filters exist, selectively remove only variants.attributes.* filters.
    if (isset($queryParams['filters']) && is_array($queryParams['filters'])) {
      foreach ($queryParams['filters'] as $filterKey => $filterValue) {
        if (strpos($filterKey, 'variants.attributes.') === 0) {
          unset($queryParams['filters'][$filterKey]);
        }
      }

      // If no filters remain after removing attributes, remove the array.
      if (empty($queryParams['filters'])) {
        unset($queryParams['filters']);
      }
    }

    return $queryParams;
  }

}

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

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