From 43996042712bc2b68ef3fed63a090eb18539fdb0 Mon Sep 17 00:00:00 2001
From: Sascha Grossenbacher <saschagros@gmail.com>
Date: Thu, 5 May 2011 13:17:17 +0200
Subject: [PATCH] Issue #1134644 by Berdir: Rewrite the category filter form to use #ajax.

---
 tests/userpoints_api.test |    6 +-
 userpoints.admin.inc      |  200 +++++++++++++-----------------------------
 userpoints.api.php        |   24 +++++
 userpoints.module         |  209 ++++++++++++++++++++++++++++++++-------------
 userpoints.pages.inc      |  153 ++++++++++++++++++---------------
 userpoints.theme.inc      |   71 ---------------
 6 files changed, 320 insertions(+), 343 deletions(-)

diff --git a/tests/userpoints_api.test b/tests/userpoints_api.test
index f82710b..e984cb3 100644
--- a/tests/userpoints_api.test
+++ b/tests/userpoints_api.test
@@ -748,8 +748,7 @@ class UserpointsAdminTestCase extends UserpointsBaseTestCase {
     // Go to the listing page, verify that the user is shown.
     $row = $this->xpath('//table/tbody/tr');
     //$this->assertEqual(strip_tags((string)$row[0]->td[0]), t('@name (details)', array('@name' => $user->name)), t('User name with details link displayed.'));
-    $this->assertEqual((string)$row[0]->td[1], $category, t('Category correctly displayed.'));
-    $this->assertEqual((string)$row[0]->td[2], 10, t('Points correctly displayed.'));
+    $this->assertEqual((string)$row[0]->td[1], 10, t('Points correctly displayed.'));
 
     // Go to the transaction listing page, verify that the transaction is shown.
     $this->clickLink(t('Transactions'));
@@ -791,8 +790,7 @@ class UserpointsAdminTestCase extends UserpointsBaseTestCase {
     $this->clickLink(t('Totals'));
     $row = $this->xpath('//table/tbody/tr');
     //$this->assertEqual(strip_tags((string)$row[0]->td[0]), t('@name (details)', array('@name' => $user->name)), t('User name with details link displayed.'));
-    $this->assertEqual((string)$row[0]->td[1], $category, t('Category correctly displayed.'));
-    $this->assertEqual((string)$row[0]->td[2], 7, t('Points correctly displayed.'));
+    $this->assertEqual((string)$row[0]->td[1], 7, t('Points correctly displayed.'));
 
     // View transaction details.
     $this->clickLink(t('Transactions'));
diff --git a/userpoints.admin.inc b/userpoints.admin.inc
index 2dfdf3e..7c52e67 100644
--- a/userpoints.admin.inc
+++ b/userpoints.admin.inc
@@ -375,38 +375,31 @@ function userpoints_admin_txn_submit($form, &$form_state) {
 /**
  * Provides an administrative interface for managing points.
  */
-function userpoints_admin_points($tid = NULL) {
-  global $user;
-  $categories = userpoints_get_categories();
-  $show_category = count($categories) > 1;
+function userpoints_admin_points($form, &$form_state) {
 
-  $header = array(
-      array('data' => t('User'), 'field' => 'u.name'),
-      array('data' => t('Category'), 'field' => 't.name'),
-      array('data' => t('!Points', userpoints_translation()), 'field' => 'p.points', 'sort' => 'desc'),
-  );
+  // If this is an AJAX request, update $_GET['q'] so that table sorting and
+  // similar links are using the correct base path.
+  if ($_GET['q'] == 'system/ajax') {
+    $_GET['q'] = 'admin/config/people/userpoints';
+  }
 
+  $header = userpoints_get_list_header();
   $query = db_select('userpoints', 'p')->extend('PagerDefault')->extend('TableSort')
-                  ->fields('p', array('uid', 'points', 'tid'))
-                  ->fields('u', array('name'))
-                  ->groupBy('p.uid')
-                  ->groupBy('u.name')
-                  ->groupBy('p.points')
-                  ->groupBy('p.tid')
-                  ->groupBy('t.name')
-                  ->orderByHeader($header)
-                  ->limit(variable_get(USERPOINTS_REPORT_USERCOUNT, 30));
+    ->fields('p', array('uid', 'points', 'tid'))
+    ->fields('u', array('name'))
+    ->groupBy('p.uid')
+    ->groupBy('u.name')
+    ->groupBy('p.points')
+    ->groupBy('p.tid')
+    ->groupBy('t.name')
+    ->orderByHeader($header)
+    ->limit(variable_get(USERPOINTS_REPORT_USERCOUNT, 30));
 
   $query->join('users', 'u', 'p.uid = u.uid');
   $query->leftJoin('taxonomy_term_data', 't', 'p.tid = t.tid');
 
-  // Check for filtering.
-  if (isset($categories[$tid])) {
-    // If a category is selected, limit both the default query and the query
-    // that displays pending points to this category.
-    $query->condition('p.tid', (int)$tid);
-    $active_category = $categories[$tid];
-  }
+  $values = userpoints_filter_parse_input($form_state);
+  $active_category = userpoints_filter_query($query, $values);
 
   if (isset($active_category)) {
     drupal_set_title(t('Totals (%category category)', userpoints_translation() + array('%category' => $active_category)), PASS_THROUGH);
@@ -422,31 +415,21 @@ function userpoints_admin_points($tid = NULL) {
 
   $rows = array();
   foreach ($query->execute() as $data) {
-    $details = '';
-    if ($user->uid == $data->uid) {
-      $details = "&nbsp;&nbsp;" . l(t("(details)"), 'myuserpoints');
-    }
-    else if (userpoints_admin_access('edit')) {
-      $details = "&nbsp;&nbsp;" . l(t("(details)"), 'user/' . $data->uid . '/points');
-    }
-    $rows[] = array(
-        array('data' => trim(theme('username', array('account' => $data)) . ' ' . $details)),
-        array('data' => isset($categories[$data->tid]) ? $categories[$data->tid] : $categories[0], 'align' => 'right'),
-        array('data' => $data->points, 'align' => 'right'),
-    );
+    $rows[] = userpoints_get_list_row($data);
   }
 
-  // If there is only one category there is no sense in display the category filter dropdown.
   $output = array();
-  if ($categories > 1) {
-    $output['filter'] = drupal_get_form('userpoints_filter_cat_select', 'admin/config/people/userpoints/', $tid);
-  }
-  $output['table'] = array(
+  $output['form'] = userpoints_filter_form(NULL, $values);
+  $output['list'] = array(
+    '#type' => 'container',
+    '#id' => 'userpoints_list_wrapper',
+  );
+  $output['list']['table'] = array(
     '#theme' => 'table',
     '#header' => $header,
     '#rows' => $rows,
   );
-  $output['pager'] = array(
+  $output['list']['pager'] = array(
     '#theme' => 'pager',
   );
   return $output;
@@ -457,32 +440,16 @@ function userpoints_admin_points($tid = NULL) {
  * Displays a list of transactions.
  *
  */
-function userpoints_admin_transactions($tid = NULL) {
-
-  // Load the categories and check if there are multiples or only a single one.
-  $categories = userpoints_get_categories();
-  $show_category = count($categories) > 1;
+function userpoints_admin_transactions($form, &$form_state, $moderate) {
 
-  // Store context in the output array so that modules have access to it.
-  $output = array(
-    '#tid' => $tid,
-    '#attached' => array(
-      'css' => array(
-        drupal_get_path('module', 'userpoints') . '/userpoints.css',
-      ),
-    ),
-  );
-
-  // Only display the category selection if there is more than one category
-  // to select from. This check is specific to the categories the user has
-  // points in.
-  if ($show_category) {
-    $output['form'] = drupal_get_form('userpoints_filter_cat_select', 'admin/config/people/userpoints/transaction/', $tid);
-    $output['form']['#weight'] = -10;
+  // If this is an AJAX request, update $_GET['q'] so that table sorting and
+  // similar links are using the correct base path.
+  if ($_GET['q'] == 'system/ajax') {
+    $_GET['q'] = $moderate ? 'admin/config/people/userpoints/moderate' : 'admin/config/people/userpoints/transactions';
   }
 
   $settings = array(
-    'show_category' => $show_category,
+    'show_status' => !$moderate,
   );
   $header = userpoints_get_transaction_header($settings);
   $query = db_select('userpoints_txn', 'p')->extend('PagerDefault')->extend('TableSort')
@@ -492,21 +459,30 @@ function userpoints_admin_transactions($tid = NULL) {
     ->orderBy('p.txn_id', 'DESC')
     ->limit(variable_get(USERPOINTS_REPORT_USERCOUNT, 30));
 
+  if ($moderate) {
+    $query->condition('p.status', USERPOINTS_TXN_STATUS_PENDING);
+  }
+
   $query->leftJoin('taxonomy_term_data', 't', 'p.tid = t.tid');
 
-  // Check for filtering.
-  if (isset($categories[$tid])) {
-    // If a category is selected, limit both the default query and the query
-    // that displays pending points to this category.
-    $query->condition('p.tid', (int)$tid);
-    $active_category = $categories[$tid];
-  }
+  $values = userpoints_filter_parse_input($form_state);
+  $active_category = userpoints_filter_query($query, $values);
 
-  if (isset($active_category)) {
-    drupal_set_title(t('Transactions (%category category)', userpoints_translation() + array('%category' => $active_category)), PASS_THROUGH);
+  if ($moderate) {
+    if (isset($active_category)) {
+      drupal_set_title(t('Moderation (%category category)', userpoints_translation() + array('%category' => $active_category)), PASS_THROUGH);
+    }
+    else {
+      drupal_set_title(t('Moderation'));
+    }
   }
   else {
-    drupal_set_title(t('Transactions'));
+    if (isset($active_category)) {
+      drupal_set_title(t('Transactions (%category category)', userpoints_translation() + array('%category' => $active_category)), PASS_THROUGH);
+    }
+    else {
+      drupal_set_title(t('Transactions'));
+    }
   }
 
   $rows = array();
@@ -514,89 +490,37 @@ function userpoints_admin_transactions($tid = NULL) {
     $rows[] = userpoints_get_transaction_row($transaction, $settings);
   }
 
-  $output['table'] = array(
-    '#theme' => 'table',
-    '#header' => $header,
-    '#rows' => $rows,
-    '#empty' => t('No !Points earned', userpoints_translation()),
-    '#weight' => -5,
-    '#attributes' => array('class' => array('userpoints-transactions-list')),
-  );
-  $output['pager'] = array(
-    '#theme' => 'pager',
-    '#weight' => 0,
-  );
-
-  return $output;
-}
-
-function userpoints_admin_manage($tid = NULL) {
-  $categories = userpoints_get_categories();
-  $show_category = count($categories) > 1;
-
   // Store context in the output array so that modules have access to it.
   $output = array(
-    '#tid' => $tid,
     '#attached' => array(
       'css' => array(
         drupal_get_path('module', 'userpoints') . '/userpoints.css',
       ),
     ),
   );
+  $output['form'] = userpoints_filter_form(NULL, $values);
 
-  $settings = array(
-    'show_category' => $show_category,
-    'show_status' => FALSE,
+  $output['list'] = array(
+    '#type' => 'container',
+    '#id' => 'userpoints_list_wrapper',
   );
-  $header = userpoints_get_transaction_header($settings);
-
-  $query = db_select('userpoints_txn', 'p')->extend('PagerDefault')->extend('TableSort')
-    ->fields('p', array('txn_id', 'uid', 'time_stamp', 'points', 'operation', 'status', 'entity_type', 'entity_id', 'tid'))
-    ->condition('p.status', USERPOINTS_TXN_STATUS_PENDING)
-    ->orderByHeader($header)
-    // Enforce consistent sort order.
-    ->orderBy('p.txn_id', 'DESC')
-    ->limit(variable_get(USERPOINTS_REPORT_USERCOUNT, 30));
 
-  $query->leftJoin('taxonomy_term_data', 't', 'p.tid = t.tid');
-
-  // Check for filtering. isset() is used because 0 is a valid value
-  // (Uncategorized).
-  if (isset($tid)) {
-    // If a category is selected, limit both the default query and the query
-    // that displays pending points to this category.
-    $query->condition('p.tid', (int)$tid);
-    $active_category = $categories[$tid];
-  }
-
-  if (isset($active_category)) {
-    drupal_set_title(t('Moderation (%category category)', userpoints_translation() + array('%category' => $active_category)), PASS_THROUGH);
-  }
-  else {
-    drupal_set_title(t('Moderation'));
-  }
-  $rows = array();
-  foreach ($query->execute() as $data) {
-    $rows[] = userpoints_get_transaction_row($data, $settings);
-  }
-
-  if ($show_category) {
-    $output['filter'] = drupal_get_form('userpoints_filter_cat_select', 'admin/config/people/userpoints/moderate/', $tid);
-  }
-  $output['table'] = array(
+  $output['list']['table'] = array(
     '#theme' => 'table',
     '#header' => $header,
     '#rows' => $rows,
-    '#empty' => t('No !points awaiting moderation', userpoints_translation()),
-    '#attributes' => array('class' => array('userpoints-moderation-list')),
+    '#empty' => $moderate ? t('No !points awaiting moderation', userpoints_translation()) : t('No !Points earned', userpoints_translation()),
+    '#weight' => -5,
+    '#attributes' => array('class' => array($moderate ? 'userpoints-moderation-list' : 'userpoints-transactions-list')),
   );
-  $output['pager'] = array(
+  $output['list']['pager'] = array(
     '#theme' => 'pager',
+    '#weight' => 0,
   );
+
   return $output;
 }
 
-
 function userpoints_confirm_approve($form, $form_state, $operation, $transaction) {
   $form = array(
     'txn_id' => array(
diff --git a/userpoints.api.php b/userpoints.api.php
index c9349a7..ac545f6 100644
--- a/userpoints.api.php
+++ b/userpoints.api.php
@@ -30,4 +30,28 @@ function hook_userpoints_info() {
       'admin description' => t('Expire an existing transaction'),
     )
   );
+}
+
+/**
+ * Allows to customize the output of a the users by points page.
+ *
+ * @param $output
+ *   Render array with the content.
+ *
+ * @see userpoints_list_users().
+ */
+function hook_userpoints_list_alter(&$output) {
+
+}
+
+/**
+ * Allows to customize the output of a the my userpoints page.
+ *
+ * @param $output
+ *   Render array with the content.
+ *
+ * @see userpoints_list_transactions().
+ */
+function hook_userpoints_list_transactions_alter(&$output) {
+
 }
\ No newline at end of file
diff --git a/userpoints.module b/userpoints.module
index 217796a..87970ff 100644
--- a/userpoints.module
+++ b/userpoints.module
@@ -139,16 +139,14 @@ function userpoints_menu() {
       'title' => '!Points',
       'title arguments' => userpoints_translation(),
       'description' => strtr('Manage !points', userpoints_translation()),
-      'page callback' => 'userpoints_admin_points',
+      'page callback' => 'drupal_get_form',
+      'page arguments' => array('userpoints_admin_points'),
       'access callback' => 'userpoints_admin_access',
       'access arguments' => array('list'),
       'file' => 'userpoints.admin.inc',
   );
   $items['admin/config/people/userpoints/list'] = array(
       'title' => 'Totals',
-      'description' => strtr('List users by !points', userpoints_translation()),
-      'access callback' => 'userpoints_admin_access',
-      'access arguments' => array('list'),
       'file' => 'userpoints.admin.inc',
       'type' => MENU_DEFAULT_LOCAL_TASK,
       'weight' => -2,
@@ -157,7 +155,8 @@ function userpoints_menu() {
       'title' => 'Transactions',
       'title arguments' => userpoints_translation(),
       'description' => 'List transactions',
-      'page callback' => 'userpoints_admin_transactions',
+      'page callback' => 'drupal_get_form',
+      'page arguments' => array('userpoints_admin_transactions', FALSE),
       'access callback' => 'userpoints_admin_access',
       'access arguments' => array('edit'),
       'file' => 'userpoints.admin.inc',
@@ -169,7 +168,8 @@ function userpoints_menu() {
       'title' => 'Moderation',
       'title arguments' => userpoints_translation(),
       'description' => strtr('Review !points in moderation', userpoints_translation()),
-      'page callback' => 'userpoints_admin_manage',
+      'page callback' => 'drupal_get_form',
+      'page arguments' => array('userpoints_admin_transactions', TRUE),
       'access callback' => 'userpoints_admin_access',
       'access arguments' => array('moderate'),
       'file' => 'userpoints.admin.inc',
@@ -205,8 +205,8 @@ function userpoints_menu() {
   $items['userpoints'] = array(
       'title' => 'Users by !points',
       'title arguments' => userpoints_translation(),
-      'page callback' => 'userpoints_list_users',
-      'page arguments' => array(NULL),
+      'page callback' => 'drupal_get_form',
+      'page arguments' => array('userpoints_list_users'),
       'access arguments' => array('view userpoints'),
       'file' => 'userpoints.pages.inc',
       'type' => MENU_NORMAL_ITEM,
@@ -224,7 +224,19 @@ function userpoints_menu() {
   $items['myuserpoints'] = array(
     'title' => 'My !points',
     'title arguments' => userpoints_translation(),
-    'page callback' => 'userpoints_list_my_userpoints',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('userpoints_list_transactions'),
+    'access callback' => 'userpoints_access_my_points',
+    'file' => 'userpoints.pages.inc',
+    'type' => MENU_NORMAL_ITEM,
+    'menu_name' => 'user-menu',
+  );
+
+  $items['myuserpoints/%'] = array(
+    'title' => 'My !points',
+    'title arguments' => userpoints_translation(),
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('userpoints_list_transactions', NULL, 1),
     'access callback' => 'userpoints_access_my_points',
     'file' => 'userpoints.pages.inc',
     'type' => MENU_NORMAL_ITEM,
@@ -234,8 +246,8 @@ function userpoints_menu() {
   $items['user/%user/points'] = array(
     'title' => '!Points',
     'title arguments' => userpoints_translation(),
-    'page callback' => 'userpoints_list_my_userpoints',
-    'page arguments' => array(1),
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('userpoints_list_transactions', 1),
     'access callback' => 'userpoints_access_my_points',
     'access arguments' => array(1),
     'file' => 'userpoints.pages.inc',
@@ -417,33 +429,14 @@ function userpoints_permission() {
  */
 function userpoints_theme() {
   return array(
-      'userpoints_list_users' => array(
-          'variables' => array(
-              'header' => NULL,
-              'rows' => NULL,
-              'tid' => NULL,
-              'pager_limit' => NULL,
-          ),
-          'file' => 'userpoints.theme.inc',
-      ),
-      'userpoints_list_users_header' => array(
-          'variables' => array(),
-          'file' => 'userpoints.theme.inc',
-      ),
-      'userpoints_view_category' => array(
-          'render element' => 'element',
-          'file' => 'userpoints.theme.inc',
-      ),
-      'userpoints_view_item' => array(
-          'render element' => 'element',
-          'file' => 'userpoints.theme.inc',
-      ),
-      'userpoints_list_users_row' => array(
-          'variables' => array(
-              'row' => NULL,
-          ),
-          'file' => 'userpoints.theme.inc',
-      ),
+    'userpoints_view_category' => array(
+      'render element' => 'element',
+      'file' => 'userpoints.theme.inc',
+    ),
+    'userpoints_view_item' => array(
+      'render element' => 'element',
+      'file' => 'userpoints.theme.inc',
+    ),
   );
 }
 
@@ -1328,33 +1321,89 @@ function userpoints_get_points_list($account = NULL) {
 /**
  * Provides a dropdown to filter by category.
  */
-function userpoints_filter_cat_select($form, &$form_state, $path, $tid, $account = NULL) {
-  $current_cat = url($path . $tid);
+function userpoints_filter_form($account = NULL, $values = array()) {
+  $categories = userpoints_get_categories($account);
   $form = array();
-  $formname = 'catselect';
-
-  $cats = userpoints_get_categories($account);
-  $options = array();
-  $options[url(substr($path, 0, strlen($path) - 1))] = t('Display all');
-  foreach ($cats as $key => $value) {
-    $options[url($path . $key)] = $value;
-  }
-  $form['catselect'] = array(
+  if (count($categories) > 1) {
+    $categories = array('all' => t('Display all')) + $categories;
+    $form['tid'] = array(
       '#type' => 'select',
-      '#name' => $formname,
-      '#id' => $formname,
       '#title' => t('Filter by category'),
-      '#default_value' => $current_cat,
-      '#options' => $options,
-      '#multiple' => FALSE,
-      '#required' => FALSE,
-      '#attributes' => array('onChange' => "top.location.href=document.getElementById('$formname').options[document.getElementById('$formname').selectedIndex].value"),
-  );
+      '#options' => $categories,
+      '#default_value' => isset($values['tid']) ? $values['tid'] : 'all',
+      '#ajax' => array(
+        'callback' => 'userpoints_filter_form_ajax_callback',
+        'wrapper' => 'userpoints_list_wrapper',
+      ),
+    );
+  }
 
   return $form;
 }
 
 /**
+ * Ajax callback for userpoints filter form.
+ */
+function userpoints_filter_form_ajax_callback($form, $form_state) {
+  $commands = array();
+  // First, replace the points listing.
+  $commands[] = ajax_command_replace('#userpoints_list_wrapper', drupal_render($form['list']));
+
+  // Bartik uses the page-title id, Seven has a class of that name. Replace
+  // the title for both of them and assume that most other themes use the
+  // same.
+  $commands[] = ajax_command_html('h1#page-title, h1.page-title', drupal_get_title());
+
+  return array('#type' => 'ajax', '#commands' => $commands);
+}
+
+/**
+ * Parse input and generate an values array.
+ *
+ * @param $form_state
+ *   Form state with the submitted values.
+ * @param $tid
+ *   Category id to be used as a default.
+ *
+ * @return
+ *   Array of values to be used with userpoints_filter_form().
+ */
+function userpoints_filter_parse_input($form_state, $tid = NULL) {
+  // Enforce tid if passed in through the URL.
+  $values = isset($form_state['values']) ? $form_state['values'] : array('tid' => NULL);
+  if (!isset($values['tid'])) {
+    if (isset($_GET['tid'])) {
+      $values['tid'] = $_GET['tid'];
+    }
+    elseif ($tid) {
+      $values['tid'] = $tid;
+    }
+  }
+
+  if (isset($values['tid'])) {
+    // Add tid argument to GET.
+    $_GET['tid'] = $values['tid'];
+  }
+
+  return $values;
+}
+
+/**
+ * Filter a query according to the selected filters.
+ */
+function userpoints_filter_query(SelectQueryInterface $query, $values) {
+    // Check for filtering. isset() is used because 0 is a valid value
+  // (Uncategorized).
+  if (isset($values['tid']) && $values['tid'] != 'all') {
+    // If a category is selected, limit both the default query and the query
+    // that displays pending points to this category.
+    $query->condition('p.tid', (int)$values['tid']);
+    $categories = userpoints_get_categories();
+    return $categories[$values['tid']];
+  }
+}
+
+/**
  * Implements hook_block_info().
  */
 function userpoints_block_info() {
@@ -1846,7 +1895,7 @@ function userpoints_get_transaction_actions($transaction, $show_view = TRUE) {
  */
 function userpoints_get_transaction_header($settings) {
   $settings += array(
-    'show_category' => FALSE,
+    'show_category' => count(userpoints_get_categories()) > 1,
     'show_user' => TRUE,
     'show_status' => TRUE,
   );
@@ -1872,6 +1921,44 @@ function userpoints_get_transaction_header($settings) {
 }
 
 /**
+ * Returns the header array definition for userpoints listings.
+ */
+function userpoints_get_list_header() {
+  $header = array(
+    array('data' => t('User'), 'field' => 'u.name'),
+  );
+  if (count(userpoints_get_categories()) > 1) {
+    $header[] = array('data' => t('Category'), 'field' => 't.name');
+  }
+  $header[] = array('data' => t('!Points', userpoints_translation()), 'field' => 'p.points', 'sort' => 'desc');
+  return $header;
+}
+
+function userpoints_get_list_row($data) {
+  global $user;
+
+  $categories = userpoints_get_categories();
+
+  $details = '';
+  if ($user->uid == $data->uid) {
+    $details = "&nbsp;&nbsp;" . l(t("details"), 'myuserpoints');
+  }
+  else if (userpoints_admin_access('edit')) {
+    $details = "&nbsp;&nbsp;" . l(t("details"), 'user/' . $data->uid . '/points');
+  }
+
+  $name = theme('username', array('account' => $data));
+  $row = array(
+    array('data' => $details ? t('!name (!details)', array('!name' => $name, '!details' => $details)) : $name),
+  );
+  if (count($categories) > 1) {
+    $row[] = array('data' => isset($categories[$data->tid]) ? $categories[$data->tid] : $categories[0], 'align' => 'right');
+  }
+  $row[] = array('data' => $data->points, 'align' => 'right');
+  return $row;
+}
+
+/**
  * Returns a single row for a transaction listing.
  * @param $transaction
  *   Transaction object.
@@ -1886,7 +1973,6 @@ function userpoints_get_transaction_header($settings) {
  */
 function userpoints_get_transaction_row($transaction, $settings = array()) {
   $settings += array(
-    'show_category' => TRUE,
     'show_user' => TRUE,
     'show_status' => TRUE,
   );
@@ -1911,8 +1997,9 @@ function userpoints_get_transaction_row($transaction, $settings = array()) {
     'data' => $transaction->points,
     'class' => array('userpoints-transactions-field-points', 'userpoints-transaction-points-' . ($transaction->points > 0 ? 'positive' : 'negative')),
   );
-  if ($settings['show_category']) {
-    $categories = userpoints_get_categories();
+
+  $categories = userpoints_get_categories();
+  if (count($categories) > 1) {
     $row['data'][] = array(
       'data' => isset($categories[$transaction->tid]) ? $categories[$transaction->tid] : $categories[0],
       'class' => array('userpoints-transactions-field-category'),
diff --git a/userpoints.pages.inc b/userpoints.pages.inc
index 37fcbbe..9340d7c 100644
--- a/userpoints.pages.inc
+++ b/userpoints.pages.inc
@@ -12,40 +12,24 @@
  * @param $account
  *   For which account to display. Defaults to the current user.
  */
-function userpoints_list_my_userpoints($account = NULL, $tid = NULL) {
+function userpoints_list_transactions($form, &$form_state, $account = NULL, $tid = NULL) {
+
+  // If this is an AJAX request, update $_GET['q'] so that table sorting and
+  // similar links are using the correct base path.
+  if ($_GET['q'] == 'system/ajax') {
+    $q = 'myuserpoints';
+    if (!empty($account)) {
+      $q = 'user/' . $account->uid . '/points';
+    }
+    $_GET['q'] = $q;
+  }
+
   if (empty($account)) {
     global $user;
     $account = $user;
   }
 
-  if (!is_object($account)) {
-    return MENU_ACCESS_DENIED;
-  }
-
-  // Load the categories and check if there are multiples or only a single one.
-  $categories = userpoints_get_categories();
-  $show_category = count($categories) > 1;
-
-  // Store context in the output array so that modules have access to it.
-  $output = array(
-    '#account' => $account,
-    '#tid' => $tid,
-    '#attached' => array(
-      'css' => array(
-        drupal_get_path('module', 'userpoints') . '/userpoints.css',
-      ),
-    ),
-  );
-
-  // Only display the category selection if there is more than one category
-  // to select from. This check is specific to the categories the user has
-  // points in.
-  if ($show_category) {
-    $output['form'] = drupal_get_form('userpoints_filter_cat_select', 'myuserpoints/', $tid, $account);
-    $output['form']['#weight'] = -10;
-  }
   $settings = array(
-    'show_category' => $show_category,
     'show_user' => FALSE,
   );
   $header = userpoints_get_transaction_header($settings);
@@ -60,20 +44,14 @@ function userpoints_list_my_userpoints($account = NULL, $tid = NULL) {
 
   $query->leftJoin('taxonomy_term_data', 't', 'p.tid = t.tid');
 
-  $unapproved_query = db_select('userpoints_txn')
+  $unapproved_query = db_select('userpoints_txn', 'p')
     ->condition('uid', $account->uid)
     ->condition('status', USERPOINTS_TXN_STATUS_PENDING);
   $unapproved_query->addExpression('SUM(points)');
 
-  // Check for filtering. isset() is used because 0 is a valid value
-  // (Uncategorized).
-  if (isset($tid)) {
-    // If a category is selected, limit both the default query and the query
-    // that displays pending points to this category.
-    $query->condition('p.tid', (int)$tid);
-    $active_category = $categories[$tid];
-    $unapproved_query->condition('tid', (int)$tid);
-  }
+  $values = userpoints_filter_parse_input($form_state, $tid);
+  $active_category = userpoints_filter_query($query, $values);
+  userpoints_filter_query($unapproved_query, $values);
 
   if (isset($active_category)) {
     drupal_set_title(t('!Points for @username (%category category)', userpoints_translation() + array('%category' => $active_category, '@username' => format_username($account))), PASS_THROUGH);
@@ -89,7 +67,23 @@ function userpoints_list_my_userpoints($account = NULL, $tid = NULL) {
     $rows[] = userpoints_get_transaction_row($transaction, $settings);
   }
 
-  $output['table'] = array(
+  // Store context in the output array so that modules have access to it.
+  $output = array(
+    '#account' => $account,
+    '#attached' => array(
+      'css' => array(
+        drupal_get_path('module', 'userpoints') . '/userpoints.css',
+      ),
+    ),
+  );
+
+  $output['form'] = userpoints_filter_form($account, $values);
+
+  $output['list'] = array(
+    '#type' => 'container',
+    '#id' => 'userpoints_list_wrapper',
+  );
+  $output['list']['table'] = array(
     '#theme' => 'table',
     '#header' => $header,
     '#rows' => $rows,
@@ -97,7 +91,7 @@ function userpoints_list_my_userpoints($account = NULL, $tid = NULL) {
     '#weight' => -5,
     '#attributes' => array('class' => array('userpoints-myuserpoints-list')),
   );
-  $output['pager'] = array(
+  $output['list']['pager'] = array(
     '#markup' => theme('pager'),
     '#weight' => 0,
   );
@@ -108,7 +102,7 @@ function userpoints_list_my_userpoints($account = NULL, $tid = NULL) {
     ->fetchField();
 
   // Display both pending and approved points in a simple table.
-  $output['summary_table'] = array(
+  $output['list']['summary_table'] = array(
     '#theme' => 'table',
     '#header' => array(
       array(
@@ -118,7 +112,7 @@ function userpoints_list_my_userpoints($account = NULL, $tid = NULL) {
     ),
     '#rows' => array(
       array(
-        'data' => array(t('Approved !points', userpoints_translation()), userpoints_get_current_points($account->uid, isset($tid) ? $tid : 'all')),
+        'data' => array(t('Approved !points', userpoints_translation()), userpoints_get_current_points($account->uid, isset($values['tid']) ? $values['tid'] : 'all')),
         'class' => array('userpoints-myuserpoints-total-approved'),
       ),
       array(
@@ -133,7 +127,7 @@ function userpoints_list_my_userpoints($account = NULL, $tid = NULL) {
   // For simplicity, the generated output is passed to a custom alter function.
   // This would also be possible through hook_page_alter(), but that hook is
   // hard to use.
-  drupal_alter('userpoints_myuserpoints', $output);
+  drupal_alter('userpoints_list_transactions', $output);
 
   return $output;
 }
@@ -142,37 +136,39 @@ function userpoints_list_my_userpoints($account = NULL, $tid = NULL) {
 /**
  * Lists the users and their point totals by all or by category.
  */
-function userpoints_list_users($tid = NULL) {
+function userpoints_list_users($form, &$form_state, $tid = NULL) {
+
+  // If this is an AJAX request, update $_GET['q'] so that table sorting and
+  // similar links are using the correct base path.
+  if ($_GET['q'] == 'system/ajax') {
+    $_GET['q'] = 'userpoints';
+  }
+
+  $header = userpoints_get_list_header();
 
-  $header = theme('userpoints_list_users_header');
   $query = db_select('userpoints', 'p')->extend('PagerDefault')->extend('TableSort')
-                  ->fields('p', array('uid', 'points', 'tid'))
-                  ->fields('u', array('name'))
-                  ->groupBy('p.uid')
-                  ->groupBy('u.name')
-                  ->groupBy('p.points')
-                  ->groupBy('p.tid')
-                  ->groupBy('t.name')
-                  ->orderByHeader($header)
-                  ->limit(variable_get(USERPOINTS_REPORT_USERCOUNT, 30));
-
-  $query->join('users', 'u', 'p.uid = u.uid AND u.status = 1');
+    ->fields('p', array('uid', 'points', 'tid'))
+    ->fields('u', array('name'))
+    ->groupBy('p.uid')
+    ->groupBy('u.name')
+    ->groupBy('p.points')
+    ->groupBy('p.tid')
+    ->groupBy('t.name')
+    ->orderByHeader($header)
+    ->limit(variable_get(USERPOINTS_REPORT_USERCOUNT, 30));
+
+  $query->join('users', 'u', 'p.uid = u.uid');
   $query->leftJoin('taxonomy_term_data', 't', 'p.tid = t.tid');
-  $query->addField('t', 'name', 'cat');
 
-  // Check for filtering.
-  if (!is_null($tid) && $tid == 0) {
-    $query->condition('p.tid', 0);
-    $cat = t('!Uncategorized', userpoints_translation());
-  }
-  elseif ($tid > 0) {
-    $query->condition('p.tid', $tid);
-    $cat = db_query("SELECT name from {taxonomy_term_data} WHERE tid = :tid", array(':tid' => $tid))->fetchField();
+  $values = userpoints_filter_parse_input($form_state, $tid);
+  $active_category = userpoints_filter_query($query, $values);
+
+  if (isset($active_category)) {
+    drupal_set_title(t('All points (%category category)', userpoints_translation() + array('%category' => $active_category)), PASS_THROUGH);
   }
   else {
-    $cat = t('All');
+    drupal_set_title(t('All points'));
   }
-  drupal_set_title(t("@category !points", userpoints_translation() + array('@category' => $cat)));
 
   if (variable_get(USERPOINTS_REPORT_DISPLAYZERO, 1) == 0) {
     // The user would NOT like to see users with zero points.
@@ -181,9 +177,28 @@ function userpoints_list_users($tid = NULL) {
 
   $rows = array();
   foreach ($query->execute() as $data) {
-    $rows[] = theme('userpoints_list_users_row', array('row' => $data));
+    $rows[] = userpoints_get_list_row($data);
   }
-  return theme('userpoints_list_users', array('header' => $header, 'rows' => $rows, 'tid' => $tid));
+
+  $output = array();
+  $output['form'] = userpoints_filter_form(NULL, $values);
+  $output['list'] = array(
+    '#type' => 'container',
+    '#id' => 'userpoints_list_wrapper',
+  );
+  $output['list']['table'] = array(
+    '#theme' => 'table',
+    '#header' => $header,
+    '#rows' => $rows,
+  );
+  $output['list']['pager'] = array(
+    '#theme' => 'pager',
+  );
+
+  // Allow other modules and themes to customize the result.
+  drupal_alter('userpoints_list', $output);
+
+  return $output;
 }
 
 /**
diff --git a/userpoints.theme.inc b/userpoints.theme.inc
index 669b50b..a37fa96 100644
--- a/userpoints.theme.inc
+++ b/userpoints.theme.inc
@@ -5,77 +5,6 @@
  * Theme functions for userpoints.module
  */
 
-/*
- * Themes the output of users by points page.
- *
- * @return
- *   HTML of the table and a pager
- *
- * @see theme_userpoints_list_users_rows()
- */
-function theme_userpoints_list_users($attributes) {
-  $header = $attributes['header'];
-  $rows = $attributes['rows'];
-  $tid = $attributes['tid'];
-
-  // If there is only one category, there is no sense in display the category filter dropdown
-  $output = array();
-  if (count(userpoints_get_categories()) > 1) {
-    $output['form'] = drupal_get_form('userpoints_filter_cat_select', 'userpoints/', $tid);
-  }
-  $output['table'] = array(
-    '#markup' => theme('table', array('header' => $header, 'rows' => $rows)),
-  );
-  $output['pager'] = array(
-    '#markup' => theme('pager'),
-  );
-  return $output;
-}
-
-/**
- * Themes the header of the table on the "user by points" page.
- *
- * @return
- *   An header array used by theme_table() and TableSort.
- */
-function theme_userpoints_list_users_header() {
-  return array(
-      array('data' => t('User'), 'field' => 'u.name'),
-      array('data' => t('Category'), 'field' => 't.name'),
-      array('data' => t('!Points', userpoints_translation()), 'field' => 'p.points', 'sort' => 'desc'),
-  );
-}
-
-/*
- * Themes the output of a single row of the "user by points" page
- *
- * @return
- *   Array for a single row, suitable for inclusion with theme_table.
- */
-function theme_userpoints_list_users_row($attributes) {
-  global $user;
-
-  $row = $attributes['row'];
-
-  if (!$row->cat) {
-    $row->cat = t('!Uncategorized', userpoints_translation());
-  }
-
-  $details = '';
-  if ($user->uid == $row->uid) {
-    $details = "&nbsp;&nbsp;" . l(t("(details)"), 'myuserpoints');
-  }
-  else if (userpoints_admin_access('edit')) {
-    $details = "&nbsp;&nbsp;" . l(t("(details)"), 'user/' . $row->uid . '/points');
-  }
-
-  return array(
-      array('data' => theme('username', array('account' => $row)) . $details),
-      array('data' => $row->cat, 'align' => 'right'),
-      array('data' => $row->points, 'align' => 'right'),
-  );
-}
-
 /**
  * Theme implementation to display a userpoints category.
  */
-- 
1.7.4.1

