Is it possible to totally disable receiving private messages for a user? It could be subject to limitation like in #267814: Setting to define certain roles (e.g. staff) which a user cannot block PMs from, or an enhancement of the configuration settings in #341370: Add "Send msg to author" link to selected node types. But fundamentally I want the ability for a user to say, "I don't receive private messages." It appears that from line 706 ( "2) Make sure he accepts private messages." ) of privatemsg.module that somebody else had this idea, but there doesn't appear to be any code actually implementing that idea.

Am I missing something?

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

NaheemSays’s picture

By this do you mean private messages from anyone or just from a selected user?

If selected user, then the "block user messages" module does that. but in general there is nothing atm.

rmjiv’s picture

I meant in general. I didn't think there was, but that line in privatemsg.module gave me hope. :)

I'd be willing to write the feature if we can figure out what would make the most sense for other users.

I'm thinking an enhancement to the privatemsg main module makes the most sense. If the "receive messages" setting is off for the user, it could override any other setting for "message me" type links, etc. The key would be to not break override functionality like described in #267814: Setting to define certain roles (e.g. staff) which a user cannot block PMs from.

Thoughts?

Berdir’s picture

As nbz said, there is currently nothing like that, but this is easy to implement in a small custom module, you don't need to change privatemsg.module for that.

You just need to implement the following hook: http://blog.worldempire.ch/api/function/hook_privatemsg_block_message/1*.

Create a table where you store which user is allowed to write to which, add a new permission "write a private messages to all users", add a way to "mark" users that they are allowed to write you and then check in the block_message hook if $author has the mentioned permission or if $author is allowed to write $recipient.

privatemsg.module will then take care of the rest (don't display send message links, block messages to users which you are not allowed to send and so on).

* That hook will be changed, instead of being called multiple times for multiple recipients, it will receive an array of recipients. See #376023: #288183 followup: change hook_privatemsg_block_message to work on multiple recipients.

rmjiv’s picture

Is this a feature that you'd be interested incorporating into the privatemsg module itself or if I write it as a submodule, into the privatemsg family?

If you don't think it has enough value as a feature I'll just write it for us. But if there is enough interest I'll write a more generic version and contribute it back.

rmjiv’s picture

Assigned: Unassigned » rmjiv
Status: Active » Needs work
FileSize
1.47 KB

I've taken an initial run at this.

The basic semantics of this module are that if you've disabled messages, the whole functionality is turned off. You can't access your inbox or sent messages, etc. Given that approach a couple of issues arose:

1. A way of overriding the privatemsg_user_access function with a hook would be nice. The method I took to hiding the menu tabs isn't generic and is pretty clumsy.

2. I attempted to handle what I could for viewing another user's private messages section, but since the functionality in the actual module isn't working I make no promises.

3. I think that fundamentally this is a feature that belongs in the private messages base module. :)

NaheemSays’s picture

Not had a look at the attachment, but have you tried modifying the "block user messages" module? Have an access check in there so that if the recipient cannot read messages, show an error and block the message?

rmjiv’s picture

I considered it. But I don't think it makes sense to allow a user to block receiving any PMs and still allow them to send them. And putting code in the "Block User messages" module to stop the sending of private messages as well as hiding the inbox, etc. seemed to change the meaning of that module too much.

NaheemSays’s picture

The inbox etc will be hidden from a user if they do not have access to read the messages anyway? (well, if it isn't, it should be)

The block user messages check should be to see if a recipient can read the message or not and this can be tweaked for other situations where it may not be a good idea to receive messages to an account that sends them (think of a notification account linked into some rule where no one reads the replies...)

Berdir’s picture

I do now understand what you want, it wasn't clear to me from what you wrote above. I thought you want the opposite of the pm_block_user module, a module that would allow to only allow specific users to write you, a whitelist instead of a blacklist system.

Yes, it probably makes sense to have such a feature in privatemsg.module A few things, from looking over your code:

- You should not try to fix things that are broken in privatemsg.module with sort of an overlay. If you are interested in fixing the read all messages stuff, please contribute to #298502: Better access to another user's messages. The problem with that issue is that it a) currently heavily conflicts with other things, like thread actions and nbz'x inbox patch and b) it is way more complex than just allowing users to see the the list of messages of another users, see me ideas at #17.

- Why not creating your own access function which calls privatemsg_view_access? (For now, adding it to privatemsg.module would make it easier, but that needs some time to get reviewed etc.)

- You should probably add the setting to the user edit form...

- If implementing it as a patch against privatemsg.module, don't forget to add a admin setting to turn that feature on/off.

rmjiv’s picture

FileSize
4.25 KB

I finally got around to implementing this as a patch against the privatemsg module. It came out cleaner than as a submodule. This also incorporates the relevant code from #393946: User settings page for the settings page.

Berdir’s picture

Looking at the patch, some things I noticed..

- I'd suggest adding either a permission or an admin setting which allows to enable/disable that feature.
- Because of the above, it makes sense to use my proposed user_settings hook. If we wouldn't do that, other modules could simply alter your form.

+ * Implementation of hook_privatemsg_user_settings.
+ *

- Minor coding style issue: add () to the hook name and remove "empty" line

- I'd suggest to move the submit button to privatemsg_user_settings() and rename it to "Save settings" (doesn't need a title, imho) or something like that so that all modules can share that one.

- I think you can remove most of those empty lines in privatemsg_is_disabled and privatemsg_privatemsg_user_settings

+function privatemsg_is_disabled($uid) {
- A simple apidoc for that function would be nice, explain what it does, expect and returns

+ drupal_set_message(t('You have @status private messages.', array('@status' =>$status) ));
- enabled/disabled won't be translatable that way, you need to use a separate t() call.

- Can't we use $user->data to store if a user has enabled/disabled privatemsg? that would save us another table and a additional query on each page request. IIRC, you could something like: (untested, according to http://api.drupal.org/api/function/user_save/6)

user_save($user, array('privatemsg_disabled' => FALSE));

And then access it with

if (array_key_exists($user->data['privatemsg_disabled']) && $user->data['privatemsg_disabled'] == FALSE) {

- If not, you need to provide a update function which creates your table.

rmjiv’s picture

FileSize
5.66 KB

I'm willing to add another permission setting if you feel it's important. I'm not sure I understand your follow up point, though.

I went ahead and rewrote this to use the $user->data field, but the more I thought about it the less I liked it. I agree that we're going to have issues if we don't store the setting so it doesn't have to be retrieved every time, but there are other ways to handle that (hook_user for example). The $user->data design is deeply flawed in my opinion and Drupal should remove it entirely, it violates the modularity of the architecture.

So I ended up reverting those changes and made some other code changes to only look up of the disabled flag once per request.

I also made the other suggested changes. Rerolled patch is attached.

v8powerage’s picture

Hi

rmjiv - I couldn't apply patch from #12 into Privatemsg 6.x-1.0-rc2, why it's so?

Berdir’s picture

Version: 6.x-1.x-dev

That's why ;)

Patches are always against the -dev release. They *might* apply against specific releases, but in our case, there are too many changes.

v8powerage’s picture

Hi
I tried apply patch to the latest dev release but couldn't make it anyway. Perhaps You have proper version, already patched so You could post it here?

rmjiv’s picture

There have been a number of changes to -dev since the patch was rolled. I may have time to re-roll the patch in the next week or so.

rmjiv’s picture

Status: Needs work » Needs review
FileSize
8.94 KB

Rerolled patch against privatemsg-6.x-2.x-dev.

Berdir’s picture

Status: Needs review » Needs work

Thanks for the re-roll. Don't worry about the amount of points below, your code is very clean, my points are mostly about design decisions/UX. If you have any questions, please post them here and I'll try to answer asap.

+++ privatemsg.install	(working copy)
@@ -103,9 +103,22 @@ function privatemsg_schema() {
-  return $schema;
+   return $schema;

This seems to have too many spaces...

+++ privatemsg.pages.inc	(working copy)
@@ -450,6 +450,65 @@ function privatemsg_delete_submit($form,
+function privatemsg_user_settings($form_state, $uid = NULL) {

We decided to go away from a settings page below /messages/ and instead use the user edit page (Not the users.data storage, we just visually embed it).

This makes a lot of custom code, permissions, menu entries and so on unecessary and is imho better UX because all settings a user can edit are at the same place.

You can find an example inside pm_email_notify_user(), hooks form and submit. You could also think about providing a Privatemsg category so that we can put the email setting there too in a follow-up patch.

+++ privatemsg.pages.inc	(working copy)
@@ -450,6 +450,65 @@ function privatemsg_delete_submit($form,
+  $form['disable'] = array(
+    '#type' => 'radios',
+    '#title' => t('Private Message Receiving'),
+    '#default_value' => (int) !privatemsg_is_disabled($user),
+    '#options' => array(t('Disabled'), t('Enabled')),

Maybe using a checkbox would make more sense here? Not sure, but that's we've done in other places. Also, a description would be great.

+++ privatemsg.module	(working copy)
@@ -27,6 +27,7 @@ function privatemsg_perm() {
     'administer privatemsg settings',
+    'administer privatemsg user settings',

If we place it in the user edit page, that should be handled by the administer users permission...

+++ privatemsg.module	(working copy)
@@ -257,6 +286,25 @@ function privatemsg_view_access() {
+    $account->privatemsg_disabled = (db_result(db_query('SELECT COUNT(0) FROM {pm_disable} WHERE uid = %d ', $account->uid)) <> 0);
+  }

you don't need the COUNT(0) here, "SELECT 1 FROM" .. is faster and enough.

+++ privatemsg.module	(working copy)
@@ -840,6 +888,11 @@ function privatemsg_sql_unread_count(&$f
+  // exclude users that have disable private messaging
+  $fragments['inner_join'][] = "LEFT JOIN {pm_disable} pd on pd.uid=u.uid";
+  $fragments['where'][] = "pd.uid is NULL";

Probably a faster option would be to use a "NOT IN (SELECT pd.uid FROM {pm_disable} pd)" since that sub-query needs to executed only once.

This review is powered by Dreditor.

rmjiv’s picture

FileSize
6.61 KB

I made the suggested changes.

I ended up going with NOT EXISTS (SELECT 1 FROM {pm_disable} pd WHERE pd.uid=u.uid) since I think that's the "right" way to do it. And the performance will vary depending on how many users have disabled private messaging.

I deferred creating a category for private messages, I'll leave to somebody with more familiarity with the project to determine the usefulness of that option.

Re-rolled.

rmjiv’s picture

Version: 6.x-1.x-dev »
Status: Needs work » Needs review

Updating status and version.

Berdir’s picture

Your changes look visually good, thanks for the fast reroll!

Î'll test this as soon as I find the time.

One last thing you could do if you have the time and are interested in doing so. It would be great to have a test for this. Nothing complicated, just trying to send/recieve a message from/to a user that does have disabled private messages.

You can start with an already existing test case (a method that begins with "test...") in privatemsg.module and either extend that or create a new one.

I would do something like that:

- Create two users (let's call them A and B), both with read/write permission
- Login as user A
- Write a message from A to B
- Login as user B
- Disable privatemsg for user B (through the UI)
- Check that the previously sent message is still available
- Check that B can't response to the message nor send a new one
- Login as user A
- Try to send a message to user B and check that the correct error message is displayed.

With that, we should have covered the whole patch (I think) and can be quite certain that it won't get broken through other patches).

Again, you don't need to do it but it will greatly increase the chances that your patch is commited since I'd have to do it on my own if you don't. If you need assistance in writing tests and/or getting simpletest running, you're welcome to ask here or in IRC (#drupal-games)

rmjiv’s picture

With test cases. I also changed the name of the form field in hook_user, it was logically backwards.

I haven't done simple tests before, so if I did anything wrong let me know.

Berdir’s picture

Status: Needs review » Needs work

Awesome work, thanks very much!

+++ privatemsg.test	(working copy)
@@ -333,6 +333,68 @@ class PrivatemsgTestCase extends DrupalW
+    $account = user_load($disableduser->uid, TRUE);

Not sure, is that line really necessary? You only need the uid before the second user_load()

+++ privatemsg.test	(working copy)
@@ -333,6 +333,68 @@ class PrivatemsgTestCase extends DrupalW
+    $this->assertFalse($result['success'], $message = 'Message reply was not sent.');

The "$message = " is unecessary here, I think.

I'm on crack. Are you, too?

rmjiv’s picture

Status: Needs work » Needs review
FileSize
11.95 KB

Correct in both cases. I copied the user_load pattern from user.test where it has this:

// Create a user.
$account = $this->drupalCreateUser(array('cancel account'));
$this->drupalLogin($account);
// Load real user object.
$account = user_load($account->uid, TRUE); 

I figured I was missing something and it needed to be there, but I can't see a difference in the loaded objects.

Re-rolled.

Berdir’s picture

Version: » 7.x-1.x-dev
Status: Needs review » Patch (to be ported)

Ok, I finally tried to patch and everything looks fine - commited against 6.x-2.x! Thanks again!

This needs to be ported to 7.x, I assume the tests have to be updated a bit and it also needs to use DBTNG. You are welcome to port the patch but I will do it sooner or later myself.

Berdir’s picture

Status: Patch (to be ported) » Fixed

Ported and commited to D7.

Status: Fixed » Closed (fixed)

Automatically closed -- issue fixed for 2 weeks with no activity.

rmjiv’s picture

Version: 7.x-1.x-dev »
Status: Closed (fixed) » Needs work

Whoops, I just found a bug in this code. The "submit" case in privatemsg_user hook needs to have an if statement like so:
(starting at line 969)

case 'submit':
  if ($category == 'account') {
    $current = privatemsg_is_disabled($account);
    $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);
      }

    }
  }
  break;

Otherwise the status gets set to disabled if the profile is saved for another category. I'm not in sync with CVS right now or I'd roll the patch. Let me know if you need me to.

Berdir’s picture

Noticed this when porting to drupal 7 but forgot it then. Aka, it's fixed in Drupal 7 already:

  if ($category == 'account' && isset($edit['pm_enable'])) {

I will roll a patch, but it might take a few days so if you can spare a few minutes and do it, that would be great.

Berdir’s picture

Status: Needs work » Needs review
FileSize
1.52 KB

Added the check...

Berdir’s picture

Status: Needs review » Fixed

Actually, I've seen that I've already handled that here: #691030: My account page visibility. Setting back to fixed.

Status: Fixed » Closed (fixed)

Automatically closed -- issue fixed for 2 weeks with no activity.