? pm_block_user/add-rule-button.png
? pm_block_user/remove-rule-button.png
Index: pm_block_user/pm_block_user.css
===================================================================
RCS file: pm_block_user/pm_block_user.css
diff -N pm_block_user/pm_block_user.css
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ pm_block_user/pm_block_user.css	9 Apr 2009 18:29:25 -0000
@@ -0,0 +1,39 @@
+#pm-block-user-settings #edit-pm-block-actions-more {
+  margin: 0;
+}
+/* Stop the pesky throbber messing with the submit button's position. */
+#pm-block-user-settings #edit-submit-form {
+  display: block;
+}
+#pm-block-user-settings .throbber {
+  margin: 4px 0;
+}
+#pm-block-user-settings label {
+  line-height: 24px;
+  margin-left: 2px;
+}
+#add-rule-button, #remove-rule-button {
+  display: block;
+  clear: both;
+  height: 24px;
+}
+#add-rule-button input,
+#remove-rule-button input {
+  display: inline-block;
+  overflow: hidden;
+  width: 24px;
+  height: 24px;
+  padding: 0;
+  margin: 0;
+  border: 0;
+  text-indent: -9999px;
+  cursor: pointer; /* Hand shaped cursor */
+  cursor: hand; /* for old versions of IE */
+}
+#add-rule-button input {
+  background: transparent url('add-rule-button.png') left top no-repeat;
+}
+#remove-rule-button input {
+  background: transparent url('remove-rule-button.png') left top no-repeat;
+}
+
Index: pm_block_user/pm_block_user.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/privatemsg/pm_block_user/pm_block_user.module,v
retrieving revision 1.1.2.3
diff -u -p -r1.1.2.3 pm_block_user.module
--- pm_block_user/pm_block_user.module	20 Feb 2009 00:34:27 -0000	1.1.2.3
+++ pm_block_user/pm_block_user.module	9 Apr 2009 18:29:25 -0000
@@ -7,6 +7,16 @@
  */
 
 /**
+ * Implementation of hook_help().
+ */
+function pm_block_user_help($path) {
+  switch ($path) {
+    case 'admin/settings/messages/block':
+      return '<p>' . t('This area is used to define the rules which govern whether a user may blocked from sending private messages. Rules allow control of who may block messages from whom. By default all users are allowed to block messages from anyone else. However, a site may have groups of users that need to contact or get information to others, for example: the site may have administrative staff or be a forum with moderators. Groups of users are defined by roles, which can be managed on the <a href="@roles">roles configuration page</a>.', array('@roles' => url('admin/user/roles'))) . '</p>';
+  }
+}
+
+/**
  * Implementation of hook_menu().
  */
 function pm_block_user_menu() {
@@ -14,13 +24,329 @@ function pm_block_user_menu() {
     'title'            => 'Block user messages',
     'page callback'    => 'drupal_get_form',
     'page arguments'   => array('pm_block_user_form', 2),
-    'access callback'  => 'privatemsg_user_access',
+    'access callback'  => '_pm_block_user_access',
+    'access arguments' => array(2),
     'type'             => MENU_CALLBACK,
     'weight'           => -10,
   );
+
+  $items['admin/settings/messages/block'] = array(
+    'title'            => 'Block author rules',
+    'description'      => 'Configure rules for which users may block each other.',
+    'page callback'    => 'drupal_get_form',
+    'page arguments'   => array('pm_block_user_settings'),
+    'access arguments' => array('administer privatemsg settings'),
+    'type'             => MENU_LOCAL_TASK,
+  );
+
+  $items['messages/block/js'] = array(
+    'title' => 'Javascript Block Actions Form',
+    'page callback' => 'pm_block_user_js',
+    'access arguments' => array('administer privatemsg settings'),
+    'type' => MENU_CALLBACK,
+  );
+
   return $items;
 }
 
+/**
+ * Menu callback for blocked user settings.
+ */
+function pm_block_user_settings(&$form_state) {
+  drupal_add_css(drupal_get_path('module', 'pm_block_user') .'/pm_block_user.css');
+  // Need to cache form for AHAH, so it can be rebuilt from cache later.
+  $form = array(
+    '#cache' => TRUE,
+  );
+
+  // Container for just the actions, used for AHAH.
+  $form['block_actions'] = array(
+    '#tree' => TRUE,
+    '#prefix' => '<div id="block-actions">',
+    '#suffix' => '</div>',
+    '#theme' => 'pm_block_user_actions',
+  );
+
+  // Should we populate the form with data from $form_state or the database?
+  if (!isset($form_state['pm_block_user']['block_actions'])) {
+    $block_actions = variable_get('pm_block_user_actions', array());
+  }
+  else {
+    $block_actions = $form_state['pm_block_user']['block_actions'];
+  }
+  foreach ($block_actions as $delta => $details) {
+    // $delta may be zero, if $block_actions is an empty array.
+    $delta = ($delta > 0 ? $delta : 1);
+    $details['delta'] = $delta;
+    $form['block_actions'][$delta] = _pm_block_user_actions_form($details);
+  }
+
+  // The magic AHAH callback that adds more rows.
+  $form['pm_block_actions_more'] = array(
+    '#type' => 'submit',
+    '#value' => t('More'),
+    '#weight' => 1,
+    '#prefix' => '<div id="add-rule-button">',
+    '#suffix' => '<label for="edit-pm-block-actions-more">' . t('Add new rule') . '</label></div>',
+    '#submit' => array('pm_block_user_more_submit'),
+    '#ahah' => array(
+      'path' => 'messages/block/js',
+      'wrapper' => 'block-actions',
+      'method' => 'replace',
+      'effect' => 'fade',
+    ),
+  );
+
+  $form['submit_form'] = array(
+    '#type' => 'submit',
+    '#weight' => 10,
+    '#value' => t('Save configuration'),
+  );
+
+  return $form;
+}
+
+/**
+ * Builds row of sending, receiving roles and actions that go with them.
+ * 
+ * @param $details
+ *   Details of the row: default values and the unique row number (delta).
+ * @return
+ *   Part of a form with controls for sending, receiving and actions.
+ */
+function _pm_block_user_actions_form($details) {
+  $form = array(
+    '#tree' => TRUE,
+  );
+  $delta = $details['delta'];
+  // FALSE by default, or if the user has checked the 'Enabled' check box for
+  // this row.
+  $row_disabled = (isset($details['enabled']) ? !$details['enabled'] : FALSE);
+
+  $form['sender'] = array(
+    '#type' => 'select',
+    '#options' => user_roles(TRUE),
+    '#default_value' => (isset($details['sender']) ? $details['sender'] : 2),
+    '#disabled' => $row_disabled,
+  );
+  $form['receiver'] = array(
+    '#type' => 'select',
+    '#options' => user_roles(TRUE),
+    '#default_value' => (isset($details['receiver']) ? $details['receiver'] : 2),
+    '#disabled' => $row_disabled,
+  );
+  $form['action']  = array(
+    '#type' => 'radios',
+    '#options' => array(t('Allow blocking'), t('Disallow blocking')),
+    '#default_value' => (isset($details['action']) ? $details['action'] : 1),
+    '#disabled' => $row_disabled,
+  );
+  $form['enabled'] = array(
+    '#type' => 'checkbox',
+    '#default_value' => (isset($details['enabled']) ? $details['enabled'] : TRUE),
+  );
+  $form['remove'] = array(
+    '#type' => 'submit',
+    '#submit' => array('pm_block_user_remove_submit'),
+    '#value' => t('Remove_' . $delta),
+    '#attributes' => array('class' => 'remove-action'),
+    '#prefix' => '<div id="remove-rule-button">',
+    '#suffix' => '<label for="edit-remove">' . t('Remove rule') . '</label></div>',
+    '#ahah' => array(
+      'path' => 'messages/block/js',
+      'wrapper' => 'block-actions',
+      'method' => 'replace',
+      'effect' => 'fade',
+    ),
+  );
+
+  return $form;
+}
+
+/**
+ * Submit handler for 'More' button, adds a new action.
+ *
+ * @see pm_block_user_remove_submit()
+ */
+function pm_block_user_more_submit($form, &$form_state) {
+  unset($form_state['submit_handlers']);
+  form_execute_handlers('submit', $form, $form_state);
+  // Get the submitted actions, then put them into a special area of
+  // the $form_state.
+  $submitted_values = $form_state['values'];
+  // Add an empty action.
+  $submitted_values['block_actions'][] = array();
+  $form_state['pm_block_user'] = $submitted_values;
+  // Rebuild the form by passing our $form_state through the
+  // pm_block_user_settings() builder function.
+  $form_state['rebuild'] = TRUE;
+}
+
+/**
+ * Submit handler for 'Remove' button, removes an action.
+ *
+ * @see pm_block_user_more_submit()
+ */
+function pm_block_user_remove_submit($form, &$form_state) {
+  unset($form_state['submit_handlers']);
+  form_execute_handlers('submit', $form, $form_state);
+  $submitted_values = $form_state['values'];
+  // Remove the requested action.
+  $delta = $form_state['clicked_button']['#parents'][1];
+  unset($submitted_values['block_actions'][$delta]);
+  $form_state['pm_block_user'] = $submitted_values;
+  $form_state['rebuild'] = TRUE;
+}
+
+/**
+ * Submit handler for admin form.
+ */
+function pm_block_user_settings_submit($form, &$form_state) {
+  // We don't want it to submit when we're adding/removing actions.
+  if ($form_state['clicked_button']['#id'] == 'edit-submit-form') {
+    // If the form's 'block_actions' aren't set, the user has deleted all the
+    // rows in the table, so we save an empty array. Otherwise it'll be treated
+    // as default by the form builder callback, which will generate a new
+    // default row.
+    if (isset($form_state['values']['block_actions'])) {
+      variable_set('pm_block_user_actions', $form_state['values']['block_actions']);
+    }
+    else {
+      variable_set('pm_block_user_actions', array());
+    }
+    drupal_set_message(t('The configuration options have been saved.'));
+  }
+}
+
+/**
+ * Menu callback for AHAH handling.
+ */
+function pm_block_user_js() {
+  // See: http://drupal.org/node/331941 for the philosophy of Drupal AHAH.
+  $form_state = array('storage' => NULL, 'submitted' => FALSE);
+  $form_build_id = $_POST['form_build_id'];
+  $form = form_get_cache($form_build_id, $form_state);
+  $args = $form['#parameters'];
+  $form_id = array_shift($args);
+  $form['#post'] = $_POST;
+  $form['#redirect'] = FALSE;
+  $form['#programmed'] = FALSE;
+  $form_state['post'] = $_POST;
+  drupal_process_form($form_id, $form, $form_state);
+  $form = drupal_rebuild_form($form_id, $form_state, $args, $form_build_id);
+  $output_form = $form['block_actions'];
+  unset($output_form['#prefix'], $output_form['#suffix']); // Prevent duplicate wrappers.
+  $output = theme('status_messages') . drupal_render($output_form);
+
+  // Hack to make behaviours attach to new HTML controls (delete buttons in
+  // this case).
+  $javascript = drupal_add_js(NULL, NULL, 'header');
+  drupal_json(array('status' => TRUE, 'data' => $output));
+}
+
+/**
+ * Implementation of hook_theme().
+ */
+function pm_block_user_theme() {
+  return array(
+    'pm_block_user_actions' => array(
+      'arguments' => array('form' => NULL),
+    ),
+  );
+}
+
+/**
+ * Theme the user actions form.
+ *
+ * @ingroup themeable
+ */
+function theme_pm_block_user_actions($form) {
+  $rows = array();
+  $headers = array(
+    t('If the sender has the role'),
+    t('And the receiver has the role'),
+    t('Action'),
+    t('Enabled'),
+    '',
+  );
+  $form_data = element_children($form);
+
+  foreach ($form_data as $key) {
+    // Build the table row.
+    $row = array(
+      'data' => array(
+        array('data' => drupal_render($form[$key]['sender'])),
+        array('data' => drupal_render($form[$key]['receiver'])),
+        array('data' => drupal_render($form[$key]['action'])),
+        array('data' => drupal_render($form[$key]['enabled'])),
+        array('data' => drupal_render($form[$key]['remove'])),
+      ),
+    );
+
+    // Add additional attributes to the row, such as a class for this row.
+    if (isset($form[$key]['#attributes'])) {
+      $row = array_merge($row, $form[$key]['#attributes']);
+    }
+    $rows[] = $row;
+  }
+
+  // If there are no rows, output some instructions for the user.
+  if (empty($form_data)) {
+    $rows[] = array(
+      array(
+        'data' => t("No rules have been added. All users may block private messages from each other. To limit which users may be blocked, click 'Add new rule'."),
+        'colspan' => '5',
+      ),
+    );
+  }
+
+  $output = theme('table', $headers, $rows);
+  $output .= drupal_render($form);
+
+  return $output;
+}
+
+/**
+ * Provides access argument for blocking user menu item.
+ *
+ * @param $account
+ *   User object representing the account the menu item will block private
+ *   messages from.
+ *
+ * @return
+ *   TRUE if the user is allowed to block $account, or FALSE if not.
+ */
+function _pm_block_user_access($account) {
+  global $user;
+  if (!user_access('read privatemsg', $user)) {
+    return FALSE;
+  }
+  if (!_pm_block_user_checkrole($account)) {
+    return FALSE;
+  }
+  return TRUE;
+}
+
+/**
+ * Checks whether it is possible to block private messages from a given user.
+ *
+ * @param $account
+ *   User object representing the user account to check.
+ *
+ * @return
+ *   Boolean value which is FALSE if it is not possible to block the user and
+ *   TRUE if the user can be blocked.
+ */
+function _pm_block_user_checkrole($account) {
+  if ($account->uid == 1) {
+    return FALSE;
+  }
+  if (array_intersect(array_keys((array) $account->roles), variable_get('pm_block_user_unblockable_roles', array()))) {
+    return FALSE;
+  }
+  return TRUE;
+}
+
 function pm_block_user_form($form_state, $author) {
   global $user;
 
@@ -69,8 +395,13 @@ function pm_block_user_form($form_state,
   }
 }
 
+/**
+ * Implementation of hook_form_submit().
+ */
 function pm_block_user_form_submit($form, &$form_state) {
-
+  if (!_pm_block_user_checkrole(user_load($author_id))) {
+    return;
+  }
   if ($form_state['values']['confirm']) {
     switch ($form_state['values']['block_action']) {
       case 'block_user':
@@ -90,7 +421,9 @@ function pm_block_user_form_submit($form
  * Implementation of hook_privatemsg_block_message.
  */
  function pm_block_user_privatemsg_block_message($author, $recipient) {
-  global $user;
+  if (_pm_block_user_checkrole($recipient)) {
+    return;
+  }
   $result = db_result(db_query('SELECT COUNT(*) FROM {pm_block_user} WHERE author = %d AND recipient = %d', $author->uid, $recipient->uid));
   if ($result <> 0) {
     return t('!name has chosen to not recieve any more messages from you.', array('!name' => $recipient->name));
@@ -107,10 +440,13 @@ function pm_block_user_privatemsg_sql_lo
 /**
  * Implementation of hook_privatemsg_message_view_alter.
  */
- function pm_block_user_privatemsg_message_view_alter(&$vars) {
+function pm_block_user_privatemsg_message_view_alter(&$vars) {
   global $user;
 
   $author_id = $vars['message']['author']->uid;
+  if (!_pm_block_user_checkrole(user_load($author_id))) {
+    return;
+  }
   if (!isset($vars['message']['thread_id'])) {
     // No thread id, this is probably only a preview
     return;
@@ -125,4 +461,4 @@ function pm_block_user_privatemsg_sql_lo
       $vars['message_actions'][] = array('title' => t('Block author'), 'href' => 'messages/block/'. $author_id, array('query' => 'destination=messages/view/' . $thread_id));
     }
   }
-}
\ No newline at end of file
+}
