#442102 by nbz: UX: Replace tag checkboxes with autocomplete textfield for message... 6.x-1.0-rc3
authorOleg Terenchuk
Sat, 11 Jul 2009 13:37:31 +0000 (13:37 +0000)
committerOleg Terenchuk
Sat, 11 Jul 2009 13:37:31 +0000 (13:37 +0000)
privatemsg_filter/privatemsg_filter.install
privatemsg_filter/privatemsg_filter.module

index ebfb287..a051ae7 100644 (file)
@@ -66,5 +66,13 @@ function privatemsg_filter_install() {
 
 function privatemsg_filter_uninstall() {
   variable_del('privatemsg_filter_searchbody');
+  variable_del('privatemsg_filter_tagfield_weight');
   drupal_uninstall_schema('privatemsg_filter');
 }
+
+function privatemsg_filter_update_6001() {
+  $ret = array();
+  $ret[] = update_sql("UPDATE {permission} SET perm = REPLACE(perm, 'use privatemsg_filter', 'filter private messages') WHERE perm LIKE '%use privatemsg_filter%'");
+  $ret[] = update_sql("UPDATE {permission} SET perm = REPLACE(perm, 'create privatemsg_filter', 'create private message tags') WHERE perm LIKE '%create privatemsg_filter%'");
+  return $ret;
+}
\ No newline at end of file
index 690017b..51d2cc6 100644 (file)
@@ -11,9 +11,9 @@
  */
 function privatemsg_filter_perm() {
   return array(
-    'use privatemsg_filter',
-    'create privatemsg_filter',
-    'delete privatemsg_filter',
+    'filter private messages',
+    'tag private messages',
+    'create private message tags',
   );
 }
 
@@ -21,13 +21,6 @@ function privatemsg_filter_perm() {
  * Implementation of hook_menu().
  */
 function privatemsg_filter_menu() {
-  $items['messages/tags'] = array(
-    'title'            => 'Tags',
-    'page callback'    => 'privatemsg_filter_page',
-    'access callback'  => 'privatemsg_user_access',
-    'access arguments' => array('use privatemsg_filter'),
-    'type'             => MENU_LOCAL_TASK,
-  );
   $items['admin/settings/messages/filter'] = array(
     'title'            => 'Filter',
     'description'      => 'Configure filter settings.',
@@ -67,6 +60,13 @@ function privatemsg_filter_menu() {
     'type'             => MENU_CALLBACK,
     'weight'           => -10,
   );
+  $items['messages/filter/tag-autocomplete'] = array(
+    'page callback'    => 'privatemsg_filter_tags_autocomplete',
+    'access callback'  => 'privatemsg_user_access',
+    'access arguments' => array('tag private messages'),
+    'type'             => MENU_CALLBACK,
+    'weight'           => -10,
+  );
   return $items;
 }
 
@@ -80,137 +80,23 @@ function privatemsg_filter_admin() {
     '#default_value' => variable_get('privatemsg_filter_searchbody', FALSE),
   );
 
-  return system_settings_form($form);
-}
-
-function privatemsg_filter_page() {
-  $content = '';
-  drupal_set_title(t('Tags'));
-
-  $sql = 'SELECT * FROM {pm_tags}';
-  $query = db_query($sql);
-  $tag_array = array();
-
-  while ($result = db_fetch_object($query)) {
-    $tag_array[] = l($result->tag, 'messages', array('query' => 'tags='. $result->tag));
-  }
-  if (count($tag_array)) {
-    $content .= '<h2>'. t('Current tags:') .'</h2>';
-    $content .= implode(', ', $tag_array) .'.';
-  }
-
-  if (privatemsg_user_access('create privatemsg_filter')) {
-    $content .= drupal_get_form('privatemsg_filter_add_tags');
-  }
-  if (privatemsg_user_access('delete privatemsg_filter')) {
-    $content .= drupal_get_form('privatemsg_filter_delete_tags');
-  }
-
-  return $content;
-}
-
-/**
- * Add new tags
- */
-function privatemsg_filter_add_tags($form_state) {
-  $form['addtags'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Add tags'),
-    '#collapsible' => TRUE,
-    '#collapsed' => FALSE,
-  );
-
-  $form['addtags']['newtags'] = array(
-    '#type' => 'textfield',
-    '#title' => t('What tags would you like to add?'),
-    '#description' => t('Please insert a comma separated list of new tags in the form of "tag1, tag2, tag3...". All spaces will be replaced by hyphens.'),
-    '#default_value' => '',
-    '#required' => 1,
+    $form['privatemsg_filter_tagfield_weight'] = array(
+    '#type'          => 'textfield',
+    '#title'         => t('Position of the tagging textfield'),
+    '#description'   => t('Use higher values to push the form lower down the page, lower or negative values to raise it higher.'),
+    '#size'          => 4,
+    '#default_value' => variable_get('privatemsg_filter_tagfield_weight', 10),
   );
 
-  $form['addtags']['submit'] = array(
-   '#type'     => 'submit',
-   '#value'    => t('Add tags'),
-  );
-
-
-  return $form;
-}
-
-function privatemsg_filter_add_tags_submit($form, &$form_state) {
-
-  if (isset($form_state['values']['submit'])) {
-    $tags = explode(',', $form_state['values']['newtags']);
-
-    foreach ($tags as $tag) {
-      $tag = trim($tag);
-      $tag = str_replace(' ', '-', $tag);
-      $count = db_result(db_query("SELECT COUNT(*) FROM {pm_tags} WHERE tag = '%s'", $tag));
-      if ($count == 0) {
-        db_query("INSERT INTO {pm_tags} (tag) VALUES ('%s')", $tag);
-        $inserted[] = $tag;
-      }
-    }
-  }
-  if (count($inserted)) {
-    drupal_set_message(t('!count tags have been saved: !tags.', array('!count' => count($inserted), '!tags' => implode(', ', $inserted))));
-  }
-}
-
-/**
- * Delete existing tags
- */
-function privatemsg_filter_delete_tags($form_state) {
-
-  $form['deletetags'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Delete tags'),
-    '#collapsible' => TRUE,
-    '#collapsed' => TRUE,
-  );
-
-  $sql = 'SELECT * FROM {pm_tags}';
-  $query = db_query($sql);
-  $tag_array = array();
-
-  while ($result = db_fetch_object($query)) {
-    $checked = 0;
-    $form_data = array('#type' => 'checkbox', '#title' => $result->tag, '#default_value' => $checked, '#return_value' => 1);
-    $form['deletetags']['tag_'. $result->tag_id] = $form_data;
-  }
-
-  $form['deletetags']['submit'] = array(
-    '#type'     => 'submit',
-    '#value'    => t('Delete selected tags'),
-  );
-
-  return $form;
-
-}
-
-function privatemsg_filter_delete_tags_submit($form, &$form_state) {
-  if (isset($form_state['values']['submit'])) {
-    $sql = 'SELECT * FROM {pm_tags}';
-    $query = db_query($sql);
-
-    while ($result = db_fetch_object($query)) {
-      if ($form_state['values']['tag_'. $result->tag_id] == 1) {
-        db_query('DELETE FROM {pm_tags_index} WHERE tag_id = %d', $result->tag_id);
-        db_query('DELETE FROM {pm_tags} WHERE tag_id = %d', $result->tag_id);
-        $deleted[] = $result->tag;
-      }
-    }
-  }
-  if (count($deleted)) {
-    drupal_set_message(t('!count tags have been deleted: !tags.', array('!count' => count($deleted), '!tags' => implode(', ', $deleted))));
-  }
+  return system_settings_form($form);
 }
 
 function privatemsg_filter_get_filter($account) {
   $filter = array();
   if (isset($_GET['tags'])) {
+    $_GET['tags'] = urldecode($_GET['tags']);
     $tag_data = privatemsg_filter_get_tags_data($account);
-    foreach (explode(' ', $_GET['tags']) as $tag) {
+    foreach (explode(',', $_GET['tags']) as $tag) {
       if (isset($tag_data[$tag])) {
         $filter['tags'][$tag] = $tag;
       }
@@ -245,12 +131,12 @@ function privatemsg_filter_get_tags_data($account) {
     return $tag_data;
   }
 
-  // Only show the tags that a user has used.
-  $sql = 'SELECT pmt.tag, pmt.tag_id FROM {pm_tags_index} pmti LEFT JOIN {pm_tags} pmt ON pmti.tag_id = pmt.tag_id WHERE pmti.uid = %d GROUP BY pmt.tag_id, pmt.tag';
-  $query = db_query($sql, $account->uid);
+  // Only show the tags that a user have used.
+  $query = _privatemsg_assemble_query(array('used_tags', 'privatemsg_filter'), $account);
+  $results = db_query($query['query']);
 
   $tag_data = array();
-  while ($result = db_fetch_object($query)) {
+  while ($result = db_fetch_object($results)) {
     $tag_data[$result->tag_id] = $result->tag;
   }
   return $tag_data;
@@ -394,7 +280,7 @@ function privatemsg_filter_create_get_query($filter)
     }
 
     if (isset($query['tags'])) {
-      $query['tags'] = implode(' ', $query['tags']);
+      $query['tags'] = implode(',', $query['tags']);
     }
   }
 
@@ -419,10 +305,10 @@ function privatemsg_filter_create_get_query($filter)
 }
 
 /**
- * Implementation of hook_form_alter().
+ * Implementation of hook_form_alter() to add a filter widget to the message listing pages.
  */
 function privatemsg_filter_form_privatemsg_list_alter(&$form, $form_state) {
-  if (privatemsg_user_access('use privatemsg_filter')) {
+  if (privatemsg_user_access('filter private messages')) {
     $form += privatemsg_filter_dropdown($form_state, $form['#account']);
   }
 }
@@ -479,22 +365,38 @@ function privatemsg_filter_privatemsg_sql_list_alter(&$fragments, $account, $arg
   }
 }
 
+/**
+ * Hook into the view messages page to add a form for tagging purposes.
+ */
 function privatemsg_filter_privatemsg_view_messages_alter(&$content, $thread) {
-  if (count($thread['messages']) > 0 && db_result(db_query('SELECT COUNT(*) FROM {pm_tags}')) > 0) {
+  if (count($thread['messages']) > 0) {
     $content['tags']['#value'] = drupal_get_form('privatemsg_filter_form');
-    $content['tags']['#weight'] = 10;
+    $content['tags']['#weight'] = variable_get('privatemsg_filter_tagfield_weight', 10);
   }
 }
 
+/**
+ * Form to show and allow modification of tagging information for a conversation.
+ */
 function privatemsg_filter_form(&$form_state) {
   global $user;
   $thread_id = arg(2);
 
+  // Get a list of current tags for this thread
+  $query = _privatemsg_assemble_query(array('used_tags', 'privatemsg_filter'), $user, $thread_id);
+  $results = db_query($query['query']);
+  $count = db_result(db_query($query['count']));
+  $tags = '';
+  while ($tag = db_fetch_array($results)) {
+    $tags .= $tag['tag']. ', ';
+  }
+
   $form['tags'] = array(
     '#type' => 'fieldset',
     '#title' => t('Tags'),
+    '#access' => privatemsg_user_access('tag private messages'),
     '#collapsible' => TRUE,
-    '#collapsed' => TRUE,
+    '#collapsed' => empty($count) ? TRUE : FALSE,
   );
   $form['tags']['user_id'] = array(
     '#type' => 'value',
@@ -505,22 +407,18 @@ function privatemsg_filter_form(&$form_state) {
     '#value' => $thread_id,
   );
 
-  $sql = 'SELECT * FROM {pm_tags}';
-  $query = db_query($sql);
-  $tag_array = array();
-
-  while ($result = db_fetch_object($query)) {
-    $checked = 0;
-    if (db_result(db_query('SELECT COUNT(*) FROM {pm_tags_index} WHERE tag_id = %d AND (uid = %d AND thread_id = %d)', $result->tag_id, $user->uid, $thread_id))) {
-      $checked = 1;
-    }
-    $form_data = array('#type' => 'checkbox', '#title' => $result->tag, '#default_value' => $checked, '#return_value' => 1);
-    $form['tags']['tag_'. $result->tag_id] = $form_data;
-  }
+  $form['tags']['tags'] = array(
+    '#type'               => 'textfield',
+    '#title'              => t('Tags for this conversation'),
+    '#description'        => t('Separate multiple tags with commas.'),
+    '#size'               => 50,
+    '#default_value'      => $tags,
+    '#autocomplete_path'  => 'messages/filter/tag-autocomplete',
+  );
 
   $form['tags']['submit'] = array(
     '#type'     => 'submit',
-    '#value'    => t('Tag this message'),
+    '#value'    => t('Tag this conversation'),
     '#submit'   => array('privatemsg_filter_form_submit'),
   );
 
@@ -529,19 +427,69 @@ function privatemsg_filter_form(&$form_state) {
 
 function privatemsg_filter_form_submit($form, &$form_state) {
   if (isset($form_state['values']['submit'])) {
-    $sql = 'SELECT * FROM {pm_tags}';
-    $query = db_query($sql);
+    $tags = explode(',', $form_state['values']['tags']);
 
-    while ($result = db_fetch_object($query)) {
-      if ($form_state['values']['tag_'. $result->tag_id] == 0) {
-        db_query('DELETE FROM {pm_tags_index} WHERE tag_id = %d AND (uid = %d AND thread_id = %d)', $result->tag_id, $form_state['values']['user_id'], $form_state['values']['thread_id']);
+    // Step 1 - Delete all tag mapping. I cannot think of a better way to remove tags that are no longer in the textfield, so ideas welcome.
+    db_query('DELETE FROM {pm_tags_index} WHERE uid = %d AND thread_id = %d', $form_state['values']['user_id'], $form_state['values']['thread_id']);
+
+    foreach ($tags as $tag) {
+      // Step 2 - We need to sanitise the tag.
+      // Since we allow tags to be passed via the url, there needs to be some sanity testing of each tag.
+      // Currently we replace blank spaces and a # with a "-", but this needs to be expanded to cover all the url special cases.
+      $tag = trim($tag);
+      if (empty($tag)) {
+        // Do not save a blank tag.
+        continue;
       }
-      elseif (db_result(db_query('SELECT COUNT(*) FROM {pm_tags_index} WHERE tag_id = %d AND (uid = %d AND thread_id = %d)', $result->tag_id, $form_state['values']['user_id'], $form_state['values']['thread_id'])) == 0) {
-        db_query('INSERT INTO {pm_tags_index} (tag_id, uid, thread_id) VALUES (%d, %d, %d)', $result->tag_id, $form_state['values']['user_id'], $form_state['values']['thread_id']);
+
+      // Step 3 - Make sure that the tag exists and if it does not, we need to create it.
+      $tag_id = db_result(db_query("SELECT tag_id FROM {pm_tags} WHERE tag = '%s'", $tag));
+      if (empty($tag_id) && privatemsg_user_access('create private message tags')) {
+        db_query("INSERT INTO {pm_tags} (tag) VALUES ('%s')", $tag);
+        $tag_id = db_last_insert_id('pm_tags', 'tag_id');
+      }
+      elseif (empty($tag_id)) {
+        // The user does not have permission to create new tags - disregard this tag and move onto the next.
+        drupal_set_message(t('Tag %tag was ignored because you do not have permission to create new tags.', array('%tag' => $tag)));
+        continue;
       }
+
+      // Step 4 - map the tag to the thread and the user.
+      db_query('INSERT INTO {pm_tags_index} (tag_id, uid, thread_id) VALUES (%d, %d, %d)', $tag_id, $form_state['values']['user_id'], $form_state['values']['thread_id']);
+    }
+  drupal_set_message(t('Tagging information has been saved.'));
+  }
+}
+
+/**
+ * Return autocomplete results for tags.
+ *
+ * Most of this code has been lifted/modified from privatemsg_user_name_autocomplete().
+ */
+function privatemsg_filter_tags_autocomplete($string) {
+
+  // 1: Parse $string and build a list of tags.
+  $tags = array();
+  $fragments = explode(',', $string);
+  foreach ($fragments as $index => $tag) {
+    $tag = trim($tag);
+    $tags[$tag] = $tag;
+  }
+
+  // 2: Find the next tag suggestion.
+  $fragment = array_pop($tags);
+  $matches = array();
+  if (!empty($fragment)) {
+    $query = _privatemsg_assemble_query(array('tags_autocomplete', 'privatemsg_filter'), $fragment, $tags);
+    $result = db_query_range($query['query'], $fragment, 0, 10);
+    $prefix = count($tags) ? implode(", ", $tags) .", " : '';
+    // 3: Build proper suggestions and print.
+    while ($tag = db_fetch_object($result)) {
+      $matches[$prefix . $tag->tag .", "] = $tag->tag;
     }
-    drupal_set_message(t('Tagging information has been saved.'));
   }
+  // convert to object to prevent drupal bug, see http://drupal.org/node/175361
+  drupal_json((object)$matches);
 }
 
 /**
@@ -561,4 +509,51 @@ function privatemsg_filter_privatemsg_sql_autocomplete_alter(&$fragments, $searc
     $fragments['inner_join'][] = 'INNER JOIN {pm_index} piu ON piu.uid = %d AND pip.mid = piu.mid';
     $fragments['query_args']['join'][] = $user->uid;
   }
+}
+
+/**
+ * Query definition to get the tags in use by the specified user.
+ *
+ * @param $fragments
+ *  Query fragments array.
+ * @param $user
+ *  User object for whom we want the tags.
+ * @param $thread_id
+ *  The thread_id - Only needed if we want the tags for a specific thread instead of all used tags on any thread.
+ */
+function privatemsg_filter_sql_used_tags(&$fragments, $user, $thread_id = NULL) {
+  $fragments['primary_table'] = '{pm_tags} t';
+  $fragments['select'][] = 't.tag';
+  $fragments['select'][] = 't.tag_id';
+  $fragments['inner_join'][]  = 'INNER JOIN {pm_tags_index} ti on ti.tag_id = t.tag_id';
+  if (!empty($thread_id)) {
+    $fragments['where'][] = 'ti.thread_id = %d';
+    $fragments['query_args']['where'][] = $thread_id;
+  }
+  $fragments['where'][] = 'ti.uid = %d';
+  $fragments['query_args']['where'][] = $user->uid;
+
+  $fragments['order_by'][] = 't.tag ASC';
+}
+
+/**
+ * Query definition to get autocomplete suggestions for tags
+ *
+ * @param $fragments
+ *  Query fragments array.
+ * @param $search
+ *  String fragment to use for tag suggestions.
+ * @param $tags
+ *  Array of tags not to be used as suggestions.
+ */
+function privatemsg_filter_sql_tags_autocomplete(&$fragments, $search, $tags) {
+  $fragments['primary_table'] = '{pm_tags} pmt';
+  $fragments['select'][] = 'pmt.tag';
+  $fragments['where'][] = "pmt.tag LIKE '%s'";
+  $fragments['query_args']['where'][] = $search .'%%';
+  if (!empty($tags)) {
+    $fragments['where'][] = "pmt.tag NOT IN (". db_placeholders($tags, 'text') .")";
+    $fragments['query_args']['where'] += $tags;
+  }
+  $fragments['order_by'][] = 'pmt.tag ASC';
 }
\ No newline at end of file