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

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

declare(strict_types=1);

namespace Drupal\a12s_theme_builder\Block;

use Drupal\a12s_core\FormHelperTrait;
use Drupal\block\BlockInterface;
use Drupal\Component\Serialization\Json;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Form\SubformStateInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Template\Attribute;

/**
 * Provides form for managing global attributes for blocks.
 */
class GlobalAttributes extends SettingsAlterBase {

  use FormHelperTrait;

  /**
   * {@inheritdoc}
   */
  public function defaultValues(): array {
    return [
      'element' => NULL,
      'attribute' => NULL,
      'value' => '',
      'settings' => [
        'data_attribute' => [
          'name' => '',
          'json_encode' => FALSE,
        ],
        'aria_attribute' => [
          'name' => '',
        ],
      ],
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function applies(BlockInterface $entity): bool {
    return TRUE;
  }

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

  /**
   * {@inheritdoc}
   */
  public function buildForm(array &$form, SubformStateInterface $formState, array $settings = []): void {
    $attributes = $settings['attributes'] ?? [];
    $wrapperId = Html::cleanCssIdentifier($this->configurationKey()) . '-wrapper';

    $form += [
      '#type' => 'details',
      '#title' => $this->t('Global attributes'),
      //'#open' => !empty($attributes),
      '#parents' => [],
    ];

    // @todo Add a collapsible documentation.

    $form['attributes'] = [
      '#type' => 'table',
      '#header' => [
        $this->t('Element'),
        $this->t('Attribute'),
        $this->t('Value'),
        $this->t('Settings'),
      ],
      '#element_validate' => [
        [static::class, 'validateAttributes'],
      ],
      '#prefix' => '<div id="' . $wrapperId . '">',
      '#suffix' => '</div>',
    ];

    $values = $formState->getValue('attributes', []);
    $formParents = $form['#parents'];
    $attributesElement = &$form['attributes'];
    $getCurrentParents = function() use ($formParents, &$attributesElement) {
      return array_merge($formParents, ['attributes', count(Element::children($attributesElement))]);
    };

    foreach ($attributes as $attribute) {
      if (is_array($attribute)) {
        $form['attributes'][] = $this->buildAttributeRow($getCurrentParents(), $attribute);

        if ($values) {
          array_shift($values);
        }
      }
    }

    // Add extra rows.
    foreach ($values as $attribute) {
      if (is_array($attribute)) {
        $form['attributes'][] = $this->buildAttributeRow($getCurrentParents(), $attribute);
      }
    }

    // Add a new row.
    $form['attributes'][] = $this->buildAttributeRow($getCurrentParents());

    $form['add_attributes'] = [
      '#type' => 'button',
      '#value' => $this->t('Add another attribute'),
      '#ajax' => [
        'callback' => [$this, 'addAttributeRow'],
        'wrapper' => $wrapperId,
      ],
    ];
  }

  /**
   * Validates attributes for the full table form element.
   *
   * @param array $element
   *   The form element to validate attributes for.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state object.
   * @param array $complete_form
   *   The complete form array.
   *
   * @return void
   */
  public static function validateAttributes(array &$element, FormStateInterface $form_state, array &$complete_form): void {
    $input_exists = FALSE;
    $input = NestedArray::getValue($form_state->getValues(), $element['#parents'], $input_exists);

    if ($input && is_array($input)) {
      foreach ($input as $index => $item) {
        // Is element empty?
        if (empty($item['element']) || empty($item['attribute'])) {
          continue;
        }

        if ($item['attribute'] === 'data') {
          // @todo check for valid data attribute name?
          if (empty($item['settings']['data_attribute']['name'])) {
            $form_state->setError($element[$index]['settings']['data_attribute']['name'], t('The data attribute name is required.'));
          }
        }
        elseif (!(isset($item['value']) && strlen($item['value']))) {
          $form_state->setError($element[$index]['value'], t('The value is required.'));
        }
      }
    }
  }

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

    // Order values.
    usort($values, function($a, $b) {
      $order = ['block', 'title', 'content'];
      $aIndex = array_search($a['element'], $order);
      $bIndex = array_search($b['element'], $order);
      return $aIndex - $bIndex;
    });

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

  /**
   * {@inheritdoc}
   */
  protected function saveSettings(array $values, FormStateInterface $formState): void {
    $key = $this->configurationKey();
    /** @var \Drupal\block\Entity\Block $block */
    $block = $formState->getFormObject()->getEntity();
    $thirdPartySetting = $block->getThirdPartySetting('a12s_theme_builder', $key, []);

    // Values are stored under "attributes" key, to allow further integration
    // with menu_attributes module, with icon fonts...
    if (empty($values)) {
      unset($thirdPartySetting['attributes']);
    }
    else {
      $thirdPartySetting['attributes'] = $values;
    }

    parent::saveSettings($thirdPartySetting, $formState);
  }

  /**
   * AJAX callback; return the updated attributes table.
   *
   * @param array &$form
   *   The form array.
   *
   * @return array
   */
  public function addAttributeRow(array &$form): array {
    return $form['a12s_theme_builder'][$this->configurationKey()]['attributes'];
  }

  /**
   * Build the settings for an attribute.
   *
   * @param array $parents
   *   The element parents.
   * @param array $attribute
   *   The attribute values.
   *
   * @return array
   */
  protected function buildAttributeRow(array $parents, array $attribute = []): array {
    $this->mergeWithDefault($attribute);

    $row['element'] = [
      '#type' => 'select',
      '#title' => t('Element'),
      '#options' => [
        'block' => $this->t('Block container'),
        'title' => $this->t('Title'),
        'content' => $this->t('Content'),
      ],
      '#empty_option' => $this->t('- Select -'),
      '#title_display' => 'invisible',
      '#default_value' => $attribute['element'],
    ];

    $row['attribute'] = [
      '#type' => 'select',
      '#title' => t('Attribute'),
      '#options' => [
        'id' => $this->t('Identifier'),
        'class' => $this->t('CSS Class'),
        'data' => $this->t('Data attribute'),
        'role' => $this->t('Role'),
        'aria' => $this->t('ARIA attribute'),
      ],
      '#empty_option' => $this->t('- Select -'),
      '#title_display' => 'invisible',
      '#default_value' => $attribute['attribute'],
    ];

    // @todo make "value" required when attribute is "class".
    // @todo add AJAX and convert to select for ARIA and role?
    // @todo add placeholder for item title (necessary for aria-label).
    $row['value'] = [
      '#type' => 'textfield',
      '#title' => t('Value'),
      '#title_display' => 'invisible',
      '#default_value' => $attribute['value'],
    ];

    $row['settings'] = [
      '#type' => 'container',
    ];

    $row['settings']['data_attribute'] = [
      '#type' => 'fieldset',
      '#title' => $this->t('Data attribute'),
      '#states' => [
        'visible' => [
          $this->getInputNameFromPath('select', $parents, 'attribute') => ['value' => 'data'],
        ],
      ],
    ];

    $row['settings']['data_attribute']['name'] = [
      '#type' => 'textfield',
      '#title' => t('Name'),
      '#default_value' => $attribute['settings']['data_attribute']['name'],
      '#states' => [
        'required' => [
          $this->getInputNameFromPath('select', $parents, 'attribute') => ['value' => 'data'],
        ],
      ],
    ];

    $row['settings']['data_attribute']['json_encode'] = [
      '#type' => 'checkbox',
      '#title' => t('Encode in JSON format'),
      '#default_value' => $attribute['settings']['data_attribute']['json_encode'],
    ];

    $row['settings']['aria_attribute'] = [
      '#type' => 'fieldset',
      '#title' => $this->t('ARIA attribute'),
      '#states' => [
        'visible' => [
          $this->getInputNameFromPath('select', $parents, 'attribute') => ['value' => 'aria'],
        ],
      ],
    ];

    $row['settings']['aria_attribute']['name'] = [
      '#type' => 'select',
      '#title' => t('Name'),
      '#default_value' => $attribute['settings']['aria_attribute']['name'],
      '#options' => [
        'aria-autocomplete' => $this->t('ARIA autocomplete'),
        'aria-checked' => $this->t('ARIA checked'),
        'aria-disabled' => $this->t('ARIA disabled'),
        'aria-errormessage' => $this->t('ARIA error message'),
        'aria-expanded' => $this->t('ARIA expanded'),
        'aria-haspopup' => $this->t('ARIA has popup'),
        'aria-hidden' => $this->t('ARIA hidden'),
        'aria-invalid' => $this->t('ARIA invalid'),
        'aria-label' => $this->t('ARIA label'),
        'aria-level' => $this->t('ARIA level'),
        'aria-modal' => $this->t('ARIA modal'),
        'aria-multiline' => $this->t('ARIA multiline'),
        'aria-multiselectable' => $this->t('ARIA multiselectable'),
        'aria-orientation' => $this->t('ARIA orientation'),
        'aria-placeholder' => $this->t('ARIA placeholder'),
        'aria-pressed' => $this->t('ARIA pressed'),
        'aria-readonly' => $this->t('ARIA readonly'),
        'aria-selected' => $this->t('ARIA selected'),
        'aria-sort' => $this->t('ARIA sort'),
        'aria-valuemax' => $this->t('ARIA valuemax'),
        'aria-valuemin' => $this->t('ARIA valuemin'),
        'aria-valuenow' => $this->t('ARIA valuenow'),
        'aria-valuetext' => $this->t('ARIA valuetext'),
      ],
      '#states' => [
        'required' => [
          $this->getInputNameFromPath('select', $parents, 'attribute') => ['value' => 'aria'],
        ],
      ],
    ];

    return $row;
  }

  /**
   * {@inheritdoc}
   */
  protected function usePreRenderCallback(): bool {
    return TRUE;
  }

  /**
   * {@inheritdoc}
   *
   * Inject the "menu_attributes" settings to the build array.
   */
  public function preRenderBlock($build): array {
    if ($attributes = &$build['#a12s_theme_builder'][$this->configurationKey()]['attributes']) {
      $map = [
        'block' => '#attributes',
        'title' => '#title_attributes',
        'content' => '#content_attributes',
      ];

      foreach ($attributes as $attribute) {
        if (isset($map[$attribute['element']])) {
          $property = $map[$attribute['element']];
          $build += [$property => []];
          $this->processAttribute($build[$property], $attribute);
        }
      }
    }

    return $build;
  }

  /**
   * Processes an attribute and modifies the whole attributes if applicable.
   *
   * @param Attribute|array &$attributes
   *   A reference to the attributes array to modify.
   * @param array $attribute
   *   The attribute to process.
   * @param array $context
   *   The current context.
   */
  protected function processAttribute(Attribute|array &$attributes, array $attribute, array $context = []): void {
    $this->mergeWithDefault($attribute);
    // @todo use DI.
    /** @var \Drupal\a12s_theme_builder\ThemeHelper $themeHelper */
    $themeHelper = \Drupal::service('a12s_theme_builder.helper');

    if ($this->attributeApplies($attribute['settings'] ?? [], $context)) {
      switch ($attribute['attribute']) {
        case 'id':
          $value = $this->processAttributeValue($attribute['value'] ?? '', $context);
          $themeHelper->setAttribute($attributes, 'id', $value);
          break;
        case 'class':
          $value = $this->processAttributeValue($attribute['value'] ?? '', $context);
          $themeHelper->addClasses($attributes, $value);
          break;
        case 'data':
          $settings = $attribute['settings']['data_attribute'] ?? [];

          if (!empty($settings['name'])) {
            $name = "data-{$settings['name']}";
            $value = $this->processAttributeValue($attribute['value'] ?? '', $context);

            if (!empty($value) && !empty($settings['json_encode'])) {
              $value = Json::encode($value);
            }

            $themeHelper->setAttribute($attributes, $name, $value);
          }
          break;
        case 'role':
          $themeHelper->setAttribute($attributes, 'role', $attribute['value'] ?? '');
          break;
        case 'aria':
          $settings = $attribute['settings']['aria_attribute'] ?? [];
          $value = $this->processAttributeValue($attribute['value'] ?? '', $context);

          if (!empty($settings['name'])) {
            $themeHelper->setAttribute($attributes, $settings['name'], $value);
          }
          break;
      }
    }
  }

  /**
   * Process the attribute value.
   *
   * @param string $value
   *   The attribute value to be processed.
   * @param array $context
   *   Optional. The current context.
   *
   * @return string
   */
  protected function processAttributeValue(string $value, array $context = []): string {
    return $value;
  }

  /**
   * Determines if the given attribute applies based on the settings.
   *
   * @param array $settings
   *   The attribute settings.
   * @param array $context
   *   The current context.
   *
   * @return bool Returns TRUE if the attribute applies, or FALSE otherwise.
   */
  protected function attributeApplies(array $settings, array $context = []): bool {
    return TRUE;
  }

  /**
   * Cleans the attributes array by removing any empty or default values.
   *
   * @param \Drupal\Core\Form\SubformStateInterface $formState
   *   The subform state object.
   *
   * @return array
   *   An array of cleaned attributes.
   */
  protected function cleanAttributes(SubformStateInterface $formState): array {
    $values = [];

    foreach ($formState->getValue(['attributes'], []) as $attribute) {
      // Ignore empty values.
      if (empty($attribute['element']) || empty($attribute['attribute'])) {
        continue;
      }

      // Remove default values.
      if ($attribute = $this->cleanValues($attribute)) {
        $values[] = $attribute;
      }
    }

    return $values;
  }

}

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

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