You are here

userpoints.module in User Points 5

File

userpoints.module
View source
<?php

// Copyright 2005-2007 Khalid Baheyeldin http://2bits.com
define('USERPOINTS_PERM_VIEW', 'view userpoints');
define('USERPOINTS_PERM_USE', 'use userpoints');
define('USERPOINTS_PERM_ADMIN', 'admin userpoints');
define('USERPOINTS_TRANS_UCPOINTS', 'userpoints_trans_ucpoints');
define('USERPOINTS_TRANS_LCPOINTS', 'userpoints_trans_lcpoints');
define('USERPOINTS_TRANS_LCPOINT', 'userpoints_trans_lcpoint');
define('USERPOINTS_STATUS', 'userpoints_status');
define('USERPOINTS_POINTS_MODERATION', 'userpoints_points_moderation');
define('USERPOINTS_TXN_STATUS_APPROVED', 0);
define('USERPOINTS_TXN_STATUS_PENDING', 1);
define('USERPOINTS_TXN_STATUS_DECLINED', 2);
define('USERPOINTS_PAGE_COUNT', 30);

/**
 * returns an array of common translation placeholders
 */
function userpoints_translation() {
  static $trans;
  if (!isset($trans)) {
    $trans = array(
      '!Points' => variable_get(USERPOINTS_TRANS_UCPOINTS, 'Points'),
      '!points' => variable_get(USERPOINTS_TRANS_LCPOINTS, 'points'),
      '!point' => variable_get(USERPOINTS_TRANS_LCPOINT, 'point'),
    );
  }
  return $trans;
}
function userpoints_txn_status() {
  return array(
    USERPOINTS_TXN_STATUS_APPROVED => t('Approved'),
    USERPOINTS_TXN_STATUS_PENDING => t('Pending'),
    USERPOINTS_TXN_STATUS_DECLINED => t('Declined'),
  );
}

/**
 * Implementation of hook_help().
 */
function userpoints_help($section) {
  switch ($section) {
    case 'admin/settings/userpoints':
      $output = t('Configure userpoints moderation and branding translation', userpoints_translation());
      break;
    case 'admin/help#userpoints':
      $output = t('Users earn !points as they post nodes, comments, and vote on nodes', userpoints_translation());
  }
  return $output;
}

/**
 * Implementation of hook_menu().
 */
function userpoints_menu($may_cache) {
  $items = array();
  if ($may_cache) {
    $items[] = array(
      'path' => 'userpoints',
      'callback' => 'userpoints_list_users',
      'title' => t('users by !points', userpoints_translation()),
      'description' => t('List users by !points', userpoints_translation()),
      'access' => user_access(USERPOINTS_PERM_VIEW),
      'type' => MENU_NORMAL_ITEM,
    );
    $items[] = array(
      'path' => 'admin/settings/userpoints',
      'callback' => 'drupal_get_form',
      'callback arguments' => array(
        'userpoints_admin_settings',
      ),
      'title' => t('!Points settings', userpoints_translation()),
      'description' => t('Configure userpoints settings'),
      'access' => user_access(USERPOINTS_PERM_ADMIN),
      'type' => MENU_NORMAL_ITEM,
    );
    $items[] = array(
      'path' => 'admin/user/userpoints',
      'callback' => 'userpoints_admin_manage',
      'title' => t('!Points', userpoints_translation()),
      'description' => t('Manage !points', userpoints_translation()),
      'access' => user_access(USERPOINTS_PERM_ADMIN),
    );
    $items[] = array(
      'path' => 'admin/user/userpoints/manage',
      'callback' => 'userpoints_admin_manage',
      'title' => t('Manage'),
      'access' => user_access(USERPOINTS_PERM_ADMIN),
      'type' => MENU_DEFAULT_LOCAL_TASK,
      'weight' => -1,
    );
    $items[] = array(
      'path' => 'admin/user/userpoints/add',
      'callback' => 'drupal_get_form',
      'callback arguments' => array(
        'userpoints_admin_txn',
      ),
      'title' => t('Add'),
      'description' => t('Admin add/delete userpoints'),
      'access' => user_access(USERPOINTS_PERM_ADMIN),
      'type' => MENU_LOCAL_TASK,
    );
    $items[] = array(
      'path' => 'admin/user/userpoints/edit',
      'callback' => 'drupal_get_form',
      'callback arguments' => array(
        'userpoints_admin_txn',
      ),
      'access' => user_access(USERPOINTS_PERM_ADMIN),
      'type' => MENU_CALLBACK,
    );
    $items[] = array(
      'path' => 'admin/user/userpoints/approve',
      'callback' => 'userpoints_admin_approve',
      'access' => user_access(USERPOINTS_PERM_ADMIN),
      'type' => MENU_CALLBACK,
    );
    $items[] = array(
      'path' => 'admin/user/userpoints/decline',
      'callback' => 'userpoints_admin_approve',
      'access' => user_access(USERPOINTS_PERM_ADMIN),
      'type' => MENU_CALLBACK,
    );
  }
  return $items;
}

/**
 * Implementation of hook_perm().
 */
function userpoints_perm() {
  return array(
    USERPOINTS_PERM_VIEW,
    USERPOINTS_PERM_USE,
    USERPOINTS_PERM_ADMIN,
  );
}

/**
 * menu callback for settings form.
 */
function userpoints_admin_settings() {
  $form['status'] = array(
    '#type' => 'fieldset',
    '#title' => t('Moderation'),
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
    '#weight' => -1,
  );
  $form['status'][USERPOINTS_POINTS_MODERATION] = array(
    '#type' => 'radios',
    '#title' => t('Transaction status'),
    '#default_value' => variable_get(USERPOINTS_POINTS_MODERATION, 0),
    '#options' => array(
      t('Approved'),
      t('Moderated'),
    ),
    '#description' => t('Select whether all !points should be approved automatically, or moderated, and require admin approval', userpoints_translation()),
  );
  $group = 'renaming';
  $form[$group] = array(
    '#type' => 'fieldset',
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
    '#title' => t('Points branding'),
  );
  $form[$group][USERPOINTS_TRANS_UCPOINTS] = array(
    '#type' => 'textfield',
    '#title' => t('Word to use in the interface for the upper case plural word !Points', userpoints_translation()),
    '#default_value' => variable_get(USERPOINTS_TRANS_UCPOINTS, 'Points'),
    '#size' => 20,
    '#maxlength' => 20,
  );
  $form[$group][USERPOINTS_TRANS_LCPOINTS] = array(
    '#type' => 'textfield',
    '#title' => t('Word to use in the interface for the lower case plural word !points', userpoints_translation()),
    '#default_value' => variable_get(USERPOINTS_TRANS_LCPOINTS, 'points'),
    '#size' => 20,
    '#maxlength' => 20,
  );
  $form[$group][USERPOINTS_TRANS_LCPOINT] = array(
    '#type' => 'textfield',
    '#title' => t('Word to use in the interface for the lower case singular word !point', userpoints_translation()),
    '#default_value' => variable_get(USERPOINTS_TRANS_LCPOINT, 'point'),
    '#size' => 20,
    '#maxlength' => 20,
  );
  $form['setting'] = module_invoke_all('userpoints', 'setting');
  return system_settings_form($form);
}

/**
 * @param uid: user id of the user to get or lose the points
 *
 * @return number of current points in that user's account
 */
function userpoints_get_current_points($uid = 0) {
  return (int) db_result(db_query('SELECT points FROM {userpoints} WHERE uid = %d', $uid));
}

/**
 * @param op: operation to be performed.
 *    'points' 
 *      modules should use this to award points
 *    'points before' 
 *      hook that fires before points are added
 *    'points after' 
 *      hook that fires after points have been added
 * @param points: number of points to add (if positive) or subtract (if negative)
 * @param uid: user id of the user to get or lose the points
 * @param event: optional event ID
 * @param description: optional description 
 * @param reference: optional reference information, indexed
 *
 * @return FALSE when no action is take, TRUE when points are credited or debited
 */
function userpoints_userpointsapi($op, $points = 0, $uid = 0, $event = NULL, $description = NULL, $reference = NULL) {

  // anonymous users do not get points, and there have to be points to process
  if ($uid == 0 || $points == 0) {
    return FALSE;
  }

  // When called explicitly, we handle only three cases. The rest are hooks
  // that should be defined in other modules.
  if ($op != 'points' && $op != 'txn approve' && $op != 'points approved') {
    return FALSE;
  }

  // Load the user
  $user = user_load(array(
    'uid' => $uid,
  ));

  // Call the _userpoints hook, and stop if one of them returns FALSE
  // This will be handy later
  $rc = module_invoke_all('userpoints', 'points before', $points, $uid, $event);
  foreach ($rc as $key => $value) {
    if ($value == FALSE) {

      // Do not process the points
      return FALSE;
    }
  }
  if ($points < 0) {
    $msg = t('lost');
  }
  else {
    $msg = t('earned');
  }

  // If $op is 'points', then the points MAY or MAYBE NOT subject to
  // approval, depending on user settings. If they are, then
  // write the transaction (with the noderation flag on) and return - which
  // will provent the rest of the function from actually assigning the
  // points
  if ($op == 'points') {
    $moderation = variable_get(USERPOINTS_POINTS_MODERATION, FALSE);
    userpoints_transaction($moderation, $points, $uid, $event, $description, $reference);

    // If points are being moderated, then write a message to the user and
    // that's it!
    if ($moderation) {

      // Points are moderated, so we do nothing for now.
      drupal_set_message(t('User %uname %op %pointsvalue !points, pending administrator approval.', array_merge(userpoints_translation(), array(
        '%uname' => $user->name,
        '%op' => $msg,
        '%pointsvalue' => abs($points),
        '%total' => $current_points,
      ))));
      return TRUE;
    }
  }

  // If the points are pre-approved, then just write the trail
  if ($op == 'points approved') {
    userpoints_transaction(FALSE, $points, $uid, $event, $description, $reference);
  }

  // ***********************
  // At this point, regardless of what $op is, the module needs to
  // actually award the points. (Keep in mind that if $op was 'point' and
  // moderation was on, then it won't get to this part).
  // ***********************
  // Calculate the current point
  $current_points = (int) $points + userpoints_get_current_points($uid);

  // Check if user has earned points
  if ($points > 0) {

    // Set the max_points to be the current max points, plus whatever earned in this transaction
    $max_points = db_result(db_query('SELECT max_points FROM {userpoints} WHERE uid = %d', $uid));
    $max_points = $current_points + (int) $max_points;
  }
  drupal_set_message(t('User %uname %op %pointsvalue !points! Total now is %total points.', array_merge(userpoints_translation(), array(
    '%uname' => $user->name,
    '%op' => $msg,
    '%pointsvalue' => abs($points),
    '%total' => $current_points,
  ))));
  if (_userpoints_user_exists($uid)) {
    db_query("UPDATE {userpoints}\n      SET points = %d, max_points = %d, last_update = %d WHERE uid = %d", $current_points, $max_points, time(), $uid);
  }
  else {
    $result = db_query("INSERT INTO {userpoints}\n      VALUES (%d, %d, %d, %d)", $uid, $current_points, $max_points, time());
  }
  module_invoke_all('userpoints', 'points after', $points, $uid, $event);
  return TRUE;
}
function userpoints_transaction($moderation, $points = 0, $uid = 0, $event = NULL, $description = NULL, $reference = NULL) {
  db_query("INSERT INTO {userpoints_txn}\n    VALUES (0, %d, 0, %d, %d, %d, '%s', '%s', '%s')", $uid, $points, time(), $moderation, $event, $description, $reference);
  return $moderation;
}
function _userpoints_user_exists($uid) {
  return (int) db_result(db_query('SELECT COUNT(*) FROM {userpoints} WHERE uid = %d', $uid));
}
function userpoints_user($op, &$edit, &$account, $category = '') {
  switch ($op) {
    case 'delete':

      // The user is being deleted, delete all traces in userpoints and txn tables
      db_query('DELETE FROM {userpoints} WHERE uid = %d', $account->uid);
      db_query('DELETE FROM {userpoints_txn} WHERE uid = %d', $account->uid);
      break;
    case 'view':

      // Get the points for the user
      $points = userpoints_get_current_points($account->uid);
      if (user_access(USERPOINTS_PERM_ADMIN)) {
        $points = l($points, 'admin/user/userpoints/add/' . $account->uid, array(
          'title' => t('Manage points'),
        ));
      }
      $disp_points[] = array(
        'title' => t('User !points', userpoints_translation()),
        'value' => $points,
      );
      return array(
        t('!Points', userpoints_translation()) => $disp_points,
      );
      break;
  }
}
function userpoints_admin_manage() {
  $header = array(
    array(
      'data' => t('User'),
      'field' => 'uid',
      'sort' => 'desc',
    ),
    array(
      'data' => t('Time stamp'),
      'field' => 'time_stamp',
    ),
    array(
      'data' => t('!Points', userpoints_translation()),
      'field' => 'points',
    ),
    array(
      'data' => t('Event'),
      'field' => 'event',
    ),
    array(
      'data' => t('Operation', userpoints_translation()),
    ),
  );
  $sql = "SELECT t.txn_id, t.uid, t.time_stamp, t.points, t.event, t.status FROM {userpoints_txn} t WHERE t.status = %d";
  $sql .= tablesort_sql($header);
  $result = pager_query($sql, USERPOINTS_PAGE_COUNT, 0, NULL, USERPOINTS_TXN_STATUS_PENDING);
  while ($data = db_fetch_object($result)) {
    $user = user_load(array(
      'uid' => $data->uid,
    ));
    $rows[] = array(
      array(
        'data' => theme('username', $user),
      ),
      array(
        'data' => format_date($data->time_stamp, 'custom', 'Y-m-d H:i'),
      ),
      array(
        'data' => $data->points,
        'align' => 'right',
      ),
      array(
        'data' => $data->event,
      ),
      array(
        'data' => l('approve', "admin/user/userpoints/approve/{$data->txn_id}") . ' ' . l('decline', "admin/user/userpoints/decline/{$data->txn_id}") . ' ' . l('edit', "admin/user/userpoints/edit/{$data->txn_id}"),
      ),
    );
  }
  $output = theme('table', $header, $rows);
  $output .= theme('pager', NULL, 30, 0);
  return $output;
}
function userpoints_admin_approve() {
  global $user;
  $op = arg(3);
  $txn_id = (int) arg(4);
  if ($txn_id) {
    switch ($op) {
      case 'approve':
        $result = db_query("SELECT uid, event, points, description, reference\n          FROM {userpoints_txn}\n          WHERE status = %d AND txn_id = %d", USERPOINTS_TXN_STATUS_PENDING, $txn_id);
        $data = db_fetch_object($result);
        drupal_set_message(t('Transaction approved'));

        // This transaction has not been credited to the user's account, so credit it first
        userpoints_userpointsapi('txn approve', $data->points, $data->uid, $data->event, $data->description, $data->reference);

        // Set it to approved
        db_query("UPDATE {userpoints_txn}\n          SET status = %d, approver_uid = %d WHERE txn_id = %d", USERPOINTS_TXN_STATUS_APPROVED, $user->uid, $txn_id);
        break;
      case 'decline':
        db_query("UPDATE {userpoints_txn}\n          SET status = %d, approver_uid = %d  WHERE txn_id = %d", USERPOINTS_TXN_STATUS_DECLINED, $user->uid, $txn_id);
        drupal_set_message(t('Transaction declined'));
        break;
    }
  }
  drupal_goto('admin/user/userpoints');
}
function userpoints_admin_txn() {
  global $user;
  $mode = arg(3);
  $txn_id = (int) arg(4);
  if ($txn_id) {
    $txn_user = user_load(array(
      'uid' => $txn_id,
    ));
  }
  $timestamp = format_date(time(), 'custom', 'Y-m-d H:i O');
  if ($mode == 'edit' && $txn_id) {
    $result = db_query('SELECT * FROM {userpoints_txn} WHERE txn_id = %d', $txn_id);
    $txn = db_fetch_object($result);
    $timestamp = format_date($txn->time_stamp, 'custom', 'Y-m-d H:i O');
  }
  $form['txn_user'] = array(
    '#type' => 'textfield',
    '#title' => t('User Name'),
    '#size' => 30,
    '#maxlength' => 60,
    '#default_value' => $txn_user->name,
    '#autocomplete_path' => 'user/autocomplete',
    '#description' => t('Drupal User ID for the user you want the points to affect'),
  );
  $form['points'] = array(
    '#type' => 'textfield',
    '#title' => t('Points'),
    '#size' => 10,
    '#maxlength' => 10,
    '#default_value' => $txn->points,
    '#description' => t('Number of points to add/subtract from the user. For example, 25 (to add points) or -25 (to subtract points).'),
  );
  $form['time_stamp'] = array(
    '#type' => 'textfield',
    '#title' => t('Date/Time'),
    '#default_value' => $timestamp,
    '#size' => 30,
    '#maxlength' => 30,
    '#description' => t('Date and time of this transaction, in the form YYYY-MM-DD HH:MM +ZZZZ'),
  );
  $form['description'] = array(
    '#type' => 'textarea',
    '#title' => t('Description'),
    '#default_value' => $txn->description,
    '#width' => 70,
    '#lines' => 5,
    '#description' => t('Enter an optional description for this transaction, such as the reason it is created.'),
  );
  $form['reference'] = array(
    '#type' => 'textfield',
    '#title' => t('Reference'),
    '#default_value' => $txn->reference,
    '#size' => 30,
    '#maxlength' => 128,
    '#description' => t('Enter optional reference for this transaction. This field will be indexed and searchable.'),
  );
  switch ($mode) {
    case 'add':
      $form['approver_uid'] = array(
        '#type' => 'hidden',
        '#value' => $user->uid,
      );
      $form['event'] = array(
        '#type' => 'hidden',
        '#value' => 'admin',
      );
      $form['status'] = array(
        '#type' => 'hidden',
        '#value' => USERPOINTS_TXN_STATUS_PENDING,
      );
      $form['mode'] = array(
        '#type' => 'hidden',
        '#value' => $mode,
      );
      break;
    case 'edit':
      $form['txn_user'] = array(
        '#type' => 'hidden',
        '#value' => $txn_user,
      );
      $form['approver_uid'] = array(
        '#type' => 'textfield',
        '#description' => t('Approver ID'),
        '#default_value' => $txn->approver_uid,
        '#size' => 10,
        '#maxlength' => 7,
        '#description' => t('The user ID of the person who approved this transaction. 0 means not yet approved.'),
      );
      $form['event'] = array(
        '#type' => 'textfield',
        '#description' => t('Event'),
        '#default_value' => $txn->event,
        '#size' => 20,
        '#maxlength' => 20,
        '#description' => t('The event type for this transaction. Normally, it is "admin".'),
      );
      $form['status'] = array(
        '#title' => t('Approval status'),
        '#type' => 'radios',
        '#options' => userpoints_txn_status(),
        '#description' => t('Approval status of the transaction.'),
        '#default_value' => $txn->status,
      );
      break;
  }
  $form['mode'] = array(
    '#type' => 'hidden',
    '#default_value' => $mode,
  );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Save'),
  );
  return $form;
}
function userpoints_admin_txn_submit($form_id, $form = NULL) {
  if ($form_id != 'userpoints_admin_txn') {
    return;
  }
  $txn_user = user_load(array(
    'name' => $form['txn_user'],
  ));
  switch ($form['mode']) {
    case 'add':
      userpoints_userpointsapi('points', $form['points'], $txn_user->uid, 'admin', $form['description'], $form['reference']);
      break;
    case 'edit':
      db_query("UPDATE {userpoints_txn} \n        SET uid = %d, approver_uid = %d, points = %d, time_stamp = %d, event = '%s', description = '%s', reference = '%s', status = %d WHERE txn_id = %d", $form['uid'], $form['approver_uid'], $form['points'], strtotime($form['time_stamp']), $form['event'], $form['description'], $form['reference'], $form['status'], $form['txn_id']);
      drupal_set_message(t('Transaction has been updated.'));
  }
  drupal_goto('admin/user/userpoints');
}
function userpoints_list_users() {
  $sql = "SELECT p.uid, u.name, p.points\n    FROM {userpoints} p INNER JOIN {users} u USING (uid)";
  $sql_cnt = "SELECT COUNT(DISTINCT(uid)) FROM {userpoints}";
  $header = array(
    array(
      'data' => t('User'),
      'field' => 'u.name',
    ),
    array(
      'data' => t('!Points', userpoints_translation()),
      'field' => 'p.points',
      'sort' => 'desc',
    ),
  );
  $sql .= tablesort_sql($header);
  $result = pager_query($sql, 30, 0, $sql_cnt);
  while ($data = db_fetch_object($result)) {
    $rows[] = array(
      array(
        'data' => theme('username', $data),
      ),
      array(
        'data' => $data->points,
        'align' => 'right',
      ),
    );
  }
  $output = theme('table', $header, $rows);
  $output .= theme('pager', NULL, 30, 0);
  return $output;
}
function userpoints_block($op = 'list', $delta = 0, $edit = array()) {
  global $user;
  $num = 5;
  switch ($op) {
    case 'list':
      $blocks[0]['info'] = t('User\'s !points', userpoints_translation());
      $blocks[1]['info'] = t('Highest Users');
      return $blocks;
    case 'view':
      if (user_access(USERPOINTS_PERM_VIEW)) {
        switch ($delta) {
          case 0:
            $title = t('@user\'s !points', array_merge(array(
              '@user' => $user->name,
            ), userpoints_translation()));
            if ($user->uid) {
              $points = (int) db_result(db_query('SELECT points FROM {userpoints} WHERE uid = %d', $user->uid));
              $show_points = format_plural($points, t('!point', userpoints_translation()), t('!points', userpoints_translation()));
              $content = t('You have %p %c', array(
                '%p' => $points,
                '%c' => $show_points,
              ));
            }
            else {
              $content = t('!Points are visible to logged in users only', userpoints_translation());
            }
            break;
          case 1:
            $title = t('Highest Users');
            $result = db_query_range('SELECT p.uid, u.name, p.points
              FROM {userpoints} p INNER JOIN {users} u USING (uid)
              ORDER BY p.points DESC', 0, $num);
            while ($data = db_fetch_object($result)) {
              $rows[] = array(
                array(
                  'data' => theme('username', $data),
                ),
                array(
                  'data' => $data->points,
                  'align' => 'right',
                ),
              );
            }
            $header = array(
              t('User'),
              t('!Points', userpoints_translation()),
            );
            $content = theme('table', $header, $rows);
            $content .= '<div class="more-link">' . l(t('more'), 'userpoints', array(
              'title' => t('All users by !points', userpoints_translation()),
            )) . '</div>';
            break;
        }
        $block['subject'] = $title;
        $block['content'] = $content;
        return $block;
      }
  }
}