From 76101192a4a0782d678e0e58687490661fb31780 Mon Sep 17 00:00:00 2001
From: Mark Carver <mark.carver@me.com>
Date: Sun, 7 Feb 2016 16:32:06 -0600
Subject: [PATCH] Issue #2664344 by markcarver: Plugin hardcodes a lot of
 theme-able output and is missing necessary options

---
 alpha_pagination.module                       |  17 +-
 views/alpha_pagination_handler_pagination.inc | 592 +++++++++++++++++++-------
 2 files changed, 457 insertions(+), 152 deletions(-)

diff --git a/alpha_pagination.module b/alpha_pagination.module
index 5e94691..7c202f4 100644
--- a/alpha_pagination.module
+++ b/alpha_pagination.module
@@ -24,9 +24,24 @@ function alpha_pagination_views_api() {
   );
 }
 
+
+/**
+ * Implements hook_library().
+ */
+function alpha_pagination_library() {
+  $libraries['alpha_pagination'] = array(
+    'title' => 'Views Alpha Pagination',
+    'website' => 'https://www.drupal.org/project/alpha_pagination',
+    'css' => array(
+      drupal_get_path('module', 'alpha_pagination') . '/css/alpha_pagination.css' => array(),
+    ),
+  );
+  return $libraries;
+}
+
 /**
  * Implements hook_node_presave().
  */
 function alpha_pagination_node_presave($entity) {
   cache_clear_all('alpha_pagination:*', 'cache', TRUE);
-}
\ No newline at end of file
+}
diff --git a/views/alpha_pagination_handler_pagination.inc b/views/alpha_pagination_handler_pagination.inc
index 1853a35..a772368 100644
--- a/views/alpha_pagination_handler_pagination.inc
+++ b/views/alpha_pagination_handler_pagination.inc
@@ -37,14 +37,16 @@ class alpha_pagination_handler_pagination extends views_handler_area {
       'default' => 'title',
       'translatable' => TRUE,
     );
-    $options['paginate_view_numbers'] = array(
-      'default' => 0,
-      'translatable' => FALSE,
-    );
+
+    // Classes.
     $options['paginate_class'] = array(
       'default' => 'alpha-pagination',
       'translatable' => FALSE,
     );
+    $options['paginate_list_class'] = array(
+      'default' => 'alpha-pagination-list',
+      'translatable' => FALSE,
+    );
     $options['paginate_active_class'] = array(
       'default' => 'active',
       'translatable' => FALSE,
@@ -53,6 +55,43 @@ class alpha_pagination_handler_pagination extends views_handler_area {
       'default' => 'inactive',
       'translatable' => FALSE,
     );
+    $options['paginate_link_class'] = array(
+      'default' => '',
+      'translatable' => FALSE,
+    );
+
+    // All.
+    $options['paginate_all_display'] = array(
+      'default' => 1,
+      'translatable' => FALSE,
+    );
+    $options['paginate_all_class'] = array(
+      'default' => 'all',
+      'translatable' => FALSE,
+    );
+    $options['paginate_all_label'] = array(
+      'default' => t('All'),
+      'translatable' => TRUE,
+    );
+    $options['paginate_all_position'] = array(
+      'default' => 'after',
+      'translatable' => FALSE,
+    );
+
+    // Numeric.
+    $options['paginate_view_numbers'] = array(
+      'default' => 0,
+      'translatable' => FALSE,
+    );
+    $options['paginate_numeric_position'] = array(
+      'default' => 'before',
+      'translatable' => FALSE,
+    );
+    $options['paginate_numeric_class'] = array(
+      'default' => 'numeric',
+      'translatable' => FALSE,
+    );
+
     return $options;
   }
 
@@ -62,7 +101,7 @@ class alpha_pagination_handler_pagination extends views_handler_area {
    * a cache-clear is performed. This eliminates that step.
    */
   function options_submit(&$form, &$form_state) {
-    cache_clear_all('alpha_pagination:*', 'cache', TRUE);
+    cache_clear_all($this->getCid(), 'cache', TRUE);
   }
 
   /**
@@ -71,7 +110,7 @@ class alpha_pagination_handler_pagination extends views_handler_area {
   function options_form(&$form, &$form_state) {
     parent::options_form($form, $form_state);
     $form['pre_letter_path'] = array(
-      '#title' => t('Path to results view.'),
+      '#title' => t('Path to results view'),
       '#type' => 'textfield',
       '#size' => 60,
       '#default_value' => $this->options['pre_letter_path'],
@@ -98,40 +137,139 @@ class alpha_pagination_handler_pagination extends views_handler_area {
       }
     }
     $form['paginate_view_field'] = array(
-      '#title' => t('View field to paginate against.'),
+      '#title' => t('View field to paginate against'),
       '#type' => 'select',
       '#options' => $fields,
       '#default_value' => $this->options['paginate_view_field'],
       '#description' => t('This will be the content field that drives the pagination.'),
     );
-    $form['paginate_view_numbers'] = array(
-      '#title' => t('Include Numeric Values'),
-      '#type' => 'select',
-      '#options' => array(
-        0 => t('No'),
-        1 => t('Yes'),
-      ),
-      '#default_value' => $this->options['paginate_view_numbers'],
-      '#description' => t('Include the numbers 0-9 in our pagination'),
+
+
+    // Class options.
+    $form['paginate_classes'] = array(
+      '#type' => 'fieldset',
+      '#title' => t('Classes'),
+      '#description' => t('Provide additional CSS classes on elements in the pagination; separated by spaces.'),
+      '#collapsible' => TRUE,
     );
     $form['paginate_class'] = array(
-      '#title' => t('CSS Class'),
+      '#title' => t('Wrapper'),
       '#type' => 'textfield',
       '#default_value' => $this->options['paginate_class'],
-      '#description' => t('Add custom CSS Classes to wrap around each pagination option.'),
+      '#description' => t('The wrapper around the item list.'),
+      '#fieldset' => 'paginate_classes',
+    );
+    $form['paginate_list_class'] = array(
+      '#title' => t('Item List'),
+      '#type' => 'textfield',
+      '#default_value' => $this->options['paginate_list_class'],
+      '#description' => t('The item list.'),
+      '#fieldset' => 'paginate_classes',
     );
     $form['paginate_active_class'] = array(
-      '#title' => t('CSS active item class'),
+      '#title' => t('Active item'),
       '#type' => 'textfield',
       '#default_value' => $this->options['paginate_active_class'],
-      '#description' => t('Add CSS class to active LI elements.'),
+      '#description' => t('The active list item.'),
+      '#fieldset' => 'paginate_classes',
     );
     $form['paginate_inactive_class'] = array(
-      '#title' => t('CSS inactive item class'),
+      '#title' => t('Inactive item'),
       '#type' => 'textfield',
       '#default_value' => $this->options['paginate_inactive_class'],
-      '#description' => t('Add CSS class to disabled LI elements.'),
-      '#dependency' => array('edit-options-paginate-toggle-empty' => array(1)),
+      '#description' => t('The inactive list item(s) that are not links, a.k.a. "no results".'),
+      '#fieldset' => 'paginate_classes',
+    );
+    $form['paginate_link_class'] = array(
+      '#title' => t('Link'),
+      '#type' => 'textfield',
+      '#default_value' => $this->options['paginate_link_class'],
+      '#description' => t('The link element.'),
+      '#fieldset' => 'paginate_classes',
+    );
+
+    // "All" options.
+    $form['paginate_all_options'] = array(
+      '#type' => 'fieldset',
+      '#title' => t('"All" item'),
+      '#collapsible' => TRUE,
+    );
+    $form['paginate_all_display'] = array(
+      '#type' => 'select',
+      '#title' => t('Display the "All" item'),
+      '#options' => array(
+        0 => t('No'),
+        1 => t('Yes'),
+      ),
+      '#default_value' => $this->options['paginate_all_display'],
+      '#description' => t('Displays the "All" link in the pagination.'),
+      '#fieldset' => 'paginate_all_options',
+    );
+    $form['paginate_all_position'] = array(
+      '#type' => 'select',
+      '#title' => t('Position'),
+      '#options' => array(
+        'before' => t('Before'),
+        'after' => t('After'),
+      ),
+      '#default_value' => $this->options['paginate_all_position'],
+      '#description' => t('Determines where the "All" item will show up in the pagination.'),
+      '#dependency' => array('edit-options-paginate-all-display' => array(1)),
+      '#fieldset' => 'paginate_all_options',
+    );
+    $form['paginate_all_label'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Label'),
+      '#default_value' => $this->options['paginate_all_label'],
+      '#description' => t('The label to use for display the "All" item in the pagination.'),
+      '#dependency' => array('edit-options-paginate-all-display' => array(1)),
+      '#fieldset' => 'paginate_all_options',
+    );
+    $form['paginate_all_class'] = array(
+      '#title' => t('Classes'),
+      '#type' => 'textfield',
+      '#default_value' => $this->options['paginate_all_class'],
+      '#description' => t('CSS classes for "All" item (on <code>&lt;li&gt;</code> element); separated by spaces.'),
+      '#dependency' => array('edit-options-paginate-all-display' => array(1)),
+      '#fieldset' => 'paginate_all_options',
+    );
+
+    // "Numeric" options.
+    $form['paginate_numeric_options'] = array(
+      '#type' => 'fieldset',
+      '#title' => t('Numeric items'),
+      '#collapsible' => TRUE,
+    );
+    $form['paginate_view_numbers'] = array(
+      '#title' => t('Display numeric items'),
+      '#type' => 'select',
+      '#options' => array(
+        0 => t('No'),
+        1 => t('Yes'),
+      ),
+      '#default_value' => $this->options['paginate_view_numbers'],
+      '#description' => t('Displays numeric (0-9) links in the pagination.'),
+      '#fieldset' => 'paginate_numeric_options',
+    );
+    $form['paginate_numeric_position'] = array(
+      '#type' => 'select',
+      '#title' => t('Position'),
+      '#options' => array(
+        'before' => t('Before'),
+        'after' => t('After'),
+      ),
+      '#default_value' => $this->options['paginate_numeric_position'],
+      '#description' => t('Determines whether numeric items are shown before or after alphabetical links in the pagination.'),
+      '#dependency' => array('edit-options-paginate-view-numbers' => array(1)),
+      '#fieldset' => 'paginate_numeric_options',
+    );
+    $form['paginate_numeric_class'] = array(
+      '#title' => t('Classes'),
+      '#type' => 'textfield',
+      '#default_value' => $this->options['paginate_numeric_class'],
+      '#description' => t('CSS classes for numeric item (on <code>&lt;li&gt;</code> element); separated by spaces.'),
+      '#dependency' => array('edit-options-paginate-view-numbers' => array(1)),
+      '#fieldset' => 'paginate_numeric_options',
     );
   }
 
@@ -168,158 +306,310 @@ class alpha_pagination_handler_pagination extends views_handler_area {
    *   A string representing the complete paginator including linked and unlinked options.
    */
   function render($empty = FALSE) {
-    // Initialize our alphabet array so it can be used regardless of caching.
-    if ($this->options['paginate_view_numbers'] == 1) {
-      $alphabet = array_merge(range('0', '9'), range('A', 'Z'));
+    // Create the wrapper.
+    $wrapper = array(
+      '#theme_wrappers' => array('container__alpha_pagination__wrapper'),
+      '#attributes' => array(),
+      '#attached' => array(
+        'library' => array(
+          array('alpha_pagination', 'alpha_pagination'),
+        )
+      ),
+    );
+    $this->addOptionClasses('paginate_class', $wrapper['#attributes']);
+
+    // Account for an argument-less call to the page.
+    if (empty($this->view->args)) {
+      $this->view->args[0] = 'all';
     }
-    else {
-      $alphabet = range('A', 'Z');
+
+    $all_label = !empty($this->options['paginate_all_label']) ? t(check_plain($this->options['paginate_all_label'])) : t('All');
+
+    // Iterate over the alphabet and populate the items for the item list.
+    $items = array();
+    foreach ($this->getItems() as $key => $is_link) {
+      $all = drupal_strtolower($key) === 'all';
+      $numeric = is_numeric($key);
+      $active = (string) $key === (string) $this->view->args[0];
+      $is_link = !$active && ($all || $is_link);
+      $label = $all ? $all_label : t(drupal_ucfirst($key));
+
+      // Theme link item.
+      if ($is_link) {
+        $item_data = array(
+          '#theme' => 'link__alpha_pagination__' . drupal_html_class($key),
+          '#text' => $label,
+          '#path' => sprintf('%s/%s', $this->options['pre_letter_path'], $key),
+          '#options' => array(
+            'attributes' => array(),
+            'html' => FALSE
+          ),
+        );
+        $this->addOptionClasses('paginate_link_class', $item_data['#options']['attributes']);
+      }
+      // Theme non-link item (a.k.a "inactive").
+      //
+      // Some themes require text that's in a "pagination" list to be wrapped
+      // in another element. Typically, this would be a link, like above,
+      // however since this is an inactive option, it should be wrapped in a
+      // themeable element that can be targeted by preprocessors if needed.
+      else {
+        $item_data = array(
+          '#type' => 'html_tag',
+          '#theme' => 'html_tag__alpha_pagination__inactive',
+          '#tag' => 'span',
+          '#value' => $label,
+        );
+      }
+
+      // Unfortunately, the implementation of item_list in D7 core does not
+      // allow render arrays to be passed as data and requires premature
+      // rendering here.
+      // @todo In D8, pass the render array directly since it can process it.
+      $item = array('data' => drupal_render($item_data));
+
+      // Add the necessary classes for item.
+      if ($all) {
+        $this->addOptionClasses('paginate_all_class', $item);
+      }
+      if ($numeric) {
+        $this->addOptionClasses('paginate_numeric_class', $item);
+      }
+      if ($active) {
+        $this->addOptionClasses('paginate_active_class', $item);
+      }
+      elseif (!$is_link) {
+        $this->addOptionClasses('paginate_inactive_class', $item);
+      }
+
+      // Add the constructed item to the list.
+      $items[] = $item;
     }
-    $alphabet[] = 'all';
 
-    // 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.
-    if ($cache = cache_get('alpha_pagination:' . md5($this->options['query']))) {
-      $has_results = $cache->data;
+    // Sanitize any classes provided for the item list.
+    $item_list = array(
+      '#theme' => 'item_list__alpha_pagination',
+      '#attributes' => array(),
+      '#items' => $items,
+    );
+    $this->addOptionClasses('paginate_list_class', $item_list['#attributes']);
+
+    // Append the item list to the wrapper.
+    $wrapper[] = $item_list;
+
+    return drupal_render($wrapper);
+  }
+
+  /**
+   * Add classes to an attributes array from a view option.
+   *
+   * @param string $option
+   *   The name of the view option that contains a space separated list 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.
+   */
+  protected function addOptionClasses($option, array &$attributes) {
+    // Sanitize any classes provided for the item list.
+    $classes = array_filter(explode(' ', $this->options[$option]));
+    foreach ($classes as &$class) {
+      $class = views_clean_css_identifier($class);
     }
-    else {
-      // Construct the actual SQL query for the view being generated. We then need to 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.
-      $query_parts = explode("\n", $this->options['query']);
-
-      // Get the base field. This will change depending on the type of view we are
-      // putting the paginator on.
-      $base_field = $this->view->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;
-        }
+
+    // Don't add any classes if it's empty, which will add an empty attribute.
+    if ($classes) {
+      if (!isset($attributes['class'])) {
+        $attributes['class'] = array();
       }
+      $attributes['class'] = array_unique(array_merge($attributes['class'], $classes));
+    }
+
+    return $classes;
+  }
+
+  /**
+   * Retrieves a cache identifier for the view, display and query, if set.
+   *
+   * @return string
+   *   A cache identifier.
+   */
+  protected function getCid() {
+    $query = !empty($this->options['query']) ? md5($this->options['query']) : '';
+    return "alpha_pagination:{$this->view->name}:{$this->view->current_display}:$query";
+  }
+
+  /**
+   * Retrieves the items used to populate the pagination item list.
+   *
+   * @return array
+   *   An associative array containing the result state as the value, keyed by
+   *   the letter, number or "all".
+   */
+  protected function getItems() {
+    // 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 = cache_get($cid)) && !empty($cache->data)) {
+      return $cache->data;
+    }
+
+    // Construct the alphabet pagination items.
+    $items = range('A', 'Z');
 
-      // 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;
+    // Append or prepend numeric items.
+    if ($this->options['paginate_view_numbers']) {
+      if ($this->options['paginate_numeric_position'] === 'after') {
+        $items =  array_merge($items, range('0', '9'));
       }
+      else {
+        $items =  array_merge(range('0', '9'), $items);
+      }
+    }
 
-      // Construct the query from the array and change the single quotes from HTML special
-      // characters back into single quotes.
-      $query = join("\n", $query_parts);
-      $query = str_replace("&#039;", '\'', $query);
-      $query = str_replace("&amp;", '&', $query);
-      $query = str_replace("&lt;", '<', $query);
-      $query = str_replace("&gt;", '>', $query);
-
-      // Based on our query, get the list of node id's that are affected. We will use these
-      // to generate our alphabetical pagination.
-      $nids = array();
-      $result = db_query($query);
-      while($idata = $result->fetchObject()) {
-        $nids[] = $idata->$base_field;
+    // Append or prepend the "all" item.
+    if ($this->options['paginate_all_display']) {
+      if ($this->options['paginate_all_position'] === 'before') {
+        $items =  array_merge(array('all'), $items);
       }
+      else {
+        $items[] =  'all';
+      }
+    }
 
-      // Initialize our results array with the FALSE state so each is disabled by default.
-      $has_results = array();
-      foreach ($alphabet as $letter) {
-        $has_results[$letter] = FALSE;
+    // Initialize the results with FALSE values so each prefix is disabled by
+    // default. They'rea later filled in as TRUE below when there is actual
+    // entity prefixes that exist.
+    $results = array_fill_keys($items, FALSE);
+
+    // Retrieve the entity prefixes.
+    if ($prefixes = $this->getEntityPrefixes()) {
+      // Ensure that "all" is TRUE if there are prefixes.
+      if (isset($results['all'])) {
+        $results['all'] = TRUE;
       }
 
-      // If we have nodes (which .... well we should), then get the distinct first letter
-      // from our field tables and mark them as TRUE so their letters link up. 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.
-      if (count($nids) > 0) {
-        switch ($this->options['paginate_view_field']) {
-          case 'title':
-            $table = 'node';
-            $field = 'title';
-            $where = 'nid';
-            break;
-          default:
-            if (strpos($this->options['paginate_view_field'], ':') === FALSE) {
-              // Format field name and table for single value fields
-              $field = $this->options['paginate_view_field'] . '_value';
-              $table = 'field_data_' . $this->options['paginate_view_field'];
-            } else {
-              // Format field name and table for compound value fields
-              $field = str_replace(':', '_', $this->options['paginate_view_field']);
-              $field_name_components = explode(':', $this->options['paginate_view_field']);
-              $table = 'field_data_' . $field_name_components[0];
-            }
-            $where = 'entity_id';
-            break;
-        }
-        $result = db_query('SELECT DISTINCT(SUBSTR(' . $field . ', 1, 1)) AS letter
-                            FROM {' . $table . '}
-                            WHERE ' . $where . ' IN ( :nids )', array(':nids' => $nids));
-        while ($idata = $result->fetchObject()) {
-          $has_results[strtoupper($idata->letter)] = TRUE;
+      // Set prefixes to TRUE if it exists from the default list that was
+      // constructed from view options above.
+      foreach ($prefixes as $prefix) {
+        if (isset($results[$prefix])) {
+          $results[$prefix] = TRUE;
         }
-        // Set the cache with the results of our efforts.
-        cache_set('alpha_pagination:' . md5($this->options['query']), $has_results, 'cache', CACHE_TEMPORARY);
       }
     }
 
-    $elements = array('paginator' => array(
-      '#type' => 'container',
-      '#attributes' => array('class' => $this->options['paginate_class']),
-      '#attached' => array('css' => array(drupal_get_path('module', 'alpha_pagination') . '/css/alpha_pagination.css' => array('group' => CSS_THEME))),
-    ));
+    // Cache the results.
+    cache_set($cid, $results, 'cache');
 
-    $items = array();
-    
-    // Account for an argummentless call to the page.
-    if (empty($this->view->args)) {
-      $this->view->args[0] = 'all';
-    }
-    
-    foreach ($alphabet as $pager_option) {
-      // Check to see if current element is active and selected
-      $item_class = ((string) $pager_option == (string) $this->view->args[0]) ? $this->options['paginate_active_class'] : '';
-      $inactive_item = $this->options['paginate_inactive_class'];
-
-      if (strtolower($pager_option) == 'all' || $has_results[$pager_option] === TRUE) {
-        $items[] = array(
-          'data' => l(t(ucfirst($pager_option)), sprintf('%s/%s', $this->options['pre_letter_path'], $pager_option)),
-          'class' => array($item_class),
-        );
+    return $results;
+  }
+
+  /**
+   * 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.
+   */
+  protected function getEntityPrefixes() {
+    $prefixes = array();
+
+    if ($entity_ids = $this->getEntityIds()) {
+      switch ($this->options['paginate_view_field']) {
+        case 'title':
+          $table = $this->view->base_table;
+          $where = $this->view->base_field;
+
+          // Extract the "title" field from the entity property info.
+          $table_data = views_fetch_data($table);
+          $entity_info = entity_get_property_info($table_data['table']['entity type']);
+          $field = isset($entity_info['properties']['title']['schema field']) ? $entity_info['properties']['title']['schema field'] : 'title';
+          break;
+
+        default:
+          if (strpos($this->options['paginate_view_field'], ':') === FALSE) {
+            // Format field name and table for single value fields
+            $field = $this->options['paginate_view_field'] . '_value';
+            $table = 'field_data_' . $this->options['paginate_view_field'];
+          } else {
+            // Format field name and table for compound value fields
+            $field = str_replace(':', '_', $this->options['paginate_view_field']);
+            $field_name_components = explode(':', $this->options['paginate_view_field']);
+            $table = 'field_data_' . $field_name_components[0];
+          }
+          $where = 'entity_id';
+          break;
       }
-      else {
-        $items[] = array(
-          'data' => t(ucfirst($pager_option)),
-          'class' => array($this->options['paginate_inactive_class']),
-        );
+      $result = db_query('SELECT DISTINCT(SUBSTR(' . $field . ', 1, 1)) AS prefix
+                            FROM {' . $table . '}
+                            WHERE ' . $where . ' IN ( :nids )', array(':nids' => $entity_ids));
+      while ($data = $result->fetchObject()) {
+        $prefixes[] = is_numeric($data->prefix) ? $data->prefix : drupal_strtoupper($data->prefix);
       }
     }
 
-    // Classes
-    $pagination_classes = explode(' ', $this->options['paginate_class']);
-    foreach ($pagination_classes as &$class) {
-      $class = views_clean_css_identifier($class);
+    return array_unique(array_filter($prefixes));
+  }
+
+  /**
+   * 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.
+   */
+  protected function getEntityIds () {
+    $query_parts = explode("\n", $this->options['query']);
+
+    // Get the base field. This will change depending on the type of view we
+    // are putting the paginator on.
+    $base_field = $this->view->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;
+      }
     }
-    $pagination_classes = implode(' ', $pagination_classes);
 
-    // If we have custom classes, we want to sanitize for views them and go forward.
-    $pagination_classes = explode(' ', $this->options['paginate_class']);
-    foreach ($pagination_classes as &$class) {
-      $class = views_clean_css_identifier($class);
+    // 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;
     }
-    $pagination_classes = implode(' ', $pagination_classes);
 
-    $elements['paginator'][] = array(
-      '#theme' => 'item_list',
-      '#attributes' => array('class' => $pagination_classes),
-      '#items' => $items,
-    );
+    // Construct the query from the array and change the single quotes from
+    // HTML special characters back into single quotes.
+    $query = join("\n", $query_parts);
+    $query = str_replace("&#039;", '\'', $query);
+    $query = str_replace("&amp;", '&', $query);
+    $query = str_replace("&lt;", '<', $query);
+    $query = str_replace("&gt;", '>', $query);
 
-    return drupal_render($elements);
+    // Based on our query, get the list of entity identifiers that are affected.
+    // These will be used to generate the pagination items.
+    $entity_ids = array();
+    $result = db_query($query);
+    while($data = $result->fetchObject()) {
+      $entity_ids[] = $data->$base_field;
+    }
+    return $entity_ids;
   }
-}
\ No newline at end of file
+
+}
-- 
2.6.4

