? .cvsignore
? userpoints_entity_operation_autocomplete_form.patch
? userpoints_entity_operation_autocomplete_form2.patch
? userpoints_entity_operation_autocomplete_form3.patch
Index: userpoints.admin.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/userpoints/Attic/userpoints.admin.inc,v
retrieving revision 1.1.2.11
diff -u -p -r1.1.2.11 userpoints.admin.inc
--- userpoints.admin.inc	1 Dec 2010 10:15:03 -0000	1.1.2.11
+++ userpoints.admin.inc	24 Jan 2011 20:57:30 -0000
@@ -32,6 +32,8 @@ function userpoints_confirm_approve_subm
 function userpoints_admin_txn($form, &$form_state, $mode, $txn_id = NULL) {
   global $user;
 
+  drupal_add_css(drupal_get_path('module', 'userpoints') . '/userpoints.css');
+
   $timestamp = format_date(REQUEST_TIME, 'custom', 'Y-m-d H:i O');
   if ($mode == 'edit' && $txn_id) {
     $txn = db_query('SELECT * FROM {userpoints_txn} WHERE txn_id = :txn_id', array(':txn_id' => $txn_id))->fetch();
@@ -82,8 +84,32 @@ function userpoints_admin_txn($form, &$f
       '#weight' => -15,
   );
 
+  if (module_exists('taxonomy')) {
+    $options = userpoints_get_categories();
+    $form['tid'] = array(
+      '#type' => 'select',
+      '#title' => t('Category'),
+      '#default_value' => isset($txn->tid) ? $txn->tid : userpoints_get_default_tid(),
+      '#options' => $options,
+      '#description' => t('The !points category that should apply to this transaction.', userpoints_translation()),
+      '#weight' => 0,
+      // Only show the category if there are actually categories to choose from.
+      '#access' => count($options) > 1,
+    );
+  }
+
+  $form['additional_settings'] = array(
+    '#type' => 'vertical_tabs',
+  );
+
+  $form['status'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Status'),
+    '#group' => 'additional_settings',
+  );
+
   if ($mode == 'add') {
-    $form['moderate'] = array(
+    $form['status']['moderate'] = array(
       '#title' => t('Moderated'),
       '#type' => 'checkbox',
       '#description' => t('If checked, this !points transaction must be approved, through the moderation process.', userpoints_translation()),
@@ -93,7 +119,7 @@ function userpoints_admin_txn($form, &$f
     );
   }
   else {
-    $form['status'] = array(
+    $form['status']['status'] = array(
       '#title' => t('Approval status'),
       '#type' => 'radios',
       '#options' => userpoints_txn_status(),
@@ -104,7 +130,7 @@ function userpoints_admin_txn($form, &$f
     );
   }
 
-  $form['time_stamp'] = array(
+  $form['status']['time_stamp'] = array(
     '#type' => 'textfield',
     '#title' => t('Date/Time'),
     '#default_value' => $timestamp,
@@ -129,7 +155,7 @@ function userpoints_admin_txn($form, &$f
       $expirydate = format_date($expirydate, 'custom', 'Y-m-d H:i:s O');
     }
   }
-  $form['expirydate'] = array(
+  $form['status']['expirydate'] = array(
       '#type' => 'textfield',
       '#title' => t('Expiration date'),
       '#default_value' => $expirydate ? $expirydate : '',
@@ -138,32 +164,25 @@ function userpoints_admin_txn($form, &$f
       '#description' => t('The date and time that the !points should expire. Use this format: YYYY-MM-DD HH:MM +ZZZZ. Leave this field blank if the !points should never expire.', userpoints_translation()),
       '#weight' => 25,
   );
-  if (module_exists('taxonomy')) {
-    $options = userpoints_get_categories();
-    $form['tid'] = array(
-      '#type' => 'select',
-      '#title' => t('Category'),
-      '#default_value' => isset($txn->tid) ? $txn->tid : userpoints_get_default_tid(),
-      '#options' => $options,
-      '#description' => t('The !points category that should apply to this transaction.', userpoints_translation()),
-      '#weight' => 0,
-      // Only show the category if there are actually categories to choose from.
-      '#access' => count($options) > 1,
-    );
-  }
-  
-  $form['operation'] = array(
+
+  $form['reason'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Reason'),
+    '#group' => 'additional_settings',
+  );
+
+  $form['reason']['operation'] = array(
       '#type' => 'textfield',
       '#title' => t('Operation'),
       '#default_value' => isset($txn->operation) ? $txn->operation : t('admin'),
-      '#size' => 32,
       '#maxlength' => 48,
-      '#description' => t('The operation type for this transaction (default is %admin).', array('%admin' => t('admin'))),
+      '#description' => t('The operation type for this transaction (default is %admin). Any value is valid but using a defined operation will cause an auto-generated description (specific to the chosen operation) to be included. This description can be translated into multiple languages.', array('%admin' => t('admin'))),
       '#weight' => 5,
       '#required' => FALSE,
+      '#autocomplete_path' => 'userpoints/operation-autocomplete',
   );
 
-  $form['description'] = array(
+  $form['reason']['description'] = array(
       '#type' => 'textarea',
       '#title' => t('Description'),
       '#default_value' => isset($txn->description) ? $txn->description : '',
@@ -174,8 +193,37 @@ function userpoints_admin_txn($form, &$f
   );
 
   $form['reference'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Reference'),
+    '#group' => 'additional_settings',
+  );
+
+  $options = array('' => '< ' . t('None') . ' >');
+  foreach (entity_get_info() as $type => $info) {
+    $options[$type] = $info['label'];
+  }
+  $form['reference']['entity_type'] = array(
+    '#type' => 'select',
+    '#title' => t('Linked entity'),
+    '#weight' => 20,
+    '#options' => $options,
+    '#default_value' => isset($txn->entity_type) ? $txn->entity_type : '',
+  );
+
+  $form['reference']['entity_id'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Entity ID'),
+    '#title_display' => 'invisible',
+    '#weight' => 25,
+    '#size' => 5,
+    '#maxlength' => 20,
+    '#default_value' => isset($txn->entity_id) ? $txn->entity_id : '',
+    '#description' => t('Choose the entity type and ID to be referenced. A link to the entity will be shown.'),
+  );
+
+  $form['reference']['reference'] = array(
       '#type' => 'textfield',
-      '#title' => t('Reference'),
+      '#title' => t('Internal reference'),
       '#default_value' => isset($txn->reference) ? $txn->reference : '',
       '#size' => 30,
       '#maxlength' => 128,
@@ -185,7 +233,7 @@ function userpoints_admin_txn($form, &$f
 
   $approved_by = !empty($txn->approver_uid) ? user_load($txn->approver_uid) : NULL;
   if ($approved_by) {
-    $form['approver'] = array(
+    $form['status']['approver'] = array(
       '#type' => 'textfield',
       '#title' => t('Moderator'),
       '#default_value' => $approved_by->name,
@@ -208,6 +256,51 @@ function userpoints_admin_txn($form, &$f
   );
   return $form;
 }
+/**
+ * Autocomplete callback for search an operation.
+ *
+ * @param $search
+ *   Search string.
+ */
+function userpoints_operation_autocomplete($search) {
+  $results = array();
+  if (!empty($search)) {
+    foreach (userpoints_get_info() as $operation => $info) {
+      if (strpos($operation, $search) !== FALSE) {
+        $results[$operation] = userpoints_create_operation_autocomplete_label($operation, $info, $search);
+      }
+      elseif (isset($info['admin description']) && strpos($info['admin description'], $search) !== FALSE) {
+        $results[$operation] = userpoints_create_operation_autocomplete_label($operation, $info, $search);
+      }
+      elseif (isset($info['description']) && strpos($info['description'], $search) !== FALSE) {
+        $results[$operation] = userpoints_create_operation_autocomplete_label($operation, $info, $search);
+      }
+    }
+  }
+  drupal_json_output((object)$results);
+}
+
+/**
+ * Create a autocomplete label with highlighted search text.
+ *
+ * @param $operation
+ *   Operation string.
+ * @param $info
+ *   Operation info array.
+ * @param $search
+ *   Search text that will be highlighted if found.
+ *
+ * @return
+ *   Highlighted label. If existing, the admin description is used and if not,
+ *   the operation string.
+ */
+function userpoints_create_operation_autocomplete_label($operation, $info, $search) {
+  $label = $operation;
+  if (!empty($info['admin description'])) {
+    $label = $info['admin description'];
+  }
+  return preg_replace("/(" . preg_quote($search) . ")/i","<b>$1</b>", $label);
+}
 
 /**
  * Validate function for userpoints transaction form.
@@ -252,6 +345,10 @@ function userpoints_admin_txn_submit($fo
       // Check for the existence of an expirydate.
       $params['expirydate'] = strtotime($form_state['values']['expirydate']);
     }
+    if (!empty($form_state['values']['entity_id']) && !empty($form_state['values']['entity_type'])) {
+      $params['entity_type'] = $form_state['values']['entity_type'];
+      $params['entity_id'] = (int)$form_state['values']['entity_id'];
+    }
   }
   else {
     $expirydate = 0;
@@ -282,6 +379,10 @@ function userpoints_admin_txn_submit($fo
       'txn_id' => $form_state['values']['txn']->txn_id,
       'display' => FALSE,
     );
+    if (!empty($form_state['values']['entity_id']) && !empty($form_state['values']['entity_type'])) {
+      $params['entity_type'] = $form_state['values']['entity_type'];
+      $params['entity_id'] = (int)$form_state['values']['entity_id'];
+    }
     drupal_set_message(t('Changes to the !points transaction have been saved.', userpoints_translation()));
   }
   userpoints_userpointsapi($params);
Index: userpoints.api.php
===================================================================
RCS file: userpoints.api.php
diff -N userpoints.api.php
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ userpoints.api.php	24 Jan 2011 20:57:30 -0000
@@ -0,0 +1,34 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * API documentation for userpoints.module
+ */
+
+/**
+ * Return information about registered operations.
+ *
+ * Modules can register operion strings
+ *
+ * @return
+ *   An array whose keys are operation strings used in
+ *   userpoints_userpoints_api() and the which has the following properties:
+ *   - description: A string that is used as a reason when transactions are
+ *     displayed. Either this or a callback (see below) is required.
+ *   - description callback: If the reason is dynamic, because he for example
+ *     includes the title of a node, a callback function can be given which
+ *     receives the transaction object and (if existing) and entity object as
+ *     arguments.
+ *   - admin description: A description which is searched for and displayed in
+ *     the operation autocomplete field in the add points form.
+ *
+ */
+function hook_userpoints_info() {
+  return array(
+    'expiry' => array(
+      'description' => t('!Points have expired.', userpoints_translation()),
+      'admin description' => t('Expire an existing transaction'),
+    )
+  );
+}
\ No newline at end of file
Index: userpoints.css
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/userpoints/Attic/userpoints.css,v
retrieving revision 1.1.2.3
diff -u -p -r1.1.2.3 userpoints.css
--- userpoints.css	19 Nov 2010 00:47:21 -0000	1.1.2.3
+++ userpoints.css	24 Jan 2011 20:57:30 -0000
@@ -47,3 +47,7 @@ table.userpoints-myuserpoints-total {
 .userpoints-view-transaction dd {
   margin: 0 0 1em 0;
 }
+
+div.form-item-entity-type, div.form-item-entity-id {
+    display: inline;
+}
Index: userpoints.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/userpoints/userpoints.module,v
retrieving revision 1.68.2.23
diff -u -p -r1.68.2.23 userpoints.module
--- userpoints.module	22 Jan 2011 22:12:29 -0000	1.68.2.23
+++ userpoints.module	24 Jan 2011 20:57:34 -0000
@@ -248,6 +248,15 @@ function userpoints_menu() {
       'type' => MENU_NORMAL_ITEM,
   );
 
+  $items['userpoints/operation-autocomplete'] = array(
+      'title' => 'Operation autocomplete',
+      'page callback' => 'userpoints_operation_autocomplete',
+      'access callback' => 'userpoints_admin_access',
+      'access arguments' => array('add'),
+      'file' => 'userpoints.admin.inc',
+      'type' => MENU_CALLBACK,
+  );
+
   $items['userpoints/view/%userpoints_transaction'] = array(
       'title' => '!Point transaction',
       'title callback' => 'userpoints_view_transaction_title',
@@ -1250,7 +1259,7 @@ function userpoints_views_api() {
  *   The operation being performed.
  * @param &$params
  *   Parameters to be passed to the hook.
- * 
+ *
  * @return
  *   An array of return values of the hook implementations. If modules return
  *   arrays from their implementations, those are merged into one array.
@@ -1357,6 +1366,7 @@ function userpoints_userpoints_info() {
   return array(
     'expiry' => array(
       'description' => t('!Points have expired.', userpoints_translation()),
+      'admin description' => t('Expire an existing transaction'),
     )
   );
 }
Index: userpoints_rules.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/userpoints/userpoints_rules.module,v
retrieving revision 1.1.4.2
diff -u -p -r1.1.4.2 userpoints_rules.module
--- userpoints_rules.module	30 Oct 2010 08:27:57 -0000	1.1.4.2
+++ userpoints_rules.module	24 Jan 2011 20:57:34 -0000
@@ -1,4 +1,5 @@
 <?php
+
 // $Id: userpoints_rules.module,v 1.1.4.2 2010/10/30 08:27:57 berdir Exp $
 
 /**
@@ -7,53 +8,56 @@
  */
 
 /**
- * Rules events -Trigger event when user points awarded.
+ * Implements hook_userpoints().
  */
 function userpoints_rules_userpoints($op, &$params = array()) {
   // Transactions without points are not passed to rules for now.
   if (!is_array($params) || !isset($params['points'])) {
-   return;
+    return;
   }
 
+
   $event = '';
   switch ($op) {
     case 'points after':
-        $event = 'userpoints_event_points_awarded_after';
-        break;
+      $event = 'userpoints_event_points_awarded_after';
+      break;
 
     case 'points before':
-        $event = 'userpoints_event_points_awarded_before';
-        break;
+      $event = 'userpoints_event_points_awarded_before';
+      break;
   }
 
-  if (!empty($event)) {
-    // Convert to an object.
-    $userpoints_transaction = (object) $params;
-
-    // Provide uid as wrapped user entity.
-    $userpoints_transaction->user = entity_metadata_wrapper('user', $userpoints_transaction->uid);
-
-    $userpoints_transaction->entity = NULL;
-    if (!empty($userpoints_transaction->entity_type) && !empty($userpoints_transaction->entity_id)) {
-      // Make entity available as lazy loading wrapper.
-      $userpoints_transaction->entity = entity_metadata_wrapper($userpoints_transaction->entity_type, $userpoints_transaction->entity_id);
-    }
-    // Invoke rules event.
-    rules_invoke_event($event, $userpoints_transaction);
-    // Convert back to keep any changes to the properties.
-    $params = (array)$userpoints_transaction;
-
-    $params['uid'] = $userpoints_transaction->user->getIdentifier();
-    unset($params['user']);
-
-    if ($userpoints_transaction->entity) {
-      $params['entity_type'] = $userpoints_transaction->entity->type();
-      try {
+  try {
+
+    if (!empty($event)) {
+      // Convert to an object.
+      $userpoints_transaction = (object) $params;
+
+      // Provide uid as wrapped user entity.
+      $userpoints_transaction->user = entity_metadata_wrapper('user', $userpoints_transaction->uid);
+
+      $userpoints_transaction->entity = NULL;
+      if (!empty($userpoints_transaction->entity_type) && !empty($userpoints_transaction->entity_id)) {
+        // Make entity available as lazy loading wrapper
+        $userpoints_transaction->entity = entity_metadata_wrapper($userpoints_transaction->entity_type, $userpoints_transaction->entity_id);
+      }
+      // Invoke rules event.
+      rules_invoke_event($event, $userpoints_transaction);
+
+      // Convert back to keep any changes to the properties.
+      $params = (array) $userpoints_transaction;
+      $params['uid'] = $userpoints_transaction->user->getIdentifier();
+      if ($userpoints_transaction->entity) {
+        $params['entity_type'] = $userpoints_transaction->entity->type();
         $params['entity_id'] = $userpoints_transaction->entity->getIdentifier();
-      } catch (Exception $e) {
-        throw $e;
       }
+      unset($params['entity']);
     }
-    unset($params['entity']);
+  } catch (EntityMetadataWrapperException $e) {
+    // The referenced entity does not exist.
+    // @todo: Find a way to handle this more gracefully.
   }
+  unset($params['entity']);
+  unset($params['user']);
 }
\ No newline at end of file
