diff --git a/includes/mail.inc b/includes/mail.inc index 0e510fc..3a554ec 100644 --- a/includes/mail.inc +++ b/includes/mail.inc @@ -289,6 +289,7 @@ function project_issue_mail_notify($nid) { // some punctuation) are used. See example in Appendix A.1.2. $from = '"' . mime_header_encode($sender->name) . "\" <$sender->mail>"; + // Send notification to each connected user. while ($recipient = db_fetch_object($result)) { // To save work, only go through a user_load if we need it. if ($check_file_perms || $check_node_access) { @@ -298,6 +299,8 @@ function project_issue_mail_notify($nid) { else { $language = language_default(); } + // Also load in this recipient's project_issue e-mail preferences. + project_issue_load_user_preferences($recipient); $can_access = $check_node_access ? node_access('view', $node, $account) : TRUE; @@ -305,6 +308,7 @@ function project_issue_mail_notify($nid) { $display_files = $check_file_perms ? user_access('view uploaded files', $account) : TRUE; $params['display_files'] = $display_files; + $params['recipient'] = $recipient; drupal_mail('project_issue', 'project_issue_update_notification', $recipient->mail, $language, $params, $from); } } @@ -362,20 +366,42 @@ function _project_issue_mail($key, &$message, $params) { $message['headers']['Message-Id'] = "nid&cid=$cid&host=@$domain>"; $message['headers']['In-Reply-To'] = "nid&host=@$domain>"; $message['headers']['References'] = "nid&host=@$domain> nid&cid=$previous_cid&host=@$domain> nid&revcount=1&host=@$domain>"; - } else { + } + else { // Only original issue in this email. $message['headers']['Message-Id'] = "nid&host=@$domain>"; } project_issue_mail_output($node->title, 0); - $message['subject'] = t('[!short_name] [!category] !title', array('!short_name' => $project->project['uri'], '!category' => $node->project_issue['category'], '!title' => $node->title)); // Create link to related node $links = t('Issue status update for !link', array('!link' => "\n". url("node/$node->nid", array('absolute' => TRUE)))) ."\n"; $links .= t('Post a follow up: !link', array('!link' => "\n". url("comment/reply/$node->nid", array('fragment' => 'comment-form', 'absolute' => TRUE)))) ."\n"; - $message['body'][] = $links; - $message['body'][] = project_issue_mail_generate_followup_mail_body($node, $history, $params['display_files']); + $body = project_issue_mail_generate_followup_mail_body($node, $history, $params['display_files'], $params['recipient']); + + $preferences = $params['recipient']->project_issue; + // Construct the appropriate subject based on the user's preferences. + $subject_tokens = array( + '!short_name' => $project->project['uri'], + '!category' => $node->project_issue['category'], + '!title' => $node->title, + ); + if (!empty($preferences['mail_subject_project']) && !empty($preferences['mail_subject_category'])) { + $subject = t('[!short_name] [!category] !title', $subjet_tokens); + } + elseif(!empty($preferences['mail_subject_project']) && empty($preferences['mail_subject_category'])) { + $subject = t('[!short_name] !title', $subjet_tokens); + } + elseif(empty($preferences['mail_subject_project']) && !empty($preferences['mail_subject_category'])) { + $subject = t('[!category] !title', $subjet_tokens); + } + else { + $subject = t('!title', $subjet_tokens); + } + $message['subject'] = $subject; + $message['body']['links'] = $links; + $message['body']['body'] = $body; break; case 'project_issue_critical_summary': @@ -416,23 +442,20 @@ function _project_issue_mail($key, &$message, $params) { * An array containing the history of issue followups. * @param $display_files * Boolean indicating if file attachments should be displayed. + * @param $recipient + * User object indicating recipient's notification preferences. * @return * A string of the email body. */ -function project_issue_mail_generate_followup_mail_body($node, $history, $display_files) { +function project_issue_mail_generate_followup_mail_body($node, $history, $display_files, $recipient = NULL) { global $user; - static $output_with_files = NULL, $output_without_files = NULL; + static $cache = array(); + + $mail_body = isset($recipient->project_issue['mail_body']) ? $recipient->project_issue['mail_body'] : PROJECT_ISSUE_MAIL_BODY_FULL_HISTORY; // Return cached output if available. - if ($display_files) { - if (isset($output_with_files)) { - return $output_with_files; - } - } - else { - if (isset($output_without_files)) { - return $output_without_files; - } + if (isset($cache[$display_files][$mail_body])) { + return $cache[$display_files][$mail_body]; } // Get most recent update. @@ -483,30 +506,26 @@ function project_issue_mail_generate_followup_mail_body($node, $history, $displa project_issue_mail_output($content, 1, $entry->format); $body = "$content\n$entry->name\n"; - $hr = str_repeat('-', 72); - - if (count($history)) { + // Append complete follow-up history if recient prefers that. + if ($mail_body == PROJECT_ISSUE_MAIL_BODY_FULL_HISTORY) { + $hr = str_repeat('-', 72); - $body .= "\n\n"; - $body .= t('Original issue:') ."\n"; - $body .= project_issue_mail_format_entry(array_shift($history), $display_files, TRUE); if (count($history)) { - $body .= "\n". t('Previous comments (!count):', array('!count' => count($history))) ."\n"; - foreach ($history as $entry) { - $body .= project_issue_mail_format_entry($entry, $display_files); + $body .= "\n\n"; + $body .= t('Original issue:') ."\n"; + $body .= project_issue_mail_format_entry(array_shift($history), $display_files, TRUE); + if (count($history)) { + $body .= "\n". t('Previous comments (!count):', array('!count' => count($history))) ."\n"; + foreach ($history as $entry) { + $body .= project_issue_mail_format_entry($entry, $display_files); + } } } } - $output = "$summary\n$body"; // Set cached output. - if ($display_files) { - $output_with_files = $output; - } - else { - $output_without_files = $output; - } + $cache[$display_files][$mail_body] = $output; return $output; } diff --git a/includes/user_preferences.inc b/includes/user_preferences.inc new file mode 100644 index 0000000..b523831 --- /dev/null +++ b/includes/user_preferences.inc @@ -0,0 +1,69 @@ + 'value', + '#value' => $account, + ); + + $form['project_issue'] = array( + '#type' => 'fieldset', + '#title' => t('Issue subscriptions'), + '#collapsible' => TRUE, + ); + + $options = array( + 'mail_subject_project' => t('Project name'), + 'mail_subject_category' => t('Issue category'), + ); + $issue_preferences = array_filter($account->project_issue); + $defaults = array_keys(array_intersect_key($options, $issue_preferences)); + + $form['project_issue']['project_issue_mail_subject'] = array( + '#type' => 'checkboxes', + '#title' => t('E-mail subject includes'), + '#options' => $options, + '#default_value' => $defaults, + ); + + $form['project_issue']['project_issue_mail_body'] = array( + '#type' => 'radios', + '#title' => t('E-mail body includes'), + '#options' => array( + PROJECT_ISSUE_MAIL_BODY_FULL_HISTORY => t('Full issue history'), + PROJECT_ISSUE_MAIL_BODY_NEW_CONTENT => t('Only new content'), + ), + '#default_value' => $account->project_issue['mail_body'], + ); + + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Save preferences'), + '#weight' => 100, + ); + + return $form; +} + +/** + * Submit handler for the user/N/notifications form. + */ +function project_issue_user_preferences_form_submit($form, &$form_state) { + $account = $form_state['values']['account']; + $account->project_issue = array( + 'mail_body' => $form_state['values']['project_issue_mail_body'], + 'mail_subject_project' => !empty($form_state['values']['project_issue_mail_subject']['mail_subject_project']), + 'mail_subject_category' => !empty($form_state['values']['project_issue_mail_subject']['mail_subject_category']), + ); + project_issue_save_user_preferences($account); + drupal_set_message(t('Preferences saved.')); +} diff --git a/project_issue.install b/project_issue.install index 1c24aff..78eff23 100644 --- a/project_issue.install +++ b/project_issue.install @@ -293,7 +293,7 @@ function project_issue_schema() { 'default' => 0, ), 'uid' => array( - 'description' => 'The {users}.uid for this subscriber.', + 'description' => 'Foreign key: The {users}.uid for this subscriber.', 'type' => 'int', 'unsigned' => 1, 'not null' => TRUE, @@ -381,6 +381,41 @@ function project_issue_schema() { ), 'primary key' => array('nid', 'uid'), ); + + $schema['project_issue_user_preference'] = array( + 'description' => 'Global per-user preferences for issues.', + 'fields' => array( + 'uid' => array( + 'description' => 'Foreign key: The {users}.uid for the user.', + 'type' => 'int', + 'unsigned' => 1, + 'not null' => TRUE, + 'default' => 0, + ), + 'mail_body' => array( + 'description' => 'User preference for the body of notification e-mails (e.g. full history vs. only new content).', + 'type' => 'int', + 'size' => 'tiny', + 'unsigned' => 1, + 'not null' => FALSE, + ), + 'mail_subject_project' => array( + 'description' => 'Defines if the subject of notification e-mails includes the project name.', + 'type' => 'int', + 'size' => 'tiny', + 'unsigned' => 1, + 'not null' => FALSE, + ), + 'mail_subject_category' => array( + 'description' => 'Defines if the subject of notification e-mails includes the issue category (bug, feature, etc).', + 'type' => 'int', + 'size' => 'tiny', + 'unsigned' => 1, + 'not null' => FALSE, + ), + ), + 'primary key' => array('uid'), + ); return $schema; } @@ -626,3 +661,47 @@ function project_issue_update_6006() { return $ret; } + +/** + * Add the {project_issue_user_preference} table. + */ +function project_issue_update_6007() { + $ret = array(); + + $table = array( + 'description' => 'User preferences for issues.', + 'fields' => array( + 'uid' => array( + 'description' => 'The {users}.uid for this user.', + 'type' => 'int', + 'unsigned' => 1, + 'not null' => TRUE, + 'default' => 0, + ), + 'mail_body' => array( + 'description' => 'User preference for the body of notification e-mails (e.g. full history vs. only new content).', + 'type' => 'int', + 'size' => 'tiny', + 'unsigned' => 1, + 'not null' => FALSE, + ), + 'mail_subject_project' => array( + 'description' => 'Defines if the subject of notification e-mails includes the project name.', + 'type' => 'int', + 'size' => 'tiny', + 'unsigned' => 1, + 'not null' => FALSE, + ), + 'mail_subject_category' => array( + 'description' => 'Defines if the subject of notification e-mails includes the issue category (bug, feature, etc).', + 'type' => 'int', + 'size' => 'tiny', + 'unsigned' => 1, + 'not null' => FALSE, + ), + ), + 'primary key' => array('uid'), + ); + db_create_table($ret, 'project_issue_user_preference', $table); + return $ret; +} diff --git a/project_issue.module b/project_issue.module index 59d9380..c754844 100644 --- a/project_issue.module +++ b/project_issue.module @@ -9,6 +9,11 @@ define('PROJECT_ISSUE_AUTO_CLOSE_DAYS', 14); define('PROJECT_ISSUE_STATE_FIXED', 2); /// Project issue state = closed. define('PROJECT_ISSUE_STATE_CLOSED', 7); +// Email notification e-mail contains the full issue history. +define('PROJECT_ISSUE_MAIL_BODY_FULL_HISTORY', 0); +// Email notification e-mail only contains new content. +define('PROJECT_ISSUE_MAIL_BODY_NEW_CONTENT', 1); + /** * Implementation of hook_init(). @@ -193,6 +198,17 @@ function project_issue_menu() { 'type' => MENU_CALLBACK, ); + // User tab for issue notifications. + $items['user/%user/notifications'] = array( + 'title' => 'Notifications', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('project_issue_user_preferences_form', 1), + 'access callback' => 'user_edit_access', + 'access arguments' => array(1), + 'type' => MENU_LOCAL_TASK, + 'file' => 'includes/user_preferences.inc', + ); + // Autocomplete paths. // Autocomplete a comma-separated list of projects that have issues enabled. @@ -478,6 +494,63 @@ function project_issue_node_info() { ); } +/** + * Load per-user preferences related to project issues. + * + * @param stdClass $account + * Reference to an object that at least holds the UID of the user to load + * preferences for. The preferences are populated into a 'project_issue' + * array inside the object. + */ +function project_issue_load_user_preferences(&$account) { + if ($account->uid) { + $row = db_fetch_object(db_query('SELECT mail_body, mail_subject_project, mail_subject_category FROM {project_issue_user_preference} WHERE uid = %d', $account->uid)); + $account->project_issue = array( + 'mail_body' => isset($row->mail_body) ? $row->mail_body : PROJECT_ISSUE_MAIL_BODY_FULL_HISTORY, + 'mail_subject_project' => isset($row->mail_subject_project) ? $row->mail_subject_project : TRUE, + 'mail_subject_category' => isset($row->mail_subject_category) ? $row->mail_subject_category : TRUE, + ); + } + else { + $account->project_issue = array( + 'mail_body' => PROJECT_ISSUE_MAIL_BODY_FULL_HISTORY, + 'mail_subject_project' => TRUE, + 'mail_subject_category' => TRUE, + ); + } +} + +/** + * Save per-user preferences related to project issues. + * + * @param stdClass $account + * An object that contains at least the 'uid' of the {users}.uid for the + * account to udpate and a 'project_issue' array with the following + * elements: 'mail_body', 'mail_subject_project', 'mail_subject_category'. + */ +function project_issue_save_user_preferences($account) { + db_query("UPDATE {project_issue_user_preference} SET mail_body = %d, mail_subject_project = %d, mail_subject_category = %d WHERE uid = %d", $account->project_issue['mail_body'], $account->project_issue['mail_subject_project'], $account->project_issue['mail_subject_category'], $account->uid); + if (!db_affected_rows()) { + db_query("INSERT INTO {project_issue_user_preference} (uid, mail_body, mail_subject_project, mail_subject_category) VALUES (%d, %d, %d, %d)", $account->uid, $account->project_issue['mail_body'], $account->project_issue['mail_subject_project'], $account->project_issue['mail_subject_category']); + } +} + +/** + * Implement hook_user(). + * + * This manages the rows in {project_issue_user_preference} for per-user + * preferences related to the issue queues (for now, the issue notification + * e-mails). + */ +function project_issue_user($type, &$edit, &$account, $category = NULL) { + if ($type == 'delete') { + db_query('DELETE FROM {project_issue_user_preference} WHERE uid = %d', $account->uid); + } + elseif ($type == 'insert') { + db_query('INSERT INTO {project_issue_user_preference} (uid, mail_body, mail_subject_project, mail_subject_category) VALUES (%d, %d, %d, %d)', $account->uid, PROJECT_ISSUE_MAIL_BODY_FULL_HISTORY, 1, 1); + } +} + function project_issue_perm() { $perms = array( 'create project issues',