##// END OF EJS Templates
replace Mailer deliver syntax to Rails3 style...
Toshi MARUYAMA -
r9455:7af8d7caf064
parent child
Show More
@@ -1,279 +1,279
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 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 AccountController < ApplicationController
18 class AccountController < ApplicationController
19 helper :custom_fields
19 helper :custom_fields
20 include CustomFieldsHelper
20 include CustomFieldsHelper
21
21
22 # prevents login action to be filtered by check_if_login_required application scope filter
22 # prevents login action to be filtered by check_if_login_required application scope filter
23 skip_before_filter :check_if_login_required
23 skip_before_filter :check_if_login_required
24
24
25 # Login request and validation
25 # Login request and validation
26 def login
26 def login
27 if request.get?
27 if request.get?
28 logout_user
28 logout_user
29 else
29 else
30 authenticate_user
30 authenticate_user
31 end
31 end
32 rescue AuthSourceException => e
32 rescue AuthSourceException => e
33 logger.error "An error occured when authenticating #{params[:username]}: #{e.message}"
33 logger.error "An error occured when authenticating #{params[:username]}: #{e.message}"
34 render_error :message => e.message
34 render_error :message => e.message
35 end
35 end
36
36
37 # Log out current user and redirect to welcome page
37 # Log out current user and redirect to welcome page
38 def logout
38 def logout
39 logout_user
39 logout_user
40 redirect_to home_url
40 redirect_to home_url
41 end
41 end
42
42
43 # Enable user to choose a new password
43 # Enable user to choose a new password
44 def lost_password
44 def lost_password
45 redirect_to(home_url) && return unless Setting.lost_password?
45 redirect_to(home_url) && return unless Setting.lost_password?
46 if params[:token]
46 if params[:token]
47 @token = Token.find_by_action_and_value("recovery", params[:token])
47 @token = Token.find_by_action_and_value("recovery", params[:token])
48 redirect_to(home_url) && return unless @token and !@token.expired?
48 redirect_to(home_url) && return unless @token and !@token.expired?
49 @user = @token.user
49 @user = @token.user
50 if request.post?
50 if request.post?
51 @user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation]
51 @user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation]
52 if @user.save
52 if @user.save
53 @token.destroy
53 @token.destroy
54 flash[:notice] = l(:notice_account_password_updated)
54 flash[:notice] = l(:notice_account_password_updated)
55 redirect_to :action => 'login'
55 redirect_to :action => 'login'
56 return
56 return
57 end
57 end
58 end
58 end
59 render :template => "account/password_recovery"
59 render :template => "account/password_recovery"
60 return
60 return
61 else
61 else
62 if request.post?
62 if request.post?
63 user = User.find_by_mail(params[:mail])
63 user = User.find_by_mail(params[:mail])
64 # user not found in db
64 # user not found in db
65 (flash.now[:error] = l(:notice_account_unknown_email); return) unless user
65 (flash.now[:error] = l(:notice_account_unknown_email); return) unless user
66 # user uses an external authentification
66 # user uses an external authentification
67 (flash.now[:error] = l(:notice_can_t_change_password); return) if user.auth_source_id
67 (flash.now[:error] = l(:notice_can_t_change_password); return) if user.auth_source_id
68 # create a new token for password recovery
68 # create a new token for password recovery
69 token = Token.new(:user => user, :action => "recovery")
69 token = Token.new(:user => user, :action => "recovery")
70 if token.save
70 if token.save
71 Mailer.deliver_lost_password(token)
71 Mailer.lost_password(token).deliver
72 flash[:notice] = l(:notice_account_lost_email_sent)
72 flash[:notice] = l(:notice_account_lost_email_sent)
73 redirect_to :action => 'login'
73 redirect_to :action => 'login'
74 return
74 return
75 end
75 end
76 end
76 end
77 end
77 end
78 end
78 end
79
79
80 # User self-registration
80 # User self-registration
81 def register
81 def register
82 redirect_to(home_url) && return unless Setting.self_registration? || session[:auth_source_registration]
82 redirect_to(home_url) && return unless Setting.self_registration? || session[:auth_source_registration]
83 if request.get?
83 if request.get?
84 session[:auth_source_registration] = nil
84 session[:auth_source_registration] = nil
85 @user = User.new(:language => Setting.default_language)
85 @user = User.new(:language => Setting.default_language)
86 else
86 else
87 @user = User.new
87 @user = User.new
88 @user.safe_attributes = params[:user]
88 @user.safe_attributes = params[:user]
89 @user.admin = false
89 @user.admin = false
90 @user.register
90 @user.register
91 if session[:auth_source_registration]
91 if session[:auth_source_registration]
92 @user.activate
92 @user.activate
93 @user.login = session[:auth_source_registration][:login]
93 @user.login = session[:auth_source_registration][:login]
94 @user.auth_source_id = session[:auth_source_registration][:auth_source_id]
94 @user.auth_source_id = session[:auth_source_registration][:auth_source_id]
95 if @user.save
95 if @user.save
96 session[:auth_source_registration] = nil
96 session[:auth_source_registration] = nil
97 self.logged_user = @user
97 self.logged_user = @user
98 flash[:notice] = l(:notice_account_activated)
98 flash[:notice] = l(:notice_account_activated)
99 redirect_to :controller => 'my', :action => 'account'
99 redirect_to :controller => 'my', :action => 'account'
100 end
100 end
101 else
101 else
102 @user.login = params[:user][:login]
102 @user.login = params[:user][:login]
103 @user.password, @user.password_confirmation = params[:user][:password], params[:user][:password_confirmation]
103 @user.password, @user.password_confirmation = params[:user][:password], params[:user][:password_confirmation]
104
104
105 case Setting.self_registration
105 case Setting.self_registration
106 when '1'
106 when '1'
107 register_by_email_activation(@user)
107 register_by_email_activation(@user)
108 when '3'
108 when '3'
109 register_automatically(@user)
109 register_automatically(@user)
110 else
110 else
111 register_manually_by_administrator(@user)
111 register_manually_by_administrator(@user)
112 end
112 end
113 end
113 end
114 end
114 end
115 end
115 end
116
116
117 # Token based account activation
117 # Token based account activation
118 def activate
118 def activate
119 redirect_to(home_url) && return unless Setting.self_registration? && params[:token]
119 redirect_to(home_url) && return unless Setting.self_registration? && params[:token]
120 token = Token.find_by_action_and_value('register', params[:token])
120 token = Token.find_by_action_and_value('register', params[:token])
121 redirect_to(home_url) && return unless token and !token.expired?
121 redirect_to(home_url) && return unless token and !token.expired?
122 user = token.user
122 user = token.user
123 redirect_to(home_url) && return unless user.registered?
123 redirect_to(home_url) && return unless user.registered?
124 user.activate
124 user.activate
125 if user.save
125 if user.save
126 token.destroy
126 token.destroy
127 flash[:notice] = l(:notice_account_activated)
127 flash[:notice] = l(:notice_account_activated)
128 end
128 end
129 redirect_to :action => 'login'
129 redirect_to :action => 'login'
130 end
130 end
131
131
132 private
132 private
133
133
134 def authenticate_user
134 def authenticate_user
135 if Setting.openid? && using_open_id?
135 if Setting.openid? && using_open_id?
136 open_id_authenticate(params[:openid_url])
136 open_id_authenticate(params[:openid_url])
137 else
137 else
138 password_authentication
138 password_authentication
139 end
139 end
140 end
140 end
141
141
142 def password_authentication
142 def password_authentication
143 user = User.try_to_login(params[:username], params[:password])
143 user = User.try_to_login(params[:username], params[:password])
144
144
145 if user.nil?
145 if user.nil?
146 invalid_credentials
146 invalid_credentials
147 elsif user.new_record?
147 elsif user.new_record?
148 onthefly_creation_failed(user, {:login => user.login, :auth_source_id => user.auth_source_id })
148 onthefly_creation_failed(user, {:login => user.login, :auth_source_id => user.auth_source_id })
149 else
149 else
150 # Valid user
150 # Valid user
151 successful_authentication(user)
151 successful_authentication(user)
152 end
152 end
153 end
153 end
154
154
155 def open_id_authenticate(openid_url)
155 def open_id_authenticate(openid_url)
156 authenticate_with_open_id(openid_url, :required => [:nickname, :fullname, :email], :return_to => signin_url, :method => :post) do |result, identity_url, registration|
156 authenticate_with_open_id(openid_url, :required => [:nickname, :fullname, :email], :return_to => signin_url, :method => :post) do |result, identity_url, registration|
157 if result.successful?
157 if result.successful?
158 user = User.find_or_initialize_by_identity_url(identity_url)
158 user = User.find_or_initialize_by_identity_url(identity_url)
159 if user.new_record?
159 if user.new_record?
160 # Self-registration off
160 # Self-registration off
161 redirect_to(home_url) && return unless Setting.self_registration?
161 redirect_to(home_url) && return unless Setting.self_registration?
162
162
163 # Create on the fly
163 # Create on the fly
164 user.login = registration['nickname'] unless registration['nickname'].nil?
164 user.login = registration['nickname'] unless registration['nickname'].nil?
165 user.mail = registration['email'] unless registration['email'].nil?
165 user.mail = registration['email'] unless registration['email'].nil?
166 user.firstname, user.lastname = registration['fullname'].split(' ') unless registration['fullname'].nil?
166 user.firstname, user.lastname = registration['fullname'].split(' ') unless registration['fullname'].nil?
167 user.random_password
167 user.random_password
168 user.register
168 user.register
169
169
170 case Setting.self_registration
170 case Setting.self_registration
171 when '1'
171 when '1'
172 register_by_email_activation(user) do
172 register_by_email_activation(user) do
173 onthefly_creation_failed(user)
173 onthefly_creation_failed(user)
174 end
174 end
175 when '3'
175 when '3'
176 register_automatically(user) do
176 register_automatically(user) do
177 onthefly_creation_failed(user)
177 onthefly_creation_failed(user)
178 end
178 end
179 else
179 else
180 register_manually_by_administrator(user) do
180 register_manually_by_administrator(user) do
181 onthefly_creation_failed(user)
181 onthefly_creation_failed(user)
182 end
182 end
183 end
183 end
184 else
184 else
185 # Existing record
185 # Existing record
186 if user.active?
186 if user.active?
187 successful_authentication(user)
187 successful_authentication(user)
188 else
188 else
189 account_pending
189 account_pending
190 end
190 end
191 end
191 end
192 end
192 end
193 end
193 end
194 end
194 end
195
195
196 def successful_authentication(user)
196 def successful_authentication(user)
197 # Valid user
197 # Valid user
198 self.logged_user = user
198 self.logged_user = user
199 # generate a key and set cookie if autologin
199 # generate a key and set cookie if autologin
200 if params[:autologin] && Setting.autologin?
200 if params[:autologin] && Setting.autologin?
201 set_autologin_cookie(user)
201 set_autologin_cookie(user)
202 end
202 end
203 call_hook(:controller_account_success_authentication_after, {:user => user })
203 call_hook(:controller_account_success_authentication_after, {:user => user })
204 redirect_back_or_default :controller => 'my', :action => 'page'
204 redirect_back_or_default :controller => 'my', :action => 'page'
205 end
205 end
206
206
207 def set_autologin_cookie(user)
207 def set_autologin_cookie(user)
208 token = Token.create(:user => user, :action => 'autologin')
208 token = Token.create(:user => user, :action => 'autologin')
209 cookie_name = Redmine::Configuration['autologin_cookie_name'] || 'autologin'
209 cookie_name = Redmine::Configuration['autologin_cookie_name'] || 'autologin'
210 cookie_options = {
210 cookie_options = {
211 :value => token.value,
211 :value => token.value,
212 :expires => 1.year.from_now,
212 :expires => 1.year.from_now,
213 :path => (Redmine::Configuration['autologin_cookie_path'] || '/'),
213 :path => (Redmine::Configuration['autologin_cookie_path'] || '/'),
214 :secure => (Redmine::Configuration['autologin_cookie_secure'] ? true : false),
214 :secure => (Redmine::Configuration['autologin_cookie_secure'] ? true : false),
215 :httponly => true
215 :httponly => true
216 }
216 }
217 cookies[cookie_name] = cookie_options
217 cookies[cookie_name] = cookie_options
218 end
218 end
219
219
220 # Onthefly creation failed, display the registration form to fill/fix attributes
220 # Onthefly creation failed, display the registration form to fill/fix attributes
221 def onthefly_creation_failed(user, auth_source_options = { })
221 def onthefly_creation_failed(user, auth_source_options = { })
222 @user = user
222 @user = user
223 session[:auth_source_registration] = auth_source_options unless auth_source_options.empty?
223 session[:auth_source_registration] = auth_source_options unless auth_source_options.empty?
224 render :action => 'register'
224 render :action => 'register'
225 end
225 end
226
226
227 def invalid_credentials
227 def invalid_credentials
228 logger.warn "Failed login for '#{params[:username]}' from #{request.remote_ip} at #{Time.now.utc}"
228 logger.warn "Failed login for '#{params[:username]}' from #{request.remote_ip} at #{Time.now.utc}"
229 flash.now[:error] = l(:notice_account_invalid_creditentials)
229 flash.now[:error] = l(:notice_account_invalid_creditentials)
230 end
230 end
231
231
232 # Register a user for email activation.
232 # Register a user for email activation.
233 #
233 #
234 # Pass a block for behavior when a user fails to save
234 # Pass a block for behavior when a user fails to save
235 def register_by_email_activation(user, &block)
235 def register_by_email_activation(user, &block)
236 token = Token.new(:user => user, :action => "register")
236 token = Token.new(:user => user, :action => "register")
237 if user.save and token.save
237 if user.save and token.save
238 Mailer.deliver_register(token)
238 Mailer.register(token).deliver
239 flash[:notice] = l(:notice_account_register_done)
239 flash[:notice] = l(:notice_account_register_done)
240 redirect_to :action => 'login'
240 redirect_to :action => 'login'
241 else
241 else
242 yield if block_given?
242 yield if block_given?
243 end
243 end
244 end
244 end
245
245
246 # Automatically register a user
246 # Automatically register a user
247 #
247 #
248 # Pass a block for behavior when a user fails to save
248 # Pass a block for behavior when a user fails to save
249 def register_automatically(user, &block)
249 def register_automatically(user, &block)
250 # Automatic activation
250 # Automatic activation
251 user.activate
251 user.activate
252 user.last_login_on = Time.now
252 user.last_login_on = Time.now
253 if user.save
253 if user.save
254 self.logged_user = user
254 self.logged_user = user
255 flash[:notice] = l(:notice_account_activated)
255 flash[:notice] = l(:notice_account_activated)
256 redirect_to :controller => 'my', :action => 'account'
256 redirect_to :controller => 'my', :action => 'account'
257 else
257 else
258 yield if block_given?
258 yield if block_given?
259 end
259 end
260 end
260 end
261
261
262 # Manual activation by the administrator
262 # Manual activation by the administrator
263 #
263 #
264 # Pass a block for behavior when a user fails to save
264 # Pass a block for behavior when a user fails to save
265 def register_manually_by_administrator(user, &block)
265 def register_manually_by_administrator(user, &block)
266 if user.save
266 if user.save
267 # Sends an email to the administrators
267 # Sends an email to the administrators
268 Mailer.deliver_account_activation_request(user)
268 Mailer.account_activation_request(user).deliver
269 account_pending
269 account_pending
270 else
270 else
271 yield if block_given?
271 yield if block_given?
272 end
272 end
273 end
273 end
274
274
275 def account_pending
275 def account_pending
276 flash[:notice] = l(:notice_account_pending)
276 flash[:notice] = l(:notice_account_pending)
277 redirect_to :action => 'login'
277 redirect_to :action => 'login'
278 end
278 end
279 end
279 end
@@ -1,84 +1,84
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 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 AdminController < ApplicationController
18 class AdminController < ApplicationController
19 layout 'admin'
19 layout 'admin'
20 menu_item :projects, :only => :projects
20 menu_item :projects, :only => :projects
21 menu_item :plugins, :only => :plugins
21 menu_item :plugins, :only => :plugins
22 menu_item :info, :only => :info
22 menu_item :info, :only => :info
23
23
24 before_filter :require_admin
24 before_filter :require_admin
25 helper :sort
25 helper :sort
26 include SortHelper
26 include SortHelper
27
27
28 def index
28 def index
29 @no_configuration_data = Redmine::DefaultData::Loader::no_data?
29 @no_configuration_data = Redmine::DefaultData::Loader::no_data?
30 end
30 end
31
31
32 def projects
32 def projects
33 @status = params[:status] || 1
33 @status = params[:status] || 1
34
34
35 scope = Project.status(@status)
35 scope = Project.status(@status)
36 scope = scope.like(params[:name]) if params[:name].present?
36 scope = scope.like(params[:name]) if params[:name].present?
37
37
38 @projects = scope.all(:order => 'lft')
38 @projects = scope.all(:order => 'lft')
39
39
40 render :action => "projects", :layout => false if request.xhr?
40 render :action => "projects", :layout => false if request.xhr?
41 end
41 end
42
42
43 def plugins
43 def plugins
44 @plugins = Redmine::Plugin.all
44 @plugins = Redmine::Plugin.all
45 end
45 end
46
46
47 # Loads the default configuration
47 # Loads the default configuration
48 # (roles, trackers, statuses, workflow, enumerations)
48 # (roles, trackers, statuses, workflow, enumerations)
49 def default_configuration
49 def default_configuration
50 if request.post?
50 if request.post?
51 begin
51 begin
52 Redmine::DefaultData::Loader::load(params[:lang])
52 Redmine::DefaultData::Loader::load(params[:lang])
53 flash[:notice] = l(:notice_default_data_loaded)
53 flash[:notice] = l(:notice_default_data_loaded)
54 rescue Exception => e
54 rescue Exception => e
55 flash[:error] = l(:error_can_t_load_default_data, e.message)
55 flash[:error] = l(:error_can_t_load_default_data, e.message)
56 end
56 end
57 end
57 end
58 redirect_to :action => 'index'
58 redirect_to :action => 'index'
59 end
59 end
60
60
61 def test_email
61 def test_email
62 raise_delivery_errors = ActionMailer::Base.raise_delivery_errors
62 raise_delivery_errors = ActionMailer::Base.raise_delivery_errors
63 # Force ActionMailer to raise delivery errors so we can catch it
63 # Force ActionMailer to raise delivery errors so we can catch it
64 ActionMailer::Base.raise_delivery_errors = true
64 ActionMailer::Base.raise_delivery_errors = true
65 begin
65 begin
66 @test = Mailer.deliver_test_email(User.current)
66 @test = Mailer.test_email(User.current).deliver
67 flash[:notice] = l(:notice_email_sent, User.current.mail)
67 flash[:notice] = l(:notice_email_sent, User.current.mail)
68 rescue Exception => e
68 rescue Exception => e
69 flash[:error] = l(:notice_email_error, e.message)
69 flash[:error] = l(:notice_email_error, e.message)
70 end
70 end
71 ActionMailer::Base.raise_delivery_errors = raise_delivery_errors
71 ActionMailer::Base.raise_delivery_errors = raise_delivery_errors
72 redirect_to :controller => 'settings', :action => 'edit', :tab => 'notifications'
72 redirect_to :controller => 'settings', :action => 'edit', :tab => 'notifications'
73 end
73 end
74
74
75 def info
75 def info
76 @db_adapter_name = ActiveRecord::Base.connection.adapter_name
76 @db_adapter_name = ActiveRecord::Base.connection.adapter_name
77 @checklist = [
77 @checklist = [
78 [:text_default_administrator_account_changed, User.default_admin_account_changed?],
78 [:text_default_administrator_account_changed, User.default_admin_account_changed?],
79 [:text_file_repository_writable, File.writable?(Attachment.storage_path)],
79 [:text_file_repository_writable, File.writable?(Attachment.storage_path)],
80 [:text_plugin_assets_writable, File.writable?(Redmine::Plugin.public_directory)],
80 [:text_plugin_assets_writable, File.writable?(Redmine::Plugin.public_directory)],
81 [:text_rmagick_available, Object.const_defined?(:Magick)]
81 [:text_rmagick_available, Object.const_defined?(:Magick)]
82 ]
82 ]
83 end
83 end
84 end
84 end
@@ -1,92 +1,94
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 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 DocumentsController < ApplicationController
18 class DocumentsController < ApplicationController
19 default_search_scope :documents
19 default_search_scope :documents
20 model_object Document
20 model_object Document
21 before_filter :find_project_by_project_id, :only => [:index, :new, :create]
21 before_filter :find_project_by_project_id, :only => [:index, :new, :create]
22 before_filter :find_model_object, :except => [:index, :new, :create]
22 before_filter :find_model_object, :except => [:index, :new, :create]
23 before_filter :find_project_from_association, :except => [:index, :new, :create]
23 before_filter :find_project_from_association, :except => [:index, :new, :create]
24 before_filter :authorize
24 before_filter :authorize
25
25
26 helper :attachments
26 helper :attachments
27
27
28 def index
28 def index
29 @sort_by = %w(category date title author).include?(params[:sort_by]) ? params[:sort_by] : 'category'
29 @sort_by = %w(category date title author).include?(params[:sort_by]) ? params[:sort_by] : 'category'
30 documents = @project.documents.find :all, :include => [:attachments, :category]
30 documents = @project.documents.find :all, :include => [:attachments, :category]
31 case @sort_by
31 case @sort_by
32 when 'date'
32 when 'date'
33 @grouped = documents.group_by {|d| d.updated_on.to_date }
33 @grouped = documents.group_by {|d| d.updated_on.to_date }
34 when 'title'
34 when 'title'
35 @grouped = documents.group_by {|d| d.title.first.upcase}
35 @grouped = documents.group_by {|d| d.title.first.upcase}
36 when 'author'
36 when 'author'
37 @grouped = documents.select{|d| d.attachments.any?}.group_by {|d| d.attachments.last.author}
37 @grouped = documents.select{|d| d.attachments.any?}.group_by {|d| d.attachments.last.author}
38 else
38 else
39 @grouped = documents.group_by(&:category)
39 @grouped = documents.group_by(&:category)
40 end
40 end
41 @document = @project.documents.build
41 @document = @project.documents.build
42 render :layout => false if request.xhr?
42 render :layout => false if request.xhr?
43 end
43 end
44
44
45 def show
45 def show
46 @attachments = @document.attachments.find(:all, :order => "created_on DESC")
46 @attachments = @document.attachments.find(:all, :order => "created_on DESC")
47 end
47 end
48
48
49 def new
49 def new
50 @document = @project.documents.build
50 @document = @project.documents.build
51 @document.safe_attributes = params[:document]
51 @document.safe_attributes = params[:document]
52 end
52 end
53
53
54 def create
54 def create
55 @document = @project.documents.build
55 @document = @project.documents.build
56 @document.safe_attributes = params[:document]
56 @document.safe_attributes = params[:document]
57 @document.save_attachments(params[:attachments])
57 @document.save_attachments(params[:attachments])
58 if @document.save
58 if @document.save
59 render_attachment_warning_if_needed(@document)
59 render_attachment_warning_if_needed(@document)
60 flash[:notice] = l(:notice_successful_create)
60 flash[:notice] = l(:notice_successful_create)
61 redirect_to :action => 'index', :project_id => @project
61 redirect_to :action => 'index', :project_id => @project
62 else
62 else
63 render :action => 'new'
63 render :action => 'new'
64 end
64 end
65 end
65 end
66
66
67 def edit
67 def edit
68 end
68 end
69
69
70 def update
70 def update
71 @document.safe_attributes = params[:document]
71 @document.safe_attributes = params[:document]
72 if request.put? and @document.save
72 if request.put? and @document.save
73 flash[:notice] = l(:notice_successful_update)
73 flash[:notice] = l(:notice_successful_update)
74 redirect_to :action => 'show', :id => @document
74 redirect_to :action => 'show', :id => @document
75 else
75 else
76 render :action => 'edit'
76 render :action => 'edit'
77 end
77 end
78 end
78 end
79
79
80 def destroy
80 def destroy
81 @document.destroy if request.delete?
81 @document.destroy if request.delete?
82 redirect_to :controller => 'documents', :action => 'index', :project_id => @project
82 redirect_to :controller => 'documents', :action => 'index', :project_id => @project
83 end
83 end
84
84
85 def add_attachment
85 def add_attachment
86 attachments = Attachment.attach_files(@document, params[:attachments])
86 attachments = Attachment.attach_files(@document, params[:attachments])
87 render_attachment_warning_if_needed(@document)
87 render_attachment_warning_if_needed(@document)
88
88
89 Mailer.deliver_attachments_added(attachments[:files]) if attachments.present? && attachments[:files].present? && Setting.notified_events.include?('document_added')
89 if attachments.present? && attachments[:files].present? && Setting.notified_events.include?('document_added')
90 Mailer.attachments_added(attachments[:files]).deliver
91 end
90 redirect_to :action => 'show', :id => @document
92 redirect_to :action => 'show', :id => @document
91 end
93 end
92 end
94 end
@@ -1,53 +1,53
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 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 FilesController < ApplicationController
18 class FilesController < ApplicationController
19 menu_item :files
19 menu_item :files
20
20
21 before_filter :find_project_by_project_id
21 before_filter :find_project_by_project_id
22 before_filter :authorize
22 before_filter :authorize
23
23
24 helper :sort
24 helper :sort
25 include SortHelper
25 include SortHelper
26
26
27 def index
27 def index
28 sort_init 'filename', 'asc'
28 sort_init 'filename', 'asc'
29 sort_update 'filename' => "#{Attachment.table_name}.filename",
29 sort_update 'filename' => "#{Attachment.table_name}.filename",
30 'created_on' => "#{Attachment.table_name}.created_on",
30 'created_on' => "#{Attachment.table_name}.created_on",
31 'size' => "#{Attachment.table_name}.filesize",
31 'size' => "#{Attachment.table_name}.filesize",
32 'downloads' => "#{Attachment.table_name}.downloads"
32 'downloads' => "#{Attachment.table_name}.downloads"
33
33
34 @containers = [ Project.find(@project.id, :include => :attachments, :order => sort_clause)]
34 @containers = [ Project.find(@project.id, :include => :attachments, :order => sort_clause)]
35 @containers += @project.versions.find(:all, :include => :attachments, :order => sort_clause).sort.reverse
35 @containers += @project.versions.find(:all, :include => :attachments, :order => sort_clause).sort.reverse
36 render :layout => !request.xhr?
36 render :layout => !request.xhr?
37 end
37 end
38
38
39 def new
39 def new
40 @versions = @project.versions.sort
40 @versions = @project.versions.sort
41 end
41 end
42
42
43 def create
43 def create
44 container = (params[:version_id].blank? ? @project : @project.versions.find_by_id(params[:version_id]))
44 container = (params[:version_id].blank? ? @project : @project.versions.find_by_id(params[:version_id]))
45 attachments = Attachment.attach_files(container, params[:attachments])
45 attachments = Attachment.attach_files(container, params[:attachments])
46 render_attachment_warning_if_needed(container)
46 render_attachment_warning_if_needed(container)
47
47
48 if !attachments.empty? && !attachments[:files].blank? && Setting.notified_events.include?('file_added')
48 if !attachments.empty? && !attachments[:files].blank? && Setting.notified_events.include?('file_added')
49 Mailer.deliver_attachments_added(attachments[:files])
49 Mailer.attachments_added(attachments[:files]).deliver
50 end
50 end
51 redirect_to project_files_path(@project)
51 redirect_to project_files_path(@project)
52 end
52 end
53 end
53 end
@@ -1,227 +1,227
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 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 UsersController < ApplicationController
18 class UsersController < ApplicationController
19 layout 'admin'
19 layout 'admin'
20
20
21 before_filter :require_admin, :except => :show
21 before_filter :require_admin, :except => :show
22 before_filter :find_user, :only => [:show, :edit, :update, :destroy, :edit_membership, :destroy_membership]
22 before_filter :find_user, :only => [:show, :edit, :update, :destroy, :edit_membership, :destroy_membership]
23 accept_api_auth :index, :show, :create, :update, :destroy
23 accept_api_auth :index, :show, :create, :update, :destroy
24
24
25 helper :sort
25 helper :sort
26 include SortHelper
26 include SortHelper
27 helper :custom_fields
27 helper :custom_fields
28 include CustomFieldsHelper
28 include CustomFieldsHelper
29
29
30 def index
30 def index
31 sort_init 'login', 'asc'
31 sort_init 'login', 'asc'
32 sort_update %w(login firstname lastname mail admin created_on last_login_on)
32 sort_update %w(login firstname lastname mail admin created_on last_login_on)
33
33
34 case params[:format]
34 case params[:format]
35 when 'xml', 'json'
35 when 'xml', 'json'
36 @offset, @limit = api_offset_and_limit
36 @offset, @limit = api_offset_and_limit
37 else
37 else
38 @limit = per_page_option
38 @limit = per_page_option
39 end
39 end
40
40
41 @status = params[:status] || 1
41 @status = params[:status] || 1
42
42
43 scope = User.logged.status(@status)
43 scope = User.logged.status(@status)
44 scope = scope.like(params[:name]) if params[:name].present?
44 scope = scope.like(params[:name]) if params[:name].present?
45 scope = scope.in_group(params[:group_id]) if params[:group_id].present?
45 scope = scope.in_group(params[:group_id]) if params[:group_id].present?
46
46
47 @user_count = scope.count
47 @user_count = scope.count
48 @user_pages = Paginator.new self, @user_count, @limit, params['page']
48 @user_pages = Paginator.new self, @user_count, @limit, params['page']
49 @offset ||= @user_pages.current.offset
49 @offset ||= @user_pages.current.offset
50 @users = scope.find :all,
50 @users = scope.find :all,
51 :order => sort_clause,
51 :order => sort_clause,
52 :limit => @limit,
52 :limit => @limit,
53 :offset => @offset
53 :offset => @offset
54
54
55 respond_to do |format|
55 respond_to do |format|
56 format.html {
56 format.html {
57 @groups = Group.all.sort
57 @groups = Group.all.sort
58 render :layout => !request.xhr?
58 render :layout => !request.xhr?
59 }
59 }
60 format.api
60 format.api
61 end
61 end
62 end
62 end
63
63
64 def show
64 def show
65 # show projects based on current user visibility
65 # show projects based on current user visibility
66 @memberships = @user.memberships.all(:conditions => Project.visible_condition(User.current))
66 @memberships = @user.memberships.all(:conditions => Project.visible_condition(User.current))
67
67
68 events = Redmine::Activity::Fetcher.new(User.current, :author => @user).events(nil, nil, :limit => 10)
68 events = Redmine::Activity::Fetcher.new(User.current, :author => @user).events(nil, nil, :limit => 10)
69 @events_by_day = events.group_by(&:event_date)
69 @events_by_day = events.group_by(&:event_date)
70
70
71 unless User.current.admin?
71 unless User.current.admin?
72 if !@user.active? || (@user != User.current && @memberships.empty? && events.empty?)
72 if !@user.active? || (@user != User.current && @memberships.empty? && events.empty?)
73 render_404
73 render_404
74 return
74 return
75 end
75 end
76 end
76 end
77
77
78 respond_to do |format|
78 respond_to do |format|
79 format.html { render :layout => 'base' }
79 format.html { render :layout => 'base' }
80 format.api
80 format.api
81 end
81 end
82 end
82 end
83
83
84 def new
84 def new
85 @user = User.new(:language => Setting.default_language, :mail_notification => Setting.default_notification_option)
85 @user = User.new(:language => Setting.default_language, :mail_notification => Setting.default_notification_option)
86 @auth_sources = AuthSource.find(:all)
86 @auth_sources = AuthSource.find(:all)
87 end
87 end
88
88
89 def create
89 def create
90 @user = User.new(:language => Setting.default_language, :mail_notification => Setting.default_notification_option)
90 @user = User.new(:language => Setting.default_language, :mail_notification => Setting.default_notification_option)
91 @user.safe_attributes = params[:user]
91 @user.safe_attributes = params[:user]
92 @user.admin = params[:user][:admin] || false
92 @user.admin = params[:user][:admin] || false
93 @user.login = params[:user][:login]
93 @user.login = params[:user][:login]
94 @user.password, @user.password_confirmation = params[:user][:password], params[:user][:password_confirmation] unless @user.auth_source_id
94 @user.password, @user.password_confirmation = params[:user][:password], params[:user][:password_confirmation] unless @user.auth_source_id
95
95
96 if @user.save
96 if @user.save
97 @user.pref.attributes = params[:pref]
97 @user.pref.attributes = params[:pref]
98 @user.pref[:no_self_notified] = (params[:no_self_notified] == '1')
98 @user.pref[:no_self_notified] = (params[:no_self_notified] == '1')
99 @user.pref.save
99 @user.pref.save
100 @user.notified_project_ids = (@user.mail_notification == 'selected' ? params[:notified_project_ids] : [])
100 @user.notified_project_ids = (@user.mail_notification == 'selected' ? params[:notified_project_ids] : [])
101
101
102 Mailer.deliver_account_information(@user, params[:user][:password]) if params[:send_information]
102 Mailer.account_information(@user, params[:user][:password]).deliver if params[:send_information]
103
103
104 respond_to do |format|
104 respond_to do |format|
105 format.html {
105 format.html {
106 flash[:notice] = l(:notice_successful_create)
106 flash[:notice] = l(:notice_successful_create)
107 redirect_to(params[:continue] ?
107 redirect_to(params[:continue] ?
108 {:controller => 'users', :action => 'new'} :
108 {:controller => 'users', :action => 'new'} :
109 {:controller => 'users', :action => 'edit', :id => @user}
109 {:controller => 'users', :action => 'edit', :id => @user}
110 )
110 )
111 }
111 }
112 format.api { render :action => 'show', :status => :created, :location => user_url(@user) }
112 format.api { render :action => 'show', :status => :created, :location => user_url(@user) }
113 end
113 end
114 else
114 else
115 @auth_sources = AuthSource.find(:all)
115 @auth_sources = AuthSource.find(:all)
116 # Clear password input
116 # Clear password input
117 @user.password = @user.password_confirmation = nil
117 @user.password = @user.password_confirmation = nil
118
118
119 respond_to do |format|
119 respond_to do |format|
120 format.html { render :action => 'new' }
120 format.html { render :action => 'new' }
121 format.api { render_validation_errors(@user) }
121 format.api { render_validation_errors(@user) }
122 end
122 end
123 end
123 end
124 end
124 end
125
125
126 def edit
126 def edit
127 @auth_sources = AuthSource.find(:all)
127 @auth_sources = AuthSource.find(:all)
128 @membership ||= Member.new
128 @membership ||= Member.new
129 end
129 end
130
130
131 def update
131 def update
132 @user.admin = params[:user][:admin] if params[:user][:admin]
132 @user.admin = params[:user][:admin] if params[:user][:admin]
133 @user.login = params[:user][:login] if params[:user][:login]
133 @user.login = params[:user][:login] if params[:user][:login]
134 if params[:user][:password].present? && (@user.auth_source_id.nil? || params[:user][:auth_source_id].blank?)
134 if params[:user][:password].present? && (@user.auth_source_id.nil? || params[:user][:auth_source_id].blank?)
135 @user.password, @user.password_confirmation = params[:user][:password], params[:user][:password_confirmation]
135 @user.password, @user.password_confirmation = params[:user][:password], params[:user][:password_confirmation]
136 end
136 end
137 @user.safe_attributes = params[:user]
137 @user.safe_attributes = params[:user]
138 # Was the account actived ? (do it before User#save clears the change)
138 # Was the account actived ? (do it before User#save clears the change)
139 was_activated = (@user.status_change == [User::STATUS_REGISTERED, User::STATUS_ACTIVE])
139 was_activated = (@user.status_change == [User::STATUS_REGISTERED, User::STATUS_ACTIVE])
140 # TODO: Similar to My#account
140 # TODO: Similar to My#account
141 @user.pref.attributes = params[:pref]
141 @user.pref.attributes = params[:pref]
142 @user.pref[:no_self_notified] = (params[:no_self_notified] == '1')
142 @user.pref[:no_self_notified] = (params[:no_self_notified] == '1')
143
143
144 if @user.save
144 if @user.save
145 @user.pref.save
145 @user.pref.save
146 @user.notified_project_ids = (@user.mail_notification == 'selected' ? params[:notified_project_ids] : [])
146 @user.notified_project_ids = (@user.mail_notification == 'selected' ? params[:notified_project_ids] : [])
147
147
148 if was_activated
148 if was_activated
149 Mailer.deliver_account_activated(@user)
149 Mailer.account_activated(@user).deliver
150 elsif @user.active? && params[:send_information] && !params[:user][:password].blank? && @user.auth_source_id.nil?
150 elsif @user.active? && params[:send_information] && !params[:user][:password].blank? && @user.auth_source_id.nil?
151 Mailer.deliver_account_information(@user, params[:user][:password])
151 Mailer.account_information(@user, params[:user][:password]).deliver
152 end
152 end
153
153
154 respond_to do |format|
154 respond_to do |format|
155 format.html {
155 format.html {
156 flash[:notice] = l(:notice_successful_update)
156 flash[:notice] = l(:notice_successful_update)
157 redirect_to_referer_or edit_user_path(@user)
157 redirect_to_referer_or edit_user_path(@user)
158 }
158 }
159 format.api { head :ok }
159 format.api { head :ok }
160 end
160 end
161 else
161 else
162 @auth_sources = AuthSource.find(:all)
162 @auth_sources = AuthSource.find(:all)
163 @membership ||= Member.new
163 @membership ||= Member.new
164 # Clear password input
164 # Clear password input
165 @user.password = @user.password_confirmation = nil
165 @user.password = @user.password_confirmation = nil
166
166
167 respond_to do |format|
167 respond_to do |format|
168 format.html { render :action => :edit }
168 format.html { render :action => :edit }
169 format.api { render_validation_errors(@user) }
169 format.api { render_validation_errors(@user) }
170 end
170 end
171 end
171 end
172 end
172 end
173
173
174 def destroy
174 def destroy
175 @user.destroy
175 @user.destroy
176 respond_to do |format|
176 respond_to do |format|
177 format.html { redirect_to(users_url) }
177 format.html { redirect_to(users_url) }
178 format.api { head :ok }
178 format.api { head :ok }
179 end
179 end
180 end
180 end
181
181
182 def edit_membership
182 def edit_membership
183 @membership = Member.edit_membership(params[:membership_id], params[:membership], @user)
183 @membership = Member.edit_membership(params[:membership_id], params[:membership], @user)
184 @membership.save
184 @membership.save
185 respond_to do |format|
185 respond_to do |format|
186 if @membership.valid?
186 if @membership.valid?
187 format.html { redirect_to :controller => 'users', :action => 'edit', :id => @user, :tab => 'memberships' }
187 format.html { redirect_to :controller => 'users', :action => 'edit', :id => @user, :tab => 'memberships' }
188 format.js {
188 format.js {
189 render(:update) {|page|
189 render(:update) {|page|
190 page.replace_html "tab-content-memberships", :partial => 'users/memberships'
190 page.replace_html "tab-content-memberships", :partial => 'users/memberships'
191 page.visual_effect(:highlight, "member-#{@membership.id}")
191 page.visual_effect(:highlight, "member-#{@membership.id}")
192 }
192 }
193 }
193 }
194 else
194 else
195 format.js {
195 format.js {
196 render(:update) {|page|
196 render(:update) {|page|
197 page.alert(l(:notice_failed_to_save_members, :errors => @membership.errors.full_messages.join(', ')))
197 page.alert(l(:notice_failed_to_save_members, :errors => @membership.errors.full_messages.join(', ')))
198 }
198 }
199 }
199 }
200 end
200 end
201 end
201 end
202 end
202 end
203
203
204 def destroy_membership
204 def destroy_membership
205 @membership = Member.find(params[:membership_id])
205 @membership = Member.find(params[:membership_id])
206 if @membership.deletable?
206 if @membership.deletable?
207 @membership.destroy
207 @membership.destroy
208 end
208 end
209 respond_to do |format|
209 respond_to do |format|
210 format.html { redirect_to :controller => 'users', :action => 'edit', :id => @user, :tab => 'memberships' }
210 format.html { redirect_to :controller => 'users', :action => 'edit', :id => @user, :tab => 'memberships' }
211 format.js { render(:update) {|page| page.replace_html "tab-content-memberships", :partial => 'users/memberships'} }
211 format.js { render(:update) {|page| page.replace_html "tab-content-memberships", :partial => 'users/memberships'} }
212 end
212 end
213 end
213 end
214
214
215 private
215 private
216
216
217 def find_user
217 def find_user
218 if params[:id] == 'current'
218 if params[:id] == 'current'
219 require_login || return
219 require_login || return
220 @user = User.current
220 @user = User.current
221 else
221 else
222 @user = User.find(params[:id])
222 @user = User.find(params[:id])
223 end
223 end
224 rescue ActiveRecord::RecordNotFound
224 rescue ActiveRecord::RecordNotFound
225 render_404
225 render_404
226 end
226 end
227 end
227 end
@@ -1,24 +1,24
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 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 CommentObserver < ActiveRecord::Observer
18 class CommentObserver < ActiveRecord::Observer
19 def after_create(comment)
19 def after_create(comment)
20 if comment.commented.is_a?(News) && Setting.notified_events.include?('news_comment_added')
20 if comment.commented.is_a?(News) && Setting.notified_events.include?('news_comment_added')
21 Mailer.deliver_news_comment_added(comment)
21 Mailer.news_comment_added(comment).deliver
22 end
22 end
23 end
23 end
24 end
24 end
@@ -1,22 +1,22
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 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 DocumentObserver < ActiveRecord::Observer
18 class DocumentObserver < ActiveRecord::Observer
19 def after_create(document)
19 def after_create(document)
20 Mailer.deliver_document_added(document) if Setting.notified_events.include?('document_added')
20 Mailer.document_added(document).deliver if Setting.notified_events.include?('document_added')
21 end
21 end
22 end
22 end
@@ -1,22 +1,22
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 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 IssueObserver < ActiveRecord::Observer
18 class IssueObserver < ActiveRecord::Observer
19 def after_create(issue)
19 def after_create(issue)
20 Mailer.deliver_issue_add(issue) if Setting.notified_events.include?('issue_added')
20 Mailer.issue_add(issue).deliver if Setting.notified_events.include?('issue_added')
21 end
21 end
22 end
22 end
@@ -1,29 +1,29
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 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 JournalObserver < ActiveRecord::Observer
18 class JournalObserver < ActiveRecord::Observer
19 def after_create(journal)
19 def after_create(journal)
20 if journal.notify? &&
20 if journal.notify? &&
21 (Setting.notified_events.include?('issue_updated') ||
21 (Setting.notified_events.include?('issue_updated') ||
22 (Setting.notified_events.include?('issue_note_added') && journal.notes.present?) ||
22 (Setting.notified_events.include?('issue_note_added') && journal.notes.present?) ||
23 (Setting.notified_events.include?('issue_status_updated') && journal.new_status.present?) ||
23 (Setting.notified_events.include?('issue_status_updated') && journal.new_status.present?) ||
24 (Setting.notified_events.include?('issue_priority_updated') && journal.new_value_for('priority_id').present?)
24 (Setting.notified_events.include?('issue_priority_updated') && journal.new_value_for('priority_id').present?)
25 )
25 )
26 Mailer.deliver_issue_edit(journal)
26 Mailer.issue_edit(journal).deliver
27 end
27 end
28 end
28 end
29 end
29 end
@@ -1,459 +1,459
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 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 include ActionView::Helpers::SanitizeHelper
19 include ActionView::Helpers::SanitizeHelper
20 include Redmine::I18n
20 include Redmine::I18n
21
21
22 class UnauthorizedAction < StandardError; end
22 class UnauthorizedAction < StandardError; end
23 class MissingInformation < StandardError; end
23 class MissingInformation < StandardError; end
24
24
25 attr_reader :email, :user
25 attr_reader :email, :user
26
26
27 def self.receive(email, options={})
27 def self.receive(email, options={})
28 @@handler_options = options.dup
28 @@handler_options = options.dup
29
29
30 @@handler_options[:issue] ||= {}
30 @@handler_options[:issue] ||= {}
31
31
32 if @@handler_options[:allow_override].is_a?(String)
32 if @@handler_options[:allow_override].is_a?(String)
33 @@handler_options[:allow_override] = @@handler_options[:allow_override].split(',').collect(&:strip)
33 @@handler_options[:allow_override] = @@handler_options[:allow_override].split(',').collect(&:strip)
34 end
34 end
35 @@handler_options[:allow_override] ||= []
35 @@handler_options[:allow_override] ||= []
36 # Project needs to be overridable if not specified
36 # Project needs to be overridable if not specified
37 @@handler_options[:allow_override] << 'project' unless @@handler_options[:issue].has_key?(:project)
37 @@handler_options[:allow_override] << 'project' unless @@handler_options[:issue].has_key?(:project)
38 # Status overridable by default
38 # Status overridable by default
39 @@handler_options[:allow_override] << 'status' unless @@handler_options[:issue].has_key?(:status)
39 @@handler_options[:allow_override] << 'status' unless @@handler_options[:issue].has_key?(:status)
40
40
41 @@handler_options[:no_permission_check] = (@@handler_options[:no_permission_check].to_s == '1' ? true : false)
41 @@handler_options[:no_permission_check] = (@@handler_options[:no_permission_check].to_s == '1' ? true : false)
42
42
43 email.force_encoding('ASCII-8BIT') if email.respond_to?(:force_encoding)
43 email.force_encoding('ASCII-8BIT') if email.respond_to?(:force_encoding)
44 super(email)
44 super(email)
45 end
45 end
46
46
47 def logger
47 def logger
48 Rails.logger
48 Rails.logger
49 end
49 end
50
50
51 cattr_accessor :ignored_emails_headers
51 cattr_accessor :ignored_emails_headers
52 @@ignored_emails_headers = {
52 @@ignored_emails_headers = {
53 'X-Auto-Response-Suppress' => 'OOF',
53 'X-Auto-Response-Suppress' => 'OOF',
54 'Auto-Submitted' => 'auto-replied'
54 'Auto-Submitted' => 'auto-replied'
55 }
55 }
56
56
57 # Processes incoming emails
57 # Processes incoming emails
58 # Returns the created object (eg. an issue, a message) or false
58 # Returns the created object (eg. an issue, a message) or false
59 def receive(email)
59 def receive(email)
60 @email = email
60 @email = email
61 sender_email = email.from.to_a.first.to_s.strip
61 sender_email = email.from.to_a.first.to_s.strip
62 # Ignore emails received from the application emission address to avoid hell cycles
62 # Ignore emails received from the application emission address to avoid hell cycles
63 if sender_email.downcase == Setting.mail_from.to_s.strip.downcase
63 if sender_email.downcase == Setting.mail_from.to_s.strip.downcase
64 if logger && logger.info
64 if logger && logger.info
65 logger.info "MailHandler: ignoring email from Redmine emission address [#{sender_email}]"
65 logger.info "MailHandler: ignoring email from Redmine emission address [#{sender_email}]"
66 end
66 end
67 return false
67 return false
68 end
68 end
69 # Ignore auto generated emails
69 # Ignore auto generated emails
70 self.class.ignored_emails_headers.each do |key, ignored_value|
70 self.class.ignored_emails_headers.each do |key, ignored_value|
71 value = email.header[key]
71 value = email.header[key]
72 if value && value.to_s.downcase == ignored_value.downcase
72 if value && value.to_s.downcase == ignored_value.downcase
73 if logger && logger.info
73 if logger && logger.info
74 logger.info "MailHandler: ignoring email with #{key}:#{value} header"
74 logger.info "MailHandler: ignoring email with #{key}:#{value} header"
75 end
75 end
76 return false
76 return false
77 end
77 end
78 end
78 end
79 @user = User.find_by_mail(sender_email) if sender_email.present?
79 @user = User.find_by_mail(sender_email) if sender_email.present?
80 if @user && !@user.active?
80 if @user && !@user.active?
81 if logger && logger.info
81 if logger && logger.info
82 logger.info "MailHandler: ignoring email from non-active user [#{@user.login}]"
82 logger.info "MailHandler: ignoring email from non-active user [#{@user.login}]"
83 end
83 end
84 return false
84 return false
85 end
85 end
86 if @user.nil?
86 if @user.nil?
87 # Email was submitted by an unknown user
87 # Email was submitted by an unknown user
88 case @@handler_options[:unknown_user]
88 case @@handler_options[:unknown_user]
89 when 'accept'
89 when 'accept'
90 @user = User.anonymous
90 @user = User.anonymous
91 when 'create'
91 when 'create'
92 @user = create_user_from_email
92 @user = create_user_from_email
93 if @user
93 if @user
94 if logger && logger.info
94 if logger && logger.info
95 logger.info "MailHandler: [#{@user.login}] account created"
95 logger.info "MailHandler: [#{@user.login}] account created"
96 end
96 end
97 Mailer.deliver_account_information(@user, @user.password)
97 Mailer.account_information(@user, @user.password).deliver
98 else
98 else
99 if logger && logger.error
99 if logger && logger.error
100 logger.error "MailHandler: could not create account for [#{sender_email}]"
100 logger.error "MailHandler: could not create account for [#{sender_email}]"
101 end
101 end
102 return false
102 return false
103 end
103 end
104 else
104 else
105 # Default behaviour, emails from unknown users are ignored
105 # Default behaviour, emails from unknown users are ignored
106 if logger && logger.info
106 if logger && logger.info
107 logger.info "MailHandler: ignoring email from unknown user [#{sender_email}]"
107 logger.info "MailHandler: ignoring email from unknown user [#{sender_email}]"
108 end
108 end
109 return false
109 return false
110 end
110 end
111 end
111 end
112 User.current = @user
112 User.current = @user
113 dispatch
113 dispatch
114 end
114 end
115
115
116 private
116 private
117
117
118 MESSAGE_ID_RE = %r{^<?redmine\.([a-z0-9_]+)\-(\d+)\.\d+@}
118 MESSAGE_ID_RE = %r{^<?redmine\.([a-z0-9_]+)\-(\d+)\.\d+@}
119 ISSUE_REPLY_SUBJECT_RE = %r{\[[^\]]*#(\d+)\]}
119 ISSUE_REPLY_SUBJECT_RE = %r{\[[^\]]*#(\d+)\]}
120 MESSAGE_REPLY_SUBJECT_RE = %r{\[[^\]]*msg(\d+)\]}
120 MESSAGE_REPLY_SUBJECT_RE = %r{\[[^\]]*msg(\d+)\]}
121
121
122 def dispatch
122 def dispatch
123 headers = [email.in_reply_to, email.references].flatten.compact
123 headers = [email.in_reply_to, email.references].flatten.compact
124 if headers.detect {|h| h.to_s =~ MESSAGE_ID_RE}
124 if headers.detect {|h| h.to_s =~ MESSAGE_ID_RE}
125 klass, object_id = $1, $2.to_i
125 klass, object_id = $1, $2.to_i
126 method_name = "receive_#{klass}_reply"
126 method_name = "receive_#{klass}_reply"
127 if self.class.private_instance_methods.collect(&:to_s).include?(method_name)
127 if self.class.private_instance_methods.collect(&:to_s).include?(method_name)
128 send method_name, object_id
128 send method_name, object_id
129 else
129 else
130 # ignoring it
130 # ignoring it
131 end
131 end
132 elsif m = email.subject.match(ISSUE_REPLY_SUBJECT_RE)
132 elsif m = email.subject.match(ISSUE_REPLY_SUBJECT_RE)
133 receive_issue_reply(m[1].to_i)
133 receive_issue_reply(m[1].to_i)
134 elsif m = email.subject.match(MESSAGE_REPLY_SUBJECT_RE)
134 elsif m = email.subject.match(MESSAGE_REPLY_SUBJECT_RE)
135 receive_message_reply(m[1].to_i)
135 receive_message_reply(m[1].to_i)
136 else
136 else
137 dispatch_to_default
137 dispatch_to_default
138 end
138 end
139 rescue ActiveRecord::RecordInvalid => e
139 rescue ActiveRecord::RecordInvalid => e
140 # TODO: send a email to the user
140 # TODO: send a email to the user
141 logger.error e.message if logger
141 logger.error e.message if logger
142 false
142 false
143 rescue MissingInformation => e
143 rescue MissingInformation => e
144 logger.error "MailHandler: missing information from #{user}: #{e.message}" if logger
144 logger.error "MailHandler: missing information from #{user}: #{e.message}" if logger
145 false
145 false
146 rescue UnauthorizedAction => e
146 rescue UnauthorizedAction => e
147 logger.error "MailHandler: unauthorized attempt from #{user}" if logger
147 logger.error "MailHandler: unauthorized attempt from #{user}" if logger
148 false
148 false
149 end
149 end
150
150
151 def dispatch_to_default
151 def dispatch_to_default
152 receive_issue
152 receive_issue
153 end
153 end
154
154
155 # Creates a new issue
155 # Creates a new issue
156 def receive_issue
156 def receive_issue
157 project = target_project
157 project = target_project
158 # check permission
158 # check permission
159 unless @@handler_options[:no_permission_check]
159 unless @@handler_options[:no_permission_check]
160 raise UnauthorizedAction unless user.allowed_to?(:add_issues, project)
160 raise UnauthorizedAction unless user.allowed_to?(:add_issues, project)
161 end
161 end
162
162
163 issue = Issue.new(:author => user, :project => project)
163 issue = Issue.new(:author => user, :project => project)
164 issue.safe_attributes = issue_attributes_from_keywords(issue)
164 issue.safe_attributes = issue_attributes_from_keywords(issue)
165 issue.safe_attributes = {'custom_field_values' => custom_field_values_from_keywords(issue)}
165 issue.safe_attributes = {'custom_field_values' => custom_field_values_from_keywords(issue)}
166 issue.subject = email.subject.to_s.chomp[0,255]
166 issue.subject = email.subject.to_s.chomp[0,255]
167 if issue.subject.blank?
167 if issue.subject.blank?
168 issue.subject = '(no subject)'
168 issue.subject = '(no subject)'
169 end
169 end
170 issue.description = cleaned_up_text_body
170 issue.description = cleaned_up_text_body
171
171
172 # add To and Cc as watchers before saving so the watchers can reply to Redmine
172 # add To and Cc as watchers before saving so the watchers can reply to Redmine
173 add_watchers(issue)
173 add_watchers(issue)
174 issue.save!
174 issue.save!
175 add_attachments(issue)
175 add_attachments(issue)
176 logger.info "MailHandler: issue ##{issue.id} created by #{user}" if logger && logger.info
176 logger.info "MailHandler: issue ##{issue.id} created by #{user}" if logger && logger.info
177 issue
177 issue
178 end
178 end
179
179
180 # Adds a note to an existing issue
180 # Adds a note to an existing issue
181 def receive_issue_reply(issue_id)
181 def receive_issue_reply(issue_id)
182 issue = Issue.find_by_id(issue_id)
182 issue = Issue.find_by_id(issue_id)
183 return unless issue
183 return unless issue
184 # check permission
184 # check permission
185 unless @@handler_options[:no_permission_check]
185 unless @@handler_options[:no_permission_check]
186 unless user.allowed_to?(:add_issue_notes, issue.project) ||
186 unless user.allowed_to?(:add_issue_notes, issue.project) ||
187 user.allowed_to?(:edit_issues, issue.project)
187 user.allowed_to?(:edit_issues, issue.project)
188 raise UnauthorizedAction
188 raise UnauthorizedAction
189 end
189 end
190 end
190 end
191
191
192 # ignore CLI-supplied defaults for new issues
192 # ignore CLI-supplied defaults for new issues
193 @@handler_options[:issue].clear
193 @@handler_options[:issue].clear
194
194
195 journal = issue.init_journal(user)
195 journal = issue.init_journal(user)
196 issue.safe_attributes = issue_attributes_from_keywords(issue)
196 issue.safe_attributes = issue_attributes_from_keywords(issue)
197 issue.safe_attributes = {'custom_field_values' => custom_field_values_from_keywords(issue)}
197 issue.safe_attributes = {'custom_field_values' => custom_field_values_from_keywords(issue)}
198 journal.notes = cleaned_up_text_body
198 journal.notes = cleaned_up_text_body
199 add_attachments(issue)
199 add_attachments(issue)
200 issue.save!
200 issue.save!
201 if logger && logger.info
201 if logger && logger.info
202 logger.info "MailHandler: issue ##{issue.id} updated by #{user}"
202 logger.info "MailHandler: issue ##{issue.id} updated by #{user}"
203 end
203 end
204 journal
204 journal
205 end
205 end
206
206
207 # Reply will be added to the issue
207 # Reply will be added to the issue
208 def receive_journal_reply(journal_id)
208 def receive_journal_reply(journal_id)
209 journal = Journal.find_by_id(journal_id)
209 journal = Journal.find_by_id(journal_id)
210 if journal && journal.journalized_type == 'Issue'
210 if journal && journal.journalized_type == 'Issue'
211 receive_issue_reply(journal.journalized_id)
211 receive_issue_reply(journal.journalized_id)
212 end
212 end
213 end
213 end
214
214
215 # Receives a reply to a forum message
215 # Receives a reply to a forum message
216 def receive_message_reply(message_id)
216 def receive_message_reply(message_id)
217 message = Message.find_by_id(message_id)
217 message = Message.find_by_id(message_id)
218 if message
218 if message
219 message = message.root
219 message = message.root
220
220
221 unless @@handler_options[:no_permission_check]
221 unless @@handler_options[:no_permission_check]
222 raise UnauthorizedAction unless user.allowed_to?(:add_messages, message.project)
222 raise UnauthorizedAction unless user.allowed_to?(:add_messages, message.project)
223 end
223 end
224
224
225 if !message.locked?
225 if !message.locked?
226 reply = Message.new(:subject => email.subject.gsub(%r{^.*msg\d+\]}, '').strip,
226 reply = Message.new(:subject => email.subject.gsub(%r{^.*msg\d+\]}, '').strip,
227 :content => cleaned_up_text_body)
227 :content => cleaned_up_text_body)
228 reply.author = user
228 reply.author = user
229 reply.board = message.board
229 reply.board = message.board
230 message.children << reply
230 message.children << reply
231 add_attachments(reply)
231 add_attachments(reply)
232 reply
232 reply
233 else
233 else
234 if logger && logger.info
234 if logger && logger.info
235 logger.info "MailHandler: ignoring reply from [#{sender_email}] to a locked topic"
235 logger.info "MailHandler: ignoring reply from [#{sender_email}] to a locked topic"
236 end
236 end
237 end
237 end
238 end
238 end
239 end
239 end
240
240
241 def add_attachments(obj)
241 def add_attachments(obj)
242 if email.attachments && email.attachments.any?
242 if email.attachments && email.attachments.any?
243 email.attachments.each do |attachment|
243 email.attachments.each do |attachment|
244 obj.attachments << Attachment.create(:container => obj,
244 obj.attachments << Attachment.create(:container => obj,
245 :file => attachment.decoded,
245 :file => attachment.decoded,
246 :filename => attachment.filename,
246 :filename => attachment.filename,
247 :author => user,
247 :author => user,
248 :content_type => attachment.mime_type)
248 :content_type => attachment.mime_type)
249 end
249 end
250 end
250 end
251 end
251 end
252
252
253 # Adds To and Cc as watchers of the given object if the sender has the
253 # Adds To and Cc as watchers of the given object if the sender has the
254 # appropriate permission
254 # appropriate permission
255 def add_watchers(obj)
255 def add_watchers(obj)
256 if user.allowed_to?("add_#{obj.class.name.underscore}_watchers".to_sym, obj.project)
256 if user.allowed_to?("add_#{obj.class.name.underscore}_watchers".to_sym, obj.project)
257 addresses = [email.to, email.cc].flatten.compact.uniq.collect {|a| a.strip.downcase}
257 addresses = [email.to, email.cc].flatten.compact.uniq.collect {|a| a.strip.downcase}
258 unless addresses.empty?
258 unless addresses.empty?
259 watchers = User.active.find(:all, :conditions => ['LOWER(mail) IN (?)', addresses])
259 watchers = User.active.find(:all, :conditions => ['LOWER(mail) IN (?)', addresses])
260 watchers.each {|w| obj.add_watcher(w)}
260 watchers.each {|w| obj.add_watcher(w)}
261 end
261 end
262 end
262 end
263 end
263 end
264
264
265 def get_keyword(attr, options={})
265 def get_keyword(attr, options={})
266 @keywords ||= {}
266 @keywords ||= {}
267 if @keywords.has_key?(attr)
267 if @keywords.has_key?(attr)
268 @keywords[attr]
268 @keywords[attr]
269 else
269 else
270 @keywords[attr] = begin
270 @keywords[attr] = begin
271 if (options[:override] || @@handler_options[:allow_override].include?(attr.to_s)) &&
271 if (options[:override] || @@handler_options[:allow_override].include?(attr.to_s)) &&
272 (v = extract_keyword!(plain_text_body, attr, options[:format]))
272 (v = extract_keyword!(plain_text_body, attr, options[:format]))
273 v
273 v
274 elsif !@@handler_options[:issue][attr].blank?
274 elsif !@@handler_options[:issue][attr].blank?
275 @@handler_options[:issue][attr]
275 @@handler_options[:issue][attr]
276 end
276 end
277 end
277 end
278 end
278 end
279 end
279 end
280
280
281 # Destructively extracts the value for +attr+ in +text+
281 # Destructively extracts the value for +attr+ in +text+
282 # Returns nil if no matching keyword found
282 # Returns nil if no matching keyword found
283 def extract_keyword!(text, attr, format=nil)
283 def extract_keyword!(text, attr, format=nil)
284 keys = [attr.to_s.humanize]
284 keys = [attr.to_s.humanize]
285 if attr.is_a?(Symbol)
285 if attr.is_a?(Symbol)
286 if user && user.language.present?
286 if user && user.language.present?
287 keys << l("field_#{attr}", :default => '', :locale => user.language)
287 keys << l("field_#{attr}", :default => '', :locale => user.language)
288 end
288 end
289 if Setting.default_language.present?
289 if Setting.default_language.present?
290 keys << l("field_#{attr}", :default => '', :locale => Setting.default_language)
290 keys << l("field_#{attr}", :default => '', :locale => Setting.default_language)
291 end
291 end
292 end
292 end
293 keys.reject! {|k| k.blank?}
293 keys.reject! {|k| k.blank?}
294 keys.collect! {|k| Regexp.escape(k)}
294 keys.collect! {|k| Regexp.escape(k)}
295 format ||= '.+'
295 format ||= '.+'
296 keyword = nil
296 keyword = nil
297 regexp = /^(#{keys.join('|')})[ \t]*:[ \t]*(#{format})\s*$/i
297 regexp = /^(#{keys.join('|')})[ \t]*:[ \t]*(#{format})\s*$/i
298 if m = text.match(regexp)
298 if m = text.match(regexp)
299 keyword = m[2].strip
299 keyword = m[2].strip
300 text.gsub!(regexp, '')
300 text.gsub!(regexp, '')
301 end
301 end
302 keyword
302 keyword
303 end
303 end
304
304
305 def target_project
305 def target_project
306 # TODO: other ways to specify project:
306 # TODO: other ways to specify project:
307 # * parse the email To field
307 # * parse the email To field
308 # * specific project (eg. Setting.mail_handler_target_project)
308 # * specific project (eg. Setting.mail_handler_target_project)
309 target = Project.find_by_identifier(get_keyword(:project))
309 target = Project.find_by_identifier(get_keyword(:project))
310 raise MissingInformation.new('Unable to determine target project') if target.nil?
310 raise MissingInformation.new('Unable to determine target project') if target.nil?
311 target
311 target
312 end
312 end
313
313
314 # Returns a Hash of issue attributes extracted from keywords in the email body
314 # Returns a Hash of issue attributes extracted from keywords in the email body
315 def issue_attributes_from_keywords(issue)
315 def issue_attributes_from_keywords(issue)
316 assigned_to = (k = get_keyword(:assigned_to, :override => true)) && find_assignee_from_keyword(k, issue)
316 assigned_to = (k = get_keyword(:assigned_to, :override => true)) && find_assignee_from_keyword(k, issue)
317
317
318 attrs = {
318 attrs = {
319 'tracker_id' => (k = get_keyword(:tracker)) && issue.project.trackers.named(k).first.try(:id),
319 'tracker_id' => (k = get_keyword(:tracker)) && issue.project.trackers.named(k).first.try(:id),
320 'status_id' => (k = get_keyword(:status)) && IssueStatus.named(k).first.try(:id),
320 'status_id' => (k = get_keyword(:status)) && IssueStatus.named(k).first.try(:id),
321 'priority_id' => (k = get_keyword(:priority)) && IssuePriority.named(k).first.try(:id),
321 'priority_id' => (k = get_keyword(:priority)) && IssuePriority.named(k).first.try(:id),
322 'category_id' => (k = get_keyword(:category)) && issue.project.issue_categories.named(k).first.try(:id),
322 'category_id' => (k = get_keyword(:category)) && issue.project.issue_categories.named(k).first.try(:id),
323 'assigned_to_id' => assigned_to.try(:id),
323 'assigned_to_id' => assigned_to.try(:id),
324 'fixed_version_id' => (k = get_keyword(:fixed_version, :override => true)) &&
324 'fixed_version_id' => (k = get_keyword(:fixed_version, :override => true)) &&
325 issue.project.shared_versions.named(k).first.try(:id),
325 issue.project.shared_versions.named(k).first.try(:id),
326 'start_date' => get_keyword(:start_date, :override => true, :format => '\d{4}-\d{2}-\d{2}'),
326 'start_date' => get_keyword(:start_date, :override => true, :format => '\d{4}-\d{2}-\d{2}'),
327 'due_date' => get_keyword(:due_date, :override => true, :format => '\d{4}-\d{2}-\d{2}'),
327 'due_date' => get_keyword(:due_date, :override => true, :format => '\d{4}-\d{2}-\d{2}'),
328 'estimated_hours' => get_keyword(:estimated_hours, :override => true),
328 'estimated_hours' => get_keyword(:estimated_hours, :override => true),
329 'done_ratio' => get_keyword(:done_ratio, :override => true, :format => '(\d|10)?0')
329 'done_ratio' => get_keyword(:done_ratio, :override => true, :format => '(\d|10)?0')
330 }.delete_if {|k, v| v.blank? }
330 }.delete_if {|k, v| v.blank? }
331
331
332 if issue.new_record? && attrs['tracker_id'].nil?
332 if issue.new_record? && attrs['tracker_id'].nil?
333 attrs['tracker_id'] = issue.project.trackers.find(:first).try(:id)
333 attrs['tracker_id'] = issue.project.trackers.find(:first).try(:id)
334 end
334 end
335
335
336 attrs
336 attrs
337 end
337 end
338
338
339 # Returns a Hash of issue custom field values extracted from keywords in the email body
339 # Returns a Hash of issue custom field values extracted from keywords in the email body
340 def custom_field_values_from_keywords(customized)
340 def custom_field_values_from_keywords(customized)
341 customized.custom_field_values.inject({}) do |h, v|
341 customized.custom_field_values.inject({}) do |h, v|
342 if value = get_keyword(v.custom_field.name, :override => true)
342 if value = get_keyword(v.custom_field.name, :override => true)
343 h[v.custom_field.id.to_s] = value
343 h[v.custom_field.id.to_s] = value
344 end
344 end
345 h
345 h
346 end
346 end
347 end
347 end
348
348
349 # Returns the text/plain part of the email
349 # Returns the text/plain part of the email
350 # If not found (eg. HTML-only email), returns the body with tags removed
350 # If not found (eg. HTML-only email), returns the body with tags removed
351 def plain_text_body
351 def plain_text_body
352 return @plain_text_body unless @plain_text_body.nil?
352 return @plain_text_body unless @plain_text_body.nil?
353
353
354 part = email.text_part || email.html_part || email
354 part = email.text_part || email.html_part || email
355 @plain_text_body = Redmine::CodesetUtil.to_utf8(part.body.decoded, part.charset)
355 @plain_text_body = Redmine::CodesetUtil.to_utf8(part.body.decoded, part.charset)
356
356
357 if @plain_text_body.respond_to?(:force_encoding)
357 if @plain_text_body.respond_to?(:force_encoding)
358 # @plain_text_body = @plain_text_body.force_encoding(@email.charset).encode("UTF-8")
358 # @plain_text_body = @plain_text_body.force_encoding(@email.charset).encode("UTF-8")
359 end
359 end
360
360
361 # strip html tags and remove doctype directive
361 # strip html tags and remove doctype directive
362 @plain_text_body = strip_tags(@plain_text_body.strip)
362 @plain_text_body = strip_tags(@plain_text_body.strip)
363 @plain_text_body.sub! %r{^<!DOCTYPE .*$}, ''
363 @plain_text_body.sub! %r{^<!DOCTYPE .*$}, ''
364 @plain_text_body
364 @plain_text_body
365 end
365 end
366
366
367 def cleaned_up_text_body
367 def cleaned_up_text_body
368 cleanup_body(plain_text_body)
368 cleanup_body(plain_text_body)
369 end
369 end
370
370
371 def self.full_sanitizer
371 def self.full_sanitizer
372 @full_sanitizer ||= HTML::FullSanitizer.new
372 @full_sanitizer ||= HTML::FullSanitizer.new
373 end
373 end
374
374
375 def self.assign_string_attribute_with_limit(object, attribute, value, limit=nil)
375 def self.assign_string_attribute_with_limit(object, attribute, value, limit=nil)
376 limit ||= object.class.columns_hash[attribute.to_s].limit || 255
376 limit ||= object.class.columns_hash[attribute.to_s].limit || 255
377 value = value.to_s.slice(0, limit)
377 value = value.to_s.slice(0, limit)
378 object.send("#{attribute}=", value)
378 object.send("#{attribute}=", value)
379 end
379 end
380
380
381 # Returns a User from an email address and a full name
381 # Returns a User from an email address and a full name
382 def self.new_user_from_attributes(email_address, fullname=nil)
382 def self.new_user_from_attributes(email_address, fullname=nil)
383 user = User.new
383 user = User.new
384
384
385 # Truncating the email address would result in an invalid format
385 # Truncating the email address would result in an invalid format
386 user.mail = email_address
386 user.mail = email_address
387 assign_string_attribute_with_limit(user, 'login', email_address, User::LOGIN_LENGTH_LIMIT)
387 assign_string_attribute_with_limit(user, 'login', email_address, User::LOGIN_LENGTH_LIMIT)
388
388
389 names = fullname.blank? ? email_address.gsub(/@.*$/, '').split('.') : fullname.split
389 names = fullname.blank? ? email_address.gsub(/@.*$/, '').split('.') : fullname.split
390 assign_string_attribute_with_limit(user, 'firstname', names.shift)
390 assign_string_attribute_with_limit(user, 'firstname', names.shift)
391 assign_string_attribute_with_limit(user, 'lastname', names.join(' '))
391 assign_string_attribute_with_limit(user, 'lastname', names.join(' '))
392 user.lastname = '-' if user.lastname.blank?
392 user.lastname = '-' if user.lastname.blank?
393
393
394 password_length = [Setting.password_min_length.to_i, 10].max
394 password_length = [Setting.password_min_length.to_i, 10].max
395 user.password = Redmine::Utils.random_hex(password_length / 2 + 1)
395 user.password = Redmine::Utils.random_hex(password_length / 2 + 1)
396 user.language = Setting.default_language
396 user.language = Setting.default_language
397
397
398 unless user.valid?
398 unless user.valid?
399 user.login = "user#{Redmine::Utils.random_hex(6)}" unless user.errors[:login].blank?
399 user.login = "user#{Redmine::Utils.random_hex(6)}" unless user.errors[:login].blank?
400 user.firstname = "-" unless user.errors[:firstname].blank?
400 user.firstname = "-" unless user.errors[:firstname].blank?
401 user.lastname = "-" unless user.errors[:lastname].blank?
401 user.lastname = "-" unless user.errors[:lastname].blank?
402 end
402 end
403
403
404 user
404 user
405 end
405 end
406
406
407 # Creates a User for the +email+ sender
407 # Creates a User for the +email+ sender
408 # Returns the user or nil if it could not be created
408 # Returns the user or nil if it could not be created
409 def create_user_from_email
409 def create_user_from_email
410 from = email.header['from'].to_s
410 from = email.header['from'].to_s
411 addr, name = from, nil
411 addr, name = from, nil
412 if m = from.match(/^"?(.+?)"?\s+<(.+@.+)>$/)
412 if m = from.match(/^"?(.+?)"?\s+<(.+@.+)>$/)
413 addr, name = m[2], m[1]
413 addr, name = m[2], m[1]
414 end
414 end
415 if addr.present?
415 if addr.present?
416 user = self.class.new_user_from_attributes(addr, name)
416 user = self.class.new_user_from_attributes(addr, name)
417 if user.save
417 if user.save
418 user
418 user
419 else
419 else
420 logger.error "MailHandler: failed to create User: #{user.errors.full_messages}" if logger
420 logger.error "MailHandler: failed to create User: #{user.errors.full_messages}" if logger
421 nil
421 nil
422 end
422 end
423 else
423 else
424 logger.error "MailHandler: failed to create User: no FROM address found" if logger
424 logger.error "MailHandler: failed to create User: no FROM address found" if logger
425 nil
425 nil
426 end
426 end
427 end
427 end
428
428
429 # Removes the email body of text after the truncation configurations.
429 # Removes the email body of text after the truncation configurations.
430 def cleanup_body(body)
430 def cleanup_body(body)
431 delimiters = Setting.mail_handler_body_delimiters.to_s.split(/[\r\n]+/).reject(&:blank?).map {|s| Regexp.escape(s)}
431 delimiters = Setting.mail_handler_body_delimiters.to_s.split(/[\r\n]+/).reject(&:blank?).map {|s| Regexp.escape(s)}
432 unless delimiters.empty?
432 unless delimiters.empty?
433 regex = Regexp.new("^[> ]*(#{ delimiters.join('|') })\s*[\r\n].*", Regexp::MULTILINE)
433 regex = Regexp.new("^[> ]*(#{ delimiters.join('|') })\s*[\r\n].*", Regexp::MULTILINE)
434 body = body.gsub(regex, '')
434 body = body.gsub(regex, '')
435 end
435 end
436 body.strip
436 body.strip
437 end
437 end
438
438
439 def find_assignee_from_keyword(keyword, issue)
439 def find_assignee_from_keyword(keyword, issue)
440 keyword = keyword.to_s.downcase
440 keyword = keyword.to_s.downcase
441 assignable = issue.assignable_users
441 assignable = issue.assignable_users
442 assignee = nil
442 assignee = nil
443 assignee ||= assignable.detect {|a|
443 assignee ||= assignable.detect {|a|
444 a.mail.to_s.downcase == keyword ||
444 a.mail.to_s.downcase == keyword ||
445 a.login.to_s.downcase == keyword
445 a.login.to_s.downcase == keyword
446 }
446 }
447 if assignee.nil? && keyword.match(/ /)
447 if assignee.nil? && keyword.match(/ /)
448 firstname, lastname = *(keyword.split) # "First Last Throwaway"
448 firstname, lastname = *(keyword.split) # "First Last Throwaway"
449 assignee ||= assignable.detect {|a|
449 assignee ||= assignable.detect {|a|
450 a.is_a?(User) && a.firstname.to_s.downcase == firstname &&
450 a.is_a?(User) && a.firstname.to_s.downcase == firstname &&
451 a.lastname.to_s.downcase == lastname
451 a.lastname.to_s.downcase == lastname
452 }
452 }
453 end
453 end
454 if assignee.nil?
454 if assignee.nil?
455 assignee ||= assignable.detect {|a| a.is_a?(Group) && a.name.downcase == keyword}
455 assignee ||= assignable.detect {|a| a.is_a?(Group) && a.name.downcase == keyword}
456 end
456 end
457 assignee
457 assignee
458 end
458 end
459 end
459 end
@@ -1,22 +1,22
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 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 MessageObserver < ActiveRecord::Observer
18 class MessageObserver < ActiveRecord::Observer
19 def after_create(message)
19 def after_create(message)
20 Mailer.deliver_message_posted(message) if Setting.notified_events.include?('message_posted')
20 Mailer.message_posted(message).deliver if Setting.notified_events.include?('message_posted')
21 end
21 end
22 end
22 end
@@ -1,22 +1,22
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 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 NewsObserver < ActiveRecord::Observer
18 class NewsObserver < ActiveRecord::Observer
19 def after_create(news)
19 def after_create(news)
20 Mailer.deliver_news_added(news) if Setting.notified_events.include?('news_added')
20 Mailer.news_added(news).deliver if Setting.notified_events.include?('news_added')
21 end
21 end
22 end
22 end
@@ -1,28 +1,28
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 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 WikiContentObserver < ActiveRecord::Observer
18 class WikiContentObserver < ActiveRecord::Observer
19 def after_create(wiki_content)
19 def after_create(wiki_content)
20 Mailer.deliver_wiki_content_added(wiki_content) if Setting.notified_events.include?('wiki_content_added')
20 Mailer.wiki_content_added(wiki_content).deliver if Setting.notified_events.include?('wiki_content_added')
21 end
21 end
22
22
23 def after_update(wiki_content)
23 def after_update(wiki_content)
24 if wiki_content.text_changed?
24 if wiki_content.text_changed?
25 Mailer.deliver_wiki_content_updated(wiki_content) if Setting.notified_events.include?('wiki_content_updated')
25 Mailer.wiki_content_updated(wiki_content).deliver if Setting.notified_events.include?('wiki_content_updated')
26 end
26 end
27 end
27 end
28 end
28 end
@@ -1,188 +1,188
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 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 namespace :redmine do
18 namespace :redmine do
19 namespace :email do
19 namespace :email do
20
20
21 desc <<-END_DESC
21 desc <<-END_DESC
22 Read an email from standard input.
22 Read an email from standard input.
23
23
24 General options:
24 General options:
25 unknown_user=ACTION how to handle emails from an unknown user
25 unknown_user=ACTION how to handle emails from an unknown user
26 ACTION can be one of the following values:
26 ACTION can be one of the following values:
27 ignore: email is ignored (default)
27 ignore: email is ignored (default)
28 accept: accept as anonymous user
28 accept: accept as anonymous user
29 create: create a user account
29 create: create a user account
30 no_permission_check=1 disable permission checking when receiving
30 no_permission_check=1 disable permission checking when receiving
31 the email
31 the email
32
32
33 Issue attributes control options:
33 Issue attributes control options:
34 project=PROJECT identifier of the target project
34 project=PROJECT identifier of the target project
35 status=STATUS name of the target status
35 status=STATUS name of the target status
36 tracker=TRACKER name of the target tracker
36 tracker=TRACKER name of the target tracker
37 category=CATEGORY name of the target category
37 category=CATEGORY name of the target category
38 priority=PRIORITY name of the target priority
38 priority=PRIORITY name of the target priority
39 allow_override=ATTRS allow email content to override attributes
39 allow_override=ATTRS allow email content to override attributes
40 specified by previous options
40 specified by previous options
41 ATTRS is a comma separated list of attributes
41 ATTRS is a comma separated list of attributes
42
42
43 Examples:
43 Examples:
44 # No project specified. Emails MUST contain the 'Project' keyword:
44 # No project specified. Emails MUST contain the 'Project' keyword:
45 rake redmine:email:read RAILS_ENV="production" < raw_email
45 rake redmine:email:read RAILS_ENV="production" < raw_email
46
46
47 # Fixed project and default tracker specified, but emails can override
47 # Fixed project and default tracker specified, but emails can override
48 # both tracker and priority attributes:
48 # both tracker and priority attributes:
49 rake redmine:email:read RAILS_ENV="production" \\
49 rake redmine:email:read RAILS_ENV="production" \\
50 project=foo \\
50 project=foo \\
51 tracker=bug \\
51 tracker=bug \\
52 allow_override=tracker,priority < raw_email
52 allow_override=tracker,priority < raw_email
53 END_DESC
53 END_DESC
54
54
55 task :read => :environment do
55 task :read => :environment do
56 options = { :issue => {} }
56 options = { :issue => {} }
57 %w(project status tracker category priority).each { |a| options[:issue][a.to_sym] = ENV[a] if ENV[a] }
57 %w(project status tracker category priority).each { |a| options[:issue][a.to_sym] = ENV[a] if ENV[a] }
58 options[:allow_override] = ENV['allow_override'] if ENV['allow_override']
58 options[:allow_override] = ENV['allow_override'] if ENV['allow_override']
59 options[:unknown_user] = ENV['unknown_user'] if ENV['unknown_user']
59 options[:unknown_user] = ENV['unknown_user'] if ENV['unknown_user']
60 options[:no_permission_check] = ENV['no_permission_check'] if ENV['no_permission_check']
60 options[:no_permission_check] = ENV['no_permission_check'] if ENV['no_permission_check']
61
61
62 MailHandler.receive(STDIN.read, options)
62 MailHandler.receive(STDIN.read, options)
63 end
63 end
64
64
65 desc <<-END_DESC
65 desc <<-END_DESC
66 Read emails from an IMAP server.
66 Read emails from an IMAP server.
67
67
68 General options:
68 General options:
69 unknown_user=ACTION how to handle emails from an unknown user
69 unknown_user=ACTION how to handle emails from an unknown user
70 ACTION can be one of the following values:
70 ACTION can be one of the following values:
71 ignore: email is ignored (default)
71 ignore: email is ignored (default)
72 accept: accept as anonymous user
72 accept: accept as anonymous user
73 create: create a user account
73 create: create a user account
74 no_permission_check=1 disable permission checking when receiving
74 no_permission_check=1 disable permission checking when receiving
75 the email
75 the email
76
76
77 Available IMAP options:
77 Available IMAP options:
78 host=HOST IMAP server host (default: 127.0.0.1)
78 host=HOST IMAP server host (default: 127.0.0.1)
79 port=PORT IMAP server port (default: 143)
79 port=PORT IMAP server port (default: 143)
80 ssl=SSL Use SSL? (default: false)
80 ssl=SSL Use SSL? (default: false)
81 username=USERNAME IMAP account
81 username=USERNAME IMAP account
82 password=PASSWORD IMAP password
82 password=PASSWORD IMAP password
83 folder=FOLDER IMAP folder to read (default: INBOX)
83 folder=FOLDER IMAP folder to read (default: INBOX)
84
84
85 Issue attributes control options:
85 Issue attributes control options:
86 project=PROJECT identifier of the target project
86 project=PROJECT identifier of the target project
87 status=STATUS name of the target status
87 status=STATUS name of the target status
88 tracker=TRACKER name of the target tracker
88 tracker=TRACKER name of the target tracker
89 category=CATEGORY name of the target category
89 category=CATEGORY name of the target category
90 priority=PRIORITY name of the target priority
90 priority=PRIORITY name of the target priority
91 allow_override=ATTRS allow email content to override attributes
91 allow_override=ATTRS allow email content to override attributes
92 specified by previous options
92 specified by previous options
93 ATTRS is a comma separated list of attributes
93 ATTRS is a comma separated list of attributes
94
94
95 Processed emails control options:
95 Processed emails control options:
96 move_on_success=MAILBOX move emails that were successfully received
96 move_on_success=MAILBOX move emails that were successfully received
97 to MAILBOX instead of deleting them
97 to MAILBOX instead of deleting them
98 move_on_failure=MAILBOX move emails that were ignored to MAILBOX
98 move_on_failure=MAILBOX move emails that were ignored to MAILBOX
99
99
100 Examples:
100 Examples:
101 # No project specified. Emails MUST contain the 'Project' keyword:
101 # No project specified. Emails MUST contain the 'Project' keyword:
102
102
103 rake redmine:email:receive_imap RAILS_ENV="production" \\
103 rake redmine:email:receive_imap RAILS_ENV="production" \\
104 host=imap.foo.bar username=redmine@example.net password=xxx
104 host=imap.foo.bar username=redmine@example.net password=xxx
105
105
106
106
107 # Fixed project and default tracker specified, but emails can override
107 # Fixed project and default tracker specified, but emails can override
108 # both tracker and priority attributes:
108 # both tracker and priority attributes:
109
109
110 rake redmine:email:receive_imap RAILS_ENV="production" \\
110 rake redmine:email:receive_imap RAILS_ENV="production" \\
111 host=imap.foo.bar username=redmine@example.net password=xxx ssl=1 \\
111 host=imap.foo.bar username=redmine@example.net password=xxx ssl=1 \\
112 project=foo \\
112 project=foo \\
113 tracker=bug \\
113 tracker=bug \\
114 allow_override=tracker,priority
114 allow_override=tracker,priority
115 END_DESC
115 END_DESC
116
116
117 task :receive_imap => :environment do
117 task :receive_imap => :environment do
118 imap_options = {:host => ENV['host'],
118 imap_options = {:host => ENV['host'],
119 :port => ENV['port'],
119 :port => ENV['port'],
120 :ssl => ENV['ssl'],
120 :ssl => ENV['ssl'],
121 :username => ENV['username'],
121 :username => ENV['username'],
122 :password => ENV['password'],
122 :password => ENV['password'],
123 :folder => ENV['folder'],
123 :folder => ENV['folder'],
124 :move_on_success => ENV['move_on_success'],
124 :move_on_success => ENV['move_on_success'],
125 :move_on_failure => ENV['move_on_failure']}
125 :move_on_failure => ENV['move_on_failure']}
126
126
127 options = { :issue => {} }
127 options = { :issue => {} }
128 %w(project status tracker category priority).each { |a| options[:issue][a.to_sym] = ENV[a] if ENV[a] }
128 %w(project status tracker category priority).each { |a| options[:issue][a.to_sym] = ENV[a] if ENV[a] }
129 options[:allow_override] = ENV['allow_override'] if ENV['allow_override']
129 options[:allow_override] = ENV['allow_override'] if ENV['allow_override']
130 options[:unknown_user] = ENV['unknown_user'] if ENV['unknown_user']
130 options[:unknown_user] = ENV['unknown_user'] if ENV['unknown_user']
131 options[:no_permission_check] = ENV['no_permission_check'] if ENV['no_permission_check']
131 options[:no_permission_check] = ENV['no_permission_check'] if ENV['no_permission_check']
132
132
133 Redmine::IMAP.check(imap_options, options)
133 Redmine::IMAP.check(imap_options, options)
134 end
134 end
135
135
136 desc <<-END_DESC
136 desc <<-END_DESC
137 Read emails from an POP3 server.
137 Read emails from an POP3 server.
138
138
139 Available POP3 options:
139 Available POP3 options:
140 host=HOST POP3 server host (default: 127.0.0.1)
140 host=HOST POP3 server host (default: 127.0.0.1)
141 port=PORT POP3 server port (default: 110)
141 port=PORT POP3 server port (default: 110)
142 username=USERNAME POP3 account
142 username=USERNAME POP3 account
143 password=PASSWORD POP3 password
143 password=PASSWORD POP3 password
144 apop=1 use APOP authentication (default: false)
144 apop=1 use APOP authentication (default: false)
145 delete_unprocessed=1 delete messages that could not be processed
145 delete_unprocessed=1 delete messages that could not be processed
146 successfully from the server (default
146 successfully from the server (default
147 behaviour is to leave them on the server)
147 behaviour is to leave them on the server)
148
148
149 See redmine:email:receive_imap for more options and examples.
149 See redmine:email:receive_imap for more options and examples.
150 END_DESC
150 END_DESC
151
151
152 task :receive_pop3 => :environment do
152 task :receive_pop3 => :environment do
153 pop_options = {:host => ENV['host'],
153 pop_options = {:host => ENV['host'],
154 :port => ENV['port'],
154 :port => ENV['port'],
155 :apop => ENV['apop'],
155 :apop => ENV['apop'],
156 :username => ENV['username'],
156 :username => ENV['username'],
157 :password => ENV['password'],
157 :password => ENV['password'],
158 :delete_unprocessed => ENV['delete_unprocessed']}
158 :delete_unprocessed => ENV['delete_unprocessed']}
159
159
160 options = { :issue => {} }
160 options = { :issue => {} }
161 %w(project status tracker category priority).each { |a| options[:issue][a.to_sym] = ENV[a] if ENV[a] }
161 %w(project status tracker category priority).each { |a| options[:issue][a.to_sym] = ENV[a] if ENV[a] }
162 options[:allow_override] = ENV['allow_override'] if ENV['allow_override']
162 options[:allow_override] = ENV['allow_override'] if ENV['allow_override']
163 options[:unknown_user] = ENV['unknown_user'] if ENV['unknown_user']
163 options[:unknown_user] = ENV['unknown_user'] if ENV['unknown_user']
164 options[:no_permission_check] = ENV['no_permission_check'] if ENV['no_permission_check']
164 options[:no_permission_check] = ENV['no_permission_check'] if ENV['no_permission_check']
165
165
166 Redmine::POP3.check(pop_options, options)
166 Redmine::POP3.check(pop_options, options)
167 end
167 end
168
168
169 desc "Send a test email to the user with the provided login name"
169 desc "Send a test email to the user with the provided login name"
170 task :test, [:login] => :environment do |task, args|
170 task :test, [:login] => :environment do |task, args|
171 include Redmine::I18n
171 include Redmine::I18n
172 abort l(:notice_email_error, "Please include the user login to test with. Example: rake redmine:email:test[login]") if args[:login].blank?
172 abort l(:notice_email_error, "Please include the user login to test with. Example: rake redmine:email:test[login]") if args[:login].blank?
173
173
174 user = User.find_by_login(args[:login])
174 user = User.find_by_login(args[:login])
175 abort l(:notice_email_error, "User #{args[:login]} not found") unless user && user.logged?
175 abort l(:notice_email_error, "User #{args[:login]} not found") unless user && user.logged?
176
176
177 ActionMailer::Base.raise_delivery_errors = true
177 ActionMailer::Base.raise_delivery_errors = true
178 begin
178 begin
179 Mailer.with_synched_deliveries do
179 Mailer.with_synched_deliveries do
180 Mailer.deliver_test_email(user)
180 Mailer.test_email(user).deliver
181 end
181 end
182 puts l(:notice_email_sent, user.mail)
182 puts l(:notice_email_sent, user.mail)
183 rescue Exception => e
183 rescue Exception => e
184 abort l(:notice_email_error, e.message)
184 abort l(:notice_email_error, e.message)
185 end
185 end
186 end
186 end
187 end
187 end
188 end
188 end
@@ -1,172 +1,172
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 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 require File.expand_path('../../../../test_helper', __FILE__)
18 require File.expand_path('../../../../test_helper', __FILE__)
19
19
20 class Redmine::Hook::ManagerTest < ActionView::TestCase
20 class Redmine::Hook::ManagerTest < ActionView::TestCase
21
21
22 fixtures :issues
22 fixtures :issues
23
23
24 # Some hooks that are manually registered in these tests
24 # Some hooks that are manually registered in these tests
25 class TestHook < Redmine::Hook::ViewListener; end
25 class TestHook < Redmine::Hook::ViewListener; end
26
26
27 class TestHook1 < TestHook
27 class TestHook1 < TestHook
28 def view_layouts_base_html_head(context)
28 def view_layouts_base_html_head(context)
29 'Test hook 1 listener.'
29 'Test hook 1 listener.'
30 end
30 end
31 end
31 end
32
32
33 class TestHook2 < TestHook
33 class TestHook2 < TestHook
34 def view_layouts_base_html_head(context)
34 def view_layouts_base_html_head(context)
35 'Test hook 2 listener.'
35 'Test hook 2 listener.'
36 end
36 end
37 end
37 end
38
38
39 class TestHook3 < TestHook
39 class TestHook3 < TestHook
40 def view_layouts_base_html_head(context)
40 def view_layouts_base_html_head(context)
41 "Context keys: #{context.keys.collect(&:to_s).sort.join(', ')}."
41 "Context keys: #{context.keys.collect(&:to_s).sort.join(', ')}."
42 end
42 end
43 end
43 end
44
44
45 class TestLinkToHook < TestHook
45 class TestLinkToHook < TestHook
46 def view_layouts_base_html_head(context)
46 def view_layouts_base_html_head(context)
47 link_to('Issues', :controller => 'issues')
47 link_to('Issues', :controller => 'issues')
48 end
48 end
49 end
49 end
50
50
51 class TestHookHelperController < ActionController::Base
51 class TestHookHelperController < ActionController::Base
52 include Redmine::Hook::Helper
52 include Redmine::Hook::Helper
53 end
53 end
54
54
55 class TestHookHelperView < ActionView::Base
55 class TestHookHelperView < ActionView::Base
56 include Redmine::Hook::Helper
56 include Redmine::Hook::Helper
57 end
57 end
58
58
59 Redmine::Hook.clear_listeners
59 Redmine::Hook.clear_listeners
60
60
61 def setup
61 def setup
62 @hook_module = Redmine::Hook
62 @hook_module = Redmine::Hook
63 end
63 end
64
64
65 def teardown
65 def teardown
66 @hook_module.clear_listeners
66 @hook_module.clear_listeners
67 end
67 end
68
68
69 def test_clear_listeners
69 def test_clear_listeners
70 assert_equal 0, @hook_module.hook_listeners(:view_layouts_base_html_head).size
70 assert_equal 0, @hook_module.hook_listeners(:view_layouts_base_html_head).size
71 @hook_module.add_listener(TestHook1)
71 @hook_module.add_listener(TestHook1)
72 @hook_module.add_listener(TestHook2)
72 @hook_module.add_listener(TestHook2)
73 assert_equal 2, @hook_module.hook_listeners(:view_layouts_base_html_head).size
73 assert_equal 2, @hook_module.hook_listeners(:view_layouts_base_html_head).size
74
74
75 @hook_module.clear_listeners
75 @hook_module.clear_listeners
76 assert_equal 0, @hook_module.hook_listeners(:view_layouts_base_html_head).size
76 assert_equal 0, @hook_module.hook_listeners(:view_layouts_base_html_head).size
77 end
77 end
78
78
79 def test_add_listener
79 def test_add_listener
80 assert_equal 0, @hook_module.hook_listeners(:view_layouts_base_html_head).size
80 assert_equal 0, @hook_module.hook_listeners(:view_layouts_base_html_head).size
81 @hook_module.add_listener(TestHook1)
81 @hook_module.add_listener(TestHook1)
82 assert_equal 1, @hook_module.hook_listeners(:view_layouts_base_html_head).size
82 assert_equal 1, @hook_module.hook_listeners(:view_layouts_base_html_head).size
83 end
83 end
84
84
85 def test_call_hook
85 def test_call_hook
86 @hook_module.add_listener(TestHook1)
86 @hook_module.add_listener(TestHook1)
87 assert_equal ['Test hook 1 listener.'], hook_helper.call_hook(:view_layouts_base_html_head)
87 assert_equal ['Test hook 1 listener.'], hook_helper.call_hook(:view_layouts_base_html_head)
88 end
88 end
89
89
90 def test_call_hook_with_context
90 def test_call_hook_with_context
91 @hook_module.add_listener(TestHook3)
91 @hook_module.add_listener(TestHook3)
92 assert_equal ['Context keys: bar, controller, foo, project, request.'],
92 assert_equal ['Context keys: bar, controller, foo, project, request.'],
93 hook_helper.call_hook(:view_layouts_base_html_head, :foo => 1, :bar => 'a')
93 hook_helper.call_hook(:view_layouts_base_html_head, :foo => 1, :bar => 'a')
94 end
94 end
95
95
96 def test_call_hook_with_multiple_listeners
96 def test_call_hook_with_multiple_listeners
97 @hook_module.add_listener(TestHook1)
97 @hook_module.add_listener(TestHook1)
98 @hook_module.add_listener(TestHook2)
98 @hook_module.add_listener(TestHook2)
99 assert_equal ['Test hook 1 listener.', 'Test hook 2 listener.'], hook_helper.call_hook(:view_layouts_base_html_head)
99 assert_equal ['Test hook 1 listener.', 'Test hook 2 listener.'], hook_helper.call_hook(:view_layouts_base_html_head)
100 end
100 end
101
101
102 # Context: Redmine::Hook::Helper.call_hook default_url
102 # Context: Redmine::Hook::Helper.call_hook default_url
103 def test_call_hook_default_url_options
103 def test_call_hook_default_url_options
104 @hook_module.add_listener(TestLinkToHook)
104 @hook_module.add_listener(TestLinkToHook)
105
105
106 assert_equal ['<a href="/issues">Issues</a>'], hook_helper.call_hook(:view_layouts_base_html_head)
106 assert_equal ['<a href="/issues">Issues</a>'], hook_helper.call_hook(:view_layouts_base_html_head)
107 end
107 end
108
108
109 # Context: Redmine::Hook::Helper.call_hook
109 # Context: Redmine::Hook::Helper.call_hook
110 def test_call_hook_with_project_added_to_context
110 def test_call_hook_with_project_added_to_context
111 @hook_module.add_listener(TestHook3)
111 @hook_module.add_listener(TestHook3)
112 assert_match /project/i, hook_helper.call_hook(:view_layouts_base_html_head)[0]
112 assert_match /project/i, hook_helper.call_hook(:view_layouts_base_html_head)[0]
113 end
113 end
114
114
115 def test_call_hook_from_controller_with_controller_added_to_context
115 def test_call_hook_from_controller_with_controller_added_to_context
116 @hook_module.add_listener(TestHook3)
116 @hook_module.add_listener(TestHook3)
117 assert_match /controller/i, hook_helper.call_hook(:view_layouts_base_html_head)[0]
117 assert_match /controller/i, hook_helper.call_hook(:view_layouts_base_html_head)[0]
118 end
118 end
119
119
120 def test_call_hook_from_controller_with_request_added_to_context
120 def test_call_hook_from_controller_with_request_added_to_context
121 @hook_module.add_listener(TestHook3)
121 @hook_module.add_listener(TestHook3)
122 assert_match /request/i, hook_helper.call_hook(:view_layouts_base_html_head)[0]
122 assert_match /request/i, hook_helper.call_hook(:view_layouts_base_html_head)[0]
123 end
123 end
124
124
125 def test_call_hook_from_view_with_project_added_to_context
125 def test_call_hook_from_view_with_project_added_to_context
126 @hook_module.add_listener(TestHook3)
126 @hook_module.add_listener(TestHook3)
127 assert_match /project/i, view_hook_helper.call_hook(:view_layouts_base_html_head)
127 assert_match /project/i, view_hook_helper.call_hook(:view_layouts_base_html_head)
128 end
128 end
129
129
130 def test_call_hook_from_view_with_controller_added_to_context
130 def test_call_hook_from_view_with_controller_added_to_context
131 @hook_module.add_listener(TestHook3)
131 @hook_module.add_listener(TestHook3)
132 assert_match /controller/i, view_hook_helper.call_hook(:view_layouts_base_html_head)
132 assert_match /controller/i, view_hook_helper.call_hook(:view_layouts_base_html_head)
133 end
133 end
134
134
135 def test_call_hook_from_view_with_request_added_to_context
135 def test_call_hook_from_view_with_request_added_to_context
136 @hook_module.add_listener(TestHook3)
136 @hook_module.add_listener(TestHook3)
137 assert_match /request/i, view_hook_helper.call_hook(:view_layouts_base_html_head)
137 assert_match /request/i, view_hook_helper.call_hook(:view_layouts_base_html_head)
138 end
138 end
139
139
140 def test_call_hook_from_view_should_join_responses_with_a_space
140 def test_call_hook_from_view_should_join_responses_with_a_space
141 @hook_module.add_listener(TestHook1)
141 @hook_module.add_listener(TestHook1)
142 @hook_module.add_listener(TestHook2)
142 @hook_module.add_listener(TestHook2)
143 assert_equal 'Test hook 1 listener. Test hook 2 listener.',
143 assert_equal 'Test hook 1 listener. Test hook 2 listener.',
144 view_hook_helper.call_hook(:view_layouts_base_html_head)
144 view_hook_helper.call_hook(:view_layouts_base_html_head)
145 end
145 end
146
146
147 def test_call_hook_should_not_change_the_default_url_for_email_notifications
147 def test_call_hook_should_not_change_the_default_url_for_email_notifications
148 issue = Issue.find(1)
148 issue = Issue.find(1)
149
149
150 ActionMailer::Base.deliveries.clear
150 ActionMailer::Base.deliveries.clear
151 Mailer.deliver_issue_add(issue)
151 Mailer.issue_add(issue).deliver
152 mail = ActionMailer::Base.deliveries.last
152 mail = ActionMailer::Base.deliveries.last
153
153
154 @hook_module.add_listener(TestLinkToHook)
154 @hook_module.add_listener(TestLinkToHook)
155 hook_helper.call_hook(:view_layouts_base_html_head)
155 hook_helper.call_hook(:view_layouts_base_html_head)
156
156
157 ActionMailer::Base.deliveries.clear
157 ActionMailer::Base.deliveries.clear
158 Mailer.deliver_issue_add(issue)
158 Mailer.issue_add(issue).deliver
159 mail2 = ActionMailer::Base.deliveries.last
159 mail2 = ActionMailer::Base.deliveries.last
160
160
161 assert_equal mail_body(mail), mail_body(mail2)
161 assert_equal mail_body(mail), mail_body(mail2)
162 end
162 end
163
163
164 def hook_helper
164 def hook_helper
165 @hook_helper ||= TestHookHelperController.new
165 @hook_helper ||= TestHookHelperController.new
166 end
166 end
167
167
168 def view_hook_helper
168 def view_hook_helper
169 @view_hook_helper ||= TestHookHelperView.new(Rails.root.to_s + '/app/views')
169 @view_hook_helper ||= TestHookHelperView.new(Rails.root.to_s + '/app/views')
170 end
170 end
171 end
171 end
172
172
@@ -1,540 +1,540
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 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 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19
19
20 class MailerTest < ActiveSupport::TestCase
20 class MailerTest < ActiveSupport::TestCase
21 include Redmine::I18n
21 include Redmine::I18n
22 include ActionDispatch::Assertions::SelectorAssertions
22 include ActionDispatch::Assertions::SelectorAssertions
23 fixtures :projects, :enabled_modules, :issues, :users, :members,
23 fixtures :projects, :enabled_modules, :issues, :users, :members,
24 :member_roles, :roles, :documents, :attachments, :news,
24 :member_roles, :roles, :documents, :attachments, :news,
25 :tokens, :journals, :journal_details, :changesets,
25 :tokens, :journals, :journal_details, :changesets,
26 :trackers, :projects_trackers,
26 :trackers, :projects_trackers,
27 :issue_statuses, :enumerations, :messages, :boards, :repositories,
27 :issue_statuses, :enumerations, :messages, :boards, :repositories,
28 :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions,
28 :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions,
29 :versions,
29 :versions,
30 :comments
30 :comments
31
31
32 def setup
32 def setup
33 ActionMailer::Base.deliveries.clear
33 ActionMailer::Base.deliveries.clear
34 Setting.host_name = 'mydomain.foo'
34 Setting.host_name = 'mydomain.foo'
35 Setting.protocol = 'http'
35 Setting.protocol = 'http'
36 Setting.plain_text_mail = '0'
36 Setting.plain_text_mail = '0'
37 end
37 end
38
38
39 def test_generated_links_in_emails
39 def test_generated_links_in_emails
40 Setting.default_language = 'en'
40 Setting.default_language = 'en'
41 Setting.host_name = 'mydomain.foo'
41 Setting.host_name = 'mydomain.foo'
42 Setting.protocol = 'https'
42 Setting.protocol = 'https'
43
43
44 journal = Journal.find(3)
44 journal = Journal.find(3)
45 assert Mailer.deliver_issue_edit(journal)
45 assert Mailer.issue_edit(journal).deliver
46
46
47 mail = last_email
47 mail = last_email
48 assert_not_nil mail
48 assert_not_nil mail
49
49
50 assert_select_email do
50 assert_select_email do
51 # link to the main ticket
51 # link to the main ticket
52 assert_select 'a[href=?]',
52 assert_select 'a[href=?]',
53 'https://mydomain.foo/issues/2#change-3',
53 'https://mydomain.foo/issues/2#change-3',
54 :text => 'Feature request #2: Add ingredients categories'
54 :text => 'Feature request #2: Add ingredients categories'
55 # link to a referenced ticket
55 # link to a referenced ticket
56 assert_select 'a[href=?][title=?]',
56 assert_select 'a[href=?][title=?]',
57 'https://mydomain.foo/issues/1',
57 'https://mydomain.foo/issues/1',
58 'Can\'t print recipes (New)',
58 'Can\'t print recipes (New)',
59 :text => '#1'
59 :text => '#1'
60 # link to a changeset
60 # link to a changeset
61 assert_select 'a[href=?][title=?]',
61 assert_select 'a[href=?][title=?]',
62 'https://mydomain.foo/projects/ecookbook/repository/revisions/2',
62 'https://mydomain.foo/projects/ecookbook/repository/revisions/2',
63 'This commit fixes #1, #2 and references #1 &amp; #3',
63 'This commit fixes #1, #2 and references #1 &amp; #3',
64 :text => 'r2'
64 :text => 'r2'
65 # link to a description diff
65 # link to a description diff
66 assert_select 'a[href=?][title=?]',
66 assert_select 'a[href=?][title=?]',
67 'https://mydomain.foo/journals/diff/3?detail_id=4',
67 'https://mydomain.foo/journals/diff/3?detail_id=4',
68 'View differences',
68 'View differences',
69 :text => 'diff'
69 :text => 'diff'
70 # link to an attachment
70 # link to an attachment
71 assert_select 'a[href=?]',
71 assert_select 'a[href=?]',
72 'https://mydomain.foo/attachments/download/4/source.rb',
72 'https://mydomain.foo/attachments/download/4/source.rb',
73 :text => 'source.rb'
73 :text => 'source.rb'
74 end
74 end
75 end
75 end
76
76
77 def test_generated_links_with_prefix
77 def test_generated_links_with_prefix
78 Setting.default_language = 'en'
78 Setting.default_language = 'en'
79 relative_url_root = Redmine::Utils.relative_url_root
79 relative_url_root = Redmine::Utils.relative_url_root
80 Setting.host_name = 'mydomain.foo/rdm'
80 Setting.host_name = 'mydomain.foo/rdm'
81 Setting.protocol = 'http'
81 Setting.protocol = 'http'
82
82
83 journal = Journal.find(3)
83 journal = Journal.find(3)
84 assert Mailer.deliver_issue_edit(journal)
84 assert Mailer.issue_edit(journal).deliver
85
85
86 mail = last_email
86 mail = last_email
87 assert_not_nil mail
87 assert_not_nil mail
88
88
89 assert_select_email do
89 assert_select_email do
90 # link to the main ticket
90 # link to the main ticket
91 assert_select 'a[href=?]',
91 assert_select 'a[href=?]',
92 'http://mydomain.foo/rdm/issues/2#change-3',
92 'http://mydomain.foo/rdm/issues/2#change-3',
93 :text => 'Feature request #2: Add ingredients categories'
93 :text => 'Feature request #2: Add ingredients categories'
94 # link to a referenced ticket
94 # link to a referenced ticket
95 assert_select 'a[href=?][title=?]',
95 assert_select 'a[href=?][title=?]',
96 'http://mydomain.foo/rdm/issues/1',
96 'http://mydomain.foo/rdm/issues/1',
97 'Can\'t print recipes (New)',
97 'Can\'t print recipes (New)',
98 :text => '#1'
98 :text => '#1'
99 # link to a changeset
99 # link to a changeset
100 assert_select 'a[href=?][title=?]',
100 assert_select 'a[href=?][title=?]',
101 'http://mydomain.foo/rdm/projects/ecookbook/repository/revisions/2',
101 'http://mydomain.foo/rdm/projects/ecookbook/repository/revisions/2',
102 'This commit fixes #1, #2 and references #1 &amp; #3',
102 'This commit fixes #1, #2 and references #1 &amp; #3',
103 :text => 'r2'
103 :text => 'r2'
104 # link to a description diff
104 # link to a description diff
105 assert_select 'a[href=?][title=?]',
105 assert_select 'a[href=?][title=?]',
106 'http://mydomain.foo/rdm/journals/diff/3?detail_id=4',
106 'http://mydomain.foo/rdm/journals/diff/3?detail_id=4',
107 'View differences',
107 'View differences',
108 :text => 'diff'
108 :text => 'diff'
109 # link to an attachment
109 # link to an attachment
110 assert_select 'a[href=?]',
110 assert_select 'a[href=?]',
111 'http://mydomain.foo/rdm/attachments/download/4/source.rb',
111 'http://mydomain.foo/rdm/attachments/download/4/source.rb',
112 :text => 'source.rb'
112 :text => 'source.rb'
113 end
113 end
114 end
114 end
115
115
116 def test_generated_links_with_prefix_and_no_relative_url_root
116 def test_generated_links_with_prefix_and_no_relative_url_root
117 Setting.default_language = 'en'
117 Setting.default_language = 'en'
118 relative_url_root = Redmine::Utils.relative_url_root
118 relative_url_root = Redmine::Utils.relative_url_root
119 Setting.host_name = 'mydomain.foo/rdm'
119 Setting.host_name = 'mydomain.foo/rdm'
120 Setting.protocol = 'http'
120 Setting.protocol = 'http'
121 Redmine::Utils.relative_url_root = nil
121 Redmine::Utils.relative_url_root = nil
122
122
123 journal = Journal.find(3)
123 journal = Journal.find(3)
124 assert Mailer.deliver_issue_edit(journal)
124 assert Mailer.issue_edit(journal).deliver
125
125
126 mail = last_email
126 mail = last_email
127 assert_not_nil mail
127 assert_not_nil mail
128
128
129 assert_select_email do
129 assert_select_email do
130 # link to the main ticket
130 # link to the main ticket
131 assert_select 'a[href=?]',
131 assert_select 'a[href=?]',
132 'http://mydomain.foo/rdm/issues/2#change-3',
132 'http://mydomain.foo/rdm/issues/2#change-3',
133 :text => 'Feature request #2: Add ingredients categories'
133 :text => 'Feature request #2: Add ingredients categories'
134 # link to a referenced ticket
134 # link to a referenced ticket
135 assert_select 'a[href=?][title=?]',
135 assert_select 'a[href=?][title=?]',
136 'http://mydomain.foo/rdm/issues/1',
136 'http://mydomain.foo/rdm/issues/1',
137 'Can\'t print recipes (New)',
137 'Can\'t print recipes (New)',
138 :text => '#1'
138 :text => '#1'
139 # link to a changeset
139 # link to a changeset
140 assert_select 'a[href=?][title=?]',
140 assert_select 'a[href=?][title=?]',
141 'http://mydomain.foo/rdm/projects/ecookbook/repository/revisions/2',
141 'http://mydomain.foo/rdm/projects/ecookbook/repository/revisions/2',
142 'This commit fixes #1, #2 and references #1 &amp; #3',
142 'This commit fixes #1, #2 and references #1 &amp; #3',
143 :text => 'r2'
143 :text => 'r2'
144 # link to a description diff
144 # link to a description diff
145 assert_select 'a[href=?][title=?]',
145 assert_select 'a[href=?][title=?]',
146 'http://mydomain.foo/rdm/journals/diff/3?detail_id=4',
146 'http://mydomain.foo/rdm/journals/diff/3?detail_id=4',
147 'View differences',
147 'View differences',
148 :text => 'diff'
148 :text => 'diff'
149 # link to an attachment
149 # link to an attachment
150 assert_select 'a[href=?]',
150 assert_select 'a[href=?]',
151 'http://mydomain.foo/rdm/attachments/download/4/source.rb',
151 'http://mydomain.foo/rdm/attachments/download/4/source.rb',
152 :text => 'source.rb'
152 :text => 'source.rb'
153 end
153 end
154 ensure
154 ensure
155 # restore it
155 # restore it
156 Redmine::Utils.relative_url_root = relative_url_root
156 Redmine::Utils.relative_url_root = relative_url_root
157 end
157 end
158
158
159 def test_email_headers
159 def test_email_headers
160 issue = Issue.find(1)
160 issue = Issue.find(1)
161 Mailer.deliver_issue_add(issue)
161 Mailer.issue_add(issue).deliver
162 mail = last_email
162 mail = last_email
163 assert_not_nil mail
163 assert_not_nil mail
164 assert_equal 'OOF', mail.header['X-Auto-Response-Suppress'].to_s
164 assert_equal 'OOF', mail.header['X-Auto-Response-Suppress'].to_s
165 assert_equal 'auto-generated', mail.header['Auto-Submitted'].to_s
165 assert_equal 'auto-generated', mail.header['Auto-Submitted'].to_s
166 end
166 end
167
167
168 def test_email_headers_should_include_sender
168 def test_email_headers_should_include_sender
169 issue = Issue.find(1)
169 issue = Issue.find(1)
170 Mailer.deliver_issue_add(issue)
170 Mailer.issue_add(issue).deliver
171 mail = last_email
171 mail = last_email
172 assert_equal issue.author.login, mail.header['X-Redmine-Sender'].to_s
172 assert_equal issue.author.login, mail.header['X-Redmine-Sender'].to_s
173 end
173 end
174
174
175 def test_plain_text_mail
175 def test_plain_text_mail
176 Setting.plain_text_mail = 1
176 Setting.plain_text_mail = 1
177 journal = Journal.find(2)
177 journal = Journal.find(2)
178 Mailer.deliver_issue_edit(journal)
178 Mailer.issue_edit(journal).deliver
179 mail = last_email
179 mail = last_email
180 assert_equal "text/plain; charset=UTF-8", mail.content_type
180 assert_equal "text/plain; charset=UTF-8", mail.content_type
181 assert_equal 0, mail.parts.size
181 assert_equal 0, mail.parts.size
182 assert !mail.encoded.include?('href')
182 assert !mail.encoded.include?('href')
183 end
183 end
184
184
185 def test_html_mail
185 def test_html_mail
186 Setting.plain_text_mail = 0
186 Setting.plain_text_mail = 0
187 journal = Journal.find(2)
187 journal = Journal.find(2)
188 Mailer.deliver_issue_edit(journal)
188 Mailer.issue_edit(journal).deliver
189 mail = last_email
189 mail = last_email
190 assert_equal 2, mail.parts.size
190 assert_equal 2, mail.parts.size
191 assert mail.encoded.include?('href')
191 assert mail.encoded.include?('href')
192 end
192 end
193
193
194 def test_from_header
194 def test_from_header
195 with_settings :mail_from => 'redmine@example.net' do
195 with_settings :mail_from => 'redmine@example.net' do
196 Mailer.deliver_test_email(User.find(1))
196 Mailer.test_email(User.find(1)).deliver
197 end
197 end
198 mail = last_email
198 mail = last_email
199 assert_equal 'redmine@example.net', mail.from_addrs.first
199 assert_equal 'redmine@example.net', mail.from_addrs.first
200 end
200 end
201
201
202 def test_from_header_with_phrase
202 def test_from_header_with_phrase
203 with_settings :mail_from => 'Redmine app <redmine@example.net>' do
203 with_settings :mail_from => 'Redmine app <redmine@example.net>' do
204 Mailer.deliver_test_email(User.find(1))
204 Mailer.test_email(User.find(1)).deliver
205 end
205 end
206 mail = last_email
206 mail = last_email
207 assert_equal 'redmine@example.net', mail.from_addrs.first
207 assert_equal 'redmine@example.net', mail.from_addrs.first
208 assert_equal 'Redmine app <redmine@example.net>', mail.header['From'].to_s
208 assert_equal 'Redmine app <redmine@example.net>', mail.header['From'].to_s
209 end
209 end
210
210
211 def test_should_not_send_email_without_recipient
211 def test_should_not_send_email_without_recipient
212 news = News.find(:first)
212 news = News.find(:first)
213 user = news.author
213 user = news.author
214 # Remove members except news author
214 # Remove members except news author
215 news.project.memberships.each {|m| m.destroy unless m.user == user}
215 news.project.memberships.each {|m| m.destroy unless m.user == user}
216
216
217 user.pref[:no_self_notified] = false
217 user.pref[:no_self_notified] = false
218 user.pref.save
218 user.pref.save
219 User.current = user
219 User.current = user
220 Mailer.deliver_news_added(news.reload)
220 Mailer.news_added(news.reload).deliver
221 assert_equal 1, last_email.bcc.size
221 assert_equal 1, last_email.bcc.size
222
222
223 # nobody to notify
223 # nobody to notify
224 user.pref[:no_self_notified] = true
224 user.pref[:no_self_notified] = true
225 user.pref.save
225 user.pref.save
226 User.current = user
226 User.current = user
227 ActionMailer::Base.deliveries.clear
227 ActionMailer::Base.deliveries.clear
228 Mailer.deliver_news_added(news.reload)
228 Mailer.news_added(news.reload).deliver
229 assert ActionMailer::Base.deliveries.empty?
229 assert ActionMailer::Base.deliveries.empty?
230 end
230 end
231
231
232 def test_issue_add_message_id
232 def test_issue_add_message_id
233 issue = Issue.find(1)
233 issue = Issue.find(1)
234 Mailer.deliver_issue_add(issue)
234 Mailer.issue_add(issue).deliver
235 mail = last_email
235 mail = last_email
236 assert_equal Mailer.message_id_for(issue), mail.message_id
236 assert_equal Mailer.message_id_for(issue), mail.message_id
237 assert_nil mail.references
237 assert_nil mail.references
238 end
238 end
239
239
240 def test_issue_edit_message_id
240 def test_issue_edit_message_id
241 journal = Journal.find(1)
241 journal = Journal.find(1)
242 Mailer.deliver_issue_edit(journal)
242 Mailer.issue_edit(journal).deliver
243 mail = last_email
243 mail = last_email
244 assert_equal Mailer.message_id_for(journal), mail.message_id
244 assert_equal Mailer.message_id_for(journal), mail.message_id
245 assert_include Mailer.message_id_for(journal.issue), mail.references
245 assert_include Mailer.message_id_for(journal.issue), mail.references
246 assert_select_email do
246 assert_select_email do
247 # link to the update
247 # link to the update
248 assert_select "a[href=?]",
248 assert_select "a[href=?]",
249 "http://mydomain.foo/issues/#{journal.journalized_id}#change-#{journal.id}"
249 "http://mydomain.foo/issues/#{journal.journalized_id}#change-#{journal.id}"
250 end
250 end
251 end
251 end
252
252
253 def test_message_posted_message_id
253 def test_message_posted_message_id
254 message = Message.find(1)
254 message = Message.find(1)
255 Mailer.deliver_message_posted(message)
255 Mailer.message_posted(message).deliver
256 mail = last_email
256 mail = last_email
257 assert_equal Mailer.message_id_for(message), mail.message_id
257 assert_equal Mailer.message_id_for(message), mail.message_id
258 assert_nil mail.references
258 assert_nil mail.references
259 assert_select_email do
259 assert_select_email do
260 # link to the message
260 # link to the message
261 assert_select "a[href=?]",
261 assert_select "a[href=?]",
262 "http://mydomain.foo/boards/#{message.board.id}/topics/#{message.id}",
262 "http://mydomain.foo/boards/#{message.board.id}/topics/#{message.id}",
263 :text => message.subject
263 :text => message.subject
264 end
264 end
265 end
265 end
266
266
267 def test_reply_posted_message_id
267 def test_reply_posted_message_id
268 message = Message.find(3)
268 message = Message.find(3)
269 Mailer.deliver_message_posted(message)
269 Mailer.message_posted(message).deliver
270 mail = last_email
270 mail = last_email
271 assert_equal Mailer.message_id_for(message), mail.message_id
271 assert_equal Mailer.message_id_for(message), mail.message_id
272 assert_include Mailer.message_id_for(message.parent), mail.references
272 assert_include Mailer.message_id_for(message.parent), mail.references
273 assert_select_email do
273 assert_select_email do
274 # link to the reply
274 # link to the reply
275 assert_select "a[href=?]",
275 assert_select "a[href=?]",
276 "http://mydomain.foo/boards/#{message.board.id}/topics/#{message.root.id}?r=#{message.id}#message-#{message.id}",
276 "http://mydomain.foo/boards/#{message.board.id}/topics/#{message.root.id}?r=#{message.id}#message-#{message.id}",
277 :text => message.subject
277 :text => message.subject
278 end
278 end
279 end
279 end
280
280
281 context("#issue_add") do
281 context("#issue_add") do
282 setup do
282 setup do
283 ActionMailer::Base.deliveries.clear
283 ActionMailer::Base.deliveries.clear
284 Setting.bcc_recipients = '1'
284 Setting.bcc_recipients = '1'
285 @issue = Issue.find(1)
285 @issue = Issue.find(1)
286 end
286 end
287
287
288 should "notify project members" do
288 should "notify project members" do
289 assert Mailer.deliver_issue_add(@issue)
289 assert Mailer.issue_add(@issue).deliver
290 assert last_email.bcc.include?('dlopper@somenet.foo')
290 assert last_email.bcc.include?('dlopper@somenet.foo')
291 end
291 end
292
292
293 should "not notify project members that are not allow to view the issue" do
293 should "not notify project members that are not allow to view the issue" do
294 Role.find(2).remove_permission!(:view_issues)
294 Role.find(2).remove_permission!(:view_issues)
295 assert Mailer.deliver_issue_add(@issue)
295 assert Mailer.issue_add(@issue).deliver
296 assert !last_email.bcc.include?('dlopper@somenet.foo')
296 assert !last_email.bcc.include?('dlopper@somenet.foo')
297 end
297 end
298
298
299 should "notify issue watchers" do
299 should "notify issue watchers" do
300 user = User.find(9)
300 user = User.find(9)
301 # minimal email notification options
301 # minimal email notification options
302 user.pref[:no_self_notified] = '1'
302 user.pref[:no_self_notified] = '1'
303 user.pref.save
303 user.pref.save
304 user.mail_notification = false
304 user.mail_notification = false
305 user.save
305 user.save
306
306
307 Watcher.create!(:watchable => @issue, :user => user)
307 Watcher.create!(:watchable => @issue, :user => user)
308 assert Mailer.deliver_issue_add(@issue)
308 assert Mailer.issue_add(@issue).deliver
309 assert last_email.bcc.include?(user.mail)
309 assert last_email.bcc.include?(user.mail)
310 end
310 end
311
311
312 should "not notify watchers not allowed to view the issue" do
312 should "not notify watchers not allowed to view the issue" do
313 user = User.find(9)
313 user = User.find(9)
314 Watcher.create!(:watchable => @issue, :user => user)
314 Watcher.create!(:watchable => @issue, :user => user)
315 Role.non_member.remove_permission!(:view_issues)
315 Role.non_member.remove_permission!(:view_issues)
316 assert Mailer.deliver_issue_add(@issue)
316 assert Mailer.issue_add(@issue).deliver
317 assert !last_email.bcc.include?(user.mail)
317 assert !last_email.bcc.include?(user.mail)
318 end
318 end
319 end
319 end
320
320
321 # test mailer methods for each language
321 # test mailer methods for each language
322 def test_issue_add
322 def test_issue_add
323 issue = Issue.find(1)
323 issue = Issue.find(1)
324 valid_languages.each do |lang|
324 valid_languages.each do |lang|
325 Setting.default_language = lang.to_s
325 Setting.default_language = lang.to_s
326 assert Mailer.deliver_issue_add(issue)
326 assert Mailer.issue_add(issue).deliver
327 end
327 end
328 end
328 end
329
329
330 def test_issue_edit
330 def test_issue_edit
331 journal = Journal.find(1)
331 journal = Journal.find(1)
332 valid_languages.each do |lang|
332 valid_languages.each do |lang|
333 Setting.default_language = lang.to_s
333 Setting.default_language = lang.to_s
334 assert Mailer.deliver_issue_edit(journal)
334 assert Mailer.issue_edit(journal).deliver
335 end
335 end
336 end
336 end
337
337
338 def test_document_added
338 def test_document_added
339 document = Document.find(1)
339 document = Document.find(1)
340 valid_languages.each do |lang|
340 valid_languages.each do |lang|
341 Setting.default_language = lang.to_s
341 Setting.default_language = lang.to_s
342 assert Mailer.deliver_document_added(document)
342 assert Mailer.document_added(document).deliver
343 end
343 end
344 end
344 end
345
345
346 def test_attachments_added
346 def test_attachments_added
347 attachements = [ Attachment.find_by_container_type('Document') ]
347 attachements = [ Attachment.find_by_container_type('Document') ]
348 valid_languages.each do |lang|
348 valid_languages.each do |lang|
349 Setting.default_language = lang.to_s
349 Setting.default_language = lang.to_s
350 assert Mailer.deliver_attachments_added(attachements)
350 assert Mailer.attachments_added(attachements).deliver
351 end
351 end
352 end
352 end
353
353
354 def test_version_file_added
354 def test_version_file_added
355 attachements = [ Attachment.find_by_container_type('Version') ]
355 attachements = [ Attachment.find_by_container_type('Version') ]
356 assert Mailer.deliver_attachments_added(attachements)
356 assert Mailer.attachments_added(attachements).deliver
357 assert_not_nil last_email.bcc
357 assert_not_nil last_email.bcc
358 assert last_email.bcc.any?
358 assert last_email.bcc.any?
359 assert_select_email do
359 assert_select_email do
360 assert_select "a[href=?]", "http://mydomain.foo/projects/ecookbook/files"
360 assert_select "a[href=?]", "http://mydomain.foo/projects/ecookbook/files"
361 end
361 end
362 end
362 end
363
363
364 def test_project_file_added
364 def test_project_file_added
365 attachements = [ Attachment.find_by_container_type('Project') ]
365 attachements = [ Attachment.find_by_container_type('Project') ]
366 assert Mailer.deliver_attachments_added(attachements)
366 assert Mailer.attachments_added(attachements).deliver
367 assert_not_nil last_email.bcc
367 assert_not_nil last_email.bcc
368 assert last_email.bcc.any?
368 assert last_email.bcc.any?
369 assert_select_email do
369 assert_select_email do
370 assert_select "a[href=?]", "http://mydomain.foo/projects/ecookbook/files"
370 assert_select "a[href=?]", "http://mydomain.foo/projects/ecookbook/files"
371 end
371 end
372 end
372 end
373
373
374 def test_news_added
374 def test_news_added
375 news = News.find(:first)
375 news = News.find(:first)
376 valid_languages.each do |lang|
376 valid_languages.each do |lang|
377 Setting.default_language = lang.to_s
377 Setting.default_language = lang.to_s
378 assert Mailer.deliver_news_added(news)
378 assert Mailer.news_added(news).deliver
379 end
379 end
380 end
380 end
381
381
382 def test_news_comment_added
382 def test_news_comment_added
383 comment = Comment.find(2)
383 comment = Comment.find(2)
384 valid_languages.each do |lang|
384 valid_languages.each do |lang|
385 Setting.default_language = lang.to_s
385 Setting.default_language = lang.to_s
386 assert Mailer.deliver_news_comment_added(comment)
386 assert Mailer.news_comment_added(comment).deliver
387 end
387 end
388 end
388 end
389
389
390 def test_message_posted
390 def test_message_posted
391 message = Message.find(:first)
391 message = Message.find(:first)
392 recipients = ([message.root] + message.root.children).collect {|m| m.author.mail if m.author}
392 recipients = ([message.root] + message.root.children).collect {|m| m.author.mail if m.author}
393 recipients = recipients.compact.uniq
393 recipients = recipients.compact.uniq
394 valid_languages.each do |lang|
394 valid_languages.each do |lang|
395 Setting.default_language = lang.to_s
395 Setting.default_language = lang.to_s
396 assert Mailer.deliver_message_posted(message)
396 assert Mailer.message_posted(message).deliver
397 end
397 end
398 end
398 end
399
399
400 def test_wiki_content_added
400 def test_wiki_content_added
401 content = WikiContent.find(1)
401 content = WikiContent.find(1)
402 valid_languages.each do |lang|
402 valid_languages.each do |lang|
403 Setting.default_language = lang.to_s
403 Setting.default_language = lang.to_s
404 assert_difference 'ActionMailer::Base.deliveries.size' do
404 assert_difference 'ActionMailer::Base.deliveries.size' do
405 assert Mailer.deliver_wiki_content_added(content)
405 assert Mailer.wiki_content_added(content).deliver
406 end
406 end
407 end
407 end
408 end
408 end
409
409
410 def test_wiki_content_updated
410 def test_wiki_content_updated
411 content = WikiContent.find(1)
411 content = WikiContent.find(1)
412 valid_languages.each do |lang|
412 valid_languages.each do |lang|
413 Setting.default_language = lang.to_s
413 Setting.default_language = lang.to_s
414 assert_difference 'ActionMailer::Base.deliveries.size' do
414 assert_difference 'ActionMailer::Base.deliveries.size' do
415 assert Mailer.deliver_wiki_content_updated(content)
415 assert Mailer.wiki_content_updated(content).deliver
416 end
416 end
417 end
417 end
418 end
418 end
419
419
420 def test_account_information
420 def test_account_information
421 user = User.find(2)
421 user = User.find(2)
422 valid_languages.each do |lang|
422 valid_languages.each do |lang|
423 user.update_attribute :language, lang.to_s
423 user.update_attribute :language, lang.to_s
424 user.reload
424 user.reload
425 assert Mailer.deliver_account_information(user, 'pAsswORd')
425 assert Mailer.account_information(user, 'pAsswORd').deliver
426 end
426 end
427 end
427 end
428
428
429 def test_lost_password
429 def test_lost_password
430 token = Token.find(2)
430 token = Token.find(2)
431 valid_languages.each do |lang|
431 valid_languages.each do |lang|
432 token.user.update_attribute :language, lang.to_s
432 token.user.update_attribute :language, lang.to_s
433 token.reload
433 token.reload
434 assert Mailer.deliver_lost_password(token)
434 assert Mailer.lost_password(token).deliver
435 end
435 end
436 end
436 end
437
437
438 def test_register
438 def test_register
439 token = Token.find(1)
439 token = Token.find(1)
440 Setting.host_name = 'redmine.foo'
440 Setting.host_name = 'redmine.foo'
441 Setting.protocol = 'https'
441 Setting.protocol = 'https'
442
442
443 valid_languages.each do |lang|
443 valid_languages.each do |lang|
444 token.user.update_attribute :language, lang.to_s
444 token.user.update_attribute :language, lang.to_s
445 token.reload
445 token.reload
446 ActionMailer::Base.deliveries.clear
446 ActionMailer::Base.deliveries.clear
447 assert Mailer.deliver_register(token)
447 assert Mailer.register(token).deliver
448 mail = last_email
448 mail = last_email
449 assert_select_email do
449 assert_select_email do
450 assert_select "a[href=?]",
450 assert_select "a[href=?]",
451 "https://redmine.foo/account/activate?token=#{token.value}",
451 "https://redmine.foo/account/activate?token=#{token.value}",
452 :text => "https://redmine.foo/account/activate?token=#{token.value}"
452 :text => "https://redmine.foo/account/activate?token=#{token.value}"
453 end
453 end
454 end
454 end
455 end
455 end
456
456
457 def test_test
457 def test_test
458 user = User.find(1)
458 user = User.find(1)
459 valid_languages.each do |lang|
459 valid_languages.each do |lang|
460 user.update_attribute :language, lang.to_s
460 user.update_attribute :language, lang.to_s
461 assert Mailer.deliver_test_email(user)
461 assert Mailer.test_email(user).deliver
462 end
462 end
463 end
463 end
464
464
465 def test_reminders
465 def test_reminders
466 Mailer.reminders(:days => 42)
466 Mailer.reminders(:days => 42)
467 assert_equal 1, ActionMailer::Base.deliveries.size
467 assert_equal 1, ActionMailer::Base.deliveries.size
468 mail = last_email
468 mail = last_email
469 assert mail.bcc.include?('dlopper@somenet.foo')
469 assert mail.bcc.include?('dlopper@somenet.foo')
470 assert_mail_body_match 'Bug #3: Error 281 when updating a recipe', mail
470 assert_mail_body_match 'Bug #3: Error 281 when updating a recipe', mail
471 assert_equal '1 issue(s) due in the next 42 days', mail.subject
471 assert_equal '1 issue(s) due in the next 42 days', mail.subject
472 end
472 end
473
473
474 def test_reminders_should_not_include_closed_issues
474 def test_reminders_should_not_include_closed_issues
475 with_settings :default_language => 'en' do
475 with_settings :default_language => 'en' do
476 Issue.create!(:project_id => 1, :tracker_id => 1, :status_id => 5,
476 Issue.create!(:project_id => 1, :tracker_id => 1, :status_id => 5,
477 :subject => 'Closed issue', :assigned_to_id => 3,
477 :subject => 'Closed issue', :assigned_to_id => 3,
478 :due_date => 5.days.from_now,
478 :due_date => 5.days.from_now,
479 :author_id => 2)
479 :author_id => 2)
480 ActionMailer::Base.deliveries.clear
480 ActionMailer::Base.deliveries.clear
481
481
482 Mailer.reminders(:days => 42)
482 Mailer.reminders(:days => 42)
483 assert_equal 1, ActionMailer::Base.deliveries.size
483 assert_equal 1, ActionMailer::Base.deliveries.size
484 mail = last_email
484 mail = last_email
485 assert mail.bcc.include?('dlopper@somenet.foo')
485 assert mail.bcc.include?('dlopper@somenet.foo')
486 assert_mail_body_no_match 'Closed issue', mail
486 assert_mail_body_no_match 'Closed issue', mail
487 end
487 end
488 end
488 end
489
489
490 def test_reminders_for_users
490 def test_reminders_for_users
491 Mailer.reminders(:days => 42, :users => ['5'])
491 Mailer.reminders(:days => 42, :users => ['5'])
492 assert_equal 0, ActionMailer::Base.deliveries.size # No mail for dlopper
492 assert_equal 0, ActionMailer::Base.deliveries.size # No mail for dlopper
493 Mailer.reminders(:days => 42, :users => ['3'])
493 Mailer.reminders(:days => 42, :users => ['3'])
494 assert_equal 1, ActionMailer::Base.deliveries.size # No mail for dlopper
494 assert_equal 1, ActionMailer::Base.deliveries.size # No mail for dlopper
495 mail = last_email
495 mail = last_email
496 assert mail.bcc.include?('dlopper@somenet.foo')
496 assert mail.bcc.include?('dlopper@somenet.foo')
497 assert_mail_body_match 'Bug #3: Error 281 when updating a recipe', mail
497 assert_mail_body_match 'Bug #3: Error 281 when updating a recipe', mail
498 end
498 end
499
499
500 def test_mailer_should_not_change_locale
500 def test_mailer_should_not_change_locale
501 Setting.default_language = 'en'
501 Setting.default_language = 'en'
502 # Set current language to italian
502 # Set current language to italian
503 set_language_if_valid 'it'
503 set_language_if_valid 'it'
504 # Send an email to a french user
504 # Send an email to a french user
505 user = User.find(1)
505 user = User.find(1)
506 user.language = 'fr'
506 user.language = 'fr'
507 Mailer.deliver_account_activated(user)
507 Mailer.account_activated(user).deliver
508 mail = last_email
508 mail = last_email
509 assert_mail_body_match 'Votre compte', mail
509 assert_mail_body_match 'Votre compte', mail
510
510
511 assert_equal :it, current_language
511 assert_equal :it, current_language
512 end
512 end
513
513
514 def test_with_deliveries_off
514 def test_with_deliveries_off
515 Mailer.with_deliveries false do
515 Mailer.with_deliveries false do
516 Mailer.deliver_test_email(User.find(1))
516 Mailer.test_email(User.find(1)).deliver
517 end
517 end
518 assert ActionMailer::Base.deliveries.empty?
518 assert ActionMailer::Base.deliveries.empty?
519 # should restore perform_deliveries
519 # should restore perform_deliveries
520 assert ActionMailer::Base.perform_deliveries
520 assert ActionMailer::Base.perform_deliveries
521 end
521 end
522
522
523 def test_layout_should_include_the_emails_header
523 def test_layout_should_include_the_emails_header
524 with_settings :emails_header => "*Header content*" do
524 with_settings :emails_header => "*Header content*" do
525 assert Mailer.deliver_test_email(User.find(1))
525 assert Mailer.test_email(User.find(1)).deliver
526 assert_select_email do
526 assert_select_email do
527 assert_select ".header" do
527 assert_select ".header" do
528 assert_select "strong", :text => "Header content"
528 assert_select "strong", :text => "Header content"
529 end
529 end
530 end
530 end
531 end
531 end
532 end
532 end
533
533
534 private
534 private
535 def last_email
535 def last_email
536 mail = ActionMailer::Base.deliveries.last
536 mail = ActionMailer::Base.deliveries.last
537 assert_not_nil mail
537 assert_not_nil mail
538 mail
538 mail
539 end
539 end
540 end
540 end
General Comments 0
You need to be logged in to leave comments. Login now