Sep 25

Rev Gav

Creating a Private Messaging System with Toolset

Introduction

As I discovered, creating a Private Messaging system between Users of a WordPress website is no easy thing!

I tried many different approaches and methods, all of which hit walls (for one reason or another). So… I’m writing this post in case I ever need to do it again, or you would like to give it a go yourself.

Criteria

I run a social site with members and member profiles, etc. and I wanted Members to be able to private message each other.

  • Be able to enable their ‘mailbox’ and delete their ‘mailbox’
  • Be able to select members from a dropdown list to message them.
  • Have a notification system to highlight ‘new’ messages and be able to ‘mark all as read’.
  • Ensure conversations and messages are private, only visible between the sender and recipient.

What it looks like on the front end

Before moving on to the implementation, here is what it looks like:

Mailbox (page)

There is a My Mailbox page which, if a user has not created a mailbox provides a button to let them Create it.

And here’s the same page if the Mailbox has been created:

As you can see there is a link to enable a person to rename their mailbox and a button to enable them to delete it.


Conversations (page)

Then I have a Conversations page that lists all the conversations in a user’s mailbox (if they have one):

A few things to point out here!

Firstly, at the top is a Start Conversation button and a Mark as Read button that clears the ‘orange dot’ notifications.

The Conversations are listed in order of the conversation with the most recent Message, and as you can see, the top Conversation has not been ‘cleared’ yet and there is a notification of a new message. Note: The second conversation also has a message sent by me, but as it was sent by me, I don’t need a notification!

For each conversation there is who the conversation is with (in this case a member called ‘Guest’), the subject of the conversation, and the date the conversation was started. Then, underneath this is the latest message and who sent it with an excerpt of the message and a date.

You will see the two bottom messages are called ‘NOTE’ this is because the system lets you post messages to yourself which automagically get labelled as NOTEs.

New Conversation


Start Conversation (page)

When you click on the Start Conversation button on the Conversations page it takes you to this page:

There is a simple drop-down list to be able to select the member and a box to add the subject of your conversation.


A Single Conversation (content template)

Once a conversation is started it looks like this:

It should all be pretty self explanatory. In this conversation there are three messages with the most recent at the top.

At the top of the page is the date of the conversation, a title, and links to edit the conversation title, or delete the conversation.

Then there is a text box, the ability to attach a file, and a Send button to send a message to this conversation.

You will see that Members can only Edit or Delete their own messages. Note: I am seeing Edit and Delete links on ALL messages because I am logged in as an Admin.


Privacy

Of course, if you try and view a Conversation (via URL) that does not belong to you you get this:

And if you try and view any Message directly (via URL) you get this:


Custom Post Types and Relationships

All this looks pretty simple but it’s a little more complex behind the scenes!

Custom Post Types (CPTs)

There are four CPTs:

  • Inboxes
  • Mailboxes
  • Conversations
  • Messages

Relationships

  • Inboxes (ONE) to Conversations (MANY)
  • Mailboxes (MANY) to Conversations (MANY)
  • Conversations (ONE) to Messages (MANY)

The Important Techie Bit

Now, this is the IMPORTANT techie bit (well, one of them).

WordPress/Toolset has no way of connecting Users to Posts (other than a single author or a custom field that contains a list of users) and so I create a single Inbox CPT for each user. This is what is generated when someone chooses to create their Mailbox (remember the button? – see above). The reason for this is that in a CRED Form we can then select the Inbox from a dropdown that corresponds to that user!

Conversations, as children of Inboxes, are connected to an Inbox which in turn belongs (is authored) by a User.

Now this is the complex bit.

All Conversations are automatically linked to the user’s own Inbox but of course, a Conversation has both a sender and recipient, so it needs to be connected to BOTH Inboxes… BUT IT CANNOT BECAUSE TO BE ABLE TO SELECT AN INBOX USING A DROPDOWN THE INBOX CAN ONLY BE IN A ONE-TO-MANY RELATIONSHIP AND NOT A MANY-TO-MANY RELATIONSHIP.

Did you get that? A Conversation cannot be linked to two Inboxes as the dropdown to select an Inbox will not work, for dropdowns can only be in one-to-many relationships.

And this is where the second CPT comes in called Mailbox.

When an Inbox is linked to a Conversation (one-to-many), a Mailbox is also linked to that Conversation (many-to-many) for BOTH the sender and the recipient. The Conversation is connected to two Mailboxes.

So, in the back end, we are dealing with Mailboxes and Conversations. The Inbox CPT is, in effect, just a way of connecting the Mailbox to a given User and to allow selection.


The Custom Fields

User Field Group

(Call this Field Group whatever you want – as long as it is a User Field Group)

Field Name: Last Read
Field slug: last-read
Field type: Date
Input date and time

Field Group – Conversations Meta

This Field Group is assigned to Conversations. Again, call it whatever you want.

Field name: Latest Message
Field slug: latest-message
Field type: Date
Input date and time

Field name: Latest Message Author ID
Field slug: latest-message-author-id
Field type: Number


The Code Snippets

Now, there’s quite a bit of PHP code in the functions.php file to make things work. For example, if someone deletes a Conversation then all the child Messages have to be deleted too. And if someone deletes their Mailbox then all Conversations and associated Messages have to be deleted.

Shortcode – checkmailbox

// Shortcode to check if an author of the current Conversation's Mailbox is Current logged-in user.
add_shortcode('checkmailbox', 'checkmailboxfunc');
function checkmailboxfunc() {
  $post_id = ( empty( $post->ID ) ) ? get_the_ID() : $post->ID;
  $pmailboxes = toolset_get_related_posts( $post_id,'mailbox-conversation',array('query_by_role' => 'child','role_to_return' => 'parent'));
  $currentuser = get_current_user_id();
  if ( is_array( $pmailboxes ) ) {
    foreach ($pmailboxes as $key => $imailbox) {
      $authorid = get_post_field( 'post_author', $imailbox );
      if ($currentuser == $authorid) {
      return $authorid;
      }
    }
  }
}

Shortcode – markasread

// Shortcode to mark Messages as Read
add_shortcode('markasread', 'markingasread');
function markingasread(){
  $todaysDateTime = time();
  // update the custom field last-read with todays date
  $user_code = get_current_user_id();
  update_user_meta($user_code, 'wpcf-last-read', $todaysDateTime);
}

Hook – When a new Inbox is created

// When Inbox created also create Mailbox
add_action('cred_submit_complete', 'create_mailbox',10,2);
function create_mailbox($post_id, $form_data) {
  if ($form_data['id']==ID-OF-FORM-TO-CREATE-INBOX) {
    $current_user = wp_get_current_user();
    $new_post = array(
      'post_title' => $current_user->display_name,
      'post_status' => 'publish',
      'post_date' => date('Y-m-d H:i:s'),
      'post_author' => $user_ID,
      'post_type' => 'mailbox'
    );
    $post_id = wp_insert_post($new_post);
  }
}

Hook – When a new Conversation is created

// When new Conversation posted link the user Mailbox
add_action('cred_submit_complete', 'link_user_mailbox',10,2);
function link_user_mailbox($post_id, $form_data) {
  if ($form_data['id']== ID-OF-FORM-TO-CREATE-CONVERSATION) {
    // A - LINK THE MAILBOX of the selected RECIPIENT to the CONVERSATION
    // First get the Post ID for the parent related INBOX already selected in the form
    $parentid = toolset_get_related_post ($post_id, 'inbox-conversation');
    // Secondly get the author of that Post (Inbox).
    $recipientid = get_post_field ('post_author', $parentid);
    // Thirdly get an array of Mailbox IDs for that Author
    $args = array(
      'author' => $recipientid, 
      'posts_per_page' => 1,
      'post_type' => 'mailbox'
    );
    $mailboxarray = get_posts( $args );
    // Fourthly get the first Mailbox ID (yes I know there's only one anyway)
    $mailboxid = array_shift( $mailboxarray );
    // Finally connect the Mailbox ID to the Conversation ID
    toolset_connect_posts( 'mailbox-conversation', $mailboxid, $post_id );
    // B - LINK THE MAILBOX of the Conversation AUTHOR to the CONVERSATION
    $authorid = get_post_field ('post_author', $post_id);
    $args2 = array(
      'author' => $authorid, 
      'posts_per_page' => 1,
      'post_type' => 'mailbox'
    );
    $mailboxarray2 = get_posts( $args2 );
    $mailboxid2 = array_shift( $mailboxarray2 );
    toolset_connect_posts( 'mailbox-conversation', $mailboxid2, $post_id );
    // C - Update Latest Message Date when Conversation posted - so field is populated
    $todaysDateTime = time();
    update_post_meta( $post_id, 'wpcf-latest-message', $todaysDateTime );
    // D - Update Latest Message Author to Conversation Author
    update_post_meta( $post_id, 'wpcf-latest-message-author-id', $authorid ); 
  }
}

Hook – When New Message is Posted

// Action to update Parent Conversation Date and Author when a New Message is posted
add_action('cred_submit_complete', 'update_convo_date',10,2);
function update_convo_date($post_id, $form_data) {
  if ($form_data['id']==ID-OF-FORM-TO-POST-NEW-MESSAGE) {
    $parentid = toolset_get_related_post ($post_id, 'conversation-message');
    $todaysDateTime = time();
    update_post_meta( $parentid, 'wpcf-latest-message', $todaysDateTime );
    $authorid = get_post_field ('post_author', $post_id);
    update_post_meta( $parentid, 'wpcf-latest-message-author-id', $authorid );
  }
}

Hook – When a Mailbox is Deleted

// Hook to delete child Conversations and the child Messages if Mailbox Deleted AND Inbox
add_action( 'before_delete_post', 'delete_conversations', 1, 2 );
function delete_conversations( $post_id, $post ) {
  if ( $post->post_type == 'mailbox' && $post->post_status == 'publish' ) {
    $childconvos = toolset_get_related_posts( $post_id,'mailbox-conversation',array('query_by_role' => 'parent','role_to_return' => 'child'));
    if ( is_array( $childconvos ) ) {
      foreach ($childconvos as $key => $childconvo) {
        $childmessages = toolset_get_related_posts( $childconvo, 'conversation-message',array('query_by_role' => 'parent','role_to_return' => 'child'));
        if ( is_array( $childmessages ) ) {
          foreach ($childmessages as $key => $childmessage) {
            wp_delete_post( $childmessage, true );
          }
        }
        wp_delete_post( $childconvo, true );
      }
    }
    // Delete the Inbox too
    $args = array (
      'post_type' => 'inbox',
      'author' => get_current_user_id(),
      'orderby' => 'date',
    );
    $query = new WP_Query( $args );
    if ( $query->have_posts() ) {
      while ( $query->have_posts() ) {
        $query->the_post(); //get the current post
        wp_delete_post( $query->post->ID, true );
      }
    }
  }
}

Hook – When a Conversation is Deleted

// Hook to delete child Messages if parent Conversation is deleted
add_action( 'before_delete_post', 'delete_conversation_messages', 1, 2 );
function delete_conversation_messages( $post_id, $post ){
  if ( $post->post_type == 'conversation' && $post->post_status == 'publish' ) { 
    $children = toolset_get_related_posts( $post_id,'conversation-message',array('query_by_role' => 'parent','role_to_return' => 'child'));
    if ( is_array( $children ) ) {
      foreach ($children as $key => $child) {
        wp_delete_post( $child, true );
      }
    }
  }
}

Redirect – Edit Message Form

// Redirect Edit Message form to parent conversation plus anchor messagePOSTID
if ($form_data['id']==ID-OF-EDIT-MESSAGE-FORM) {
  $originalid = toolset_get_related_post( $post_id, 'conversation-message' );
  $parent_post_url = get_permalink($originalid).'#message'.$post_id;
  return $parent_post_url;
}

Redirect – New Conversation Form

// Redirect New Conversation to the conversation
if ($form_data['id']==ID-OF-NEW-CONVERSATION-FORM) {
  $parent_post_url = get_permalink($post_id);
  return $parent_post_url;
}

You need to be a logged in Guest or Member to reply.
You need to be a logged in Guest or Member to like this post.

© fab.church