From 5965e000221be2b5596bc26d0346c3fe3a473e77 Mon Sep 17 00:00:00 2001
From: Eric Davis <edavis@littlestreamsoftware.com>
Date: Mon, 17 Nov 2008 13:58:00 -0800
Subject: [PATCH] Remove the body of incoming emails after specific delimiters.

A User can now enter a set of email delimiters that will be parsed out of the
emails.  These delimiters are converted to Ruby Regular Expression objects and
will match across multiple line, ignoring case.
---
 app/models/mail_handler.rb                         |   26 +++++-
 app/views/settings/_mail_handler.rhtml             |    9 ++
 config/locales/en.yml                              |    2 +
 config/settings.yml                                |    2 +
 .../mail_handler/ticket_on_given_project.eml       |    4 +
 test/fixtures/mail_handler/ticket_reply.eml        |    5 +
 test/unit/mail_handler_test.rb                     |   98 ++++++++++++++++++++
 7 files changed, 142 insertions(+), 4 deletions(-)

diff --git a/app/models/mail_handler.rb b/app/models/mail_handler.rb
index cda556f..70fc212 100644
--- a/app/models/mail_handler.rb
+++ b/app/models/mail_handler.rb
@@ -81,7 +81,7 @@ class MailHandler < ActionMailer::Base
   MESSAGE_ID_RE = %r{^<redmine\.([a-z0-9_]+)\-(\d+)\.\d+@}
   ISSUE_REPLY_SUBJECT_RE = %r{\[[^\]]*#(\d+)\]}
   MESSAGE_REPLY_SUBJECT_RE = %r{\[[^\]]*msg(\d+)\]}
-  
+
   def dispatch
     headers = [email.in_reply_to, email.references].flatten.compact
     if headers.detect {|h| h.to_s =~ MESSAGE_ID_RE}
@@ -131,7 +131,6 @@ class MailHandler < ActionMailer::Base
     if issue.subject.blank?
       issue.subject = '(no subject)'
     end
-    issue.description = plain_text_body
     # custom fields
     issue.custom_field_values = issue.available_custom_fields.inject({}) do |h, c|
       if value = get_keyword(c.name, :override => true)
@@ -139,6 +138,7 @@ class MailHandler < ActionMailer::Base
       end
       h
     end
+    issue.description = cleanup_body(plain_text_body)
     # add To and Cc as watchers before saving so the watchers can reply to Redmine
     add_watchers(issue)
     issue.save!
@@ -167,7 +167,7 @@ class MailHandler < ActionMailer::Base
     raise UnauthorizedAction unless status.nil? || user.allowed_to?(:edit_issues, issue.project)
 
     # add the note
-    journal = issue.init_journal(user, plain_text_body)
+    journal = issue.init_journal(user, cleanup_body(plain_text_body))
     add_attachments(issue)
     # check workflow
     if status && issue.new_statuses_allowed_to(user).include?(status)
@@ -242,7 +242,7 @@ class MailHandler < ActionMailer::Base
       end
     end
   end
-  
+
   # Returns the text/plain part of the email
   # If not found (eg. HTML-only email), returns the body with tags removed
   def plain_text_body
@@ -287,4 +287,22 @@ class MailHandler < ActionMailer::Base
       user.save ? user : nil
     end
   end
+
+  private
+  
+  # Removes the email body of text after the truncation configurations.
+  def cleanup_body(body)
+    if Setting.emails_truncate.present?
+      truncate_tokens = Setting.emails_truncate.split("\n")
+
+      truncate_tokens.each do |token|
+        next if token.blank?
+        
+        regex = Regexp.new(token + '.*', Regexp::IGNORECASE | Regexp::MULTILINE)
+        body.gsub!(regex, '')
+      end
+    end
+    
+    return body.chomp
+  end
 end
diff --git a/app/views/settings/_mail_handler.rhtml b/app/views/settings/_mail_handler.rhtml
index 8d83358..8541281 100644
--- a/app/views/settings/_mail_handler.rhtml
+++ b/app/views/settings/_mail_handler.rhtml
@@ -1,6 +1,14 @@
 <% form_tag({:action => 'edit', :tab => 'mail_handler'}) do %>
 
 <div class="box tabular settings">
+  <p>
+    <label><%= l(:setting_emails_truncate) %></label>
+    <%= text_area_tag 'settings[emails_truncate]', Setting.emails_truncate, :rows => 5, :cols => 80 %>
+    <br /><em><%= l(:text_line_separated) %></em>
+  </p>
+</div>
+
+<div class="box tabular settings">
 <p><label><%= l(:setting_mail_handler_api_enabled) %></label>
 <%= hidden_field_tag 'settings[mail_handler_api_enabled]', 0 %>
 <%= check_box_tag 'settings[mail_handler_api_enabled]', 1, Setting.mail_handler_api_enabled?,
@@ -16,4 +24,5 @@
 </div>
 
 <%= submit_tag l(:button_save) %>
+
 <% end %>
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 89fee2e..a9473f6 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -323,6 +323,7 @@ en:
   setting_issue_done_ratio_issue_field: Use the issue field
   setting_issue_done_ratio_issue_status: Use the issue status
   setting_start_of_week: Start calendars on
+  setting_emails_truncate: "Truncate emails after the following strings"
   
   permission_add_project: Create project
   permission_edit_project: Edit project
@@ -801,6 +802,7 @@ en:
   text_tracker_no_workflow: No workflow defined for this tracker
   text_unallowed_characters: Unallowed characters
   text_comma_separated: Multiple values allowed (comma separated).
+  text_line_separated: Multiple values allowed (line separated).
   text_issues_ref_in_commit_messages: Referencing and fixing issues in commit messages
   text_issue_added: "Issue {{id}} has been reported by {{author}}."
   text_issue_updated: "Issue {{id}} has been updated by {{author}}."
diff --git a/config/settings.yml b/config/settings.yml
index 1cbcba5..af6946e 100644
--- a/config/settings.yml
+++ b/config/settings.yml
@@ -174,3 +174,5 @@ gravatar_default:
   default: ''
 start_of_week:
   default: ''
+emails_truncate:
+  default: ''
diff --git a/test/fixtures/mail_handler/ticket_on_given_project.eml b/test/fixtures/mail_handler/ticket_on_given_project.eml
index 5dbd0dc..f965ac9 100644
--- a/test/fixtures/mail_handler/ticket_on_given_project.eml
+++ b/test/fixtures/mail_handler/ticket_on_given_project.eml
@@ -30,6 +30,10 @@ sed, mauris. Pellentesque habitant morbi tristique senectus et netus et
 malesuada fames ac turpis egestas. Quisque sit amet libero. In hac habitasse 
 platea dictumst.
 
+---
+
+This paragraph is after the delimiter so it shouldn't appear.
+
 Nulla et nunc. Duis pede. Donec et ipsum. Nam ut dui tincidunt neque 
 sollicitudin iaculis. Duis vitae dolor. Vestibulum eget massa. Sed lorem. 
 Nullam volutpat cursus erat. Cras felis dolor, lacinia quis, rutrum et, 
diff --git a/test/fixtures/mail_handler/ticket_reply.eml b/test/fixtures/mail_handler/ticket_reply.eml
index 74724cc..72eaceb 100644
--- a/test/fixtures/mail_handler/ticket_reply.eml
+++ b/test/fixtures/mail_handler/ticket_reply.eml
@@ -25,6 +25,11 @@ Content-Type: text/plain;
 Content-Transfer-Encoding: quoted-printable
 
 This is reply
+
+---
+
+This is my sig.
+
 ------=_NextPart_000_0067_01C8D3CE.711F9CC0
 Content-Type: text/html;
 	charset="utf-8"
diff --git a/test/unit/mail_handler_test.rb b/test/unit/mail_handler_test.rb
index 79cddd9..f2727d1 100644
--- a/test/unit/mail_handler_test.rb
+++ b/test/unit/mail_handler_test.rb
@@ -204,7 +204,83 @@ class MailHandlerTest < ActiveSupport::TestCase
     assert issue.is_a?(Issue)
     assert_equal 1, ActionMailer::Base.deliveries.size
   end
+
+  context "truncate emails based on the Setting" do
+    context "with no setting" do
+      setup do
+        Setting.emails_truncate = ''
+      end
+
+      should "add the entire email into the issue" do
+        issue = submit_email('ticket_on_given_project.eml')
+
+        assert_issue_created(issue)
+        assert issue.description.include?('---')
+        assert issue.description.include?('This paragraph is after the delimiter')
+      end
+    end
+
+    context "with a single string" do
+      setup do
+        Setting.emails_truncate = '---'
+      end
+
+      should "truncate the email at the delimiter for the issue" do
+        issue = submit_email('ticket_on_given_project.eml')
+
+        assert_issue_created(issue)
+        assert !issue.description.include?('---')
+        assert !issue.description.include?('This paragraph is after the delimiter')
+        assert issue.description.include?('platea dictumst.')
+      end
+
+    end
+
+    context "with multiple strings" do
+      setup do
+        Setting.emails_truncate = "---\nBREAK\nid"
+      end
+
+      should "truncate the email at the first delimiter found (id)" do
+        issue = submit_email('ticket_on_given_project.eml')
+
+        assert_issue_created(issue)
+        assert !issue.description.include?('---')
+        assert !issue.description.include?('This paragraph is after the delimiter')
+        assert !issue.description.include?('platea dictumst.')
+        assert issue.description.include?('blandit eleifend augue. Nulla facilisi. Duis ')
+      end
+
+    end
+
+    
+  end
   
+  def test_add_issue_with_strip_delimiter_should_not_have_text_after_delimiter
+    with_settings :emails_truncate => '---' do
+      # This email contains: 'Project: onlinestore'
+      issue = submit_email('ticket_on_given_project.eml')
+      assert issue.is_a?(Issue)
+      assert !issue.new_record?
+      issue.reload
+      assert_equal 'New ticket on a given project', issue.subject
+      assert !issue.description.include?("This paragraph is after the delimiter so it shouldn't appear."), "Delimited text was not stripped"
+      assert !issue.description.include?("---"), "Delimiter was not stripped"
+    end
+  end
+
+  def test_add_issue_with_strip_delimiter_should_not_have_delimiter
+    with_settings :emails_truncate => '---' do
+      # This email contains: 'Project: onlinestore'
+      issue = submit_email('ticket_on_given_project.eml')
+      assert issue.is_a?(Issue)
+      assert !issue.new_record?
+      issue.reload
+      assert_equal 'New ticket on a given project', issue.subject
+      assert !issue.description.include?("---"), "Delimiter was not stripped"
+    end
+  end
+
   def test_add_issue_note
     journal = submit_email('ticket_reply.eml')
     assert journal.is_a?(Journal)
@@ -259,10 +335,32 @@ class MailHandlerTest < ActiveSupport::TestCase
     assert_equal 'This is a html-only email.', issue.description
   end
 
+  def test_add_issue_note_with_strip_delimiter_should_not_have_text_after_delimiter
+    with_settings :emails_truncate => '---' do
+      journal = submit_email('ticket_reply.eml')
+      assert journal.is_a?(Journal)
+      assert !journal.notes.include?("This is my sig"), "Delimited text was not stripped"
+    end
+  end
+  
+  def test_add_issue_note_with_strip_delimiter_should_not_have_delimiter
+    with_settings :emails_truncate => '---' do
+      journal = submit_email('ticket_reply.eml')
+      assert journal.is_a?(Journal)
+      assert !journal.notes.include?("---"), "Delimiter was not stripped"
+    end
+  end
+  
   private
   
   def submit_email(filename, options={})
     raw = IO.read(File.join(FIXTURES_PATH, filename))
     MailHandler.receive(raw, options)
   end
+
+  def assert_issue_created(issue)
+    assert issue.is_a?(Issue)
+    assert !issue.new_record?
+    issue.reload
+  end
 end
-- 
1.6.5


