##// END OF EJS Templates
Added an API token for each User to use when making API requests. (#3920)...
Eric Davis -
r3103:aa9951b38b27
parent child
Show More
@@ -0,0 +1,13
1 class AddApiKeysForUsers < ActiveRecord::Migration
2 def self.up
3 say_with_time("Generating API keys for active users") do
4 User.active.all(:include => :api_token).each do |user|
5 user.api_key
6 end
7 end
8 end
9
10 def self.down
11 # No-op
12 end
13 end
@@ -1,170 +1,183
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2009 Jean-Philippe Lang
2 # Copyright (C) 2006-2009 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class MyController < ApplicationController
18 class MyController < ApplicationController
19 before_filter :require_login
19 before_filter :require_login
20
20
21 helper :issues
21 helper :issues
22 helper :custom_fields
22 helper :custom_fields
23
23
24 BLOCKS = { 'issuesassignedtome' => :label_assigned_to_me_issues,
24 BLOCKS = { 'issuesassignedtome' => :label_assigned_to_me_issues,
25 'issuesreportedbyme' => :label_reported_issues,
25 'issuesreportedbyme' => :label_reported_issues,
26 'issueswatched' => :label_watched_issues,
26 'issueswatched' => :label_watched_issues,
27 'news' => :label_news_latest,
27 'news' => :label_news_latest,
28 'calendar' => :label_calendar,
28 'calendar' => :label_calendar,
29 'documents' => :label_document_plural,
29 'documents' => :label_document_plural,
30 'timelog' => :label_spent_time
30 'timelog' => :label_spent_time
31 }.merge(Redmine::Views::MyPage::Block.additional_blocks).freeze
31 }.merge(Redmine::Views::MyPage::Block.additional_blocks).freeze
32
32
33 DEFAULT_LAYOUT = { 'left' => ['issuesassignedtome'],
33 DEFAULT_LAYOUT = { 'left' => ['issuesassignedtome'],
34 'right' => ['issuesreportedbyme']
34 'right' => ['issuesreportedbyme']
35 }.freeze
35 }.freeze
36
36
37 verify :xhr => true,
37 verify :xhr => true,
38 :only => [:add_block, :remove_block, :order_blocks]
38 :only => [:add_block, :remove_block, :order_blocks]
39
39
40 def index
40 def index
41 page
41 page
42 render :action => 'page'
42 render :action => 'page'
43 end
43 end
44
44
45 # Show user's page
45 # Show user's page
46 def page
46 def page
47 @user = User.current
47 @user = User.current
48 @blocks = @user.pref[:my_page_layout] || DEFAULT_LAYOUT
48 @blocks = @user.pref[:my_page_layout] || DEFAULT_LAYOUT
49 end
49 end
50
50
51 # Edit user's account
51 # Edit user's account
52 def account
52 def account
53 @user = User.current
53 @user = User.current
54 @pref = @user.pref
54 @pref = @user.pref
55 if request.post?
55 if request.post?
56 @user.attributes = params[:user]
56 @user.attributes = params[:user]
57 @user.mail_notification = (params[:notification_option] == 'all')
57 @user.mail_notification = (params[:notification_option] == 'all')
58 @user.pref.attributes = params[:pref]
58 @user.pref.attributes = params[:pref]
59 @user.pref[:no_self_notified] = (params[:no_self_notified] == '1')
59 @user.pref[:no_self_notified] = (params[:no_self_notified] == '1')
60 if @user.save
60 if @user.save
61 @user.pref.save
61 @user.pref.save
62 @user.notified_project_ids = (params[:notification_option] == 'selected' ? params[:notified_project_ids] : [])
62 @user.notified_project_ids = (params[:notification_option] == 'selected' ? params[:notified_project_ids] : [])
63 set_language_if_valid @user.language
63 set_language_if_valid @user.language
64 flash[:notice] = l(:notice_account_updated)
64 flash[:notice] = l(:notice_account_updated)
65 redirect_to :action => 'account'
65 redirect_to :action => 'account'
66 return
66 return
67 end
67 end
68 end
68 end
69 @notification_options = [[l(:label_user_mail_option_all), 'all'],
69 @notification_options = [[l(:label_user_mail_option_all), 'all'],
70 [l(:label_user_mail_option_none), 'none']]
70 [l(:label_user_mail_option_none), 'none']]
71 # Only users that belong to more than 1 project can select projects for which they are notified
71 # Only users that belong to more than 1 project can select projects for which they are notified
72 # Note that @user.membership.size would fail since AR ignores :include association option when doing a count
72 # Note that @user.membership.size would fail since AR ignores :include association option when doing a count
73 @notification_options.insert 1, [l(:label_user_mail_option_selected), 'selected'] if @user.memberships.length > 1
73 @notification_options.insert 1, [l(:label_user_mail_option_selected), 'selected'] if @user.memberships.length > 1
74 @notification_option = @user.mail_notification? ? 'all' : (@user.notified_projects_ids.empty? ? 'none' : 'selected')
74 @notification_option = @user.mail_notification? ? 'all' : (@user.notified_projects_ids.empty? ? 'none' : 'selected')
75 end
75 end
76
76
77 # Manage user's password
77 # Manage user's password
78 def password
78 def password
79 @user = User.current
79 @user = User.current
80 if @user.auth_source_id
80 if @user.auth_source_id
81 flash[:error] = l(:notice_can_t_change_password)
81 flash[:error] = l(:notice_can_t_change_password)
82 redirect_to :action => 'account'
82 redirect_to :action => 'account'
83 return
83 return
84 end
84 end
85 if request.post?
85 if request.post?
86 if @user.check_password?(params[:password])
86 if @user.check_password?(params[:password])
87 @user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation]
87 @user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation]
88 if @user.save
88 if @user.save
89 flash[:notice] = l(:notice_account_password_updated)
89 flash[:notice] = l(:notice_account_password_updated)
90 redirect_to :action => 'account'
90 redirect_to :action => 'account'
91 end
91 end
92 else
92 else
93 flash[:error] = l(:notice_account_wrong_password)
93 flash[:error] = l(:notice_account_wrong_password)
94 end
94 end
95 end
95 end
96 end
96 end
97
97
98 # Create a new feeds key
98 # Create a new feeds key
99 def reset_rss_key
99 def reset_rss_key
100 if request.post?
100 if request.post?
101 if User.current.rss_token
101 if User.current.rss_token
102 User.current.rss_token.destroy
102 User.current.rss_token.destroy
103 User.current.reload
103 User.current.reload
104 end
104 end
105 User.current.rss_key
105 User.current.rss_key
106 flash[:notice] = l(:notice_feeds_access_key_reseted)
106 flash[:notice] = l(:notice_feeds_access_key_reseted)
107 end
107 end
108 redirect_to :action => 'account'
108 redirect_to :action => 'account'
109 end
109 end
110
110
111 # Create a new API key
112 def reset_api_key
113 if request.post?
114 if User.current.api_token
115 User.current.api_token.destroy
116 User.current.reload
117 end
118 User.current.api_key
119 flash[:notice] = l(:notice_api_access_key_reseted)
120 end
121 redirect_to :action => 'account'
122 end
123
111 # User's page layout configuration
124 # User's page layout configuration
112 def page_layout
125 def page_layout
113 @user = User.current
126 @user = User.current
114 @blocks = @user.pref[:my_page_layout] || DEFAULT_LAYOUT.dup
127 @blocks = @user.pref[:my_page_layout] || DEFAULT_LAYOUT.dup
115 @block_options = []
128 @block_options = []
116 BLOCKS.each {|k, v| @block_options << [l("my.blocks.#{v}", :default => [v, v.to_s.humanize]), k.dasherize]}
129 BLOCKS.each {|k, v| @block_options << [l("my.blocks.#{v}", :default => [v, v.to_s.humanize]), k.dasherize]}
117 end
130 end
118
131
119 # Add a block to user's page
132 # Add a block to user's page
120 # The block is added on top of the page
133 # The block is added on top of the page
121 # params[:block] : id of the block to add
134 # params[:block] : id of the block to add
122 def add_block
135 def add_block
123 block = params[:block].to_s.underscore
136 block = params[:block].to_s.underscore
124 (render :nothing => true; return) unless block && (BLOCKS.keys.include? block)
137 (render :nothing => true; return) unless block && (BLOCKS.keys.include? block)
125 @user = User.current
138 @user = User.current
126 layout = @user.pref[:my_page_layout] || {}
139 layout = @user.pref[:my_page_layout] || {}
127 # remove if already present in a group
140 # remove if already present in a group
128 %w(top left right).each {|f| (layout[f] ||= []).delete block }
141 %w(top left right).each {|f| (layout[f] ||= []).delete block }
129 # add it on top
142 # add it on top
130 layout['top'].unshift block
143 layout['top'].unshift block
131 @user.pref[:my_page_layout] = layout
144 @user.pref[:my_page_layout] = layout
132 @user.pref.save
145 @user.pref.save
133 render :partial => "block", :locals => {:user => @user, :block_name => block}
146 render :partial => "block", :locals => {:user => @user, :block_name => block}
134 end
147 end
135
148
136 # Remove a block to user's page
149 # Remove a block to user's page
137 # params[:block] : id of the block to remove
150 # params[:block] : id of the block to remove
138 def remove_block
151 def remove_block
139 block = params[:block].to_s.underscore
152 block = params[:block].to_s.underscore
140 @user = User.current
153 @user = User.current
141 # remove block in all groups
154 # remove block in all groups
142 layout = @user.pref[:my_page_layout] || {}
155 layout = @user.pref[:my_page_layout] || {}
143 %w(top left right).each {|f| (layout[f] ||= []).delete block }
156 %w(top left right).each {|f| (layout[f] ||= []).delete block }
144 @user.pref[:my_page_layout] = layout
157 @user.pref[:my_page_layout] = layout
145 @user.pref.save
158 @user.pref.save
146 render :nothing => true
159 render :nothing => true
147 end
160 end
148
161
149 # Change blocks order on user's page
162 # Change blocks order on user's page
150 # params[:group] : group to order (top, left or right)
163 # params[:group] : group to order (top, left or right)
151 # params[:list-(top|left|right)] : array of block ids of the group
164 # params[:list-(top|left|right)] : array of block ids of the group
152 def order_blocks
165 def order_blocks
153 group = params[:group]
166 group = params[:group]
154 @user = User.current
167 @user = User.current
155 if group.is_a?(String)
168 if group.is_a?(String)
156 group_items = (params["list-#{group}"] || []).collect(&:underscore)
169 group_items = (params["list-#{group}"] || []).collect(&:underscore)
157 if group_items and group_items.is_a? Array
170 if group_items and group_items.is_a? Array
158 layout = @user.pref[:my_page_layout] || {}
171 layout = @user.pref[:my_page_layout] || {}
159 # remove group blocks if they are presents in other groups
172 # remove group blocks if they are presents in other groups
160 %w(top left right).each {|f|
173 %w(top left right).each {|f|
161 layout[f] = (layout[f] || []) - group_items
174 layout[f] = (layout[f] || []) - group_items
162 }
175 }
163 layout[group] = group_items
176 layout[group] = group_items
164 @user.pref[:my_page_layout] = layout
177 @user.pref[:my_page_layout] = layout
165 @user.pref.save
178 @user.pref.save
166 end
179 end
167 end
180 end
168 render :nothing => true
181 render :nothing => true
169 end
182 end
170 end
183 end
@@ -1,348 +1,360
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2009 Jean-Philippe Lang
2 # Copyright (C) 2006-2009 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require "digest/sha1"
18 require "digest/sha1"
19
19
20 class User < Principal
20 class User < Principal
21
21
22 # Account statuses
22 # Account statuses
23 STATUS_ANONYMOUS = 0
23 STATUS_ANONYMOUS = 0
24 STATUS_ACTIVE = 1
24 STATUS_ACTIVE = 1
25 STATUS_REGISTERED = 2
25 STATUS_REGISTERED = 2
26 STATUS_LOCKED = 3
26 STATUS_LOCKED = 3
27
27
28 USER_FORMATS = {
28 USER_FORMATS = {
29 :firstname_lastname => '#{firstname} #{lastname}',
29 :firstname_lastname => '#{firstname} #{lastname}',
30 :firstname => '#{firstname}',
30 :firstname => '#{firstname}',
31 :lastname_firstname => '#{lastname} #{firstname}',
31 :lastname_firstname => '#{lastname} #{firstname}',
32 :lastname_coma_firstname => '#{lastname}, #{firstname}',
32 :lastname_coma_firstname => '#{lastname}, #{firstname}',
33 :username => '#{login}'
33 :username => '#{login}'
34 }
34 }
35
35
36 has_and_belongs_to_many :groups, :after_add => Proc.new {|user, group| group.user_added(user)},
36 has_and_belongs_to_many :groups, :after_add => Proc.new {|user, group| group.user_added(user)},
37 :after_remove => Proc.new {|user, group| group.user_removed(user)}
37 :after_remove => Proc.new {|user, group| group.user_removed(user)}
38 has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify
38 has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify
39 has_many :changesets, :dependent => :nullify
39 has_many :changesets, :dependent => :nullify
40 has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
40 has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
41 has_one :rss_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='feeds'"
41 has_one :rss_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='feeds'"
42 has_one :api_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='api'"
42 belongs_to :auth_source
43 belongs_to :auth_source
43
44
44 # Active non-anonymous users scope
45 # Active non-anonymous users scope
45 named_scope :active, :conditions => "#{User.table_name}.status = #{STATUS_ACTIVE}"
46 named_scope :active, :conditions => "#{User.table_name}.status = #{STATUS_ACTIVE}"
46
47
47 acts_as_customizable
48 acts_as_customizable
48
49
49 attr_accessor :password, :password_confirmation
50 attr_accessor :password, :password_confirmation
50 attr_accessor :last_before_login_on
51 attr_accessor :last_before_login_on
51 # Prevents unauthorized assignments
52 # Prevents unauthorized assignments
52 attr_protected :login, :admin, :password, :password_confirmation, :hashed_password, :group_ids
53 attr_protected :login, :admin, :password, :password_confirmation, :hashed_password, :group_ids
53
54
54 validates_presence_of :login, :firstname, :lastname, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }
55 validates_presence_of :login, :firstname, :lastname, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }
55 validates_uniqueness_of :login, :if => Proc.new { |user| !user.login.blank? }
56 validates_uniqueness_of :login, :if => Proc.new { |user| !user.login.blank? }
56 validates_uniqueness_of :mail, :if => Proc.new { |user| !user.mail.blank? }, :case_sensitive => false
57 validates_uniqueness_of :mail, :if => Proc.new { |user| !user.mail.blank? }, :case_sensitive => false
57 # Login must contain lettres, numbers, underscores only
58 # Login must contain lettres, numbers, underscores only
58 validates_format_of :login, :with => /^[a-z0-9_\-@\.]*$/i
59 validates_format_of :login, :with => /^[a-z0-9_\-@\.]*$/i
59 validates_length_of :login, :maximum => 30
60 validates_length_of :login, :maximum => 30
60 validates_format_of :firstname, :lastname, :with => /^[\w\s\'\-\.]*$/i
61 validates_format_of :firstname, :lastname, :with => /^[\w\s\'\-\.]*$/i
61 validates_length_of :firstname, :lastname, :maximum => 30
62 validates_length_of :firstname, :lastname, :maximum => 30
62 validates_format_of :mail, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :allow_nil => true
63 validates_format_of :mail, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :allow_nil => true
63 validates_length_of :mail, :maximum => 60, :allow_nil => true
64 validates_length_of :mail, :maximum => 60, :allow_nil => true
64 validates_confirmation_of :password, :allow_nil => true
65 validates_confirmation_of :password, :allow_nil => true
65
66
66 def before_create
67 def before_create
67 self.mail_notification = false
68 self.mail_notification = false
68 true
69 true
69 end
70 end
70
71
71 def before_save
72 def before_save
72 # update hashed_password if password was set
73 # update hashed_password if password was set
73 self.hashed_password = User.hash_password(self.password) if self.password
74 self.hashed_password = User.hash_password(self.password) if self.password
74 end
75 end
75
76
76 def reload(*args)
77 def reload(*args)
77 @name = nil
78 @name = nil
78 super
79 super
79 end
80 end
80
81
81 def identity_url=(url)
82 def identity_url=(url)
82 if url.blank?
83 if url.blank?
83 write_attribute(:identity_url, '')
84 write_attribute(:identity_url, '')
84 else
85 else
85 begin
86 begin
86 write_attribute(:identity_url, OpenIdAuthentication.normalize_identifier(url))
87 write_attribute(:identity_url, OpenIdAuthentication.normalize_identifier(url))
87 rescue OpenIdAuthentication::InvalidOpenId
88 rescue OpenIdAuthentication::InvalidOpenId
88 # Invlaid url, don't save
89 # Invlaid url, don't save
89 end
90 end
90 end
91 end
91 self.read_attribute(:identity_url)
92 self.read_attribute(:identity_url)
92 end
93 end
93
94
94 # Returns the user that matches provided login and password, or nil
95 # Returns the user that matches provided login and password, or nil
95 def self.try_to_login(login, password)
96 def self.try_to_login(login, password)
96 # Make sure no one can sign in with an empty password
97 # Make sure no one can sign in with an empty password
97 return nil if password.to_s.empty?
98 return nil if password.to_s.empty?
98 user = find(:first, :conditions => ["login=?", login])
99 user = find(:first, :conditions => ["login=?", login])
99 if user
100 if user
100 # user is already in local database
101 # user is already in local database
101 return nil if !user.active?
102 return nil if !user.active?
102 if user.auth_source
103 if user.auth_source
103 # user has an external authentication method
104 # user has an external authentication method
104 return nil unless user.auth_source.authenticate(login, password)
105 return nil unless user.auth_source.authenticate(login, password)
105 else
106 else
106 # authentication with local password
107 # authentication with local password
107 return nil unless User.hash_password(password) == user.hashed_password
108 return nil unless User.hash_password(password) == user.hashed_password
108 end
109 end
109 else
110 else
110 # user is not yet registered, try to authenticate with available sources
111 # user is not yet registered, try to authenticate with available sources
111 attrs = AuthSource.authenticate(login, password)
112 attrs = AuthSource.authenticate(login, password)
112 if attrs
113 if attrs
113 user = new(*attrs)
114 user = new(*attrs)
114 user.login = login
115 user.login = login
115 user.language = Setting.default_language
116 user.language = Setting.default_language
116 if user.save
117 if user.save
117 user.reload
118 user.reload
118 logger.info("User '#{user.login}' created from the LDAP") if logger
119 logger.info("User '#{user.login}' created from the LDAP") if logger
119 end
120 end
120 end
121 end
121 end
122 end
122 user.update_attribute(:last_login_on, Time.now) if user && !user.new_record?
123 user.update_attribute(:last_login_on, Time.now) if user && !user.new_record?
123 user
124 user
124 rescue => text
125 rescue => text
125 raise text
126 raise text
126 end
127 end
127
128
128 # Returns the user who matches the given autologin +key+ or nil
129 # Returns the user who matches the given autologin +key+ or nil
129 def self.try_to_autologin(key)
130 def self.try_to_autologin(key)
130 tokens = Token.find_all_by_action_and_value('autologin', key)
131 tokens = Token.find_all_by_action_and_value('autologin', key)
131 # Make sure there's only 1 token that matches the key
132 # Make sure there's only 1 token that matches the key
132 if tokens.size == 1
133 if tokens.size == 1
133 token = tokens.first
134 token = tokens.first
134 if (token.created_on > Setting.autologin.to_i.day.ago) && token.user && token.user.active?
135 if (token.created_on > Setting.autologin.to_i.day.ago) && token.user && token.user.active?
135 token.user.update_attribute(:last_login_on, Time.now)
136 token.user.update_attribute(:last_login_on, Time.now)
136 token.user
137 token.user
137 end
138 end
138 end
139 end
139 end
140 end
140
141
141 # Return user's full name for display
142 # Return user's full name for display
142 def name(formatter = nil)
143 def name(formatter = nil)
143 if formatter
144 if formatter
144 eval('"' + (USER_FORMATS[formatter] || USER_FORMATS[:firstname_lastname]) + '"')
145 eval('"' + (USER_FORMATS[formatter] || USER_FORMATS[:firstname_lastname]) + '"')
145 else
146 else
146 @name ||= eval('"' + (USER_FORMATS[Setting.user_format] || USER_FORMATS[:firstname_lastname]) + '"')
147 @name ||= eval('"' + (USER_FORMATS[Setting.user_format] || USER_FORMATS[:firstname_lastname]) + '"')
147 end
148 end
148 end
149 end
149
150
150 def active?
151 def active?
151 self.status == STATUS_ACTIVE
152 self.status == STATUS_ACTIVE
152 end
153 end
153
154
154 def registered?
155 def registered?
155 self.status == STATUS_REGISTERED
156 self.status == STATUS_REGISTERED
156 end
157 end
157
158
158 def locked?
159 def locked?
159 self.status == STATUS_LOCKED
160 self.status == STATUS_LOCKED
160 end
161 end
161
162
162 def check_password?(clear_password)
163 def check_password?(clear_password)
163 User.hash_password(clear_password) == self.hashed_password
164 User.hash_password(clear_password) == self.hashed_password
164 end
165 end
165
166
166 # Generate and set a random password. Useful for automated user creation
167 # Generate and set a random password. Useful for automated user creation
167 # Based on Token#generate_token_value
168 # Based on Token#generate_token_value
168 #
169 #
169 def random_password
170 def random_password
170 chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
171 chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
171 password = ''
172 password = ''
172 40.times { |i| password << chars[rand(chars.size-1)] }
173 40.times { |i| password << chars[rand(chars.size-1)] }
173 self.password = password
174 self.password = password
174 self.password_confirmation = password
175 self.password_confirmation = password
175 self
176 self
176 end
177 end
177
178
178 def pref
179 def pref
179 self.preference ||= UserPreference.new(:user => self)
180 self.preference ||= UserPreference.new(:user => self)
180 end
181 end
181
182
182 def time_zone
183 def time_zone
183 @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone])
184 @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone])
184 end
185 end
185
186
186 def wants_comments_in_reverse_order?
187 def wants_comments_in_reverse_order?
187 self.pref[:comments_sorting] == 'desc'
188 self.pref[:comments_sorting] == 'desc'
188 end
189 end
189
190
190 # Return user's RSS key (a 40 chars long string), used to access feeds
191 # Return user's RSS key (a 40 chars long string), used to access feeds
191 def rss_key
192 def rss_key
192 token = self.rss_token || Token.create(:user => self, :action => 'feeds')
193 token = self.rss_token || Token.create(:user => self, :action => 'feeds')
193 token.value
194 token.value
194 end
195 end
196
197 # Return user's API key (a 40 chars long string), used to access the API
198 def api_key
199 token = self.api_token || Token.create(:user => self, :action => 'api')
200 token.value
201 end
195
202
196 # Return an array of project ids for which the user has explicitly turned mail notifications on
203 # Return an array of project ids for which the user has explicitly turned mail notifications on
197 def notified_projects_ids
204 def notified_projects_ids
198 @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
205 @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
199 end
206 end
200
207
201 def notified_project_ids=(ids)
208 def notified_project_ids=(ids)
202 Member.update_all("mail_notification = #{connection.quoted_false}", ['user_id = ?', id])
209 Member.update_all("mail_notification = #{connection.quoted_false}", ['user_id = ?', id])
203 Member.update_all("mail_notification = #{connection.quoted_true}", ['user_id = ? AND project_id IN (?)', id, ids]) if ids && !ids.empty?
210 Member.update_all("mail_notification = #{connection.quoted_true}", ['user_id = ? AND project_id IN (?)', id, ids]) if ids && !ids.empty?
204 @notified_projects_ids = nil
211 @notified_projects_ids = nil
205 notified_projects_ids
212 notified_projects_ids
206 end
213 end
207
214
208 def self.find_by_rss_key(key)
215 def self.find_by_rss_key(key)
209 token = Token.find_by_value(key)
216 token = Token.find_by_value(key)
210 token && token.user.active? ? token.user : nil
217 token && token.user.active? ? token.user : nil
211 end
218 end
212
219
220 def self.find_by_api_key(key)
221 token = Token.find_by_action_and_value('api', key)
222 token && token.user.active? ? token.user : nil
223 end
224
213 # Makes find_by_mail case-insensitive
225 # Makes find_by_mail case-insensitive
214 def self.find_by_mail(mail)
226 def self.find_by_mail(mail)
215 find(:first, :conditions => ["LOWER(mail) = ?", mail.to_s.downcase])
227 find(:first, :conditions => ["LOWER(mail) = ?", mail.to_s.downcase])
216 end
228 end
217
229
218 def to_s
230 def to_s
219 name
231 name
220 end
232 end
221
233
222 # Returns the current day according to user's time zone
234 # Returns the current day according to user's time zone
223 def today
235 def today
224 if time_zone.nil?
236 if time_zone.nil?
225 Date.today
237 Date.today
226 else
238 else
227 Time.now.in_time_zone(time_zone).to_date
239 Time.now.in_time_zone(time_zone).to_date
228 end
240 end
229 end
241 end
230
242
231 def logged?
243 def logged?
232 true
244 true
233 end
245 end
234
246
235 def anonymous?
247 def anonymous?
236 !logged?
248 !logged?
237 end
249 end
238
250
239 # Return user's roles for project
251 # Return user's roles for project
240 def roles_for_project(project)
252 def roles_for_project(project)
241 roles = []
253 roles = []
242 # No role on archived projects
254 # No role on archived projects
243 return roles unless project && project.active?
255 return roles unless project && project.active?
244 if logged?
256 if logged?
245 # Find project membership
257 # Find project membership
246 membership = memberships.detect {|m| m.project_id == project.id}
258 membership = memberships.detect {|m| m.project_id == project.id}
247 if membership
259 if membership
248 roles = membership.roles
260 roles = membership.roles
249 else
261 else
250 @role_non_member ||= Role.non_member
262 @role_non_member ||= Role.non_member
251 roles << @role_non_member
263 roles << @role_non_member
252 end
264 end
253 else
265 else
254 @role_anonymous ||= Role.anonymous
266 @role_anonymous ||= Role.anonymous
255 roles << @role_anonymous
267 roles << @role_anonymous
256 end
268 end
257 roles
269 roles
258 end
270 end
259
271
260 # Return true if the user is a member of project
272 # Return true if the user is a member of project
261 def member_of?(project)
273 def member_of?(project)
262 !roles_for_project(project).detect {|role| role.member?}.nil?
274 !roles_for_project(project).detect {|role| role.member?}.nil?
263 end
275 end
264
276
265 # Return true if the user is allowed to do the specified action on project
277 # Return true if the user is allowed to do the specified action on project
266 # action can be:
278 # action can be:
267 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
279 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
268 # * a permission Symbol (eg. :edit_project)
280 # * a permission Symbol (eg. :edit_project)
269 def allowed_to?(action, project, options={})
281 def allowed_to?(action, project, options={})
270 if project
282 if project
271 # No action allowed on archived projects
283 # No action allowed on archived projects
272 return false unless project.active?
284 return false unless project.active?
273 # No action allowed on disabled modules
285 # No action allowed on disabled modules
274 return false unless project.allows_to?(action)
286 return false unless project.allows_to?(action)
275 # Admin users are authorized for anything else
287 # Admin users are authorized for anything else
276 return true if admin?
288 return true if admin?
277
289
278 roles = roles_for_project(project)
290 roles = roles_for_project(project)
279 return false unless roles
291 return false unless roles
280 roles.detect {|role| (project.is_public? || role.member?) && role.allowed_to?(action)}
292 roles.detect {|role| (project.is_public? || role.member?) && role.allowed_to?(action)}
281
293
282 elsif options[:global]
294 elsif options[:global]
283 # Admin users are always authorized
295 # Admin users are always authorized
284 return true if admin?
296 return true if admin?
285
297
286 # authorize if user has at least one role that has this permission
298 # authorize if user has at least one role that has this permission
287 roles = memberships.collect {|m| m.roles}.flatten.uniq
299 roles = memberships.collect {|m| m.roles}.flatten.uniq
288 roles.detect {|r| r.allowed_to?(action)} || (self.logged? ? Role.non_member.allowed_to?(action) : Role.anonymous.allowed_to?(action))
300 roles.detect {|r| r.allowed_to?(action)} || (self.logged? ? Role.non_member.allowed_to?(action) : Role.anonymous.allowed_to?(action))
289 else
301 else
290 false
302 false
291 end
303 end
292 end
304 end
293
305
294 def self.current=(user)
306 def self.current=(user)
295 @current_user = user
307 @current_user = user
296 end
308 end
297
309
298 def self.current
310 def self.current
299 @current_user ||= User.anonymous
311 @current_user ||= User.anonymous
300 end
312 end
301
313
302 # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
314 # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
303 # one anonymous user per database.
315 # one anonymous user per database.
304 def self.anonymous
316 def self.anonymous
305 anonymous_user = AnonymousUser.find(:first)
317 anonymous_user = AnonymousUser.find(:first)
306 if anonymous_user.nil?
318 if anonymous_user.nil?
307 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0)
319 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0)
308 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
320 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
309 end
321 end
310 anonymous_user
322 anonymous_user
311 end
323 end
312
324
313 protected
325 protected
314
326
315 def validate
327 def validate
316 # Password length validation based on setting
328 # Password length validation based on setting
317 if !password.nil? && password.size < Setting.password_min_length.to_i
329 if !password.nil? && password.size < Setting.password_min_length.to_i
318 errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
330 errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
319 end
331 end
320 end
332 end
321
333
322 private
334 private
323
335
324 # Return password digest
336 # Return password digest
325 def self.hash_password(clear_password)
337 def self.hash_password(clear_password)
326 Digest::SHA1.hexdigest(clear_password || "")
338 Digest::SHA1.hexdigest(clear_password || "")
327 end
339 end
328 end
340 end
329
341
330 class AnonymousUser < User
342 class AnonymousUser < User
331
343
332 def validate_on_create
344 def validate_on_create
333 # There should be only one AnonymousUser in the database
345 # There should be only one AnonymousUser in the database
334 errors.add_to_base 'An anonymous user already exists.' if AnonymousUser.find(:first)
346 errors.add_to_base 'An anonymous user already exists.' if AnonymousUser.find(:first)
335 end
347 end
336
348
337 def available_custom_fields
349 def available_custom_fields
338 []
350 []
339 end
351 end
340
352
341 # Overrides a few properties
353 # Overrides a few properties
342 def logged?; false end
354 def logged?; false end
343 def admin; false end
355 def admin; false end
344 def name(*args); I18n.t(:label_user_anonymous) end
356 def name(*args); I18n.t(:label_user_anonymous) end
345 def mail; nil end
357 def mail; nil end
346 def time_zone; nil end
358 def time_zone; nil end
347 def rss_key; nil end
359 def rss_key; nil end
348 end
360 end
@@ -1,8 +1,26
1 <h3><%=l(:label_my_account)%></h3>
1 <h3><%=l(:label_my_account)%></h3>
2
2
3 <p><%=l(:field_login)%>: <strong><%= @user.login %></strong><br />
3 <p><%=l(:field_login)%>: <strong><%= @user.login %></strong><br />
4 <%=l(:field_created_on)%>: <%= format_time(@user.created_on) %></p>
4 <%=l(:field_created_on)%>: <%= format_time(@user.created_on) %></p>
5
6
7 <h4><%= l(:label_feeds_access_key) %></h4>
8
9 <p>
5 <% if @user.rss_token %>
10 <% if @user.rss_token %>
6 <p><%= l(:label_feeds_access_key_created_on, distance_of_time_in_words(Time.now, @user.rss_token.created_on)) %>
11 <%= l(:label_feeds_access_key_created_on, distance_of_time_in_words(Time.now, @user.rss_token.created_on)) %>
7 (<%= link_to l(:button_reset), {:action => 'reset_rss_key'}, :method => :post %>)</p>
12 <% else %>
13 <%= l(:label_missing_feeds_access_key) %>
14 <% end %>
15 (<%= link_to l(:button_reset), {:action => 'reset_rss_key'}, :method => :post %>)
16 </p>
17
18 <h4><%= l(:label_api_access_key) %></h4>
19 <p>
20 <% if @user.api_token %>
21 <%= l(:label_api_access_key_created_on, distance_of_time_in_words(Time.now, @user.api_token.created_on)) %>
22 <% else %>
23 <%= l(:label_missing_api_access_key) %>
8 <% end %>
24 <% end %>
25 (<%= link_to l(:button_reset), {:action => 'reset_api_key'}, :method => :post %>)
26 </p>
@@ -1,61 +1,73
1 <div class="contextual">
1 <div class="contextual">
2 <%= link_to(l(:button_change_password), :action => 'password') unless @user.auth_source_id %>
2 <%= link_to(l(:button_change_password), :action => 'password') unless @user.auth_source_id %>
3 <%= call_hook(:view_my_account_contextual, :user => @user)%>
3 <%= call_hook(:view_my_account_contextual, :user => @user)%>
4 </div>
4 </div>
5 <h2><%=l(:label_my_account)%></h2>
5 <h2><%=l(:label_my_account)%></h2>
6 <%= error_messages_for 'user' %>
6 <%= error_messages_for 'user' %>
7
7
8 <% form_for :user, @user, :url => { :action => "account" },
8 <% form_for :user, @user, :url => { :action => "account" },
9 :builder => TabularFormBuilder,
9 :builder => TabularFormBuilder,
10 :lang => current_language,
10 :lang => current_language,
11 :html => { :id => 'my_account_form' } do |f| %>
11 :html => { :id => 'my_account_form' } do |f| %>
12 <div class="splitcontentleft">
12 <div class="splitcontentleft">
13 <h3><%=l(:label_information_plural)%></h3>
13 <h3><%=l(:label_information_plural)%></h3>
14 <div class="box tabular">
14 <div class="box tabular">
15 <p><%= f.text_field :firstname, :required => true %></p>
15 <p><%= f.text_field :firstname, :required => true %></p>
16 <p><%= f.text_field :lastname, :required => true %></p>
16 <p><%= f.text_field :lastname, :required => true %></p>
17 <p><%= f.text_field :mail, :required => true %></p>
17 <p><%= f.text_field :mail, :required => true %></p>
18 <p><%= f.select :language, lang_options_for_select %></p>
18 <p><%= f.select :language, lang_options_for_select %></p>
19 <% if Setting.openid? %>
19 <% if Setting.openid? %>
20 <p><%= f.text_field :identity_url %></p>
20 <p><%= f.text_field :identity_url %></p>
21 <% end %>
21 <% end %>
22
22
23 <% @user.custom_field_values.select(&:editable?).each do |value| %>
23 <% @user.custom_field_values.select(&:editable?).each do |value| %>
24 <p><%= custom_field_tag_with_label :user, value %></p>
24 <p><%= custom_field_tag_with_label :user, value %></p>
25 <% end %>
25 <% end %>
26 <%= call_hook(:view_my_account, :user => @user, :form => f) %>
26 <%= call_hook(:view_my_account, :user => @user, :form => f) %>
27 </div>
27 </div>
28
28
29 <%= submit_tag l(:button_save) %>
29 <%= submit_tag l(:button_save) %>
30 </div>
30 </div>
31
31
32 <div class="splitcontentright">
32 <div class="splitcontentright">
33 <h3><%=l(:field_mail_notification)%></h3>
33 <h3><%=l(:field_mail_notification)%></h3>
34 <div class="box">
34 <div class="box">
35 <%= select_tag 'notification_option', options_for_select(@notification_options, @notification_option),
35 <%= select_tag 'notification_option', options_for_select(@notification_options, @notification_option),
36 :onchange => 'if ($("notification_option").value == "selected") {Element.show("notified-projects")} else {Element.hide("notified-projects")}' %>
36 :onchange => 'if ($("notification_option").value == "selected") {Element.show("notified-projects")} else {Element.hide("notified-projects")}' %>
37 <% content_tag 'div', :id => 'notified-projects', :style => (@notification_option == 'selected' ? '' : 'display:none;') do %>
37 <% content_tag 'div', :id => 'notified-projects', :style => (@notification_option == 'selected' ? '' : 'display:none;') do %>
38 <p><% User.current.projects.each do |project| %>
38 <p><% User.current.projects.each do |project| %>
39 <label><%= check_box_tag 'notified_project_ids[]', project.id, @user.notified_projects_ids.include?(project.id) %> <%=h project.name %></label><br />
39 <label><%= check_box_tag 'notified_project_ids[]', project.id, @user.notified_projects_ids.include?(project.id) %> <%=h project.name %></label><br />
40 <% end %></p>
40 <% end %></p>
41 <p><em><%= l(:text_user_mail_option) %></em></p>
41 <p><em><%= l(:text_user_mail_option) %></em></p>
42 <% end %>
42 <% end %>
43 <p><label><%= check_box_tag 'no_self_notified', 1, @user.pref[:no_self_notified] %> <%= l(:label_user_mail_no_self_notified) %></label></p>
43 <p><label><%= check_box_tag 'no_self_notified', 1, @user.pref[:no_self_notified] %> <%= l(:label_user_mail_no_self_notified) %></label></p>
44 </div>
44 </div>
45
45
46 <h3><%=l(:label_preferences)%></h3>
46 <h3><%=l(:label_preferences)%></h3>
47 <div class="box tabular">
47 <div class="box tabular">
48 <% fields_for :pref, @user.pref, :builder => TabularFormBuilder, :lang => current_language do |pref_fields| %>
48 <% fields_for :pref, @user.pref, :builder => TabularFormBuilder, :lang => current_language do |pref_fields| %>
49 <p><%= pref_fields.check_box :hide_mail %></p>
49 <p><%= pref_fields.check_box :hide_mail %></p>
50 <p><%= pref_fields.select :time_zone, ActiveSupport::TimeZone.all.collect {|z| [ z.to_s, z.name ]}, :include_blank => true %></p>
50 <p><%= pref_fields.select :time_zone, ActiveSupport::TimeZone.all.collect {|z| [ z.to_s, z.name ]}, :include_blank => true %></p>
51 <p><%= pref_fields.select :comments_sorting, [[l(:label_chronological_order), 'asc'], [l(:label_reverse_chronological_order), 'desc']] %></p>
51 <p><%= pref_fields.select :comments_sorting, [[l(:label_chronological_order), 'asc'], [l(:label_reverse_chronological_order), 'desc']] %></p>
52 <% end %>
52 <% end %>
53 </div>
53 </div>
54
55 <% if @user.api_token %>
56 <h3><%=l(:label_api_access_key) %></h3>
57 <div class="box">
58 <p>
59 <%= link_to_function(l(:text_show), "$('api-access-key').show();")%>
60 <pre id='api-access-key'><%= @user.api_key %></pre>
61 </p>
62 <%= javascript_tag("$('api-access-key').hide();") %>
63 </div>
64 <% end %>
65
54 </div>
66 </div>
55 <% end %>
67 <% end %>
56
68
57 <% content_for :sidebar do %>
69 <% content_for :sidebar do %>
58 <%= render :partial => 'sidebar' %>
70 <%= render :partial => 'sidebar' %>
59 <% end %>
71 <% end %>
60
72
61 <% html_title(l(:label_my_account)) -%>
73 <% html_title(l(:label_my_account)) -%>
@@ -1,865 +1,872
1 en:
1 en:
2 date:
2 date:
3 formats:
3 formats:
4 # Use the strftime parameters for formats.
4 # Use the strftime parameters for formats.
5 # When no format has been given, it uses default.
5 # When no format has been given, it uses default.
6 # You can provide other formats here if you like!
6 # You can provide other formats here if you like!
7 default: "%m/%d/%Y"
7 default: "%m/%d/%Y"
8 short: "%b %d"
8 short: "%b %d"
9 long: "%B %d, %Y"
9 long: "%B %d, %Y"
10
10
11 day_names: [Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday]
11 day_names: [Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday]
12 abbr_day_names: [Sun, Mon, Tue, Wed, Thu, Fri, Sat]
12 abbr_day_names: [Sun, Mon, Tue, Wed, Thu, Fri, Sat]
13
13
14 # Don't forget the nil at the beginning; there's no such thing as a 0th month
14 # Don't forget the nil at the beginning; there's no such thing as a 0th month
15 month_names: [~, January, February, March, April, May, June, July, August, September, October, November, December]
15 month_names: [~, January, February, March, April, May, June, July, August, September, October, November, December]
16 abbr_month_names: [~, Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec]
16 abbr_month_names: [~, Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec]
17 # Used in date_select and datime_select.
17 # Used in date_select and datime_select.
18 order: [ :year, :month, :day ]
18 order: [ :year, :month, :day ]
19
19
20 time:
20 time:
21 formats:
21 formats:
22 default: "%m/%d/%Y %I:%M %p"
22 default: "%m/%d/%Y %I:%M %p"
23 time: "%I:%M %p"
23 time: "%I:%M %p"
24 short: "%d %b %H:%M"
24 short: "%d %b %H:%M"
25 long: "%B %d, %Y %H:%M"
25 long: "%B %d, %Y %H:%M"
26 am: "am"
26 am: "am"
27 pm: "pm"
27 pm: "pm"
28
28
29 datetime:
29 datetime:
30 distance_in_words:
30 distance_in_words:
31 half_a_minute: "half a minute"
31 half_a_minute: "half a minute"
32 less_than_x_seconds:
32 less_than_x_seconds:
33 one: "less than 1 second"
33 one: "less than 1 second"
34 other: "less than {{count}} seconds"
34 other: "less than {{count}} seconds"
35 x_seconds:
35 x_seconds:
36 one: "1 second"
36 one: "1 second"
37 other: "{{count}} seconds"
37 other: "{{count}} seconds"
38 less_than_x_minutes:
38 less_than_x_minutes:
39 one: "less than a minute"
39 one: "less than a minute"
40 other: "less than {{count}} minutes"
40 other: "less than {{count}} minutes"
41 x_minutes:
41 x_minutes:
42 one: "1 minute"
42 one: "1 minute"
43 other: "{{count}} minutes"
43 other: "{{count}} minutes"
44 about_x_hours:
44 about_x_hours:
45 one: "about 1 hour"
45 one: "about 1 hour"
46 other: "about {{count}} hours"
46 other: "about {{count}} hours"
47 x_days:
47 x_days:
48 one: "1 day"
48 one: "1 day"
49 other: "{{count}} days"
49 other: "{{count}} days"
50 about_x_months:
50 about_x_months:
51 one: "about 1 month"
51 one: "about 1 month"
52 other: "about {{count}} months"
52 other: "about {{count}} months"
53 x_months:
53 x_months:
54 one: "1 month"
54 one: "1 month"
55 other: "{{count}} months"
55 other: "{{count}} months"
56 about_x_years:
56 about_x_years:
57 one: "about 1 year"
57 one: "about 1 year"
58 other: "about {{count}} years"
58 other: "about {{count}} years"
59 over_x_years:
59 over_x_years:
60 one: "over 1 year"
60 one: "over 1 year"
61 other: "over {{count}} years"
61 other: "over {{count}} years"
62
62
63 number:
63 number:
64 human:
64 human:
65 format:
65 format:
66 delimiter: ""
66 delimiter: ""
67 precision: 1
67 precision: 1
68 storage_units:
68 storage_units:
69 format: "%n %u"
69 format: "%n %u"
70 units:
70 units:
71 byte:
71 byte:
72 one: "Byte"
72 one: "Byte"
73 other: "Bytes"
73 other: "Bytes"
74 kb: "KB"
74 kb: "KB"
75 mb: "MB"
75 mb: "MB"
76 gb: "GB"
76 gb: "GB"
77 tb: "TB"
77 tb: "TB"
78
78
79
79
80 # Used in array.to_sentence.
80 # Used in array.to_sentence.
81 support:
81 support:
82 array:
82 array:
83 sentence_connector: "and"
83 sentence_connector: "and"
84 skip_last_comma: false
84 skip_last_comma: false
85
85
86 activerecord:
86 activerecord:
87 errors:
87 errors:
88 messages:
88 messages:
89 inclusion: "is not included in the list"
89 inclusion: "is not included in the list"
90 exclusion: "is reserved"
90 exclusion: "is reserved"
91 invalid: "is invalid"
91 invalid: "is invalid"
92 confirmation: "doesn't match confirmation"
92 confirmation: "doesn't match confirmation"
93 accepted: "must be accepted"
93 accepted: "must be accepted"
94 empty: "can't be empty"
94 empty: "can't be empty"
95 blank: "can't be blank"
95 blank: "can't be blank"
96 too_long: "is too long (maximum is {{count}} characters)"
96 too_long: "is too long (maximum is {{count}} characters)"
97 too_short: "is too short (minimum is {{count}} characters)"
97 too_short: "is too short (minimum is {{count}} characters)"
98 wrong_length: "is the wrong length (should be {{count}} characters)"
98 wrong_length: "is the wrong length (should be {{count}} characters)"
99 taken: "has already been taken"
99 taken: "has already been taken"
100 not_a_number: "is not a number"
100 not_a_number: "is not a number"
101 not_a_date: "is not a valid date"
101 not_a_date: "is not a valid date"
102 greater_than: "must be greater than {{count}}"
102 greater_than: "must be greater than {{count}}"
103 greater_than_or_equal_to: "must be greater than or equal to {{count}}"
103 greater_than_or_equal_to: "must be greater than or equal to {{count}}"
104 equal_to: "must be equal to {{count}}"
104 equal_to: "must be equal to {{count}}"
105 less_than: "must be less than {{count}}"
105 less_than: "must be less than {{count}}"
106 less_than_or_equal_to: "must be less than or equal to {{count}}"
106 less_than_or_equal_to: "must be less than or equal to {{count}}"
107 odd: "must be odd"
107 odd: "must be odd"
108 even: "must be even"
108 even: "must be even"
109 greater_than_start_date: "must be greater than start date"
109 greater_than_start_date: "must be greater than start date"
110 not_same_project: "doesn't belong to the same project"
110 not_same_project: "doesn't belong to the same project"
111 circular_dependency: "This relation would create a circular dependency"
111 circular_dependency: "This relation would create a circular dependency"
112
112
113 actionview_instancetag_blank_option: Please select
113 actionview_instancetag_blank_option: Please select
114
114
115 general_text_No: 'No'
115 general_text_No: 'No'
116 general_text_Yes: 'Yes'
116 general_text_Yes: 'Yes'
117 general_text_no: 'no'
117 general_text_no: 'no'
118 general_text_yes: 'yes'
118 general_text_yes: 'yes'
119 general_lang_name: 'English'
119 general_lang_name: 'English'
120 general_csv_separator: ','
120 general_csv_separator: ','
121 general_csv_decimal_separator: '.'
121 general_csv_decimal_separator: '.'
122 general_csv_encoding: ISO-8859-1
122 general_csv_encoding: ISO-8859-1
123 general_pdf_encoding: ISO-8859-1
123 general_pdf_encoding: ISO-8859-1
124 general_first_day_of_week: '7'
124 general_first_day_of_week: '7'
125
125
126 notice_account_updated: Account was successfully updated.
126 notice_account_updated: Account was successfully updated.
127 notice_account_invalid_creditentials: Invalid user or password
127 notice_account_invalid_creditentials: Invalid user or password
128 notice_account_password_updated: Password was successfully updated.
128 notice_account_password_updated: Password was successfully updated.
129 notice_account_wrong_password: Wrong password
129 notice_account_wrong_password: Wrong password
130 notice_account_register_done: Account was successfully created. To activate your account, click on the link that was emailed to you.
130 notice_account_register_done: Account was successfully created. To activate your account, click on the link that was emailed to you.
131 notice_account_unknown_email: Unknown user.
131 notice_account_unknown_email: Unknown user.
132 notice_can_t_change_password: This account uses an external authentication source. Impossible to change the password.
132 notice_can_t_change_password: This account uses an external authentication source. Impossible to change the password.
133 notice_account_lost_email_sent: An email with instructions to choose a new password has been sent to you.
133 notice_account_lost_email_sent: An email with instructions to choose a new password has been sent to you.
134 notice_account_activated: Your account has been activated. You can now log in.
134 notice_account_activated: Your account has been activated. You can now log in.
135 notice_successful_create: Successful creation.
135 notice_successful_create: Successful creation.
136 notice_successful_update: Successful update.
136 notice_successful_update: Successful update.
137 notice_successful_delete: Successful deletion.
137 notice_successful_delete: Successful deletion.
138 notice_successful_connection: Successful connection.
138 notice_successful_connection: Successful connection.
139 notice_file_not_found: The page you were trying to access doesn't exist or has been removed.
139 notice_file_not_found: The page you were trying to access doesn't exist or has been removed.
140 notice_locking_conflict: Data has been updated by another user.
140 notice_locking_conflict: Data has been updated by another user.
141 notice_not_authorized: You are not authorized to access this page.
141 notice_not_authorized: You are not authorized to access this page.
142 notice_email_sent: "An email was sent to {{value}}"
142 notice_email_sent: "An email was sent to {{value}}"
143 notice_email_error: "An error occurred while sending mail ({{value}})"
143 notice_email_error: "An error occurred while sending mail ({{value}})"
144 notice_feeds_access_key_reseted: Your RSS access key was reset.
144 notice_feeds_access_key_reseted: Your RSS access key was reset.
145 notice_api_access_key_reseted: Your API access key was reset.
145 notice_failed_to_save_issues: "Failed to save {{count}} issue(s) on {{total}} selected: {{ids}}."
146 notice_failed_to_save_issues: "Failed to save {{count}} issue(s) on {{total}} selected: {{ids}}."
146 notice_no_issue_selected: "No issue is selected! Please, check the issues you want to edit."
147 notice_no_issue_selected: "No issue is selected! Please, check the issues you want to edit."
147 notice_account_pending: "Your account was created and is now pending administrator approval."
148 notice_account_pending: "Your account was created and is now pending administrator approval."
148 notice_default_data_loaded: Default configuration successfully loaded.
149 notice_default_data_loaded: Default configuration successfully loaded.
149 notice_unable_delete_version: Unable to delete version.
150 notice_unable_delete_version: Unable to delete version.
150 notice_issue_done_ratios_updated: Issue done ratios updated.
151 notice_issue_done_ratios_updated: Issue done ratios updated.
151
152
152 error_can_t_load_default_data: "Default configuration could not be loaded: {{value}}"
153 error_can_t_load_default_data: "Default configuration could not be loaded: {{value}}"
153 error_scm_not_found: "The entry or revision was not found in the repository."
154 error_scm_not_found: "The entry or revision was not found in the repository."
154 error_scm_command_failed: "An error occurred when trying to access the repository: {{value}}"
155 error_scm_command_failed: "An error occurred when trying to access the repository: {{value}}"
155 error_scm_annotate: "The entry does not exist or can not be annotated."
156 error_scm_annotate: "The entry does not exist or can not be annotated."
156 error_issue_not_found_in_project: 'The issue was not found or does not belong to this project'
157 error_issue_not_found_in_project: 'The issue was not found or does not belong to this project'
157 error_no_tracker_in_project: 'No tracker is associated to this project. Please check the Project settings.'
158 error_no_tracker_in_project: 'No tracker is associated to this project. Please check the Project settings.'
158 error_no_default_issue_status: 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").'
159 error_no_default_issue_status: 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").'
159 error_can_not_reopen_issue_on_closed_version: 'An issue assigned to a closed version can not be reopened'
160 error_can_not_reopen_issue_on_closed_version: 'An issue assigned to a closed version can not be reopened'
160 error_can_not_archive_project: This project can not be archived
161 error_can_not_archive_project: This project can not be archived
161 error_issue_done_ratios_not_updated: "Issue done ratios not updated."
162 error_issue_done_ratios_not_updated: "Issue done ratios not updated."
162 error_workflow_copy_source: 'Please select a source tracker or role'
163 error_workflow_copy_source: 'Please select a source tracker or role'
163 error_workflow_copy_target: 'Please select target tracker(s) and role(s)'
164 error_workflow_copy_target: 'Please select target tracker(s) and role(s)'
164
165
165 warning_attachments_not_saved: "{{count}} file(s) could not be saved."
166 warning_attachments_not_saved: "{{count}} file(s) could not be saved."
166
167
167 mail_subject_lost_password: "Your {{value}} password"
168 mail_subject_lost_password: "Your {{value}} password"
168 mail_body_lost_password: 'To change your password, click on the following link:'
169 mail_body_lost_password: 'To change your password, click on the following link:'
169 mail_subject_register: "Your {{value}} account activation"
170 mail_subject_register: "Your {{value}} account activation"
170 mail_body_register: 'To activate your account, click on the following link:'
171 mail_body_register: 'To activate your account, click on the following link:'
171 mail_body_account_information_external: "You can use your {{value}} account to log in."
172 mail_body_account_information_external: "You can use your {{value}} account to log in."
172 mail_body_account_information: Your account information
173 mail_body_account_information: Your account information
173 mail_subject_account_activation_request: "{{value}} account activation request"
174 mail_subject_account_activation_request: "{{value}} account activation request"
174 mail_body_account_activation_request: "A new user ({{value}}) has registered. The account is pending your approval:"
175 mail_body_account_activation_request: "A new user ({{value}}) has registered. The account is pending your approval:"
175 mail_subject_reminder: "{{count}} issue(s) due in the next days"
176 mail_subject_reminder: "{{count}} issue(s) due in the next days"
176 mail_body_reminder: "{{count}} issue(s) that are assigned to you are due in the next {{days}} days:"
177 mail_body_reminder: "{{count}} issue(s) that are assigned to you are due in the next {{days}} days:"
177 mail_subject_wiki_content_added: "'{{page}}' wiki page has been added"
178 mail_subject_wiki_content_added: "'{{page}}' wiki page has been added"
178 mail_body_wiki_content_added: "The '{{page}}' wiki page has been added by {{author}}."
179 mail_body_wiki_content_added: "The '{{page}}' wiki page has been added by {{author}}."
179 mail_subject_wiki_content_updated: "'{{page}}' wiki page has been updated"
180 mail_subject_wiki_content_updated: "'{{page}}' wiki page has been updated"
180 mail_body_wiki_content_updated: "The '{{page}}' wiki page has been updated by {{author}}."
181 mail_body_wiki_content_updated: "The '{{page}}' wiki page has been updated by {{author}}."
181
182
182 gui_validation_error: 1 error
183 gui_validation_error: 1 error
183 gui_validation_error_plural: "{{count}} errors"
184 gui_validation_error_plural: "{{count}} errors"
184
185
185 field_name: Name
186 field_name: Name
186 field_description: Description
187 field_description: Description
187 field_summary: Summary
188 field_summary: Summary
188 field_is_required: Required
189 field_is_required: Required
189 field_firstname: Firstname
190 field_firstname: Firstname
190 field_lastname: Lastname
191 field_lastname: Lastname
191 field_mail: Email
192 field_mail: Email
192 field_filename: File
193 field_filename: File
193 field_filesize: Size
194 field_filesize: Size
194 field_downloads: Downloads
195 field_downloads: Downloads
195 field_author: Author
196 field_author: Author
196 field_created_on: Created
197 field_created_on: Created
197 field_updated_on: Updated
198 field_updated_on: Updated
198 field_field_format: Format
199 field_field_format: Format
199 field_is_for_all: For all projects
200 field_is_for_all: For all projects
200 field_possible_values: Possible values
201 field_possible_values: Possible values
201 field_regexp: Regular expression
202 field_regexp: Regular expression
202 field_min_length: Minimum length
203 field_min_length: Minimum length
203 field_max_length: Maximum length
204 field_max_length: Maximum length
204 field_value: Value
205 field_value: Value
205 field_category: Category
206 field_category: Category
206 field_title: Title
207 field_title: Title
207 field_project: Project
208 field_project: Project
208 field_issue: Issue
209 field_issue: Issue
209 field_status: Status
210 field_status: Status
210 field_notes: Notes
211 field_notes: Notes
211 field_is_closed: Issue closed
212 field_is_closed: Issue closed
212 field_is_default: Default value
213 field_is_default: Default value
213 field_tracker: Tracker
214 field_tracker: Tracker
214 field_subject: Subject
215 field_subject: Subject
215 field_due_date: Due date
216 field_due_date: Due date
216 field_assigned_to: Assigned to
217 field_assigned_to: Assigned to
217 field_priority: Priority
218 field_priority: Priority
218 field_fixed_version: Target version
219 field_fixed_version: Target version
219 field_user: User
220 field_user: User
220 field_role: Role
221 field_role: Role
221 field_homepage: Homepage
222 field_homepage: Homepage
222 field_is_public: Public
223 field_is_public: Public
223 field_parent: Subproject of
224 field_parent: Subproject of
224 field_is_in_roadmap: Issues displayed in roadmap
225 field_is_in_roadmap: Issues displayed in roadmap
225 field_login: Login
226 field_login: Login
226 field_mail_notification: Email notifications
227 field_mail_notification: Email notifications
227 field_admin: Administrator
228 field_admin: Administrator
228 field_last_login_on: Last connection
229 field_last_login_on: Last connection
229 field_language: Language
230 field_language: Language
230 field_effective_date: Date
231 field_effective_date: Date
231 field_password: Password
232 field_password: Password
232 field_new_password: New password
233 field_new_password: New password
233 field_password_confirmation: Confirmation
234 field_password_confirmation: Confirmation
234 field_version: Version
235 field_version: Version
235 field_type: Type
236 field_type: Type
236 field_host: Host
237 field_host: Host
237 field_port: Port
238 field_port: Port
238 field_account: Account
239 field_account: Account
239 field_base_dn: Base DN
240 field_base_dn: Base DN
240 field_attr_login: Login attribute
241 field_attr_login: Login attribute
241 field_attr_firstname: Firstname attribute
242 field_attr_firstname: Firstname attribute
242 field_attr_lastname: Lastname attribute
243 field_attr_lastname: Lastname attribute
243 field_attr_mail: Email attribute
244 field_attr_mail: Email attribute
244 field_onthefly: On-the-fly user creation
245 field_onthefly: On-the-fly user creation
245 field_start_date: Start
246 field_start_date: Start
246 field_done_ratio: % Done
247 field_done_ratio: % Done
247 field_auth_source: Authentication mode
248 field_auth_source: Authentication mode
248 field_hide_mail: Hide my email address
249 field_hide_mail: Hide my email address
249 field_comments: Comment
250 field_comments: Comment
250 field_url: URL
251 field_url: URL
251 field_start_page: Start page
252 field_start_page: Start page
252 field_subproject: Subproject
253 field_subproject: Subproject
253 field_hours: Hours
254 field_hours: Hours
254 field_activity: Activity
255 field_activity: Activity
255 field_spent_on: Date
256 field_spent_on: Date
256 field_identifier: Identifier
257 field_identifier: Identifier
257 field_is_filter: Used as a filter
258 field_is_filter: Used as a filter
258 field_issue_to: Related issue
259 field_issue_to: Related issue
259 field_delay: Delay
260 field_delay: Delay
260 field_assignable: Issues can be assigned to this role
261 field_assignable: Issues can be assigned to this role
261 field_redirect_existing_links: Redirect existing links
262 field_redirect_existing_links: Redirect existing links
262 field_estimated_hours: Estimated time
263 field_estimated_hours: Estimated time
263 field_column_names: Columns
264 field_column_names: Columns
264 field_time_zone: Time zone
265 field_time_zone: Time zone
265 field_searchable: Searchable
266 field_searchable: Searchable
266 field_default_value: Default value
267 field_default_value: Default value
267 field_comments_sorting: Display comments
268 field_comments_sorting: Display comments
268 field_parent_title: Parent page
269 field_parent_title: Parent page
269 field_editable: Editable
270 field_editable: Editable
270 field_watcher: Watcher
271 field_watcher: Watcher
271 field_identity_url: OpenID URL
272 field_identity_url: OpenID URL
272 field_content: Content
273 field_content: Content
273 field_group_by: Group results by
274 field_group_by: Group results by
274 field_sharing: Sharing
275 field_sharing: Sharing
275
276
276 setting_app_title: Application title
277 setting_app_title: Application title
277 setting_app_subtitle: Application subtitle
278 setting_app_subtitle: Application subtitle
278 setting_welcome_text: Welcome text
279 setting_welcome_text: Welcome text
279 setting_default_language: Default language
280 setting_default_language: Default language
280 setting_login_required: Authentication required
281 setting_login_required: Authentication required
281 setting_self_registration: Self-registration
282 setting_self_registration: Self-registration
282 setting_attachment_max_size: Attachment max. size
283 setting_attachment_max_size: Attachment max. size
283 setting_issues_export_limit: Issues export limit
284 setting_issues_export_limit: Issues export limit
284 setting_mail_from: Emission email address
285 setting_mail_from: Emission email address
285 setting_bcc_recipients: Blind carbon copy recipients (bcc)
286 setting_bcc_recipients: Blind carbon copy recipients (bcc)
286 setting_plain_text_mail: Plain text mail (no HTML)
287 setting_plain_text_mail: Plain text mail (no HTML)
287 setting_host_name: Host name and path
288 setting_host_name: Host name and path
288 setting_text_formatting: Text formatting
289 setting_text_formatting: Text formatting
289 setting_wiki_compression: Wiki history compression
290 setting_wiki_compression: Wiki history compression
290 setting_feeds_limit: Feed content limit
291 setting_feeds_limit: Feed content limit
291 setting_default_projects_public: New projects are public by default
292 setting_default_projects_public: New projects are public by default
292 setting_autofetch_changesets: Autofetch commits
293 setting_autofetch_changesets: Autofetch commits
293 setting_sys_api_enabled: Enable WS for repository management
294 setting_sys_api_enabled: Enable WS for repository management
294 setting_commit_ref_keywords: Referencing keywords
295 setting_commit_ref_keywords: Referencing keywords
295 setting_commit_fix_keywords: Fixing keywords
296 setting_commit_fix_keywords: Fixing keywords
296 setting_autologin: Autologin
297 setting_autologin: Autologin
297 setting_date_format: Date format
298 setting_date_format: Date format
298 setting_time_format: Time format
299 setting_time_format: Time format
299 setting_cross_project_issue_relations: Allow cross-project issue relations
300 setting_cross_project_issue_relations: Allow cross-project issue relations
300 setting_issue_list_default_columns: Default columns displayed on the issue list
301 setting_issue_list_default_columns: Default columns displayed on the issue list
301 setting_repositories_encodings: Repositories encodings
302 setting_repositories_encodings: Repositories encodings
302 setting_commit_logs_encoding: Commit messages encoding
303 setting_commit_logs_encoding: Commit messages encoding
303 setting_emails_footer: Emails footer
304 setting_emails_footer: Emails footer
304 setting_protocol: Protocol
305 setting_protocol: Protocol
305 setting_per_page_options: Objects per page options
306 setting_per_page_options: Objects per page options
306 setting_user_format: Users display format
307 setting_user_format: Users display format
307 setting_activity_days_default: Days displayed on project activity
308 setting_activity_days_default: Days displayed on project activity
308 setting_display_subprojects_issues: Display subprojects issues on main projects by default
309 setting_display_subprojects_issues: Display subprojects issues on main projects by default
309 setting_enabled_scm: Enabled SCM
310 setting_enabled_scm: Enabled SCM
310 setting_mail_handler_api_enabled: Enable WS for incoming emails
311 setting_mail_handler_api_enabled: Enable WS for incoming emails
311 setting_mail_handler_api_key: API key
312 setting_mail_handler_api_key: API key
312 setting_sequential_project_identifiers: Generate sequential project identifiers
313 setting_sequential_project_identifiers: Generate sequential project identifiers
313 setting_gravatar_enabled: Use Gravatar user icons
314 setting_gravatar_enabled: Use Gravatar user icons
314 setting_gravatar_default: Default Gravatar image
315 setting_gravatar_default: Default Gravatar image
315 setting_diff_max_lines_displayed: Max number of diff lines displayed
316 setting_diff_max_lines_displayed: Max number of diff lines displayed
316 setting_file_max_size_displayed: Max size of text files displayed inline
317 setting_file_max_size_displayed: Max size of text files displayed inline
317 setting_repository_log_display_limit: Maximum number of revisions displayed on file log
318 setting_repository_log_display_limit: Maximum number of revisions displayed on file log
318 setting_openid: Allow OpenID login and registration
319 setting_openid: Allow OpenID login and registration
319 setting_password_min_length: Minimum password length
320 setting_password_min_length: Minimum password length
320 setting_new_project_user_role_id: Role given to a non-admin user who creates a project
321 setting_new_project_user_role_id: Role given to a non-admin user who creates a project
321 setting_default_projects_modules: Default enabled modules for new projects
322 setting_default_projects_modules: Default enabled modules for new projects
322 setting_issue_done_ratio: Calculate the issue done ratio with
323 setting_issue_done_ratio: Calculate the issue done ratio with
323 setting_issue_done_ratio_issue_field: Use the issue field
324 setting_issue_done_ratio_issue_field: Use the issue field
324 setting_issue_done_ratio_issue_status: Use the issue status
325 setting_issue_done_ratio_issue_status: Use the issue status
325 setting_start_of_week: Start calendars on
326 setting_start_of_week: Start calendars on
326
327
327 permission_add_project: Create project
328 permission_add_project: Create project
328 permission_edit_project: Edit project
329 permission_edit_project: Edit project
329 permission_select_project_modules: Select project modules
330 permission_select_project_modules: Select project modules
330 permission_manage_members: Manage members
331 permission_manage_members: Manage members
331 permission_manage_versions: Manage versions
332 permission_manage_versions: Manage versions
332 permission_manage_categories: Manage issue categories
333 permission_manage_categories: Manage issue categories
333 permission_view_issues: View Issues
334 permission_view_issues: View Issues
334 permission_add_issues: Add issues
335 permission_add_issues: Add issues
335 permission_edit_issues: Edit issues
336 permission_edit_issues: Edit issues
336 permission_manage_issue_relations: Manage issue relations
337 permission_manage_issue_relations: Manage issue relations
337 permission_add_issue_notes: Add notes
338 permission_add_issue_notes: Add notes
338 permission_edit_issue_notes: Edit notes
339 permission_edit_issue_notes: Edit notes
339 permission_edit_own_issue_notes: Edit own notes
340 permission_edit_own_issue_notes: Edit own notes
340 permission_move_issues: Move issues
341 permission_move_issues: Move issues
341 permission_delete_issues: Delete issues
342 permission_delete_issues: Delete issues
342 permission_manage_public_queries: Manage public queries
343 permission_manage_public_queries: Manage public queries
343 permission_save_queries: Save queries
344 permission_save_queries: Save queries
344 permission_view_gantt: View gantt chart
345 permission_view_gantt: View gantt chart
345 permission_view_calendar: View calendar
346 permission_view_calendar: View calendar
346 permission_view_issue_watchers: View watchers list
347 permission_view_issue_watchers: View watchers list
347 permission_add_issue_watchers: Add watchers
348 permission_add_issue_watchers: Add watchers
348 permission_delete_issue_watchers: Delete watchers
349 permission_delete_issue_watchers: Delete watchers
349 permission_log_time: Log spent time
350 permission_log_time: Log spent time
350 permission_view_time_entries: View spent time
351 permission_view_time_entries: View spent time
351 permission_edit_time_entries: Edit time logs
352 permission_edit_time_entries: Edit time logs
352 permission_edit_own_time_entries: Edit own time logs
353 permission_edit_own_time_entries: Edit own time logs
353 permission_manage_news: Manage news
354 permission_manage_news: Manage news
354 permission_comment_news: Comment news
355 permission_comment_news: Comment news
355 permission_manage_documents: Manage documents
356 permission_manage_documents: Manage documents
356 permission_view_documents: View documents
357 permission_view_documents: View documents
357 permission_manage_files: Manage files
358 permission_manage_files: Manage files
358 permission_view_files: View files
359 permission_view_files: View files
359 permission_manage_wiki: Manage wiki
360 permission_manage_wiki: Manage wiki
360 permission_rename_wiki_pages: Rename wiki pages
361 permission_rename_wiki_pages: Rename wiki pages
361 permission_delete_wiki_pages: Delete wiki pages
362 permission_delete_wiki_pages: Delete wiki pages
362 permission_view_wiki_pages: View wiki
363 permission_view_wiki_pages: View wiki
363 permission_view_wiki_edits: View wiki history
364 permission_view_wiki_edits: View wiki history
364 permission_edit_wiki_pages: Edit wiki pages
365 permission_edit_wiki_pages: Edit wiki pages
365 permission_delete_wiki_pages_attachments: Delete attachments
366 permission_delete_wiki_pages_attachments: Delete attachments
366 permission_protect_wiki_pages: Protect wiki pages
367 permission_protect_wiki_pages: Protect wiki pages
367 permission_manage_repository: Manage repository
368 permission_manage_repository: Manage repository
368 permission_browse_repository: Browse repository
369 permission_browse_repository: Browse repository
369 permission_view_changesets: View changesets
370 permission_view_changesets: View changesets
370 permission_commit_access: Commit access
371 permission_commit_access: Commit access
371 permission_manage_boards: Manage boards
372 permission_manage_boards: Manage boards
372 permission_view_messages: View messages
373 permission_view_messages: View messages
373 permission_add_messages: Post messages
374 permission_add_messages: Post messages
374 permission_edit_messages: Edit messages
375 permission_edit_messages: Edit messages
375 permission_edit_own_messages: Edit own messages
376 permission_edit_own_messages: Edit own messages
376 permission_delete_messages: Delete messages
377 permission_delete_messages: Delete messages
377 permission_delete_own_messages: Delete own messages
378 permission_delete_own_messages: Delete own messages
378
379
379 project_module_issue_tracking: Issue tracking
380 project_module_issue_tracking: Issue tracking
380 project_module_time_tracking: Time tracking
381 project_module_time_tracking: Time tracking
381 project_module_news: News
382 project_module_news: News
382 project_module_documents: Documents
383 project_module_documents: Documents
383 project_module_files: Files
384 project_module_files: Files
384 project_module_wiki: Wiki
385 project_module_wiki: Wiki
385 project_module_repository: Repository
386 project_module_repository: Repository
386 project_module_boards: Boards
387 project_module_boards: Boards
387
388
388 label_user: User
389 label_user: User
389 label_user_plural: Users
390 label_user_plural: Users
390 label_user_new: New user
391 label_user_new: New user
391 label_user_anonymous: Anonymous
392 label_user_anonymous: Anonymous
392 label_project: Project
393 label_project: Project
393 label_project_new: New project
394 label_project_new: New project
394 label_project_plural: Projects
395 label_project_plural: Projects
395 label_x_projects:
396 label_x_projects:
396 zero: no projects
397 zero: no projects
397 one: 1 project
398 one: 1 project
398 other: "{{count}} projects"
399 other: "{{count}} projects"
399 label_project_all: All Projects
400 label_project_all: All Projects
400 label_project_latest: Latest projects
401 label_project_latest: Latest projects
401 label_issue: Issue
402 label_issue: Issue
402 label_issue_new: New issue
403 label_issue_new: New issue
403 label_issue_plural: Issues
404 label_issue_plural: Issues
404 label_issue_view_all: View all issues
405 label_issue_view_all: View all issues
405 label_issues_by: "Issues by {{value}}"
406 label_issues_by: "Issues by {{value}}"
406 label_issue_added: Issue added
407 label_issue_added: Issue added
407 label_issue_updated: Issue updated
408 label_issue_updated: Issue updated
408 label_document: Document
409 label_document: Document
409 label_document_new: New document
410 label_document_new: New document
410 label_document_plural: Documents
411 label_document_plural: Documents
411 label_document_added: Document added
412 label_document_added: Document added
412 label_role: Role
413 label_role: Role
413 label_role_plural: Roles
414 label_role_plural: Roles
414 label_role_new: New role
415 label_role_new: New role
415 label_role_and_permissions: Roles and permissions
416 label_role_and_permissions: Roles and permissions
416 label_member: Member
417 label_member: Member
417 label_member_new: New member
418 label_member_new: New member
418 label_member_plural: Members
419 label_member_plural: Members
419 label_tracker: Tracker
420 label_tracker: Tracker
420 label_tracker_plural: Trackers
421 label_tracker_plural: Trackers
421 label_tracker_new: New tracker
422 label_tracker_new: New tracker
422 label_workflow: Workflow
423 label_workflow: Workflow
423 label_issue_status: Issue status
424 label_issue_status: Issue status
424 label_issue_status_plural: Issue statuses
425 label_issue_status_plural: Issue statuses
425 label_issue_status_new: New status
426 label_issue_status_new: New status
426 label_issue_category: Issue category
427 label_issue_category: Issue category
427 label_issue_category_plural: Issue categories
428 label_issue_category_plural: Issue categories
428 label_issue_category_new: New category
429 label_issue_category_new: New category
429 label_custom_field: Custom field
430 label_custom_field: Custom field
430 label_custom_field_plural: Custom fields
431 label_custom_field_plural: Custom fields
431 label_custom_field_new: New custom field
432 label_custom_field_new: New custom field
432 label_enumerations: Enumerations
433 label_enumerations: Enumerations
433 label_enumeration_new: New value
434 label_enumeration_new: New value
434 label_information: Information
435 label_information: Information
435 label_information_plural: Information
436 label_information_plural: Information
436 label_please_login: Please log in
437 label_please_login: Please log in
437 label_register: Register
438 label_register: Register
438 label_login_with_open_id_option: or login with OpenID
439 label_login_with_open_id_option: or login with OpenID
439 label_password_lost: Lost password
440 label_password_lost: Lost password
440 label_home: Home
441 label_home: Home
441 label_my_page: My page
442 label_my_page: My page
442 label_my_account: My account
443 label_my_account: My account
443 label_my_projects: My projects
444 label_my_projects: My projects
444 label_administration: Administration
445 label_administration: Administration
445 label_login: Sign in
446 label_login: Sign in
446 label_logout: Sign out
447 label_logout: Sign out
447 label_help: Help
448 label_help: Help
448 label_reported_issues: Reported issues
449 label_reported_issues: Reported issues
449 label_assigned_to_me_issues: Issues assigned to me
450 label_assigned_to_me_issues: Issues assigned to me
450 label_last_login: Last connection
451 label_last_login: Last connection
451 label_registered_on: Registered on
452 label_registered_on: Registered on
452 label_activity: Activity
453 label_activity: Activity
453 label_overall_activity: Overall activity
454 label_overall_activity: Overall activity
454 label_user_activity: "{{value}}'s activity"
455 label_user_activity: "{{value}}'s activity"
455 label_new: New
456 label_new: New
456 label_logged_as: Logged in as
457 label_logged_as: Logged in as
457 label_environment: Environment
458 label_environment: Environment
458 label_authentication: Authentication
459 label_authentication: Authentication
459 label_auth_source: Authentication mode
460 label_auth_source: Authentication mode
460 label_auth_source_new: New authentication mode
461 label_auth_source_new: New authentication mode
461 label_auth_source_plural: Authentication modes
462 label_auth_source_plural: Authentication modes
462 label_subproject_plural: Subprojects
463 label_subproject_plural: Subprojects
463 label_and_its_subprojects: "{{value}} and its subprojects"
464 label_and_its_subprojects: "{{value}} and its subprojects"
464 label_min_max_length: Min - Max length
465 label_min_max_length: Min - Max length
465 label_list: List
466 label_list: List
466 label_date: Date
467 label_date: Date
467 label_integer: Integer
468 label_integer: Integer
468 label_float: Float
469 label_float: Float
469 label_boolean: Boolean
470 label_boolean: Boolean
470 label_string: Text
471 label_string: Text
471 label_text: Long text
472 label_text: Long text
472 label_attribute: Attribute
473 label_attribute: Attribute
473 label_attribute_plural: Attributes
474 label_attribute_plural: Attributes
474 label_download: "{{count}} Download"
475 label_download: "{{count}} Download"
475 label_download_plural: "{{count}} Downloads"
476 label_download_plural: "{{count}} Downloads"
476 label_no_data: No data to display
477 label_no_data: No data to display
477 label_change_status: Change status
478 label_change_status: Change status
478 label_history: History
479 label_history: History
479 label_attachment: File
480 label_attachment: File
480 label_attachment_new: New file
481 label_attachment_new: New file
481 label_attachment_delete: Delete file
482 label_attachment_delete: Delete file
482 label_attachment_plural: Files
483 label_attachment_plural: Files
483 label_file_added: File added
484 label_file_added: File added
484 label_report: Report
485 label_report: Report
485 label_report_plural: Reports
486 label_report_plural: Reports
486 label_news: News
487 label_news: News
487 label_news_new: Add news
488 label_news_new: Add news
488 label_news_plural: News
489 label_news_plural: News
489 label_news_latest: Latest news
490 label_news_latest: Latest news
490 label_news_view_all: View all news
491 label_news_view_all: View all news
491 label_news_added: News added
492 label_news_added: News added
492 label_settings: Settings
493 label_settings: Settings
493 label_overview: Overview
494 label_overview: Overview
494 label_version: Version
495 label_version: Version
495 label_version_new: New version
496 label_version_new: New version
496 label_version_plural: Versions
497 label_version_plural: Versions
497 label_confirmation: Confirmation
498 label_confirmation: Confirmation
498 label_export_to: 'Also available in:'
499 label_export_to: 'Also available in:'
499 label_read: Read...
500 label_read: Read...
500 label_public_projects: Public projects
501 label_public_projects: Public projects
501 label_open_issues: open
502 label_open_issues: open
502 label_open_issues_plural: open
503 label_open_issues_plural: open
503 label_closed_issues: closed
504 label_closed_issues: closed
504 label_closed_issues_plural: closed
505 label_closed_issues_plural: closed
505 label_x_open_issues_abbr_on_total:
506 label_x_open_issues_abbr_on_total:
506 zero: 0 open / {{total}}
507 zero: 0 open / {{total}}
507 one: 1 open / {{total}}
508 one: 1 open / {{total}}
508 other: "{{count}} open / {{total}}"
509 other: "{{count}} open / {{total}}"
509 label_x_open_issues_abbr:
510 label_x_open_issues_abbr:
510 zero: 0 open
511 zero: 0 open
511 one: 1 open
512 one: 1 open
512 other: "{{count}} open"
513 other: "{{count}} open"
513 label_x_closed_issues_abbr:
514 label_x_closed_issues_abbr:
514 zero: 0 closed
515 zero: 0 closed
515 one: 1 closed
516 one: 1 closed
516 other: "{{count}} closed"
517 other: "{{count}} closed"
517 label_total: Total
518 label_total: Total
518 label_permissions: Permissions
519 label_permissions: Permissions
519 label_current_status: Current status
520 label_current_status: Current status
520 label_new_statuses_allowed: New statuses allowed
521 label_new_statuses_allowed: New statuses allowed
521 label_all: all
522 label_all: all
522 label_none: none
523 label_none: none
523 label_nobody: nobody
524 label_nobody: nobody
524 label_next: Next
525 label_next: Next
525 label_previous: Previous
526 label_previous: Previous
526 label_used_by: Used by
527 label_used_by: Used by
527 label_details: Details
528 label_details: Details
528 label_add_note: Add a note
529 label_add_note: Add a note
529 label_per_page: Per page
530 label_per_page: Per page
530 label_calendar: Calendar
531 label_calendar: Calendar
531 label_months_from: months from
532 label_months_from: months from
532 label_gantt: Gantt
533 label_gantt: Gantt
533 label_internal: Internal
534 label_internal: Internal
534 label_last_changes: "last {{count}} changes"
535 label_last_changes: "last {{count}} changes"
535 label_change_view_all: View all changes
536 label_change_view_all: View all changes
536 label_personalize_page: Personalize this page
537 label_personalize_page: Personalize this page
537 label_comment: Comment
538 label_comment: Comment
538 label_comment_plural: Comments
539 label_comment_plural: Comments
539 label_x_comments:
540 label_x_comments:
540 zero: no comments
541 zero: no comments
541 one: 1 comment
542 one: 1 comment
542 other: "{{count}} comments"
543 other: "{{count}} comments"
543 label_comment_add: Add a comment
544 label_comment_add: Add a comment
544 label_comment_added: Comment added
545 label_comment_added: Comment added
545 label_comment_delete: Delete comments
546 label_comment_delete: Delete comments
546 label_query: Custom query
547 label_query: Custom query
547 label_query_plural: Custom queries
548 label_query_plural: Custom queries
548 label_query_new: New query
549 label_query_new: New query
549 label_filter_add: Add filter
550 label_filter_add: Add filter
550 label_filter_plural: Filters
551 label_filter_plural: Filters
551 label_equals: is
552 label_equals: is
552 label_not_equals: is not
553 label_not_equals: is not
553 label_in_less_than: in less than
554 label_in_less_than: in less than
554 label_in_more_than: in more than
555 label_in_more_than: in more than
555 label_greater_or_equal: '>='
556 label_greater_or_equal: '>='
556 label_less_or_equal: '<='
557 label_less_or_equal: '<='
557 label_in: in
558 label_in: in
558 label_today: today
559 label_today: today
559 label_all_time: all time
560 label_all_time: all time
560 label_yesterday: yesterday
561 label_yesterday: yesterday
561 label_this_week: this week
562 label_this_week: this week
562 label_last_week: last week
563 label_last_week: last week
563 label_last_n_days: "last {{count}} days"
564 label_last_n_days: "last {{count}} days"
564 label_this_month: this month
565 label_this_month: this month
565 label_last_month: last month
566 label_last_month: last month
566 label_this_year: this year
567 label_this_year: this year
567 label_date_range: Date range
568 label_date_range: Date range
568 label_less_than_ago: less than days ago
569 label_less_than_ago: less than days ago
569 label_more_than_ago: more than days ago
570 label_more_than_ago: more than days ago
570 label_ago: days ago
571 label_ago: days ago
571 label_contains: contains
572 label_contains: contains
572 label_not_contains: doesn't contain
573 label_not_contains: doesn't contain
573 label_day_plural: days
574 label_day_plural: days
574 label_repository: Repository
575 label_repository: Repository
575 label_repository_plural: Repositories
576 label_repository_plural: Repositories
576 label_browse: Browse
577 label_browse: Browse
577 label_modification: "{{count}} change"
578 label_modification: "{{count}} change"
578 label_modification_plural: "{{count}} changes"
579 label_modification_plural: "{{count}} changes"
579 label_branch: Branch
580 label_branch: Branch
580 label_tag: Tag
581 label_tag: Tag
581 label_revision: Revision
582 label_revision: Revision
582 label_revision_plural: Revisions
583 label_revision_plural: Revisions
583 label_revision_id: "Revision {{value}}"
584 label_revision_id: "Revision {{value}}"
584 label_associated_revisions: Associated revisions
585 label_associated_revisions: Associated revisions
585 label_added: added
586 label_added: added
586 label_modified: modified
587 label_modified: modified
587 label_copied: copied
588 label_copied: copied
588 label_renamed: renamed
589 label_renamed: renamed
589 label_deleted: deleted
590 label_deleted: deleted
590 label_latest_revision: Latest revision
591 label_latest_revision: Latest revision
591 label_latest_revision_plural: Latest revisions
592 label_latest_revision_plural: Latest revisions
592 label_view_revisions: View revisions
593 label_view_revisions: View revisions
593 label_view_all_revisions: View all revisions
594 label_view_all_revisions: View all revisions
594 label_max_size: Maximum size
595 label_max_size: Maximum size
595 label_sort_highest: Move to top
596 label_sort_highest: Move to top
596 label_sort_higher: Move up
597 label_sort_higher: Move up
597 label_sort_lower: Move down
598 label_sort_lower: Move down
598 label_sort_lowest: Move to bottom
599 label_sort_lowest: Move to bottom
599 label_roadmap: Roadmap
600 label_roadmap: Roadmap
600 label_roadmap_due_in: "Due in {{value}}"
601 label_roadmap_due_in: "Due in {{value}}"
601 label_roadmap_overdue: "{{value}} late"
602 label_roadmap_overdue: "{{value}} late"
602 label_roadmap_no_issues: No issues for this version
603 label_roadmap_no_issues: No issues for this version
603 label_search: Search
604 label_search: Search
604 label_result_plural: Results
605 label_result_plural: Results
605 label_all_words: All words
606 label_all_words: All words
606 label_wiki: Wiki
607 label_wiki: Wiki
607 label_wiki_edit: Wiki edit
608 label_wiki_edit: Wiki edit
608 label_wiki_edit_plural: Wiki edits
609 label_wiki_edit_plural: Wiki edits
609 label_wiki_page: Wiki page
610 label_wiki_page: Wiki page
610 label_wiki_page_plural: Wiki pages
611 label_wiki_page_plural: Wiki pages
611 label_index_by_title: Index by title
612 label_index_by_title: Index by title
612 label_index_by_date: Index by date
613 label_index_by_date: Index by date
613 label_current_version: Current version
614 label_current_version: Current version
614 label_preview: Preview
615 label_preview: Preview
615 label_feed_plural: Feeds
616 label_feed_plural: Feeds
616 label_changes_details: Details of all changes
617 label_changes_details: Details of all changes
617 label_issue_tracking: Issue tracking
618 label_issue_tracking: Issue tracking
618 label_spent_time: Spent time
619 label_spent_time: Spent time
619 label_f_hour: "{{value}} hour"
620 label_f_hour: "{{value}} hour"
620 label_f_hour_plural: "{{value}} hours"
621 label_f_hour_plural: "{{value}} hours"
621 label_time_tracking: Time tracking
622 label_time_tracking: Time tracking
622 label_change_plural: Changes
623 label_change_plural: Changes
623 label_statistics: Statistics
624 label_statistics: Statistics
624 label_commits_per_month: Commits per month
625 label_commits_per_month: Commits per month
625 label_commits_per_author: Commits per author
626 label_commits_per_author: Commits per author
626 label_view_diff: View differences
627 label_view_diff: View differences
627 label_diff_inline: inline
628 label_diff_inline: inline
628 label_diff_side_by_side: side by side
629 label_diff_side_by_side: side by side
629 label_options: Options
630 label_options: Options
630 label_copy_workflow_from: Copy workflow from
631 label_copy_workflow_from: Copy workflow from
631 label_permissions_report: Permissions report
632 label_permissions_report: Permissions report
632 label_watched_issues: Watched issues
633 label_watched_issues: Watched issues
633 label_related_issues: Related issues
634 label_related_issues: Related issues
634 label_applied_status: Applied status
635 label_applied_status: Applied status
635 label_loading: Loading...
636 label_loading: Loading...
636 label_relation_new: New relation
637 label_relation_new: New relation
637 label_relation_delete: Delete relation
638 label_relation_delete: Delete relation
638 label_relates_to: related to
639 label_relates_to: related to
639 label_duplicates: duplicates
640 label_duplicates: duplicates
640 label_duplicated_by: duplicated by
641 label_duplicated_by: duplicated by
641 label_blocks: blocks
642 label_blocks: blocks
642 label_blocked_by: blocked by
643 label_blocked_by: blocked by
643 label_precedes: precedes
644 label_precedes: precedes
644 label_follows: follows
645 label_follows: follows
645 label_end_to_start: end to start
646 label_end_to_start: end to start
646 label_end_to_end: end to end
647 label_end_to_end: end to end
647 label_start_to_start: start to start
648 label_start_to_start: start to start
648 label_start_to_end: start to end
649 label_start_to_end: start to end
649 label_stay_logged_in: Stay logged in
650 label_stay_logged_in: Stay logged in
650 label_disabled: disabled
651 label_disabled: disabled
651 label_show_completed_versions: Show completed versions
652 label_show_completed_versions: Show completed versions
652 label_me: me
653 label_me: me
653 label_board: Forum
654 label_board: Forum
654 label_board_new: New forum
655 label_board_new: New forum
655 label_board_plural: Forums
656 label_board_plural: Forums
656 label_topic_plural: Topics
657 label_topic_plural: Topics
657 label_message_plural: Messages
658 label_message_plural: Messages
658 label_message_last: Last message
659 label_message_last: Last message
659 label_message_new: New message
660 label_message_new: New message
660 label_message_posted: Message added
661 label_message_posted: Message added
661 label_reply_plural: Replies
662 label_reply_plural: Replies
662 label_send_information: Send account information to the user
663 label_send_information: Send account information to the user
663 label_year: Year
664 label_year: Year
664 label_month: Month
665 label_month: Month
665 label_week: Week
666 label_week: Week
666 label_date_from: From
667 label_date_from: From
667 label_date_to: To
668 label_date_to: To
668 label_language_based: Based on user's language
669 label_language_based: Based on user's language
669 label_sort_by: "Sort by {{value}}"
670 label_sort_by: "Sort by {{value}}"
670 label_send_test_email: Send a test email
671 label_send_test_email: Send a test email
672 label_feeds_access_key: RSS access key
673 label_missing_feeds_access_key: Missing a RSS access key
671 label_feeds_access_key_created_on: "RSS access key created {{value}} ago"
674 label_feeds_access_key_created_on: "RSS access key created {{value}} ago"
672 label_module_plural: Modules
675 label_module_plural: Modules
673 label_added_time_by: "Added by {{author}} {{age}} ago"
676 label_added_time_by: "Added by {{author}} {{age}} ago"
674 label_updated_time_by: "Updated by {{author}} {{age}} ago"
677 label_updated_time_by: "Updated by {{author}} {{age}} ago"
675 label_updated_time: "Updated {{value}} ago"
678 label_updated_time: "Updated {{value}} ago"
676 label_jump_to_a_project: Jump to a project...
679 label_jump_to_a_project: Jump to a project...
677 label_file_plural: Files
680 label_file_plural: Files
678 label_changeset_plural: Changesets
681 label_changeset_plural: Changesets
679 label_default_columns: Default columns
682 label_default_columns: Default columns
680 label_no_change_option: (No change)
683 label_no_change_option: (No change)
681 label_bulk_edit_selected_issues: Bulk edit selected issues
684 label_bulk_edit_selected_issues: Bulk edit selected issues
682 label_theme: Theme
685 label_theme: Theme
683 label_default: Default
686 label_default: Default
684 label_search_titles_only: Search titles only
687 label_search_titles_only: Search titles only
685 label_user_mail_option_all: "For any event on all my projects"
688 label_user_mail_option_all: "For any event on all my projects"
686 label_user_mail_option_selected: "For any event on the selected projects only..."
689 label_user_mail_option_selected: "For any event on the selected projects only..."
687 label_user_mail_option_none: "Only for things I watch or I'm involved in"
690 label_user_mail_option_none: "Only for things I watch or I'm involved in"
688 label_user_mail_no_self_notified: "I don't want to be notified of changes that I make myself"
691 label_user_mail_no_self_notified: "I don't want to be notified of changes that I make myself"
689 label_registration_activation_by_email: account activation by email
692 label_registration_activation_by_email: account activation by email
690 label_registration_manual_activation: manual account activation
693 label_registration_manual_activation: manual account activation
691 label_registration_automatic_activation: automatic account activation
694 label_registration_automatic_activation: automatic account activation
692 label_display_per_page: "Per page: {{value}}"
695 label_display_per_page: "Per page: {{value}}"
693 label_age: Age
696 label_age: Age
694 label_change_properties: Change properties
697 label_change_properties: Change properties
695 label_general: General
698 label_general: General
696 label_more: More
699 label_more: More
697 label_scm: SCM
700 label_scm: SCM
698 label_plugins: Plugins
701 label_plugins: Plugins
699 label_ldap_authentication: LDAP authentication
702 label_ldap_authentication: LDAP authentication
700 label_downloads_abbr: D/L
703 label_downloads_abbr: D/L
701 label_optional_description: Optional description
704 label_optional_description: Optional description
702 label_add_another_file: Add another file
705 label_add_another_file: Add another file
703 label_preferences: Preferences
706 label_preferences: Preferences
704 label_chronological_order: In chronological order
707 label_chronological_order: In chronological order
705 label_reverse_chronological_order: In reverse chronological order
708 label_reverse_chronological_order: In reverse chronological order
706 label_planning: Planning
709 label_planning: Planning
707 label_incoming_emails: Incoming emails
710 label_incoming_emails: Incoming emails
708 label_generate_key: Generate a key
711 label_generate_key: Generate a key
709 label_issue_watchers: Watchers
712 label_issue_watchers: Watchers
710 label_example: Example
713 label_example: Example
711 label_display: Display
714 label_display: Display
712 label_sort: Sort
715 label_sort: Sort
713 label_ascending: Ascending
716 label_ascending: Ascending
714 label_descending: Descending
717 label_descending: Descending
715 label_date_from_to: From {{start}} to {{end}}
718 label_date_from_to: From {{start}} to {{end}}
716 label_wiki_content_added: Wiki page added
719 label_wiki_content_added: Wiki page added
717 label_wiki_content_updated: Wiki page updated
720 label_wiki_content_updated: Wiki page updated
718 label_group: Group
721 label_group: Group
719 label_group_plural: Groups
722 label_group_plural: Groups
720 label_group_new: New group
723 label_group_new: New group
721 label_time_entry_plural: Spent time
724 label_time_entry_plural: Spent time
722 label_version_sharing_none: Not shared
725 label_version_sharing_none: Not shared
723 label_version_sharing_descendants: With subprojects
726 label_version_sharing_descendants: With subprojects
724 label_version_sharing_hierarchy: With project hierarchy
727 label_version_sharing_hierarchy: With project hierarchy
725 label_version_sharing_tree: With project tree
728 label_version_sharing_tree: With project tree
726 label_version_sharing_system: With all projects
729 label_version_sharing_system: With all projects
727 label_update_issue_done_ratios: Update issue done ratios
730 label_update_issue_done_ratios: Update issue done ratios
728 label_copy_source: Source
731 label_copy_source: Source
729 label_copy_target: Target
732 label_copy_target: Target
730 label_copy_same_as_target: Same as target
733 label_copy_same_as_target: Same as target
731 label_display_used_statuses_only: Only display statuses that are used by this tracker
734 label_display_used_statuses_only: Only display statuses that are used by this tracker
735 label_api_access_key: API access key
736 label_missing_api_access_key: Missing an API access key
737 label_api_access_key_created_on: "API access key created {{value}} ago"
732
738
733 button_login: Login
739 button_login: Login
734 button_submit: Submit
740 button_submit: Submit
735 button_save: Save
741 button_save: Save
736 button_check_all: Check all
742 button_check_all: Check all
737 button_uncheck_all: Uncheck all
743 button_uncheck_all: Uncheck all
738 button_delete: Delete
744 button_delete: Delete
739 button_create: Create
745 button_create: Create
740 button_create_and_continue: Create and continue
746 button_create_and_continue: Create and continue
741 button_test: Test
747 button_test: Test
742 button_edit: Edit
748 button_edit: Edit
743 button_add: Add
749 button_add: Add
744 button_change: Change
750 button_change: Change
745 button_apply: Apply
751 button_apply: Apply
746 button_clear: Clear
752 button_clear: Clear
747 button_lock: Lock
753 button_lock: Lock
748 button_unlock: Unlock
754 button_unlock: Unlock
749 button_download: Download
755 button_download: Download
750 button_list: List
756 button_list: List
751 button_view: View
757 button_view: View
752 button_move: Move
758 button_move: Move
753 button_move_and_follow: Move and follow
759 button_move_and_follow: Move and follow
754 button_back: Back
760 button_back: Back
755 button_cancel: Cancel
761 button_cancel: Cancel
756 button_activate: Activate
762 button_activate: Activate
757 button_sort: Sort
763 button_sort: Sort
758 button_log_time: Log time
764 button_log_time: Log time
759 button_rollback: Rollback to this version
765 button_rollback: Rollback to this version
760 button_watch: Watch
766 button_watch: Watch
761 button_unwatch: Unwatch
767 button_unwatch: Unwatch
762 button_reply: Reply
768 button_reply: Reply
763 button_archive: Archive
769 button_archive: Archive
764 button_unarchive: Unarchive
770 button_unarchive: Unarchive
765 button_reset: Reset
771 button_reset: Reset
766 button_rename: Rename
772 button_rename: Rename
767 button_change_password: Change password
773 button_change_password: Change password
768 button_copy: Copy
774 button_copy: Copy
769 button_copy_and_follow: Copy and follow
775 button_copy_and_follow: Copy and follow
770 button_annotate: Annotate
776 button_annotate: Annotate
771 button_update: Update
777 button_update: Update
772 button_configure: Configure
778 button_configure: Configure
773 button_quote: Quote
779 button_quote: Quote
774 button_duplicate: Duplicate
780 button_duplicate: Duplicate
775
781
776 status_active: active
782 status_active: active
777 status_registered: registered
783 status_registered: registered
778 status_locked: locked
784 status_locked: locked
779
785
780 version_status_open: open
786 version_status_open: open
781 version_status_locked: locked
787 version_status_locked: locked
782 version_status_closed: closed
788 version_status_closed: closed
783
789
784 field_active: Active
790 field_active: Active
785
791
786 text_select_mail_notifications: Select actions for which email notifications should be sent.
792 text_select_mail_notifications: Select actions for which email notifications should be sent.
787 text_regexp_info: eg. ^[A-Z0-9]+$
793 text_regexp_info: eg. ^[A-Z0-9]+$
788 text_min_max_length_info: 0 means no restriction
794 text_min_max_length_info: 0 means no restriction
789 text_project_destroy_confirmation: Are you sure you want to delete this project and related data ?
795 text_project_destroy_confirmation: Are you sure you want to delete this project and related data ?
790 text_subprojects_destroy_warning: "Its subproject(s): {{value}} will be also deleted."
796 text_subprojects_destroy_warning: "Its subproject(s): {{value}} will be also deleted."
791 text_workflow_edit: Select a role and a tracker to edit the workflow
797 text_workflow_edit: Select a role and a tracker to edit the workflow
792 text_are_you_sure: Are you sure ?
798 text_are_you_sure: Are you sure ?
793 text_journal_changed: "{{label}} changed from {{old}} to {{new}}"
799 text_journal_changed: "{{label}} changed from {{old}} to {{new}}"
794 text_journal_set_to: "{{label}} set to {{value}}"
800 text_journal_set_to: "{{label}} set to {{value}}"
795 text_journal_deleted: "{{label}} deleted ({{old}})"
801 text_journal_deleted: "{{label}} deleted ({{old}})"
796 text_journal_added: "{{label}} {{value}} added"
802 text_journal_added: "{{label}} {{value}} added"
797 text_tip_task_begin_day: task beginning this day
803 text_tip_task_begin_day: task beginning this day
798 text_tip_task_end_day: task ending this day
804 text_tip_task_end_day: task ending this day
799 text_tip_task_begin_end_day: task beginning and ending this day
805 text_tip_task_begin_end_day: task beginning and ending this day
800 text_project_identifier_info: 'Only lower case letters (a-z), numbers and dashes are allowed.<br />Once saved, the identifier can not be changed.'
806 text_project_identifier_info: 'Only lower case letters (a-z), numbers and dashes are allowed.<br />Once saved, the identifier can not be changed.'
801 text_caracters_maximum: "{{count}} characters maximum."
807 text_caracters_maximum: "{{count}} characters maximum."
802 text_caracters_minimum: "Must be at least {{count}} characters long."
808 text_caracters_minimum: "Must be at least {{count}} characters long."
803 text_length_between: "Length between {{min}} and {{max}} characters."
809 text_length_between: "Length between {{min}} and {{max}} characters."
804 text_tracker_no_workflow: No workflow defined for this tracker
810 text_tracker_no_workflow: No workflow defined for this tracker
805 text_unallowed_characters: Unallowed characters
811 text_unallowed_characters: Unallowed characters
806 text_comma_separated: Multiple values allowed (comma separated).
812 text_comma_separated: Multiple values allowed (comma separated).
807 text_issues_ref_in_commit_messages: Referencing and fixing issues in commit messages
813 text_issues_ref_in_commit_messages: Referencing and fixing issues in commit messages
808 text_issue_added: "Issue {{id}} has been reported by {{author}}."
814 text_issue_added: "Issue {{id}} has been reported by {{author}}."
809 text_issue_updated: "Issue {{id}} has been updated by {{author}}."
815 text_issue_updated: "Issue {{id}} has been updated by {{author}}."
810 text_wiki_destroy_confirmation: Are you sure you want to delete this wiki and all its content ?
816 text_wiki_destroy_confirmation: Are you sure you want to delete this wiki and all its content ?
811 text_issue_category_destroy_question: "Some issues ({{count}}) are assigned to this category. What do you want to do ?"
817 text_issue_category_destroy_question: "Some issues ({{count}}) are assigned to this category. What do you want to do ?"
812 text_issue_category_destroy_assignments: Remove category assignments
818 text_issue_category_destroy_assignments: Remove category assignments
813 text_issue_category_reassign_to: Reassign issues to this category
819 text_issue_category_reassign_to: Reassign issues to this category
814 text_user_mail_option: "For unselected projects, you will only receive notifications about things you watch or you're involved in (eg. issues you're the author or assignee)."
820 text_user_mail_option: "For unselected projects, you will only receive notifications about things you watch or you're involved in (eg. issues you're the author or assignee)."
815 text_no_configuration_data: "Roles, trackers, issue statuses and workflow have not been configured yet.\nIt is highly recommended to load the default configuration. You will be able to modify it once loaded."
821 text_no_configuration_data: "Roles, trackers, issue statuses and workflow have not been configured yet.\nIt is highly recommended to load the default configuration. You will be able to modify it once loaded."
816 text_load_default_configuration: Load the default configuration
822 text_load_default_configuration: Load the default configuration
817 text_status_changed_by_changeset: "Applied in changeset {{value}}."
823 text_status_changed_by_changeset: "Applied in changeset {{value}}."
818 text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s) ?'
824 text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s) ?'
819 text_select_project_modules: 'Select modules to enable for this project:'
825 text_select_project_modules: 'Select modules to enable for this project:'
820 text_default_administrator_account_changed: Default administrator account changed
826 text_default_administrator_account_changed: Default administrator account changed
821 text_file_repository_writable: Attachments directory writable
827 text_file_repository_writable: Attachments directory writable
822 text_plugin_assets_writable: Plugin assets directory writable
828 text_plugin_assets_writable: Plugin assets directory writable
823 text_rmagick_available: RMagick available (optional)
829 text_rmagick_available: RMagick available (optional)
824 text_destroy_time_entries_question: "{{hours}} hours were reported on the issues you are about to delete. What do you want to do ?"
830 text_destroy_time_entries_question: "{{hours}} hours were reported on the issues you are about to delete. What do you want to do ?"
825 text_destroy_time_entries: Delete reported hours
831 text_destroy_time_entries: Delete reported hours
826 text_assign_time_entries_to_project: Assign reported hours to the project
832 text_assign_time_entries_to_project: Assign reported hours to the project
827 text_reassign_time_entries: 'Reassign reported hours to this issue:'
833 text_reassign_time_entries: 'Reassign reported hours to this issue:'
828 text_user_wrote: "{{value}} wrote:"
834 text_user_wrote: "{{value}} wrote:"
829 text_enumeration_destroy_question: "{{count}} objects are assigned to this value."
835 text_enumeration_destroy_question: "{{count}} objects are assigned to this value."
830 text_enumeration_category_reassign_to: 'Reassign them to this value:'
836 text_enumeration_category_reassign_to: 'Reassign them to this value:'
831 text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them."
837 text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them."
832 text_repository_usernames_mapping: "Select or update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
838 text_repository_usernames_mapping: "Select or update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
833 text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.'
839 text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.'
834 text_custom_field_possible_values_info: 'One line for each value'
840 text_custom_field_possible_values_info: 'One line for each value'
835 text_wiki_page_destroy_question: "This page has {{descendants}} child page(s) and descendant(s). What do you want to do?"
841 text_wiki_page_destroy_question: "This page has {{descendants}} child page(s) and descendant(s). What do you want to do?"
836 text_wiki_page_nullify_children: "Keep child pages as root pages"
842 text_wiki_page_nullify_children: "Keep child pages as root pages"
837 text_wiki_page_destroy_children: "Delete child pages and all their descendants"
843 text_wiki_page_destroy_children: "Delete child pages and all their descendants"
838 text_wiki_page_reassign_children: "Reassign child pages to this parent page"
844 text_wiki_page_reassign_children: "Reassign child pages to this parent page"
845 text_show: Show
839
846
840 default_role_manager: Manager
847 default_role_manager: Manager
841 default_role_developper: Developer
848 default_role_developper: Developer
842 default_role_reporter: Reporter
849 default_role_reporter: Reporter
843 default_tracker_bug: Bug
850 default_tracker_bug: Bug
844 default_tracker_feature: Feature
851 default_tracker_feature: Feature
845 default_tracker_support: Support
852 default_tracker_support: Support
846 default_issue_status_new: New
853 default_issue_status_new: New
847 default_issue_status_in_progress: In Progress
854 default_issue_status_in_progress: In Progress
848 default_issue_status_resolved: Resolved
855 default_issue_status_resolved: Resolved
849 default_issue_status_feedback: Feedback
856 default_issue_status_feedback: Feedback
850 default_issue_status_closed: Closed
857 default_issue_status_closed: Closed
851 default_issue_status_rejected: Rejected
858 default_issue_status_rejected: Rejected
852 default_doc_category_user: User documentation
859 default_doc_category_user: User documentation
853 default_doc_category_tech: Technical documentation
860 default_doc_category_tech: Technical documentation
854 default_priority_low: Low
861 default_priority_low: Low
855 default_priority_normal: Normal
862 default_priority_normal: Normal
856 default_priority_high: High
863 default_priority_high: High
857 default_priority_urgent: Urgent
864 default_priority_urgent: Urgent
858 default_priority_immediate: Immediate
865 default_priority_immediate: Immediate
859 default_activity_design: Design
866 default_activity_design: Design
860 default_activity_development: Development
867 default_activity_development: Development
861
868
862 enumeration_issue_priorities: Issue priorities
869 enumeration_issue_priorities: Issue priorities
863 enumeration_doc_categories: Document categories
870 enumeration_doc_categories: Document categories
864 enumeration_activities: Activities (time tracking)
871 enumeration_activities: Activities (time tracking)
865 enumeration_system_activity: System Activity
872 enumeration_system_activity: System Activity
@@ -1,166 +1,200
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
19 require 'my_controller'
19 require 'my_controller'
20
20
21 # Re-raise errors caught by the controller.
21 # Re-raise errors caught by the controller.
22 class MyController; def rescue_action(e) raise e end; end
22 class MyController; def rescue_action(e) raise e end; end
23
23
24 class MyControllerTest < ActionController::TestCase
24 class MyControllerTest < ActionController::TestCase
25 fixtures :users, :user_preferences, :roles, :projects, :issues, :issue_statuses, :trackers, :enumerations, :custom_fields
25 fixtures :users, :user_preferences, :roles, :projects, :issues, :issue_statuses, :trackers, :enumerations, :custom_fields
26
26
27 def setup
27 def setup
28 @controller = MyController.new
28 @controller = MyController.new
29 @request = ActionController::TestRequest.new
29 @request = ActionController::TestRequest.new
30 @request.session[:user_id] = 2
30 @request.session[:user_id] = 2
31 @response = ActionController::TestResponse.new
31 @response = ActionController::TestResponse.new
32 end
32 end
33
33
34 def test_index
34 def test_index
35 get :index
35 get :index
36 assert_response :success
36 assert_response :success
37 assert_template 'page'
37 assert_template 'page'
38 end
38 end
39
39
40 def test_page
40 def test_page
41 get :page
41 get :page
42 assert_response :success
42 assert_response :success
43 assert_template 'page'
43 assert_template 'page'
44 end
44 end
45
45
46 def test_my_account_should_show_editable_custom_fields
46 def test_my_account_should_show_editable_custom_fields
47 get :account
47 get :account
48 assert_response :success
48 assert_response :success
49 assert_template 'account'
49 assert_template 'account'
50 assert_equal User.find(2), assigns(:user)
50 assert_equal User.find(2), assigns(:user)
51
51
52 assert_tag :input, :attributes => { :name => 'user[custom_field_values][4]'}
52 assert_tag :input, :attributes => { :name => 'user[custom_field_values][4]'}
53 end
53 end
54
54
55 def test_my_account_should_not_show_non_editable_custom_fields
55 def test_my_account_should_not_show_non_editable_custom_fields
56 UserCustomField.find(4).update_attribute :editable, false
56 UserCustomField.find(4).update_attribute :editable, false
57
57
58 get :account
58 get :account
59 assert_response :success
59 assert_response :success
60 assert_template 'account'
60 assert_template 'account'
61 assert_equal User.find(2), assigns(:user)
61 assert_equal User.find(2), assigns(:user)
62
62
63 assert_no_tag :input, :attributes => { :name => 'user[custom_field_values][4]'}
63 assert_no_tag :input, :attributes => { :name => 'user[custom_field_values][4]'}
64 end
64 end
65
65
66 def test_update_account
66 def test_update_account
67 post :account, :user => {:firstname => "Joe",
67 post :account, :user => {:firstname => "Joe",
68 :login => "root",
68 :login => "root",
69 :admin => 1,
69 :admin => 1,
70 :custom_field_values => {"4" => "0100562500"}}
70 :custom_field_values => {"4" => "0100562500"}}
71 assert_redirected_to 'my/account'
71 assert_redirected_to 'my/account'
72 user = User.find(2)
72 user = User.find(2)
73 assert_equal user, assigns(:user)
73 assert_equal user, assigns(:user)
74 assert_equal "Joe", user.firstname
74 assert_equal "Joe", user.firstname
75 assert_equal "jsmith", user.login
75 assert_equal "jsmith", user.login
76 assert_equal "0100562500", user.custom_value_for(4).value
76 assert_equal "0100562500", user.custom_value_for(4).value
77 assert !user.admin?
77 assert !user.admin?
78 end
78 end
79
79
80 def test_change_password
80 def test_change_password
81 get :password
81 get :password
82 assert_response :success
82 assert_response :success
83 assert_template 'password'
83 assert_template 'password'
84
84
85 # non matching password confirmation
85 # non matching password confirmation
86 post :password, :password => 'jsmith',
86 post :password, :password => 'jsmith',
87 :new_password => 'hello',
87 :new_password => 'hello',
88 :new_password_confirmation => 'hello2'
88 :new_password_confirmation => 'hello2'
89 assert_response :success
89 assert_response :success
90 assert_template 'password'
90 assert_template 'password'
91 assert_tag :tag => "div", :attributes => { :class => "errorExplanation" }
91 assert_tag :tag => "div", :attributes => { :class => "errorExplanation" }
92
92
93 # wrong password
93 # wrong password
94 post :password, :password => 'wrongpassword',
94 post :password, :password => 'wrongpassword',
95 :new_password => 'hello',
95 :new_password => 'hello',
96 :new_password_confirmation => 'hello'
96 :new_password_confirmation => 'hello'
97 assert_response :success
97 assert_response :success
98 assert_template 'password'
98 assert_template 'password'
99 assert_equal 'Wrong password', flash[:error]
99 assert_equal 'Wrong password', flash[:error]
100
100
101 # good password
101 # good password
102 post :password, :password => 'jsmith',
102 post :password, :password => 'jsmith',
103 :new_password => 'hello',
103 :new_password => 'hello',
104 :new_password_confirmation => 'hello'
104 :new_password_confirmation => 'hello'
105 assert_redirected_to 'my/account'
105 assert_redirected_to 'my/account'
106 assert User.try_to_login('jsmith', 'hello')
106 assert User.try_to_login('jsmith', 'hello')
107 end
107 end
108
108
109 def test_page_layout
109 def test_page_layout
110 get :page_layout
110 get :page_layout
111 assert_response :success
111 assert_response :success
112 assert_template 'page_layout'
112 assert_template 'page_layout'
113 end
113 end
114
114
115 def test_add_block
115 def test_add_block
116 xhr :post, :add_block, :block => 'issuesreportedbyme'
116 xhr :post, :add_block, :block => 'issuesreportedbyme'
117 assert_response :success
117 assert_response :success
118 assert User.find(2).pref[:my_page_layout]['top'].include?('issuesreportedbyme')
118 assert User.find(2).pref[:my_page_layout]['top'].include?('issuesreportedbyme')
119 end
119 end
120
120
121 def test_remove_block
121 def test_remove_block
122 xhr :post, :remove_block, :block => 'issuesassignedtome'
122 xhr :post, :remove_block, :block => 'issuesassignedtome'
123 assert_response :success
123 assert_response :success
124 assert !User.find(2).pref[:my_page_layout].values.flatten.include?('issuesassignedtome')
124 assert !User.find(2).pref[:my_page_layout].values.flatten.include?('issuesassignedtome')
125 end
125 end
126
126
127 def test_order_blocks
127 def test_order_blocks
128 xhr :post, :order_blocks, :group => 'left', 'list-left' => ['documents', 'calendar', 'latestnews']
128 xhr :post, :order_blocks, :group => 'left', 'list-left' => ['documents', 'calendar', 'latestnews']
129 assert_response :success
129 assert_response :success
130 assert_equal ['documents', 'calendar', 'latestnews'], User.find(2).pref[:my_page_layout]['left']
130 assert_equal ['documents', 'calendar', 'latestnews'], User.find(2).pref[:my_page_layout]['left']
131 end
131 end
132
132
133 context "POST to reset_rss_key" do
133 context "POST to reset_rss_key" do
134 context "with an existing rss_token" do
134 context "with an existing rss_token" do
135 setup do
135 setup do
136 @previous_token_value = User.find(2).rss_key # Will generate one if it's missing
136 @previous_token_value = User.find(2).rss_key # Will generate one if it's missing
137 post :reset_rss_key
137 post :reset_rss_key
138 end
138 end
139
139
140 should "destroy the existing token" do
140 should "destroy the existing token" do
141 assert_not_equal @previous_token_value, User.find(2).rss_key
141 assert_not_equal @previous_token_value, User.find(2).rss_key
142 end
142 end
143
143
144 should "create a new token" do
144 should "create a new token" do
145 assert User.find(2).rss_token
145 assert User.find(2).rss_token
146 end
146 end
147
147
148 should_set_the_flash_to /reset/
148 should_set_the_flash_to /reset/
149 should_redirect_to('my account') {'/my/account' }
149 should_redirect_to('my account') {'/my/account' }
150 end
150 end
151
151
152 context "with no rss_token" do
152 context "with no rss_token" do
153 setup do
153 setup do
154 assert_nil User.find(2).rss_token
154 assert_nil User.find(2).rss_token
155 post :reset_rss_key
155 post :reset_rss_key
156 end
156 end
157
157
158 should "create a new token" do
158 should "create a new token" do
159 assert User.find(2).rss_token
159 assert User.find(2).rss_token
160 end
160 end
161
161
162 should_set_the_flash_to /reset/
162 should_set_the_flash_to /reset/
163 should_redirect_to('my account') {'/my/account' }
163 should_redirect_to('my account') {'/my/account' }
164 end
164 end
165 end
165 end
166
167 context "POST to reset_api_key" do
168 context "with an existing api_token" do
169 setup do
170 @previous_token_value = User.find(2).api_key # Will generate one if it's missing
171 post :reset_api_key
172 end
173
174 should "destroy the existing token" do
175 assert_not_equal @previous_token_value, User.find(2).api_key
176 end
177
178 should "create a new token" do
179 assert User.find(2).api_token
180 end
181
182 should_set_the_flash_to /reset/
183 should_redirect_to('my account') {'/my/account' }
184 end
185
186 context "with no api_token" do
187 setup do
188 assert_nil User.find(2).api_token
189 post :reset_api_key
190 end
191
192 should "create a new token" do
193 assert User.find(2).api_token
194 end
195
196 should_set_the_flash_to /reset/
197 should_redirect_to('my account') {'/my/account' }
198 end
199 end
166 end
200 end
@@ -1,229 +1,279
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006 Jean-Philippe Lang
2 # Copyright (C) 2006 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
19
19
20 class UserTest < ActiveSupport::TestCase
20 class UserTest < ActiveSupport::TestCase
21 fixtures :users, :members, :projects, :roles, :member_roles
21 fixtures :users, :members, :projects, :roles, :member_roles
22
22
23 def setup
23 def setup
24 @admin = User.find(1)
24 @admin = User.find(1)
25 @jsmith = User.find(2)
25 @jsmith = User.find(2)
26 @dlopper = User.find(3)
26 @dlopper = User.find(3)
27 end
27 end
28
28
29 test 'object_daddy creation' do
29 test 'object_daddy creation' do
30 User.generate_with_protected!(:firstname => 'Testing connection')
30 User.generate_with_protected!(:firstname => 'Testing connection')
31 User.generate_with_protected!(:firstname => 'Testing connection')
31 User.generate_with_protected!(:firstname => 'Testing connection')
32 assert_equal 2, User.count(:all, :conditions => {:firstname => 'Testing connection'})
32 assert_equal 2, User.count(:all, :conditions => {:firstname => 'Testing connection'})
33 end
33 end
34
34
35 def test_truth
35 def test_truth
36 assert_kind_of User, @jsmith
36 assert_kind_of User, @jsmith
37 end
37 end
38
38
39 def test_create
39 def test_create
40 user = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
40 user = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
41
41
42 user.login = "jsmith"
42 user.login = "jsmith"
43 user.password, user.password_confirmation = "password", "password"
43 user.password, user.password_confirmation = "password", "password"
44 # login uniqueness
44 # login uniqueness
45 assert !user.save
45 assert !user.save
46 assert_equal 1, user.errors.count
46 assert_equal 1, user.errors.count
47
47
48 user.login = "newuser"
48 user.login = "newuser"
49 user.password, user.password_confirmation = "passwd", "password"
49 user.password, user.password_confirmation = "passwd", "password"
50 # password confirmation
50 # password confirmation
51 assert !user.save
51 assert !user.save
52 assert_equal 1, user.errors.count
52 assert_equal 1, user.errors.count
53
53
54 user.password, user.password_confirmation = "password", "password"
54 user.password, user.password_confirmation = "password", "password"
55 assert user.save
55 assert user.save
56 end
56 end
57
57
58 def test_mail_uniqueness_should_not_be_case_sensitive
58 def test_mail_uniqueness_should_not_be_case_sensitive
59 u = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
59 u = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
60 u.login = 'newuser1'
60 u.login = 'newuser1'
61 u.password, u.password_confirmation = "password", "password"
61 u.password, u.password_confirmation = "password", "password"
62 assert u.save
62 assert u.save
63
63
64 u = User.new(:firstname => "new", :lastname => "user", :mail => "newUser@Somenet.foo")
64 u = User.new(:firstname => "new", :lastname => "user", :mail => "newUser@Somenet.foo")
65 u.login = 'newuser2'
65 u.login = 'newuser2'
66 u.password, u.password_confirmation = "password", "password"
66 u.password, u.password_confirmation = "password", "password"
67 assert !u.save
67 assert !u.save
68 assert_equal I18n.translate('activerecord.errors.messages.taken'), u.errors.on(:mail)
68 assert_equal I18n.translate('activerecord.errors.messages.taken'), u.errors.on(:mail)
69 end
69 end
70
70
71 def test_update
71 def test_update
72 assert_equal "admin", @admin.login
72 assert_equal "admin", @admin.login
73 @admin.login = "john"
73 @admin.login = "john"
74 assert @admin.save, @admin.errors.full_messages.join("; ")
74 assert @admin.save, @admin.errors.full_messages.join("; ")
75 @admin.reload
75 @admin.reload
76 assert_equal "john", @admin.login
76 assert_equal "john", @admin.login
77 end
77 end
78
78
79 def test_destroy
79 def test_destroy
80 User.find(2).destroy
80 User.find(2).destroy
81 assert_nil User.find_by_id(2)
81 assert_nil User.find_by_id(2)
82 assert Member.find_all_by_user_id(2).empty?
82 assert Member.find_all_by_user_id(2).empty?
83 end
83 end
84
84
85 def test_validate
85 def test_validate
86 @admin.login = ""
86 @admin.login = ""
87 assert !@admin.save
87 assert !@admin.save
88 assert_equal 1, @admin.errors.count
88 assert_equal 1, @admin.errors.count
89 end
89 end
90
90
91 def test_password
91 def test_password
92 user = User.try_to_login("admin", "admin")
92 user = User.try_to_login("admin", "admin")
93 assert_kind_of User, user
93 assert_kind_of User, user
94 assert_equal "admin", user.login
94 assert_equal "admin", user.login
95 user.password = "hello"
95 user.password = "hello"
96 assert user.save
96 assert user.save
97
97
98 user = User.try_to_login("admin", "hello")
98 user = User.try_to_login("admin", "hello")
99 assert_kind_of User, user
99 assert_kind_of User, user
100 assert_equal "admin", user.login
100 assert_equal "admin", user.login
101 assert_equal User.hash_password("hello"), user.hashed_password
101 assert_equal User.hash_password("hello"), user.hashed_password
102 end
102 end
103
103
104 def test_name_format
104 def test_name_format
105 assert_equal 'Smith, John', @jsmith.name(:lastname_coma_firstname)
105 assert_equal 'Smith, John', @jsmith.name(:lastname_coma_firstname)
106 Setting.user_format = :firstname_lastname
106 Setting.user_format = :firstname_lastname
107 assert_equal 'John Smith', @jsmith.reload.name
107 assert_equal 'John Smith', @jsmith.reload.name
108 Setting.user_format = :username
108 Setting.user_format = :username
109 assert_equal 'jsmith', @jsmith.reload.name
109 assert_equal 'jsmith', @jsmith.reload.name
110 end
110 end
111
111
112 def test_lock
112 def test_lock
113 user = User.try_to_login("jsmith", "jsmith")
113 user = User.try_to_login("jsmith", "jsmith")
114 assert_equal @jsmith, user
114 assert_equal @jsmith, user
115
115
116 @jsmith.status = User::STATUS_LOCKED
116 @jsmith.status = User::STATUS_LOCKED
117 assert @jsmith.save
117 assert @jsmith.save
118
118
119 user = User.try_to_login("jsmith", "jsmith")
119 user = User.try_to_login("jsmith", "jsmith")
120 assert_equal nil, user
120 assert_equal nil, user
121 end
121 end
122
122
123 def test_create_anonymous
123 def test_create_anonymous
124 AnonymousUser.delete_all
124 AnonymousUser.delete_all
125 anon = User.anonymous
125 anon = User.anonymous
126 assert !anon.new_record?
126 assert !anon.new_record?
127 assert_kind_of AnonymousUser, anon
127 assert_kind_of AnonymousUser, anon
128 end
128 end
129
129
130 should_have_one :rss_token
131
130 def test_rss_key
132 def test_rss_key
131 assert_nil @jsmith.rss_token
133 assert_nil @jsmith.rss_token
132 key = @jsmith.rss_key
134 key = @jsmith.rss_key
133 assert_equal 40, key.length
135 assert_equal 40, key.length
134
136
135 @jsmith.reload
137 @jsmith.reload
136 assert_equal key, @jsmith.rss_key
138 assert_equal key, @jsmith.rss_key
137 end
139 end
140
138
141
142 should_have_one :api_token
143
144 context "User#api_key" do
145 should "generate a new one if the user doesn't have one" do
146 user = User.generate_with_protected!(:api_token => nil)
147 assert_nil user.api_token
148
149 key = user.api_key
150 assert_equal 40, key.length
151 user.reload
152 assert_equal key, user.api_key
153 end
154
155 should "return the existing api token value" do
156 user = User.generate_with_protected!
157 token = Token.generate!(:action => 'api')
158 user.api_token = token
159 assert user.save
160
161 assert_equal token.value, user.api_key
162 end
163 end
164
165 context "User#find_by_api_key" do
166 should "return nil if no matching key is found" do
167 assert_nil User.find_by_api_key('zzzzzzzzz')
168 end
169
170 should "return nil if the key is found for an inactive user" do
171 user = User.generate_with_protected!(:status => User::STATUS_LOCKED)
172 token = Token.generate!(:action => 'api')
173 user.api_token = token
174 user.save
175
176 assert_nil User.find_by_api_key(token.value)
177 end
178
179 should "return the user if the key is found for an active user" do
180 user = User.generate_with_protected!(:status => User::STATUS_ACTIVE)
181 token = Token.generate!(:action => 'api')
182 user.api_token = token
183 user.save
184
185 assert_equal user, User.find_by_api_key(token.value)
186 end
187 end
188
139 def test_roles_for_project
189 def test_roles_for_project
140 # user with a role
190 # user with a role
141 roles = @jsmith.roles_for_project(Project.find(1))
191 roles = @jsmith.roles_for_project(Project.find(1))
142 assert_kind_of Role, roles.first
192 assert_kind_of Role, roles.first
143 assert_equal "Manager", roles.first.name
193 assert_equal "Manager", roles.first.name
144
194
145 # user with no role
195 # user with no role
146 assert_nil @dlopper.roles_for_project(Project.find(2)).detect {|role| role.member?}
196 assert_nil @dlopper.roles_for_project(Project.find(2)).detect {|role| role.member?}
147 end
197 end
148
198
149 def test_mail_notification_all
199 def test_mail_notification_all
150 @jsmith.mail_notification = true
200 @jsmith.mail_notification = true
151 @jsmith.notified_project_ids = []
201 @jsmith.notified_project_ids = []
152 @jsmith.save
202 @jsmith.save
153 @jsmith.reload
203 @jsmith.reload
154 assert @jsmith.projects.first.recipients.include?(@jsmith.mail)
204 assert @jsmith.projects.first.recipients.include?(@jsmith.mail)
155 end
205 end
156
206
157 def test_mail_notification_selected
207 def test_mail_notification_selected
158 @jsmith.mail_notification = false
208 @jsmith.mail_notification = false
159 @jsmith.notified_project_ids = [1]
209 @jsmith.notified_project_ids = [1]
160 @jsmith.save
210 @jsmith.save
161 @jsmith.reload
211 @jsmith.reload
162 assert Project.find(1).recipients.include?(@jsmith.mail)
212 assert Project.find(1).recipients.include?(@jsmith.mail)
163 end
213 end
164
214
165 def test_mail_notification_none
215 def test_mail_notification_none
166 @jsmith.mail_notification = false
216 @jsmith.mail_notification = false
167 @jsmith.notified_project_ids = []
217 @jsmith.notified_project_ids = []
168 @jsmith.save
218 @jsmith.save
169 @jsmith.reload
219 @jsmith.reload
170 assert !@jsmith.projects.first.recipients.include?(@jsmith.mail)
220 assert !@jsmith.projects.first.recipients.include?(@jsmith.mail)
171 end
221 end
172
222
173 def test_comments_sorting_preference
223 def test_comments_sorting_preference
174 assert !@jsmith.wants_comments_in_reverse_order?
224 assert !@jsmith.wants_comments_in_reverse_order?
175 @jsmith.pref.comments_sorting = 'asc'
225 @jsmith.pref.comments_sorting = 'asc'
176 assert !@jsmith.wants_comments_in_reverse_order?
226 assert !@jsmith.wants_comments_in_reverse_order?
177 @jsmith.pref.comments_sorting = 'desc'
227 @jsmith.pref.comments_sorting = 'desc'
178 assert @jsmith.wants_comments_in_reverse_order?
228 assert @jsmith.wants_comments_in_reverse_order?
179 end
229 end
180
230
181 def test_find_by_mail_should_be_case_insensitive
231 def test_find_by_mail_should_be_case_insensitive
182 u = User.find_by_mail('JSmith@somenet.foo')
232 u = User.find_by_mail('JSmith@somenet.foo')
183 assert_not_nil u
233 assert_not_nil u
184 assert_equal 'jsmith@somenet.foo', u.mail
234 assert_equal 'jsmith@somenet.foo', u.mail
185 end
235 end
186
236
187 def test_random_password
237 def test_random_password
188 u = User.new
238 u = User.new
189 u.random_password
239 u.random_password
190 assert !u.password.blank?
240 assert !u.password.blank?
191 assert !u.password_confirmation.blank?
241 assert !u.password_confirmation.blank?
192 end
242 end
193
243
194 if Object.const_defined?(:OpenID)
244 if Object.const_defined?(:OpenID)
195
245
196 def test_setting_identity_url
246 def test_setting_identity_url
197 normalized_open_id_url = 'http://example.com/'
247 normalized_open_id_url = 'http://example.com/'
198 u = User.new( :identity_url => 'http://example.com/' )
248 u = User.new( :identity_url => 'http://example.com/' )
199 assert_equal normalized_open_id_url, u.identity_url
249 assert_equal normalized_open_id_url, u.identity_url
200 end
250 end
201
251
202 def test_setting_identity_url_without_trailing_slash
252 def test_setting_identity_url_without_trailing_slash
203 normalized_open_id_url = 'http://example.com/'
253 normalized_open_id_url = 'http://example.com/'
204 u = User.new( :identity_url => 'http://example.com' )
254 u = User.new( :identity_url => 'http://example.com' )
205 assert_equal normalized_open_id_url, u.identity_url
255 assert_equal normalized_open_id_url, u.identity_url
206 end
256 end
207
257
208 def test_setting_identity_url_without_protocol
258 def test_setting_identity_url_without_protocol
209 normalized_open_id_url = 'http://example.com/'
259 normalized_open_id_url = 'http://example.com/'
210 u = User.new( :identity_url => 'example.com' )
260 u = User.new( :identity_url => 'example.com' )
211 assert_equal normalized_open_id_url, u.identity_url
261 assert_equal normalized_open_id_url, u.identity_url
212 end
262 end
213
263
214 def test_setting_blank_identity_url
264 def test_setting_blank_identity_url
215 u = User.new( :identity_url => 'example.com' )
265 u = User.new( :identity_url => 'example.com' )
216 u.identity_url = ''
266 u.identity_url = ''
217 assert u.identity_url.blank?
267 assert u.identity_url.blank?
218 end
268 end
219
269
220 def test_setting_invalid_identity_url
270 def test_setting_invalid_identity_url
221 u = User.new( :identity_url => 'this is not an openid url' )
271 u = User.new( :identity_url => 'this is not an openid url' )
222 assert u.identity_url.blank?
272 assert u.identity_url.blank?
223 end
273 end
224
274
225 else
275 else
226 puts "Skipping openid tests."
276 puts "Skipping openid tests."
227 end
277 end
228
278
229 end
279 end
General Comments 0
You need to be logged in to leave comments. Login now