Index: pm_service/pm_service.inc
===================================================================
RCS file: pm_service/pm_service.inc
diff -N pm_service/pm_service.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ pm_service/pm_service.inc	3 Dec 2010 01:47:41 -0000
@@ -0,0 +1,231 @@
+<?php
+
+// $Id$
+
+/**
+ * @file
+ *  Link general private message module functionalities to services module.
+ */
+
+/**
+ * Get all of the logged in user's private messages.
+ *
+ * @param $type
+ * String. Which type of messages you would like to retrieve.
+ * Possibilities are inbox or sent.
+ *
+ * @param $load_full
+ * Boolean. Whether to load the full message for each message or not.
+ *
+ * @param $offset
+ *   Integer (optional). An offset integer for paging.
+ * @param $limit
+ *   Integer (optional). A limit integer for paging.
+ *
+ * @return
+ *   An array of messages.
+ *
+ */
+function pm_service_get($type = 'inbox', $load_full = FALSE, $offset = 0, $limit = 0, $uid = '') {
+
+  $messages = array();
+  $pms = array();
+
+  // If no type passed set default type to inbox.
+  if (!$type) {
+    $type = 'inbox';
+  }
+  
+  // User needs to be authenticated to proceed.
+  global $user;
+  if (!user_is_logged_in()) {
+    return services_error(t('This user is not logged in.'), 403);
+  }
+    
+  // If a user id other than the current user's ID is passed,
+  // validate that the authenticated user has the correct 
+  // permissions to read another user's messages.
+  if (is_numeric($uid) && ($uid != $user->uid)) {
+    if (user_access("read all private messages")) {
+      $account = user_load($uid);
+    }
+    else {
+      return services_error(t('This user does not have permissions to read all messages.'), 403);
+    }
+  }
+  // Use the current user for the account.
+  else {
+    $account = $user;
+  }
+
+  // Construct the query and retrieve the correct set of messages.
+  $query = _privatemsg_assemble_query('list', $account, $type);
+  
+  // Handle any paging if an offset or a limit was passed in.
+  if (empty($offset) && !empty($limit)) {
+    $result = db_query_range($query['query'], 0, $limit);
+  }
+  elseif (!empty($offset) && !empty($limit)) {
+    $result = db_query_range($query['query'], $offset, $limit);
+  }
+  elseif (!empty($offset) && empty($limit)) {
+    $rows = (int)db_result(db_query($query['count']));
+    $result = db_query_range($query['query'], $offset, $rows);
+  }
+  else {
+    // No paging, we are retrieving everything.
+    $result = db_query($query['query']);
+  }
+  
+  // Loop the result object to get the messages.
+  while ($row = db_fetch_object($result)) {
+    $pms[$row->thread_id] = $row;
+  }
+  
+  // Check whether we are loading the full thread or not.
+  if (!empty($pms) && $load_full) {
+    foreach ($pms as $thread_id => $msg) {
+      $messages[] = privatemsg_thread_load($thread_id, $account, NULL, FALSE);
+    }
+  }
+  else {
+    $messages = $pms;
+  }
+
+  // Return messages.
+  return $messages;
+  
+}
+
+/**
+ * Get the number of unread private messages of the logged-in user.
+ *
+ * @return
+ *   The unread count.
+ */
+function pm_service_unread_count($uid = '') {
+
+  // User needs to be authenticated to proceed.
+  global $user;
+  if (!user_is_logged_in()) {
+    return services_error(t('This user is not logged in.'), 403);
+  }
+    
+  // If a user id other than the current user's ID is passed,
+  // validate that the authenticated user has the correct 
+  // permissions to read another user's messages.
+  if (is_numeric($uid) && ($uid != $user->uid)) {
+    if (user_access("read all private messages")) {
+      $account = user_load($uid);
+    }
+    else {
+      return services_error(t('This user does not have permissions to use this service.'), 403);
+    }
+  }
+  // Use the current user for the account.
+  else {
+    $account = $user;
+  }
+
+  // Return unread count.
+  return privatemsg_unread_count($account);
+
+}
+
+/**
+ * Send a private message to one or more recipients.
+ *
+ * @param $recipients
+ * String. A comma separated list of usernames for recipients of the message.
+ *
+ * @param $subject
+ * String. A message subject
+ *
+ * @param $body
+ * String. A message body
+ *
+ * @param $thread_id
+ * Integer. A thread ID. pass this parameter if you are sending a message reply.
+ *
+ * @return
+ * Boolean. Return TRUE if sending the message was successful.
+ */
+function pm_service_send($recipients, $subject, $body = '', $thread_id = NULL) {
+
+  $form_state = array();
+  
+  // Make sure the message author is logged in.
+  global $user;
+  $account = user_load($user->uid);
+  
+  if (!user_is_logged_in()) {
+    return services_error(t('Author is not logged in.'), 403);
+  }
+  
+  // Validate at least 1 recipient has been passed in.
+  if ($recipients == '' && !$thread_id) {
+    return services_error(t('There are no recipients, please enter a recipient for the message.'), 400);
+  }
+  elseif (!$thread_id) {
+    // No thread ID - we are sending a new message.
+    // Convert the recipients string to an array of user objects.
+    list($recipients, $invalid) = _privatemsg_parse_userstring($recipients);
+    if (!empty($invalid)) {
+      // At least one of the recipients could not be found.
+      $invalid_usernames = array('@names' => implode(', ', $invalid));
+      return services_error(t('One or more usernames are invalid: @names', $invalid_usernames), 400);
+    }
+    $result = privatemsg_new_thread($recipients, $subject, $body, array('author' => $account));
+    if ($result['success']) {
+      return TRUE;
+    }
+    else {
+      return services_error(implode("\n", $result['messages']['error']), 400);
+    }
+  }
+  else {
+    // There is a thread id so we are sending a reply.
+    $result = privatemsg_reply($thread_id, $body, array('author' => $account));
+    if ($result['success']) {
+      return TRUE;
+    }
+    elseif (!empty($result[0])) {
+      // If $result[0] this means the thread could not be loaded.
+      return services_error($result[0], 404);
+    }
+    else {
+      // Rlse there was some other problem.
+      return services_error(implode("\n", $result['messages']['error']), 400);
+    }
+  }
+  
+}
+
+/**
+ * Get all messages in a thread.
+ *
+ * @param <int> @thread_id
+ *   ID of the thread to be loaded.
+ * @param <int> $offset
+ *   Optional: Message offset from the start of the thread.
+ * @return
+ *   An array of messages in a thread.
+ */
+function pm_service_get_thread($thread_id, $offset = 0) {
+
+  // Return if wrong paramters are passed.
+  if (!$thread_id || !is_numeric($thread_id)) {
+    return services_error(t('Invalid parameters passed'), 400);
+  }
+
+  // Make sure the user is logged in.
+  global $user;
+  $account = user_load($user->uid);
+  if (!user_is_logged_in()) {
+    return services_error(t('The user is not logged in.'), 403);
+  }
+
+  // Return the full thread.
+  return privatemsg_thread_load($thread_id, $account, $offset);
+
+}
\ No newline at end of file
Index: pm_service/pm_service.info
===================================================================
RCS file: pm_service/pm_service.info
diff -N pm_service/pm_service.info
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ pm_service/pm_service.info	3 Dec 2010 01:47:41 -0000
@@ -0,0 +1,7 @@
+; $Id$
+name = Privatemsg Service
+description = Integrates PrivateMSG functionality with Services.
+package = Services - services
+dependencies[] = services
+dependencies[] = privatemsg
+core = 6.x
Index: pm_service/pm_service.module
===================================================================
RCS file: pm_service/pm_service.module
diff -N pm_service/pm_service.module
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ pm_service/pm_service.module	3 Dec 2010 01:47:41 -0000
@@ -0,0 +1,143 @@
+<?php
+
+// $Id$
+
+/**
+ * @file
+ *  Link general privatemsg module functionalities to services module.
+ */
+
+/**
+ * Implementation of hook_perm().
+ */
+function pm_service_perm() {
+  return array(
+    'get private messages from remote',
+    'send private messages from remote',
+  );
+}
+
+/**
+ * Implementation of hook_service().
+ */
+function pm_service_service() {
+  return array(
+  
+    // pm.get
+    array(
+      '#method'           => 'pm.get',
+      '#callback'         => 'pm_service_get',
+      '#access arguments' => array('get private messages from remote'),
+      '#file'             => array('file' => 'inc', 'module' => 'pm_service'),
+      '#args'             => array(
+        array(
+          '#name'         => 'type',
+          '#type'         => 'string',
+          '#optional'     => TRUE,
+          '#description'  => t('The type of messages to retrieve. Can be inbox or sent.')
+        ),
+        array(
+          '#name'         => 'load_full',
+          '#type'         => 'boolean',
+          '#optional'     => TRUE,
+          '#description'  => t('A boolean to indicate whether to load the all messages in a thread or just the message preview.')
+        ),
+        array(
+          '#name'           => 'offset',
+          '#type'           => 'int',
+          '#optional'       => TRUE,
+          '#description'    => t('An offset integer for paging.')
+        ),
+        array(
+          '#name'           => 'limit',
+          '#type'           => 'int',
+          '#optional'       => TRUE,
+          '#description'    => t('A limit integer for paging.')
+        ),
+        array(
+          '#name'           => 'uid',
+          '#type'           => 'int',
+          '#optional'       => TRUE,
+          '#description'    => t('Instead of receiving messages from the authenticated user, receive messages from another user. The currently authenticated user needs to have the "read all private messages" permission to get messages from another user.')
+        ),        
+      ),
+      '#return'           => 'array',
+      '#help'             => t('Get a logged-in user\'s private messages.')
+    ),
+    
+    // pm.getUnreadCount
+    array(
+      '#method'           => 'pm.unreadCount',
+      '#callback'         => 'pm_service_unread_count',
+      '#access arguments' => array('get private messages from remote'),
+      '#file'             => array('file' => 'inc', 'module' => 'pm_service'),
+      '#args'             => array(
+        array(
+          '#name'           => 'uid',
+          '#type'           => 'int',
+          '#optional'       => TRUE,
+          '#description'    => t('Instead of receiving the count of unread messages from the authenticated user, receive the count of unread messages from another user. The currently authenticated user needs to have the "read all private messages" permission to get messages from another user.')
+        ),        
+      ),      
+      '#return'           => 'int',
+      '#help'             => t('Get a users private messages unread count.')
+    ),
+    
+    // pm.send
+    array(
+      '#method'           => 'pm.send',
+      '#callback'         => 'pm_service_send',
+      '#access arguments' => array('send private messages from remote'),
+      '#file'             => array('file' => 'inc', 'module' => 'pm_service'),
+      '#args'             => array(
+        array(
+          '#name'         => 'recipients',
+          '#type'         => 'string',
+          '#description'  => t('A comma separated list of recipients usernames.'),
+        ),
+        array(
+          '#name'         => 'subject',
+          '#type'         => 'string',
+          '#description'  => t('A message subject.'),
+        ),
+        array(
+          '#name'         => 'body',
+          '#type'         => 'string',
+          '#optional'     => TRUE,
+          '#description'  => t('A message body.'),
+        ),
+        array(
+          '#name'         => 'thread_id',
+          '#type'         => 'int',
+          '#optional'     => TRUE,
+          '#description'  => t('A thread ID if this is an already existing message, otherwise leave blank for a new message.'),
+        ),
+      ),
+      '#return'           => 'bool',
+      '#help'             => t('Returns TRUE if the message sending was a success.')
+    ),
+    
+    // pm.getThread
+    array(
+      '#method'           => 'pm.getThread',
+      '#callback'         => 'pm_service_get_thread',
+      '#access arguments' => array('get private messages from remote'),
+      '#file'             => array('file' => 'inc', 'module' => 'pm_service'),
+      '#args'             => array(
+        array(
+          '#name'         => 'thread_id',
+          '#type'         => 'int',
+          '#description'  => t('The ID of the thread that should be retrieved.')
+        ),
+        array(
+          '#name'         => 'offset',
+          '#type'         => 'int',
+          '#optional'     => TRUE,
+          '#description'  => t('Message offset from the start of the thread.')
+        ),
+      ),
+      '#return'           => 'array',
+      '#help'             => t('Get all messages in a thread. User needs to be logged in. Unless the logged-in user has the \'read all private messages\' permission, the user will only be able to get threads that he/she participated in.')
+    ),    
+  );
+}
\ No newline at end of file
Index: pm_service/pm_service.test
===================================================================
RCS file: pm_service/pm_service.test
diff -N pm_service/pm_service.test
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ pm_service/pm_service.test	3 Dec 2010 01:47:41 -0000
@@ -0,0 +1,214 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Privatemsg Services test
+ */
+
+class PrivatemsgServiceTestCase extends DrupalWebTestCase {
+
+  /**
+   * getInfo() returns properties that are displayed in the test selection form.
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'Privatemsg Services functionality.',
+      'description' => 'Ensure that the Privatemsg services integration functions properly.',
+      'group' => 'Privatemsg',
+      'dependencies' => array('services'),
+    );
+  }
+
+  /**
+   * setUp() performs any pre-requisite tasks that need to happen.
+   */
+  public function setUp() {
+    // Must include every single module that PM Service module relies on.
+    parent::setUp('privatemsg', 'services', 'services_keyauth', 'pm_service');
+  }
+
+  /**
+   * Test pm.get service (get messages through services)
+   */
+  public function testPrivatemsgServiceGet() {
+
+    // Setup 2 users.
+    $author    = $this->drupalCreateUser(array('write privatemsg'));
+    $recipient = $this->drupalCreateUser(array('administer services', 'access administration pages', 'get private messages from remote'));
+
+    // Send 1st message to recipient.
+    $subject = 'My First Message';
+    $body = $this->randomName(20);
+    privatemsg_new_thread(array($recipient), $subject, $body, array('author' => $author));
+
+    // Send 2nd message to recipient.
+    $subject = 'My Second Message';
+    $body = $this->randomName(20);
+    privatemsg_new_thread(array($recipient), $subject, $body, array('author' => $author));
+
+    // Have recipient navigate to services browser page for pm.get and call the method.
+    $this->drupalLogin($recipient);
+    $this->drupalGet('admin/build/services/browse/pm.get');
+
+    // Have recipient click on the "Call method" button.
+    $this->drupalPost('admin/build/services/browse/pm.get', array(), t('Call method'));
+
+    // Make sure the that the 1st messages is returned.
+    $this->assertRaw('My First Message', t('Verify that the custom title of thread #1 was found'));
+
+    // Make sure the that the 1st messages is returned.
+    $this->assertRaw('My Second Message', t('Verify that the custom title of thread #2 was found'));
+
+  }
+
+  /**
+   * Test pm.getThread service (get thread through service)
+   */
+  public function testPrivatemsgServiceGetThread() {
+
+    // Setup 2 users
+    $author    = $this->drupalCreateUser(array('write privatemsg'));
+    $recipient = $this->drupalCreateUser(array('administer services', 'access administration pages', 'get private messages from remote'));
+
+    // Send 1 message to recipient
+    $subject = 'My First Message';
+    $body = $this->randomName(20);
+    privatemsg_new_thread(array($recipient), $subject, $body, array('author' => $author));
+
+    // Have recipient navigate to services browser page for pm.get and call the method.
+    $this->drupalLogin($recipient);
+    $this->drupalGet('admin/build/services/browse/pm.getThread');
+
+    // Have recipient click on the "Call method" button.
+    $edit = array(
+      'arg[0]' => db_result(db_query_range("SELECT thread_id FROM {pm_index} ORDER BY mid", 0, 1)),
+    );
+    $this->drupalPost('admin/build/services/browse/pm.getThread', $edit, t('Call method'));
+
+    // Make sure the that 2 messages are returned.
+    $this->assertRaw('My First Message', t('Verify that the custom title of thread #1 was found'));
+
+  }
+
+  /**
+   * Test pm.unreadCount service (determines the number of unread messages)
+   */
+  public function testPrivatemsgServiceUnreadCount() {
+
+    // Setup 2 users
+    $author    = $this->drupalCreateUser(array('write privatemsg'));
+    $recipient = $this->drupalCreateUser(array('administer services', 'access administration pages', 'get private messages from remote'));
+
+    // Send 2 messages to this user.
+    $subject = $this->randomName(20);
+    $subject2 = $this->randomName(20);
+    $body = $this->randomName(100);
+    $body2 = $this->randomName(100);
+    $this->pass("Send 2 messages to the recipient.");
+    privatemsg_new_thread(array($recipient), $subject, $body, array('author' => $author));
+    privatemsg_new_thread(array($recipient), $subject2, $body2, array('author' => $author));
+
+    // Have recipient navigate to services browser page for pm.unreadCount and call the method.
+    $this->drupalLogin($recipient);
+    $this->drupalGet('admin/build/services/browse/pm.unreadCount');
+
+    // Have recipient click on the "Call method" button.
+    $this->drupalPost('admin/build/services/browse/pm.unreadCount', array(), t('Call method'));
+
+    // Make sure the that 2 messages are returned.
+    $this->assertRaw('<h3>Result</h3><code><pre>2</pre></code>', t('Verify that the count of unread messages is "2".'));
+
+  }
+
+  /**
+   * Test pm.send service (create a new thread)
+   */
+  public function testPrivatemsgServiceSend() {
+
+    // Setup 2 users.
+    $author_permissions = array(
+      'write privatemsg',
+      'administer services',
+      'access administration pages',
+      'send private messages from remote',
+    );
+    $author = $this->drupalCreateUser($author_permissions);
+    $recipient = $this->drupalCreateUser(array('read privatemsg'));
+
+    // Have author navigate to services browser page for pm.send and call the method.
+    $this->drupalLogin($author);
+    $this->drupalGet('admin/build/services/browse/pm.send');
+
+    // Author sends a message to recipient through services.
+    $edit = array(
+      'arg[0]' => $recipient->name, // Recipient name.
+      'arg[1]' => 'Message Subject', // Message subject.
+      'arg[2]' => 'Body of this messages', // Message body.
+    );
+    $this->drupalPost('admin/build/services/browse/pm.send', $edit, t('Call method'));
+
+    // Make sure the reply got sent out successfully.
+    $this->assertRaw('<h3>Result</h3><code><pre>1</pre></code>', t('Messages was successfully sent to recipient.'));
+
+  }
+
+  /**
+   * Test pm.send service (reply to a thread through services)
+   */
+  public function testPrivatemsgServiceReply() {
+
+    // Setup 2 users.
+    $author_permissions = array(
+      'write privatemsg',
+      'administer services',
+      'access administration pages',
+      'get private messages from remote',
+    );
+    $author = $this->drupalCreateUser($author_permissions);
+    $recipient_permissions = array(
+      'write privatemsg',
+      'administer services',
+      'access administration pages',
+      'get private messages from remote',
+      'send private messages from remote',
+    );
+    $recipient = $this->drupalCreateUser($recipient_permissions);
+
+    // Author sends a message to recipient.
+    $subject = 'My First Message';
+    $body = $this->randomName(20);
+    privatemsg_new_thread(array($recipient), $subject, $body, array('author' => $author));
+
+    // Recipient logs in and navigates to the services admin page to send a message.
+    $this->drupalLogin($recipient);
+    $this->drupalGet('admin/build/services/browse/pm.send');
+
+    // Recipient replies to the first thread sent by author.
+    $edit = array(
+      'arg[0]' => $author->name,
+      'arg[1]' => 'Recipient replies',
+      'arg[2]' => 'This is my reply',
+      'arg[3]' => db_result(db_query_range("SELECT thread_id FROM {pm_index} ORDER BY mid", 0, 1)),
+    );
+    $this->drupalPost('admin/build/services/browse/pm.send', $edit, t('Call method'));
+
+    // Make sure the reply got sent out successfully.
+    $this->assertRaw('<h3>Result</h3><code><pre>1</pre></code>', t('Reply was successfully sent by recipient.'));
+
+    // Login the author and make sure he received the reply (testing through services call).
+    $this->drupalLogin($author);
+    $this->drupalGet('admin/build/services/browse/pm.getThread');
+
+    // Have recipient click on the "Call method" button.
+    $edit = array(
+      'arg[0]' => 1,
+    );
+    $this->drupalPost('admin/build/services/browse/pm.getThread', $edit, t('Call method'));
+
+    // Make sure the that the reply from the recipient is visible in thread #1.
+    $this->assertRaw('This is my reply', t('Verify that author received the reply from recipient.'));
+
+  }
+
+}
\ No newline at end of file
