##// 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
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
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 @status = params[:status] || 1
42 @status = params[:status] || 1
43
43
44 scope = User.logged.status(@status)
44 scope = User.logged.status(@status).preload(:email_address)
45 scope = scope.like(params[:name]) if params[:name].present?
45 scope = scope.like(params[:name]) if params[:name].present?
46 scope = scope.in_group(params[:group_id]) if params[:group_id].present?
46 scope = scope.in_group(params[:group_id]) if params[:group_id].present?
47
47
@@ -42,6 +42,12 module UsersHelper
42 end
42 end
43 end
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 def user_settings_tabs
51 def user_settings_tabs
46 tabs = [{:name => 'general', :partial => 'users/general', :label => :label_general},
52 tabs = [{:name => 'general', :partial => 'users/general', :label => :label_general},
47 {:name => 'memberships', :partial => 'users/memberships', :label => :label_project_plural}
53 {:name => 'memberships', :partial => 'users/memberships', :label => :label_project_plural}
@@ -60,6 +60,10 class Document < ActiveRecord::Base
60 @updated_on
60 @updated_on
61 end
61 end
62
62
63 def notified_users
64 project.notified_users.reject {|user| !visible?(user)}
65 end
66
63 private
67 private
64
68
65 def send_notification
69 def send_notification
@@ -306,7 +306,7 class MailHandler < ActionMailer::Base
306 if user.allowed_to?("add_#{obj.class.name.underscore}_watchers".to_sym, obj.project)
306 if user.allowed_to?("add_#{obj.class.name.underscore}_watchers".to_sym, obj.project)
307 addresses = [email.to, email.cc].flatten.compact.uniq.collect {|a| a.strip.downcase}
307 addresses = [email.to, email.cc].flatten.compact.uniq.collect {|a| a.strip.downcase}
308 unless addresses.empty?
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 obj.add_watcher(w)
310 obj.add_watcher(w)
311 end
311 end
312 end
312 end
@@ -39,8 +39,8 class Mailer < ActionMailer::Base
39 @issue = issue
39 @issue = issue
40 @users = to_users + cc_users
40 @users = to_users + cc_users
41 @issue_url = url_for(:controller => 'issues', :action => 'show', :id => issue)
41 @issue_url = url_for(:controller => 'issues', :action => 'show', :id => issue)
42 mail :to => to_users.map(&:mail),
42 mail :to => to_users,
43 :cc => cc_users.map(&:mail),
43 :cc => cc_users,
44 :subject => "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] (#{issue.status.name}) #{issue.subject}"
44 :subject => "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] (#{issue.status.name}) #{issue.subject}"
45 end
45 end
46
46
@@ -71,8 +71,8 class Mailer < ActionMailer::Base
71 @journal = journal
71 @journal = journal
72 @journal_details = journal.visible_details(@users.first)
72 @journal_details = journal.visible_details(@users.first)
73 @issue_url = url_for(:controller => 'issues', :action => 'show', :id => issue, :anchor => "change-#{journal.id}")
73 @issue_url = url_for(:controller => 'issues', :action => 'show', :id => issue, :anchor => "change-#{journal.id}")
74 mail :to => to_users.map(&:mail),
74 mail :to => to_users,
75 :cc => cc_users.map(&:mail),
75 :cc => cc_users,
76 :subject => s
76 :subject => s
77 end
77 end
78
78
@@ -95,7 +95,7 class Mailer < ActionMailer::Base
95 @issues_url = url_for(:controller => 'issues', :action => 'index',
95 @issues_url = url_for(:controller => 'issues', :action => 'index',
96 :set_filter => 1, :assigned_to_id => user.id,
96 :set_filter => 1, :assigned_to_id => user.id,
97 :sort => 'due_date:asc')
97 :sort => 'due_date:asc')
98 mail :to => user.mail,
98 mail :to => user,
99 :subject => l(:mail_subject_reminder, :count => issues.size, :days => days)
99 :subject => l(:mail_subject_reminder, :count => issues.size, :days => days)
100 end
100 end
101
101
@@ -109,7 +109,7 class Mailer < ActionMailer::Base
109 @author = User.current
109 @author = User.current
110 @document = document
110 @document = document
111 @document_url = url_for(:controller => 'documents', :action => 'show', :id => document)
111 @document_url = url_for(:controller => 'documents', :action => 'show', :id => document)
112 mail :to => document.recipients,
112 mail :to => document.notified_users,
113 :subject => "[#{document.project.name}] #{l(:label_document_new)}: #{document.title}"
113 :subject => "[#{document.project.name}] #{l(:label_document_new)}: #{document.title}"
114 end
114 end
115
115
@@ -127,15 +127,15 class Mailer < ActionMailer::Base
127 when 'Project'
127 when 'Project'
128 added_to_url = url_for(:controller => 'files', :action => 'index', :project_id => container)
128 added_to_url = url_for(:controller => 'files', :action => 'index', :project_id => container)
129 added_to = "#{l(:label_project)}: #{container}"
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 when 'Version'
131 when 'Version'
132 added_to_url = url_for(:controller => 'files', :action => 'index', :project_id => container.project)
132 added_to_url = url_for(:controller => 'files', :action => 'index', :project_id => container.project)
133 added_to = "#{l(:label_version)}: #{container.name}"
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 when 'Document'
135 when 'Document'
136 added_to_url = url_for(:controller => 'documents', :action => 'show', :id => container.id)
136 added_to_url = url_for(:controller => 'documents', :action => 'show', :id => container.id)
137 added_to = "#{l(:label_document)}: #{container.title}"
137 added_to = "#{l(:label_document)}: #{container.title}"
138 recipients = container.recipients
138 recipients = container.notified_users
139 end
139 end
140 redmine_headers 'Project' => container.project.identifier
140 redmine_headers 'Project' => container.project.identifier
141 @attachments = attachments
141 @attachments = attachments
@@ -157,8 +157,8 class Mailer < ActionMailer::Base
157 references news
157 references news
158 @news = news
158 @news = news
159 @news_url = url_for(:controller => 'news', :action => 'show', :id => news)
159 @news_url = url_for(:controller => 'news', :action => 'show', :id => news)
160 mail :to => news.recipients,
160 mail :to => news.notified_users,
161 :cc => news.cc_for_added_news,
161 :cc => news.notified_watchers_for_added_news,
162 :subject => "[#{news.project.name}] #{l(:label_news)}: #{news.title}"
162 :subject => "[#{news.project.name}] #{l(:label_news)}: #{news.title}"
163 end
163 end
164
164
@@ -176,8 +176,8 class Mailer < ActionMailer::Base
176 @news = news
176 @news = news
177 @comment = comment
177 @comment = comment
178 @news_url = url_for(:controller => 'news', :action => 'show', :id => news)
178 @news_url = url_for(:controller => 'news', :action => 'show', :id => news)
179 mail :to => news.recipients,
179 mail :to => news.notified_users,
180 :cc => news.watcher_recipients,
180 :cc => news.notified_watchers,
181 :subject => "Re: [#{news.project.name}] #{l(:label_news)}: #{news.title}"
181 :subject => "Re: [#{news.project.name}] #{l(:label_news)}: #{news.title}"
182 end
182 end
183
183
@@ -192,8 +192,8 class Mailer < ActionMailer::Base
192 @author = message.author
192 @author = message.author
193 message_id message
193 message_id message
194 references message.root
194 references message.root
195 recipients = message.recipients
195 recipients = message.notified_users
196 cc = ((message.root.watcher_recipients + message.board.watcher_recipients).uniq - recipients)
196 cc = ((message.root.notified_watchers + message.board.notified_watchers).uniq - recipients)
197 @message = message
197 @message = message
198 @message_url = url_for(message.event_url)
198 @message_url = url_for(message.event_url)
199 mail :to => recipients,
199 mail :to => recipients,
@@ -211,8 +211,8 class Mailer < ActionMailer::Base
211 'Wiki-Page-Id' => wiki_content.page.id
211 'Wiki-Page-Id' => wiki_content.page.id
212 @author = wiki_content.author
212 @author = wiki_content.author
213 message_id wiki_content
213 message_id wiki_content
214 recipients = wiki_content.recipients
214 recipients = wiki_content.notified_users
215 cc = wiki_content.page.wiki.watcher_recipients - recipients
215 cc = wiki_content.page.wiki.notified_watchers - recipients
216 @wiki_content = wiki_content
216 @wiki_content = wiki_content
217 @wiki_content_url = url_for(:controller => 'wiki', :action => 'show',
217 @wiki_content_url = url_for(:controller => 'wiki', :action => 'show',
218 :project_id => wiki_content.project,
218 :project_id => wiki_content.project,
@@ -232,8 +232,8 class Mailer < ActionMailer::Base
232 'Wiki-Page-Id' => wiki_content.page.id
232 'Wiki-Page-Id' => wiki_content.page.id
233 @author = wiki_content.author
233 @author = wiki_content.author
234 message_id wiki_content
234 message_id wiki_content
235 recipients = wiki_content.recipients
235 recipients = wiki_content.notified_users
236 cc = wiki_content.page.wiki.watcher_recipients + wiki_content.page.watcher_recipients - recipients
236 cc = wiki_content.page.wiki.notified_watchers + wiki_content.page.notified_watchers - recipients
237 @wiki_content = wiki_content
237 @wiki_content = wiki_content
238 @wiki_content_url = url_for(:controller => 'wiki', :action => 'show',
238 @wiki_content_url = url_for(:controller => 'wiki', :action => 'show',
239 :project_id => wiki_content.project,
239 :project_id => wiki_content.project,
@@ -267,7 +267,7 class Mailer < ActionMailer::Base
267 # Mailer.account_activation_request(user).deliver => sends an email to all active administrators
267 # Mailer.account_activation_request(user).deliver => sends an email to all active administrators
268 def account_activation_request(user)
268 def account_activation_request(user)
269 # Send the email to all active administrators
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 @user = user
271 @user = user
272 @url = url_for(:controller => 'users', :action => 'index',
272 @url = url_for(:controller => 'users', :action => 'index',
273 :status => User::STATUS_REGISTERED,
273 :status => User::STATUS_REGISTERED,
@@ -378,12 +378,20 class Mailer < ActionMailer::Base
378 'From' => Setting.mail_from,
378 'From' => Setting.mail_from,
379 'List-Id' => "<#{Setting.mail_from.to_s.gsub('@', '.')}>"
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 # Removes the author from the recipients and cc
388 # Removes the author from the recipients and cc
382 # if the author does not want to receive notifications
389 # if the author does not want to receive notifications
383 # about what the author do
390 # about what the author do
384 if @author && @author.logged? && @author.pref.no_self_notified
391 if @author && @author.logged? && @author.pref.no_self_notified
385 headers[:to].delete(@author.mail) if headers[:to].is_a?(Array)
392 addresses = @author.mails
386 headers[:cc].delete(@author.mail) if headers[:cc].is_a?(Array)
393 headers[:to] -= addresses if headers[:to].is_a?(Array)
394 headers[:cc] -= addresses if headers[:cc].is_a?(Array)
387 end
395 end
388
396
389 if @author && @author.logged?
397 if @author && @author.logged?
@@ -447,6 +455,25 class Mailer < ActionMailer::Base
447 end
455 end
448 end
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 private
477 private
451
478
452 # Appends a Redmine header field (name is prepended with 'X-Redmine-')
479 # Appends a Redmine header field (name is prepended with 'X-Redmine-')
@@ -103,6 +103,10 class Message < ActiveRecord::Base
103 usr && usr.logged? && (usr.allowed_to?(:delete_messages, project) || (self.author == usr && usr.allowed_to?(:delete_own_messages, project)))
103 usr && usr.logged? && (usr.allowed_to?(:delete_messages, project) || (self.author == usr && usr.allowed_to?(:delete_own_messages, project)))
104 end
104 end
105
105
106 def notified_users
107 project.notified_users.reject {|user| !visible?(user)}
108 end
109
106 private
110 private
107
111
108 def add_author_as_watcher
112 def add_author_as_watcher
@@ -54,20 +54,29 class News < ActiveRecord::Base
54 user.allowed_to?(:comment_news, project)
54 user.allowed_to?(:comment_news, project)
55 end
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 def recipients
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 end
63 end
60
64
61 # Returns the email addresses that should be cc'd when a new news is added
65 # Returns the users that should be cc'd when a new news is added
62 def cc_for_added_news
66 def notified_watchers_for_added_news
63 cc = []
67 watchers = []
64 if m = project.enabled_module('news')
68 if m = project.enabled_module('news')
65 cc = m.notified_watchers
69 watchers = m.notified_watchers
66 unless project.is_public?
70 unless project.is_public?
67 cc = cc.select {|user| project.users.include?(user)}
71 watchers = watchers.select {|user| project.users.include?(user)}
68 end
72 end
69 end
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 end
80 end
72
81
73 # returns latest news for projects visible by user
82 # returns latest news for projects visible by user
@@ -68,7 +68,8 class Principal < ActiveRecord::Base
68 where({})
68 where({})
69 else
69 else
70 pattern = "%#{q}%"
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 params = {:p => pattern}
73 params = {:p => pattern}
73 if q =~ /^(.+)\s+(.+)$/
74 if q =~ /^(.+)\s+(.+)$/
74 a, b = "#{$1}%", "#{$2}%"
75 a, b = "#{$1}%", "#{$2}%"
@@ -108,6 +109,14 class Principal < ActiveRecord::Base
108 to_s
109 to_s
109 end
110 end
110
111
112 def mail=(*args)
113 nil
114 end
115
116 def mail
117 nil
118 end
119
111 def visible?(user=User.current)
120 def visible?(user=User.current)
112 Principal.visible(user).where(:id => id).first == self
121 Principal.visible(user).where(:id => id).first == self
113 end
122 end
@@ -145,7 +154,6 class Principal < ActiveRecord::Base
145 self.hashed_password ||= ''
154 self.hashed_password ||= ''
146 self.firstname ||= ''
155 self.firstname ||= ''
147 self.lastname ||= ''
156 self.lastname ||= ''
148 self.mail ||= ''
149 true
157 true
150 end
158 end
151 end
159 end
@@ -81,6 +81,8 class User < Principal
81 has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
81 has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
82 has_one :rss_token, lambda {where "action='feeds'"}, :class_name => 'Token'
82 has_one :rss_token, lambda {where "action='feeds'"}, :class_name => 'Token'
83 has_one :api_token, lambda {where "action='api'"}, :class_name => 'Token'
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 belongs_to :auth_source
86 belongs_to :auth_source
85
87
86 scope :logged, lambda { where("#{User.table_name}.status <> #{STATUS_ANONYMOUS}") }
88 scope :logged, lambda { where("#{User.table_name}.status <> #{STATUS_ANONYMOUS}") }
@@ -96,15 +98,12 class User < Principal
96 LOGIN_LENGTH_LIMIT = 60
98 LOGIN_LENGTH_LIMIT = 60
97 MAIL_LENGTH_LIMIT = 60
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 validates_uniqueness_of :login, :if => Proc.new { |user| user.login_changed? && user.login.present? }, :case_sensitive => false
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 # Login must contain letters, numbers, underscores only
103 # Login must contain letters, numbers, underscores only
103 validates_format_of :login, :with => /\A[a-z0-9_\-@\.]*\z/i
104 validates_format_of :login, :with => /\A[a-z0-9_\-@\.]*\z/i
104 validates_length_of :login, :maximum => LOGIN_LENGTH_LIMIT
105 validates_length_of :login, :maximum => LOGIN_LENGTH_LIMIT
105 validates_length_of :firstname, :lastname, :maximum => 30
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 validates_inclusion_of :mail_notification, :in => MAIL_NOTIFICATION_OPTIONS.collect(&:first), :allow_blank => true
107 validates_inclusion_of :mail_notification, :in => MAIL_NOTIFICATION_OPTIONS.collect(&:first), :allow_blank => true
109 validate :validate_password_length
108 validate :validate_password_length
110 validate do
109 validate do
@@ -113,6 +112,7 class User < Principal
113 end
112 end
114 end
113 end
115
114
115 before_validation :instantiate_email_address
116 before_create :set_mail_notification
116 before_create :set_mail_notification
117 before_save :generate_password_if_needed, :update_hashed_password
117 before_save :generate_password_if_needed, :update_hashed_password
118 before_destroy :remove_references_before_destroy
118 before_destroy :remove_references_before_destroy
@@ -127,6 +127,14 class User < Principal
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)
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 scope :sorted, lambda { order(*User.fields_for_order_statement)}
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 def set_mail_notification
139 def set_mail_notification
132 self.mail_notification = Setting.default_notification_option if self.mail_notification.blank?
140 self.mail_notification = Setting.default_notification_option if self.mail_notification.blank?
@@ -152,8 +160,21 class User < Principal
152 base_reload(*args)
160 base_reload(*args)
153 end
161 end
154
162
163 def mail
164 email_address.try(:address)
165 end
166
155 def mail=(arg)
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 end
178 end
158
179
159 def self.find_or_initialize_by_identity_url(url)
180 def self.find_or_initialize_by_identity_url(url)
@@ -421,7 +442,7 class User < Principal
421
442
422 # Makes find_by_mail case-insensitive
443 # Makes find_by_mail case-insensitive
423 def self.find_by_mail(mail)
444 def self.find_by_mail(mail)
424 where("LOWER(mail) = ?", mail.to_s.downcase).first
445 having_mail(mail).first
425 end
446 end
426
447
427 # Returns true if the default admin account can no longer be used
448 # Returns true if the default admin account can no longer be used
@@ -669,7 +690,7 class User < Principal
669 def self.anonymous
690 def self.anonymous
670 anonymous_user = AnonymousUser.first
691 anonymous_user = AnonymousUser.first
671 if anonymous_user.nil?
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 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
694 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
674 end
695 end
675 anonymous_user
696 anonymous_user
@@ -699,6 +720,10 class User < Principal
699 end
720 end
700 end
721 end
701
722
723 def instantiate_email_address
724 email_address || build_email_address
725 end
726
702 private
727 private
703
728
704 def generate_password_if_needed
729 def generate_password_if_needed
@@ -708,16 +733,13 class User < Principal
708 end
733 end
709 end
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 # Delete the autologin tokens on password change to prohibit session leakage.
737 # Delete the autologin tokens on password change to prohibit session leakage.
713 # This helps to keep the account secure in case the associated email account
738 # This helps to keep the account secure in case the associated email account
714 # was compromised.
739 # was compromised.
715 def destroy_tokens
740 def destroy_tokens
716 tokens = []
741 if hashed_password_changed?
717 tokens |= ['recovery', 'autologin'] if hashed_password_changed?
742 tokens = ['recovery', 'autologin']
718 tokens |= ['recovery'] if mail_changed?
719
720 if tokens.any?
721 Token.where(:user_id => id, :action => tokens).delete_all
743 Token.where(:user_id => id, :action => tokens).delete_all
722 end
744 end
723 end
745 end
@@ -779,6 +801,7 class AnonymousUser < User
779 def logged?; false end
801 def logged?; false end
780 def admin; false end
802 def admin; false end
781 def name(*args); I18n.t(:label_user_anonymous) end
803 def name(*args); I18n.t(:label_user_anonymous) end
804 def mail=(*args); nil end
782 def mail; nil end
805 def mail; nil end
783 def time_zone; nil end
806 def time_zone; nil end
784 def rss_key; nil end
807 def rss_key; nil end
@@ -804,4 +827,9 class AnonymousUser < User
804 def destroy
827 def destroy
805 false
828 false
806 end
829 end
830
831 protected
832
833 def instantiate_email_address
834 end
807 end
835 end
@@ -41,11 +41,13 class WikiContent < ActiveRecord::Base
41 page.nil? ? [] : page.attachments
41 page.nil? ? [] : page.attachments
42 end
42 end
43
43
44 def notified_users
45 project.notified_users.reject {|user| !visible?(user)}
46 end
47
44 # Returns the mail addresses of users that should be notified
48 # Returns the mail addresses of users that should be notified
45 def recipients
49 def recipients
46 notified = project.notified_users
50 notified_users.collect(&:mail)
47 notified.reject! {|user| !visible?(user)}
48 notified.collect(&:mail)
49 end
51 end
50
52
51 # Return true if the content is the current page content
53 # Return true if the content is the current page content
@@ -1,4 +1,5
1 <div class="contextual">
1 <div class="contextual">
2 <%= additional_emails_link(@user) %>
2 <%= link_to(l(:button_change_password), {:action => 'password'}, :class => 'icon icon-passwd') if @user.change_password_allowed? %>
3 <%= link_to(l(:button_change_password), {:action => 'password'}, :class => 'icon icon-passwd') if @user.change_password_allowed? %>
3 <%= call_hook(:view_my_account_contextual, :user => @user)%>
4 <%= call_hook(:view_my_account_contextual, :user => @user)%>
4 </div>
5 </div>
@@ -16,6 +16,8
16
16
17 <p><%= setting_check_box :lost_password, :label => :label_password_lost %></p>
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 <p><%= setting_check_box :openid, :disabled => !Object.const_defined?(:OpenID) %></p>
21 <p><%= setting_check_box :openid, :disabled => !Object.const_defined?(:OpenID) %></p>
20
22
21 <p><%= setting_check_box :rest_api_enabled %></p>
23 <p><%= setting_check_box :rest_api_enabled %></p>
@@ -1,5 +1,6
1 <div class="contextual">
1 <div class="contextual">
2 <%= link_to l(:label_profile), user_path(@user), :class => 'icon icon-user' %>
2 <%= link_to l(:label_profile), user_path(@user), :class => 'icon icon-user' %>
3 <%= additional_emails_link(@user) %>
3 <%= change_status_link(@user) %>
4 <%= change_status_link(@user) %>
4 <%= delete_link user_path(@user) if User.current != @user %>
5 <%= delete_link user_path(@user) if User.current != @user %>
5 </div>
6 </div>
@@ -5,8 +5,7 module ActiveRecord
5 include Redmine::I18n
5 include Redmine::I18n
6 # Translate attribute names for validation errors display
6 # Translate attribute names for validation errors display
7 def self.human_attribute_name(attr, *args)
7 def self.human_attribute_name(attr, *args)
8 attr = attr.to_s.sub(/_id$/, '')
8 attr = attr.to_s.sub(/_id$/, '').sub(/^.+\./, '')
9
10 l("field_#{name.underscore.gsub('/', '_')}_#{attr}", :default => ["field_#{attr}".to_sym, attr])
9 l("field_#{name.underscore.gsub('/', '_')}_#{attr}", :default => ["field_#{attr}".to_sym, attr])
11 end
10 end
12 end
11 end
@@ -227,6 +227,7 en:
227 field_firstname: First name
227 field_firstname: First name
228 field_lastname: Last name
228 field_lastname: Last name
229 field_mail: Email
229 field_mail: Email
230 field_address: Email
230 field_filename: File
231 field_filename: File
231 field_filesize: Size
232 field_filesize: Size
232 field_downloads: Downloads
233 field_downloads: Downloads
@@ -413,6 +414,7 en:
413 setting_force_default_language_for_anonymous: Force default language for anonymous users
414 setting_force_default_language_for_anonymous: Force default language for anonymous users
414 setting_force_default_language_for_loggedin: Force default language for logged-in users
415 setting_force_default_language_for_loggedin: Force default language for logged-in users
415 setting_link_copied_issue: Link issues on copy
416 setting_link_copied_issue: Link issues on copy
417 setting_max_additional_emails: Maximum number of additional email addresses
416
418
417 permission_add_project: Create project
419 permission_add_project: Create project
418 permission_add_subprojects: Create subprojects
420 permission_add_subprojects: Create subprojects
@@ -931,6 +933,10 en:
931 label_search_attachments_no: Do not search attachments
933 label_search_attachments_no: Do not search attachments
932 label_search_attachments_only: Search attachments only
934 label_search_attachments_only: Search attachments only
933 label_search_open_issues_only: Open issues only
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 button_login: Login
941 button_login: Login
936 button_submit: Submit
942 button_submit: Submit
@@ -247,6 +247,7 fr:
247 field_firstname: PrΓ©nom
247 field_firstname: PrΓ©nom
248 field_lastname: Nom
248 field_lastname: Nom
249 field_mail: Email
249 field_mail: Email
250 field_address: Email
250 field_filename: Fichier
251 field_filename: Fichier
251 field_filesize: Taille
252 field_filesize: Taille
252 field_downloads: TΓ©lΓ©chargements
253 field_downloads: TΓ©lΓ©chargements
@@ -433,6 +434,7 fr:
433 setting_force_default_language_for_anonymous: Forcer la langue par dΓ©fault pour les utilisateurs anonymes
434 setting_force_default_language_for_anonymous: Forcer la langue par dΓ©fault pour les utilisateurs anonymes
434 setting_force_default_language_for_loggedin: Forcer la langue par dΓ©fault pour les utilisateurs identifiΓ©s
435 setting_force_default_language_for_loggedin: Forcer la langue par dΓ©fault pour les utilisateurs identifiΓ©s
435 setting_link_copied_issue: Lier les demandes lors de la copie
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 permission_add_project: CrΓ©er un projet
439 permission_add_project: CrΓ©er un projet
438 permission_add_subprojects: CrΓ©er des sous-projets
440 permission_add_subprojects: CrΓ©er des sous-projets
@@ -951,6 +953,10 fr:
951 label_search_attachments_no: Ne pas rechercher les fichiers
953 label_search_attachments_no: Ne pas rechercher les fichiers
952 label_search_attachments_only: Rechercher les fichiers uniquement
954 label_search_attachments_only: Rechercher les fichiers uniquement
953 label_search_open_issues_only: Demandes ouvertes uniquement
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 button_login: Connexion
961 button_login: Connexion
956 button_submit: Soumettre
962 button_submit: Soumettre
@@ -75,6 +75,7 Rails.application.routes.draw do
75
75
76 resources :users do
76 resources :users do
77 resources :memberships, :controller => 'principal_memberships'
77 resources :memberships, :controller => 'principal_memberships'
78 resources :email_addresses, :only => [:index, :create, :update, :destroy]
78 end
79 end
79
80
80 post 'watchers/watch', :to => 'watchers#watch', :as => 'watch'
81 post 'watchers/watch', :to => 'watchers#watch', :as => 'watch'
@@ -36,6 +36,10 unsubscribe:
36 password_min_length:
36 password_min_length:
37 format: int
37 format: int
38 default: 8
38 default: 8
39 # Maximum number of additional email addresses per user
40 max_additional_emails:
41 format: int
42 default: 5
39 # Maximum lifetime of user sessions in minutes
43 # Maximum lifetime of user sessions in minutes
40 session_lifetime:
44 session_lifetime:
41 format: int
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 var el = $('#'+id).first();
382 var el = $('#'+id).first();
383 if (el.length === 0 || el.is(':visible')) {return;}
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 el.dialog({
385 el.dialog({
386 width: width,
386 width: width,
387 modal: true,
387 modal: true,
@@ -132,6 +132,7 table.list td.checkbox { width: 15px; padding: 2px 0 0 0; }
132 table.list td.checkbox input {padding:0px;}
132 table.list td.checkbox input {padding:0px;}
133 table.list td.buttons { width: 15%; white-space:nowrap; text-align: right; }
133 table.list td.buttons { width: 15%; white-space:nowrap; text-align: right; }
134 table.list td.buttons a { padding-right: 0.6em; }
134 table.list td.buttons a { padding-right: 0.6em; }
135 table.list td.buttons img {vertical-align:middle;}
135 table.list td.reorder {width:15%; white-space:nowrap; text-align:center; }
136 table.list td.reorder {width:15%; white-space:nowrap; text-align:center; }
136 table.list caption { text-align: left; padding: 0.5em 0.5em 0.5em 0; }
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 tr.version td.date, tr.version td.status, tr.version td.sharing { text-align: center; white-space:nowrap; }
210 tr.version td.date, tr.version td.status, tr.version td.sharing { text-align: center; white-space:nowrap; }
210
211
211 tr.user td {width:13%;white-space: nowrap;}
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 tr.user td.email { width:18%; }
214 tr.user td.email { width:18%; }
214 tr.user.locked, tr.user.registered { color: #aaa; }
215 tr.user.locked, tr.user.registered { color: #aaa; }
215 tr.user.locked a, tr.user.registered a { color: #aaa; }
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 .icon-zoom-out { background-image: url(../images/zoom_out.png); }
1047 .icon-zoom-out { background-image: url(../images/zoom_out.png); }
1047 .icon-passwd { background-image: url(../images/textfield_key.png); }
1048 .icon-passwd { background-image: url(../images/textfield_key.png); }
1048 .icon-test { background-image: url(../images/bullet_go.png); }
1049 .icon-test { background-image: url(../images/bullet_go.png); }
1050 .icon-email-add { background-image: url(../images/email_add.png); }
1049
1051
1050 .icon-file { background-image: url(../images/files/default.png); }
1052 .icon-file { background-image: url(../images/files/default.png); }
1051 .icon-file.text-plain { background-image: url(../images/files/text.png); }
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 users_001:
2 users_001:
21 created_on: 2006-07-19 19:12:21 +02:00
3 created_on: 2006-07-19 19:12:21 +02:00
22 status: 1
4 status: 1
@@ -27,7 +9,6 users_001:
27 hashed_password: b5b6ff9543bf1387374cdfa27a54c96d236a7150
9 hashed_password: b5b6ff9543bf1387374cdfa27a54c96d236a7150
28 updated_on: 2006-07-19 22:57:52 +02:00
10 updated_on: 2006-07-19 22:57:52 +02:00
29 admin: true
11 admin: true
30 mail: admin@somenet.foo
31 lastname: Admin
12 lastname: Admin
32 firstname: Redmine
13 firstname: Redmine
33 id: 1
14 id: 1
@@ -45,7 +26,6 users_002:
45 hashed_password: bfbe06043353a677d0215b26a5800d128d5413bc
26 hashed_password: bfbe06043353a677d0215b26a5800d128d5413bc
46 updated_on: 2006-07-19 22:42:15 +02:00
27 updated_on: 2006-07-19 22:42:15 +02:00
47 admin: false
28 admin: false
48 mail: jsmith@somenet.foo
49 lastname: Smith
29 lastname: Smith
50 firstname: John
30 firstname: John
51 id: 2
31 id: 2
@@ -63,7 +43,6 users_003:
63 hashed_password: 8f659c8d7c072f189374edacfa90d6abbc26d8ed
43 hashed_password: 8f659c8d7c072f189374edacfa90d6abbc26d8ed
64 updated_on: 2006-07-19 19:33:19 +02:00
44 updated_on: 2006-07-19 19:33:19 +02:00
65 admin: false
45 admin: false
66 mail: dlopper@somenet.foo
67 lastname: Lopper
46 lastname: Lopper
68 firstname: Dave
47 firstname: Dave
69 id: 3
48 id: 3
@@ -71,6 +50,23 users_003:
71 mail_notification: all
50 mail_notification: all
72 login: dlopper
51 login: dlopper
73 type: User
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 users_005:
70 users_005:
75 id: 5
71 id: 5
76 created_on: 2006-07-19 19:33:19 +02:00
72 created_on: 2006-07-19 19:33:19 +02:00
@@ -81,7 +77,6 users_005:
81 hashed_password: 1
77 hashed_password: 1
82 updated_on: 2006-07-19 19:33:19 +02:00
78 updated_on: 2006-07-19 19:33:19 +02:00
83 admin: false
79 admin: false
84 mail: dlopper2@somenet.foo
85 lastname: Lopper2
80 lastname: Lopper2
86 firstname: Dave2
81 firstname: Dave2
87 auth_source_id:
82 auth_source_id:
@@ -97,7 +92,6 users_006:
97 hashed_password: 1
92 hashed_password: 1
98 updated_on: 2006-07-19 19:33:19 +02:00
93 updated_on: 2006-07-19 19:33:19 +02:00
99 admin: false
94 admin: false
100 mail: ''
101 lastname: Anonymous
95 lastname: Anonymous
102 firstname: ''
96 firstname: ''
103 auth_source_id:
97 auth_source_id:
@@ -116,7 +110,6 users_007:
116 hashed_password: 8f659c8d7c072f189374edacfa90d6abbc26d8ed
110 hashed_password: 8f659c8d7c072f189374edacfa90d6abbc26d8ed
117 updated_on: 2006-07-19 19:33:19 +02:00
111 updated_on: 2006-07-19 19:33:19 +02:00
118 admin: false
112 admin: false
119 mail: someone@foo.bar
120 lastname: One
113 lastname: One
121 firstname: Some
114 firstname: Some
122 auth_source_id:
115 auth_source_id:
@@ -134,7 +127,6 users_008:
134 hashed_password: 8f659c8d7c072f189374edacfa90d6abbc26d8ed
127 hashed_password: 8f659c8d7c072f189374edacfa90d6abbc26d8ed
135 updated_on: 2006-07-19 19:33:19 +02:00
128 updated_on: 2006-07-19 19:33:19 +02:00
136 admin: false
129 admin: false
137 mail: miscuser8@foo.bar
138 lastname: Misc
130 lastname: Misc
139 firstname: User
131 firstname: User
140 auth_source_id:
132 auth_source_id:
@@ -150,7 +142,6 users_009:
150 hashed_password: 1
142 hashed_password: 1
151 updated_on: 2006-07-19 19:33:19 +02:00
143 updated_on: 2006-07-19 19:33:19 +02:00
152 admin: false
144 admin: false
153 mail: miscuser9@foo.bar
154 lastname: Misc
145 lastname: Misc
155 firstname: User
146 firstname: User
156 auth_source_id:
147 auth_source_id:
@@ -18,7 +18,7
18 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19
19
20 class AdminControllerTest < ActionController::TestCase
20 class AdminControllerTest < ActionController::TestCase
21 fixtures :projects, :users, :roles
21 fixtures :projects, :users, :email_addresses, :roles
22
22
23 def setup
23 def setup
24 User.current = nil
24 User.current = nil
@@ -18,7 +18,7
18 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19
19
20 class DocumentsControllerTest < ActionController::TestCase
20 class DocumentsControllerTest < ActionController::TestCase
21 fixtures :projects, :users, :roles, :members, :member_roles,
21 fixtures :projects, :users, :email_addresses, :roles, :members, :member_roles,
22 :enabled_modules, :documents, :enumerations,
22 :enabled_modules, :documents, :enumerations,
23 :groups_users, :attachments
23 :groups_users, :attachments
24
24
@@ -19,7 +19,7 require File.expand_path('../../test_helper', __FILE__)
19
19
20 class IssuesControllerTest < ActionController::TestCase
20 class IssuesControllerTest < ActionController::TestCase
21 fixtures :projects,
21 fixtures :projects,
22 :users,
22 :users, :email_addresses,
23 :roles,
23 :roles,
24 :members,
24 :members,
25 :member_roles,
25 :member_roles,
@@ -20,7 +20,7 require File.expand_path('../../test_helper', __FILE__)
20 class IssuesCustomFieldsVisibilityTest < ActionController::TestCase
20 class IssuesCustomFieldsVisibilityTest < ActionController::TestCase
21 tests IssuesController
21 tests IssuesController
22 fixtures :projects,
22 fixtures :projects,
23 :users,
23 :users, :email_addresses,
24 :roles,
24 :roles,
25 :members,
25 :members,
26 :member_roles,
26 :member_roles,
@@ -18,7 +18,7
18 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19
19
20 class MailHandlerControllerTest < ActionController::TestCase
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 :trackers, :projects_trackers, :enumerations
22 :trackers, :projects_trackers, :enumerations
23
23
24 FIXTURES_PATH = File.dirname(__FILE__) + '/../fixtures/mail_handler'
24 FIXTURES_PATH = File.dirname(__FILE__) + '/../fixtures/mail_handler'
@@ -18,7 +18,7
18 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19
19
20 class MessagesControllerTest < ActionController::TestCase
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 def setup
23 def setup
24 User.current = nil
24 User.current = nil
@@ -18,7 +18,7
18 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19
19
20 class MyControllerTest < ActionController::TestCase
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 :issues, :issue_statuses, :trackers, :enumerations, :custom_fields, :auth_sources
22 :issues, :issue_statuses, :trackers, :enumerations, :custom_fields, :auth_sources
23
23
24 def setup
24 def setup
@@ -18,7 +18,7
18 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19
19
20 class NewsControllerTest < ActionController::TestCase
20 class NewsControllerTest < ActionController::TestCase
21 fixtures :projects, :users, :roles, :members, :member_roles,
21 fixtures :projects, :users, :email_addresses, :roles, :members, :member_roles,
22 :enabled_modules, :news, :comments,
22 :enabled_modules, :news, :comments,
23 :attachments
23 :attachments
24
24
@@ -18,7 +18,7
18 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19
19
20 class ProjectsControllerTest < ActionController::TestCase
20 class ProjectsControllerTest < ActionController::TestCase
21 fixtures :projects, :versions, :users, :roles, :members,
21 fixtures :projects, :versions, :users, :email_addresses, :roles, :members,
22 :member_roles, :issues, :journals, :journal_details,
22 :member_roles, :issues, :journals, :journal_details,
23 :trackers, :projects_trackers, :issue_statuses,
23 :trackers, :projects_trackers, :issue_statuses,
24 :enabled_modules, :enumerations, :boards, :messages,
24 :enabled_modules, :enumerations, :boards, :messages,
@@ -20,7 +20,7 require File.expand_path('../../test_helper', __FILE__)
20 class RepositoriesBazaarControllerTest < ActionController::TestCase
20 class RepositoriesBazaarControllerTest < ActionController::TestCase
21 tests RepositoriesController
21 tests RepositoriesController
22
22
23 fixtures :projects, :users, :roles, :members, :member_roles,
23 fixtures :projects, :users, :email_addresses, :roles, :members, :member_roles,
24 :repositories, :enabled_modules
24 :repositories, :enabled_modules
25
25
26 REPOSITORY_PATH = Rails.root.join('tmp/test/bazaar_repository').to_s
26 REPOSITORY_PATH = Rails.root.join('tmp/test/bazaar_repository').to_s
@@ -18,7 +18,7
18 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19
19
20 class RepositoriesControllerTest < ActionController::TestCase
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 :repositories, :issues, :issue_statuses, :changesets, :changes,
22 :repositories, :issues, :issue_statuses, :changesets, :changes,
23 :issue_categories, :enumerations, :custom_fields, :custom_values, :trackers
23 :issue_categories, :enumerations, :custom_fields, :custom_values, :trackers
24
24
@@ -20,7 +20,7 require File.expand_path('../../test_helper', __FILE__)
20 class RepositoriesCvsControllerTest < ActionController::TestCase
20 class RepositoriesCvsControllerTest < ActionController::TestCase
21 tests RepositoriesController
21 tests RepositoriesController
22
22
23 fixtures :projects, :users, :roles, :members, :member_roles,
23 fixtures :projects, :users, :email_addresses, :roles, :members, :member_roles,
24 :repositories, :enabled_modules
24 :repositories, :enabled_modules
25
25
26 REPOSITORY_PATH = Rails.root.join('tmp/test/cvs_repository').to_s
26 REPOSITORY_PATH = Rails.root.join('tmp/test/cvs_repository').to_s
@@ -20,7 +20,7 require File.expand_path('../../test_helper', __FILE__)
20 class RepositoriesDarcsControllerTest < ActionController::TestCase
20 class RepositoriesDarcsControllerTest < ActionController::TestCase
21 tests RepositoriesController
21 tests RepositoriesController
22
22
23 fixtures :projects, :users, :roles, :members, :member_roles,
23 fixtures :projects, :users, :email_addresses, :roles, :members, :member_roles,
24 :repositories, :enabled_modules
24 :repositories, :enabled_modules
25
25
26 REPOSITORY_PATH = Rails.root.join('tmp/test/darcs_repository').to_s
26 REPOSITORY_PATH = Rails.root.join('tmp/test/darcs_repository').to_s
@@ -20,7 +20,7 require File.expand_path('../../test_helper', __FILE__)
20 class RepositoriesFilesystemControllerTest < ActionController::TestCase
20 class RepositoriesFilesystemControllerTest < ActionController::TestCase
21 tests RepositoriesController
21 tests RepositoriesController
22
22
23 fixtures :projects, :users, :roles, :members, :member_roles,
23 fixtures :projects, :users, :email_addresses, :roles, :members, :member_roles,
24 :repositories, :enabled_modules
24 :repositories, :enabled_modules
25
25
26 REPOSITORY_PATH = Rails.root.join('tmp/test/filesystem_repository').to_s
26 REPOSITORY_PATH = Rails.root.join('tmp/test/filesystem_repository').to_s
@@ -20,7 +20,7 require File.expand_path('../../test_helper', __FILE__)
20 class RepositoriesGitControllerTest < ActionController::TestCase
20 class RepositoriesGitControllerTest < ActionController::TestCase
21 tests RepositoriesController
21 tests RepositoriesController
22
22
23 fixtures :projects, :users, :roles, :members, :member_roles,
23 fixtures :projects, :users, :email_addresses, :roles, :members, :member_roles,
24 :repositories, :enabled_modules
24 :repositories, :enabled_modules
25
25
26 REPOSITORY_PATH = Rails.root.join('tmp/test/git_repository').to_s
26 REPOSITORY_PATH = Rails.root.join('tmp/test/git_repository').to_s
@@ -20,7 +20,7 require File.expand_path('../../test_helper', __FILE__)
20 class RepositoriesMercurialControllerTest < ActionController::TestCase
20 class RepositoriesMercurialControllerTest < ActionController::TestCase
21 tests RepositoriesController
21 tests RepositoriesController
22
22
23 fixtures :projects, :users, :roles, :members, :member_roles,
23 fixtures :projects, :users, :email_addresses, :roles, :members, :member_roles,
24 :repositories, :enabled_modules
24 :repositories, :enabled_modules
25
25
26 REPOSITORY_PATH = Rails.root.join('tmp/test/mercurial_repository').to_s
26 REPOSITORY_PATH = Rails.root.join('tmp/test/mercurial_repository').to_s
@@ -20,7 +20,7 require File.expand_path('../../test_helper', __FILE__)
20 class RepositoriesSubversionControllerTest < ActionController::TestCase
20 class RepositoriesSubversionControllerTest < ActionController::TestCase
21 tests RepositoriesController
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 :repositories, :issues, :issue_statuses, :changesets, :changes,
24 :repositories, :issues, :issue_statuses, :changesets, :changes,
25 :issue_categories, :enumerations, :custom_fields, :custom_values, :trackers
25 :issue_categories, :enumerations, :custom_fields, :custom_values, :trackers
26
26
@@ -20,7 +20,7 require File.expand_path('../../test_helper', __FILE__)
20 class UsersControllerTest < ActionController::TestCase
20 class UsersControllerTest < ActionController::TestCase
21 include Redmine::I18n
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 :custom_fields, :custom_values, :groups_users,
24 :custom_fields, :custom_values, :groups_users,
25 :auth_sources,
25 :auth_sources,
26 :enabled_modules,
26 :enabled_modules,
@@ -18,7 +18,7
18 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19
19
20 class WikiControllerTest < ActionController::TestCase
20 class WikiControllerTest < ActionController::TestCase
21 fixtures :projects, :users, :roles, :members, :member_roles,
21 fixtures :projects, :users, :email_addresses, :roles, :members, :member_roles,
22 :enabled_modules, :wikis, :wiki_pages, :wiki_contents,
22 :enabled_modules, :wikis, :wiki_pages, :wiki_contents,
23 :wiki_content_versions, :attachments,
23 :wiki_content_versions, :attachments,
24 :issues, :issue_statuses
24 :issues, :issue_statuses
@@ -18,7 +18,7
18 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19
19
20 class AccountTest < Redmine::IntegrationTest
20 class AccountTest < Redmine::IntegrationTest
21 fixtures :users, :roles
21 fixtures :users, :email_addresses, :roles
22
22
23 def test_login
23 def test_login
24 get "/my/page"
24 get "/my/page"
@@ -18,7 +18,7
18 require File.expand_path('../../../test_helper', __FILE__)
18 require File.expand_path('../../../test_helper', __FILE__)
19
19
20 class Redmine::ApiTest::UsersTest < Redmine::ApiTest::Base
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 test "GET /users.xml should return users" do
23 test "GET /users.xml should return users" do
24 get '/users.xml', {}, credentials('admin')
24 get '/users.xml', {}, credentials('admin')
@@ -19,7 +19,7 require File.expand_path('../../test_helper', __FILE__)
19
19
20 class IssuesTest < Redmine::IntegrationTest
20 class IssuesTest < Redmine::IntegrationTest
21 fixtures :projects,
21 fixtures :projects,
22 :users,
22 :users, :email_addresses,
23 :roles,
23 :roles,
24 :members,
24 :members,
25 :member_roles,
25 :member_roles,
@@ -18,7 +18,7
18 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19
19
20 class UsersTest < Redmine::IntegrationTest
20 class UsersTest < Redmine::IntegrationTest
21 fixtures :users
21 fixtures :users, :email_addresses
22
22
23 def test_destroy_should_not_accept_get_requests
23 def test_destroy_should_not_accept_get_requests
24 assert_no_difference 'User.count' do
24 assert_no_difference 'User.count' do
@@ -20,7 +20,7 require File.expand_path('../../test_helper', __FILE__)
20 class DocumentTest < ActiveSupport::TestCase
20 class DocumentTest < ActiveSupport::TestCase
21 fixtures :projects, :enumerations, :documents, :attachments,
21 fixtures :projects, :enumerations, :documents, :attachments,
22 :enabled_modules,
22 :enabled_modules,
23 :users, :members, :member_roles, :roles,
23 :users, :email_addresses, :members, :member_roles, :roles,
24 :groups_users
24 :groups_users
25
25
26 def test_create
26 def test_create
@@ -18,7 +18,7
18 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19
19
20 class IssueTest < ActiveSupport::TestCase
20 class IssueTest < ActiveSupport::TestCase
21 fixtures :projects, :users, :members, :member_roles, :roles,
21 fixtures :projects, :users, :email_addresses, :members, :member_roles, :roles,
22 :groups_users,
22 :groups_users,
23 :trackers, :projects_trackers,
23 :trackers, :projects_trackers,
24 :enabled_modules,
24 :enabled_modules,
@@ -223,6 +223,17 class MailHandlerTest < ActiveSupport::TestCase
223 assert_equal 1, issue.watcher_user_ids.size
223 assert_equal 1, issue.watcher_user_ids.size
224 end
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 def test_add_issue_by_unknown_user
237 def test_add_issue_by_unknown_user
227 assert_no_difference 'User.count' do
238 assert_no_difference 'User.count' do
228 assert_equal false,
239 assert_equal false,
@@ -20,7 +20,7 require File.expand_path('../../test_helper', __FILE__)
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, :email_addresses, :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,
@@ -298,6 +298,14 class MailerTest < ActiveSupport::TestCase
298 assert last_email.bcc.include?('dlopper@somenet.foo')
298 assert last_email.bcc.include?('dlopper@somenet.foo')
299 end
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 test "#issue_add should not notify project members that are not allow to view the issue" do
309 test "#issue_add should not notify project members that are not allow to view the issue" do
302 issue = Issue.find(1)
310 issue = Issue.find(1)
303 Role.find(2).remove_permission!(:view_issues)
311 Role.find(2).remove_permission!(:view_issues)
@@ -771,6 +779,30 class MailerTest < ActiveSupport::TestCase
771 ActionMailer::Base.delivery_method = :test
779 ActionMailer::Base.delivery_method = :test
772 end
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 private
806 private
775
807
776 def last_email
808 def last_email
@@ -18,7 +18,7
18 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19
19
20 class UserTest < ActiveSupport::TestCase
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 :trackers, :issue_statuses,
22 :trackers, :issue_statuses,
23 :projects_trackers,
23 :projects_trackers,
24 :watchers,
24 :watchers,
@@ -57,11 +57,41 class UserTest < ActiveSupport::TestCase
57 assert_equal "foo@bar.com", u.mail
57 assert_equal "foo@bar.com", u.mail
58 end
58 end
59
59
60 def test_mail_validation
60 def test_should_create_email_address
61 u = User.new
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 u.mail = ''
92 u.mail = ''
63 assert !u.valid?
93 assert !u.save
64 assert_include I18n.translate('activerecord.errors.messages.blank'), u.errors[:mail]
94 assert_equal ["Email #{I18n.translate('activerecord.errors.messages.blank')}"], u.errors.full_messages
65 end
95 end
66
96
67 def test_login_length_validation
97 def test_login_length_validation
@@ -151,6 +181,7 class UserTest < ActiveSupport::TestCase
151 end
181 end
152
182
153 def test_mail_uniqueness_should_not_be_case_sensitive
183 def test_mail_uniqueness_should_not_be_case_sensitive
184 set_language_if_valid 'en'
154 u = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
185 u = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
155 u.login = 'newuser1'
186 u.login = 'newuser1'
156 u.password, u.password_confirmation = "password", "password"
187 u.password, u.password_confirmation = "password", "password"
@@ -160,7 +191,7 class UserTest < ActiveSupport::TestCase
160 u.login = 'newuser2'
191 u.login = 'newuser2'
161 u.password, u.password_confirmation = "password", "password"
192 u.password, u.password_confirmation = "password", "password"
162 assert !u.save
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 end
195 end
165
196
166 def test_update
197 def test_update
@@ -677,7 +708,7 class UserTest < ActiveSupport::TestCase
677 assert_kind_of AnonymousUser, anon1
708 assert_kind_of AnonymousUser, anon1
678 anon2 = AnonymousUser.create(
709 anon2 = AnonymousUser.create(
679 :lastname => 'Anonymous', :firstname => '',
710 :lastname => 'Anonymous', :firstname => '',
680 :mail => '', :login => '', :status => 0)
711 :login => '', :status => 0)
681 assert_equal 1, anon2.errors.count
712 assert_equal 1, anon2.errors.count
682 end
713 end
683
714
@@ -18,7 +18,7
18 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19
19
20 class WatcherTest < ActiveSupport::TestCase
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 :issues, :issue_statuses, :enumerations, :trackers, :projects_trackers,
22 :issues, :issue_statuses, :enumerations, :trackers, :projects_trackers,
23 :boards, :messages,
23 :boards, :messages,
24 :wikis, :wiki_pages,
24 :wikis, :wiki_pages,
General Comments 0
You need to be logged in to leave comments. Login now