##// END OF EJS Templates
Mail handler: check workflow for status set/change....
Jean-Philippe Lang -
r2072:2fa8030afc2f
parent child
Show More
@@ -1,153 +1,160
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class MailHandler < ActionMailer::Base
18 class MailHandler < ActionMailer::Base
19
19
20 class UnauthorizedAction < StandardError; end
20 class UnauthorizedAction < StandardError; end
21 class MissingInformation < StandardError; end
21 class MissingInformation < StandardError; end
22
22
23 attr_reader :email, :user
23 attr_reader :email, :user
24
24
25 def self.receive(email, options={})
25 def self.receive(email, options={})
26 @@handler_options = options.dup
26 @@handler_options = options.dup
27
27
28 @@handler_options[:issue] ||= {}
28 @@handler_options[:issue] ||= {}
29
29
30 @@handler_options[:allow_override] = @@handler_options[:allow_override].split(',').collect(&:strip) if @@handler_options[:allow_override].is_a?(String)
30 @@handler_options[:allow_override] = @@handler_options[:allow_override].split(',').collect(&:strip) if @@handler_options[:allow_override].is_a?(String)
31 @@handler_options[:allow_override] ||= []
31 @@handler_options[:allow_override] ||= []
32 # Project needs to be overridable if not specified
32 # Project needs to be overridable if not specified
33 @@handler_options[:allow_override] << 'project' unless @@handler_options[:issue].has_key?(:project)
33 @@handler_options[:allow_override] << 'project' unless @@handler_options[:issue].has_key?(:project)
34 # Status needs to be overridable if not specified
34 # Status overridable by default
35 @@handler_options[:allow_override] << 'status' unless @@handler_options[:issue].has_key?(:status)
35 @@handler_options[:allow_override] << 'status' unless @@handler_options[:issue].has_key?(:status)
36 super email
36 super email
37 end
37 end
38
38
39 # Processes incoming emails
39 # Processes incoming emails
40 def receive(email)
40 def receive(email)
41 @email = email
41 @email = email
42 @user = User.find_active(:first, :conditions => ["LOWER(mail) = ?", email.from.first.to_s.strip.downcase])
42 @user = User.find_active(:first, :conditions => ["LOWER(mail) = ?", email.from.first.to_s.strip.downcase])
43 unless @user
43 unless @user
44 # Unknown user => the email is ignored
44 # Unknown user => the email is ignored
45 # TODO: ability to create the user's account
45 # TODO: ability to create the user's account
46 logger.info "MailHandler: email submitted by unknown user [#{email.from.first}]" if logger && logger.info
46 logger.info "MailHandler: email submitted by unknown user [#{email.from.first}]" if logger && logger.info
47 return false
47 return false
48 end
48 end
49 User.current = @user
49 User.current = @user
50 dispatch
50 dispatch
51 end
51 end
52
52
53 private
53 private
54
54
55 ISSUE_REPLY_SUBJECT_RE = %r{\[[^\]]+#(\d+)\]}
55 ISSUE_REPLY_SUBJECT_RE = %r{\[[^\]]+#(\d+)\]}
56
56
57 def dispatch
57 def dispatch
58 if m = email.subject.match(ISSUE_REPLY_SUBJECT_RE)
58 if m = email.subject.match(ISSUE_REPLY_SUBJECT_RE)
59 receive_issue_update(m[1].to_i)
59 receive_issue_update(m[1].to_i)
60 else
60 else
61 receive_issue
61 receive_issue
62 end
62 end
63 rescue ActiveRecord::RecordInvalid => e
63 rescue ActiveRecord::RecordInvalid => e
64 # TODO: send a email to the user
64 # TODO: send a email to the user
65 logger.error e.message if logger
65 logger.error e.message if logger
66 false
66 false
67 rescue MissingInformation => e
67 rescue MissingInformation => e
68 logger.error "MailHandler: missing information from #{user}: #{e.message}" if logger
68 logger.error "MailHandler: missing information from #{user}: #{e.message}" if logger
69 false
69 false
70 rescue UnauthorizedAction => e
70 rescue UnauthorizedAction => e
71 logger.error "MailHandler: unauthorized attempt from #{user}" if logger
71 logger.error "MailHandler: unauthorized attempt from #{user}" if logger
72 false
72 false
73 end
73 end
74
74
75 # Creates a new issue
75 # Creates a new issue
76 def receive_issue
76 def receive_issue
77 project = target_project
77 project = target_project
78 tracker = (get_keyword(:tracker) && project.trackers.find_by_name(get_keyword(:tracker))) || project.trackers.find(:first)
78 tracker = (get_keyword(:tracker) && project.trackers.find_by_name(get_keyword(:tracker))) || project.trackers.find(:first)
79 category = (get_keyword(:category) && project.issue_categories.find_by_name(get_keyword(:category)))
79 category = (get_keyword(:category) && project.issue_categories.find_by_name(get_keyword(:category)))
80 priority = (get_keyword(:priority) && Enumeration.find_by_opt_and_name('IPRI', get_keyword(:priority)))
80 priority = (get_keyword(:priority) && Enumeration.find_by_opt_and_name('IPRI', get_keyword(:priority)))
81 status = (get_keyword(:status) && IssueStatus.find_by_name(get_keyword(:status))) || IssueStatus.default
81 status = (get_keyword(:status) && IssueStatus.find_by_name(get_keyword(:status)))
82
82
83 # check permission
83 # check permission
84 raise UnauthorizedAction unless user.allowed_to?(:add_issues, project)
84 raise UnauthorizedAction unless user.allowed_to?(:add_issues, project)
85 issue = Issue.new(:author => user, :project => project, :tracker => tracker, :category => category, :priority => priority, :status => status)
85 issue = Issue.new(:author => user, :project => project, :tracker => tracker, :category => category, :priority => priority)
86 # check workflow
87 if status && issue.new_statuses_allowed_to(user).include?(status)
88 issue.status = status
89 end
86 issue.subject = email.subject.chomp.toutf8
90 issue.subject = email.subject.chomp.toutf8
87 issue.description = email.plain_text_body.chomp
91 issue.description = email.plain_text_body.chomp
88 issue.save!
92 issue.save!
89 add_attachments(issue)
93 add_attachments(issue)
90 logger.info "MailHandler: issue ##{issue.id} created by #{user}" if logger && logger.info
94 logger.info "MailHandler: issue ##{issue.id} created by #{user}" if logger && logger.info
91 Mailer.deliver_issue_add(issue) if Setting.notified_events.include?('issue_added')
95 Mailer.deliver_issue_add(issue) if Setting.notified_events.include?('issue_added')
92 issue
96 issue
93 end
97 end
94
98
95 def target_project
99 def target_project
96 # TODO: other ways to specify project:
100 # TODO: other ways to specify project:
97 # * parse the email To field
101 # * parse the email To field
98 # * specific project (eg. Setting.mail_handler_target_project)
102 # * specific project (eg. Setting.mail_handler_target_project)
99 target = Project.find_by_identifier(get_keyword(:project))
103 target = Project.find_by_identifier(get_keyword(:project))
100 raise MissingInformation.new('Unable to determine target project') if target.nil?
104 raise MissingInformation.new('Unable to determine target project') if target.nil?
101 target
105 target
102 end
106 end
103
107
104 # Adds a note to an existing issue
108 # Adds a note to an existing issue
105 def receive_issue_update(issue_id)
109 def receive_issue_update(issue_id)
106 status = (get_keyword(:status) && IssueStatus.find_by_name(get_keyword(:status)))
110 status = (get_keyword(:status) && IssueStatus.find_by_name(get_keyword(:status)))
107
111
108 issue = Issue.find_by_id(issue_id)
112 issue = Issue.find_by_id(issue_id)
109 return unless issue
113 return unless issue
110 # check permission
114 # check permission
111 raise UnauthorizedAction unless user.allowed_to?(:add_issue_notes, issue.project) || user.allowed_to?(:edit_issues, issue.project)
115 raise UnauthorizedAction unless user.allowed_to?(:add_issue_notes, issue.project) || user.allowed_to?(:edit_issues, issue.project)
112 raise UnauthorizedAction unless status.nil? || user.allowed_to?(:edit_issues, issue.project)
116 raise UnauthorizedAction unless status.nil? || user.allowed_to?(:edit_issues, issue.project)
113
117
114 # add the note
118 # add the note
115 journal = issue.init_journal(user, email.plain_text_body.chomp)
119 journal = issue.init_journal(user, email.plain_text_body.chomp)
116 add_attachments(issue)
120 add_attachments(issue)
117 issue.status = status unless status.nil?
121 # check workflow
122 if status && issue.new_statuses_allowed_to(user).include?(status)
123 issue.status = status
124 end
118 issue.save!
125 issue.save!
119 logger.info "MailHandler: issue ##{issue.id} updated by #{user}" if logger && logger.info
126 logger.info "MailHandler: issue ##{issue.id} updated by #{user}" if logger && logger.info
120 Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
127 Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
121 journal
128 journal
122 end
129 end
123
130
124 def add_attachments(obj)
131 def add_attachments(obj)
125 if email.has_attachments?
132 if email.has_attachments?
126 email.attachments.each do |attachment|
133 email.attachments.each do |attachment|
127 Attachment.create(:container => obj,
134 Attachment.create(:container => obj,
128 :file => attachment,
135 :file => attachment,
129 :author => user,
136 :author => user,
130 :content_type => attachment.content_type)
137 :content_type => attachment.content_type)
131 end
138 end
132 end
139 end
133 end
140 end
134
141
135 def get_keyword(attr)
142 def get_keyword(attr)
136 if @@handler_options[:allow_override].include?(attr.to_s) && email.plain_text_body =~ /^#{attr}:[ \t]*(.+)$/i
143 if @@handler_options[:allow_override].include?(attr.to_s) && email.plain_text_body =~ /^#{attr}:[ \t]*(.+)$/i
137 $1.strip
144 $1.strip
138 elsif !@@handler_options[:issue][attr].blank?
145 elsif !@@handler_options[:issue][attr].blank?
139 @@handler_options[:issue][attr]
146 @@handler_options[:issue][attr]
140 end
147 end
141 end
148 end
142 end
149 end
143
150
144 class TMail::Mail
151 class TMail::Mail
145 # Returns body of the first plain text part found if any
152 # Returns body of the first plain text part found if any
146 def plain_text_body
153 def plain_text_body
147 return @plain_text_body unless @plain_text_body.nil?
154 return @plain_text_body unless @plain_text_body.nil?
148 p = self.parts.collect {|c| (c.respond_to?(:parts) && !c.parts.empty?) ? c.parts : c}.flatten
155 p = self.parts.collect {|c| (c.respond_to?(:parts) && !c.parts.empty?) ? c.parts : c}.flatten
149 plain = p.detect {|c| c.content_type == 'text/plain'}
156 plain = p.detect {|c| c.content_type == 'text/plain'}
150 @plain_text_body = plain.nil? ? self.body : plain.body
157 @plain_text_body = plain.nil? ? self.body : plain.body
151 end
158 end
152 end
159 end
153
160
General Comments 0
You need to be logged in to leave comments. Login now