From 4419ff2cd9accebf7bb354e2fb1a6fcfecdac5ef Mon Sep 17 00:00:00 2001
From: Sascha Grossenbacher <saschagros+test@gmail.com>
Date: Mon, 14 Mar 2011 18:16:42 +0100
Subject: [PATCH] Issue #1089290 by Berdir: Added user settings API for is disabled information including tests.

---
 privatemsg.install |   65 ++++++++++++++++++--
 privatemsg.module  |  177 +++++++++++++++++++++++++++++++++++++++++++++------
 privatemsg.test    |   54 ++++++++++++++++-
 3 files changed, 269 insertions(+), 27 deletions(-)

diff --git a/privatemsg.install b/privatemsg.install
index 6f457a6..6fed143 100644
--- a/privatemsg.install
+++ b/privatemsg.install
@@ -105,17 +105,33 @@ function privatemsg_schema() {
     'primary key'     => array('mid'),
   );
 
-  $schema['pm_disable'] = array(
-    'description'       => '{pm_disable} holds the list of users that have disabled private messaging',
+  $schema['pm_setting'] = array(
+    'description'       => '{pm_setting} holds user specific (including defaults) settings',
     'fields' => array(
-      'uid'    => array(
-        'description'   => 'ID of the user',
+      'id'    => array(
+        'description'   => 'Together with type, associates a setting to a user, role, global default, ...',
         'type'          => 'int',
         'not null'      => TRUE,
         'unsigned'      => TRUE,
       ),
+      'type' => array(
+        'description'   => 'Together with id, associates a setting to a user, role, global default, ...',
+        'type'          => 'varchar',
+        'length'        => 128,
+        'not null'      => TRUE,
+      ),
+      'setting' => array(
+        'description'   => 'The name of a setting',
+        'type'          => 'varchar',
+        'length'        => 128,
+        'not null'      => TRUE,
+      ),
+      'value'    => array(
+        'description'   => 'Holds the value of a given setting',
+        'type'          => 'int',
+      ),
     ),
-    'primary key'       => array('uid'),
+    'primary key'       => array('id', 'type', 'setting'),
   );
 
   return $schema;
@@ -676,6 +692,45 @@ function privatemsg_update_6204() {
 }
 
 /**
+ * Add the {pm_settings} table.
+ */
+function privatemsg_update_6205() {
+  $ret = array();
+
+    $table = array(
+    'description'       => '{pm_setting} holds user specific (including defaults) settings',
+    'fields' => array(
+      'id'    => array(
+        'description'   => 'Together with type, associates a setting to a user, role, global default, ...',
+        'type'          => 'int',
+        'not null'      => TRUE,
+        'unsigned'      => TRUE,
+      ),
+      'type' => array(
+        'description'   => 'Together with id, associates a setting to a user, role, global default, ...',
+        'type'          => 'varchar',
+        'length'        => 128,
+        'not null'      => TRUE,
+      ),
+      'setting' => array(
+        'description'   => 'The name of a setting',
+        'type'          => 'varchar',
+        'length'        => 128,
+        'not null'      => TRUE,
+      ),
+      'value'    => array(
+        'description'   => 'Holds the value of a given setting',
+        'type'          => 'int',
+      ),
+    ),
+    'primary key'       => array('id', 'type', 'setting'),
+  );
+
+  db_create_table($ret, 'pm_setting', $table);
+  return $ret;
+}
+
+/**
  * Checks if an index exists in the given table.
  *
  * @param $table
diff --git a/privatemsg.module b/privatemsg.module
index 58d99e4..440ba10 100644
--- a/privatemsg.module
+++ b/privatemsg.module
@@ -392,21 +392,19 @@ function privatemsg_view_access($thread) {
  * @return
  *   TRUE if user has disabled private messaging, FALSE otherwise
  */
-function privatemsg_is_disabled(&$account) {
+function privatemsg_is_disabled($account) {
   if (!$account || !isset($account->uid) || !$account->uid) {
     return FALSE;
   }
 
-  if (!isset($account->privatemsg_disabled)) {
-    // Make sure we have a fully loaded user object and try to load it if not.
-    if ((!empty($account->roles) || $account = user_load($account->uid)) && user_access('allow disabling privatemsg', $account)) {
-      $account->privatemsg_disabled = (bool)db_result(db_query('SELECT 1 FROM {pm_disable} WHERE uid = %d', $account->uid));
-    }
-    else {
-      $account->privatemsg_disabled = FALSE;
-    }
+  // Make sure we have a fully loaded user object and try to load it if not.
+  if ((!empty($account->roles) || $account = user_load($account->uid)) && user_access('allow disabling privatemsg', $account)) {
+    $ids = privatemsg_get_default_setting_ids($account);
+    return (bool)privatemsg_get_setting('disabled', $ids);
+  }
+  else {
+    return FALSE;
   }
-  return $account->privatemsg_disabled;
 }
 
 /**
@@ -1199,8 +1197,8 @@ function privatemsg_sql_autocomplete(&$fragments, $search, $names) {
   $fragments['primary_table'] = '{users} u';
   $fragments['select'][] = 'u.uid';
 
-  // exclude users that have disabled private messaging
-  $fragments['where'][] = "NOT EXISTS (SELECT 1 FROM {pm_disable} pd WHERE pd.uid=u.uid)";
+  // Exclude users that have disabled private messaging.
+  $fragments['where'][] = "NOT EXISTS (SELECT 1 FROM {pm_setting} pms WHERE pms.id = u.uid AND pms.type = 'user' AND pms.setting = 'disabled')";
 
   // Escape the % to get it through the placeholder replacement.
   $fragments['where'][] = "u.name LIKE '%s'";
@@ -1244,6 +1242,26 @@ function privatemsg_sql_deleted(&$fragments, $days) {
   $fragments['query_args']['having'][] = time() - $days * 86400;
 }
 
+function privatemsg_sql_privatemsg_query_settings(&$fragments, $setting, $query_ids) {
+  $fragments['primary_table'] = '{pm_setting} pms';
+  
+  $fragments['select'][] = 'pms.type';
+  $fragments['select'][] = 'pms.id';
+  $fragments['select'][] = 'pms.value';
+  
+  $fragments['where'][] = "pms.setting = '%s'";
+  $fragments['query_args']['where'][] = $setting;
+  
+  $ids_condition = array();
+  foreach ($query_ids as $type => $type_ids) {
+    $ids_condition[] = "pms.type = '%s' AND pms.id IN (" . db_placeholders($type_ids) . ")";
+    $fragments['query_args']['where'][] = $type;
+    $fragments['query_args']['where'] = array_merge($fragments['query_args']['where'], $type_ids);
+  }
+
+  $fragments['where'][] = '(' . implode(') OR (', $ids_condition) . ')';
+}
+
 /**
  * @}
  */
@@ -1293,16 +1311,9 @@ function privatemsg_user($op, &$edit, &$account, $category = NULL) {
         $disabled = (!$edit['pm_enable']);
         unset($edit['pm_enable']);
 
-        $account->privatemsg_disabled = $disabled;
-
         // only perform the save if the value has changed
         if ($current != $disabled) {
-          if ($disabled) {
-            db_query('INSERT into {pm_disable} values (%d)', $account->uid);
-          }
-          else {
-            db_query('DELETE from {pm_disable} where uid = %d', $account->uid);
-          }
+          privatemsg_set_setting('user', $account->uid, 'disabled', $disabled);
         }
       }
       break;
@@ -1346,7 +1357,7 @@ function privatemsg_user($op, &$edit, &$account, $category = NULL) {
       db_query("DELETE FROM {pm_index} WHERE recipient = %d and type IN ('user', 'hidden')", $account->uid);
 
       // DELETE any disable flag for user.
-      db_query("DELETE from {pm_disable} WHERE uid = %d", $account->uid);
+      privatemsg_del_setting('user', $account->uid, 'disabled');
       break;
   }
 }
@@ -2861,3 +2872,127 @@ function privatemsg_privatemsg_header_info() {
     ),
   );
 }
+
+/**
+ * Retrieve a user setting.
+ * @param $setting
+ *   Name of the setting.
+ * @param $ids
+ *   For which ids should be looked. Keyed by the type, the value is an array of
+ *   ids for that type. The first key is the most specific (typically user),
+ *   followed by optional others, ordered by importance. For example roles and
+ *   then global.
+ *
+ * @return
+ *   The most specific value found.
+ *
+ * @see privatemsg_get_default_settings_ids().
+ */
+function privatemsg_get_setting($setting, $ids = NULL) {
+  $cache = &_privatemsg_setting_static_cache();
+
+  if (empty($ids)) {
+    $ids = privatemsg_get_default_setting_ids();
+  }
+
+  // First, try the static cache with the most specific type only. Do not check
+  // others since there might be a more specific setting which is not yet
+  // cached.
+  $type_ids = reset($ids);
+  $type = key($ids);
+  foreach ($type_ids as $type_id) {
+    if (isset($cache[$setting][$type][$type_id]) && $cache[$setting][$type][$type_id] !== FALSE && $cache[$setting][$type][$type_id] >= 0) {
+      return $cache[$setting][$type][$type_id];
+    }
+  }
+
+  // Second, look for all uncached settings.
+  $query_ids = array();
+  foreach ($ids as $type => $type_ids) {
+    foreach ($type_ids as $type_id) {
+      if (!isset($cache[$setting][$type][$type_id])) {
+        $query_ids[$type][] = $type_id;
+        // Default to FALSE for that value in case nothing can be found.
+        $cache[$setting][$type][$type_id] = FALSE;
+      }
+    }
+  }
+
+  // If there are any, query them.
+  if (!empty($query_ids)) {
+    // Build the query and execute it.
+    $query = _privatemsg_assemble_query('privatemsg_query_settings', $setting, $query_ids);
+    $result = db_query($query['query']);
+
+    while ($row = db_fetch_object($result)) {
+      $cache[$setting][$row->type][$row->id] = $row->value;
+    }
+  }
+
+  // Now, go over all cached settings and return the first match.
+  foreach ($ids as $type => $type_ids) {
+    foreach ($type_ids as $type_id) {
+      if (isset($cache[$setting][$type][$type_id]) && $cache[$setting][$type][$type_id] !== FALSE && $cache[$setting][$type][$type_id] >= 0) {
+        return $cache[$setting][$type][$type_id];
+      }
+    }
+  }
+
+  // Nothing matched, return default.
+  return 0;
+}
+
+function privatemsg_set_setting($type, $id, $setting, $value) {
+  // Based on variable_set().
+  db_query("UPDATE {pm_setting} SET value = %d WHERE type = '%s' AND id = %d AND setting = '%s'", $value, $type, $id, $setting);
+  if (!db_affected_rows()) {
+    db_query("INSERT INTO {pm_setting} (type, id, setting, value) VALUES ('%s', %d, '%s', %d)",$type, $id, $setting, $value);
+  }
+
+  // Update static cache.
+  $cache = &_privatemsg_setting_static_cache();
+  $cache[$setting][$type][$id] = $value;
+}
+
+function privatemsg_del_setting($type, $id, $setting) {
+  // Based on variable_set().
+  db_query("DELETE FROM {pm_setting} WHERE type = '%s' AND id = %d AND setting = '%s'", $type, $id, $setting);
+
+  // Update static cache.
+  $cache = &_privatemsg_setting_static_cache();
+  unset($cache[$setting][$type][$id]);
+}
+
+/**
+ * Holds the static cache for privatemsg user settings.
+ *
+ * @return
+ *   The statically cached settings of the current page.
+ */
+function &_privatemsg_setting_static_cache() {
+  static $cache = array();
+  return $cache;
+}
+
+/**
+ * Extract the default ids of a user account.
+ *
+ * Defaults to the user id, role ids and the global default.
+ *
+ * @param $account
+ *   User object, defaults to the current user.
+ *
+ * @return
+ *   Array of ids to be used in privatemsg_get_setting().
+ */
+function privatemsg_get_default_setting_ids($account = NULL) {
+  if (!$account) {
+    global $user;
+    $account = $user;
+  }
+  return array(
+    'user' => array($account->uid),
+    'role' => array_keys($account->roles),
+    'global' => array(0),
+  );
+}
diff --git a/privatemsg.test b/privatemsg.test
index 7fd61f2..dadfc01 100644
--- a/privatemsg.test
+++ b/privatemsg.test
@@ -1228,7 +1228,7 @@ class PrivatemsgAPITestCase extends DrupalWebTestCase {
     $this->assertFieldByName('subject', $subject);
 
     // Disable privatemsg for recipient 3.
-    db_query('INSERT into {pm_disable} values (%d)', $recipient3->uid);
+    privatemsg_set_setting('user', $recipient3->uid, 'disabled', 1);
 
     $this->assertFalse(privatemsg_get_link(array($recipient3), $author));
 
@@ -1255,4 +1255,56 @@ class PrivatemsgAPITestCase extends DrupalWebTestCase {
     $this->drupalLogin($recipient1);
     $this->assertFalse(privatemsg_get_link(array($author), $recipient1));
   }
+
+  /**
+   * Tests for the privatemsg_*_setting() functions.
+   */
+  function testSettings() {
+
+    $admin = user_load(1);
+    $user = $this->drupalCreateUser(array('write privatemsg'));
+    $user2 = $this->drupalCreateUser(array('write privatemsg'));
+
+
+    // Create some global and role default settings.
+    privatemsg_set_setting('global', 0, 'test', 1);
+    privatemsg_set_setting('role', array_pop(array_keys($user->roles)), 'test', 2);
+
+    // Add some user specific setting.
+    privatemsg_set_setting('user', $admin->uid, 'test', 3);
+    privatemsg_set_setting('user', $user->uid, 'test2', 4);
+    privatemsg_set_setting('user', $user2->uid, 'test2', -1);
+
+    // Clear the static cache.
+    $cache = &_privatemsg_setting_static_cache();
+    $cache = array();
+
+    // Get the ids for each user.
+    $admin_ids = privatemsg_get_default_setting_ids($admin);
+    $user_ids = privatemsg_get_default_setting_ids($user);
+    $user2_ids = privatemsg_get_default_setting_ids($user2);
+
+    $this->assertEqual(privatemsg_get_setting('test', $admin_ids), 3, t('The admin has a user level setting.'));
+    $this->assertEqual(privatemsg_get_setting('test', $user_ids), 2, t('The first user has role-level default.'));
+    $this->assertEqual(privatemsg_get_setting('test', $user2_ids), 1, t('The second user defaults to the global.'));
+
+    $this->assertEqual(privatemsg_get_setting('test2', $user_ids), 4, t('The value for another setting is read correctly.'));
+    $this->assertEqual(privatemsg_get_setting('test2', $user2_ids), 0, t('Negative values are ignored.'));
+
+    // Update existing settings, verify that the updated value is used now.
+    privatemsg_set_setting('role', array_pop(array_keys($user->roles)), 'test', 5);
+    privatemsg_set_setting('user', $admin->uid, 'test', 6);
+    privatemsg_set_setting('user', $user2->uid, 'test', 7);
+
+    $this->assertEqual(privatemsg_get_setting('test', $admin_ids), 6, t('The updated user level setting is used.'));
+    $this->assertEqual(privatemsg_get_setting('test', $user_ids), 5, t('The updated role level setting is used.'));
+    $this->assertEqual(privatemsg_get_setting('test', $user2_ids), 7, t('The second user uses the new setting.'));
+
+    // Default some settings.
+    privatemsg_del_setting('role', array_pop(array_keys($user->roles)), 'test');
+    privatemsg_del_setting('user', $admin->uid, 'test');
+
+    $this->assertEqual(privatemsg_get_setting('test', $admin_ids), 1, t('The user level setting was deleted, the default is now used.'));
+    $this->assertEqual(privatemsg_get_setting('test', $user_ids), 1, t('The role level setting was deleted, the default is now used.'));
+  }
 }
-- 
1.7.4.1

