alpha_pagination-8.x-2.x-dev/src/AlphaPagination.php

src/AlphaPagination.php
<?php

namespace Drupal\alpha_pagination;

use Drupal\alpha_pagination\Plugin\views\area\AlphaPaginationArea;
use Drupal\Component\Utility\Html;
use Drupal\Component\Transliteration\TransliterationInterface;
use Drupal\Component\Utility\Crypt;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Database\Connection;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Utility\Token;
use Drupal\views\Views;
use Drupal\views\Plugin\views\ViewsHandlerInterface;

/**
 * A base views handler for alpha pagination.
 */
class AlphaPagination {

  /**
   * The AlphaPagination Views Handler.
   *
   * @var \Drupal\views\Plugin\views\ViewsHandlerInterface
   */
  protected $handler;

  /**
   * The Entity Field Manager Interface.
   *
   * @var \Drupal\Core\Entity\EntityFieldManagerInterface
   */
  protected $fieldManager;

  /**
   * The Language ID.
   *
   * @var string
   */
  protected $language;

  /**
   * The ModuleHandler Interface.
   *
   * @var \Drupal\Core\Extension\ModuleHandlerInterface
   */
  protected $moduleHandler;

  /**
   * The cache backend service.
   *
   * @var \Drupal\Core\Cache\CacheBackendInterface
   */
  protected $cacheBackend;

  /**
   * The Transliteration Interface.
   *
   * @var \Drupal\Component\Transliteration\TransliterationInterface
   */
  protected $transliteration;

  /**
   * The database connection.
   *
   * @var \Drupal\Core\Database\Connection
   */
  protected $database;

  /**
   * The token replacement instance.
   *
   * @var \Drupal\Core\Utility\Token
   */
  protected $token;

  /**
   * The url used for links.
   *
   * @var \Drupal\Core\Url
   */
  protected $url;

  /**
   * Contructs the AlphaPagination object.
   *
   * @param \Drupal\Core\Entity\EntityFieldManagerInterface $field_manager
   *   The Entity Field Manager Interface.
   * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
   *   The language manager service.
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
   *   The ModuleHander Interface.
   * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
   *   The cache backend service.
   * @param \Drupal\Component\Transliteration\TransliterationInterface $transliteration
   *   A PHPTransliteration object.
   * @param \Drupal\Core\Database\Connection $database
   *   The database connection.
   * @param \Drupal\Core\Utility\Token $token
   *   The token service.
   */
  public function __construct(EntityFieldManagerInterface $field_manager, LanguageManagerInterface $language_manager, ModuleHandlerInterface $module_handler, CacheBackendInterface $cache_backend, TransliterationInterface $transliteration, Connection $database, Token $token) {
    $this->fieldManager = $field_manager;
    $this->language = $language_manager->getCurrentLanguage()->getId();
    $this->moduleHandler = $module_handler;
    $this->cacheBackend = $cache_backend;
    $this->transliteration = $transliteration;
    $this->database = $database;
    $this->token = $token;
  }

  /**
   * Sets the view handler.
   *
   * @param \Drupal\views\Plugin\views\ViewsHandlerInterface $handler
   *   The views handler.
   */
  public function setHandler(ViewsHandlerInterface $handler) {
    $this->handler = $handler;
  }

  /**
   * {@inheritdoc}
   */
  public function __sleep() {
    $this->_handler = implode(':', [
      $this->handler->view->id(),
      $this->handler->view->current_display,
      $this->handler->areaType,
      $this->handler->realField ?: $this->handler->field,
      $this->language,
    ]);

    return ['_handler'];
  }

  /**
   * {@inheritdoc}
   */
  public function __wakeup() {
    list($name, $display_id, $type, $id, $language) = explode(':', $this->_handler);
    $view = Views::getView($name);
    $view->setDisplay($display_id);
    $this->handler = $view->display_handler->getHandler($type, $id);

    $this->language = $language;
    unset($this->_handler);
  }

  /**
   * Add classes to an attributes array from a view option.
   *
   * @param string[]|string $classes
   *   An array of classes or a string of classes.
   * @param array $attributes
   *   An attributes array to add the classes to, passed by reference.
   *
   * @return array
   *   An array of classes to be used in a render array.
   */
  public function addClasses($classes, array &$attributes) {
    $processed = [];

    // Sanitize any classes provided for the item list.
    foreach ((array) $classes as $v) {
      foreach (array_filter(explode(' ', $v)) as $vv) {
        $processed[] = Html::cleanCssIdentifier($vv);
      }
    }

    // Don't add any classes if it's empty, which will add an empty attribute.
    if ($processed) {
      if (!isset($attributes['class'])) {
        $attributes['class'] = [];
      }
      $attributes['class'] = array_unique(array_merge($attributes['class'], $processed));
    }

    return $classes;
  }

  /**
   * Builds a render array for displaying tokens.
   *
   * @param string $fieldset
   *   The #fieldset name to assign.
   *
   * @return array
   *   The render array for the token info.
   */
  public function buildTokenTree($fieldset = NULL) {
    static $build;

    if (!isset($build)) {
      if ($this->moduleHandler->moduleExists('token')) {
        $build = [
          '#type' => 'container',
          '#title' => t('Browse available tokens'),
        ];
        $build['help'] = [
          '#theme' => 'token_tree_link',
          '#token_types' => ['alpha_pagination'],
          '#global_types' => TRUE,
          '#dialog' => TRUE,
        ];
      }
      else {
        $build = [
          '#type' => 'fieldset',
          '#title' => 'Available tokens',
          '#collapsible' => TRUE,
          '#collapsed' => TRUE,
        ];
        $items = [];
        $token_info = alpha_pagination_token_info();
        foreach ($token_info['tokens'] as $_type => $_tokens) {
          foreach ($_tokens as $_token => $_data) {
            $items[] = "[$_type:$_token] - {$_data['description']}";
          }
        }
        $build['help'] = [
          '#theme' => 'item_list',
          '#items' => $items,
        ];
      }
    }

    return isset($fieldset) ? ['#fieldset' => $fieldset] + $build : $build;
  }

  /**
   * Extract the SQL query from the query information.
   *
   * Once extracted, place it into the options array so it is passed to the
   * render function. This code was lifted nearly verbatim from the views
   * module where the query is constructed for the ui to show the query in the
   * administrative area.
   *
   * @todo Need to make this better somehow?
   */
  public function ensureQuery() {
    if (!$this->getOption('query') && !empty($this->handler->view->build_info['query'])) {
      /** @var \SelectQuery $query */
      $query = $this->handler->view->build_info['query'];
      $quoted = $query->getArguments();
      foreach ($quoted as $key => $val) {
        if (is_array($val)) {
          $quoted[$key] = implode(', ', array_map([
            $this->database,
            'quote',
          ], $val));
        }
        else {
          $quoted[$key] = $this->database->quote($val);
        }
      }
      $this->handler->options['query'] = Html::escape(strtr($query, $quoted));
    }
  }

  /**
   * Retrieves alphabet characters, based on langcode.
   *
   * Note: Do not use range(); always be explicit when defining an alphabet.
   * This is necessary as you cannot rely on the server language to construct
   * proper alphabet characters.
   *
   * @param string $langcode
   *   The langcode to return. If the langcode does not exist, it will default
   *   to English.
   *
   * @return array
   *   An indexed array of alphabet characters, based on langcode.
   *
   * @see hook_alpha_pagination_alphabet_alter()
   */
  public function getAlphabet($langcode = NULL) {

    // Default (English).
    static $default = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
    static $alphabets;

    // If the langcode is not explicitly specified, default to global langcode.
    if (!isset($langcode)) {
      $langcode = $this->language;
    }

    // Retrieve alphabets.
    if (!isset($alphabets)) {
      // Attempt to retrieve from database cache.
      $cid = "alpha_pagination:alphabets";
      if (($cache = $this->cacheBackend->get($cid)) && !empty($cache->data)) {
        $alphabets = $cache->data;
      }
      // Build alphabets.
      else {
        // Arabic.
        $alphabets['ar'] = ['ا', 'ب', 'ت', 'ث', 'ج', 'ح', 'خ', 'د', 'ذ', 'ر', 'ز', 'س', 'ش', 'ص', 'ض', 'ط', 'ظ', 'ع', 'غ', 'ف', 'ق', 'ك', 'ل', 'م', 'ن', 'و', 'ه', 'ي'];

        // English. Initially the default value, but can be modified in alter.
        $alphabets['en'] = $default;

        // Русский (Russian).
        $alphabets['ru'] = ['А', 'Б', 'В', 'Г', 'Д', 'Е', 'Ё', 'Ж', 'З', 'И', 'Й', 'К', 'Л', 'М', 'Н', 'О', 'П', 'Р', 'С', 'Т', 'У', 'Ф', 'Х', 'Ц', 'Ч', 'Ш', 'Щ', 'Ы', 'Э', 'Ю', 'Я'];

        // Allow modules and themes to alter alphabets.
        $this->moduleHandler->alter('alpha_pagination_alphabet', $alphabets, $this);

        // Cache the alphabets.
        $this->cacheBackend->set($cid, $alphabets);
      }
    }

    // Return alphabet based on langcode.
    return isset($alphabets[$langcode]) ? $alphabets[$langcode] : $default;
  }

  /**
   * Retrieves all available alpha pagination areas.
   *
   * @param array $types
   *   The handler types to search.
   *
   * @return \Drupal\alpha_pagination\Plugin\views\area\AlphaPaginationArea[]
   *   An array of alpha pagination areas.
   */
  public function getAreaHandlers(array $types = ['header', 'footer']) {
    $areas = [];
    foreach ($types as $type) {
      foreach ($this->handler->displayHandler->getHandlers($type) as $handler) {
        if ($handler instanceof AlphaPaginationArea) {
          $areas[] = $handler;
        }
      }
    }
    return $areas;
  }

  /**
   * Retrieves the characters used to populate the pagination item list.
   *
   * @return \Drupal\alpha_pagination\AlphaPaginationCharacter[]
   *   An associative array containing AlphaPaginationCharacter objects, keyed
   *   by its value.
   */
  public function getCharacters() {
    /** @var \Drupal\alpha_pagination\AlphaPaginationCharacter[] $characters */
    static $characters;

    if (!isset($characters)) {
      $all = $this->getOption('paginate_all_display') === '1' ? $this->getOption('paginate_all_label', t('All')) : '';
      $all_value = $this->getOption('paginate_all_value', 'all');
      $numeric_label = $this->getOption('paginate_numeric_label');
      $numeric_type = $this->getOption('paginate_view_numbers', '0');
      $numeric_value = $this->getOption('paginate_numeric_value');
      $numeric_divider = $numeric_type !== '2' && $this->getOption('paginate_numeric_divider') ? ['-' => ''] : [];

      // Check to see if this query is cached. If it is, then just pull our
      // results set from it so that things can move quickly through here. We're
      // caching in the event the view has a very large result set.
      $cid = $this->getCid();
      if (($cache = $this->cacheBackend->get($cid)) && !empty($cache->data)) {
        $characters = $cache->data;
      }
      else {
        // Add alphabet characters.
        foreach ($this->getAlphabet() as $value) {
          $characters[$value] = $value;
        }

        // Append or prepend numeric item(s).
        $numeric = [];
        if ($numeric_type !== '0') {

          // Determine type of numeric items.
          if ($numeric_type === '2') {
            $numeric[$numeric_value] = Html::escape($numeric_label);
          }
          else {
            foreach ($this->getNumbers() as $value) {
              $numeric[$value] = $value;
            }
          }

          // Merge in numeric items.
          if ($this->getOption('paginate_numeric_position') === 'after') {
            $characters = array_merge($characters, $numeric_divider, $numeric);
          }
          else {
            $characters = array_merge($numeric, $numeric_divider, $characters);
          }
        }

        // Append or prepend the "all" item.
        if ($all) {
          if ($this->getOption('paginate_all_position') === 'before') {
            $characters = [$all_value => $all] + $characters;
          }
          else {
            $characters[$all_value] = $all;
          }
        }

        // Convert characters to objects.
        foreach ($characters as $value => $label) {
          $characters[$value] = new AlphaPaginationCharacter($this, $label, $value);
        }

        // Determine enabled prefixes.
        $prefixes = $this->getEntityPrefixes();
        foreach ($prefixes as $value) {
          // Ensure numeric prefixes use the numeric label, if necessary.
          if ($this->isNumeric($value) && $numeric_type === '2') {
            $value = $numeric_value;
          }
          if (isset($characters[$value])) {
            $characters[$value]->setEnabled(TRUE);
          }
        }

        // Remove all empty prefixes.
        if (!$this->getOption('paginate_toggle_empty')) {
          // Ensure "all" and numeric divider objects aren't removed.
          if ($all) {
            $prefixes[] = $all_value;
          }
          if ($numeric_divider) {
            $prefixes[] = '-';
          }
          // Determine if numeric results are not empty.
          $characters = array_filter(array_intersect_key($characters, array_flip($prefixes)));
        }

        // Remove all numeric values if they're all empty.
        if ($this->getOption('paginate_numeric_hide_empty')) {
          // Determine if numeric results are not empty.
          $numeric_results = array_filter(array_intersect_key($characters, array_flip($numeric)));
          if (!$numeric_results) {
            $characters = array_diff_key($characters, array_flip($numeric));
          }
        }

        // Cache the results.
        $this->cacheBackend->set($cid, $characters);
      }

      // Default current value to "all", if enabled, or the first character.
      $current = $all ? $all_value : '';

      // Attempt to determine if a valid argument was provided.
      $arg_count = count($this->handler->view->args);
      if ($arg_count) {
        $arg = $this->handler->view->args[$arg_count - 1];
        if ($arg && in_array($arg, array_keys($characters))) {
          $current = $arg;
        }
      }

      // Determine the first active character.
      if ($current) {
        foreach ($characters as $character) {
          if ($character->isNumeric() && $numeric_type === '2' ? $numeric_value === $current : $character->getValue() === $current) {
            $character->setActive(TRUE);
            break;
          }
        }
      }
    }

    return $characters;
  }

  /**
   * Retrieves a cache identifier for the view, display and query, if set.
   *
   * @return string
   *   A cache identifier.
   */
  public function getCid() {
    $this->ensureQuery();
    $data = [
      'langcode' => $this->language,
      'view' => $this->handler->view->id(),
      'display' => $this->handler->view->getDisplay()->getPluginId(),
      'query' => $this->getOption('query') ? md5($this->getOption('query')) : '',
      'options' => $this->handler->options,
    ];
    return 'alpha_pagination:' . Crypt::hashBase64(serialize($data));
  }

  /**
   * Construct the actual SQL query for the view being generated.
   *
   * Then parse it to short-circuit certain conditions that may exist and
   * make any alterations. This is not the most elegant of solutions, but it
   * is very effective.
   *
   * @return array
   *   An indexed array of entity identifiers.
   */
  public function getEntityIds() {
    $this->ensureQuery();
    $query_parts = explode("\n", $this->getOption('query'));

    // Get the base field. This will change depending on the type of view we
    // are putting the paginator on and eventually if we are using a
    // relationship to access it.
    $relationship = $this->getOption('paginate_view_relationship');
    if ($relationship == 'none') {
      $base_field = $this->handler->view->storage->get('base_field');
    }
    else {
      // Inspired from core/modules/views_ui/src/Form/Ajax/ConfigHandler.php
      // buildForm further exploration needed when chaining relationships.
      $relationship_handler = $this->handler->view->getHandler($this->handler->view->current_display, 'relationship', $relationship);
      $data = Views::viewsData()->get($relationship_handler['table']);
      if (
        isset($data[$relationship_handler['field']]['relationship']['base']) &&
        $this->handler->view->storage->get('base_table') == $data[$relationship_handler['field']]['relationship']['base']
      ) {
        $base_field = implode('_', [
          $data[$relationship_handler['field']]['relationship']['base'],
          $relationship_handler['table'],
          $data[$relationship_handler['field']]['relationship']['base field'],
        ]);
      }
    }
    // If we are dealing with a substring, then short circuit it as we are most
    // likely dealing with a glossary contextual filter.
    foreach ($query_parts as $k => $part) {
      if ($position = strpos($part, "SUBSTRING")) {
        $part = substr($part, 0, $position) . " 1 OR " . substr($part, $position);
        $query_parts[$k] = $part;
      }
    }

    // Evaluate the last line looking for anything which may limit the result
    // set as we need results against the entire set of data and not just what
    // is configured in the view.
    $last_line = array_pop($query_parts);
    if (substr($last_line, 0, 5) != "LIMIT") {
      $query_parts[] = $last_line;
    }

    // Construct the query from the array and change the single quotes from
    // HTML special characters back into single quotes.
    $query = implode("\n", $query_parts);
    $query = str_replace("&#039;", '\'', $query);
    $query = str_replace("&amp;", '&', $query);
    $query = str_replace("&lt;", '<', $query);
    $query = str_replace("&gt;", '>', $query);
    $query = str_replace("&quot;", '', $query);

    // Based on our query, get the list of entity identifiers that are affected.
    // These will be used to generate the pagination items.
    $entity_ids = [];
    $result = $this->database->query($query);
    while ($data = $result->fetchObject()) {
      $entity_ids[] = $data->$base_field;
    }
    return $entity_ids;
  }

  /**
   * Retrieve the distinct first character prefix from the field tables.
   *
   * Mark them as TRUE so their pagination item is represented properly.
   *
   * Note that the node title is a special case that we have to take from the
   * node table as opposed to the body or any custom fields.
   *
   * @todo This should be cleaned up more and fixed "properly".
   *
   * @return array
   *   An indexed array containing a unique array of entity prefixes.
   */
  public function getEntityPrefixes() {
    $prefixes = [];

    if ($entity_ids = $this->getEntityIds()) {
      switch ($this->getOption('paginate_view_field')) {
        case 'name':
          $table = $this->handler->view->storage->get('base_table');
          $where = $this->handler->view->storage->get('base_field');

          // Extract the "name" field from the entity property info.
          $entity_type = $this->handler->view->getBaseEntityType->id();
          $entity_info = $this->fieldManager->getBaseFieldDefinitions($entity_type);
          $field = isset($entity_info['properties']['name']['schema field']) ? $entity_info['properties']['name']['schema field'] : 'name';
          break;

        case 'title':
          $table = $this->handler->view->storage->get('base_table');
          $where = $this->handler->view->storage->get('base_field');

          // Extract the "title" field from the entity property info.
          $entity_type = $this->handler->view->getBaseEntityType()->id();
          $entity_info = $this->fieldManager->getBaseFieldDefinitions($entity_type);
          $field = isset($entity_info['properties']['title']['schema field']) ? $entity_info['properties']['title']['schema field'] : 'title';
          break;

        default:
          if (strpos($this->getOption('paginate_view_field'), ':') === FALSE) {
            // Format field name and table for single value fields.
            list($entityType, $field_name) = explode('__', $this->getOption('paginate_view_field'), 2);
            $field = $field_name . '_value';
            $table = $this->getOption('paginate_view_field');
          }
          else {
            // Format field name and table for compound value fields.
            list($entityType, $field_name) = explode('__', $this->getOption('paginate_view_field'), 2);
            $field = str_replace(':', '_', $field_name);
            $field_name_components = explode(':', $field_name);
            $table = $field_name_components[0];
          }
          $where = 'entity_id';
          break;
      }
      $result = $this->database->query('SELECT DISTINCT(SUBSTR(' . $field . ', 1, 1)) AS prefix
                          FROM {' . $table . '}
                          WHERE ' . $where . ' IN ( :nids[] )', [':nids[]' => $entity_ids]);
      while ($data = $result->fetchObject()) {
        $prefixes[] = is_numeric($data->prefix) ? $data->prefix : mb_strtoupper($this->transliteration->transliterate($data->prefix));
      }
    }
    return array_unique(array_filter($prefixes));
  }

  /**
   * Retrieves the proper label for a character.
   *
   * @param mixed $value
   *   The value of the label to retrieve.
   *
   * @return string
   *   The label.
   */
  public function getLabel($value) {
    $characters = $this->getCharacters();

    // Return an appropriate numeric label.
    if ($this->getOption('paginate_view_numbers') === '2' && $this->isNumeric($value)) {
      return $characters[$this->getOption('paginate_numeric_value')]->getLabel();
    }
    elseif (isset($characters[$value])) {
      return $characters[$value]->getLabel();
    }

    // Return the original value.
    return $value;
  }

  /**
   * Retrieves numeric characters, based on langcode.
   *
   * Note: Do not use range(); always be explicit when defining numbers.
   * This is necessary as you cannot rely on the server language to construct
   * proper numeric characters.
   *
   * @param string $langcode
   *   The langcode to return. If the langcode does not exist, it will default
   *   to English.
   *
   * @return array
   *   An indexed array of numeric characters, based on langcode.
   *
   * @see hook_alpha_pagination_numbers_alter()
   */
  public function getNumbers($langcode = NULL) {
    // Default (English).
    static $default = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
    static $numbers;

    // If the langcode is not explicitly specified, default to global langcode.
    if (!isset($langcode)) {
      $langcode = $this->language;
    }

    // Retrieve numbers.
    if (!isset($numbers)) {
      // Attempt to retrieve from database cache.
      $cid = "alpha_pagination:numbers";
      if (($cache = $this->cacheBackend->get($cid)) && !empty($cache->data)) {
        $numbers = $cache->data;
      }
      // Build numbers.
      else {
        // English. Initially the default value, but can be modified in alter.
        $numbers['en'] = $default;

        // Allow modules and themes to alter numbers.
        $this->moduleHandler->alter('alpha_pagination_numbers', $numbers, $this);

        // Cache the numbers.
        $this->cacheBackend->set($cid, $numbers);
      }
    }

    // Return numbers based on langcode.
    return isset($numbers[$langcode]) ? $numbers[$langcode] : $default;
  }

  /**
   * Retrieves an option from the view handler.
   *
   * @param string $name
   *   The option name to retrieve.
   * @param mixed $default
   *   The default value to return if not set.
   *
   * @return string
   *   The option value or $default if not set.
   */
  public function getOption($name, $default = '') {
    return (string) (isset($this->handler->options[$name]) ? $this->handler->options[$name] : $default);
  }

  /**
   * Provides the token data that is passed when during replacement.
   *
   * @param string $value
   *   The current character value being processed.
   *
   * @return array
   *   Token data.
   *
   * @see alpha_pagination_token_info()
   * @see alpha_pagination_tokens()
   */
  public function getTokens($value = NULL) {
    return [
      'alpha_pagination' => [
        'path' => $this->getUrl(),
        'value' => $value,
      ],
    ];
  }

  /**
   * Retrieves the URL for the current view.
   *
   * Note: this follows very similarly to \view::get_url to process arguments,
   * however it is in fact severely modified to account for characters appended
   * by this module.
   *
   * @return string
   *   The URL for the view or current_path().
   */
  public function getUrl() {
    if (empty($this->url)) {
      if (!empty($this->handler->view->override_url)) {
        $this->url = $this->handler->view->override_url;
        return $this->url;
      }

      $path = $this->handler->view->getPath();
      $args = $this->handler->view->args;
      // Exclude arguments that were computed, not passed on the URL.
      $position = 0;
      if (!empty($this->handler->view->argument)) {
        foreach ($this->handler->view->argument as $argument_id => $argument) {
          if (!empty($argument->options['default_argument_skip_url'])) {
            unset($args[$position]);
          }
          $position++;
        }
      }

      // Don't bother working if there's nothing to do:
      if (empty($path) || (empty($args) && strpos($path, '%') === FALSE)) {
        $path = \Drupal::service('path.current')->getPath();
        $pieces = explode('/', $path);
        // If getPath returns heading slash, remove it as it will ba
        // added later.
        if ($pieces[0] == '') {
          array_shift($pieces);
        }
        if (array_key_exists(end($pieces), $this->getCharacters())) {
          array_pop($pieces);
        }
        $this->url = implode('/', $pieces);
        return $this->url;
      }

      $pieces = [];
      $argument_keys = isset($this->handler->view->argument) ? array_keys($this->handler->view->argument) : [];
      $id = current($argument_keys);
      foreach (explode('/', $path) as $piece) {
        if ($piece != '%') {
          $pieces[] = $piece;
        }
        else {
          if (empty($args)) {
            // Try to never put % in a url; use the wildcard instead.
            if ($id && !empty($this->handler->view->argument[$id]->options['exception']['value'])) {
              $pieces[] = $this->handler->view->argument[$id]->options['exception']['value'];
            }
            else {
              // Gotta put something if there just isn't one.
              $pieces[] = '*';
            }
          }
          else {
            $pieces[] = array_shift($args);
          }

          if ($id) {
            $id = next($argument_keys);
          }
        }
      }

      // Just return the computed pieces, don't merge any extra remaining args.
      $this->url = implode('/', $pieces);
    }
    return $this->url;
  }

  /**
   * Retrieves the proper value for a character.
   *
   * @param mixed $value
   *   The value to retrieve.
   *
   * @return string
   *   The value.
   */
  public function getValue($value) {
    $characters = $this->getCharacters();

    // Return an appropriate numeric label.
    if ($this->getOption('paginate_view_numbers') === '2' && $this->isNumeric($value)) {
      return $characters[$this->getOption('paginate_numeric_value')]->getValue();
    }
    elseif (isset($characters[$value])) {
      return $characters[$value]->getLabel();
    }

    // Return the original value.
    return $value;
  }

  /**
   * Determines if value is "numeric".
   *
   * @param string $value
   *   The value to test.
   *
   * @return bool
   *   TRUE or FALSE
   */
  public function isNumeric($value) {
    return ($this->getOption('paginate_view_numbers') === '2' && $value === $this->getOption('paginate_numeric_value')) || in_array($value, $this->getNumbers());
  }

  /**
   * Parses an attribute string saved in the UI.
   *
   * @param string $string
   *   The attribute string to parse.
   * @param array $tokens
   *   An associative array of token data to use.
   *
   * @return array
   *   A parsed attributes array.
   */
  public function parseAttributes($string = NULL, array $tokens = []) {
    $attributes = [];
    if (!empty($string)) {
      $parts = explode(',', $string);
      foreach ($parts as $attribute) {
        if (strpos($attribute, '|') !== FALSE) {
          list($key, $value) = explode('|', $this->token->replace($attribute, $tokens, ['clear' => TRUE]));
          $attributes[$key] = $value;
        }
      }
    }
    return $attributes;
  }

  /**
   * {@inheritdoc}
   */
  public function validate() {
    $name = $this->handler->view->id();
    $display_id = $this->handler->view->current_display;

    // Immediately return if display doesn't have the handler in question.
    $items = $this->handler->view->getHandlers($this->handler->areaType, $display_id);
    $field = $this->handler->realField ?: $this->handler->field;
    if (!isset($items[$field])) {
      return [];
    }

    static $errors = [];
    if (!isset($errors["$name:$display_id"])) {
      $errors["$name:$display_id"] = [];

      // Show an error if not found.
      $areas = $this->getAreaHandlers();
      if (!$areas) {
        $errors["$name:$display_id"][] = t('The view "@name:@display" must have at least one configured alpha pagination area in either the header or footer to use "@field".', [
          '@field' => $field,
          '@name' => $this->handler->view->id(),
          '@display' => $this->handler->view->current_display,
        ]);
      }
      // Show an error if there is more than one instance.
      elseif (count($areas) > 1) {
        $errors["$name:$display_id"][] = t('The view "@name:@display" can only have one configured alpha pagination area in either the header or footer.', [
          '@name' => $this->handler->view->id(),
          '@display' => $this->handler->view->current_display,
        ]);
      }

    }

    return $errors["$name:$display_id"];
  }

}

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

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