' . t('This is the format for each digest group. A message may consist on one or many of these groups:') . '

'; $help .= '
';
        $help .= '' .t('Group subject') . "\n";
        $help .= '- ' . t('Digest line.'). "\n";
        $help .= '- ' . t('Digest line.'). "\n";
        $help .= '-  ...'. "\n";
        $help .= t('Group footer') . "\n";
        $help .= '
'; return $help; } } } /** * Implementation of hook_menu_() */ function notifications_content_menu($may_cache) { global $user; // we need the user to to build some urls $items = array(); if ($may_cache) { $items[] = array( 'path' => 'admin/messaging/notifications/content', 'title' => t('Content types'), 'type' => MENU_LOCAL_TASK, 'callback' => 'drupal_get_form', 'callback arguments' => array('notifications_content_settings_form'), ); } else { if ($user->uid && arg(0) == 'user' && is_numeric(arg(1)) && arg(2) == 'notifications' && ($user->uid == arg(1) || user_access('administer notifications'))) { $account = ($user->uid == arg(1)) ? $user : user_load(array('uid' => arg(1))); $items[] = array( 'path' => 'user/'. $account->uid .'/notifications/thread', 'type' => MENU_LOCAL_TASK, 'access' => user_access('subscribe to content'), 'title' => t('Thread'), 'callback' => 'notifications_content_page_thread', 'callback arguments' => array($account), 'weight' => 10, ); $items[] = array( 'path' => 'user/'. $account->uid .'/notifications/nodetype', 'type' => MENU_LOCAL_TASK, 'access' => user_access('subscribe to content type'), 'title' => t('Content type'), 'callback' => 'notifications_content_page_nodetype', 'callback arguments' => array($account), 'weight' => 10, ); $items[] = array( 'path' => 'user/'. $account->uid .'/notifications/author', 'type' => MENU_LOCAL_TASK, 'access' => user_access('subscribe to author'), 'title' => t('Author'), 'callback' => 'notifications_content_page_author', 'callback arguments' => array($account), 'weight' => 10, ); } } return $items; } /** * Implementation of hook_perm() */ function notifications_content_perm() { return array('subscribe to content', 'subscribe to content type', 'subscribe to author'); } /** * Admin settings form */ function notifications_content_settings_form() { // Allowed content types settings $form['content'] = array( '#type' => 'fieldset', '#title' => t('Content type subscriptions'), '#weight' => -10, '#collapsible' => TRUE, ); $form['content']['notifications_content_types'] = array( '#type' => 'checkboxes', '#title' => t('Allowed content types'), '#default_value' => notifications_content_types('type'), '#options' => node_get_types('names'), '#description' => t('Select content types which should be allowed for subscriptions to content type.'), '#multiple' => TRUE, ); return system_settings_form($form); } /** * Implementation of hook_notifications() */ function notifications_content_notifications($op, &$arg0, $arg1 = NULL, $arg2 = NULL) { switch ($op) { case 'names': $subs = &$arg0; if ($subs->event_type == 'node') { $subs->type_name = t('Content'); if (!empty($subs->fields['type'])) { $subs->names['type'] = t('Content type: %type', array('%type' => node_get_types('name', $subs->fields['type']))); } if (!empty($subs->fields['author']) && ($author = user_load(array('uid' => $subs->fields['author'])))) { $subs->names['author'] = t('Author: %name', array('%name' => $author->name)); } if (!empty($subs->fields['nid']) && ($node = node_load($subs->fields['nid']))) { $subs->names['thread'] = t('Thread: %title', array('%title' => $node->title)); } } break; case 'subscription types': $types['thread'] = array( 'event_type' => 'node', 'title' => t('Thread'), 'access' => 'subscribe to content', 'page' => 'notifications_content_page_thread', 'fields' => array('nid'), ); $types['nodetype'] = array( 'event_type' => 'node', 'title' => t('Content type'), 'access' => 'subscribe to content type', 'page' => 'notifications_content_page_nodetype', 'fields' => array('type'), ); $types['author'] = array( 'event_type' => 'node', 'title' => t('Author'), 'access' => 'subscribe to author', 'page' => 'notifications_content_page_author', 'fields' => array('author'), ); return $types; case 'subscription fields': // Information about available fields for subscriptions $fields['nid'] = array( 'name' => t('Node'), 'field' => 'nid', 'type' => 'int', ); $fields['author'] = array( 'name' => t('Author'), 'field' => 'author', 'type' => 'int', ); $fields['type'] = array( 'name' => t('Node type'), 'field' => 'author', 'type' => 'string', 'options callback' => 'node_get_types', 'options arguments' => array('names'), ); return $fields; case 'query': // This returns query elements for queue or user queries: // - If $arg0 is 'event', $arg1 is event type and $arg2 is $event object. // - If $arg0 is 'user', $arg1 is object type ('node') and $arg2 is object ($node) if ($arg0 == 'event' && $arg1 == 'node' && ($node = $arg2->node) || $arg0 == 'user' && $arg1 == 'node' && ($node = $arg2)) { $query[]['fields'] = array( 'nid' => $node->nid, 'type' => $node->type, 'author' => $node->uid, ); return $query; } break; case 'node options': return _notifications_content_node_options($arg0, $arg1); case 'event load': // $arg0 is event $event = &$arg0; $load = array(); if ($event->type == 'node') { if (!empty($event->params['nid'])) { $event->objects['node'] = node_load($event->params['nid']); } if (!empty($event->params['cid'])) { $event->objects['comment'] = notifications_content_comment_load($event->params['cid']); } } break; case 'event types': // Node inserts are not grouped by node but all together. The digest will look like: // New content has been submitted // - Story Title1 by Author1 // - Event Title2 by Author2 $types[] = array( 'type' => 'node', 'action' => 'insert', 'name' => t('New content of type [type-name] has been submitted'), 'line' => t('[type-name] [title] by [author-name]'), 'digest' => array('node', 'type'), 'description' => t('Node creation'), ); // These other events are grouped for each node. The digest will look like: // Story: Title of the story // - The story has been updated // - New comment by User: Comment title $types[] = array( 'type' => 'node', 'action' => 'update', 'name' => t('[type-name]: [title]'), 'line' => t('The [type-name] has been updated'), 'digest' => array('node', 'nid'), 'description' => t('Node update'), ); $types[] = array( 'type' => 'node', 'action' => 'comment', 'name' => t('[type-name]: [title]'), 'line' => t('New comment by [comment-author-name]: [comment-title]'), 'digest' => array('node', 'nid'), 'description' => t('Node comment'), ); return $types; case 'access': $type = $arg0; $account = &$arg1; $object = &$arg2; $access = TRUE; // For events we check that node and comment are allowed if ($type == 'event' && $object->type == 'node') { if (!empty($object->objects['node'])) { $access = notifications_content_node_allow($account, $object->objects['node']); } // If no access to node, we don't check more if ($access && !empty($object->objects['comment'])) { $access = $access && notifications_content_comment_allow($account, $object->objects['comment']); } // For node subscriptions we check that user can view the node } elseif ($type == 'subscription') { $access = TRUE; if (!empty($object->fields['nid'])) { if ($node = node_load($object->fields['nid'])) { $access = notifications_content_node_allow($account, $node); } else { $access = FALSE; } } if (!empty($object->fields['type'])) { $access = $access && array_key_exists($object->fields['type'], notifications_content_types()); } } return array($access); break; } } /** * Implementation of hook_messaging() * * @see notifications_messaging() */ function notifications_content_messaging($op, $arg1 = NULL, $arg2 = NULL, $arg3 = NULL, $arg4 = NULL) { switch ($op) { case 'message groups': // These are the main entries for the template configuration page // Each message group will have a number of elements to be put together $help = t('The header and footer will be taken from Notification events'); // Generic notifications event $info['notifications-event-node'] = array( 'module' => 'notifications_content', 'name' => t('Notifications for node events'), 'description' => t('Defaults for all notifications related to node events.'), 'help' => $help, ); $info['notifications-event-node-insert'] = array( 'module' => 'notifications_content', 'name' => t('Notifications for node creation'), 'description' => t('Notifications produced when a new node is created.'), 'help' => $help, ); $info['notifications-event-node-update'] = array( 'module' => 'notifications_content', 'name' => t('Notifications for node updates'), 'description' => t('Notifications produced when a node is updated.'), 'help' => $help, ); $info['notifications-event-node-comment'] = array( 'module' => 'notifications_content', 'name' => t('Notifications for node comments'), 'description' => t('Notifications produced when a comment is posted to a node.'), 'help' => $help, ); // Extra help for group digests $info['notifications-digest-node-nid'] = array( 'module' => 'notifications-content', 'name' => t('Groups digests per node'), 'description' => t('Group of events digested for each node.'), ); $info['notifications-digest-node-type'] = array( 'module' => 'notifications-content', 'name' => t('Groups digests per node type'), 'description' => t('Group of events digested for each node type.'), ); return $info; case 'message keys': // For each message group, there will be a number of elements, each one being part of the // final message. They'll usually have at least subject and content parts. // Some other elements (header and subject) will be taken from default Notifications templates $type = $arg1; switch ($type) { case 'notifications-event-node': case 'notifications-event-node-insert': case 'notifications-event-node-update': case 'notifications-event-node-comment': // Some parts will be re-used from 'notifications-event' group // So we specify only subject and main message $parts['subject'] = t('Subject'); $parts['main'] = t('Content'); $parts['digest'] = t('Digest line'); return $parts; case 'notifications-digest-node-nid': case 'notifications-digest-node-type': $parts['title'] = t('Group title'); $parts['footer'] = t('Group footer'); return $parts; } break; case 'messages': $type = $arg1; // Default texts for node notifications templates switch ($type) { case 'notifications-event-node': case 'notifications-event-node-update': return array( 'subject' => t('Update for [type-name]: [title]'), 'main' => array( '[node-teaser]', t('Read more [node-url]'), ), 'digest' => t('The [type-name] has been updated'), ); case 'notifications-event-node-insert': return array( 'subject' => t('New [type-name]: [title]'), 'main' => array( '[node-teaser]', t('Read more [node-url]'), ), 'digest' => t('[type-name] [title] by [author-name]'), ); case 'notifications-event-node-comment': return array( 'subject' => t('Comment for [type-name]: [title]'), 'main' => array( t('Comment by [comment-author-name]: [comment-title]'), '[comment-body]', t('Read more [comment-url]'), ), 'digest' => t('New comment by [comment-author-name]: [comment-title]'), ); case 'notifications-digest-node-nid': return array( 'title' => t('Updates for [type-name]: [title]'), 'footer' => t('Read more [node-url]'), ); case 'notifications-digest-node-type': return array( 'title' => t('New content of type [type-name] has been submitted'), 'footer' => '', ); } break; case 'tokens': $type = explode('-', $arg1); $tokens = array(); // These are the token groups that will be used for this module's messages if ($type[0] == 'notifications' && $type[2] == 'node') { if ($type[1] == 'event') { $tokens[] = 'node'; if ($type[3] == 'comment') { $tokens[] = 'comment'; } } elseif ($type[1] == 'digest') { if ($type[3] == 'nid') { $tokens[] = 'node'; } elseif ($type[3] == 'type') { $tokens[] = 'node-type'; } } } return $tokens; } } function _notifications_content_node_options($account, $node) { // Default node, field are the first three indexes, but they can be overridden in params // Thread $options[] = array( 'name' => t('This post'), 'type' => 'thread', 'fields' => array('nid' => $node->nid), ); // Content type $options[] = array( 'name' => t('Posts of type %type', array('%type' => node_get_types('name', $node->type))), 'type' => 'nodetype', 'fields' => array('type' => $node->type), ); // Author $options[] = array( 'name' => t('Posts by %name', array('%name' => $node->name)), 'type' => 'author', 'fields' => array('author' => $node->uid), ); return $options; } /** * Implementation of hook_nodeapi() */ function notifications_content_nodeapi(&$node, $op, $arg = 0) { global $user; switch ($op) { case 'update': // If inmediate sending is active, need to reset the node cache so we don't send old versions of the node if ($node->status && variable_get('notifications_send_immediate', 0)) { node_load(0, NULL, TRUE); } // else, fall through case 'insert': if ($node->status) { $event = array( 'module' => 'node', 'oid' => $node->nid, 'type' => 'node', 'action' => $op, 'node' => $node, 'params' => array('nid' => $node->nid), ); notifications_event($event); } break; case 'delete': // Remove all subscriptions for this node notifications_delete_subscriptions(array('event_type' => 'node'), array('nid' => $node->nid)); } } /** * Implementation of hook_comment() */ function notifications_content_comment($comment, $op) { global $user; // $comment can be an object or an array. $comment = (array)$comment; if (!isset($comment['nomail']) && $comment['status'] == 0 && ($op == 'insert' || $op == 'publish')) { $event = array( 'module' => 'node', 'type' => 'node', 'action' => 'comment', // $op, 'node' => node_load($comment['nid']), 'comment' => (object)$comment, 'params' => array('nid' => $comment['nid'], 'cid' => $comment['cid']), ); notifications_event($event); } } /** * Implementation of hook node_type */ function notifications_content_node_type($op, $info) { if ($op == 'delete') { // Remove all subscriptions for this node type notifications_delete_subscriptions(array('event_type' => 'node'), array('type' => $info->type)); } } /** * Load comments with caching * @ TODO See if this may be some use, or drop */ function notifications_content_comment_load($cid) { static $cache; if (!isset($cache[$cid])) { $comment = db_fetch_object(db_query('SELECT c.*, u.uid, u.name AS registered_name, u.data FROM {comments} c INNER JOIN {users} u ON c.uid = u.uid WHERE c.cid = %d', $cid)); $comment = drupal_unpack($comment); $comment->name = $comment->uid ? $comment->registered_name : $comment->name; $cache[$cid] = $comment; } return $cache[$cid]; } /** * Subscriptions page callback: List thread subscriptions */ function notifications_content_page_thread($account = NULL) { global $user; if (is_null($account)) { $account = $user; } // query string for node subscriptions $query = "SELECT s.*, f.value AS nid, n.type AS node_type, n.title FROM {notifications} s INNER JOIN {notifications_fields} f ON s.sid = f.sid LEFT JOIN {node} n ON f.value = CAST(n.nid AS CHAR(255)) WHERE s.uid = %d AND s.type = 'thread' AND s.event_type = 'node' AND s.conditions = 1 AND f.field = 'nid' ORDER BY node_type, n.title"; $results = pager_query($query, NOTIFICATIONS_CONTENT_PAGER, 0, NULL, $account->uid); $subscriptions = $list = array(); $content_types = notifications_content_types('name'); while ($sub = db_fetch_object($results)) { $subscriptions[$sub->nid] = $sub; $list[$sub->nid] = '['. $content_types[$sub->node_type] .'] '. l($sub->title, 'node/'. $sub->nid); } if (!$subscriptions) { $output = t('You are not currently subscribed to any active threads'); } else { $output = t('You are currently subscribed to the following threads:'); $defaults = array('type' => 'thread', 'event_type' => 'node'); $options = array('title' => t('Title')); $output .= drupal_get_form('notifications_user_form', $account, 'thread', $subscriptions, $list, $defaults, $options); $output .= theme('pager', NULL, NOTIFICATIONS_CONTENT_PAGER); } return $output; } /** * User subscriptions to content types */ function notifications_content_page_nodetype($account = NULL) { global $user; if (!isset($account)) { $account = $user; } // List of all subscribed node types $subscriptions = notifications_get_subscriptions(array('type' => 'nodetype', 'uid' => $account->uid), array('type' => NULL), TRUE, 'value'); $types = notifications_content_types('name'); if (!$types) { $output = t('There are no active content types.'); } else { $defaults = array('type' => 'nodetype', 'event_type' => 'node'); $options = array('title' => t('Type')); $output .= drupal_get_form('notifications_user_form', $account, 'nodetype', $subscriptions, $types, $defaults, $options); } return $output; } /** * User subscriptions to content types */ function notifications_content_page_author($account = NULL) { global $user; if (!isset($account)) { $account = $user; } // List of all subscribed node types $subscriptions = notifications_get_subscriptions(array('type' => 'author', 'event_type' => 'node', 'uid' => $account->uid), array('author' => NULL), TRUE, 'value', NOTIFICATIONS_CONTENT_PAGER); if (!$subscriptions) { $output = t('There are no active author subscriptions.'); } else { // Build author list $list = array(); $result = db_query("SELECT uid, name FROM {users} WHERE uid IN (". implode(',', array_keys($subscriptions)) .')'); while ($author = db_fetch_object($result)) { $list[$author->uid] = theme('username', $author); } $defaults = array('type' => 'author', 'event_type' => 'node'); //$output = drupal_get_form('notifications_content_form', $account, $subscriptions, $list, 'author', t('Author'), $defaults); $options = array('title' => t('Author')); $output = drupal_get_form('notifications_user_form', $account, 'author', $subscriptions, $list, $defaults, $options); $output .= theme('pager', NULL, NOTIFICATIONS_CONTENT_PAGER); } return $output; } /** * Generic subscriptions content form */ function notifications_content_form($account, $subscriptions, $list, $field, $field_title, $defaults = array()) { // Complete defaults $defaults += array( 'sid' => 0, 'send_interval' => notifications_user_setting('send_interval', $account), 'send_method' => notifications_user_setting('send_method', $account), 'event_type' => 'node', ); $form['defaults'] = array('#type' => 'value', '#value' => $defaults); $form['account'] = array('#type' => 'value', '#value' => $account); $form['current'] = array('#type' => 'value', '#value' => $subscriptions); $form['subscription_fields'] = array('#type' => 'value', '#value' => array()); $form['subscriptions'] = array( '#tree' => TRUE, '#theme' => 'notifications_form_table', '#header' => array('', $field_title, t('Send interval'), t('Send method')) ); foreach ($list as $key => $title) { $rowdefaults = isset($subscriptions[$key]) ? (array)($subscriptions[$key]) : $defaults; $rowdefaults += $rowdefaults; $form['subscriptions']['checkbox'][$key] = array( '#type' => 'checkbox', '#default_value' => $rowdefaults['sid'], ); $form['subscriptions']['title'][$key] = array( '#value' => $title, ); $form['subscriptions']['send_interval'][$key] = array( '#type' => 'select', '#options' => _notifications_send_intervals(), '#default_value' => $rowdefaults['send_interval'], ); $form['subscriptions']['send_method'][$key] = array( '#type' => 'select', '#options' => _notifications_send_methods(), '#default_value' => $rowdefaults['send_method'], ); // Pass on the fields for processing $form['subscription_fields']['#value'][$key] = array($field => $key); } $form['submit'] = array('#type' => 'submit', '#value' => t('Save')); return $form; } /** * Process generic form submission */ function notifications_content_form_submit($form_id, $form_values) { $account = $form_values['account']; $current = $form_values['current']; $defaults = $form_values['defaults']; $defaults += array('type' => 'node', 'uid' => $account->uid); $fields = $form_values['subscription_fields']; $values = $form_values['subscriptions']; $check = 'checkbox'; foreach ($values[$check] as $index => $value) { $subscription = NULL; if ($value) { // Checked, save only if new or changed if (!isset($current[$index])) { $subscription = $defaults; } elseif ($current[$index]->send_interval != $values['send_interval'][$index] || $current[$index]->send_method != $values['send_method'][$index]) { $subscription = (array)($current[$index]); } // Complete and save if ($subscription) { $subscription['send_interval'] = $values['send_interval'][$index]; $subscription['send_method'] = $values['send_method'][$index]; $subscription['fields'] = $fields[$index]; notifications_save_subscription($subscription); } } elseif (isset($current[$index])) { notifications_delete_subscription($current[$index]->sid); } } drupal_set_message(t('Your subscriptions have been updated.')); } /** * Get content types available for subscriptions * * @param $field * Optional field to return as array value. If none it will return the full objects. */ function notifications_content_types($field = NULL) { // Get list of available node types, all of them will be allowed by default $types = array(); if ($allowed = variable_get('notifications_content_types', array())) { $allowed = array_filter($allowed); foreach (node_get_types() as $type => $info) { if (!empty($allowed[$type])) { $types[$type] = $info; } } } else { $types = node_get_types(); } if ($field) { foreach (array_keys($types) as $type) { $types[$type] = $types[$type]->$field; } } return $types; } /** * Implementation of hook_token_list(). Documents the individual * tokens handled by the module. */ function notifications_content_token_list($type = 'all') { $tokens = array(); if ($type == 'node' || $type == 'all') { $tokens['node']['node-teaser'] = t('The node teaser.'); $tokens['node']['node-body'] = t('The node body.'); $tokens['node']['node-url'] = t('The node view url for read more links.'); $tokens['node']['node-teaser-raw'] = t('Unfiltered node teaser. WARNING - raw user input.'); $tokens['node']['node-body-raw'] = t('Unfiltered node body. WARNING - raw user input.'); } if ($type == 'comment' || $type == 'all') { $tokens['comment']['comment-url'] = t('The comment view url.'); $tokens['comment']['comment-reply-url'] = t('The comment reply url.'); } // Special case for node type if ($type == 'node-type' || $type == 'all') { $tokens['node-type']['type-name'] = t('Node type (user-friendly version)'); } return $tokens; } /** * Implementation of hook_token_values() */ function notifications_content_token_values($type, $object = NULL, $options = array()) { switch ($type) { case 'node': if ($node = $object) { $values['node-teaser'] = $node->teaser ? check_markup($node->teaser, $node->format, FALSE) : ''; $values['node-body'] = $node->body ? check_markup($node->body, $node->format, FALSE) : ''; $values['node-url'] = url('node/'. $node->nid, NULL, NULL, TRUE); $values['node-teaser-raw'] = $node->teaser ? $node->teaser : ''; $values['node-body-raw'] = $node->body ? $node->body : ''; return $values; } break; case 'comment': if ($comment = (object)$object) { $values['comment-url'] = url('node/'. $comment->nid, NULL, 'comment-'. $comment->cid, TRUE); $values['comment-reply-url'] = url('comment/reply/'. $comment->nid .'/'. $comment->cid, NULL, NULL, TRUE); return $values; } break; } } /** * Determine whether the specified user may view the specified node. * * Does a user switching and checks for node permissions. Looking for a better way * but it seems that all the node_access hooks cant be invokes without this. */ function notifications_content_node_allow($account, $node) { static $access; global $user; if (!isset($access[$account->uid][$node->nid])) { // Security! Prevent session saving while user switching session_save_session(FALSE); $current = $user; $user = $account; $access[$account->uid][$node->nid] = node_access('view', $node); $user = $current; session_save_session(TRUE); } return $access[$account->uid][$node->nid]; } /** * Determine whether the specified user may view the specified comment. * * Does a user switching and checks for node permissions. Looking for a better way * but it seems that all the node_access hooks cant be invokes without this. */ function notifications_content_comment_allow($account, $comment) { static $access; $comment = is_object($comment) ? $comment : db_fetch_object(db_query("SELECT * FROM {comments} WHERE cid = %d", $comment)); if (!isset($access[$account->uid][$comment->cid])) { if (($account->uid == $comment->uid || $comment->status == COMMENT_PUBLISHED) && user_access('access comments', $account) || user_access('administer comments', $account)) { $access[$account->uid][$comment->cid] = TRUE; } else { $access[$account->uid][$comment->cid] = FALSE; } } return $access[$account->uid][$comment->cid]; }