? project_issue-theme-changes-table.patch
? project_issue-theme-changes-table_3.patch
? project_issue-theme-changes-table_4.patch
Index: C:\www\diff_display\drupal\sites\all\modules\project_issue\comment.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/project_issue/comment.inc,v
retrieving revision 1.125
diff -u -p -r1.125 comment.inc
--- comment.inc	5 Mar 2008 16:08:24 -0000	1.125
+++ comment.inc	5 Mar 2008 21:38:51 -0000
@@ -129,7 +129,7 @@ function project_issue_comment(&$arg, $o
         // Add a dummy rid if necessary -- prevents incorrect change data.
         $test->rid = $arg->project_info['rid'] ? $arg->project_info['rid'] : 0;
         $comment_changes = project_issue_comment_changes($node, $original_node, $test, project_issue_field_labels('web'));
-        $project_issue_table = _project_issue_comment_table($comment_changes);
+        $project_issue_table = theme('project_issue_comment_table', $comment_changes);
       }
       if ($project_issue_table) {
         $arg->comment = '<div class="project-issue"><div class="summary">'. $project_issue_table .'</div></div>' . $arg->comment;
@@ -218,27 +218,88 @@ function project_issue_comment(&$arg, $o
 }
 
 /**
- * Create a project issue metadata table.
+ * Theme a project issue metadata table.
  *
  * @param $comment_changes
  *  Array containing metadata differences between comments
  *  as returned by project_issue_comment_changes().
+ * @return
+ *  The themed metadata table.
  */
-function _project_issue_comment_table($comment_changes) {
+function theme_project_issue_comment_table($comment_changes) {
   $rows = array();
   foreach ($comment_changes as $field => $change) {
     if (!empty($change['label']) && isset($change['old']) && isset($change['new'])) {
-      $rows[] = array(
-        check_plain($change['label']) .':',
-        check_plain(project_issue_change_summary($field, $change['old'])),
-        '&raquo; '. check_plain(project_issue_change_summary($field, $change['new'])),
-      );
+      $rows[] = theme('project_issue_comment_table_row', $field, $change);
     }
   }
   return theme('table', array(), $rows);
 }
 
 /**
+ * Theme a single row of the project issue metadata changes table.
+ *
+ * @param $field
+ *   The name of the field to theme.
+ * @param $change
+ *   A nested array containing changes to project issue metadata
+ *   for the given issue or comment.
+ * @return
+ *  An array representing one row of the table.
+ *
+ * NOTE:  If you override this theme function, you *must* make sure
+ * that you sanitize all output from this function that is displayed
+ * to the user.  No further escaping/filtering of the data in this
+ * table will take place after this function.  In most cases
+ * this means that you need to run the $change['label'], $change['old'],
+ * and $change['new'] values through either the check_plain() or
+ * filter_xss() function to prevent XSS and other types
+ * of problems due to any malicious input in these
+ * field values.
+ */
+function theme_project_issue_comment_table_row($field, $change) {
+  // Allow anchor, emphasis, and strong tags in metadata tables.
+  $allowed_tags = array('a', 'em', 'strong');
+
+  if (is_array($change['old']) || is_array($change['new'])) {
+    $removed = array();
+    if (is_array($change['old'])){
+      foreach ($change['old'] as $item) {
+        $removed[] = '-'. $item .' ';
+      }
+    }
+    elseif (!empty($change['old'])) {
+      $removed[] = '-'. $change['old'] .' ';
+    }
+
+    $added = array();
+    if (is_array($change['new'])) {
+      foreach ($change['new'] as $item) {
+        $added[] = '+'. $item .' ';
+      }
+    }
+    elseif (!empty($change['new'])) {
+      $added[] = '+'. $change['new'] .' ';
+    }
+
+    $row = array(
+      filter_xss(($change['label']), $allowed_tags) .':',
+      filter_xss(check_plain(implode(', ', $removed)), $allowed_tags),
+      filter_xss((implode(', ', $added)), $allowed_tags),
+    );
+    return $row;
+  }
+  else {
+    $row = array(
+      filter_xss(($change['label']), $allowed_tags) .':',
+      filter_xss((project_issue_change_summary($field, $change['old'])), $allowed_tags),
+      '&raquo; '. filter_xss((project_issue_change_summary($field, $change['new'])), $allowed_tags),
+    );
+    return $row;
+  }
+}
+
+/**
  * Returns the issue metadata table for a comment.
  *
  * @param $node
@@ -261,7 +322,7 @@ function project_issue_comment_view(&$no
     $result = db_query('SELECT p.cid, p.title, p.pid, p.rid, p.component, p.category, p.priority, p.assigned, p.sid FROM {project_issue_comments} p INNER JOIN {comments} c ON p.cid = c.cid WHERE p.nid = %d AND c.status = %d ORDER BY p.timestamp ASC', $node->nid, COMMENT_PUBLISHED);
     while ($followup = db_fetch_object($result)) {
       $followup_changes = project_issue_comment_changes($node, $old, $followup, project_issue_field_labels('web'));
-      $project_issue_tables[$followup->cid] = _project_issue_comment_table($followup_changes);
+      $project_issue_tables[$followup->cid] = theme('project_issue_comment_table', $followup_changes);
       $old = $followup;
     }
   }
@@ -329,18 +390,40 @@ function project_issue_comment_changes($
   // be responsible for storing this metadata in their own tables.  Developers
   // of modules that implement this hook should keep in mind the following:
   // 1.  Implementations of hook_followup_metadata_changes() must take the
-  //     $project_issue_comment_changes array by reference.
+  //     $changes array by reference.
   // 2.  Differences in properties will only be processed later on for
   //     elements of the array which have the 'label', 'old', and 'new' properties
   //     defined.
   // In other words, for each line in the differences table (or field in the email)
   // that is displayed, your hook should add something like the following as a
-  // new element of the $changes:
+  // new element of the $changes array:
+  //    'taxonomy_vid_10' => array(
+  //       'label' => 'Vocabulary 10',
+  //       'old' => 'MySQL, pgSQL, javascript',
+  //       'new' => 'pgSQL, newbie',
+  //     ),
+  //
+  // There are two methods you can use to indicate multiple changes of a field.
+  // The first is that for 'old' and 'new' you pass strings separated by some
+  // character, customarily a comma.  This method is used in
+  // the example above.  When using this method, the default display of the changes
+  // will be to show all old values followed by all new values.  In the example
+  // above, this would be displayed like:
+  // Vocabulary 10:  MySQL, pgSQL, javascript >> pgSQL, newbie
+  //
+  // The other method you can use when constructing 'old' and 'new' is to make
+  // both of these arrays, with each element of the array one change.  If you
+  // use this method, all elements in the 'old' array are typically interpreted
+  // as being removed, and all elements in the 'new' array are typically interpreted
+  // as being added.  An example of this type of structure is as follows:
   //    'taxonomy_vid_10' => array(
   //       'label' => 'Vocabulary 10',
-  //       'old' => 'MySQL,PGSQL',
-  //       'new' => 'PGSQL',
+  //       'old' => array('MySQL', 'javascript'),
+  //       'new' => array('newbie'),
   //     ),
+  // In this situation, the default display of these changes in a project issue
+  // metadata table would be as follows:
+  // Vocabulary 10:  -MySQL, -javascript    +newbie
   foreach (module_implements('followup_metadata_changes') as $module) {
     $function = $module .'_followup_metadata_changes';
     $function($node, $changes, $old_data, $new_data);
Index: C:\www\diff_display\drupal\sites\all\modules\project_issue\issue.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/project_issue/issue.inc,v
retrieving revision 1.299
diff -u -p -r1.299 issue.inc
--- issue.inc	4 Mar 2008 18:43:59 -0000	1.299
+++ issue.inc	5 Mar 2008 23:01:37 -0000
@@ -724,6 +724,7 @@ function project_issue_form($node) {
     '#options' => $projects,
     '#required' => TRUE,
   );
+
   if ($releases) {
     $form['project_info']['rid'] = array(
       '#type' => 'select',
@@ -874,6 +875,34 @@ function project_issue_view($node, $teas
     $rows[] = array(t('Assigned:'), $assigned);
     $rows[] = array(t('Status:'), project_issue_state($node->sid));
 
+    // Allow modules to alter the metadata displayed in the table on the actual
+    // issue node itself (at the very top of the issue).
+    // Modules should accept the $rows parameter by reference and add additional
+    // elements to the $rows array for additional lines in the table.
+    //
+    // NOTE:  Modules implementing this hook are responsible for making sure
+    // that all content printed in additional rows created by said module have
+    // properly escaped and/or filtered text.  In most cases this means passing
+    // any output through either the check_plain() or filter_xss() function.
+    //
+    // This hook is only necessary for display of metadata on the project issue
+    // node.  For display of initial metadata values in the original e-mail
+    // sent after an issue is created, your module should implement
+    // hook_followup_metadata_changes() and watch for the case where
+    // $old_data is empty, which means that $new_data represents
+    // the original values of the metadata fields for the issue.
+    //
+    // Modules implementing this hook should take the following parameters:
+    // @param $node
+    //  The project_issue node object.
+    // @param $rows
+    //  An array of rows in the project issue metadata table that will be displayed.
+    //  This parameter should be passed by reference.
+    foreach (module_implements('project_issue_issue_table_alter') as $module) {
+      $function = $module .'_project_issue_issue_table_alter';
+      $function($node, $rows);
+    }
+
     $node->content['project_issue_summary'] = array(
       '#value' => theme('project_issue_summary', $rows, project_issue_internal_links($node)),
       '#weight' => -5,
@@ -2087,7 +2116,7 @@ function project_issue_change_summary($f
       return t('<none>');
     case 'assigned':
       $user = user_load(array('uid' => $value));
-      return $value === 0 ? variable_get('anonymous', t('Anonymous')) : $user->name;
+      return (int) $value === 0 ? variable_get('anonymous', t('Anonymous')) : $user->name;
     case 'sid':
       return $value ? project_issue_state($value) : t('<none>');
     default:
Index: C:\www\diff_display\drupal\sites\all\modules\project_issue\mail.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/project_issue/mail.inc,v
retrieving revision 1.102
diff -u -p -r1.102 mail.inc
--- mail.inc	5 Mar 2008 14:35:51 -0000	1.102
+++ mail.inc	5 Mar 2008 23:05:54 -0000
@@ -348,20 +348,21 @@ function project_mail_generate_followup_
 
   $comment_changes = project_issue_comment_changes($node, $previous, $entry, $fields);
 
-  // Mail summary (status values).
-  $summary = '';
-  foreach ($comment_changes as $field => $change) {
-    $text = str_pad($change['label']. ':', 14);
-    if (!empty($change['label']) && isset($change['old']) && isset($change['new']) && $field != 'updator' && $field != 'name') {
-      $summary .= "-$text". project_issue_change_summary($field, $change['old']) ."\n";
-      $summary .= "+$text". project_issue_change_summary($field, $change['new']) ."\n";
-    }
-    elseif (!empty($change['label'])) {
-      $summary .= " $text". project_issue_change_summary($field, $node->$field) ."\n";
-    }
-  }
-
-  $summary .= project_mail_format_attachments($entry, $display_files);
+  // Since $node->name will always be the original issue author, and since $node->updator
+  // isn't a property of either $previous or #entry, these two properties
+  // will never show up as being different when project_issue_comment_changes() is called,
+  // and therefore neither of these will ever be elements of the $comment_changes array.
+  // Since we do want them to be printed in issue emails, we just need to add their labels
+  // back into the $comment_changes array here, so that theme_project_issue_mail_summary_field()
+  // will know to print the data for these two fields.
+  $comment_changes['name'] = array(
+    'label' => $fields['name'],
+  );
+  $comment_changes['updator'] = array(
+    'label' => $fields['updator'],
+  );
+  
+  $summary = theme('project_issue_mail_summary', $entry, $node, $comment_changes, $display_files);
 
   // Create main body content
   project_mail_output($content, 1, $entry->format);
@@ -396,6 +397,109 @@ function project_mail_generate_followup_
 }
 
 /**
+ * Themes the display of the issue metadata summary
+ * that is shown at the top of an issue emai.
+ *
+ * @param $entry
+ *  The object representing the current entry.  This will be a node object
+ *  if the current entry is the original issue node; otherwise this will be
+ *  a comment object.
+ * @param $node
+ *  The original issue node object.
+ * @param $changes
+ *  A nested array containing the metadata changes between the original
+ *  issue and the first comment, or two consecutive comments.  This array
+ *  is the output of the project_issue_comment_changes() function.
+ * @param $display_files
+ *   Boolean indicating if file attachments should be displayed.
+ * @return
+ *   A string containing the themed text of the issue metadata table.
+ */
+function theme_project_issue_mail_summary($entry, $node, $changes, $display_files) {
+  // Mail summary (status values).
+  $summary = '';
+  foreach ($changes as $field => $change) {
+    $summary .= theme('project_issue_mail_summary_field', $node, $field, $change);
+  }
+
+  $summary .= project_mail_format_attachments($entry, $display_files);
+  return $summary;
+}
+
+/**
+ * Theme the email output of one project issue metadata field.
+ *
+ * @param $node
+ *   The project issue node object.
+ * @param $field_name
+ *   The name of the field to theme.
+ * @param $change
+ *   A nested array containing changes to project issue metadata
+ *   for the given issue or comment.
+ * @return
+ *  A themed line or lines of text ready for inclusion into the email body.
+ */
+function theme_project_issue_mail_summary_field($node, $field_name, $change) {
+  // We need to run the label name through strip_tags here so that
+  // the spacing isn't messed up if there are HTML tags in $change['label'].
+  $text = str_pad(strip_tags($change['label']). ':', 14);
+  $summary_row = '';
+  if (!empty($change['label']) && isset($change['old']) && isset($change['new']) && $field_name != 'updator' && $field_name != 'name') {
+    if (is_array($change['old']) || is_array($change['new'])) {
+      $removed = array();
+      if (is_array($change['old'])) {
+        foreach ($change['old'] as $item) {
+          $removed[] = '-'. $item .' ';
+        }
+      }
+      elseif (!empty($change['old'])) {
+        $removed[] = '-'. $change['old'];
+      }
+
+      $added = array();
+      if (is_array($change['new'])) {
+        foreach ($change['new'] as $item) {
+          $added[] = '+'. $item .' ';
+        }
+      }
+      elseif (!empty($change['new'])) {
+        $added[] = '+'. $change['new'];
+      }
+
+      $summary_row = " $text". trim(implode(', ', $removed). '  ' .implode(', ', $added)) ."\n";
+    }
+    else {
+      $summary_row .= "-$text". project_issue_change_summary($field_name, $change['old']) ."\n";
+      $summary_row .= "+$text". project_issue_change_summary($field_name, $change['new']) ."\n";
+    }
+  }
+  elseif (!empty($change['label'])) {
+    if (!empty($change['new'])) {
+      // This condition is necessary when building the first email message of an
+      // issue, since in this case $change['old'] should not exist.
+      if (is_array($change['new'])) {
+        $summary_row .= " $text". implode(', ', $change['new']) ."\n";
+      }
+      else {
+        $summary_row .= " $text". project_issue_change_summary($field_name, $change['new']) ."\n";
+      }
+    }
+    else {
+      // This condition is where fields that are stored in the $node object and
+      // which haven't changed but should be printed anyway get processed.
+      // For example, the project, category, etc. are printed in each email
+      // whether or not they have changed.
+      if (isset($node->$field_name)) {
+        $summary_row .= " $text". project_issue_change_summary($field_name, $node->$field_name) ."\n";
+      }
+    }
+  }
+  // HTML tags in the email will make it hard to read, so pass
+  // this output through strip_tags().
+  return strip_tags($summary_row);
+}
+
+/**
  * Formats attachments for issue notification e-mails.
  *
  * @param $entry
