splide-1.0.x-dev/src/SplideManager.php

src/SplideManager.php
<?php

namespace Drupal\splide;

use Drupal\blazy\Blazy;
use Drupal\blazy\BlazyManagerBase;
use Drupal\splide\Entity\Splide;

/**
 * Provides splide manager.
 */
class SplideManager extends BlazyManagerBase implements SplideManagerInterface {

  /**
   * {@inheritdoc}
   */
  protected static $namespace = 'splide';

  /**
   * {@inheritdoc}
   */
  protected static $itemId = 'slide';

  /**
   * {@inheritdoc}
   */
  protected static $itemPrefix = 'slide';

  /**
   * The splide skin manager service.
   *
   * @var \Drupal\splide\SplideSkinManagerInterface
   */
  protected $skinManager;

  /**
   * {@inheritdoc}
   */
  public static function trustedCallbacks() {
    return ['preRenderSplide', 'preRenderSplideWrapper'];
  }

  /**
   * {@inheritdoc}
   */
  public function skinManager(): SplideSkinManagerInterface {
    return $this->skinManager;
  }

  /**
   * Sets splide skin manager service.
   */
  public function setSkinManager(SplideSkinManagerInterface $skin_manager) {
    $this->skinManager = $skin_manager;
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function build(array $build): array {
    foreach (SplideDefault::themeProperties() as $key => $default) {
      $k = $key == 'items' ? $key : "#$key";
      $build[$k] = $this->toHashtag($build, $key, $default);
    }

    $splide = [
      '#theme'      => 'splide_wrapper',
      '#items'      => [],
      '#build'      => $build,
      '#pre_render' => [[$this, 'preRenderSplideWrapper']],
      // Satisfy CTools blocks as per 2017/04/06: 2804165.
      'items'       => [],
    ];

    $this->moduleHandler->alter('splide_build', $splide, $build['#settings']);
    return empty($build['items']) ? [] : $splide;
  }

  /**
   * {@inheritdoc}
   */
  public function buildGrid(array $items, array &$settings): array {
    $this->verifySafely($settings);

    $blazies = $settings['blazies'];
    $config  = $settings['splides'];
    $count   = $blazies->get('count', 0);
    $grids   = [];

    $blazies->set('is.grid_nested', TRUE);

    // Enforces unsplide with less items.
    if (!$config->is('unsplide')) {
      $settings['unsplide'] = $unplide = $count < $settings['visible_items'];
      $config->set('is.unplide', $unplide);
    }

    // Display all items if unsplide is enforced for plain grid to lightbox.
    // Or when the total is less than visible_items.
    // @todo recheck and revert $count 1 to 2 if any side issues.
    if ($config->is('unplide')) {
      $settings['display']      = 'main';
      $settings['current_item'] = 'grid';
      $settings['count']        = $count = 1;

      // Requests to refresh grid and re-attach libraries when destroyed.
      $blazies->set('count', $count)
        ->set('is.grid', TRUE)
        ->set('is.grid_refresh', TRUE);

      $grids[0] = $this->buildGridItem($items, $settings);
    }
    else {
      // Otherwise do chunks to have a grid carousel, and also update count.
      $preserve_keys     = $settings['preserve_keys'] ?? FALSE;
      $grid_items        = array_chunk($items, $settings['visible_items'], $preserve_keys);
      $settings['count'] = $count = count($grid_items);

      $blazies->set('count', $count);
      foreach ($grid_items as $grid_item) {
        $grids[] = $this->buildGridItem($grid_item, $settings);
      }
    }

    return $grids;
  }

  /**
   * {@inheritdoc}
   */
  public function getAnimationTypes(): array {
    $types = [];
    $this->moduleHandler->alter('splide_animation_types', $types);
    return $types;
  }

  /**
   * {@inheritdoc}
   */
  public function getTransitionTypes(): array {
    $types = [
      'slide' => 'Slide',
      'loop'  => 'Loop',
      'fade'  => 'Fade',
    ];
    $this->moduleHandler->alter('splide_transition_types', $types);
    return $types;
  }

  /**
   * {@inheritdoc}
   */
  public function preRenderSplide(array $element): array {
    $build = $element['#build'];
    unset($element['#build']);

    $settings = &$build['#settings'];

    $this->verifySafely($settings);
    Splide::verifyOptionset($build, $settings['optionset']);

    $options   = &$build['#options'];
    $optionset = &$build['#optionset'];
    $blazies   = $settings['blazies'];
    $config    = $settings['splides'];
    $wheel     = $options['wheel'] ?? $optionset->getSetting('wheel');

    // @todo move here anything unique to each displays.
    $data = [
      'animation'  => $options['animation'] ?? $optionset->getSetting('animation'),
      'transition' => $options['type'] ?? $optionset->getSetting('type'),
      'wheel'      => $wheel,
    ];

    foreach ($data as $key => $value) {
      // @todo remove settings after migration.
      $settings[$key] = $value;
      $config->set(is_bool($value) ? 'is.' . $key : $key, $value);
    }

    if ($settings['display'] == 'main') {
      // Build the Splide grid if provided.
      $blazies->set('is.grid_nested', TRUE);
      if (!empty($settings['grid']) && !empty($settings['visible_items'])) {
        $build['items'] = $this->buildGrid($build['items'], $settings);
      }
      $blazies->set('is.grid_nested', FALSE);
    }

    $build['#attributes'] = $this->prepareAttributes($build);

    $this->moduleHandler->alter('splide_optionset', $optionset, $settings, $options);

    foreach (SplideDefault::themeProperties() as $key => $default) {
      $element["#$key"] = $this->toHashtag($build, $key, $default);
    }

    unset($build);
    return $element;
  }

  /**
   * {@inheritdoc}
   */
  public function loadSafely($name): Splide {
    return Splide::loadSafely($name);
  }

  /**
   * {@inheritdoc}
   */
  public function preRenderSplideWrapper($element): array {
    $build = $element['#build'];
    unset($element['#build']);

    // Prepare settings and assets.
    $settings = $this->prepareSettings($element, $build);

    // Checks if we have thumbnail navigation.
    $navs    = $build['nav']['items'] ?? [];
    $blazies = $settings['blazies'];
    $config  = $settings['splides'];

    // Prevents unused thumb going through the main display.
    unset($build['nav']);

    // Build the main Splide.
    $splide[0] = $this->splide($build);

    // Build the thumbnail Splide.
    if ($blazies->is('nav') && $navs) {
      $splide[1] = $this->buildNavigation($build, $navs);
    }

    // Reverse splides if thumbnail position is provided to get CSS float work.
    if ($config->get('navpos')) {
      $splide = array_reverse($splide);
    }

    // Collect the splide instances.
    $element['#items'] = $splide;
    $this->setAttachments($element, $settings);

    unset($build);
    return $element;
  }

  /**
   * {@inheritdoc}
   */
  public function toBlazy(array &$data, array &$captions, $delta): void {
    $settings = $this->toHashtag($data);
    $this->verifySafely($settings);

    $blazies = $settings['blazies'];
    $skin    = $settings['skin'] ?? '';
    $prefix  = 'slide';

    $blazies->set('item.id', $prefix)
      ->set('item.prefix', $prefix);

    // Only if it has captions.
    if ($captions) {
      $data['#media_attributes']['class'][] = $prefix . '__media';
      if (strpos($skin, 'full') !== FALSE) {
        $data['#caption_wrapper_attributes']['class'][] = $prefix . '__constrained';
      }
    }

    // Grid already has grid__content wrapper, skip.
    $attrs = $blazies->get('item.wrapper_attributes', []);
    if (empty($settings['grid']) || $attrs) {
      $data['#wrapper_attributes']['class'][] = $prefix . '__content';
      if ($attrs) {
        $data['#wrapper_attributes'] = $this->merge($data['#wrapper_attributes'], $attrs);
      }
    }

    parent::toBlazy($data, $captions, $delta);
  }

  /**
   * {@inheritdoc}
   */
  public function verifySafely(array &$settings, $key = 'blazies', array $defaults = []) {
    SplideDefault::verify($settings, $this);

    return parent::verifySafely($settings, $key, $defaults);
  }

  /**
   * {@inheritdoc}
   */
  protected function attachments(array &$load, array $attach, $blazies): void {
    parent::attachments($load, $attach, $blazies);
    $this->verifySafely($attach);

    $this->skinManager->attach($load, $attach, $blazies);

    $this->moduleHandler->alter('splide_attach', $load, $attach, $blazies);
  }

  /**
   * Returns grid items.
   */
  protected function buildGridItem(array $items, array $settings): array {
    $config  = $settings['splides'];
    $output  = $this->generateGridItem($items, $settings);
    $result  = $this->toGrid($output, $settings);
    $unplide = $config->is('unplide');

    $result['#attributes']['class'][] = $unplide ? 'splide__grid' : 'slide__content';

    $build = ['slide' => $result, '#settings' => $settings];

    $this->moduleHandler->alter('splide_grid_item', $build, $settings);
    return $build;
  }

  /**
   * Returns splide navigation with structured array similar to main display.
   */
  protected function buildNavigation(array &$build, array $items): array {
    $settings  = $this->toHashtag($build);
    $optionset = $this->toHashtag($build, 'optionset_nav');
    $options   = $optionset->getSettings(TRUE);
    $skin      = $settings['skin_nav'] ?? NULL;

    // Only designed for main display, not thumbnails.
    unset($settings['skin_arrows'], $settings['skin_dots']);

    // Programmatic options via skin definitions.
    if ($skin && $skins = $this->skinManager->getSkinsByGroup('nav')) {
      if ($programs = $skins[$skin]['options'] ?? []) {
        $options = $this->merge($programs, $options);
      }
    }

    $settings['optionset'] = $settings['optionset_nav'];
    $settings['skin']      = $skin;
    $settings['display']   = 'nav';
    $data['items']         = $items;
    $data['#optionset']    = $optionset;
    $data['#options']      = $options;
    $data['#settings']     = $settings;

    // Disabled irrelevant options when lacking of slides.
    $this->unsplide($options, $settings);

    // The  navigation has the same structure as the main one.
    unset($build['#optionset_nav']);
    return $this->splide($data);
  }

  /**
   * Prepare attributes for the known module features, not necessarily users'.
   */
  protected function prepareAttributes(array $build = []): array {
    $settings   = $this->toHashtag($build);
    $attributes = $this->toHashtag($build, 'attributes');

    if ($settings['display'] == 'main') {
      Blazy::containerAttributes($attributes, $settings);
    }
    return $attributes;
  }

  /**
   * Prepares js-related options.
   */
  protected function prepareOptions(Splide &$optionset, array &$options, array &$settings): void {
    $blazies = $settings['blazies'];

    // Supports programmatic options defined within skin definitions to allow
    // addition of options with other libraries integrated with Splide without
    // modifying optionset such as for Zoom, Reflection, Slicebox, Transit, etc.
    $skin = $settings['skin'] ?? NULL;
    if ($skin && $skins = $this->skinManager->getSkinsByGroup('main')) {
      if ($programs = $skins[$skin]['options'] ?? []) {
        $options = $this->merge($programs, $options);
      }
    }

    if ($settings['display'] == 'main') {
      $options['pagination'] = $pagination = Splide::toBoolOrString($optionset->getSetting('pagination'));

      // Overrides common options to re-use an optionset.
      if (!empty($settings['override'])) {
        foreach ($settings['overridables'] as $key => $override) {
          $options[$key] = $key == $override ? TRUE : FALSE;

          // Retain the original optionset specific for pagination.
          if ($key == 'pagination' && $options[$key]) {
            // @todo recheck why double equal signs fails in PHP8.
            if ($pagination === '.splide__arrows') {
              $options[$key] = '.splide__arrows';
            }
          }

          // Supports FIFO hook_splide_overridable_options_info_alter.
          // Makes no sense, but the cheap way without another option for now.
          foreach (['slide', 'loop', 'fade'] as $k) {
            if (isset($options[$k])) {
              if ($options[$k] == $key) {
                $options['type'] = $k;
              }
              unset($options[$k]);
            }
          }
        }
      }
    }

    // Disable draggable for Layout Builder UI to not conflict with UI sortable.
    $count = $blazies->get('count');
    $options['count'] = $count;

    if (strpos($blazies->get('route_name', ''), 'layout_builder.') === 0
      || $blazies->is('sandboxed')) {
      $options['drag'] = FALSE;
    }

    $this->moduleHandler->alter('splide_options', $options, $settings, $optionset);
    // Disabled irrelevant options when lacking of slides.
    $this->unsplide($options, $settings);
  }

  /**
   * Prepare settings for the known module features, not necessarily users'.
   */
  protected function prepareSettings(array &$element, array &$build): array {
    $this->hashtag($build);
    $this->hashtag($build, 'options');

    $settings = &$build['#settings'];
    $this->verifySafely($settings);

    $options   = &$build['#options'];
    $blazies   = $settings['blazies'];
    $config    = $settings['splides'];
    $id        = $blazies->get('css.id', $settings['id'] ?? NULL);
    $id        = $settings['id'] = $this->getHtmlId('splide', $id);
    $optionset = Splide::verifyOptionset($build, $settings['optionset']);

    // Additional settings, Splide supports nav for Vanilla, unlike Slick.
    $count  = $blazies->get('count') ?: $settings['count'] ?? 0;
    $total  = count($build['items']);
    $count  = $count ?: $total;
    $navpos = $settings['navpos'] ?? NULL;
    $nav    = $blazies->is('nav', !empty($settings['nav']));

    // Make it work with ElevateZoomPlus.
    if (!$blazies->is('nav_overridden')) {
      $nav = $nav || (!empty($settings['optionset_nav'])
        && isset($build['items'][1]));
    }

    // Few dups are generic and needed by Blazy to interop Slick and Splide.
    // The total is the original unmodified count, tricked at grids.
    $blazies->set('css.id', $id)
      ->set('count', $count)
      ->set('total', $total)
      ->set('is.nav', $nav);

    $options['count'] = $count;
    $options['total'] = $total;

    // Affected by formatter Override main optionset.
    $this->prepareOptions($optionset, $options, $settings);

    // Removes pagination thumbnail effect if has no thumbnails.
    $wheel      = $options['wheel'] ?? $optionset->getSetting('wheel');
    $pagination = $options['pagination'] ?? $optionset->getSetting('pagination');
    $pagination = Splide::toBoolOrString($pagination);
    $vertical   = $optionset->getSetting('vertical');
    $fx         = $pagination
      && (!empty($settings['thumbnail_style'])
      || !empty($settings['thumbnail']));

    // Currently only concerns for those affecting libraries here.
    $data = [
      'count'          => $count,
      'total'          => $total,
      'down'           => $optionset->getSetting('down'),
      'nav'            => $nav,
      'navpos'         => ($nav && $navpos) ? $navpos : '',
      'pagination_fx'  => $fx ? $settings['thumbnail_effect'] : '',
      'pagination_tab' => $pagination && !empty($settings['pagination_texts']),
      'animation'      => $options['animation'] ?? $optionset->getSetting('animation'),
      'transition'     => $options['type'] ?? $optionset->getSetting('type'),
      'vertical'       => ($options['direction'] ?? FALSE) == 'ttb' || $vertical,
      'autoplay'       => $options['autoplay'] ?? $optionset->getSetting('autoplay'),
      'autoscroll'     => $optionset->getSetting('autoScroll'),
      'intersection'   => $optionset->getSetting('intersection'),
      'wheel'          => $wheel,
    ];

    foreach ($data as $key => $value) {
      // @todo remove settings after migration.
      $settings[$key] = $value;
      $config->set(is_bool($value) ? 'is.' . $key : $key, $value);
    }

    if ($blazies->is('nav')) {
      $optionset_nav = $build['#optionset_nav'] = $this->loadSafely($settings['optionset_nav']);

      $data = [
        'vertical_nav' => $optionset_nav->getSetting('direction') == 'ttb',
        'wheel' => $options['wheel'] ?? $optionset_nav->getSetting('wheel'),
      ];

      foreach ($data as $key => $value) {
        // @todo remove settings after migration.
        $settings[$key] = $value;
        $config->set(is_bool($value) ? 'is.' . $key : $key, $value);
      }
    }
    else {
      // Pass extra attributes such as those from Commerce product variations to
      // theme_splide() since we have no asNavFor wrapper here.
      if ($attributes = $element['#attributes'] ?? []) {
        $attrs = $this->toHashtag($build, 'attributes');
        $build['#attributes'] = $this->merge($attributes, $attrs);
      }
    }

    // Supports Blazy multi-breakpoint or lightbox images if provided.
    // Cases: Blazy within Views gallery, or references without direct image.
    if ($data = $blazies->get('first.data')) {
      if (is_array($data)) {
        $this->isBlazy($settings, $data);
      }
    }

    foreach ($this->skinManager->getComponents() as $key) {
      $config->set('libs.' . $key, !empty($settings[$key]));
    }

    $element['#settings'] = $settings;
    return $settings;
  }

  /**
   * Returns a cacheable renderable array of a single splide instance.
   *
   * @param array $build
   *   An associative array containing:
   *   - items: An array of splide contents: text, image or media.
   *   - #options: An array of key:value pairs of custom JS overrides.
   *   - #optionset: The cached optionset object to avoid multiple invocations.
   *   - #settings: An array of key:value pairs of HTML/layout related settings.
   *
   * @return array
   *   The cacheable renderable array of a splide instance, or empty array.
   */
  protected function splide(array $build): array {
    foreach (SplideDefault::themeProperties() as $key => $default) {
      $k = $key == 'items' ? $key : "#$key";
      $build[$k] = $this->toHashtag($build, $key, $default);
    }

    return empty($build['items']) ? [] : [
      '#theme'      => 'splide',
      '#items'      => [],
      '#build'      => $build,
      '#pre_render' => [[$this, 'preRenderSplide']],
    ];
  }

  /**
   * Generates items as a grid item display.
   */
  private function generateGridItem(array $items, array $settings): \Generator {
    $blazies = $settings['blazies'];
    $config  = $settings['splides'];

    foreach ($items as $delta => $item) {
      if (!is_array($item)) {
        continue;
      }

      $sets = $this->toHashtag($item);
      $sets += $settings;
      $attrs = $this->toHashtag($item, 'attributes');
      $content_attrs = $this->toHashtag($item, 'content_attributes');
      $sets['current_item'] = 'grid';
      $sets['delta'] = $delta;

      $blazy = $sets['blazies']->reset($sets);
      $blazy->set('delta', $delta);

      // @todo remove after migrations.
      unset(
        $item['settings'],
        $item['attributes'],
        $item['content_attributes'],
        $item['item_attributes']
      );
      if (!$config->is('unplide')) {
        $attrs['class'][] = 'slide__grid';
      }

      $attrs['class'][] = 'grid--' . $delta;

      // Listens to signaled attributes via hook_alters.
      $this->gridCheckAttributes($attrs, $content_attrs, $blazies, FALSE);

      $theme = empty($settings['vanilla']) ? 'slide' : 'minimal';
      $content = [
        '#theme'    => 'splide_' . $theme,
        '#item'     => $item,
        '#delta'    => $delta,
        '#settings' => $sets,
      ];

      $slide = [
        'content' => $content,
        '#attributes' => $attrs,
        '#content_attributes' => $content_attrs,
        '#settings' => $sets,
      ];

      yield $slide;
    }
  }

  /**
   * Disabled irrelevant options when lacking of slides, unsplide softly.
   *
   * Unlike `settings.unsplide`, this doesn't destroy the markups so that
   * `settings.unsplide` can be overriden as needed unless being forced.
   */
  private function unsplide(array &$options, array $settings): void {
    $blazies = $settings['blazies'];
    if ($blazies->get('count', 0) < 2) {
      $options = array_merge($options, SplideDefault::unsplide());
    }
  }

}

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

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