Index: privatemsg_service/README.txt =================================================================== RCS file: privatemsg_service/README.txt diff -N privatemsg_service/README.txt --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ privatemsg_service/README.txt 17 Jan 2011 00:16:09 -0000 @@ -0,0 +1,78 @@ +$Id$ + +Privatemsg Service +------------------ +Integrates Privatemsg functionality with the services module. +http://drupal.org/node/433780 + + + +Requirements +------------ +Drupal 6 +PrivateMSG Module +Services Module (Privatemsg Services module is currently developed against Services 6.x-2.3) + + + +Installation +------------ +Enable Privatemsg Service in the "Site building -> Modules" administration screen. + + + +Services +-------- +See the Services browser (admin/build/services) for details regarding the services provided by this module. Here's a brief overview: + +privatemsg.get +Returns all messages for the current user. Defaults to just loading the message previews. This is the equivalent of loading the inbox of a user + +privatemsg.unreadCount +Returns the number of unread messages for a user. Defaults to the current user. + +privatemsg.send +Sends a new message from the current user. + +privatemsg.reply +Allows the current user to reply to a message. + +privatemsg.getThread +Gets all messages in a thread. + + + +Privatemsg Service Variables +---------------------------- +The Privatemsg Service module currently uses three variables that define what user fields are included in the service reply. By default, Privatemsg often times returns the full user object. Since not all the fields are included (for example the user's email address), these fields can be configured by setting an array of fields. By default, Privatemsg Services returns the user id (uid) and the username. + +privatemsg_service_participant_fields +Enhances the fields returned by the message previews in the privatemsg.get service (by default, Privatemsg only returns the uid). + +privatemsg_service_thread_author_fields +Limits the fields of a thread author in the privatemsg.getThread service (by default, Privatemsg returns the full user object). + +privatemsg_service_message_author_fields +Limits the fields of the message authors (for every message) in the privatemsg.getThread service (by default, Privatemsg returns the full user object for each message author). + + + +Hooks +----- +hook_privatemsg_service_enhance_message($message) +Allows other modules to "enhance" the messages in a thread with additional data. Currently only implemented for the privatemsg.getThread service. +Each module needs to return a key and a value for every message. Here's the default hook implementation: +function hook_privatemsg_service_enhance_message($message) { + $enhancement = array( + 'key' => 'test', + 'value' => time(), + ); + return array($enhancement); +} + + + +Credits +------- +Refactored by Daniel Hanold, (haagendazs) http://drupal.org/user/339733 +Original code by tayzlor, http://drupal.org/user/274980 Index: privatemsg_service/privatemsg_service.inc =================================================================== RCS file: privatemsg_service/privatemsg_service.inc diff -N privatemsg_service/privatemsg_service.inc --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ privatemsg_service/privatemsg_service.inc 17 Jan 2011 00:16:09 -0000 @@ -0,0 +1,301 @@ + 0) && ($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)) { + // If the full thread should be loaded, created an array + // using the thread_id as the index. + if ($load_full) { + $pms[$row->thread_id] = $row; + } + // For message previews, just create a numbered array. + else { + $pms[] = $row; + } + } + + // Load the full thread, if necessary. + if (!empty($pms) && $load_full) { + foreach ($pms as $thread_id => $msg) { + $messages[] = privatemsg_thread_load($thread_id, $account, NULL, FALSE); + } + } + // Otherwise, just return message previews. + else { + // Replace array of participant user IDs (only) + // with more information. + $messages = _privatemsg_service_enhance_participants($pms); + } + + // Return messages. + return $messages; + +} + +/** + * Get the number of unread private messages of the logged-in user. + * + * @return + * The unread count. + */ +function privatemsg_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 + * + * @return + * Boolean. Return TRUE if sending the message was successful. + */ +function privatemsg_service_send($recipients = '', $subject = '', $body = '') { + + $form_state = array(); + + // Make sure the message author is logged in. + 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 == '') { + return services_error(t('There are no recipients, please enter a recipient for the message.'), 400); + } + + // Validate that this messages has a subject. + if ($subject == '') { + return services_error(t('Please specify a subject line.'), 400); + } + + // Load the full user object. + global $user; + $account = user_load($user->uid); + + // Convert the recipients string to an array of user objects. + list($recipients, $invalid) = _privatemsg_parse_userstring($recipients); + + // Problem: At least one of the recipients could not be found. + if (!empty($invalid)) { + $invalid_usernames = array('@names' => implode(', ', $invalid)); + return services_error(t('One or more usernames are invalid: @names', $invalid_usernames), 400); + } + + // Attempt to send a new message. + $result = privatemsg_new_thread($recipients, $subject, $body, array('author' => $account)); + + // Return success status. + if ($result['success']) { + return TRUE; + } + else { + return services_error(implode("\n", $result['messages']['error']), 400); + } + +} + +/** + * Reply to an existing thread. + * + * @param $body + * String. A message body + * + * @param $thread_id + * Integer. A thread ID. + * + * @return + * Boolean. Return TRUE if replying to a thread was successful. + */ +function privatemsg_service_reply($body = '', $thread_id = '') { + + $form_state = array(); + + // Make sure the message author is logged in. + 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 ($body == '') { + return services_error(t('Please specify a body for this reply.'), 400); + } + + // Validate that this messages has a subject. + if ($thread_id == '') { + return services_error(t('Please specify a thread ID for this reply.'), 400); + } + + // Load the full user object. + global $user; + $account = user_load($user->uid); + + // Attempt to send a reply. + $result = privatemsg_reply($thread_id, $body, array('author' => $account)); + + // Return success status. + if ($result['success']) { + return TRUE; + } + // If $result[0] this means the thread could not be loaded. + elseif (!empty($result[0])) { + return services_error($result[0], 404); + } + // Else there was some other problem. + else { + return services_error(implode("\n", $result['messages']['error']), 400); + } + +} + + +/** + * Get all messages in a thread. + * + * @param @thread_id + * ID of the thread to be loaded. + * @param $offset + * Optional: Message offset from the start of the thread. + * @return + * An array of messages in a thread. + */ +function privatemsg_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. + $thread = privatemsg_thread_load($thread_id, $account, $offset); + + // Resort the array keys for messages starting at 0. + $thread['messages'] = array_values($thread['messages']); + + // Simplify the user object of this thread (for security reasons). + $thread = _privatemsg_service_simplify_thread_author($thread); + + // Simplify the user object all messages in this thread (for security reasons). + $thread = _privatemsg_service_simplify_thread_messages_author($thread); + + // Allow other modules to enhance each message in this thread with additional information. + $thread = _privatemsg_service_enhance_thread_messages($thread); + + return $thread; + +} Index: privatemsg_service/privatemsg_service.info =================================================================== RCS file: privatemsg_service/privatemsg_service.info diff -N privatemsg_service/privatemsg_service.info --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ privatemsg_service/privatemsg_service.info 17 Jan 2011 00:16:09 -0000 @@ -0,0 +1,7 @@ +; $Id$ +name = Privatemsg Service +description = Integrates Private messages functionality with Services. +package = Services - services +dependencies[] = services +dependencies[] = privatemsg +core = 6.x Index: privatemsg_service/privatemsg_service.module =================================================================== RCS file: privatemsg_service/privatemsg_service.module diff -N privatemsg_service/privatemsg_service.module --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ privatemsg_service/privatemsg_service.module 17 Jan 2011 00:16:09 -0000 @@ -0,0 +1,322 @@ + 'privatemsg.get', + '#callback' => 'privatemsg_service_get', + '#access arguments' => array('get private messages from remote'), + '#file' => array('file' => 'inc', 'module' => 'privatemsg_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. Do not pass FALSE, just leave it empty for message previews.') + ), + 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.') + ), + + // privatemsg.getUnreadCount + array( + '#method' => 'privatemsg.unreadCount', + '#callback' => 'privatemsg_service_unread_count', + '#access arguments' => array('get private messages from remote'), + '#file' => array('file' => 'inc', 'module' => 'privatemsg_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.') + ), + + // privatemsg.send + array( + '#method' => 'privatemsg.send', + '#callback' => 'privatemsg_service_send', + '#access arguments' => array('send private messages from remote'), + '#file' => array('file' => 'inc', 'module' => 'privatemsg_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.'), + ), + ), + '#return' => 'bool', + '#help' => t('Returns TRUE if the message sending was a success.') + ), + + // privatemsg.reply + array( + '#method' => 'privatemsg.reply', + '#callback' => 'privatemsg_service_reply', + '#access arguments' => array('send private messages from remote'), + '#file' => array('file' => 'inc', 'module' => 'privatemsg_service'), + '#args' => array( + array( + '#name' => 'body', + '#type' => 'string', + '#description' => t('A message body.'), + ), + array( + '#name' => 'thread_id', + '#type' => 'int', + '#description' => t('A thread ID for an existing message.'), + ), + ), + '#return' => 'bool', + '#help' => t('Returns TRUE if the message reply was a success.') + ), + + // privatemsg.getThread + array( + '#method' => 'privatemsg.getThread', + '#callback' => 'privatemsg_service_get_thread', + '#access arguments' => array('get private messages from remote'), + '#file' => array('file' => 'inc', 'module' => 'privatemsg_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.') + ), + ); +} + +/** + * Helper Functions + */ + +/** + * Return an associative array of more information + * than just the user id for each message. + * + * @param $pms + * Array of private messages previews. + * @return + * Array of private message previews with more information than the participant user ids. + */ +function _privatemsg_service_enhance_participants($pms = '') { + + if (!is_array($pms)) { + return; + } + + // Determine which information from the user object should be included. + $fields = variable_get('privatemsg_service_participant_fields', array('uid', 'name')); + + $updated_pms = array(); + + // Update participant details for all threads. + foreach($pms as $message) { + // Create an array of message participants. + $message_participants = explode(',', $message->participants); + $participant_details = array(); + $counter = 0; + foreach($message_participants as $message_participant) { + // Load the full user object for this participant. + $participant_account = user_load($message_participant); + // Cycle through required details for every participant. + foreach ($fields as $field) { + $participant_details[$counter][$field] = $participant_account->$field; + } + $counter++; + } + $message->participants = $participant_details; + $updated_pms[] = $message; + } + + return $updated_pms; + +} + +/** + * Return less information than the full user object + * for the author of this thread. + * + * @param $thread + * Privatemsg thread array. + * @return + * Thread with simplified user object. + */ +function _privatemsg_service_simplify_thread_author($thread = '') { + + if (!is_array($thread)) { + return; + } + + // Determine what fields should be included with the user object. + $fields = variable_get('privatemsg_service_thread_author_fields', array('uid', 'name')); + + $simple_author = new stdClass; + foreach ($fields as $field) { + $simple_author->$field = $thread['user']->$field; + } + + $thread['user'] = $simple_author; + return $thread; + +} + +/** + * Return less information than the full user + * object for the author of each message. + * + * @param $thread + * Privatemsg thread array. + * @return + * Thread with simplified + */ +function _privatemsg_service_simplify_thread_messages_author($thread = '') { + + if (!is_array($thread)) { + return; + } + + // Determine what fields should be included with the user object. + $fields = variable_get('privatemsg_service_message_author_fields', array('uid', 'name')); + + // Simplify the author object of every message in this thread. + $simplified_messages = array(); + foreach ($thread['messages'] as $message) { + $simple_author = new stdClass; + foreach ($fields as $field) { + $simple_author->$field = $message['author']->$field; + } + $message['author'] = $simple_author; + $simplified_messages[] = $message; + } + + $thread['messages'] = $simplified_messages; + return $thread; + +} + +/** + * Allow other modules to enhance the messages + * in a thread with additional content. + * + * @param $thread + * Privatemsg thread array. + * @return + * Thread with potentially additional content in each message + */ +function _privatemsg_service_enhance_thread_messages($thread) { + + if (!is_array($thread)) privatemsg_service + + $enhanced_messages = array(); + + foreach ($thread['messages'] as $message) { + // Allow other modules to add additional information. + $enhancements = module_invoke_all('privatemsg_service_enhance_message', $message); + // Add all additional information to the message. + foreach ($enhancements as $enhancement) { + if (isset($enhancement['key']) && isset($enhancement['value'])) { + $message[$enhancement['key']] = $enhancement['value']; + } + } + $enhanced_messages[] = $message; + } + + $thread['messages'] = $enhanced_messages; + return $thread; + +} + +/** + * Implementation of hook_privatemsg_service_enhance_message($message). + * + * @param $message + * Privatemsg message. + * @return + * Associative array. The key will be used as the array key in the message + * array, the value will be used as the value for that message enhancement. + */ +function hook_privatemsg_service_enhance_message($message) { + + $enhancement = array( + 'key' => 'test', + 'value' => time(), + ); + + return array($enhancement); + +} Index: privatemsg_service/privatemsg_service.test =================================================================== RCS file: privatemsg_service/privatemsg_service.test diff -N privatemsg_service/privatemsg_service.test --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ privatemsg_service/privatemsg_service.test 17 Jan 2011 00:16:09 -0000 @@ -0,0 +1,212 @@ + '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', 'privatemsg_service'); + } + + /** + * Test privatemsg.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 privatemsg.get and call the method. + $this->drupalLogin($recipient); + $this->drupalGet('admin/build/services/browse/privatemsg.get'); + + // Have recipient click on the "Call method" button. + $this->drupalPost('admin/build/services/browse/privatemsg.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 privatemsg.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 privatemsg.get and call the method. + $this->drupalLogin($recipient); + $this->drupalGet('admin/build/services/browse/privatemsg.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/privatemsg.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 privatemsg.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 privatemsg.unreadCount and call the method. + $this->drupalLogin($recipient); + $this->drupalGet('admin/build/services/browse/privatemsg.unreadCount'); + + // Have recipient click on the "Call method" button. + $this->drupalPost('admin/build/services/browse/privatemsg.unreadCount', array(), t('Call method')); + + // Make sure the that 2 messages are returned. + $this->assertRaw('

Result

2
', t('Verify that the count of unread messages is "2".')); + + } + + /** + * Test privatemsg.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 privatemsg.send and call the method. + $this->drupalLogin($author); + $this->drupalGet('admin/build/services/browse/privatemsg.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/privatemsg.send', $edit, t('Call method')); + + // Make sure the reply got sent out successfully. + $this->assertRaw('

Result

1
', t('Messages was successfully sent to recipient.')); + + } + + /** + * Test privatemsg.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/privatemsg.reply'); + + // Recipient replies to the first thread sent by author. + $edit = array( + 'arg[0]' => 'This is my reply body.', + 'arg[1]' => db_result(db_query_range("SELECT thread_id FROM {pm_index} ORDER BY mid", 0, 1)), + ); + $this->drupalPost('admin/build/services/browse/privatemsg.reply', $edit, t('Call method')); + + // Make sure the reply got sent out successfully. + $this->assertRaw('

Result

1
', 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/privatemsg.getThread'); + + // Have recipient click on the "Call method" button. + $edit = array( + 'arg[0]' => 1, + ); + $this->drupalPost('admin/build/services/browse/privatemsg.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.')); + + } + +}