a12s-1.0.0-beta7/modules/theme_builder/src/Block/MenuBlockAttributes.php

modules/theme_builder/src/Block/MenuBlockAttributes.php
<?php

declare(strict_types=1);

namespace Drupal\a12s_theme_builder\Block;

use Drupal\a12s_core\FormHelperTrait;
use Drupal\block\BlockInterface;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Form\SubformStateInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Template\Attribute;
use Drupal\Core\Url;

/**
 * Provides form for managing menu attributes settings and tools for rendering
 * the menu with their attributes.
 *
 * @todo Integrate with "Menu Attributes" module to support icons fonts.
 */
class MenuBlockAttributes extends GlobalAttributes {

  use FormHelperTrait;

  /**
   * {@inheritdoc}
   */
  public function defaultValues(): array {
    $default = parent::defaultValues();

    $default['settings']['levels'] = [
      'all' => 1,
      'operator' => 'equal',
      'level' => 1,
    ];

    // @todo handle some conditions.
    //$default['settings']['conditions'] = [
    //  'expanded' => '',
    //  'has_children' => '',
    //  'menu_item_uuid' => '',
    //];

    return $default;
  }

  /**
   * {@inheritdoc}
   */
  public function applies(BlockInterface $entity): bool {
    return str_starts_with($entity->getPluginId(), 'system_menu_block:') || str_starts_with($entity->getPluginId(), 'menu_block:');
  }

  /**
   * {@inheritdoc}
   */
  public function configurationKey(): string {
    return 'menu_attributes';
  }

  /**
   * {@inheritdoc}
   */
  public function buildForm(array &$form, SubformStateInterface $formState, array $settings = []): void {
    parent::buildForm($form, $formState, $settings);
    $form['#title'] = $this->t('Menu attributes');
  }

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, SubformStateInterface $formState): void {
    $values = $this->cleanAttributes($formState);

    // Order values.
    usort($values, function($a, $b) {
      $order = ['nav', 'ul', 'li', 'a'];
      $aIndex = array_search($a['element'], $order);
      $bIndex = array_search($b['element'], $order);
      // @todo If equal, order by "all levels" setting?
      return $aIndex - $bIndex;
    });

    $this->saveSettings($values, $formState);
  }

//  /**
//   * {@inheritdoc}
//   */
//  protected function saveSettings(array $values, FormStateInterface $formState): void {
//    // @todo add integration with menu_attributes module, with icon fonts...
//  }

  /**
   * Build the settings for an attribute.
   *
   * @param array $parents
   *   The element parents.
   * @param array $attribute
   *   The attribute values.
   *
   * @return array
   *
   * @todo Add some conditions regarding the "is_expanded", "is_collapsed",
   *   "in_active_trail" conditions or about the link type (link or nolink,
   *   with or without children...).
   */
  protected function buildAttributeRow(array $parents, array $attribute = []): array {
    $row = parent::buildAttributeRow($parents, $attribute);

    $row['element']['#options'] = [
      'nav' => $this->t('Navigation'),
      'ul' => $this->t('List wrapper'),
      'li' => $this->t('List item'),
      'a' => $this->t('Link'),
    ];

    $this->mergeWithDefault($attribute);

    $row['settings']['levels'] = [
      '#type' => 'fieldset',
      '#title' => $this->t('Levels'),
      '#weight' => -10,
      '#states' => [
        'invisible' => [
          $this->getInputNameFromPath('select', $parents, 'element') => [
            ['value' => ''],
            ['value' => 'nav'],
          ],
        ],
      ],
    ];

    $row['settings']['levels']['all'] = [
      '#type' => 'checkbox',
      '#title' => t('All levels'),
      '#default_value' => $attribute['settings']['levels']['all'],
    ];

    $row['settings']['levels']['custom'] = [
      '#type' => 'item',
      '#title' => $this->t('Target levels'),
      '#states' => [
        'invisible' => [
          $this->getInputNameFromPath(':input', $parents, ['settings', 'levels', 'all']) => ['checked' => TRUE],
        ],
      ],
      '#wrapper_attributes' => [
        'class' => ['container-inline'],
      ],
    ];

    $row['settings']['levels']['custom']['operator'] = [
      '#type' => 'select',
      '#title' => t('Operator'),
      '#title_display' => 'invisible',
      '#options' => [
        'equal' => $this->t('@label (@operator)', [
          '@label' => $this->t('Equal'),
          '@operator' => '=',
        ]),
        'lower' => $this->t('@label (@operator)', [
          '@label' => $this->t('Lower'),
          '@operator' => '<',
        ]),
        'lower_equal' => $this->t('@label (@operator)', [
          '@label' => $this->t('Lower or equal'),
          '@operator' => '≤',
        ]),
        'greater' => $this->t('@label (@operator)', [
          '@label' => $this->t('Greater'),
          '@operator' => '>',
        ]),
        'greater_equal' => $this->t('@label (@operator)', [
          '@label' => $this->t('Greater or equal'),
          '@operator' => '≥',
        ]),
        'different' => $this->t('@label (@operator)', [
          '@label' => $this->t('Different'),
          '@operator' => '≠',
        ]),
      ],
      '#default_value' => $attribute['settings']['levels']['operator'],
      '#parents' => array_merge($parents, ['settings', 'levels', 'operator']),
    ];

    $row['settings']['levels']['custom']['level'] = [
      '#type' => 'number',
      '#title' => t('Level'),
      '#title_display' => 'invisible',
      '#min' => 1,
      '#default_value' => $attribute['settings']['levels']['level'],
      '#parents' => array_merge($parents, ['settings', 'levels', 'level']),
    ];

    return $row;
  }

  /**
   * {@inheritdoc}
   *
   * Inject the "menu_attributes" settings to the build array.
   */
  public function preRenderBlock($build): array {
    if ($attributes = &$build['#a12s_theme_builder']['menu_attributes']['attributes']) {
      foreach ($attributes as $attribute) {
        if ($attribute['element'] === 'nav') {
          $build += ['#attributes' => []];
          $this->processAttribute($build['#attributes'], $attribute);
        }
      }

      if (!empty($build['content']['#items'])) {
        $this->processElement($build['content'], $build['content'], [], $attributes, 'ul', 1, '#items');
      }
    }

    return $build;
  }

  /**
   * {@inheritdoc}
   */
  protected function processAttribute(Attribute|array &$attributes, array $attribute, array $context = []): void {
    $context += ['level' => 1];
    parent::processAttribute($attributes, $attribute, $context);
  }

  /**
   * {@inheritdoc}
   */
  protected function processAttributeValue(string $value, array $context = []): string {
    return strtr($value, ['[level]' => $context['level']]);
  }

  /**
   * Apply recursively attributes to a menu and its children.
   *
   * @param array $root
   *   The root element of the menu.
   * @param array $element
   *   The current element being processed.
   * @param array $parents
   *   The full list of parents for the current element.
   * @param array $attributes
   *   An array of attributes to be processed.
   * @param string $type
   *   The type of element being processed. Defaults to 'ul'.
   * @param int $level
   *   The level of the element. Defaults to 1.
   * @param string|null $childKey
   *   The child key for the current element. Defaults to NULL. This is only
   *   necessary as the menu structure is not the same between the first level
   *   and the others.
   */
  protected function processElement(array &$root, array &$element, array $parents, array $attributes, string $type = 'ul', int $level = 1, ?string $childKey = NULL): void {
    $context = ['level' => $level];
    // Process the current element.
    foreach ($attributes as $attribute) {
      if ($attribute['element'] !== $type) {
        continue;
      }

      switch ($type) {
        case 'ul':
          // For root UL, things are simple as default template makes use of
          // #attributes.
          if ($level === 1) {
            $element += ['#attributes' => []];
            $this->processAttribute($element['#attributes'], $attribute, $context);
          }
          // But for other UL, we need to find some weird processes... So we
          // store the UL attributes in the parent.
          else {
            $ulAttributes = [];
            $this->processAttribute($ulAttributes, $attribute, $context);

            if (!empty($ulAttributes)) {
              $parentElement = &NestedArray::getValue($root, array_slice($parents, 0, -1));
              $parentElement['ul_attributes'] = new Attribute($ulAttributes);
            }
          }
          break;

        case 'li':
          $element += ['attributes' => new Attribute()];
          $this->processAttribute($element['attributes'], $attribute, $context);
          break;

        case 'a':
          if (isset($element['url']) && $element['url'] instanceof Url) {
            $attributesInstance = $element['url']->getOption('attributes') ?? [];
            $this->processAttribute($attributesInstance, $attribute, $context);
            $element['url']->setOption('attributes', $attributesInstance);
          }
          break;
      }
    }

    // Process children.
    switch ($type) {
      case 'ul':
        if ($childKey) {
          foreach ($element[$childKey] ?? [] as $key => &$item) {
            $this->processElement($root, $item, array_merge($parents, [$childKey, $key]), $attributes, 'li', $level);
          }
        }
        else {
          foreach (Element::children($element) as $key) {
            $this->processElement($root, $element[$key], array_merge($parents, [$key]), $attributes, 'li', $level);
          }
        }
        break;

      case 'li':
        $this->processElement($root, $element, $parents, $attributes, 'a', $level);
        break;

      case 'a':
        if (!empty($element['below'])) {
          $this->processElement($root, $element['below'], array_merge($parents, ['below']), $attributes, 'ul', ($level + 1));
        }
        break;
    }
  }

  /**
   * {@inheritdoc}
   *
   * @param array $settings
   *   The settings may contain the levels condition:
   *   - 'all' (bool): Indicates if the attribute applies to all levels.
   *   - 'level' (int): The level value to compare with current level.
   *   - 'operator' (string|null): The comparison operator for the level value.
   */
  protected function attributeApplies(array $settings, array $context = []): bool {
    $context += ['level' => 1];
    $currentLevel = $context['level'];
    $levelsSettings = $settings['levels'] ?? [];

    if (!empty($levelsSettings['all'])) {
      return TRUE;
    }
    // 0 is not a possible value.
    elseif (!empty($levelsSettings['level'])) {
      $levelValue = $levelsSettings['level'];

      return match ($levelsSettings['operator'] ?? 'unknown') {
        'lower' => $currentLevel < $levelValue,
        'lower_equal' => $currentLevel <= $levelValue,
        'greater' => $currentLevel > $levelValue,
        'greater_equal' => $currentLevel >= $levelValue,
        'different' => $currentLevel != $levelValue,
        'equal' => $currentLevel == $levelValue,
        default => FALSE,
      };
    }

    return FALSE;
  }

}

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

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