From a6e5859fb9105a94480eed390c273e7831f305b9 Mon Sep 17 00:00:00 2001
From: Sascha Grossenbacher <saschagros@gmail.com>
Date: Thu, 7 Jul 2011 13:01:28 +0200
Subject: [PATCH] Issue #1070068 by Berdir: Added {userpoints_total} to track total points and max_points

---
 tests/userpoints_api.test |   21 +++++----
 userpoints.install        |   99 +++++++++++++++++++++++++++++++++++++++++++
 userpoints.module         |   83 +++++++++++++++++++++++-------------
 userpoints.views.inc      |  103 +++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 266 insertions(+), 40 deletions(-)

diff --git a/tests/userpoints_api.test b/tests/userpoints_api.test
index e984cb3..26d02bc 100644
--- a/tests/userpoints_api.test
+++ b/tests/userpoints_api.test
@@ -66,14 +66,14 @@ class UserpointsBaseTestCase extends DrupalWebTestCase {
       $tid = userpoints_get_default_tid();
     }
 
-    debug('Current is: ' . userpoints_get_current_points($uid, $tid) . ', should be: ' . $current);
-    $this->assertEqual($current, userpoints_get_current_points($uid, $tid), t('Current points are correct.'));
+    $api_current = userpoints_get_current_points($uid, $tid);
+    $this->assertEqual($current, $api_current, t('Current points for tid %tid are correct (expected: %expected, actual: %actual).', array('%expected' => $current, '%actual' => $api_current, '%tid' => $tid)));
     if ($max !== NULL) {
       // Hijack static cache, delete this item from it.
       $max_cache = &drupal_static('userpoints_get_max_points', array());
       unset($max_cache[$uid][$tid]);
-      debug('Max is: ' . userpoints_get_max_points($uid, $tid) . ', should be: ' . $max);
-      $this->assertEqual($max, userpoints_get_max_points($uid, $tid), t('Max points are correct.'));
+      $api_max = userpoints_get_max_points($uid, $tid);
+      $this->assertEqual($max, $api_max, t('Max points for tid %tid are correct (expected: %expected, actual: %actual).', array('%expected' => $max, '%actual' => $api_max, '%tid' => $tid)));
     }
   }
 }
@@ -672,11 +672,11 @@ class UserpointsAPITestCase extends UserpointsBaseTestCase {
     // Change points and category.
     $params = array(
       'txn_id' => $txn_id,
-      'points' => 4,
+      'points' => 9,
       'tid' => 0,
     );
     userpoints_userpointsapi($params);
-    $this->verifyPoints($uid, 104, 105, 0);
+    $this->verifyPoints($uid, 109, 109, 0);
     $this->verifyPoints($uid, 50, 53, 1);
 
     // Change points and status and category.
@@ -687,19 +687,20 @@ class UserpointsAPITestCase extends UserpointsBaseTestCase {
       'status' => USERPOINTS_TXN_STATUS_DECLINED,
     );
     userpoints_userpointsapi($params);
-    $this->verifyPoints($uid, 100, 105, 0);
+    $this->verifyPoints($uid, 100, 109, 0);
     $this->verifyPoints($uid, 50, 53, 1);
 
     // Change points and status back to approved.
     $params = array(
       'txn_id' => $txn_id,
-      'points' => 9,
+      'points' => 4,
       'status' => USERPOINTS_TXN_STATUS_APPROVED,
     );
     userpoints_userpointsapi($params);
-    $this->verifyPoints($uid, 59, 59, 1);
+    $this->verifyPoints($uid, 100, 109, 0);
+    $this->verifyPoints($uid, 54, 54, 1);
 
-    $this->verifyPoints($uid, 159, 164, 'all');
+    $this->verifyPoints($uid, 154, 159, 'all');
   }
 
 }
diff --git a/userpoints.install b/userpoints.install
index b60a4a7..990c381 100644
--- a/userpoints.install
+++ b/userpoints.install
@@ -59,6 +59,40 @@ function userpoints_schema() {
     ),
   );
 
+  $schema['userpoints_total'] = array(
+    'description' => 'Holds the total user points',
+    'fields' => array(
+      'uid' => array(
+        'description' => 'User ID',
+        'type' => 'int',
+        'not null' => TRUE,
+      ),
+      'points' => array(
+        'description' => 'Current Points',
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'max_points' => array(
+        'description' => 'Out of a maximum points',
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'last_update' => array(
+        'description' => 'Timestamp',
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+    ),
+    'primary key' => array('uid'),
+    'indexes' => array(
+      'last_update' => array('last_update'),
+      'points' => array('points'),
+    ),
+  );
+
   $schema['userpoints_txn'] = array(
     'description' => 'Userpoints Transactions',
     'fields' => array(
@@ -233,3 +267,68 @@ function userpoints_update_7002() {
     'length' => 128,
   ));
 }
+
+/**
+ * Create the {userpoints_total} table.
+ */
+function userpoints_update_7003() {
+  db_create_table('userpoints_total', array(
+    'description' => 'Holds the total user points',
+    'fields' => array(
+      'uid' => array(
+        'description' => 'User ID',
+        'type' => 'int',
+        'not null' => TRUE,
+      ),
+      'points' => array(
+        'description' => 'Current Points',
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'max_points' => array(
+        'description' => 'Out of a maximum points',
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'last_update' => array(
+        'description' => 'Timestamp',
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+    ),
+    'primary key' => array('uid'),
+    'indexes' => array(
+      'last_update' => array('last_update'),
+      'points' => array('points'),
+    ),
+  ));
+}
+
+/**
+ * Filling the {userpoints_total} table.
+ */
+function userpoints_update_7004(&$sandbox) {
+  // First run, initialize sandbox and check if we are ready to run.
+  if (!isset($sandbox['current_uid'])) {
+    $sandbox['current_uid'] = 0;
+    // Assume that the uid's are distributed more or less equally over the
+    // whole data set. This allows us to calculate the approximate progress.
+    $sandbox['max'] = db_query('SELECT MAX(uid) FROM {userpoints}')->fetchField();
+  }
+
+  // Fetch the next 10 thread_ids.
+  $result = db_query_range('SELECT uid, SUM(points) AS points, SUM(max_points) AS max_points, MAX(last_update) AS last_update FROM {userpoints} WHERE uid > :uid GROUP BY uid ORDER BY uid ASC', 0, 10, array(':uid' => $sandbox['current_uid']), array('fetch' => PDO::FETCH_ASSOC));
+  $insert = db_insert('userpoints_total')->fields(array('uid', 'points', 'max_points', 'last_update'));
+  $last_uid = 0;
+  foreach ($result as $row) {
+    $insert->values($row);
+    $last_uid = $row['uid'];
+  }
+  $insert->execute();
+  $sandbox['current_uid'] = $last_uid;
+  // Set #finished based on sandbox.
+  $sandbox['#finished'] = (empty($sandbox['max']) || $last_uid == 0) ? 1 : ($sandbox['current_uid'] / $sandbox['max']);
+}
\ No newline at end of file
diff --git a/userpoints.module b/userpoints.module
index 046832d..dee1d75 100644
--- a/userpoints.module
+++ b/userpoints.module
@@ -735,7 +735,7 @@ function userpoints_get_current_points($uid = NULL, $tid = NULL) {
   }
   if (!isset($points[$uid][$tid])) {
     if ($tid === 'all') {
-      $points[$uid][$tid] = (int) db_query('SELECT SUM(points) FROM {userpoints} WHERE uid = :uid', array(':uid' => $uid))->fetchField();
+      $points[$uid][$tid] = (int) db_query('SELECT points FROM {userpoints_total} WHERE uid = :uid', array(':uid' => $uid))->fetchField();
     }
     else {
       $points[$uid][$tid] = (int) db_query('SELECT points FROM {userpoints} WHERE uid = :uid AND tid = :tid', array(':uid' => $uid, ':tid' => $tid))->fetchField();
@@ -773,8 +773,8 @@ function userpoints_get_max_points($uid = NULL, $tid = NULL) {
   if (!isset($max[$uid][$tid])) {
     // We did not cache it.
     if ($tid === 'all') {
-      // There is no term id, so we use "all".
-      $max[$uid][$tid] = db_query('SELECT SUM(max_points) FROM {userpoints} WHERE uid = :uid', array(':uid' => $uid))->fetchField();
+      // There is no term id, so we select the total.
+      $max[$uid][$tid] = db_query('SELECT max_points FROM {userpoints_total} WHERE uid = :uid', array(':uid' => $uid))->fetchField();
     }
     else {
       // A term ID is specified, so fetch its maximum points.
@@ -1070,17 +1070,14 @@ function _userpoints_transaction(&$params) {
 function _userpoints_update_cache($txn, $old_txn = NULL) {
   // Store eventual updates in this array.
   $updates = array();
+  $totals = array();
   if (!$old_txn) {
     // For new transactions, only update the cache for fully approved non-expired
     // points.
     if ($txn['status'] == USERPOINTS_TXN_STATUS_APPROVED && $txn['expired'] != 1) {
       // Calculate the current points based upon the tid.
       $updates['points'] = $txn['points'] + userpoints_get_current_points($txn['uid'], $txn['tid']);
-      $max_points = userpoints_get_max_points($txn['uid'], $txn['tid']);
-      // If the new points are higher then the max, update the maximum.
-      if ($updates['points'] > $max_points) {
-        $updates['max_points'] = $updates['points'];
-      }
+      $totals['points'] = $txn['points'] + userpoints_get_current_points($txn['uid'], 'all');
     }
   } else  {
     // For existing transactions, it is a bit more complex.
@@ -1104,51 +1101,77 @@ function _userpoints_update_cache($txn, $old_txn = NULL) {
         ))
         ->execute();
 
+      // Subtract the points from the total.
+      $totals['points'] = userpoints_get_current_points($txn['uid'], 'all') - $old_txn['points'];
+
       if ($txn['status'] == USERPOINTS_TXN_STATUS_APPROVED) {
         // Make sure to add the points so that they are added to the new category.
         $updates['points'] = userpoints_get_current_points($txn['uid'], $txn['tid']) +  $txn['points'];
+
+        // Add them to the totals.
+        $totals['points'] += $txn['points'];
       }
     }
     else if ($old_txn['status'] == USERPOINTS_TXN_STATUS_APPROVED && $txn['status'] != USERPOINTS_TXN_STATUS_APPROVED) {
       // If the transaction goes from approved to not approved, subtract the
       // points to the total.
       $updates['points'] = userpoints_get_current_points($txn['uid'], $txn['tid']) - $old_txn['points'];
-      $max_points = userpoints_get_max_points($txn['uid'], $txn['tid']);
-      // If the new points are higher then the max, update the maximum.
-      if ($updates['points'] > $max_points) {
-        $updates['max_points'] = $updates['points'];
-      }
+      $totals['points'] = userpoints_get_current_points($txn['uid'], 'all') - $old_txn['points'];
     }
     else if ($txn['points'] != $old_txn['points'] && $old_txn['status'] == USERPOINTS_TXN_STATUS_APPROVED && $txn['status'] == USERPOINTS_TXN_STATUS_APPROVED) {
       // If the category did not change but the points and the transaction
       // was and still is approved, update the points difference.
       $updates['points'] = userpoints_get_current_points($txn['uid'], $txn['tid']) + ($txn['points'] - $old_txn['points']);
+      $totals['points'] = userpoints_get_current_points($txn['uid'], 'all') + ($txn['points'] - $old_txn['points']);
+
     }
     elseif ($old_txn['status'] != USERPOINTS_TXN_STATUS_APPROVED && $txn['status'] == USERPOINTS_TXN_STATUS_APPROVED) {
       // Calculate the current points based upon the tid.
       $updates['points'] = userpoints_get_current_points($txn['uid'], $txn['tid']) + $txn['points'];
+      $totals['points'] = userpoints_get_current_points($txn['uid'], 'all') + $txn['points'];
     }
   }
-  if (empty($updates)) {
-    return;
-  }
+  if (!empty($updates)) {
+    $max_points = userpoints_get_max_points($txn['uid'], $txn['tid']);
+    // If the new points are higher then the maximum, update it.
+    if ($updates['points'] > $max_points) {
+      $updates['max_points'] = $updates['points'];
+    }
+    $updates['last_update'] = REQUEST_TIME;
 
-  $max_points = userpoints_get_max_points($txn['uid'], $txn['tid']);
-  // If the new points are higher then the maximum, update it.
-  if ($updates['points'] > $max_points) {
-    $updates['max_points'] = $updates['points'];
-  }
-  $updates['last_update'] = REQUEST_TIME;
+    debug($updates, 'updates');
 
-  // Insert or update the userpoints caching table with the user's current
-  // points.
-  db_merge('userpoints')
-    ->key(array(
-      'uid' => $txn['uid'],
-      'tid' => (int) $txn['tid'],
-    ))
-    ->fields($updates)
-    ->execute();
+    // Insert or update the userpoints caching table with the user's current
+    // points.
+    db_merge('userpoints')
+      ->key(array(
+        'uid' => $txn['uid'],
+        'tid' => (int) $txn['tid'],
+      ))
+      ->fields($updates)
+      ->execute();
+  }
+
+  // Update totals if necessary.
+  if (!empty($totals)) {
+    // Update the total max points if necessary.
+    $max_points_total = userpoints_get_max_points($txn['uid'], 'all');
+    debug($max_points_total, 'max points total');
+    if ($totals['points'] > $max_points_total) {
+      $totals['max_points'] = $totals['points'];
+    }
+    $totals['last_update'] = REQUEST_TIME;
+    debug($totals, 'totals');
+
+    // Insert or update the userpoints total caching table with the user's current
+    // points.
+    db_merge('userpoints_total')
+      ->key(array(
+        'uid' => $txn['uid'],
+      ))
+      ->fields($totals)
+      ->execute();
+  }
 }
 
 /**
diff --git a/userpoints.views.inc b/userpoints.views.inc
index 0e746fe..2a0a98f 100644
--- a/userpoints.views.inc
+++ b/userpoints.views.inc
@@ -134,6 +134,109 @@ function userpoints_views_data() {
   );
 
   // ----------------------------------------------------------------
+  // userpoints_total table
+  // Describe the userpoints_total table.
+  // Define the base group of this table. Fields that don't
+  // have a group defined will go into this field by default.
+  $data['userpoints_total']['table']['group'] = t('Userpoints total');
+
+  $data['userpoints_total']['table']['base'] = array(
+      'field' => 'uid',
+      'title' => t('Userpoints Total'),
+      'help' => t('Total !points over all categories accumulated by users on your site.', userpoints_translation()),
+  );
+
+  $data['userpoints_total']['table']['join'] = array(
+      'users' => array(
+          'left_field' => 'uid',
+          'field' => 'uid',
+      ),
+      'node' => array(
+          'left_field' => 'uid',
+          'field' => 'uid',
+      ),
+      'taxonomy_term_data' => array(
+          'left_field' => 'tid',
+          'field' => 'tid',
+      ),
+      // This goes to the node so that we have consistent authorship.
+      'node_revisions' => array(
+          'left_table' => 'node',
+          'left_field' => 'uid',
+          'field' => 'uid',
+      ),
+  );
+
+  // Describe the points column of the userpoints_total table.
+  $data['userpoints_total']['points'] = array(
+      'title' => t('Total !points', userpoints_translation()),
+      'help' => t("A User's current total !points.", userpoints_translation()), // The help that appears on the UI,
+      'field' => array(
+          'handler' => 'views_handler_field_numeric',
+          'click sortable' => TRUE,
+      ),
+      'argument' => array(
+          'handler' => 'views_handler_argument_numeric',
+          'numeric' => TRUE,
+          'name field' => 'points', // display this field in the summary
+      ),
+      'filter' => array(
+          'handler' => 'views_handler_filter_numeric',
+      ),
+      'sort' => array(
+          'handler' => 'views_handler_sort',
+      ),
+  );
+
+  // Describe the max_points column of the userpoints_total table.
+  $data['userpoints_total']['max_points'] = array(
+      'title' => t('Total max !points', userpoints_translation()),
+      'help' => t("A user's total max !points.", userpoints_translation()), // The help that appears on the UI,
+      'field' => array(
+          'handler' => 'views_handler_field_numeric',
+          'click sortable' => TRUE,
+      ),
+      'argument' => array(
+          'handler' => 'views_handler_argument_numeric',
+          'numeric' => TRUE,
+          'name field' => 'max_points', // display this field in the summary
+      ),
+      'filter' => array(
+          'handler' => 'views_handler_filter_numeric',
+      ),
+      'sort' => array(
+          'handler' => 'views_handler_sort',
+      ),
+  );
+
+  // Describe the last_update column of the userpoints_total table.
+  $data['userpoints_total']['last_update'] = array(
+      'title' => t('Last update'),
+      'help' => t("The last update timestamp for a user's total !points.", userpoints_translation()),
+      'field' => array(
+          'handler' => 'views_handler_field_date',
+      ),
+      'sort' => array(
+          'handler' => 'views_handler_sort_date',
+      ),
+      'filter' => array(
+          'handler' => 'views_handler_filter_date',
+      ),
+  );
+
+  // Add relationship to user table.
+  $data['userpoints_total']['uid'] = array(
+      'title' => t('User'),
+      'help' => t('Relate the userpoints total table to the user table.'),
+      'relationship' => array(
+          'base' => 'users',
+          'field' => 'uid',
+          'label' => t('Users'),
+          'handler' => 'views_handler_relationship',
+      ),
+  );
+
+  // ----------------------------------------------------------------
   // userpoints_txn table
   // Describe the userpoints_txn table.
   // Define the base group of this table. Fields that don't
-- 
1.7.4.1

