##// END OF EJS Templates
Add support for multiple email addresses per user (#4244)....
Jean-Philippe Lang -
r13504:e3618bdbecd9
parent child
Show More
@@ -0,0 +1,105
1 # Redmine - project management software
2 # Copyright (C) 2006-2015 Jean-Philippe Lang
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 class EmailAddressesController < ApplicationController
19 before_filter :find_user, :require_admin_or_current_user
20 before_filter :find_email_address, :only => [:update, :destroy]
21
22 def index
23 @addresses = @user.email_addresses.order(:id).where(:is_default => false).to_a
24 @address ||= EmailAddress.new
25 end
26
27 def create
28 saved = false
29 if @user.email_addresses.count <= Setting.max_additional_emails.to_i
30 @address = EmailAddress.new(:user => @user, :is_default => false)
31 attrs = params[:email_address]
32 if attrs.is_a?(Hash)
33 @address.address = attrs[:address].to_s
34 end
35 saved = @address.save
36 end
37
38 respond_to do |format|
39 format.html {
40 if saved
41 redirect_to user_email_addresses_path(@user)
42 else
43 index
44 render :action => 'index'
45 end
46 }
47 format.js {
48 @address = nil if saved
49 index
50 render :action => 'index'
51 }
52 end
53 end
54
55 def update
56 if params[:notify].present?
57 @address.notify = params[:notify].to_s
58 end
59 @address.save
60
61 respond_to do |format|
62 format.html {
63 redirect_to user_email_addresses_path(@user)
64 }
65 format.js {
66 @address = nil
67 index
68 render :action => 'index'
69 }
70 end
71 end
72
73 def destroy
74 @address.destroy
75
76 respond_to do |format|
77 format.html {
78 redirect_to user_email_addresses_path(@user)
79 }
80 format.js {
81 @address = nil
82 index
83 render :action => 'index'
84 }
85 end
86 end
87
88 private
89
90 def find_user
91 @user = User.find(params[:user_id])
92 end
93
94 def find_email_address
95 @address = @user.email_addresses.where(:is_default => false).find(params[:id])
96 rescue ActiveRecord::RecordNotFound
97 render_404
98 end
99
100 def require_admin_or_current_user
101 unless @user == User.current
102 require_admin
103 end
104 end
105 end
@@ -0,0 +1,38
1 # encoding: utf-8
2 #
3 # Redmine - project management software
4 # Copyright (C) 2006-2015 Jean-Philippe Lang
5 #
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
20 module EmailAddressesHelper
21
22 # Returns a link to enable or disable notifications for the address
23 def toggle_email_address_notify_link(address)
24 if address.notify?
25 link_to image_tag('email.png'),
26 user_email_address_path(address.user, address, :notify => '0'),
27 :method => :put,
28 :title => l(:label_disable_notifications),
29 :remote => true
30 else
31 link_to image_tag('email_disabled.png'),
32 user_email_address_path(address.user, address, :notify => '1'),
33 :method => :put,
34 :title => l(:label_enable_notifications),
35 :remote => true
36 end
37 end
38 end
@@ -0,0 +1,54
1 # Redmine - project management software
2 # Copyright (C) 2006-2015 Jean-Philippe Lang
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 class EmailAddress < ActiveRecord::Base
19 belongs_to :user
20 attr_protected :id
21
22 after_update :destroy_tokens
23 after_destroy :destroy_tokens
24
25 validates_presence_of :address
26 validates_format_of :address, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i, :allow_blank => true
27 validates_length_of :address, :maximum => User::MAIL_LENGTH_LIMIT, :allow_nil => true
28 validates_uniqueness_of :address, :case_sensitive => false,
29 :if => Proc.new {|email| email.address_changed? && email.address.present?}
30
31 def address=(arg)
32 write_attribute(:address, arg.to_s.strip)
33 end
34
35 def destroy
36 if is_default?
37 false
38 else
39 super
40 end
41 end
42
43 private
44
45 # Delete all outstanding password reset tokens on email change.
46 # This helps to keep the account secure in case the associated email account
47 # was compromised.
48 def destroy_tokens
49 if address_changed? || destroyed?
50 tokens = ['recovery']
51 Token.where(:user_id => user_id, :action => tokens).delete_all
52 end
53 end
54 end
@@ -0,0 +1,26
1 <% if @addresses.present? %>
2 <table class="list email_addresses">
3 <% @addresses.each do |address| %>
4 <tr class="<%= cycle("odd", "even") %>">
5 <td class="email"><%= address.address %></td>
6 <td class="buttons">
7 <%= toggle_email_address_notify_link(address) %>
8 <%= delete_link user_email_address_path(@user, address), :remote => true %>
9 </td>
10 </tr>
11 <% end %>
12 </table>
13 <% end %>
14
15 <% unless @addresses.size >= Setting.max_additional_emails.to_i %>
16 <div>
17 <%= form_for @address, :url => user_email_addresses_path(@user), :remote => true do |f| %>
18 <p><%= l(:label_email_address_add) %></p>
19 <%= error_messages_for @address %>
20 <p>
21 <%= f.text_field :address, :size => 40 %>
22 <%= submit_tag l(:button_add) %>
23 </p>
24 <% end %>
25 </div>
26 <% end %>
@@ -0,0 +1,2
1 <h2><%= @user.name %></h2>
2 <%= render :partial => 'email_addresses/index' %>
@@ -0,0 +1,3
1 $('#ajax-modal').html('<%= escape_javascript(render :partial => 'email_addresses/index') %>');
2 showModal('ajax-modal', '600px', '<%= escape_javascript l(:label_email_address_plural) %>');
3 $('#email_address_address').focus();
@@ -0,0 +1,12
1 class CreateEmailAddresses < ActiveRecord::Migration
2 def change
3 create_table :email_addresses do |t|
4 t.column :user_id, :integer, :null => false
5 t.column :address, :string, :null => false
6 t.column :is_default, :boolean, :null => false, :default => false
7 t.column :notify, :boolean, :null => false, :default => true
8 t.column :created_on, :timestamp, :null => false
9 t.column :updated_on, :timestamp, :null => false
10 end
11 end
12 end
@@ -0,0 +1,14
1 class PopulateEmailAddresses < ActiveRecord::Migration
2 def self.up
3 t = EmailAddress.connection.quoted_true
4 n = EmailAddress.connection.quoted_date(Time.now)
5
6 sql = "INSERT INTO #{EmailAddress.table_name} (user_id, address, is_default, notify, created_on, updated_on)" +
7 " SELECT id, mail, #{t}, #{t}, '#{n}', '#{n}' FROM #{User.table_name} WHERE type = 'User' ORDER BY id"
8 EmailAddress.connection.execute(sql)
9 end
10
11 def self.down
12 EmailAddress.delete_all
13 end
14 end
@@ -0,0 +1,9
1 class RemoveUsersMail < ActiveRecord::Migration
2 def self.up
3 remove_column :users, :mail
4 end
5
6 def self.down
7 raise IrreversibleMigration
8 end
9 end
@@ -0,0 +1,9
1 class AddEmailAddressesUserIdIndex < ActiveRecord::Migration
2 def up
3 add_index :email_addresses, :user_id
4 end
5
6 def down
7 remove_index :email_addresses, :user_id
8 end
9 end
1 NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
@@ -0,0 +1,57
1 ---
2 email_address_001:
3 id: 1
4 user_id: 1
5 address: admin@somenet.foo
6 is_default: true
7 created_on: 2006-07-19 19:34:07 +02:00
8 updated_on: 2006-07-19 19:34:07 +02:00
9 email_address_002:
10 id: 2
11 user_id: 2
12 address: jsmith@somenet.foo
13 is_default: true
14 created_on: 2006-07-19 19:34:07 +02:00
15 updated_on: 2006-07-19 19:34:07 +02:00
16 email_address_003:
17 id: 3
18 user_id: 3
19 address: dlopper@somenet.foo
20 is_default: true
21 created_on: 2006-07-19 19:34:07 +02:00
22 updated_on: 2006-07-19 19:34:07 +02:00
23 email_address_004:
24 id: 4
25 user_id: 4
26 address: rhill@somenet.foo
27 is_default: true
28 created_on: 2006-07-19 19:34:07 +02:00
29 updated_on: 2006-07-19 19:34:07 +02:00
30 email_address_005:
31 id: 5
32 user_id: 5
33 address: dlopper2@somenet.foo
34 is_default: true
35 created_on: 2006-07-19 19:34:07 +02:00
36 updated_on: 2006-07-19 19:34:07 +02:00
37 email_address_007:
38 id: 7
39 user_id: 7
40 address: someone@foo.bar
41 is_default: true
42 created_on: 2006-07-19 19:34:07 +02:00
43 updated_on: 2006-07-19 19:34:07 +02:00
44 email_address_008:
45 id: 8
46 user_id: 8
47 address: miscuser8@foo.bar
48 is_default: true
49 created_on: 2006-07-19 19:34:07 +02:00
50 updated_on: 2006-07-19 19:34:07 +02:00
51 email_address_009:
52 id: 9
53 user_id: 9
54 address: miscuser9@foo.bar
55 is_default: true
56 created_on: 2006-07-19 19:34:07 +02:00
57 updated_on: 2006-07-19 19:34:07 +02:00
@@ -0,0 +1,144
1 # Redmine - project management software
2 # Copyright (C) 2006-2015 Jean-Philippe Lang
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 require File.expand_path('../../test_helper', __FILE__)
19
20 class EmailAddressesControllerTest < ActionController::TestCase
21 fixtures :users, :email_addresses
22
23 def setup
24 User.current = nil
25 end
26
27 def test_index_with_no_additional_emails
28 @request.session[:user_id] = 2
29 get :index, :user_id => 2
30 assert_response :success
31 assert_template 'index'
32 end
33
34 def test_index_with_additional_emails
35 @request.session[:user_id] = 2
36 EmailAddress.create!(:user_id => 2, :address => 'another@somenet.foo')
37
38 get :index, :user_id => 2
39 assert_response :success
40 assert_template 'index'
41 assert_select '.email', :text => 'another@somenet.foo'
42 end
43
44 def test_index_with_additional_emails_as_js
45 @request.session[:user_id] = 2
46 EmailAddress.create!(:user_id => 2, :address => 'another@somenet.foo')
47
48 xhr :get, :index, :user_id => 2
49 assert_response :success
50 assert_template 'index'
51 assert_include 'another@somenet.foo', response.body
52 end
53
54 def test_index_by_admin_should_be_allowed
55 @request.session[:user_id] = 1
56 get :index, :user_id => 2
57 assert_response :success
58 assert_template 'index'
59 end
60
61 def test_index_by_another_user_should_be_denied
62 @request.session[:user_id] = 3
63 get :index, :user_id => 2
64 assert_response 403
65 end
66
67 def test_create
68 @request.session[:user_id] = 2
69 assert_difference 'EmailAddress.count' do
70 post :create, :user_id => 2, :email_address => {:address => 'another@somenet.foo'}
71 assert_response 302
72 assert_redirected_to '/users/2/email_addresses'
73 end
74 email = EmailAddress.order('id DESC').first
75 assert_equal 2, email.user_id
76 assert_equal 'another@somenet.foo', email.address
77 end
78
79 def test_create_as_js
80 @request.session[:user_id] = 2
81 assert_difference 'EmailAddress.count' do
82 xhr :post, :create, :user_id => 2, :email_address => {:address => 'another@somenet.foo'}
83 assert_response 200
84 end
85 end
86
87 def test_create_with_failure
88 @request.session[:user_id] = 2
89 assert_no_difference 'EmailAddress.count' do
90 post :create, :user_id => 2, :email_address => {:address => 'invalid'}
91 assert_response 200
92 end
93 end
94
95 def test_update
96 @request.session[:user_id] = 2
97 email = EmailAddress.create!(:user_id => 2, :address => 'another@somenet.foo')
98
99 put :update, :user_id => 2, :id => email.id, :notify => '0'
100 assert_response 302
101
102 assert_equal false, email.reload.notify
103 end
104
105 def test_update_as_js
106 @request.session[:user_id] = 2
107 email = EmailAddress.create!(:user_id => 2, :address => 'another@somenet.foo')
108
109 xhr :put, :update, :user_id => 2, :id => email.id, :notify => '0'
110 assert_response 200
111
112 assert_equal false, email.reload.notify
113 end
114
115 def test_destroy
116 @request.session[:user_id] = 2
117 email = EmailAddress.create!(:user_id => 2, :address => 'another@somenet.foo')
118
119 assert_difference 'EmailAddress.count', -1 do
120 delete :destroy, :user_id => 2, :id => email.id
121 assert_response 302
122 assert_redirected_to '/users/2/email_addresses'
123 end
124 end
125
126 def test_destroy_as_js
127 @request.session[:user_id] = 2
128 email = EmailAddress.create!(:user_id => 2, :address => 'another@somenet.foo')
129
130 assert_difference 'EmailAddress.count', -1 do
131 xhr :delete, :destroy, :user_id => 2, :id => email.id
132 assert_response 200
133 end
134 end
135
136 def test_should_not_destroy_default
137 @request.session[:user_id] = 2
138
139 assert_no_difference 'EmailAddress.count' do
140 delete :destroy, :user_id => 2, :id => User.find(2).email_address.id
141 assert_response 404
142 end
143 end
144 end
@@ -41,7 +41,7 class UsersController < ApplicationController
41 41
42 42 @status = params[:status] || 1
43 43
44 scope = User.logged.status(@status)
44 scope = User.logged.status(@status).preload(:email_address)
45 45 scope = scope.like(params[:name]) if params[:name].present?
46 46 scope = scope.in_group(params[:group_id]) if params[:group_id].present?
47 47
@@ -42,6 +42,12 module UsersHelper
42 42 end
43 43 end
44 44
45 def additional_emails_link(user)
46 if user.email_addresses.count > 1 || Setting.max_additional_emails.to_i > 0
47 link_to l(:label_email_address_plural), user_email_addresses_path(@user), :class => 'icon icon-email-add', :remote => true
48 end
49 end
50
45 51 def user_settings_tabs
46 52 tabs = [{:name => 'general', :partial => 'users/general', :label => :label_general},
47 53 {:name => 'memberships', :partial => 'users/memberships', :label => :label_project_plural}
@@ -60,6 +60,10 class Document < ActiveRecord::Base
60 60 @updated_on
61 61 end
62 62
63 def notified_users
64 project.notified_users.reject {|user| !visible?(user)}
65 end
66
63 67 private
64 68
65 69 def send_notification
@@ -306,7 +306,7 class MailHandler < ActionMailer::Base
306 306 if user.allowed_to?("add_#{obj.class.name.underscore}_watchers".to_sym, obj.project)
307 307 addresses = [email.to, email.cc].flatten.compact.uniq.collect {|a| a.strip.downcase}
308 308 unless addresses.empty?
309 User.active.where('LOWER(mail) IN (?)', addresses).each do |w|
309 User.active.having_mail(addresses).each do |w|
310 310 obj.add_watcher(w)
311 311 end
312 312 end
@@ -39,8 +39,8 class Mailer < ActionMailer::Base
39 39 @issue = issue
40 40 @users = to_users + cc_users
41 41 @issue_url = url_for(:controller => 'issues', :action => 'show', :id => issue)
42 mail :to => to_users.map(&:mail),
43 :cc => cc_users.map(&:mail),
42 mail :to => to_users,
43 :cc => cc_users,
44 44 :subject => "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] (#{issue.status.name}) #{issue.subject}"
45 45 end
46 46
@@ -71,8 +71,8 class Mailer < ActionMailer::Base
71 71 @journal = journal
72 72 @journal_details = journal.visible_details(@users.first)
73 73 @issue_url = url_for(:controller => 'issues', :action => 'show', :id => issue, :anchor => "change-#{journal.id}")
74 mail :to => to_users.map(&:mail),
75 :cc => cc_users.map(&:mail),
74 mail :to => to_users,
75 :cc => cc_users,
76 76 :subject => s
77 77 end
78 78
@@ -95,7 +95,7 class Mailer < ActionMailer::Base
95 95 @issues_url = url_for(:controller => 'issues', :action => 'index',
96 96 :set_filter => 1, :assigned_to_id => user.id,
97 97 :sort => 'due_date:asc')
98 mail :to => user.mail,
98 mail :to => user,
99 99 :subject => l(:mail_subject_reminder, :count => issues.size, :days => days)
100 100 end
101 101
@@ -109,7 +109,7 class Mailer < ActionMailer::Base
109 109 @author = User.current
110 110 @document = document
111 111 @document_url = url_for(:controller => 'documents', :action => 'show', :id => document)
112 mail :to => document.recipients,
112 mail :to => document.notified_users,
113 113 :subject => "[#{document.project.name}] #{l(:label_document_new)}: #{document.title}"
114 114 end
115 115
@@ -127,15 +127,15 class Mailer < ActionMailer::Base
127 127 when 'Project'
128 128 added_to_url = url_for(:controller => 'files', :action => 'index', :project_id => container)
129 129 added_to = "#{l(:label_project)}: #{container}"
130 recipients = container.project.notified_users.select {|user| user.allowed_to?(:view_files, container.project)}.collect {|u| u.mail}
130 recipients = container.project.notified_users.select {|user| user.allowed_to?(:view_files, container.project)}
131 131 when 'Version'
132 132 added_to_url = url_for(:controller => 'files', :action => 'index', :project_id => container.project)
133 133 added_to = "#{l(:label_version)}: #{container.name}"
134 recipients = container.project.notified_users.select {|user| user.allowed_to?(:view_files, container.project)}.collect {|u| u.mail}
134 recipients = container.project.notified_users.select {|user| user.allowed_to?(:view_files, container.project)}
135 135 when 'Document'
136 136 added_to_url = url_for(:controller => 'documents', :action => 'show', :id => container.id)
137 137 added_to = "#{l(:label_document)}: #{container.title}"
138 recipients = container.recipients
138 recipients = container.notified_users
139 139 end
140 140 redmine_headers 'Project' => container.project.identifier
141 141 @attachments = attachments
@@ -157,8 +157,8 class Mailer < ActionMailer::Base
157 157 references news
158 158 @news = news
159 159 @news_url = url_for(:controller => 'news', :action => 'show', :id => news)
160 mail :to => news.recipients,
161 :cc => news.cc_for_added_news,
160 mail :to => news.notified_users,
161 :cc => news.notified_watchers_for_added_news,
162 162 :subject => "[#{news.project.name}] #{l(:label_news)}: #{news.title}"
163 163 end
164 164
@@ -176,8 +176,8 class Mailer < ActionMailer::Base
176 176 @news = news
177 177 @comment = comment
178 178 @news_url = url_for(:controller => 'news', :action => 'show', :id => news)
179 mail :to => news.recipients,
180 :cc => news.watcher_recipients,
179 mail :to => news.notified_users,
180 :cc => news.notified_watchers,
181 181 :subject => "Re: [#{news.project.name}] #{l(:label_news)}: #{news.title}"
182 182 end
183 183
@@ -192,8 +192,8 class Mailer < ActionMailer::Base
192 192 @author = message.author
193 193 message_id message
194 194 references message.root
195 recipients = message.recipients
196 cc = ((message.root.watcher_recipients + message.board.watcher_recipients).uniq - recipients)
195 recipients = message.notified_users
196 cc = ((message.root.notified_watchers + message.board.notified_watchers).uniq - recipients)
197 197 @message = message
198 198 @message_url = url_for(message.event_url)
199 199 mail :to => recipients,
@@ -211,8 +211,8 class Mailer < ActionMailer::Base
211 211 'Wiki-Page-Id' => wiki_content.page.id
212 212 @author = wiki_content.author
213 213 message_id wiki_content
214 recipients = wiki_content.recipients
215 cc = wiki_content.page.wiki.watcher_recipients - recipients
214 recipients = wiki_content.notified_users
215 cc = wiki_content.page.wiki.notified_watchers - recipients
216 216 @wiki_content = wiki_content
217 217 @wiki_content_url = url_for(:controller => 'wiki', :action => 'show',
218 218 :project_id => wiki_content.project,
@@ -232,8 +232,8 class Mailer < ActionMailer::Base
232 232 'Wiki-Page-Id' => wiki_content.page.id
233 233 @author = wiki_content.author
234 234 message_id wiki_content
235 recipients = wiki_content.recipients
236 cc = wiki_content.page.wiki.watcher_recipients + wiki_content.page.watcher_recipients - recipients
235 recipients = wiki_content.notified_users
236 cc = wiki_content.page.wiki.notified_watchers + wiki_content.page.notified_watchers - recipients
237 237 @wiki_content = wiki_content
238 238 @wiki_content_url = url_for(:controller => 'wiki', :action => 'show',
239 239 :project_id => wiki_content.project,
@@ -267,7 +267,7 class Mailer < ActionMailer::Base
267 267 # Mailer.account_activation_request(user).deliver => sends an email to all active administrators
268 268 def account_activation_request(user)
269 269 # Send the email to all active administrators
270 recipients = User.active.where(:admin => true).collect { |u| u.mail }.compact
270 recipients = User.active.where(:admin => true)
271 271 @user = user
272 272 @url = url_for(:controller => 'users', :action => 'index',
273 273 :status => User::STATUS_REGISTERED,
@@ -378,12 +378,20 class Mailer < ActionMailer::Base
378 378 'From' => Setting.mail_from,
379 379 'List-Id' => "<#{Setting.mail_from.to_s.gsub('@', '.')}>"
380 380
381 # Replaces users with their email addresses
382 [:to, :cc, :bcc].each do |key|
383 if headers[key].present?
384 headers[key] = self.class.email_addresses(headers[key])
385 end
386 end
387
381 388 # Removes the author from the recipients and cc
382 389 # if the author does not want to receive notifications
383 390 # about what the author do
384 391 if @author && @author.logged? && @author.pref.no_self_notified
385 headers[:to].delete(@author.mail) if headers[:to].is_a?(Array)
386 headers[:cc].delete(@author.mail) if headers[:cc].is_a?(Array)
392 addresses = @author.mails
393 headers[:to] -= addresses if headers[:to].is_a?(Array)
394 headers[:cc] -= addresses if headers[:cc].is_a?(Array)
387 395 end
388 396
389 397 if @author && @author.logged?
@@ -447,6 +455,25 class Mailer < ActionMailer::Base
447 455 end
448 456 end
449 457
458 # Returns an array of email addresses to notify by
459 # replacing users in arg with their notified email addresses
460 #
461 # Example:
462 # Mailer.email_addresses(users)
463 # => ["foo@example.net", "bar@example.net"]
464 def self.email_addresses(arg)
465 arr = Array.wrap(arg)
466 mails = arr.reject {|a| a.is_a? Principal}
467 users = arr - mails
468 if users.any?
469 mails += EmailAddress.
470 where(:user_id => users.map(&:id)).
471 where("is_default = ? OR notify = ?", true, true).
472 pluck(:address)
473 end
474 mails
475 end
476
450 477 private
451 478
452 479 # Appends a Redmine header field (name is prepended with 'X-Redmine-')
@@ -103,6 +103,10 class Message < ActiveRecord::Base
103 103 usr && usr.logged? && (usr.allowed_to?(:delete_messages, project) || (self.author == usr && usr.allowed_to?(:delete_own_messages, project)))
104 104 end
105 105
106 def notified_users
107 project.notified_users.reject {|user| !visible?(user)}
108 end
109
106 110 private
107 111
108 112 def add_author_as_watcher
@@ -54,20 +54,29 class News < ActiveRecord::Base
54 54 user.allowed_to?(:comment_news, project)
55 55 end
56 56
57 def notified_users
58 project.users.select {|user| user.notify_about?(self) && user.allowed_to?(:view_news, project)}
59 end
60
57 61 def recipients
58 project.users.select {|user| user.notify_about?(self) && user.allowed_to?(:view_news, project)}.map(&:mail)
62 notified_users.map(&:mail)
59 63 end
60 64
61 # Returns the email addresses that should be cc'd when a new news is added
62 def cc_for_added_news
63 cc = []
65 # Returns the users that should be cc'd when a new news is added
66 def notified_watchers_for_added_news
67 watchers = []
64 68 if m = project.enabled_module('news')
65 cc = m.notified_watchers
69 watchers = m.notified_watchers
66 70 unless project.is_public?
67 cc = cc.select {|user| project.users.include?(user)}
71 watchers = watchers.select {|user| project.users.include?(user)}
68 72 end
69 73 end
70 cc.map(&:mail)
74 watchers
75 end
76
77 # Returns the email addresses that should be cc'd when a new news is added
78 def cc_for_added_news
79 notified_watchers_for_added_news.map(&:mail)
71 80 end
72 81
73 82 # returns latest news for projects visible by user
@@ -68,7 +68,8 class Principal < ActiveRecord::Base
68 68 where({})
69 69 else
70 70 pattern = "%#{q}%"
71 sql = %w(login firstname lastname mail).map {|column| "LOWER(#{table_name}.#{column}) LIKE LOWER(:p)"}.join(" OR ")
71 sql = %w(login firstname lastname).map {|column| "LOWER(#{table_name}.#{column}) LIKE LOWER(:p)"}.join(" OR ")
72 sql << " OR #{table_name}.id IN (SELECT user_id FROM #{EmailAddress.table_name} WHERE LOWER(address) LIKE LOWER(:p))"
72 73 params = {:p => pattern}
73 74 if q =~ /^(.+)\s+(.+)$/
74 75 a, b = "#{$1}%", "#{$2}%"
@@ -108,6 +109,14 class Principal < ActiveRecord::Base
108 109 to_s
109 110 end
110 111
112 def mail=(*args)
113 nil
114 end
115
116 def mail
117 nil
118 end
119
111 120 def visible?(user=User.current)
112 121 Principal.visible(user).where(:id => id).first == self
113 122 end
@@ -145,7 +154,6 class Principal < ActiveRecord::Base
145 154 self.hashed_password ||= ''
146 155 self.firstname ||= ''
147 156 self.lastname ||= ''
148 self.mail ||= ''
149 157 true
150 158 end
151 159 end
@@ -81,6 +81,8 class User < Principal
81 81 has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
82 82 has_one :rss_token, lambda {where "action='feeds'"}, :class_name => 'Token'
83 83 has_one :api_token, lambda {where "action='api'"}, :class_name => 'Token'
84 has_one :email_address, lambda {where :is_default => true}, :autosave => true
85 has_many :email_addresses, :dependent => :delete_all
84 86 belongs_to :auth_source
85 87
86 88 scope :logged, lambda { where("#{User.table_name}.status <> #{STATUS_ANONYMOUS}") }
@@ -96,15 +98,12 class User < Principal
96 98 LOGIN_LENGTH_LIMIT = 60
97 99 MAIL_LENGTH_LIMIT = 60
98 100
99 validates_presence_of :login, :firstname, :lastname, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }
101 validates_presence_of :login, :firstname, :lastname, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }
100 102 validates_uniqueness_of :login, :if => Proc.new { |user| user.login_changed? && user.login.present? }, :case_sensitive => false
101 validates_uniqueness_of :mail, :if => Proc.new { |user| user.mail_changed? && user.mail.present? }, :case_sensitive => false
102 103 # Login must contain letters, numbers, underscores only
103 104 validates_format_of :login, :with => /\A[a-z0-9_\-@\.]*\z/i
104 105 validates_length_of :login, :maximum => LOGIN_LENGTH_LIMIT
105 106 validates_length_of :firstname, :lastname, :maximum => 30
106 validates_format_of :mail, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i, :allow_blank => true
107 validates_length_of :mail, :maximum => MAIL_LENGTH_LIMIT, :allow_nil => true
108 107 validates_inclusion_of :mail_notification, :in => MAIL_NOTIFICATION_OPTIONS.collect(&:first), :allow_blank => true
109 108 validate :validate_password_length
110 109 validate do
@@ -113,6 +112,7 class User < Principal
113 112 end
114 113 end
115 114
115 before_validation :instantiate_email_address
116 116 before_create :set_mail_notification
117 117 before_save :generate_password_if_needed, :update_hashed_password
118 118 before_destroy :remove_references_before_destroy
@@ -127,6 +127,14 class User < Principal
127 127 where("#{User.table_name}.id NOT IN (SELECT gu.user_id FROM #{table_name_prefix}groups_users#{table_name_suffix} gu WHERE gu.group_id = ?)", group_id)
128 128 }
129 129 scope :sorted, lambda { order(*User.fields_for_order_statement)}
130 scope :having_mail, lambda {|arg|
131 addresses = Array.wrap(arg).map {|a| a.to_s.downcase}
132 if addresses.any?
133 joins(:email_addresses).where("LOWER(address) IN (?)", addresses).uniq
134 else
135 none
136 end
137 }
130 138
131 139 def set_mail_notification
132 140 self.mail_notification = Setting.default_notification_option if self.mail_notification.blank?
@@ -152,8 +160,21 class User < Principal
152 160 base_reload(*args)
153 161 end
154 162
163 def mail
164 email_address.try(:address)
165 end
166
155 167 def mail=(arg)
156 write_attribute(:mail, arg.to_s.strip)
168 email = email_address || build_email_address
169 email.address = arg
170 end
171
172 def mail_changed?
173 email_address.try(:address_changed?)
174 end
175
176 def mails
177 email_addresses.pluck(:address)
157 178 end
158 179
159 180 def self.find_or_initialize_by_identity_url(url)
@@ -421,7 +442,7 class User < Principal
421 442
422 443 # Makes find_by_mail case-insensitive
423 444 def self.find_by_mail(mail)
424 where("LOWER(mail) = ?", mail.to_s.downcase).first
445 having_mail(mail).first
425 446 end
426 447
427 448 # Returns true if the default admin account can no longer be used
@@ -669,7 +690,7 class User < Principal
669 690 def self.anonymous
670 691 anonymous_user = AnonymousUser.first
671 692 if anonymous_user.nil?
672 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0)
693 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :login => '', :status => 0)
673 694 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
674 695 end
675 696 anonymous_user
@@ -699,6 +720,10 class User < Principal
699 720 end
700 721 end
701 722
723 def instantiate_email_address
724 email_address || build_email_address
725 end
726
702 727 private
703 728
704 729 def generate_password_if_needed
@@ -708,16 +733,13 class User < Principal
708 733 end
709 734 end
710 735
711 # Delete all outstanding password reset tokens on password or email change.
736 # Delete all outstanding password reset tokens on password change.
712 737 # Delete the autologin tokens on password change to prohibit session leakage.
713 738 # This helps to keep the account secure in case the associated email account
714 739 # was compromised.
715 740 def destroy_tokens
716 tokens = []
717 tokens |= ['recovery', 'autologin'] if hashed_password_changed?
718 tokens |= ['recovery'] if mail_changed?
719
720 if tokens.any?
741 if hashed_password_changed?
742 tokens = ['recovery', 'autologin']
721 743 Token.where(:user_id => id, :action => tokens).delete_all
722 744 end
723 745 end
@@ -779,6 +801,7 class AnonymousUser < User
779 801 def logged?; false end
780 802 def admin; false end
781 803 def name(*args); I18n.t(:label_user_anonymous) end
804 def mail=(*args); nil end
782 805 def mail; nil end
783 806 def time_zone; nil end
784 807 def rss_key; nil end
@@ -804,4 +827,9 class AnonymousUser < User
804 827 def destroy
805 828 false
806 829 end
830
831 protected
832
833 def instantiate_email_address
834 end
807 835 end
@@ -41,11 +41,13 class WikiContent < ActiveRecord::Base
41 41 page.nil? ? [] : page.attachments
42 42 end
43 43
44 def notified_users
45 project.notified_users.reject {|user| !visible?(user)}
46 end
47
44 48 # Returns the mail addresses of users that should be notified
45 49 def recipients
46 notified = project.notified_users
47 notified.reject! {|user| !visible?(user)}
48 notified.collect(&:mail)
50 notified_users.collect(&:mail)
49 51 end
50 52
51 53 # Return true if the content is the current page content
@@ -1,4 +1,5
1 1 <div class="contextual">
2 <%= additional_emails_link(@user) %>
2 3 <%= link_to(l(:button_change_password), {:action => 'password'}, :class => 'icon icon-passwd') if @user.change_password_allowed? %>
3 4 <%= call_hook(:view_my_account_contextual, :user => @user)%>
4 5 </div>
@@ -16,6 +16,8
16 16
17 17 <p><%= setting_check_box :lost_password, :label => :label_password_lost %></p>
18 18
19 <p><%= setting_text_field :max_additional_emails, :size => 6 %></p>
20
19 21 <p><%= setting_check_box :openid, :disabled => !Object.const_defined?(:OpenID) %></p>
20 22
21 23 <p><%= setting_check_box :rest_api_enabled %></p>
@@ -1,5 +1,6
1 1 <div class="contextual">
2 2 <%= link_to l(:label_profile), user_path(@user), :class => 'icon icon-user' %>
3 <%= additional_emails_link(@user) %>
3 4 <%= change_status_link(@user) %>
4 5 <%= delete_link user_path(@user) if User.current != @user %>
5 6 </div>
@@ -5,8 +5,7 module ActiveRecord
5 5 include Redmine::I18n
6 6 # Translate attribute names for validation errors display
7 7 def self.human_attribute_name(attr, *args)
8 attr = attr.to_s.sub(/_id$/, '')
9
8 attr = attr.to_s.sub(/_id$/, '').sub(/^.+\./, '')
10 9 l("field_#{name.underscore.gsub('/', '_')}_#{attr}", :default => ["field_#{attr}".to_sym, attr])
11 10 end
12 11 end
@@ -227,6 +227,7 en:
227 227 field_firstname: First name
228 228 field_lastname: Last name
229 229 field_mail: Email
230 field_address: Email
230 231 field_filename: File
231 232 field_filesize: Size
232 233 field_downloads: Downloads
@@ -413,6 +414,7 en:
413 414 setting_force_default_language_for_anonymous: Force default language for anonymous users
414 415 setting_force_default_language_for_loggedin: Force default language for logged-in users
415 416 setting_link_copied_issue: Link issues on copy
417 setting_max_additional_emails: Maximum number of additional email addresses
416 418
417 419 permission_add_project: Create project
418 420 permission_add_subprojects: Create subprojects
@@ -931,6 +933,10 en:
931 933 label_search_attachments_no: Do not search attachments
932 934 label_search_attachments_only: Search attachments only
933 935 label_search_open_issues_only: Open issues only
936 label_email_address_plural: Emails
937 label_email_address_add: Add email address
938 label_enable_notifications: Enable notifications
939 label_disable_notifications: Disable notifications
934 940
935 941 button_login: Login
936 942 button_submit: Submit
@@ -247,6 +247,7 fr:
247 247 field_firstname: PrΓ©nom
248 248 field_lastname: Nom
249 249 field_mail: Email
250 field_address: Email
250 251 field_filename: Fichier
251 252 field_filesize: Taille
252 253 field_downloads: TΓ©lΓ©chargements
@@ -433,6 +434,7 fr:
433 434 setting_force_default_language_for_anonymous: Forcer la langue par dΓ©fault pour les utilisateurs anonymes
434 435 setting_force_default_language_for_loggedin: Forcer la langue par dΓ©fault pour les utilisateurs identifiΓ©s
435 436 setting_link_copied_issue: Lier les demandes lors de la copie
437 setting_max_additional_emails: Nombre maximal d'adresses email additionnelles
436 438
437 439 permission_add_project: CrΓ©er un projet
438 440 permission_add_subprojects: CrΓ©er des sous-projets
@@ -951,6 +953,10 fr:
951 953 label_search_attachments_no: Ne pas rechercher les fichiers
952 954 label_search_attachments_only: Rechercher les fichiers uniquement
953 955 label_search_open_issues_only: Demandes ouvertes uniquement
956 label_email_address_plural: Emails
957 label_email_address_add: Ajouter une adresse email
958 label_enable_notifications: Activer les notifications
959 label_disable_notifications: DΓ©sactiver les notifications
954 960
955 961 button_login: Connexion
956 962 button_submit: Soumettre
@@ -75,6 +75,7 Rails.application.routes.draw do
75 75
76 76 resources :users do
77 77 resources :memberships, :controller => 'principal_memberships'
78 resources :email_addresses, :only => [:index, :create, :update, :destroy]
78 79 end
79 80
80 81 post 'watchers/watch', :to => 'watchers#watch', :as => 'watch'
@@ -36,6 +36,10 unsubscribe:
36 36 password_min_length:
37 37 format: int
38 38 default: 8
39 # Maximum number of additional email addresses per user
40 max_additional_emails:
41 format: int
42 default: 5
39 43 # Maximum lifetime of user sessions in minutes
40 44 session_lifetime:
41 45 format: int
@@ -378,10 +378,10 function setPredecessorFieldsVisibility() {
378 378 }
379 379 }
380 380
381 function showModal(id, width) {
381 function showModal(id, width, title) {
382 382 var el = $('#'+id).first();
383 383 if (el.length === 0 || el.is(':visible')) {return;}
384 var title = el.find('h3.title').text();
384 if (!title) title = el.find('h3.title').text();
385 385 el.dialog({
386 386 width: width,
387 387 modal: true,
@@ -132,6 +132,7 table.list td.checkbox { width: 15px; padding: 2px 0 0 0; }
132 132 table.list td.checkbox input {padding:0px;}
133 133 table.list td.buttons { width: 15%; white-space:nowrap; text-align: right; }
134 134 table.list td.buttons a { padding-right: 0.6em; }
135 table.list td.buttons img {vertical-align:middle;}
135 136 table.list td.reorder {width:15%; white-space:nowrap; text-align:center; }
136 137 table.list caption { text-align: left; padding: 0.5em 0.5em 0.5em 0; }
137 138
@@ -209,7 +210,7 tr.version.shared td.name { background: url(../images/link.png) no-repeat 0% 70%
209 210 tr.version td.date, tr.version td.status, tr.version td.sharing { text-align: center; white-space:nowrap; }
210 211
211 212 tr.user td {width:13%;white-space: nowrap;}
212 tr.user td.username, tr.user td.firstname, tr.user td.lastname, tr.user td.email {text-align:left;}
213 td.username, td.firstname, td.lastname, td.email {text-align:left !important;}
213 214 tr.user td.email { width:18%; }
214 215 tr.user.locked, tr.user.registered { color: #aaa; }
215 216 tr.user.locked a, tr.user.registered a { color: #aaa; }
@@ -1046,6 +1047,7 a.close-icon:hover {background-image:url('../images/close_hl.png');}
1046 1047 .icon-zoom-out { background-image: url(../images/zoom_out.png); }
1047 1048 .icon-passwd { background-image: url(../images/textfield_key.png); }
1048 1049 .icon-test { background-image: url(../images/bullet_go.png); }
1050 .icon-email-add { background-image: url(../images/email_add.png); }
1049 1051
1050 1052 .icon-file { background-image: url(../images/files/default.png); }
1051 1053 .icon-file.text-plain { background-image: url(../images/files/text.png); }
@@ -1,22 +1,4
1 1 ---
2 users_004:
3 created_on: 2006-07-19 19:34:07 +02:00
4 status: 1
5 last_login_on:
6 language: en
7 # password = foo
8 salt: 3126f764c3c5ac61cbfc103f25f934cf
9 hashed_password: 9e4dd7eeb172c12a0691a6d9d3a269f7e9fe671b
10 updated_on: 2006-07-19 19:34:07 +02:00
11 admin: false
12 mail: rhill@somenet.foo
13 lastname: Hill
14 firstname: Robert
15 id: 4
16 auth_source_id:
17 mail_notification: all
18 login: rhill
19 type: User
20 2 users_001:
21 3 created_on: 2006-07-19 19:12:21 +02:00
22 4 status: 1
@@ -27,7 +9,6 users_001:
27 9 hashed_password: b5b6ff9543bf1387374cdfa27a54c96d236a7150
28 10 updated_on: 2006-07-19 22:57:52 +02:00
29 11 admin: true
30 mail: admin@somenet.foo
31 12 lastname: Admin
32 13 firstname: Redmine
33 14 id: 1
@@ -45,7 +26,6 users_002:
45 26 hashed_password: bfbe06043353a677d0215b26a5800d128d5413bc
46 27 updated_on: 2006-07-19 22:42:15 +02:00
47 28 admin: false
48 mail: jsmith@somenet.foo
49 29 lastname: Smith
50 30 firstname: John
51 31 id: 2
@@ -63,7 +43,6 users_003:
63 43 hashed_password: 8f659c8d7c072f189374edacfa90d6abbc26d8ed
64 44 updated_on: 2006-07-19 19:33:19 +02:00
65 45 admin: false
66 mail: dlopper@somenet.foo
67 46 lastname: Lopper
68 47 firstname: Dave
69 48 id: 3
@@ -71,6 +50,23 users_003:
71 50 mail_notification: all
72 51 login: dlopper
73 52 type: User
53 users_004:
54 created_on: 2006-07-19 19:34:07 +02:00
55 status: 1
56 last_login_on:
57 language: en
58 # password = foo
59 salt: 3126f764c3c5ac61cbfc103f25f934cf
60 hashed_password: 9e4dd7eeb172c12a0691a6d9d3a269f7e9fe671b
61 updated_on: 2006-07-19 19:34:07 +02:00
62 admin: false
63 lastname: Hill
64 firstname: Robert
65 id: 4
66 auth_source_id:
67 mail_notification: all
68 login: rhill
69 type: User
74 70 users_005:
75 71 id: 5
76 72 created_on: 2006-07-19 19:33:19 +02:00
@@ -81,7 +77,6 users_005:
81 77 hashed_password: 1
82 78 updated_on: 2006-07-19 19:33:19 +02:00
83 79 admin: false
84 mail: dlopper2@somenet.foo
85 80 lastname: Lopper2
86 81 firstname: Dave2
87 82 auth_source_id:
@@ -97,7 +92,6 users_006:
97 92 hashed_password: 1
98 93 updated_on: 2006-07-19 19:33:19 +02:00
99 94 admin: false
100 mail: ''
101 95 lastname: Anonymous
102 96 firstname: ''
103 97 auth_source_id:
@@ -116,7 +110,6 users_007:
116 110 hashed_password: 8f659c8d7c072f189374edacfa90d6abbc26d8ed
117 111 updated_on: 2006-07-19 19:33:19 +02:00
118 112 admin: false
119 mail: someone@foo.bar
120 113 lastname: One
121 114 firstname: Some
122 115 auth_source_id:
@@ -134,7 +127,6 users_008:
134 127 hashed_password: 8f659c8d7c072f189374edacfa90d6abbc26d8ed
135 128 updated_on: 2006-07-19 19:33:19 +02:00
136 129 admin: false
137 mail: miscuser8@foo.bar
138 130 lastname: Misc
139 131 firstname: User
140 132 auth_source_id:
@@ -150,7 +142,6 users_009:
150 142 hashed_password: 1
151 143 updated_on: 2006-07-19 19:33:19 +02:00
152 144 admin: false
153 mail: miscuser9@foo.bar
154 145 lastname: Misc
155 146 firstname: User
156 147 auth_source_id:
@@ -18,7 +18,7
18 18 require File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class AdminControllerTest < ActionController::TestCase
21 fixtures :projects, :users, :roles
21 fixtures :projects, :users, :email_addresses, :roles
22 22
23 23 def setup
24 24 User.current = nil
@@ -18,7 +18,7
18 18 require File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class DocumentsControllerTest < ActionController::TestCase
21 fixtures :projects, :users, :roles, :members, :member_roles,
21 fixtures :projects, :users, :email_addresses, :roles, :members, :member_roles,
22 22 :enabled_modules, :documents, :enumerations,
23 23 :groups_users, :attachments
24 24
@@ -19,7 +19,7 require File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class IssuesControllerTest < ActionController::TestCase
21 21 fixtures :projects,
22 :users,
22 :users, :email_addresses,
23 23 :roles,
24 24 :members,
25 25 :member_roles,
@@ -20,7 +20,7 require File.expand_path('../../test_helper', __FILE__)
20 20 class IssuesCustomFieldsVisibilityTest < ActionController::TestCase
21 21 tests IssuesController
22 22 fixtures :projects,
23 :users,
23 :users, :email_addresses,
24 24 :roles,
25 25 :members,
26 26 :member_roles,
@@ -18,7 +18,7
18 18 require File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class MailHandlerControllerTest < ActionController::TestCase
21 fixtures :users, :projects, :enabled_modules, :roles, :members, :member_roles, :issues, :issue_statuses,
21 fixtures :users, :email_addresses, :projects, :enabled_modules, :roles, :members, :member_roles, :issues, :issue_statuses,
22 22 :trackers, :projects_trackers, :enumerations
23 23
24 24 FIXTURES_PATH = File.dirname(__FILE__) + '/../fixtures/mail_handler'
@@ -18,7 +18,7
18 18 require File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class MessagesControllerTest < ActionController::TestCase
21 fixtures :projects, :users, :members, :member_roles, :roles, :boards, :messages, :enabled_modules
21 fixtures :projects, :users, :email_addresses, :members, :member_roles, :roles, :boards, :messages, :enabled_modules
22 22
23 23 def setup
24 24 User.current = nil
@@ -18,7 +18,7
18 18 require File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class MyControllerTest < ActionController::TestCase
21 fixtures :users, :user_preferences, :roles, :projects, :members, :member_roles,
21 fixtures :users, :email_addresses, :user_preferences, :roles, :projects, :members, :member_roles,
22 22 :issues, :issue_statuses, :trackers, :enumerations, :custom_fields, :auth_sources
23 23
24 24 def setup
@@ -18,7 +18,7
18 18 require File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class NewsControllerTest < ActionController::TestCase
21 fixtures :projects, :users, :roles, :members, :member_roles,
21 fixtures :projects, :users, :email_addresses, :roles, :members, :member_roles,
22 22 :enabled_modules, :news, :comments,
23 23 :attachments
24 24
@@ -18,7 +18,7
18 18 require File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class ProjectsControllerTest < ActionController::TestCase
21 fixtures :projects, :versions, :users, :roles, :members,
21 fixtures :projects, :versions, :users, :email_addresses, :roles, :members,
22 22 :member_roles, :issues, :journals, :journal_details,
23 23 :trackers, :projects_trackers, :issue_statuses,
24 24 :enabled_modules, :enumerations, :boards, :messages,
@@ -20,7 +20,7 require File.expand_path('../../test_helper', __FILE__)
20 20 class RepositoriesBazaarControllerTest < ActionController::TestCase
21 21 tests RepositoriesController
22 22
23 fixtures :projects, :users, :roles, :members, :member_roles,
23 fixtures :projects, :users, :email_addresses, :roles, :members, :member_roles,
24 24 :repositories, :enabled_modules
25 25
26 26 REPOSITORY_PATH = Rails.root.join('tmp/test/bazaar_repository').to_s
@@ -18,7 +18,7
18 18 require File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class RepositoriesControllerTest < ActionController::TestCase
21 fixtures :projects, :users, :roles, :members, :member_roles, :enabled_modules,
21 fixtures :projects, :users, :email_addresses, :roles, :members, :member_roles, :enabled_modules,
22 22 :repositories, :issues, :issue_statuses, :changesets, :changes,
23 23 :issue_categories, :enumerations, :custom_fields, :custom_values, :trackers
24 24
@@ -20,7 +20,7 require File.expand_path('../../test_helper', __FILE__)
20 20 class RepositoriesCvsControllerTest < ActionController::TestCase
21 21 tests RepositoriesController
22 22
23 fixtures :projects, :users, :roles, :members, :member_roles,
23 fixtures :projects, :users, :email_addresses, :roles, :members, :member_roles,
24 24 :repositories, :enabled_modules
25 25
26 26 REPOSITORY_PATH = Rails.root.join('tmp/test/cvs_repository').to_s
@@ -20,7 +20,7 require File.expand_path('../../test_helper', __FILE__)
20 20 class RepositoriesDarcsControllerTest < ActionController::TestCase
21 21 tests RepositoriesController
22 22
23 fixtures :projects, :users, :roles, :members, :member_roles,
23 fixtures :projects, :users, :email_addresses, :roles, :members, :member_roles,
24 24 :repositories, :enabled_modules
25 25
26 26 REPOSITORY_PATH = Rails.root.join('tmp/test/darcs_repository').to_s
@@ -20,7 +20,7 require File.expand_path('../../test_helper', __FILE__)
20 20 class RepositoriesFilesystemControllerTest < ActionController::TestCase
21 21 tests RepositoriesController
22 22
23 fixtures :projects, :users, :roles, :members, :member_roles,
23 fixtures :projects, :users, :email_addresses, :roles, :members, :member_roles,
24 24 :repositories, :enabled_modules
25 25
26 26 REPOSITORY_PATH = Rails.root.join('tmp/test/filesystem_repository').to_s
@@ -20,7 +20,7 require File.expand_path('../../test_helper', __FILE__)
20 20 class RepositoriesGitControllerTest < ActionController::TestCase
21 21 tests RepositoriesController
22 22
23 fixtures :projects, :users, :roles, :members, :member_roles,
23 fixtures :projects, :users, :email_addresses, :roles, :members, :member_roles,
24 24 :repositories, :enabled_modules
25 25
26 26 REPOSITORY_PATH = Rails.root.join('tmp/test/git_repository').to_s
@@ -20,7 +20,7 require File.expand_path('../../test_helper', __FILE__)
20 20 class RepositoriesMercurialControllerTest < ActionController::TestCase
21 21 tests RepositoriesController
22 22
23 fixtures :projects, :users, :roles, :members, :member_roles,
23 fixtures :projects, :users, :email_addresses, :roles, :members, :member_roles,
24 24 :repositories, :enabled_modules
25 25
26 26 REPOSITORY_PATH = Rails.root.join('tmp/test/mercurial_repository').to_s
@@ -20,7 +20,7 require File.expand_path('../../test_helper', __FILE__)
20 20 class RepositoriesSubversionControllerTest < ActionController::TestCase
21 21 tests RepositoriesController
22 22
23 fixtures :projects, :users, :roles, :members, :member_roles, :enabled_modules,
23 fixtures :projects, :users, :email_addresses, :roles, :members, :member_roles, :enabled_modules,
24 24 :repositories, :issues, :issue_statuses, :changesets, :changes,
25 25 :issue_categories, :enumerations, :custom_fields, :custom_values, :trackers
26 26
@@ -20,7 +20,7 require File.expand_path('../../test_helper', __FILE__)
20 20 class UsersControllerTest < ActionController::TestCase
21 21 include Redmine::I18n
22 22
23 fixtures :users, :projects, :members, :member_roles, :roles,
23 fixtures :users, :email_addresses, :projects, :members, :member_roles, :roles,
24 24 :custom_fields, :custom_values, :groups_users,
25 25 :auth_sources,
26 26 :enabled_modules,
@@ -18,7 +18,7
18 18 require File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class WikiControllerTest < ActionController::TestCase
21 fixtures :projects, :users, :roles, :members, :member_roles,
21 fixtures :projects, :users, :email_addresses, :roles, :members, :member_roles,
22 22 :enabled_modules, :wikis, :wiki_pages, :wiki_contents,
23 23 :wiki_content_versions, :attachments,
24 24 :issues, :issue_statuses
@@ -18,7 +18,7
18 18 require File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class AccountTest < Redmine::IntegrationTest
21 fixtures :users, :roles
21 fixtures :users, :email_addresses, :roles
22 22
23 23 def test_login
24 24 get "/my/page"
@@ -18,7 +18,7
18 18 require File.expand_path('../../../test_helper', __FILE__)
19 19
20 20 class Redmine::ApiTest::UsersTest < Redmine::ApiTest::Base
21 fixtures :users, :members, :member_roles, :roles, :projects
21 fixtures :users, :email_addresses, :members, :member_roles, :roles, :projects
22 22
23 23 test "GET /users.xml should return users" do
24 24 get '/users.xml', {}, credentials('admin')
@@ -19,7 +19,7 require File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class IssuesTest < Redmine::IntegrationTest
21 21 fixtures :projects,
22 :users,
22 :users, :email_addresses,
23 23 :roles,
24 24 :members,
25 25 :member_roles,
@@ -18,7 +18,7
18 18 require File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class UsersTest < Redmine::IntegrationTest
21 fixtures :users
21 fixtures :users, :email_addresses
22 22
23 23 def test_destroy_should_not_accept_get_requests
24 24 assert_no_difference 'User.count' do
@@ -20,7 +20,7 require File.expand_path('../../test_helper', __FILE__)
20 20 class DocumentTest < ActiveSupport::TestCase
21 21 fixtures :projects, :enumerations, :documents, :attachments,
22 22 :enabled_modules,
23 :users, :members, :member_roles, :roles,
23 :users, :email_addresses, :members, :member_roles, :roles,
24 24 :groups_users
25 25
26 26 def test_create
@@ -18,7 +18,7
18 18 require File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class IssueTest < ActiveSupport::TestCase
21 fixtures :projects, :users, :members, :member_roles, :roles,
21 fixtures :projects, :users, :email_addresses, :members, :member_roles, :roles,
22 22 :groups_users,
23 23 :trackers, :projects_trackers,
24 24 :enabled_modules,
@@ -223,6 +223,17 class MailHandlerTest < ActiveSupport::TestCase
223 223 assert_equal 1, issue.watcher_user_ids.size
224 224 end
225 225
226 def test_add_issue_from_additional_email_address
227 user = User.find(2)
228 user.mail = 'mainaddress@somenet.foo'
229 user.save!
230 EmailAddress.create!(:user => user, :address => 'jsmith@somenet.foo')
231
232 issue = submit_email('ticket_on_given_project.eml')
233 assert issue
234 assert_equal user, issue.author
235 end
236
226 237 def test_add_issue_by_unknown_user
227 238 assert_no_difference 'User.count' do
228 239 assert_equal false,
@@ -20,7 +20,7 require File.expand_path('../../test_helper', __FILE__)
20 20 class MailerTest < ActiveSupport::TestCase
21 21 include Redmine::I18n
22 22 include ActionDispatch::Assertions::SelectorAssertions
23 fixtures :projects, :enabled_modules, :issues, :users, :members,
23 fixtures :projects, :enabled_modules, :issues, :users, :email_addresses, :members,
24 24 :member_roles, :roles, :documents, :attachments, :news,
25 25 :tokens, :journals, :journal_details, :changesets,
26 26 :trackers, :projects_trackers,
@@ -298,6 +298,14 class MailerTest < ActiveSupport::TestCase
298 298 assert last_email.bcc.include?('dlopper@somenet.foo')
299 299 end
300 300
301 def test_issue_add_should_send_mail_to_all_user_email_address
302 EmailAddress.create!(:user_id => 3, :address => 'otheremail@somenet.foo')
303 issue = Issue.find(1)
304 assert Mailer.deliver_issue_add(issue)
305 assert last_email.bcc.include?('dlopper@somenet.foo')
306 assert last_email.bcc.include?('otheremail@somenet.foo')
307 end
308
301 309 test "#issue_add should not notify project members that are not allow to view the issue" do
302 310 issue = Issue.find(1)
303 311 Role.find(2).remove_permission!(:view_issues)
@@ -771,6 +779,30 class MailerTest < ActiveSupport::TestCase
771 779 ActionMailer::Base.delivery_method = :test
772 780 end
773 781
782 def test_email_addresses_should_keep_addresses
783 assert_equal ["foo@example.net"],
784 Mailer.email_addresses("foo@example.net")
785
786 assert_equal ["foo@example.net", "bar@example.net"],
787 Mailer.email_addresses(["foo@example.net", "bar@example.net"])
788 end
789
790 def test_email_addresses_should_replace_users_with_their_email_addresses
791 assert_equal ["admin@somenet.foo"],
792 Mailer.email_addresses(User.find(1))
793
794 assert_equal ["admin@somenet.foo", "jsmith@somenet.foo"],
795 Mailer.email_addresses(User.where(:id => [1,2])).sort
796 end
797
798 def test_email_addresses_should_include_notified_emails_addresses_only
799 EmailAddress.create!(:user_id => 2, :address => "another@somenet.foo", :notify => false)
800 EmailAddress.create!(:user_id => 2, :address => "another2@somenet.foo")
801
802 assert_equal ["another2@somenet.foo", "jsmith@somenet.foo"],
803 Mailer.email_addresses(User.find(2)).sort
804 end
805
774 806 private
775 807
776 808 def last_email
@@ -18,7 +18,7
18 18 require File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class UserTest < ActiveSupport::TestCase
21 fixtures :users, :members, :projects, :roles, :member_roles, :auth_sources,
21 fixtures :users, :email_addresses, :members, :projects, :roles, :member_roles, :auth_sources,
22 22 :trackers, :issue_statuses,
23 23 :projects_trackers,
24 24 :watchers,
@@ -57,11 +57,41 class UserTest < ActiveSupport::TestCase
57 57 assert_equal "foo@bar.com", u.mail
58 58 end
59 59
60 def test_mail_validation
61 u = User.new
60 def test_should_create_email_address
61 u = User.new(:firstname => "new", :lastname => "user")
62 u.login = "create_email_address"
63 u.mail = "defaultemail@somenet.foo"
64 assert u.save
65 u.reload
66 assert u.email_address
67 assert_equal "defaultemail@somenet.foo", u.email_address.address
68 assert_equal true, u.email_address.is_default
69 assert_equal true, u.email_address.notify
70 end
71
72 def test_should_not_create_user_without_mail
73 set_language_if_valid 'en'
74 u = User.new(:firstname => "new", :lastname => "user")
75 u.login = "user_without_mail"
76 assert !u.save
77 assert_equal ["Email #{I18n.translate('activerecord.errors.messages.blank')}"], u.errors.full_messages
78 end
79
80 def test_should_not_create_user_with_blank_mail
81 set_language_if_valid 'en'
82 u = User.new(:firstname => "new", :lastname => "user")
83 u.login = "user_with_blank_mail"
84 u.mail = ''
85 assert !u.save
86 assert_equal ["Email #{I18n.translate('activerecord.errors.messages.blank')}"], u.errors.full_messages
87 end
88
89 def test_should_not_update_user_with_blank_mail
90 set_language_if_valid 'en'
91 u = User.find(2)
62 92 u.mail = ''
63 assert !u.valid?
64 assert_include I18n.translate('activerecord.errors.messages.blank'), u.errors[:mail]
93 assert !u.save
94 assert_equal ["Email #{I18n.translate('activerecord.errors.messages.blank')}"], u.errors.full_messages
65 95 end
66 96
67 97 def test_login_length_validation
@@ -151,6 +181,7 class UserTest < ActiveSupport::TestCase
151 181 end
152 182
153 183 def test_mail_uniqueness_should_not_be_case_sensitive
184 set_language_if_valid 'en'
154 185 u = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
155 186 u.login = 'newuser1'
156 187 u.password, u.password_confirmation = "password", "password"
@@ -160,7 +191,7 class UserTest < ActiveSupport::TestCase
160 191 u.login = 'newuser2'
161 192 u.password, u.password_confirmation = "password", "password"
162 193 assert !u.save
163 assert_include I18n.translate('activerecord.errors.messages.taken'), u.errors[:mail]
194 assert_include "Email #{I18n.translate('activerecord.errors.messages.taken')}", u.errors.full_messages
164 195 end
165 196
166 197 def test_update
@@ -677,7 +708,7 class UserTest < ActiveSupport::TestCase
677 708 assert_kind_of AnonymousUser, anon1
678 709 anon2 = AnonymousUser.create(
679 710 :lastname => 'Anonymous', :firstname => '',
680 :mail => '', :login => '', :status => 0)
711 :login => '', :status => 0)
681 712 assert_equal 1, anon2.errors.count
682 713 end
683 714
@@ -18,7 +18,7
18 18 require File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class WatcherTest < ActiveSupport::TestCase
21 fixtures :projects, :users, :members, :member_roles, :roles, :enabled_modules,
21 fixtures :projects, :users, :email_addresses, :members, :member_roles, :roles, :enabled_modules,
22 22 :issues, :issue_statuses, :enumerations, :trackers, :projects_trackers,
23 23 :boards, :messages,
24 24 :wikis, :wiki_pages,
General Comments 0
You need to be logged in to leave comments. Login now