'Content subscriptions', 'type' => MENU_LOCAL_TASK, 'page callback' => 'drupal_get_form', 'page arguments' => array('notifications_content_settings_form'), 'access arguments' => array('administer site configuration'), 'file' => 'notifications_content.pages.inc', ); // User pages, will be disabled by default $items['user/%user/notifications/thread'] = array( 'type' => MENU_LOCAL_TASK, 'access callback' => FALSE, 'access arguments' => FALSE, 'title' => 'Thread', 'page callback' => 'notifications_content_page_thread', 'page arguments' => array(1), 'weight' => 10, 'file' => 'notifications_content.pages.inc', ); $items['user/%user/notifications/nodetype'] = array( 'type' => MENU_LOCAL_TASK, 'access callback' => FALSE, 'title' => 'Content type', 'page callback' => 'notifications_content_page_nodetype', 'page arguments' => array(1), 'weight' => 10, 'file' => 'notifications_content.pages.inc', ); $items['user/%user/notifications/author'] = array( 'type' => MENU_LOCAL_TASK, 'access callback' => FALSE, 'title' => t('Author'), 'page callback' => 'notifications_content_page_author', 'pàge arguments' => array(1), 'weight' => 10, 'file' => 'notifications_content.pages.inc', ); return $items; } /** * Menu access callback */ function notifications_content_access($account, $perm) { global $user; return ($account->uid && $account->uid == $user->uid && user_access($perm)) || (user_access('administer notifications') && user_access($perm, $account)); } /** * Implementation of hook_perm() */ function notifications_content_perm() { return array('subscribe to content', 'subscribe to content type', 'subscribe to author', 'skip notifications'); } /** * Implementation of hook_help() */ function notifications_content_help($path, $arg) { if ($path == 'admin/messaging/notifications/content') { $output = '
' . t('Content subscriptions are subscriptions to nodes that will produce notifications when a node is posted or updated or when a comment is posted for that nodes. Notifications will be sent only for published content so if you need to be notified of unpublished content waiting for approval you better use Triggers and Actions or some other module for that.') . '
'; $output .= ''. t('On this page you can set which of the available subscription types are allowed. Alternatively you can select the Set up for each content type option and use the Administer Content types page. These settings will be combined with permissions and other options (See user interface options if enabled) to determine which subscriptions will be finally available for users.', array('@content-type-settings' => url( 'admin/content/types'))) .'
'; return $output; } elseif (array($arg[0], $arg[1], $arg[2], $arg[3]) == array('admin', 'messaging', 'template', 'edit') && ($group = $arg[4])) { switch ($group) { case 'notifications-digest-node-nid': case 'notifications-digest-node-type': $help = '' . t('This is the format for each digest group. A message may consist on one or many of these groups:') . '
'; $help .= ''; $help .= t('Group title') . "\n"; $help .= '- ' . t('Digest line.'). "\n"; $help .= '- ' . t('Digest line.'). "\n"; $help .= '- ...'. "\n"; $help .= t('Group footer') . "\n"; $help .= ''; return $help; } } } /** * Implementation of hook_form_alter(). */ function notifications_content_form_alter(&$form, &$form_state, $form_id) { switch ($form_id) { case 'comment_form': // Load the node which is possibly cached to get the node type $node = node_load($form['nid']['#value']); if (notifications_content_type_enabled($node->type)) { if (notifications_event_enabled('node', 'comment')) { _notifications_content_add_disable_field($form); } // If editing the comment, add values to remember. If no admin fieldset, set the default value to published // because if a user without the 'administer comments' permission is editing, then it must be published. if (!empty($form['cid']['#value'])) { $form['notifications_comment_status'] = array( '#type' => 'value', '#value' => !empty($form['admin']['status']) ? $form['admin']['status']['#default_value'] : COMMENT_PUBLISHED, ); } } break; case 'node_type_form': if (isset($form['identity']['type'])) { // Hack for modules with different weights to add options here if (!isset($form['notifications'])) $form['notifications'] = array(); $form['notifications'] += array( '#type' => 'fieldset', '#title' => t('Subscription settings'), '#collapsible' => TRUE, '#collapsed' => TRUE, ); $form['notifications']['notifications_content_type'] = array( '#type' => 'checkboxes', '#title' => t('Allowed subscription types'), '#default_value' => notifications_content_type_enabled($form['#node_type']->type), '#options' => _notifications_content_type_options(), '#description' => t('Enable different subscription options for this content type.'), '#weight' => -10, ); if (!variable_get('notifications_content_per_type', 0)) { $form['notifications']['notifications_content_type']['#disabled'] = TRUE; $form['notifications']['notifications_content_type']['#description'] .= ' ' . t('To enable these options check the Notifications content settings', array('@notifications-settings' => url('admin/messaging/notifications/content'))) . ''; } } break; default: // Node form. Option to disable notifications if (isset($form['type']) && isset($form['#node']) && $form['type']['#value'] .'_node_form' == $form_id) { $types = notifications_content_types(NULL); $node = $form['#node']; // Do not add if content type disabled, creating and create events disabled, updating and update events disabled if (notifications_content_type_enabled($node->type) && (empty($node->nid) && notifications_event_enabled('node', 'insert') || !empty($node->nid) && notifications_event_enabled('node', 'update'))) { _notifications_content_add_disable_field($form, !empty($node->notifications_content_disable)); } } } } /** * Add disable (skip notifications) field set */ function _notifications_content_add_disable_field(&$form, $default = 0) { if (user_access('skip notifications')) { // Add fieldset without affecting any other elements there $form['notifications']['#type'] = 'fieldset'; $form['notifications']['#title'] = t('Notifications'); $form['notifications']['#collapsible'] = TRUE; $form['notifications']['notifications_content_disable'] = array( '#type' => 'checkbox', '#title' => t('Do not send notifications for this update.'), '#default_value' => $default, ); } } /** * Implementation of hook_theme() */ function notifications_content_theme() { return array( 'notifications_content_type_settings' => array( 'arguments' => array('element' => NULL), 'file' => 'notifications_content.pages.inc', ), ); } /** * 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' => notifications_translate('type:'. $subs->fields['type'] .':name', node_get_types('name', $subs->fields['type']), NULL, 'nodetype'))); } 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': // Some types may be globally disabled (for all content types), mark as such $disabled = !variable_get('notifications_content_per_type', 0); $types['thread'] = array( 'event_type' => 'node', 'title' => t('Thread'), 'access' => 'subscribe to content', 'page callback' => 'notifications_content_page_thread', 'user page' => 'user/%user/notifications/thread', 'fields' => array('nid'), 'description' => t('Subscribe to all changes and comments for a thread.'), 'disabled' => $disabled && !notifications_content_type_enabled(NULL, 'thread'), ); $types['nodetype'] = array( 'event_type' => 'node', 'title' => t('Content type'), 'access' => 'subscribe to content type', 'page callback' => 'notifications_content_page_nodetype', 'user page' => 'user/%user/notifications/nodetype', 'fields' => array('type'), 'description' => t('Subscribe to all content of a given type.'), 'disabled' => $disabled && !notifications_content_type_enabled(NULL, 'nodetype'), ); $types['author'] = array( 'event_type' => 'node', 'title' => t('Author'), 'access' => 'subscribe to author', 'page callback' => 'notifications_content_page_author', 'user page' => 'user/%user/notifications/author', 'fields' => array('author'), 'description' => t('Subscribe to all content submitted by a user.'), 'disabled' => $disabled && !notifications_content_type_enabled(NULL, 'author'), ); // This is a complex type, combining two fields $types['typeauthor'] = array( 'event_type' => 'node', 'title' => t('Content type and Author'), 'access' => 'subscribe to content type and author', //'page callback' => 'notifications_content_page_author', 'fields' => array('author', 'type'), 'description' => t('Subscribe to all content of a given type submitted by a user.'), 'disabled' => $disabled && !notifications_content_type_enabled(NULL, 'typeauthor'), ); return $types; case 'subscription fields': // Information about available fields for subscriptions // - format callback => will be used to convert the value into a displayable output // - value callback => will be used to convert autocomplete name into field value // - autocomplete path => path for autocomplete field // - options callback / arguments => used to produce a drop down field $fields['nid'] = array( 'name' => t('Node'), 'field' => 'nid', 'type' => 'int', 'autocomplete path' => 'notifications/autocomplete/node/title', 'autocomplete callback' => 'notifications_node_nid2autocomplete', 'format callback' => 'notifications_node_nid2title', 'value callback' => 'notifications_node_title2nid', ); $fields['author'] = array( 'name' => t('Author'), 'field' => 'author', 'type' => 'int', 'autocomplete path' => 'user/autocomplete', 'autocomplete callback' => 'notifications_content_author_name_callback', 'format callback' => 'notifications_content_author_name', 'value callback' => 'notifications_content_author_uid', ); $fields['type'] = array( 'name' => t('Node type'), 'field' => 'type', 'type' => 'string', 'options callback' => 'notifications_content_types_callback', ); return $fields; case 'query': // $arg2 is $event array. 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': // Subscription options for a node, args will be account and node return _notifications_content_node_options($arg0, $arg1); case 'user options': // Subscription options for a user account, args will be account and author return _notifications_content_user_options($arg0, $arg1); case 'event load': // $arg0 is event $event = &$arg0; $load = array(); if ($event->type == 'node') { if (!empty($event->params['nid']) && empty($event->objects['node'])) { if ($node = node_load($event->params['nid'])) { $event->objects['node'] = $node; } else { // Node not available anymore, mark event for deletion $event->delete = TRUE; } } if (!empty($event->params['cid']) && empty($event->objects['comment'])) { if ($comment = notifications_content_comment_load($event->params['cid'])) { $event->objects['comment'] = $comment; } else { // Comment not available anymore, mark event for deletion $event->delete = TRUE; } } } break; case 'event objects': return array('node' => t('Node')); 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': // Return an array with some TRUE value if the user has access to this event objects or subscription type $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) && notifications_content_type_enabled($node->type, $object->type); } else { $access = FALSE; } } if (!empty($object->fields['type'])) { $access = $access && notifications_content_type_enabled($object->fields['type'], $object->type); } } // We return an array that will be merged with the ones from other modules return array($access); break; } } /** * Wrapper for author autocomplete callback. * * @param $uid * uid of the author for which to return the name. * @param $subs_type * Optional type of subscription for which to find allowed content types. Defaults to nodetype, can be any subscription type with event-type=node for which notifications_content handles content type settings. */ function notifications_content_author_name_callback($uid, $subs_type = '') { return notifications_content_author_name($uid); } /** * Field name callback, author uid to user name */ function notifications_content_author_name($uid, $html = FALSE) { if ($account = user_load($uid)) { return $html ? theme('username', $account) : check_plain($account->name); } } function notifications_content_author_uid($name, $field = NULL) { if ($account = user_load(array('name' => $name))) { return $account->uid; } elseif ($field) { form_set_error($field, t('User name not found.')); } } /** * Implementation of hook_messaging() */ function notifications_content_messaging($op, $arg1 = NULL, $arg2 = NULL, $arg3 = NULL, $arg4 = NULL) { switch ($op) { case 'message groups': $help = t('The Header and Footer will be taken from Notification events.'); $help_digest = $help . ' ' . t('The Digest line will be used when composing Short digests on which each event will be just a line.'); // 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_digest, 'fallback' => 'notifications-event', ); $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_digest, 'fallback' => 'notifications-event-node', ); $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_digest, 'fallback' => 'notifications-event-node', ); $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_digest, 'fallback' => 'notifications-event-node', ); // Node group digests, will have specific help text in hook_help() $info['notifications-digest-node-nid'] = array( 'module' => 'notifications-content', 'name' => t('Groups digests per node'), 'description' => t('Group of events digested for each node.'), 'fallback' => 'notifications-digest', ); $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.'), 'fallback' => 'notifications-digest', ); return $info; case 'message keys': $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 return array( 'subject' => t('Subject'), 'main' => t('Content'), 'digest' => t('Digest line'), ); case 'notifications-digest-node-nid': case 'notifications-digest-node-type': $parts['title'] = t('Group title'); $parts['closing'] = t('Group footer'); return $parts; } break; case 'messages': $type = $arg1; // Event notifications 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' => array( '[title]', 'Read more [node-url]', ), ); case 'notifications-event-node-insert': return array( 'subject' => t('New [type-name]: [title]'), 'main' => array( '[node-teaser]', t('Read more [node-url]'), ), 'digest' => array( '[title]', 'Read more [node-url]', ), ); 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' => array( t('New Comment on [title] by [comment-author-name] titled [comment-title]'), t('Read more [comment-url]'), ), ); case 'notifications-digest-node-nid': // Define only group title and group footer (closing) // The 'closing' statement is typically a 'read more' link return array( 'title' => t('Updates for [type-name]: [title]'), 'closing' => t('Read more [node-url]'), ); case 'notifications-digest-node-type': return array( 'title' => t('New content of type [type-name] has been submitted'), 'closing' => '