##// END OF EJS Templates
Adds the ability for users to delete their own account (#10664). Can be disabled in application settings....
Jean-Philippe Lang -
r9283:28f0c4f131b0
parent child
Show More
@@ -0,0 +1,11
1 <h2><%=l(:label_confirmation)%></h2>
2 <div class="warning">
3 <p><%= simple_format l(:text_account_destroy_confirmation)%></p>
4 <p>
5 <% form_tag({}) do %>
6 <label><%= check_box_tag 'confirm', 1 %> <%= l(:general_text_Yes) %></label>
7 <%= submit_tag l(:button_delete_my_account) %> |
8 <%= link_to l(:button_cancel), :action => 'account' %>
9 <% end %>
10 </p>
11 </div>
@@ -1,287 +1,279
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class AccountController < ApplicationController
18 class AccountController < ApplicationController
19 helper :custom_fields
19 helper :custom_fields
20 include CustomFieldsHelper
20 include CustomFieldsHelper
21
21
22 # prevents login action to be filtered by check_if_login_required application scope filter
22 # prevents login action to be filtered by check_if_login_required application scope filter
23 skip_before_filter :check_if_login_required
23 skip_before_filter :check_if_login_required
24
24
25 # Login request and validation
25 # Login request and validation
26 def login
26 def login
27 if request.get?
27 if request.get?
28 logout_user
28 logout_user
29 else
29 else
30 authenticate_user
30 authenticate_user
31 end
31 end
32 rescue AuthSourceException => e
32 rescue AuthSourceException => e
33 logger.error "An error occured when authenticating #{params[:username]}: #{e.message}"
33 logger.error "An error occured when authenticating #{params[:username]}: #{e.message}"
34 render_error :message => e.message
34 render_error :message => e.message
35 end
35 end
36
36
37 # Log out current user and redirect to welcome page
37 # Log out current user and redirect to welcome page
38 def logout
38 def logout
39 logout_user
39 logout_user
40 redirect_to home_url
40 redirect_to home_url
41 end
41 end
42
42
43 # Enable user to choose a new password
43 # Enable user to choose a new password
44 def lost_password
44 def lost_password
45 redirect_to(home_url) && return unless Setting.lost_password?
45 redirect_to(home_url) && return unless Setting.lost_password?
46 if params[:token]
46 if params[:token]
47 @token = Token.find_by_action_and_value("recovery", params[:token])
47 @token = Token.find_by_action_and_value("recovery", params[:token])
48 redirect_to(home_url) && return unless @token and !@token.expired?
48 redirect_to(home_url) && return unless @token and !@token.expired?
49 @user = @token.user
49 @user = @token.user
50 if request.post?
50 if request.post?
51 @user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation]
51 @user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation]
52 if @user.save
52 if @user.save
53 @token.destroy
53 @token.destroy
54 flash[:notice] = l(:notice_account_password_updated)
54 flash[:notice] = l(:notice_account_password_updated)
55 redirect_to :action => 'login'
55 redirect_to :action => 'login'
56 return
56 return
57 end
57 end
58 end
58 end
59 render :template => "account/password_recovery"
59 render :template => "account/password_recovery"
60 return
60 return
61 else
61 else
62 if request.post?
62 if request.post?
63 user = User.find_by_mail(params[:mail])
63 user = User.find_by_mail(params[:mail])
64 # user not found in db
64 # user not found in db
65 (flash.now[:error] = l(:notice_account_unknown_email); return) unless user
65 (flash.now[:error] = l(:notice_account_unknown_email); return) unless user
66 # user uses an external authentification
66 # user uses an external authentification
67 (flash.now[:error] = l(:notice_can_t_change_password); return) if user.auth_source_id
67 (flash.now[:error] = l(:notice_can_t_change_password); return) if user.auth_source_id
68 # create a new token for password recovery
68 # create a new token for password recovery
69 token = Token.new(:user => user, :action => "recovery")
69 token = Token.new(:user => user, :action => "recovery")
70 if token.save
70 if token.save
71 Mailer.deliver_lost_password(token)
71 Mailer.deliver_lost_password(token)
72 flash[:notice] = l(:notice_account_lost_email_sent)
72 flash[:notice] = l(:notice_account_lost_email_sent)
73 redirect_to :action => 'login'
73 redirect_to :action => 'login'
74 return
74 return
75 end
75 end
76 end
76 end
77 end
77 end
78 end
78 end
79
79
80 # User self-registration
80 # User self-registration
81 def register
81 def register
82 redirect_to(home_url) && return unless Setting.self_registration? || session[:auth_source_registration]
82 redirect_to(home_url) && return unless Setting.self_registration? || session[:auth_source_registration]
83 if request.get?
83 if request.get?
84 session[:auth_source_registration] = nil
84 session[:auth_source_registration] = nil
85 @user = User.new(:language => Setting.default_language)
85 @user = User.new(:language => Setting.default_language)
86 else
86 else
87 @user = User.new
87 @user = User.new
88 @user.safe_attributes = params[:user]
88 @user.safe_attributes = params[:user]
89 @user.admin = false
89 @user.admin = false
90 @user.register
90 @user.register
91 if session[:auth_source_registration]
91 if session[:auth_source_registration]
92 @user.activate
92 @user.activate
93 @user.login = session[:auth_source_registration][:login]
93 @user.login = session[:auth_source_registration][:login]
94 @user.auth_source_id = session[:auth_source_registration][:auth_source_id]
94 @user.auth_source_id = session[:auth_source_registration][:auth_source_id]
95 if @user.save
95 if @user.save
96 session[:auth_source_registration] = nil
96 session[:auth_source_registration] = nil
97 self.logged_user = @user
97 self.logged_user = @user
98 flash[:notice] = l(:notice_account_activated)
98 flash[:notice] = l(:notice_account_activated)
99 redirect_to :controller => 'my', :action => 'account'
99 redirect_to :controller => 'my', :action => 'account'
100 end
100 end
101 else
101 else
102 @user.login = params[:user][:login]
102 @user.login = params[:user][:login]
103 @user.password, @user.password_confirmation = params[:user][:password], params[:user][:password_confirmation]
103 @user.password, @user.password_confirmation = params[:user][:password], params[:user][:password_confirmation]
104
104
105 case Setting.self_registration
105 case Setting.self_registration
106 when '1'
106 when '1'
107 register_by_email_activation(@user)
107 register_by_email_activation(@user)
108 when '3'
108 when '3'
109 register_automatically(@user)
109 register_automatically(@user)
110 else
110 else
111 register_manually_by_administrator(@user)
111 register_manually_by_administrator(@user)
112 end
112 end
113 end
113 end
114 end
114 end
115 end
115 end
116
116
117 # Token based account activation
117 # Token based account activation
118 def activate
118 def activate
119 redirect_to(home_url) && return unless Setting.self_registration? && params[:token]
119 redirect_to(home_url) && return unless Setting.self_registration? && params[:token]
120 token = Token.find_by_action_and_value('register', params[:token])
120 token = Token.find_by_action_and_value('register', params[:token])
121 redirect_to(home_url) && return unless token and !token.expired?
121 redirect_to(home_url) && return unless token and !token.expired?
122 user = token.user
122 user = token.user
123 redirect_to(home_url) && return unless user.registered?
123 redirect_to(home_url) && return unless user.registered?
124 user.activate
124 user.activate
125 if user.save
125 if user.save
126 token.destroy
126 token.destroy
127 flash[:notice] = l(:notice_account_activated)
127 flash[:notice] = l(:notice_account_activated)
128 end
128 end
129 redirect_to :action => 'login'
129 redirect_to :action => 'login'
130 end
130 end
131
131
132 private
132 private
133
133
134 def logout_user
135 if User.current.logged?
136 cookies.delete :autologin
137 Token.delete_all(["user_id = ? AND action = ?", User.current.id, 'autologin'])
138 self.logged_user = nil
139 end
140 end
141
142 def authenticate_user
134 def authenticate_user
143 if Setting.openid? && using_open_id?
135 if Setting.openid? && using_open_id?
144 open_id_authenticate(params[:openid_url])
136 open_id_authenticate(params[:openid_url])
145 else
137 else
146 password_authentication
138 password_authentication
147 end
139 end
148 end
140 end
149
141
150 def password_authentication
142 def password_authentication
151 user = User.try_to_login(params[:username], params[:password])
143 user = User.try_to_login(params[:username], params[:password])
152
144
153 if user.nil?
145 if user.nil?
154 invalid_credentials
146 invalid_credentials
155 elsif user.new_record?
147 elsif user.new_record?
156 onthefly_creation_failed(user, {:login => user.login, :auth_source_id => user.auth_source_id })
148 onthefly_creation_failed(user, {:login => user.login, :auth_source_id => user.auth_source_id })
157 else
149 else
158 # Valid user
150 # Valid user
159 successful_authentication(user)
151 successful_authentication(user)
160 end
152 end
161 end
153 end
162
154
163 def open_id_authenticate(openid_url)
155 def open_id_authenticate(openid_url)
164 authenticate_with_open_id(openid_url, :required => [:nickname, :fullname, :email], :return_to => signin_url) do |result, identity_url, registration|
156 authenticate_with_open_id(openid_url, :required => [:nickname, :fullname, :email], :return_to => signin_url) do |result, identity_url, registration|
165 if result.successful?
157 if result.successful?
166 user = User.find_or_initialize_by_identity_url(identity_url)
158 user = User.find_or_initialize_by_identity_url(identity_url)
167 if user.new_record?
159 if user.new_record?
168 # Self-registration off
160 # Self-registration off
169 redirect_to(home_url) && return unless Setting.self_registration?
161 redirect_to(home_url) && return unless Setting.self_registration?
170
162
171 # Create on the fly
163 # Create on the fly
172 user.login = registration['nickname'] unless registration['nickname'].nil?
164 user.login = registration['nickname'] unless registration['nickname'].nil?
173 user.mail = registration['email'] unless registration['email'].nil?
165 user.mail = registration['email'] unless registration['email'].nil?
174 user.firstname, user.lastname = registration['fullname'].split(' ') unless registration['fullname'].nil?
166 user.firstname, user.lastname = registration['fullname'].split(' ') unless registration['fullname'].nil?
175 user.random_password
167 user.random_password
176 user.register
168 user.register
177
169
178 case Setting.self_registration
170 case Setting.self_registration
179 when '1'
171 when '1'
180 register_by_email_activation(user) do
172 register_by_email_activation(user) do
181 onthefly_creation_failed(user)
173 onthefly_creation_failed(user)
182 end
174 end
183 when '3'
175 when '3'
184 register_automatically(user) do
176 register_automatically(user) do
185 onthefly_creation_failed(user)
177 onthefly_creation_failed(user)
186 end
178 end
187 else
179 else
188 register_manually_by_administrator(user) do
180 register_manually_by_administrator(user) do
189 onthefly_creation_failed(user)
181 onthefly_creation_failed(user)
190 end
182 end
191 end
183 end
192 else
184 else
193 # Existing record
185 # Existing record
194 if user.active?
186 if user.active?
195 successful_authentication(user)
187 successful_authentication(user)
196 else
188 else
197 account_pending
189 account_pending
198 end
190 end
199 end
191 end
200 end
192 end
201 end
193 end
202 end
194 end
203
195
204 def successful_authentication(user)
196 def successful_authentication(user)
205 # Valid user
197 # Valid user
206 self.logged_user = user
198 self.logged_user = user
207 # generate a key and set cookie if autologin
199 # generate a key and set cookie if autologin
208 if params[:autologin] && Setting.autologin?
200 if params[:autologin] && Setting.autologin?
209 set_autologin_cookie(user)
201 set_autologin_cookie(user)
210 end
202 end
211 call_hook(:controller_account_success_authentication_after, {:user => user })
203 call_hook(:controller_account_success_authentication_after, {:user => user })
212 redirect_back_or_default :controller => 'my', :action => 'page'
204 redirect_back_or_default :controller => 'my', :action => 'page'
213 end
205 end
214
206
215 def set_autologin_cookie(user)
207 def set_autologin_cookie(user)
216 token = Token.create(:user => user, :action => 'autologin')
208 token = Token.create(:user => user, :action => 'autologin')
217 cookie_name = Redmine::Configuration['autologin_cookie_name'] || 'autologin'
209 cookie_name = Redmine::Configuration['autologin_cookie_name'] || 'autologin'
218 cookie_options = {
210 cookie_options = {
219 :value => token.value,
211 :value => token.value,
220 :expires => 1.year.from_now,
212 :expires => 1.year.from_now,
221 :path => (Redmine::Configuration['autologin_cookie_path'] || '/'),
213 :path => (Redmine::Configuration['autologin_cookie_path'] || '/'),
222 :secure => (Redmine::Configuration['autologin_cookie_secure'] ? true : false),
214 :secure => (Redmine::Configuration['autologin_cookie_secure'] ? true : false),
223 :httponly => true
215 :httponly => true
224 }
216 }
225 cookies[cookie_name] = cookie_options
217 cookies[cookie_name] = cookie_options
226 end
218 end
227
219
228 # Onthefly creation failed, display the registration form to fill/fix attributes
220 # Onthefly creation failed, display the registration form to fill/fix attributes
229 def onthefly_creation_failed(user, auth_source_options = { })
221 def onthefly_creation_failed(user, auth_source_options = { })
230 @user = user
222 @user = user
231 session[:auth_source_registration] = auth_source_options unless auth_source_options.empty?
223 session[:auth_source_registration] = auth_source_options unless auth_source_options.empty?
232 render :action => 'register'
224 render :action => 'register'
233 end
225 end
234
226
235 def invalid_credentials
227 def invalid_credentials
236 logger.warn "Failed login for '#{params[:username]}' from #{request.remote_ip} at #{Time.now.utc}"
228 logger.warn "Failed login for '#{params[:username]}' from #{request.remote_ip} at #{Time.now.utc}"
237 flash.now[:error] = l(:notice_account_invalid_creditentials)
229 flash.now[:error] = l(:notice_account_invalid_creditentials)
238 end
230 end
239
231
240 # Register a user for email activation.
232 # Register a user for email activation.
241 #
233 #
242 # Pass a block for behavior when a user fails to save
234 # Pass a block for behavior when a user fails to save
243 def register_by_email_activation(user, &block)
235 def register_by_email_activation(user, &block)
244 token = Token.new(:user => user, :action => "register")
236 token = Token.new(:user => user, :action => "register")
245 if user.save and token.save
237 if user.save and token.save
246 Mailer.deliver_register(token)
238 Mailer.deliver_register(token)
247 flash[:notice] = l(:notice_account_register_done)
239 flash[:notice] = l(:notice_account_register_done)
248 redirect_to :action => 'login'
240 redirect_to :action => 'login'
249 else
241 else
250 yield if block_given?
242 yield if block_given?
251 end
243 end
252 end
244 end
253
245
254 # Automatically register a user
246 # Automatically register a user
255 #
247 #
256 # Pass a block for behavior when a user fails to save
248 # Pass a block for behavior when a user fails to save
257 def register_automatically(user, &block)
249 def register_automatically(user, &block)
258 # Automatic activation
250 # Automatic activation
259 user.activate
251 user.activate
260 user.last_login_on = Time.now
252 user.last_login_on = Time.now
261 if user.save
253 if user.save
262 self.logged_user = user
254 self.logged_user = user
263 flash[:notice] = l(:notice_account_activated)
255 flash[:notice] = l(:notice_account_activated)
264 redirect_to :controller => 'my', :action => 'account'
256 redirect_to :controller => 'my', :action => 'account'
265 else
257 else
266 yield if block_given?
258 yield if block_given?
267 end
259 end
268 end
260 end
269
261
270 # Manual activation by the administrator
262 # Manual activation by the administrator
271 #
263 #
272 # Pass a block for behavior when a user fails to save
264 # Pass a block for behavior when a user fails to save
273 def register_manually_by_administrator(user, &block)
265 def register_manually_by_administrator(user, &block)
274 if user.save
266 if user.save
275 # Sends an email to the administrators
267 # Sends an email to the administrators
276 Mailer.deliver_account_activation_request(user)
268 Mailer.deliver_account_activation_request(user)
277 account_pending
269 account_pending
278 else
270 else
279 yield if block_given?
271 yield if block_given?
280 end
272 end
281 end
273 end
282
274
283 def account_pending
275 def account_pending
284 flash[:notice] = l(:notice_account_pending)
276 flash[:notice] = l(:notice_account_pending)
285 redirect_to :action => 'login'
277 redirect_to :action => 'login'
286 end
278 end
287 end
279 end
@@ -1,539 +1,548
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 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 'uri'
18 require 'uri'
19 require 'cgi'
19 require 'cgi'
20
20
21 class Unauthorized < Exception; end
21 class Unauthorized < Exception; end
22
22
23 class ApplicationController < ActionController::Base
23 class ApplicationController < ActionController::Base
24 include Redmine::I18n
24 include Redmine::I18n
25
25
26 layout 'base'
26 layout 'base'
27 exempt_from_layout 'builder', 'rsb'
27 exempt_from_layout 'builder', 'rsb'
28
28
29 protect_from_forgery
29 protect_from_forgery
30 def handle_unverified_request
30 def handle_unverified_request
31 super
31 super
32 cookies.delete(:autologin)
32 cookies.delete(:autologin)
33 end
33 end
34 # Remove broken cookie after upgrade from 0.8.x (#4292)
34 # Remove broken cookie after upgrade from 0.8.x (#4292)
35 # See https://rails.lighthouseapp.com/projects/8994/tickets/3360
35 # See https://rails.lighthouseapp.com/projects/8994/tickets/3360
36 # TODO: remove it when Rails is fixed
36 # TODO: remove it when Rails is fixed
37 before_filter :delete_broken_cookies
37 before_filter :delete_broken_cookies
38 def delete_broken_cookies
38 def delete_broken_cookies
39 if cookies['_redmine_session'] && cookies['_redmine_session'] !~ /--/
39 if cookies['_redmine_session'] && cookies['_redmine_session'] !~ /--/
40 cookies.delete '_redmine_session'
40 cookies.delete '_redmine_session'
41 redirect_to home_path
41 redirect_to home_path
42 return false
42 return false
43 end
43 end
44 end
44 end
45
45
46 # FIXME: Remove this when all of Rack and Rails have learned how to
46 # FIXME: Remove this when all of Rack and Rails have learned how to
47 # properly use encodings
47 # properly use encodings
48 before_filter :params_filter
48 before_filter :params_filter
49
49
50 def params_filter
50 def params_filter
51 if RUBY_VERSION >= '1.9' && defined?(Rails) && Rails::VERSION::MAJOR < 3
51 if RUBY_VERSION >= '1.9' && defined?(Rails) && Rails::VERSION::MAJOR < 3
52 self.utf8nize!(params)
52 self.utf8nize!(params)
53 end
53 end
54 end
54 end
55
55
56 def utf8nize!(obj)
56 def utf8nize!(obj)
57 if obj.frozen?
57 if obj.frozen?
58 obj
58 obj
59 elsif obj.is_a? String
59 elsif obj.is_a? String
60 obj.respond_to?(:force_encoding) ? obj.force_encoding("UTF-8") : obj
60 obj.respond_to?(:force_encoding) ? obj.force_encoding("UTF-8") : obj
61 elsif obj.is_a? Hash
61 elsif obj.is_a? Hash
62 obj.each {|k, v| obj[k] = self.utf8nize!(v)}
62 obj.each {|k, v| obj[k] = self.utf8nize!(v)}
63 elsif obj.is_a? Array
63 elsif obj.is_a? Array
64 obj.each {|v| self.utf8nize!(v)}
64 obj.each {|v| self.utf8nize!(v)}
65 else
65 else
66 obj
66 obj
67 end
67 end
68 end
68 end
69
69
70 before_filter :user_setup, :check_if_login_required, :set_localization
70 before_filter :user_setup, :check_if_login_required, :set_localization
71 filter_parameter_logging :password
71 filter_parameter_logging :password
72
72
73 rescue_from ActionController::InvalidAuthenticityToken, :with => :invalid_authenticity_token
73 rescue_from ActionController::InvalidAuthenticityToken, :with => :invalid_authenticity_token
74 rescue_from ::Unauthorized, :with => :deny_access
74 rescue_from ::Unauthorized, :with => :deny_access
75
75
76 include Redmine::Search::Controller
76 include Redmine::Search::Controller
77 include Redmine::MenuManager::MenuController
77 include Redmine::MenuManager::MenuController
78 helper Redmine::MenuManager::MenuHelper
78 helper Redmine::MenuManager::MenuHelper
79
79
80 Redmine::Scm::Base.all.each do |scm|
80 Redmine::Scm::Base.all.each do |scm|
81 require_dependency "repository/#{scm.underscore}"
81 require_dependency "repository/#{scm.underscore}"
82 end
82 end
83
83
84 def user_setup
84 def user_setup
85 # Check the settings cache for each request
85 # Check the settings cache for each request
86 Setting.check_cache
86 Setting.check_cache
87 # Find the current user
87 # Find the current user
88 User.current = find_current_user
88 User.current = find_current_user
89 end
89 end
90
90
91 # Returns the current user or nil if no user is logged in
91 # Returns the current user or nil if no user is logged in
92 # and starts a session if needed
92 # and starts a session if needed
93 def find_current_user
93 def find_current_user
94 if session[:user_id]
94 if session[:user_id]
95 # existing session
95 # existing session
96 (User.active.find(session[:user_id]) rescue nil)
96 (User.active.find(session[:user_id]) rescue nil)
97 elsif cookies[:autologin] && Setting.autologin?
97 elsif cookies[:autologin] && Setting.autologin?
98 # auto-login feature starts a new session
98 # auto-login feature starts a new session
99 user = User.try_to_autologin(cookies[:autologin])
99 user = User.try_to_autologin(cookies[:autologin])
100 session[:user_id] = user.id if user
100 session[:user_id] = user.id if user
101 user
101 user
102 elsif params[:format] == 'atom' && params[:key] && request.get? && accept_rss_auth?
102 elsif params[:format] == 'atom' && params[:key] && request.get? && accept_rss_auth?
103 # RSS key authentication does not start a session
103 # RSS key authentication does not start a session
104 User.find_by_rss_key(params[:key])
104 User.find_by_rss_key(params[:key])
105 elsif Setting.rest_api_enabled? && accept_api_auth?
105 elsif Setting.rest_api_enabled? && accept_api_auth?
106 if (key = api_key_from_request)
106 if (key = api_key_from_request)
107 # Use API key
107 # Use API key
108 User.find_by_api_key(key)
108 User.find_by_api_key(key)
109 else
109 else
110 # HTTP Basic, either username/password or API key/random
110 # HTTP Basic, either username/password or API key/random
111 authenticate_with_http_basic do |username, password|
111 authenticate_with_http_basic do |username, password|
112 User.try_to_login(username, password) || User.find_by_api_key(username)
112 User.try_to_login(username, password) || User.find_by_api_key(username)
113 end
113 end
114 end
114 end
115 end
115 end
116 end
116 end
117
117
118 # Sets the logged in user
118 # Sets the logged in user
119 def logged_user=(user)
119 def logged_user=(user)
120 reset_session
120 reset_session
121 if user && user.is_a?(User)
121 if user && user.is_a?(User)
122 User.current = user
122 User.current = user
123 session[:user_id] = user.id
123 session[:user_id] = user.id
124 else
124 else
125 User.current = User.anonymous
125 User.current = User.anonymous
126 end
126 end
127 end
127 end
128
128
129 # Logs out current user
130 def logout_user
131 if User.current.logged?
132 cookies.delete :autologin
133 Token.delete_all(["user_id = ? AND action = ?", User.current.id, 'autologin'])
134 self.logged_user = nil
135 end
136 end
137
129 # check if login is globally required to access the application
138 # check if login is globally required to access the application
130 def check_if_login_required
139 def check_if_login_required
131 # no check needed if user is already logged in
140 # no check needed if user is already logged in
132 return true if User.current.logged?
141 return true if User.current.logged?
133 require_login if Setting.login_required?
142 require_login if Setting.login_required?
134 end
143 end
135
144
136 def set_localization
145 def set_localization
137 lang = nil
146 lang = nil
138 if User.current.logged?
147 if User.current.logged?
139 lang = find_language(User.current.language)
148 lang = find_language(User.current.language)
140 end
149 end
141 if lang.nil? && request.env['HTTP_ACCEPT_LANGUAGE']
150 if lang.nil? && request.env['HTTP_ACCEPT_LANGUAGE']
142 accept_lang = parse_qvalues(request.env['HTTP_ACCEPT_LANGUAGE']).first
151 accept_lang = parse_qvalues(request.env['HTTP_ACCEPT_LANGUAGE']).first
143 if !accept_lang.blank?
152 if !accept_lang.blank?
144 accept_lang = accept_lang.downcase
153 accept_lang = accept_lang.downcase
145 lang = find_language(accept_lang) || find_language(accept_lang.split('-').first)
154 lang = find_language(accept_lang) || find_language(accept_lang.split('-').first)
146 end
155 end
147 end
156 end
148 lang ||= Setting.default_language
157 lang ||= Setting.default_language
149 set_language_if_valid(lang)
158 set_language_if_valid(lang)
150 end
159 end
151
160
152 def require_login
161 def require_login
153 if !User.current.logged?
162 if !User.current.logged?
154 # Extract only the basic url parameters on non-GET requests
163 # Extract only the basic url parameters on non-GET requests
155 if request.get?
164 if request.get?
156 url = url_for(params)
165 url = url_for(params)
157 else
166 else
158 url = url_for(:controller => params[:controller], :action => params[:action], :id => params[:id], :project_id => params[:project_id])
167 url = url_for(:controller => params[:controller], :action => params[:action], :id => params[:id], :project_id => params[:project_id])
159 end
168 end
160 respond_to do |format|
169 respond_to do |format|
161 format.html { redirect_to :controller => "account", :action => "login", :back_url => url }
170 format.html { redirect_to :controller => "account", :action => "login", :back_url => url }
162 format.atom { redirect_to :controller => "account", :action => "login", :back_url => url }
171 format.atom { redirect_to :controller => "account", :action => "login", :back_url => url }
163 format.xml { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
172 format.xml { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
164 format.js { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
173 format.js { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
165 format.json { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
174 format.json { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
166 end
175 end
167 return false
176 return false
168 end
177 end
169 true
178 true
170 end
179 end
171
180
172 def require_admin
181 def require_admin
173 return unless require_login
182 return unless require_login
174 if !User.current.admin?
183 if !User.current.admin?
175 render_403
184 render_403
176 return false
185 return false
177 end
186 end
178 true
187 true
179 end
188 end
180
189
181 def deny_access
190 def deny_access
182 User.current.logged? ? render_403 : require_login
191 User.current.logged? ? render_403 : require_login
183 end
192 end
184
193
185 # Authorize the user for the requested action
194 # Authorize the user for the requested action
186 def authorize(ctrl = params[:controller], action = params[:action], global = false)
195 def authorize(ctrl = params[:controller], action = params[:action], global = false)
187 allowed = User.current.allowed_to?({:controller => ctrl, :action => action}, @project || @projects, :global => global)
196 allowed = User.current.allowed_to?({:controller => ctrl, :action => action}, @project || @projects, :global => global)
188 if allowed
197 if allowed
189 true
198 true
190 else
199 else
191 if @project && @project.archived?
200 if @project && @project.archived?
192 render_403 :message => :notice_not_authorized_archived_project
201 render_403 :message => :notice_not_authorized_archived_project
193 else
202 else
194 deny_access
203 deny_access
195 end
204 end
196 end
205 end
197 end
206 end
198
207
199 # Authorize the user for the requested action outside a project
208 # Authorize the user for the requested action outside a project
200 def authorize_global(ctrl = params[:controller], action = params[:action], global = true)
209 def authorize_global(ctrl = params[:controller], action = params[:action], global = true)
201 authorize(ctrl, action, global)
210 authorize(ctrl, action, global)
202 end
211 end
203
212
204 # Find project of id params[:id]
213 # Find project of id params[:id]
205 def find_project
214 def find_project
206 @project = Project.find(params[:id])
215 @project = Project.find(params[:id])
207 rescue ActiveRecord::RecordNotFound
216 rescue ActiveRecord::RecordNotFound
208 render_404
217 render_404
209 end
218 end
210
219
211 # Find project of id params[:project_id]
220 # Find project of id params[:project_id]
212 def find_project_by_project_id
221 def find_project_by_project_id
213 @project = Project.find(params[:project_id])
222 @project = Project.find(params[:project_id])
214 rescue ActiveRecord::RecordNotFound
223 rescue ActiveRecord::RecordNotFound
215 render_404
224 render_404
216 end
225 end
217
226
218 # Find a project based on params[:project_id]
227 # Find a project based on params[:project_id]
219 # TODO: some subclasses override this, see about merging their logic
228 # TODO: some subclasses override this, see about merging their logic
220 def find_optional_project
229 def find_optional_project
221 @project = Project.find(params[:project_id]) unless params[:project_id].blank?
230 @project = Project.find(params[:project_id]) unless params[:project_id].blank?
222 allowed = User.current.allowed_to?({:controller => params[:controller], :action => params[:action]}, @project, :global => true)
231 allowed = User.current.allowed_to?({:controller => params[:controller], :action => params[:action]}, @project, :global => true)
223 allowed ? true : deny_access
232 allowed ? true : deny_access
224 rescue ActiveRecord::RecordNotFound
233 rescue ActiveRecord::RecordNotFound
225 render_404
234 render_404
226 end
235 end
227
236
228 # Finds and sets @project based on @object.project
237 # Finds and sets @project based on @object.project
229 def find_project_from_association
238 def find_project_from_association
230 render_404 unless @object.present?
239 render_404 unless @object.present?
231
240
232 @project = @object.project
241 @project = @object.project
233 end
242 end
234
243
235 def find_model_object
244 def find_model_object
236 model = self.class.read_inheritable_attribute('model_object')
245 model = self.class.read_inheritable_attribute('model_object')
237 if model
246 if model
238 @object = model.find(params[:id])
247 @object = model.find(params[:id])
239 self.instance_variable_set('@' + controller_name.singularize, @object) if @object
248 self.instance_variable_set('@' + controller_name.singularize, @object) if @object
240 end
249 end
241 rescue ActiveRecord::RecordNotFound
250 rescue ActiveRecord::RecordNotFound
242 render_404
251 render_404
243 end
252 end
244
253
245 def self.model_object(model)
254 def self.model_object(model)
246 write_inheritable_attribute('model_object', model)
255 write_inheritable_attribute('model_object', model)
247 end
256 end
248
257
249 # Filter for bulk issue operations
258 # Filter for bulk issue operations
250 def find_issues
259 def find_issues
251 @issues = Issue.find_all_by_id(params[:id] || params[:ids])
260 @issues = Issue.find_all_by_id(params[:id] || params[:ids])
252 raise ActiveRecord::RecordNotFound if @issues.empty?
261 raise ActiveRecord::RecordNotFound if @issues.empty?
253 if @issues.detect {|issue| !issue.visible?}
262 if @issues.detect {|issue| !issue.visible?}
254 deny_access
263 deny_access
255 return
264 return
256 end
265 end
257 @projects = @issues.collect(&:project).compact.uniq
266 @projects = @issues.collect(&:project).compact.uniq
258 @project = @projects.first if @projects.size == 1
267 @project = @projects.first if @projects.size == 1
259 rescue ActiveRecord::RecordNotFound
268 rescue ActiveRecord::RecordNotFound
260 render_404
269 render_404
261 end
270 end
262
271
263 # make sure that the user is a member of the project (or admin) if project is private
272 # make sure that the user is a member of the project (or admin) if project is private
264 # used as a before_filter for actions that do not require any particular permission on the project
273 # used as a before_filter for actions that do not require any particular permission on the project
265 def check_project_privacy
274 def check_project_privacy
266 if @project && @project.active?
275 if @project && @project.active?
267 if @project.visible?
276 if @project.visible?
268 true
277 true
269 else
278 else
270 deny_access
279 deny_access
271 end
280 end
272 else
281 else
273 @project = nil
282 @project = nil
274 render_404
283 render_404
275 false
284 false
276 end
285 end
277 end
286 end
278
287
279 def back_url
288 def back_url
280 params[:back_url] || request.env['HTTP_REFERER']
289 params[:back_url] || request.env['HTTP_REFERER']
281 end
290 end
282
291
283 def redirect_back_or_default(default)
292 def redirect_back_or_default(default)
284 back_url = CGI.unescape(params[:back_url].to_s)
293 back_url = CGI.unescape(params[:back_url].to_s)
285 if !back_url.blank?
294 if !back_url.blank?
286 begin
295 begin
287 uri = URI.parse(back_url)
296 uri = URI.parse(back_url)
288 # do not redirect user to another host or to the login or register page
297 # do not redirect user to another host or to the login or register page
289 if (uri.relative? || (uri.host == request.host)) && !uri.path.match(%r{/(login|account/register)})
298 if (uri.relative? || (uri.host == request.host)) && !uri.path.match(%r{/(login|account/register)})
290 redirect_to(back_url)
299 redirect_to(back_url)
291 return
300 return
292 end
301 end
293 rescue URI::InvalidURIError
302 rescue URI::InvalidURIError
294 # redirect to default
303 # redirect to default
295 end
304 end
296 end
305 end
297 redirect_to default
306 redirect_to default
298 false
307 false
299 end
308 end
300
309
301 # Redirects to the request referer if present, redirects to args or call block otherwise.
310 # Redirects to the request referer if present, redirects to args or call block otherwise.
302 def redirect_to_referer_or(*args, &block)
311 def redirect_to_referer_or(*args, &block)
303 redirect_to :back
312 redirect_to :back
304 rescue ::ActionController::RedirectBackError
313 rescue ::ActionController::RedirectBackError
305 if args.any?
314 if args.any?
306 redirect_to *args
315 redirect_to *args
307 elsif block_given?
316 elsif block_given?
308 block.call
317 block.call
309 else
318 else
310 raise "#redirect_to_referer_or takes arguments or a block"
319 raise "#redirect_to_referer_or takes arguments or a block"
311 end
320 end
312 end
321 end
313
322
314 def render_403(options={})
323 def render_403(options={})
315 @project = nil
324 @project = nil
316 render_error({:message => :notice_not_authorized, :status => 403}.merge(options))
325 render_error({:message => :notice_not_authorized, :status => 403}.merge(options))
317 return false
326 return false
318 end
327 end
319
328
320 def render_404(options={})
329 def render_404(options={})
321 render_error({:message => :notice_file_not_found, :status => 404}.merge(options))
330 render_error({:message => :notice_file_not_found, :status => 404}.merge(options))
322 return false
331 return false
323 end
332 end
324
333
325 # Renders an error response
334 # Renders an error response
326 def render_error(arg)
335 def render_error(arg)
327 arg = {:message => arg} unless arg.is_a?(Hash)
336 arg = {:message => arg} unless arg.is_a?(Hash)
328
337
329 @message = arg[:message]
338 @message = arg[:message]
330 @message = l(@message) if @message.is_a?(Symbol)
339 @message = l(@message) if @message.is_a?(Symbol)
331 @status = arg[:status] || 500
340 @status = arg[:status] || 500
332
341
333 respond_to do |format|
342 respond_to do |format|
334 format.html {
343 format.html {
335 render :template => 'common/error', :layout => use_layout, :status => @status
344 render :template => 'common/error', :layout => use_layout, :status => @status
336 }
345 }
337 format.atom { head @status }
346 format.atom { head @status }
338 format.xml { head @status }
347 format.xml { head @status }
339 format.js { head @status }
348 format.js { head @status }
340 format.json { head @status }
349 format.json { head @status }
341 end
350 end
342 end
351 end
343
352
344 # Filter for actions that provide an API response
353 # Filter for actions that provide an API response
345 # but have no HTML representation for non admin users
354 # but have no HTML representation for non admin users
346 def require_admin_or_api_request
355 def require_admin_or_api_request
347 return true if api_request?
356 return true if api_request?
348 if User.current.admin?
357 if User.current.admin?
349 true
358 true
350 elsif User.current.logged?
359 elsif User.current.logged?
351 render_error(:status => 406)
360 render_error(:status => 406)
352 else
361 else
353 deny_access
362 deny_access
354 end
363 end
355 end
364 end
356
365
357 # Picks which layout to use based on the request
366 # Picks which layout to use based on the request
358 #
367 #
359 # @return [boolean, string] name of the layout to use or false for no layout
368 # @return [boolean, string] name of the layout to use or false for no layout
360 def use_layout
369 def use_layout
361 request.xhr? ? false : 'base'
370 request.xhr? ? false : 'base'
362 end
371 end
363
372
364 def invalid_authenticity_token
373 def invalid_authenticity_token
365 if api_request?
374 if api_request?
366 logger.error "Form authenticity token is missing or is invalid. API calls must include a proper Content-type header (text/xml or text/json)."
375 logger.error "Form authenticity token is missing or is invalid. API calls must include a proper Content-type header (text/xml or text/json)."
367 end
376 end
368 render_error "Invalid form authenticity token."
377 render_error "Invalid form authenticity token."
369 end
378 end
370
379
371 def render_feed(items, options={})
380 def render_feed(items, options={})
372 @items = items || []
381 @items = items || []
373 @items.sort! {|x,y| y.event_datetime <=> x.event_datetime }
382 @items.sort! {|x,y| y.event_datetime <=> x.event_datetime }
374 @items = @items.slice(0, Setting.feeds_limit.to_i)
383 @items = @items.slice(0, Setting.feeds_limit.to_i)
375 @title = options[:title] || Setting.app_title
384 @title = options[:title] || Setting.app_title
376 render :template => "common/feed.atom", :layout => false,
385 render :template => "common/feed.atom", :layout => false,
377 :content_type => 'application/atom+xml'
386 :content_type => 'application/atom+xml'
378 end
387 end
379
388
380 def self.accept_rss_auth(*actions)
389 def self.accept_rss_auth(*actions)
381 if actions.any?
390 if actions.any?
382 write_inheritable_attribute('accept_rss_auth_actions', actions)
391 write_inheritable_attribute('accept_rss_auth_actions', actions)
383 else
392 else
384 read_inheritable_attribute('accept_rss_auth_actions') || []
393 read_inheritable_attribute('accept_rss_auth_actions') || []
385 end
394 end
386 end
395 end
387
396
388 def accept_rss_auth?(action=action_name)
397 def accept_rss_auth?(action=action_name)
389 self.class.accept_rss_auth.include?(action.to_sym)
398 self.class.accept_rss_auth.include?(action.to_sym)
390 end
399 end
391
400
392 def self.accept_api_auth(*actions)
401 def self.accept_api_auth(*actions)
393 if actions.any?
402 if actions.any?
394 write_inheritable_attribute('accept_api_auth_actions', actions)
403 write_inheritable_attribute('accept_api_auth_actions', actions)
395 else
404 else
396 read_inheritable_attribute('accept_api_auth_actions') || []
405 read_inheritable_attribute('accept_api_auth_actions') || []
397 end
406 end
398 end
407 end
399
408
400 def accept_api_auth?(action=action_name)
409 def accept_api_auth?(action=action_name)
401 self.class.accept_api_auth.include?(action.to_sym)
410 self.class.accept_api_auth.include?(action.to_sym)
402 end
411 end
403
412
404 # Returns the number of objects that should be displayed
413 # Returns the number of objects that should be displayed
405 # on the paginated list
414 # on the paginated list
406 def per_page_option
415 def per_page_option
407 per_page = nil
416 per_page = nil
408 if params[:per_page] && Setting.per_page_options_array.include?(params[:per_page].to_s.to_i)
417 if params[:per_page] && Setting.per_page_options_array.include?(params[:per_page].to_s.to_i)
409 per_page = params[:per_page].to_s.to_i
418 per_page = params[:per_page].to_s.to_i
410 session[:per_page] = per_page
419 session[:per_page] = per_page
411 elsif session[:per_page]
420 elsif session[:per_page]
412 per_page = session[:per_page]
421 per_page = session[:per_page]
413 else
422 else
414 per_page = Setting.per_page_options_array.first || 25
423 per_page = Setting.per_page_options_array.first || 25
415 end
424 end
416 per_page
425 per_page
417 end
426 end
418
427
419 # Returns offset and limit used to retrieve objects
428 # Returns offset and limit used to retrieve objects
420 # for an API response based on offset, limit and page parameters
429 # for an API response based on offset, limit and page parameters
421 def api_offset_and_limit(options=params)
430 def api_offset_and_limit(options=params)
422 if options[:offset].present?
431 if options[:offset].present?
423 offset = options[:offset].to_i
432 offset = options[:offset].to_i
424 if offset < 0
433 if offset < 0
425 offset = 0
434 offset = 0
426 end
435 end
427 end
436 end
428 limit = options[:limit].to_i
437 limit = options[:limit].to_i
429 if limit < 1
438 if limit < 1
430 limit = 25
439 limit = 25
431 elsif limit > 100
440 elsif limit > 100
432 limit = 100
441 limit = 100
433 end
442 end
434 if offset.nil? && options[:page].present?
443 if offset.nil? && options[:page].present?
435 offset = (options[:page].to_i - 1) * limit
444 offset = (options[:page].to_i - 1) * limit
436 offset = 0 if offset < 0
445 offset = 0 if offset < 0
437 end
446 end
438 offset ||= 0
447 offset ||= 0
439
448
440 [offset, limit]
449 [offset, limit]
441 end
450 end
442
451
443 # qvalues http header parser
452 # qvalues http header parser
444 # code taken from webrick
453 # code taken from webrick
445 def parse_qvalues(value)
454 def parse_qvalues(value)
446 tmp = []
455 tmp = []
447 if value
456 if value
448 parts = value.split(/,\s*/)
457 parts = value.split(/,\s*/)
449 parts.each {|part|
458 parts.each {|part|
450 if m = %r{^([^\s,]+?)(?:;\s*q=(\d+(?:\.\d+)?))?$}.match(part)
459 if m = %r{^([^\s,]+?)(?:;\s*q=(\d+(?:\.\d+)?))?$}.match(part)
451 val = m[1]
460 val = m[1]
452 q = (m[2] or 1).to_f
461 q = (m[2] or 1).to_f
453 tmp.push([val, q])
462 tmp.push([val, q])
454 end
463 end
455 }
464 }
456 tmp = tmp.sort_by{|val, q| -q}
465 tmp = tmp.sort_by{|val, q| -q}
457 tmp.collect!{|val, q| val}
466 tmp.collect!{|val, q| val}
458 end
467 end
459 return tmp
468 return tmp
460 rescue
469 rescue
461 nil
470 nil
462 end
471 end
463
472
464 # Returns a string that can be used as filename value in Content-Disposition header
473 # Returns a string that can be used as filename value in Content-Disposition header
465 def filename_for_content_disposition(name)
474 def filename_for_content_disposition(name)
466 request.env['HTTP_USER_AGENT'] =~ %r{MSIE} ? ERB::Util.url_encode(name) : name
475 request.env['HTTP_USER_AGENT'] =~ %r{MSIE} ? ERB::Util.url_encode(name) : name
467 end
476 end
468
477
469 def api_request?
478 def api_request?
470 %w(xml json).include? params[:format]
479 %w(xml json).include? params[:format]
471 end
480 end
472
481
473 # Returns the API key present in the request
482 # Returns the API key present in the request
474 def api_key_from_request
483 def api_key_from_request
475 if params[:key].present?
484 if params[:key].present?
476 params[:key]
485 params[:key]
477 elsif request.headers["X-Redmine-API-Key"].present?
486 elsif request.headers["X-Redmine-API-Key"].present?
478 request.headers["X-Redmine-API-Key"]
487 request.headers["X-Redmine-API-Key"]
479 end
488 end
480 end
489 end
481
490
482 # Renders a warning flash if obj has unsaved attachments
491 # Renders a warning flash if obj has unsaved attachments
483 def render_attachment_warning_if_needed(obj)
492 def render_attachment_warning_if_needed(obj)
484 flash[:warning] = l(:warning_attachments_not_saved, obj.unsaved_attachments.size) if obj.unsaved_attachments.present?
493 flash[:warning] = l(:warning_attachments_not_saved, obj.unsaved_attachments.size) if obj.unsaved_attachments.present?
485 end
494 end
486
495
487 # Sets the `flash` notice or error based the number of issues that did not save
496 # Sets the `flash` notice or error based the number of issues that did not save
488 #
497 #
489 # @param [Array, Issue] issues all of the saved and unsaved Issues
498 # @param [Array, Issue] issues all of the saved and unsaved Issues
490 # @param [Array, Integer] unsaved_issue_ids the issue ids that were not saved
499 # @param [Array, Integer] unsaved_issue_ids the issue ids that were not saved
491 def set_flash_from_bulk_issue_save(issues, unsaved_issue_ids)
500 def set_flash_from_bulk_issue_save(issues, unsaved_issue_ids)
492 if unsaved_issue_ids.empty?
501 if unsaved_issue_ids.empty?
493 flash[:notice] = l(:notice_successful_update) unless issues.empty?
502 flash[:notice] = l(:notice_successful_update) unless issues.empty?
494 else
503 else
495 flash[:error] = l(:notice_failed_to_save_issues,
504 flash[:error] = l(:notice_failed_to_save_issues,
496 :count => unsaved_issue_ids.size,
505 :count => unsaved_issue_ids.size,
497 :total => issues.size,
506 :total => issues.size,
498 :ids => '#' + unsaved_issue_ids.join(', #'))
507 :ids => '#' + unsaved_issue_ids.join(', #'))
499 end
508 end
500 end
509 end
501
510
502 # Rescues an invalid query statement. Just in case...
511 # Rescues an invalid query statement. Just in case...
503 def query_statement_invalid(exception)
512 def query_statement_invalid(exception)
504 logger.error "Query::StatementInvalid: #{exception.message}" if logger
513 logger.error "Query::StatementInvalid: #{exception.message}" if logger
505 session.delete(:query)
514 session.delete(:query)
506 sort_clear if respond_to?(:sort_clear)
515 sort_clear if respond_to?(:sort_clear)
507 render_error "An error occurred while executing the query and has been logged. Please report this error to your Redmine administrator."
516 render_error "An error occurred while executing the query and has been logged. Please report this error to your Redmine administrator."
508 end
517 end
509
518
510 # Renders API response on validation failure
519 # Renders API response on validation failure
511 def render_validation_errors(objects)
520 def render_validation_errors(objects)
512 if objects.is_a?(Array)
521 if objects.is_a?(Array)
513 @error_messages = objects.map {|object| object.errors.full_messages}.flatten
522 @error_messages = objects.map {|object| object.errors.full_messages}.flatten
514 else
523 else
515 @error_messages = objects.errors.full_messages
524 @error_messages = objects.errors.full_messages
516 end
525 end
517 render :template => 'common/error_messages.api', :status => :unprocessable_entity, :layout => false
526 render :template => 'common/error_messages.api', :status => :unprocessable_entity, :layout => false
518 end
527 end
519
528
520 # Overrides #default_template so that the api template
529 # Overrides #default_template so that the api template
521 # is used automatically if it exists
530 # is used automatically if it exists
522 def default_template(action_name = self.action_name)
531 def default_template(action_name = self.action_name)
523 if api_request?
532 if api_request?
524 begin
533 begin
525 return self.view_paths.find_template(default_template_name(action_name), 'api')
534 return self.view_paths.find_template(default_template_name(action_name), 'api')
526 rescue ::ActionView::MissingTemplate
535 rescue ::ActionView::MissingTemplate
527 # the api template was not found
536 # the api template was not found
528 # fallback to the default behaviour
537 # fallback to the default behaviour
529 end
538 end
530 end
539 end
531 super
540 super
532 end
541 end
533
542
534 # Overrides #pick_layout so that #render with no arguments
543 # Overrides #pick_layout so that #render with no arguments
535 # doesn't use the layout for api requests
544 # doesn't use the layout for api requests
536 def pick_layout(*args)
545 def pick_layout(*args)
537 api_request? ? nil : super
546 api_request? ? nil : super
538 end
547 end
539 end
548 end
@@ -1,174 +1,192
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 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 :users
22 helper :users
23 helper :custom_fields
23 helper :custom_fields
24
24
25 BLOCKS = { 'issuesassignedtome' => :label_assigned_to_me_issues,
25 BLOCKS = { 'issuesassignedtome' => :label_assigned_to_me_issues,
26 'issuesreportedbyme' => :label_reported_issues,
26 'issuesreportedbyme' => :label_reported_issues,
27 'issueswatched' => :label_watched_issues,
27 'issueswatched' => :label_watched_issues,
28 'news' => :label_news_latest,
28 'news' => :label_news_latest,
29 'calendar' => :label_calendar,
29 'calendar' => :label_calendar,
30 'documents' => :label_document_plural,
30 'documents' => :label_document_plural,
31 'timelog' => :label_spent_time
31 'timelog' => :label_spent_time
32 }.merge(Redmine::Views::MyPage::Block.additional_blocks).freeze
32 }.merge(Redmine::Views::MyPage::Block.additional_blocks).freeze
33
33
34 DEFAULT_LAYOUT = { 'left' => ['issuesassignedtome'],
34 DEFAULT_LAYOUT = { 'left' => ['issuesassignedtome'],
35 'right' => ['issuesreportedbyme']
35 'right' => ['issuesreportedbyme']
36 }.freeze
36 }.freeze
37
37
38 def index
38 def index
39 page
39 page
40 render :action => 'page'
40 render :action => 'page'
41 end
41 end
42
42
43 # Show user's page
43 # Show user's page
44 def page
44 def page
45 @user = User.current
45 @user = User.current
46 @blocks = @user.pref[:my_page_layout] || DEFAULT_LAYOUT
46 @blocks = @user.pref[:my_page_layout] || DEFAULT_LAYOUT
47 end
47 end
48
48
49 # Edit user's account
49 # Edit user's account
50 def account
50 def account
51 @user = User.current
51 @user = User.current
52 @pref = @user.pref
52 @pref = @user.pref
53 if request.post?
53 if request.post?
54 @user.safe_attributes = params[:user]
54 @user.safe_attributes = params[:user]
55 @user.pref.attributes = params[:pref]
55 @user.pref.attributes = params[:pref]
56 @user.pref[:no_self_notified] = (params[:no_self_notified] == '1')
56 @user.pref[:no_self_notified] = (params[:no_self_notified] == '1')
57 if @user.save
57 if @user.save
58 @user.pref.save
58 @user.pref.save
59 @user.notified_project_ids = (@user.mail_notification == 'selected' ? params[:notified_project_ids] : [])
59 @user.notified_project_ids = (@user.mail_notification == 'selected' ? params[:notified_project_ids] : [])
60 set_language_if_valid @user.language
60 set_language_if_valid @user.language
61 flash[:notice] = l(:notice_account_updated)
61 flash[:notice] = l(:notice_account_updated)
62 redirect_to :action => 'account'
62 redirect_to :action => 'account'
63 return
63 return
64 end
64 end
65 end
65 end
66 end
66 end
67
67
68 # Destroys user's account
69 def destroy
70 @user = User.current
71 unless @user.own_account_deletable?
72 redirect_to :action => 'account'
73 return
74 end
75
76 if request.post? && params[:confirm]
77 @user.destroy
78 if @user.destroyed?
79 logout_user
80 flash[:notice] = l(:notice_account_deleted)
81 end
82 redirect_to home_path
83 end
84 end
85
68 # Manage user's password
86 # Manage user's password
69 def password
87 def password
70 @user = User.current
88 @user = User.current
71 unless @user.change_password_allowed?
89 unless @user.change_password_allowed?
72 flash[:error] = l(:notice_can_t_change_password)
90 flash[:error] = l(:notice_can_t_change_password)
73 redirect_to :action => 'account'
91 redirect_to :action => 'account'
74 return
92 return
75 end
93 end
76 if request.post?
94 if request.post?
77 if @user.check_password?(params[:password])
95 if @user.check_password?(params[:password])
78 @user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation]
96 @user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation]
79 if @user.save
97 if @user.save
80 flash[:notice] = l(:notice_account_password_updated)
98 flash[:notice] = l(:notice_account_password_updated)
81 redirect_to :action => 'account'
99 redirect_to :action => 'account'
82 end
100 end
83 else
101 else
84 flash[:error] = l(:notice_account_wrong_password)
102 flash[:error] = l(:notice_account_wrong_password)
85 end
103 end
86 end
104 end
87 end
105 end
88
106
89 # Create a new feeds key
107 # Create a new feeds key
90 def reset_rss_key
108 def reset_rss_key
91 if request.post?
109 if request.post?
92 if User.current.rss_token
110 if User.current.rss_token
93 User.current.rss_token.destroy
111 User.current.rss_token.destroy
94 User.current.reload
112 User.current.reload
95 end
113 end
96 User.current.rss_key
114 User.current.rss_key
97 flash[:notice] = l(:notice_feeds_access_key_reseted)
115 flash[:notice] = l(:notice_feeds_access_key_reseted)
98 end
116 end
99 redirect_to :action => 'account'
117 redirect_to :action => 'account'
100 end
118 end
101
119
102 # Create a new API key
120 # Create a new API key
103 def reset_api_key
121 def reset_api_key
104 if request.post?
122 if request.post?
105 if User.current.api_token
123 if User.current.api_token
106 User.current.api_token.destroy
124 User.current.api_token.destroy
107 User.current.reload
125 User.current.reload
108 end
126 end
109 User.current.api_key
127 User.current.api_key
110 flash[:notice] = l(:notice_api_access_key_reseted)
128 flash[:notice] = l(:notice_api_access_key_reseted)
111 end
129 end
112 redirect_to :action => 'account'
130 redirect_to :action => 'account'
113 end
131 end
114
132
115 # User's page layout configuration
133 # User's page layout configuration
116 def page_layout
134 def page_layout
117 @user = User.current
135 @user = User.current
118 @blocks = @user.pref[:my_page_layout] || DEFAULT_LAYOUT.dup
136 @blocks = @user.pref[:my_page_layout] || DEFAULT_LAYOUT.dup
119 @block_options = []
137 @block_options = []
120 BLOCKS.each {|k, v| @block_options << [l("my.blocks.#{v}", :default => [v, v.to_s.humanize]), k.dasherize]}
138 BLOCKS.each {|k, v| @block_options << [l("my.blocks.#{v}", :default => [v, v.to_s.humanize]), k.dasherize]}
121 end
139 end
122
140
123 # Add a block to user's page
141 # Add a block to user's page
124 # The block is added on top of the page
142 # The block is added on top of the page
125 # params[:block] : id of the block to add
143 # params[:block] : id of the block to add
126 def add_block
144 def add_block
127 block = params[:block].to_s.underscore
145 block = params[:block].to_s.underscore
128 (render :nothing => true; return) unless block && (BLOCKS.keys.include? block)
146 (render :nothing => true; return) unless block && (BLOCKS.keys.include? block)
129 @user = User.current
147 @user = User.current
130 layout = @user.pref[:my_page_layout] || {}
148 layout = @user.pref[:my_page_layout] || {}
131 # remove if already present in a group
149 # remove if already present in a group
132 %w(top left right).each {|f| (layout[f] ||= []).delete block }
150 %w(top left right).each {|f| (layout[f] ||= []).delete block }
133 # add it on top
151 # add it on top
134 layout['top'].unshift block
152 layout['top'].unshift block
135 @user.pref[:my_page_layout] = layout
153 @user.pref[:my_page_layout] = layout
136 @user.pref.save
154 @user.pref.save
137 render :partial => "block", :locals => {:user => @user, :block_name => block}
155 render :partial => "block", :locals => {:user => @user, :block_name => block}
138 end
156 end
139
157
140 # Remove a block to user's page
158 # Remove a block to user's page
141 # params[:block] : id of the block to remove
159 # params[:block] : id of the block to remove
142 def remove_block
160 def remove_block
143 block = params[:block].to_s.underscore
161 block = params[:block].to_s.underscore
144 @user = User.current
162 @user = User.current
145 # remove block in all groups
163 # remove block in all groups
146 layout = @user.pref[:my_page_layout] || {}
164 layout = @user.pref[:my_page_layout] || {}
147 %w(top left right).each {|f| (layout[f] ||= []).delete block }
165 %w(top left right).each {|f| (layout[f] ||= []).delete block }
148 @user.pref[:my_page_layout] = layout
166 @user.pref[:my_page_layout] = layout
149 @user.pref.save
167 @user.pref.save
150 render :nothing => true
168 render :nothing => true
151 end
169 end
152
170
153 # Change blocks order on user's page
171 # Change blocks order on user's page
154 # params[:group] : group to order (top, left or right)
172 # params[:group] : group to order (top, left or right)
155 # params[:list-(top|left|right)] : array of block ids of the group
173 # params[:list-(top|left|right)] : array of block ids of the group
156 def order_blocks
174 def order_blocks
157 group = params[:group]
175 group = params[:group]
158 @user = User.current
176 @user = User.current
159 if group.is_a?(String)
177 if group.is_a?(String)
160 group_items = (params["list-#{group}"] || []).collect(&:underscore)
178 group_items = (params["list-#{group}"] || []).collect(&:underscore)
161 if group_items and group_items.is_a? Array
179 if group_items and group_items.is_a? Array
162 layout = @user.pref[:my_page_layout] || {}
180 layout = @user.pref[:my_page_layout] || {}
163 # remove group blocks if they are presents in other groups
181 # remove group blocks if they are presents in other groups
164 %w(top left right).each {|f|
182 %w(top left right).each {|f|
165 layout[f] = (layout[f] || []) - group_items
183 layout[f] = (layout[f] || []) - group_items
166 }
184 }
167 layout[group] = group_items
185 layout[group] = group_items
168 @user.pref[:my_page_layout] = layout
186 @user.pref[:my_page_layout] = layout
169 @user.pref.save
187 @user.pref.save
170 end
188 end
171 end
189 end
172 render :nothing => true
190 render :nothing => true
173 end
191 end
174 end
192 end
@@ -1,647 +1,653
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 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 include Redmine::SafeAttributes
21 include Redmine::SafeAttributes
22
22
23 # Account statuses
23 # Account statuses
24 STATUS_ANONYMOUS = 0
24 STATUS_ANONYMOUS = 0
25 STATUS_ACTIVE = 1
25 STATUS_ACTIVE = 1
26 STATUS_REGISTERED = 2
26 STATUS_REGISTERED = 2
27 STATUS_LOCKED = 3
27 STATUS_LOCKED = 3
28
28
29 # Different ways of displaying/sorting users
29 # Different ways of displaying/sorting users
30 USER_FORMATS = {
30 USER_FORMATS = {
31 :firstname_lastname => {:string => '#{firstname} #{lastname}', :order => %w(firstname lastname id)},
31 :firstname_lastname => {:string => '#{firstname} #{lastname}', :order => %w(firstname lastname id)},
32 :firstname => {:string => '#{firstname}', :order => %w(firstname id)},
32 :firstname => {:string => '#{firstname}', :order => %w(firstname id)},
33 :lastname_firstname => {:string => '#{lastname} #{firstname}', :order => %w(lastname firstname id)},
33 :lastname_firstname => {:string => '#{lastname} #{firstname}', :order => %w(lastname firstname id)},
34 :lastname_coma_firstname => {:string => '#{lastname}, #{firstname}', :order => %w(lastname firstname id)},
34 :lastname_coma_firstname => {:string => '#{lastname}, #{firstname}', :order => %w(lastname firstname id)},
35 :username => {:string => '#{login}', :order => %w(login id)},
35 :username => {:string => '#{login}', :order => %w(login id)},
36 }
36 }
37
37
38 MAIL_NOTIFICATION_OPTIONS = [
38 MAIL_NOTIFICATION_OPTIONS = [
39 ['all', :label_user_mail_option_all],
39 ['all', :label_user_mail_option_all],
40 ['selected', :label_user_mail_option_selected],
40 ['selected', :label_user_mail_option_selected],
41 ['only_my_events', :label_user_mail_option_only_my_events],
41 ['only_my_events', :label_user_mail_option_only_my_events],
42 ['only_assigned', :label_user_mail_option_only_assigned],
42 ['only_assigned', :label_user_mail_option_only_assigned],
43 ['only_owner', :label_user_mail_option_only_owner],
43 ['only_owner', :label_user_mail_option_only_owner],
44 ['none', :label_user_mail_option_none]
44 ['none', :label_user_mail_option_none]
45 ]
45 ]
46
46
47 has_and_belongs_to_many :groups, :after_add => Proc.new {|user, group| group.user_added(user)},
47 has_and_belongs_to_many :groups, :after_add => Proc.new {|user, group| group.user_added(user)},
48 :after_remove => Proc.new {|user, group| group.user_removed(user)}
48 :after_remove => Proc.new {|user, group| group.user_removed(user)}
49 has_many :changesets, :dependent => :nullify
49 has_many :changesets, :dependent => :nullify
50 has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
50 has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
51 has_one :rss_token, :class_name => 'Token', :conditions => "action='feeds'"
51 has_one :rss_token, :class_name => 'Token', :conditions => "action='feeds'"
52 has_one :api_token, :class_name => 'Token', :conditions => "action='api'"
52 has_one :api_token, :class_name => 'Token', :conditions => "action='api'"
53 belongs_to :auth_source
53 belongs_to :auth_source
54
54
55 # Active non-anonymous users scope
55 # Active non-anonymous users scope
56 named_scope :active, :conditions => "#{User.table_name}.status = #{STATUS_ACTIVE}"
56 named_scope :active, :conditions => "#{User.table_name}.status = #{STATUS_ACTIVE}"
57 named_scope :logged, :conditions => "#{User.table_name}.status <> #{STATUS_ANONYMOUS}"
57 named_scope :logged, :conditions => "#{User.table_name}.status <> #{STATUS_ANONYMOUS}"
58 named_scope :status, lambda {|arg| arg.blank? ? {} : {:conditions => {:status => arg.to_i}} }
58 named_scope :status, lambda {|arg| arg.blank? ? {} : {:conditions => {:status => arg.to_i}} }
59
59
60 acts_as_customizable
60 acts_as_customizable
61
61
62 attr_accessor :password, :password_confirmation
62 attr_accessor :password, :password_confirmation
63 attr_accessor :last_before_login_on
63 attr_accessor :last_before_login_on
64 # Prevents unauthorized assignments
64 # Prevents unauthorized assignments
65 attr_protected :login, :admin, :password, :password_confirmation, :hashed_password
65 attr_protected :login, :admin, :password, :password_confirmation, :hashed_password
66
66
67 LOGIN_LENGTH_LIMIT = 60
67 LOGIN_LENGTH_LIMIT = 60
68 MAIL_LENGTH_LIMIT = 60
68 MAIL_LENGTH_LIMIT = 60
69
69
70 validates_presence_of :login, :firstname, :lastname, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }
70 validates_presence_of :login, :firstname, :lastname, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }
71 validates_uniqueness_of :login, :if => Proc.new { |user| !user.login.blank? }, :case_sensitive => false
71 validates_uniqueness_of :login, :if => Proc.new { |user| !user.login.blank? }, :case_sensitive => false
72 validates_uniqueness_of :mail, :if => Proc.new { |user| !user.mail.blank? }, :case_sensitive => false
72 validates_uniqueness_of :mail, :if => Proc.new { |user| !user.mail.blank? }, :case_sensitive => false
73 # Login must contain lettres, numbers, underscores only
73 # Login must contain lettres, numbers, underscores only
74 validates_format_of :login, :with => /^[a-z0-9_\-@\.]*$/i
74 validates_format_of :login, :with => /^[a-z0-9_\-@\.]*$/i
75 validates_length_of :login, :maximum => LOGIN_LENGTH_LIMIT
75 validates_length_of :login, :maximum => LOGIN_LENGTH_LIMIT
76 validates_length_of :firstname, :lastname, :maximum => 30
76 validates_length_of :firstname, :lastname, :maximum => 30
77 validates_format_of :mail, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :allow_blank => true
77 validates_format_of :mail, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :allow_blank => true
78 validates_length_of :mail, :maximum => MAIL_LENGTH_LIMIT, :allow_nil => true
78 validates_length_of :mail, :maximum => MAIL_LENGTH_LIMIT, :allow_nil => true
79 validates_confirmation_of :password, :allow_nil => true
79 validates_confirmation_of :password, :allow_nil => true
80 validates_inclusion_of :mail_notification, :in => MAIL_NOTIFICATION_OPTIONS.collect(&:first), :allow_blank => true
80 validates_inclusion_of :mail_notification, :in => MAIL_NOTIFICATION_OPTIONS.collect(&:first), :allow_blank => true
81 validate :validate_password_length
81 validate :validate_password_length
82
82
83 before_create :set_mail_notification
83 before_create :set_mail_notification
84 before_save :update_hashed_password
84 before_save :update_hashed_password
85 before_destroy :remove_references_before_destroy
85 before_destroy :remove_references_before_destroy
86
86
87 named_scope :in_group, lambda {|group|
87 named_scope :in_group, lambda {|group|
88 group_id = group.is_a?(Group) ? group.id : group.to_i
88 group_id = group.is_a?(Group) ? group.id : group.to_i
89 { :conditions => ["#{User.table_name}.id IN (SELECT gu.user_id FROM #{table_name_prefix}groups_users#{table_name_suffix} gu WHERE gu.group_id = ?)", group_id] }
89 { :conditions => ["#{User.table_name}.id IN (SELECT gu.user_id FROM #{table_name_prefix}groups_users#{table_name_suffix} gu WHERE gu.group_id = ?)", group_id] }
90 }
90 }
91 named_scope :not_in_group, lambda {|group|
91 named_scope :not_in_group, lambda {|group|
92 group_id = group.is_a?(Group) ? group.id : group.to_i
92 group_id = group.is_a?(Group) ? group.id : group.to_i
93 { :conditions => ["#{User.table_name}.id NOT IN (SELECT gu.user_id FROM #{table_name_prefix}groups_users#{table_name_suffix} gu WHERE gu.group_id = ?)", group_id] }
93 { :conditions => ["#{User.table_name}.id NOT IN (SELECT gu.user_id FROM #{table_name_prefix}groups_users#{table_name_suffix} gu WHERE gu.group_id = ?)", group_id] }
94 }
94 }
95
95
96 def set_mail_notification
96 def set_mail_notification
97 self.mail_notification = Setting.default_notification_option if self.mail_notification.blank?
97 self.mail_notification = Setting.default_notification_option if self.mail_notification.blank?
98 true
98 true
99 end
99 end
100
100
101 def update_hashed_password
101 def update_hashed_password
102 # update hashed_password if password was set
102 # update hashed_password if password was set
103 if self.password && self.auth_source_id.blank?
103 if self.password && self.auth_source_id.blank?
104 salt_password(password)
104 salt_password(password)
105 end
105 end
106 end
106 end
107
107
108 def reload(*args)
108 def reload(*args)
109 @name = nil
109 @name = nil
110 @projects_by_role = nil
110 @projects_by_role = nil
111 super
111 super
112 end
112 end
113
113
114 def mail=(arg)
114 def mail=(arg)
115 write_attribute(:mail, arg.to_s.strip)
115 write_attribute(:mail, arg.to_s.strip)
116 end
116 end
117
117
118 def identity_url=(url)
118 def identity_url=(url)
119 if url.blank?
119 if url.blank?
120 write_attribute(:identity_url, '')
120 write_attribute(:identity_url, '')
121 else
121 else
122 begin
122 begin
123 write_attribute(:identity_url, OpenIdAuthentication.normalize_identifier(url))
123 write_attribute(:identity_url, OpenIdAuthentication.normalize_identifier(url))
124 rescue OpenIdAuthentication::InvalidOpenId
124 rescue OpenIdAuthentication::InvalidOpenId
125 # Invlaid url, don't save
125 # Invlaid url, don't save
126 end
126 end
127 end
127 end
128 self.read_attribute(:identity_url)
128 self.read_attribute(:identity_url)
129 end
129 end
130
130
131 # Returns the user that matches provided login and password, or nil
131 # Returns the user that matches provided login and password, or nil
132 def self.try_to_login(login, password)
132 def self.try_to_login(login, password)
133 # Make sure no one can sign in with an empty password
133 # Make sure no one can sign in with an empty password
134 return nil if password.to_s.empty?
134 return nil if password.to_s.empty?
135 user = find_by_login(login)
135 user = find_by_login(login)
136 if user
136 if user
137 # user is already in local database
137 # user is already in local database
138 return nil if !user.active?
138 return nil if !user.active?
139 if user.auth_source
139 if user.auth_source
140 # user has an external authentication method
140 # user has an external authentication method
141 return nil unless user.auth_source.authenticate(login, password)
141 return nil unless user.auth_source.authenticate(login, password)
142 else
142 else
143 # authentication with local password
143 # authentication with local password
144 return nil unless user.check_password?(password)
144 return nil unless user.check_password?(password)
145 end
145 end
146 else
146 else
147 # user is not yet registered, try to authenticate with available sources
147 # user is not yet registered, try to authenticate with available sources
148 attrs = AuthSource.authenticate(login, password)
148 attrs = AuthSource.authenticate(login, password)
149 if attrs
149 if attrs
150 user = new(attrs)
150 user = new(attrs)
151 user.login = login
151 user.login = login
152 user.language = Setting.default_language
152 user.language = Setting.default_language
153 if user.save
153 if user.save
154 user.reload
154 user.reload
155 logger.info("User '#{user.login}' created from external auth source: #{user.auth_source.type} - #{user.auth_source.name}") if logger && user.auth_source
155 logger.info("User '#{user.login}' created from external auth source: #{user.auth_source.type} - #{user.auth_source.name}") if logger && user.auth_source
156 end
156 end
157 end
157 end
158 end
158 end
159 user.update_attribute(:last_login_on, Time.now) if user && !user.new_record?
159 user.update_attribute(:last_login_on, Time.now) if user && !user.new_record?
160 user
160 user
161 rescue => text
161 rescue => text
162 raise text
162 raise text
163 end
163 end
164
164
165 # Returns the user who matches the given autologin +key+ or nil
165 # Returns the user who matches the given autologin +key+ or nil
166 def self.try_to_autologin(key)
166 def self.try_to_autologin(key)
167 tokens = Token.find_all_by_action_and_value('autologin', key)
167 tokens = Token.find_all_by_action_and_value('autologin', key)
168 # Make sure there's only 1 token that matches the key
168 # Make sure there's only 1 token that matches the key
169 if tokens.size == 1
169 if tokens.size == 1
170 token = tokens.first
170 token = tokens.first
171 if (token.created_on > Setting.autologin.to_i.day.ago) && token.user && token.user.active?
171 if (token.created_on > Setting.autologin.to_i.day.ago) && token.user && token.user.active?
172 token.user.update_attribute(:last_login_on, Time.now)
172 token.user.update_attribute(:last_login_on, Time.now)
173 token.user
173 token.user
174 end
174 end
175 end
175 end
176 end
176 end
177
177
178 def self.name_formatter(formatter = nil)
178 def self.name_formatter(formatter = nil)
179 USER_FORMATS[formatter || Setting.user_format] || USER_FORMATS[:firstname_lastname]
179 USER_FORMATS[formatter || Setting.user_format] || USER_FORMATS[:firstname_lastname]
180 end
180 end
181
181
182 # Returns an array of fields names than can be used to make an order statement for users
182 # Returns an array of fields names than can be used to make an order statement for users
183 # according to how user names are displayed
183 # according to how user names are displayed
184 # Examples:
184 # Examples:
185 #
185 #
186 # User.fields_for_order_statement => ['users.login', 'users.id']
186 # User.fields_for_order_statement => ['users.login', 'users.id']
187 # User.fields_for_order_statement('authors') => ['authors.login', 'authors.id']
187 # User.fields_for_order_statement('authors') => ['authors.login', 'authors.id']
188 def self.fields_for_order_statement(table=nil)
188 def self.fields_for_order_statement(table=nil)
189 table ||= table_name
189 table ||= table_name
190 name_formatter[:order].map {|field| "#{table}.#{field}"}
190 name_formatter[:order].map {|field| "#{table}.#{field}"}
191 end
191 end
192
192
193 # Return user's full name for display
193 # Return user's full name for display
194 def name(formatter = nil)
194 def name(formatter = nil)
195 f = self.class.name_formatter(formatter)
195 f = self.class.name_formatter(formatter)
196 if formatter
196 if formatter
197 eval('"' + f[:string] + '"')
197 eval('"' + f[:string] + '"')
198 else
198 else
199 @name ||= eval('"' + f[:string] + '"')
199 @name ||= eval('"' + f[:string] + '"')
200 end
200 end
201 end
201 end
202
202
203 def active?
203 def active?
204 self.status == STATUS_ACTIVE
204 self.status == STATUS_ACTIVE
205 end
205 end
206
206
207 def registered?
207 def registered?
208 self.status == STATUS_REGISTERED
208 self.status == STATUS_REGISTERED
209 end
209 end
210
210
211 def locked?
211 def locked?
212 self.status == STATUS_LOCKED
212 self.status == STATUS_LOCKED
213 end
213 end
214
214
215 def activate
215 def activate
216 self.status = STATUS_ACTIVE
216 self.status = STATUS_ACTIVE
217 end
217 end
218
218
219 def register
219 def register
220 self.status = STATUS_REGISTERED
220 self.status = STATUS_REGISTERED
221 end
221 end
222
222
223 def lock
223 def lock
224 self.status = STATUS_LOCKED
224 self.status = STATUS_LOCKED
225 end
225 end
226
226
227 def activate!
227 def activate!
228 update_attribute(:status, STATUS_ACTIVE)
228 update_attribute(:status, STATUS_ACTIVE)
229 end
229 end
230
230
231 def register!
231 def register!
232 update_attribute(:status, STATUS_REGISTERED)
232 update_attribute(:status, STATUS_REGISTERED)
233 end
233 end
234
234
235 def lock!
235 def lock!
236 update_attribute(:status, STATUS_LOCKED)
236 update_attribute(:status, STATUS_LOCKED)
237 end
237 end
238
238
239 # Returns true if +clear_password+ is the correct user's password, otherwise false
239 # Returns true if +clear_password+ is the correct user's password, otherwise false
240 def check_password?(clear_password)
240 def check_password?(clear_password)
241 if auth_source_id.present?
241 if auth_source_id.present?
242 auth_source.authenticate(self.login, clear_password)
242 auth_source.authenticate(self.login, clear_password)
243 else
243 else
244 User.hash_password("#{salt}#{User.hash_password clear_password}") == hashed_password
244 User.hash_password("#{salt}#{User.hash_password clear_password}") == hashed_password
245 end
245 end
246 end
246 end
247
247
248 # Generates a random salt and computes hashed_password for +clear_password+
248 # Generates a random salt and computes hashed_password for +clear_password+
249 # The hashed password is stored in the following form: SHA1(salt + SHA1(password))
249 # The hashed password is stored in the following form: SHA1(salt + SHA1(password))
250 def salt_password(clear_password)
250 def salt_password(clear_password)
251 self.salt = User.generate_salt
251 self.salt = User.generate_salt
252 self.hashed_password = User.hash_password("#{salt}#{User.hash_password clear_password}")
252 self.hashed_password = User.hash_password("#{salt}#{User.hash_password clear_password}")
253 end
253 end
254
254
255 # Does the backend storage allow this user to change their password?
255 # Does the backend storage allow this user to change their password?
256 def change_password_allowed?
256 def change_password_allowed?
257 return true if auth_source.nil?
257 return true if auth_source.nil?
258 return auth_source.allow_password_changes?
258 return auth_source.allow_password_changes?
259 end
259 end
260
260
261 # Generate and set a random password. Useful for automated user creation
261 # Generate and set a random password. Useful for automated user creation
262 # Based on Token#generate_token_value
262 # Based on Token#generate_token_value
263 #
263 #
264 def random_password
264 def random_password
265 chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
265 chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
266 password = ''
266 password = ''
267 40.times { |i| password << chars[rand(chars.size-1)] }
267 40.times { |i| password << chars[rand(chars.size-1)] }
268 self.password = password
268 self.password = password
269 self.password_confirmation = password
269 self.password_confirmation = password
270 self
270 self
271 end
271 end
272
272
273 def pref
273 def pref
274 self.preference ||= UserPreference.new(:user => self)
274 self.preference ||= UserPreference.new(:user => self)
275 end
275 end
276
276
277 def time_zone
277 def time_zone
278 @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone])
278 @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone])
279 end
279 end
280
280
281 def wants_comments_in_reverse_order?
281 def wants_comments_in_reverse_order?
282 self.pref[:comments_sorting] == 'desc'
282 self.pref[:comments_sorting] == 'desc'
283 end
283 end
284
284
285 # Return user's RSS key (a 40 chars long string), used to access feeds
285 # Return user's RSS key (a 40 chars long string), used to access feeds
286 def rss_key
286 def rss_key
287 token = self.rss_token || Token.create(:user => self, :action => 'feeds')
287 token = self.rss_token || Token.create(:user => self, :action => 'feeds')
288 token.value
288 token.value
289 end
289 end
290
290
291 # Return user's API key (a 40 chars long string), used to access the API
291 # Return user's API key (a 40 chars long string), used to access the API
292 def api_key
292 def api_key
293 token = self.api_token || self.create_api_token(:action => 'api')
293 token = self.api_token || self.create_api_token(:action => 'api')
294 token.value
294 token.value
295 end
295 end
296
296
297 # Return an array of project ids for which the user has explicitly turned mail notifications on
297 # Return an array of project ids for which the user has explicitly turned mail notifications on
298 def notified_projects_ids
298 def notified_projects_ids
299 @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
299 @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
300 end
300 end
301
301
302 def notified_project_ids=(ids)
302 def notified_project_ids=(ids)
303 Member.update_all("mail_notification = #{connection.quoted_false}", ['user_id = ?', id])
303 Member.update_all("mail_notification = #{connection.quoted_false}", ['user_id = ?', id])
304 Member.update_all("mail_notification = #{connection.quoted_true}", ['user_id = ? AND project_id IN (?)', id, ids]) if ids && !ids.empty?
304 Member.update_all("mail_notification = #{connection.quoted_true}", ['user_id = ? AND project_id IN (?)', id, ids]) if ids && !ids.empty?
305 @notified_projects_ids = nil
305 @notified_projects_ids = nil
306 notified_projects_ids
306 notified_projects_ids
307 end
307 end
308
308
309 def valid_notification_options
309 def valid_notification_options
310 self.class.valid_notification_options(self)
310 self.class.valid_notification_options(self)
311 end
311 end
312
312
313 # Only users that belong to more than 1 project can select projects for which they are notified
313 # Only users that belong to more than 1 project can select projects for which they are notified
314 def self.valid_notification_options(user=nil)
314 def self.valid_notification_options(user=nil)
315 # Note that @user.membership.size would fail since AR ignores
315 # Note that @user.membership.size would fail since AR ignores
316 # :include association option when doing a count
316 # :include association option when doing a count
317 if user.nil? || user.memberships.length < 1
317 if user.nil? || user.memberships.length < 1
318 MAIL_NOTIFICATION_OPTIONS.reject {|option| option.first == 'selected'}
318 MAIL_NOTIFICATION_OPTIONS.reject {|option| option.first == 'selected'}
319 else
319 else
320 MAIL_NOTIFICATION_OPTIONS
320 MAIL_NOTIFICATION_OPTIONS
321 end
321 end
322 end
322 end
323
323
324 # Find a user account by matching the exact login and then a case-insensitive
324 # Find a user account by matching the exact login and then a case-insensitive
325 # version. Exact matches will be given priority.
325 # version. Exact matches will be given priority.
326 def self.find_by_login(login)
326 def self.find_by_login(login)
327 # First look for an exact match
327 # First look for an exact match
328 user = all(:conditions => {:login => login}).detect {|u| u.login == login}
328 user = all(:conditions => {:login => login}).detect {|u| u.login == login}
329 unless user
329 unless user
330 # Fail over to case-insensitive if none was found
330 # Fail over to case-insensitive if none was found
331 user = first(:conditions => ["LOWER(login) = ?", login.to_s.downcase])
331 user = first(:conditions => ["LOWER(login) = ?", login.to_s.downcase])
332 end
332 end
333 user
333 user
334 end
334 end
335
335
336 def self.find_by_rss_key(key)
336 def self.find_by_rss_key(key)
337 token = Token.find_by_value(key)
337 token = Token.find_by_value(key)
338 token && token.user.active? ? token.user : nil
338 token && token.user.active? ? token.user : nil
339 end
339 end
340
340
341 def self.find_by_api_key(key)
341 def self.find_by_api_key(key)
342 token = Token.find_by_action_and_value('api', key)
342 token = Token.find_by_action_and_value('api', key)
343 token && token.user.active? ? token.user : nil
343 token && token.user.active? ? token.user : nil
344 end
344 end
345
345
346 # Makes find_by_mail case-insensitive
346 # Makes find_by_mail case-insensitive
347 def self.find_by_mail(mail)
347 def self.find_by_mail(mail)
348 find(:first, :conditions => ["LOWER(mail) = ?", mail.to_s.downcase])
348 find(:first, :conditions => ["LOWER(mail) = ?", mail.to_s.downcase])
349 end
349 end
350
350
351 # Returns true if the default admin account can no longer be used
351 # Returns true if the default admin account can no longer be used
352 def self.default_admin_account_changed?
352 def self.default_admin_account_changed?
353 !User.active.find_by_login("admin").try(:check_password?, "admin")
353 !User.active.find_by_login("admin").try(:check_password?, "admin")
354 end
354 end
355
355
356 def to_s
356 def to_s
357 name
357 name
358 end
358 end
359
359
360 # Returns the current day according to user's time zone
360 # Returns the current day according to user's time zone
361 def today
361 def today
362 if time_zone.nil?
362 if time_zone.nil?
363 Date.today
363 Date.today
364 else
364 else
365 Time.now.in_time_zone(time_zone).to_date
365 Time.now.in_time_zone(time_zone).to_date
366 end
366 end
367 end
367 end
368
368
369 def logged?
369 def logged?
370 true
370 true
371 end
371 end
372
372
373 def anonymous?
373 def anonymous?
374 !logged?
374 !logged?
375 end
375 end
376
376
377 # Return user's roles for project
377 # Return user's roles for project
378 def roles_for_project(project)
378 def roles_for_project(project)
379 roles = []
379 roles = []
380 # No role on archived projects
380 # No role on archived projects
381 return roles unless project && project.active?
381 return roles unless project && project.active?
382 if logged?
382 if logged?
383 # Find project membership
383 # Find project membership
384 membership = memberships.detect {|m| m.project_id == project.id}
384 membership = memberships.detect {|m| m.project_id == project.id}
385 if membership
385 if membership
386 roles = membership.roles
386 roles = membership.roles
387 else
387 else
388 @role_non_member ||= Role.non_member
388 @role_non_member ||= Role.non_member
389 roles << @role_non_member
389 roles << @role_non_member
390 end
390 end
391 else
391 else
392 @role_anonymous ||= Role.anonymous
392 @role_anonymous ||= Role.anonymous
393 roles << @role_anonymous
393 roles << @role_anonymous
394 end
394 end
395 roles
395 roles
396 end
396 end
397
397
398 # Return true if the user is a member of project
398 # Return true if the user is a member of project
399 def member_of?(project)
399 def member_of?(project)
400 !roles_for_project(project).detect {|role| role.member?}.nil?
400 !roles_for_project(project).detect {|role| role.member?}.nil?
401 end
401 end
402
402
403 # Returns a hash of user's projects grouped by roles
403 # Returns a hash of user's projects grouped by roles
404 def projects_by_role
404 def projects_by_role
405 return @projects_by_role if @projects_by_role
405 return @projects_by_role if @projects_by_role
406
406
407 @projects_by_role = Hash.new {|h,k| h[k]=[]}
407 @projects_by_role = Hash.new {|h,k| h[k]=[]}
408 memberships.each do |membership|
408 memberships.each do |membership|
409 membership.roles.each do |role|
409 membership.roles.each do |role|
410 @projects_by_role[role] << membership.project if membership.project
410 @projects_by_role[role] << membership.project if membership.project
411 end
411 end
412 end
412 end
413 @projects_by_role.each do |role, projects|
413 @projects_by_role.each do |role, projects|
414 projects.uniq!
414 projects.uniq!
415 end
415 end
416
416
417 @projects_by_role
417 @projects_by_role
418 end
418 end
419
419
420 # Returns true if user is arg or belongs to arg
420 # Returns true if user is arg or belongs to arg
421 def is_or_belongs_to?(arg)
421 def is_or_belongs_to?(arg)
422 if arg.is_a?(User)
422 if arg.is_a?(User)
423 self == arg
423 self == arg
424 elsif arg.is_a?(Group)
424 elsif arg.is_a?(Group)
425 arg.users.include?(self)
425 arg.users.include?(self)
426 else
426 else
427 false
427 false
428 end
428 end
429 end
429 end
430
430
431 # Return true if the user is allowed to do the specified action on a specific context
431 # Return true if the user is allowed to do the specified action on a specific context
432 # Action can be:
432 # Action can be:
433 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
433 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
434 # * a permission Symbol (eg. :edit_project)
434 # * a permission Symbol (eg. :edit_project)
435 # Context can be:
435 # Context can be:
436 # * a project : returns true if user is allowed to do the specified action on this project
436 # * a project : returns true if user is allowed to do the specified action on this project
437 # * an array of projects : returns true if user is allowed on every project
437 # * an array of projects : returns true if user is allowed on every project
438 # * nil with options[:global] set : check if user has at least one role allowed for this action,
438 # * nil with options[:global] set : check if user has at least one role allowed for this action,
439 # or falls back to Non Member / Anonymous permissions depending if the user is logged
439 # or falls back to Non Member / Anonymous permissions depending if the user is logged
440 def allowed_to?(action, context, options={}, &block)
440 def allowed_to?(action, context, options={}, &block)
441 if context && context.is_a?(Project)
441 if context && context.is_a?(Project)
442 # No action allowed on archived projects
442 # No action allowed on archived projects
443 return false unless context.active?
443 return false unless context.active?
444 # No action allowed on disabled modules
444 # No action allowed on disabled modules
445 return false unless context.allows_to?(action)
445 return false unless context.allows_to?(action)
446 # Admin users are authorized for anything else
446 # Admin users are authorized for anything else
447 return true if admin?
447 return true if admin?
448
448
449 roles = roles_for_project(context)
449 roles = roles_for_project(context)
450 return false unless roles
450 return false unless roles
451 roles.detect {|role|
451 roles.detect {|role|
452 (context.is_public? || role.member?) &&
452 (context.is_public? || role.member?) &&
453 role.allowed_to?(action) &&
453 role.allowed_to?(action) &&
454 (block_given? ? yield(role, self) : true)
454 (block_given? ? yield(role, self) : true)
455 }
455 }
456 elsif context && context.is_a?(Array)
456 elsif context && context.is_a?(Array)
457 # Authorize if user is authorized on every element of the array
457 # Authorize if user is authorized on every element of the array
458 context.map do |project|
458 context.map do |project|
459 allowed_to?(action, project, options, &block)
459 allowed_to?(action, project, options, &block)
460 end.inject do |memo,allowed|
460 end.inject do |memo,allowed|
461 memo && allowed
461 memo && allowed
462 end
462 end
463 elsif options[:global]
463 elsif options[:global]
464 # Admin users are always authorized
464 # Admin users are always authorized
465 return true if admin?
465 return true if admin?
466
466
467 # authorize if user has at least one role that has this permission
467 # authorize if user has at least one role that has this permission
468 roles = memberships.collect {|m| m.roles}.flatten.uniq
468 roles = memberships.collect {|m| m.roles}.flatten.uniq
469 roles << (self.logged? ? Role.non_member : Role.anonymous)
469 roles << (self.logged? ? Role.non_member : Role.anonymous)
470 roles.detect {|role|
470 roles.detect {|role|
471 role.allowed_to?(action) &&
471 role.allowed_to?(action) &&
472 (block_given? ? yield(role, self) : true)
472 (block_given? ? yield(role, self) : true)
473 }
473 }
474 else
474 else
475 false
475 false
476 end
476 end
477 end
477 end
478
478
479 # Is the user allowed to do the specified action on any project?
479 # Is the user allowed to do the specified action on any project?
480 # See allowed_to? for the actions and valid options.
480 # See allowed_to? for the actions and valid options.
481 def allowed_to_globally?(action, options, &block)
481 def allowed_to_globally?(action, options, &block)
482 allowed_to?(action, nil, options.reverse_merge(:global => true), &block)
482 allowed_to?(action, nil, options.reverse_merge(:global => true), &block)
483 end
483 end
484
484
485 # Returns true if the user is allowed to delete his own account
486 def own_account_deletable?
487 Setting.unsubscribe? &&
488 (!admin? || User.active.first(:conditions => ["admin = ? AND id <> ?", true, id]).present?)
489 end
490
485 safe_attributes 'login',
491 safe_attributes 'login',
486 'firstname',
492 'firstname',
487 'lastname',
493 'lastname',
488 'mail',
494 'mail',
489 'mail_notification',
495 'mail_notification',
490 'language',
496 'language',
491 'custom_field_values',
497 'custom_field_values',
492 'custom_fields',
498 'custom_fields',
493 'identity_url'
499 'identity_url'
494
500
495 safe_attributes 'status',
501 safe_attributes 'status',
496 'auth_source_id',
502 'auth_source_id',
497 :if => lambda {|user, current_user| current_user.admin?}
503 :if => lambda {|user, current_user| current_user.admin?}
498
504
499 safe_attributes 'group_ids',
505 safe_attributes 'group_ids',
500 :if => lambda {|user, current_user| current_user.admin? && !user.new_record?}
506 :if => lambda {|user, current_user| current_user.admin? && !user.new_record?}
501
507
502 # Utility method to help check if a user should be notified about an
508 # Utility method to help check if a user should be notified about an
503 # event.
509 # event.
504 #
510 #
505 # TODO: only supports Issue events currently
511 # TODO: only supports Issue events currently
506 def notify_about?(object)
512 def notify_about?(object)
507 case mail_notification
513 case mail_notification
508 when 'all'
514 when 'all'
509 true
515 true
510 when 'selected'
516 when 'selected'
511 # user receives notifications for created/assigned issues on unselected projects
517 # user receives notifications for created/assigned issues on unselected projects
512 if object.is_a?(Issue) && (object.author == self || is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was))
518 if object.is_a?(Issue) && (object.author == self || is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was))
513 true
519 true
514 else
520 else
515 false
521 false
516 end
522 end
517 when 'none'
523 when 'none'
518 false
524 false
519 when 'only_my_events'
525 when 'only_my_events'
520 if object.is_a?(Issue) && (object.author == self || is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was))
526 if object.is_a?(Issue) && (object.author == self || is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was))
521 true
527 true
522 else
528 else
523 false
529 false
524 end
530 end
525 when 'only_assigned'
531 when 'only_assigned'
526 if object.is_a?(Issue) && (is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was))
532 if object.is_a?(Issue) && (is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was))
527 true
533 true
528 else
534 else
529 false
535 false
530 end
536 end
531 when 'only_owner'
537 when 'only_owner'
532 if object.is_a?(Issue) && object.author == self
538 if object.is_a?(Issue) && object.author == self
533 true
539 true
534 else
540 else
535 false
541 false
536 end
542 end
537 else
543 else
538 false
544 false
539 end
545 end
540 end
546 end
541
547
542 def self.current=(user)
548 def self.current=(user)
543 @current_user = user
549 @current_user = user
544 end
550 end
545
551
546 def self.current
552 def self.current
547 @current_user ||= User.anonymous
553 @current_user ||= User.anonymous
548 end
554 end
549
555
550 # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
556 # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
551 # one anonymous user per database.
557 # one anonymous user per database.
552 def self.anonymous
558 def self.anonymous
553 anonymous_user = AnonymousUser.find(:first)
559 anonymous_user = AnonymousUser.find(:first)
554 if anonymous_user.nil?
560 if anonymous_user.nil?
555 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0)
561 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0)
556 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
562 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
557 end
563 end
558 anonymous_user
564 anonymous_user
559 end
565 end
560
566
561 # Salts all existing unsalted passwords
567 # Salts all existing unsalted passwords
562 # It changes password storage scheme from SHA1(password) to SHA1(salt + SHA1(password))
568 # It changes password storage scheme from SHA1(password) to SHA1(salt + SHA1(password))
563 # This method is used in the SaltPasswords migration and is to be kept as is
569 # This method is used in the SaltPasswords migration and is to be kept as is
564 def self.salt_unsalted_passwords!
570 def self.salt_unsalted_passwords!
565 transaction do
571 transaction do
566 User.find_each(:conditions => "salt IS NULL OR salt = ''") do |user|
572 User.find_each(:conditions => "salt IS NULL OR salt = ''") do |user|
567 next if user.hashed_password.blank?
573 next if user.hashed_password.blank?
568 salt = User.generate_salt
574 salt = User.generate_salt
569 hashed_password = User.hash_password("#{salt}#{user.hashed_password}")
575 hashed_password = User.hash_password("#{salt}#{user.hashed_password}")
570 User.update_all("salt = '#{salt}', hashed_password = '#{hashed_password}'", ["id = ?", user.id] )
576 User.update_all("salt = '#{salt}', hashed_password = '#{hashed_password}'", ["id = ?", user.id] )
571 end
577 end
572 end
578 end
573 end
579 end
574
580
575 protected
581 protected
576
582
577 def validate_password_length
583 def validate_password_length
578 # Password length validation based on setting
584 # Password length validation based on setting
579 if !password.nil? && password.size < Setting.password_min_length.to_i
585 if !password.nil? && password.size < Setting.password_min_length.to_i
580 errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
586 errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
581 end
587 end
582 end
588 end
583
589
584 private
590 private
585
591
586 # Removes references that are not handled by associations
592 # Removes references that are not handled by associations
587 # Things that are not deleted are reassociated with the anonymous user
593 # Things that are not deleted are reassociated with the anonymous user
588 def remove_references_before_destroy
594 def remove_references_before_destroy
589 return if self.id.nil?
595 return if self.id.nil?
590
596
591 substitute = User.anonymous
597 substitute = User.anonymous
592 Attachment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
598 Attachment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
593 Comment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
599 Comment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
594 Issue.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
600 Issue.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
595 Issue.update_all 'assigned_to_id = NULL', ['assigned_to_id = ?', id]
601 Issue.update_all 'assigned_to_id = NULL', ['assigned_to_id = ?', id]
596 Journal.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
602 Journal.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
597 JournalDetail.update_all ['old_value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND old_value = ?", id.to_s]
603 JournalDetail.update_all ['old_value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND old_value = ?", id.to_s]
598 JournalDetail.update_all ['value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND value = ?", id.to_s]
604 JournalDetail.update_all ['value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND value = ?", id.to_s]
599 Message.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
605 Message.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
600 News.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
606 News.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
601 # Remove private queries and keep public ones
607 # Remove private queries and keep public ones
602 ::Query.delete_all ['user_id = ? AND is_public = ?', id, false]
608 ::Query.delete_all ['user_id = ? AND is_public = ?', id, false]
603 ::Query.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
609 ::Query.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
604 TimeEntry.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
610 TimeEntry.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
605 Token.delete_all ['user_id = ?', id]
611 Token.delete_all ['user_id = ?', id]
606 Watcher.delete_all ['user_id = ?', id]
612 Watcher.delete_all ['user_id = ?', id]
607 WikiContent.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
613 WikiContent.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
608 WikiContent::Version.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
614 WikiContent::Version.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
609 end
615 end
610
616
611 # Return password digest
617 # Return password digest
612 def self.hash_password(clear_password)
618 def self.hash_password(clear_password)
613 Digest::SHA1.hexdigest(clear_password || "")
619 Digest::SHA1.hexdigest(clear_password || "")
614 end
620 end
615
621
616 # Returns a 128bits random salt as a hex string (32 chars long)
622 # Returns a 128bits random salt as a hex string (32 chars long)
617 def self.generate_salt
623 def self.generate_salt
618 Redmine::Utils.random_hex(16)
624 Redmine::Utils.random_hex(16)
619 end
625 end
620
626
621 end
627 end
622
628
623 class AnonymousUser < User
629 class AnonymousUser < User
624 validate :validate_anonymous_uniqueness, :on => :create
630 validate :validate_anonymous_uniqueness, :on => :create
625
631
626 def validate_anonymous_uniqueness
632 def validate_anonymous_uniqueness
627 # There should be only one AnonymousUser in the database
633 # There should be only one AnonymousUser in the database
628 errors.add :base, 'An anonymous user already exists.' if AnonymousUser.find(:first)
634 errors.add :base, 'An anonymous user already exists.' if AnonymousUser.find(:first)
629 end
635 end
630
636
631 def available_custom_fields
637 def available_custom_fields
632 []
638 []
633 end
639 end
634
640
635 # Overrides a few properties
641 # Overrides a few properties
636 def logged?; false end
642 def logged?; false end
637 def admin; false end
643 def admin; false end
638 def name(*args); I18n.t(:label_user_anonymous) end
644 def name(*args); I18n.t(:label_user_anonymous) end
639 def mail; nil end
645 def mail; nil end
640 def time_zone; nil end
646 def time_zone; nil end
641 def rss_key; nil end
647 def rss_key; nil end
642
648
643 # Anonymous user can not be destroyed
649 # Anonymous user can not be destroyed
644 def destroy
650 def destroy
645 false
651 false
646 end
652 end
647 end
653 end
@@ -1,33 +1,36
1 <h3><%=l(:label_my_account)%></h3>
1 <h3><%=l(:label_my_account)%></h3>
2
2
3 <p><%=l(:field_login)%>: <strong><%= link_to_user(@user, :format => :username) %></strong><br />
3 <p><%=l(:field_login)%>: <strong><%= link_to_user(@user, :format => :username) %></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
5
6 <% if @user.own_account_deletable? %>
7 <p><%= link_to(l(:button_delete_my_account), {:action => 'destroy'}, :class => 'icon icon-del') %></p>
8 <% end %>
6
9
7 <h4><%= l(:label_feeds_access_key) %></h4>
10 <h4><%= l(:label_feeds_access_key) %></h4>
8
11
9 <p>
12 <p>
10 <% if @user.rss_token %>
13 <% if @user.rss_token %>
11 <%= l(:label_feeds_access_key_created_on, distance_of_time_in_words(Time.now, @user.rss_token.created_on)) %>
14 <%= l(:label_feeds_access_key_created_on, distance_of_time_in_words(Time.now, @user.rss_token.created_on)) %>
12 <% else %>
15 <% else %>
13 <%= l(:label_missing_feeds_access_key) %>
16 <%= l(:label_missing_feeds_access_key) %>
14 <% end %>
17 <% end %>
15 (<%= link_to l(:button_reset), {:action => 'reset_rss_key'}, :method => :post %>)
18 (<%= link_to l(:button_reset), {:action => 'reset_rss_key'}, :method => :post %>)
16 </p>
19 </p>
17
20
18 <% if Setting.rest_api_enabled? %>
21 <% if Setting.rest_api_enabled? %>
19 <h4><%= l(:label_api_access_key) %></h4>
22 <h4><%= l(:label_api_access_key) %></h4>
20 <div>
23 <div>
21 <%= link_to_function(l(:button_show), "$('api-access-key').toggle();")%>
24 <%= link_to_function(l(:button_show), "$('api-access-key').toggle();")%>
22 <pre id='api-access-key' class='autoscroll'><%= h(@user.api_key) %></pre>
25 <pre id='api-access-key' class='autoscroll'><%= h(@user.api_key) %></pre>
23 </div>
26 </div>
24 <%= javascript_tag("$('api-access-key').hide();") %>
27 <%= javascript_tag("$('api-access-key').hide();") %>
25 <p>
28 <p>
26 <% if @user.api_token %>
29 <% if @user.api_token %>
27 <%= l(:label_api_access_key_created_on, distance_of_time_in_words(Time.now, @user.api_token.created_on)) %>
30 <%= l(:label_api_access_key_created_on, distance_of_time_in_words(Time.now, @user.api_token.created_on)) %>
28 <% else %>
31 <% else %>
29 <%= l(:label_missing_api_access_key) %>
32 <%= l(:label_missing_api_access_key) %>
30 <% end %>
33 <% end %>
31 (<%= link_to l(:button_reset), {:action => 'reset_api_key'}, :method => :post %>)
34 (<%= link_to l(:button_reset), {:action => 'reset_api_key'}, :method => :post %>)
32 </p>
35 </p>
33 <% end %>
36 <% end %>
@@ -1,23 +1,25
1 <% form_tag({:action => 'edit', :tab => 'authentication'}) do %>
1 <% form_tag({:action => 'edit', :tab => 'authentication'}) do %>
2
2
3 <div class="box tabular settings">
3 <div class="box tabular settings">
4 <p><%= setting_check_box :login_required %></p>
4 <p><%= setting_check_box :login_required %></p>
5
5
6 <p><%= setting_select :autologin, [[l(:label_disabled), 0]] + [1, 7, 30, 365].collect{|days| [l('datetime.distance_in_words.x_days', :count => days), days.to_s]} %></p>
6 <p><%= setting_select :autologin, [[l(:label_disabled), 0]] + [1, 7, 30, 365].collect{|days| [l('datetime.distance_in_words.x_days', :count => days), days.to_s]} %></p>
7
7
8 <p><%= setting_select :self_registration, [[l(:label_disabled), "0"],
8 <p><%= setting_select :self_registration, [[l(:label_disabled), "0"],
9 [l(:label_registration_activation_by_email), "1"],
9 [l(:label_registration_activation_by_email), "1"],
10 [l(:label_registration_manual_activation), "2"],
10 [l(:label_registration_manual_activation), "2"],
11 [l(:label_registration_automatic_activation), "3"]] %></p>
11 [l(:label_registration_automatic_activation), "3"]] %></p>
12
12
13 <p><%= setting_check_box :unsubscribe %></p>
14
13 <p><%= setting_text_field :password_min_length, :size => 6 %></p>
15 <p><%= setting_text_field :password_min_length, :size => 6 %></p>
14
16
15 <p><%= setting_check_box :lost_password, :label => :label_password_lost %></p>
17 <p><%= setting_check_box :lost_password, :label => :label_password_lost %></p>
16
18
17 <p><%= setting_check_box :openid, :disabled => !Object.const_defined?(:OpenID) %></p>
19 <p><%= setting_check_box :openid, :disabled => !Object.const_defined?(:OpenID) %></p>
18
20
19 <p><%= setting_check_box :rest_api_enabled %></p>
21 <p><%= setting_check_box :rest_api_enabled %></p>
20 </div>
22 </div>
21
23
22 <%= submit_tag l(:button_save) %>
24 <%= submit_tag l(:button_save) %>
23 <% end %>
25 <% end %>
@@ -1,1025 +1,1029
1 en:
1 en:
2 # Text direction: Left-to-Right (ltr) or Right-to-Left (rtl)
2 # Text direction: Left-to-Right (ltr) or Right-to-Left (rtl)
3 direction: ltr
3 direction: ltr
4 date:
4 date:
5 formats:
5 formats:
6 # Use the strftime parameters for formats.
6 # Use the strftime parameters for formats.
7 # When no format has been given, it uses default.
7 # When no format has been given, it uses default.
8 # You can provide other formats here if you like!
8 # You can provide other formats here if you like!
9 default: "%m/%d/%Y"
9 default: "%m/%d/%Y"
10 short: "%b %d"
10 short: "%b %d"
11 long: "%B %d, %Y"
11 long: "%B %d, %Y"
12
12
13 day_names: [Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday]
13 day_names: [Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday]
14 abbr_day_names: [Sun, Mon, Tue, Wed, Thu, Fri, Sat]
14 abbr_day_names: [Sun, Mon, Tue, Wed, Thu, Fri, Sat]
15
15
16 # Don't forget the nil at the beginning; there's no such thing as a 0th month
16 # Don't forget the nil at the beginning; there's no such thing as a 0th month
17 month_names: [~, January, February, March, April, May, June, July, August, September, October, November, December]
17 month_names: [~, January, February, March, April, May, June, July, August, September, October, November, December]
18 abbr_month_names: [~, Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec]
18 abbr_month_names: [~, Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec]
19 # Used in date_select and datime_select.
19 # Used in date_select and datime_select.
20 order:
20 order:
21 - :year
21 - :year
22 - :month
22 - :month
23 - :day
23 - :day
24
24
25 time:
25 time:
26 formats:
26 formats:
27 default: "%m/%d/%Y %I:%M %p"
27 default: "%m/%d/%Y %I:%M %p"
28 time: "%I:%M %p"
28 time: "%I:%M %p"
29 short: "%d %b %H:%M"
29 short: "%d %b %H:%M"
30 long: "%B %d, %Y %H:%M"
30 long: "%B %d, %Y %H:%M"
31 am: "am"
31 am: "am"
32 pm: "pm"
32 pm: "pm"
33
33
34 datetime:
34 datetime:
35 distance_in_words:
35 distance_in_words:
36 half_a_minute: "half a minute"
36 half_a_minute: "half a minute"
37 less_than_x_seconds:
37 less_than_x_seconds:
38 one: "less than 1 second"
38 one: "less than 1 second"
39 other: "less than %{count} seconds"
39 other: "less than %{count} seconds"
40 x_seconds:
40 x_seconds:
41 one: "1 second"
41 one: "1 second"
42 other: "%{count} seconds"
42 other: "%{count} seconds"
43 less_than_x_minutes:
43 less_than_x_minutes:
44 one: "less than a minute"
44 one: "less than a minute"
45 other: "less than %{count} minutes"
45 other: "less than %{count} minutes"
46 x_minutes:
46 x_minutes:
47 one: "1 minute"
47 one: "1 minute"
48 other: "%{count} minutes"
48 other: "%{count} minutes"
49 about_x_hours:
49 about_x_hours:
50 one: "about 1 hour"
50 one: "about 1 hour"
51 other: "about %{count} hours"
51 other: "about %{count} hours"
52 x_days:
52 x_days:
53 one: "1 day"
53 one: "1 day"
54 other: "%{count} days"
54 other: "%{count} days"
55 about_x_months:
55 about_x_months:
56 one: "about 1 month"
56 one: "about 1 month"
57 other: "about %{count} months"
57 other: "about %{count} months"
58 x_months:
58 x_months:
59 one: "1 month"
59 one: "1 month"
60 other: "%{count} months"
60 other: "%{count} months"
61 about_x_years:
61 about_x_years:
62 one: "about 1 year"
62 one: "about 1 year"
63 other: "about %{count} years"
63 other: "about %{count} years"
64 over_x_years:
64 over_x_years:
65 one: "over 1 year"
65 one: "over 1 year"
66 other: "over %{count} years"
66 other: "over %{count} years"
67 almost_x_years:
67 almost_x_years:
68 one: "almost 1 year"
68 one: "almost 1 year"
69 other: "almost %{count} years"
69 other: "almost %{count} years"
70
70
71 number:
71 number:
72 format:
72 format:
73 separator: "."
73 separator: "."
74 delimiter: ""
74 delimiter: ""
75 precision: 3
75 precision: 3
76
76
77 human:
77 human:
78 format:
78 format:
79 delimiter: ""
79 delimiter: ""
80 precision: 1
80 precision: 1
81 storage_units:
81 storage_units:
82 format: "%n %u"
82 format: "%n %u"
83 units:
83 units:
84 byte:
84 byte:
85 one: "Byte"
85 one: "Byte"
86 other: "Bytes"
86 other: "Bytes"
87 kb: "kB"
87 kb: "kB"
88 mb: "MB"
88 mb: "MB"
89 gb: "GB"
89 gb: "GB"
90 tb: "TB"
90 tb: "TB"
91
91
92 # Used in array.to_sentence.
92 # Used in array.to_sentence.
93 support:
93 support:
94 array:
94 array:
95 sentence_connector: "and"
95 sentence_connector: "and"
96 skip_last_comma: false
96 skip_last_comma: false
97
97
98 activerecord:
98 activerecord:
99 errors:
99 errors:
100 template:
100 template:
101 header:
101 header:
102 one: "1 error prohibited this %{model} from being saved"
102 one: "1 error prohibited this %{model} from being saved"
103 other: "%{count} errors prohibited this %{model} from being saved"
103 other: "%{count} errors prohibited this %{model} from being saved"
104 messages:
104 messages:
105 inclusion: "is not included in the list"
105 inclusion: "is not included in the list"
106 exclusion: "is reserved"
106 exclusion: "is reserved"
107 invalid: "is invalid"
107 invalid: "is invalid"
108 confirmation: "doesn't match confirmation"
108 confirmation: "doesn't match confirmation"
109 accepted: "must be accepted"
109 accepted: "must be accepted"
110 empty: "can't be empty"
110 empty: "can't be empty"
111 blank: "can't be blank"
111 blank: "can't be blank"
112 too_long: "is too long (maximum is %{count} characters)"
112 too_long: "is too long (maximum is %{count} characters)"
113 too_short: "is too short (minimum is %{count} characters)"
113 too_short: "is too short (minimum is %{count} characters)"
114 wrong_length: "is the wrong length (should be %{count} characters)"
114 wrong_length: "is the wrong length (should be %{count} characters)"
115 taken: "has already been taken"
115 taken: "has already been taken"
116 not_a_number: "is not a number"
116 not_a_number: "is not a number"
117 not_a_date: "is not a valid date"
117 not_a_date: "is not a valid date"
118 greater_than: "must be greater than %{count}"
118 greater_than: "must be greater than %{count}"
119 greater_than_or_equal_to: "must be greater than or equal to %{count}"
119 greater_than_or_equal_to: "must be greater than or equal to %{count}"
120 equal_to: "must be equal to %{count}"
120 equal_to: "must be equal to %{count}"
121 less_than: "must be less than %{count}"
121 less_than: "must be less than %{count}"
122 less_than_or_equal_to: "must be less than or equal to %{count}"
122 less_than_or_equal_to: "must be less than or equal to %{count}"
123 odd: "must be odd"
123 odd: "must be odd"
124 even: "must be even"
124 even: "must be even"
125 greater_than_start_date: "must be greater than start date"
125 greater_than_start_date: "must be greater than start date"
126 not_same_project: "doesn't belong to the same project"
126 not_same_project: "doesn't belong to the same project"
127 circular_dependency: "This relation would create a circular dependency"
127 circular_dependency: "This relation would create a circular dependency"
128 cant_link_an_issue_with_a_descendant: "An issue cannot be linked to one of its subtasks"
128 cant_link_an_issue_with_a_descendant: "An issue cannot be linked to one of its subtasks"
129
129
130 actionview_instancetag_blank_option: Please select
130 actionview_instancetag_blank_option: Please select
131
131
132 general_text_No: 'No'
132 general_text_No: 'No'
133 general_text_Yes: 'Yes'
133 general_text_Yes: 'Yes'
134 general_text_no: 'no'
134 general_text_no: 'no'
135 general_text_yes: 'yes'
135 general_text_yes: 'yes'
136 general_lang_name: 'English'
136 general_lang_name: 'English'
137 general_csv_separator: ','
137 general_csv_separator: ','
138 general_csv_decimal_separator: '.'
138 general_csv_decimal_separator: '.'
139 general_csv_encoding: ISO-8859-1
139 general_csv_encoding: ISO-8859-1
140 general_pdf_encoding: UTF-8
140 general_pdf_encoding: UTF-8
141 general_first_day_of_week: '7'
141 general_first_day_of_week: '7'
142
142
143 notice_account_updated: Account was successfully updated.
143 notice_account_updated: Account was successfully updated.
144 notice_account_invalid_creditentials: Invalid user or password
144 notice_account_invalid_creditentials: Invalid user or password
145 notice_account_password_updated: Password was successfully updated.
145 notice_account_password_updated: Password was successfully updated.
146 notice_account_wrong_password: Wrong password
146 notice_account_wrong_password: Wrong password
147 notice_account_register_done: Account was successfully created. To activate your account, click on the link that was emailed to you.
147 notice_account_register_done: Account was successfully created. To activate your account, click on the link that was emailed to you.
148 notice_account_unknown_email: Unknown user.
148 notice_account_unknown_email: Unknown user.
149 notice_can_t_change_password: This account uses an external authentication source. Impossible to change the password.
149 notice_can_t_change_password: This account uses an external authentication source. Impossible to change the password.
150 notice_account_lost_email_sent: An email with instructions to choose a new password has been sent to you.
150 notice_account_lost_email_sent: An email with instructions to choose a new password has been sent to you.
151 notice_account_activated: Your account has been activated. You can now log in.
151 notice_account_activated: Your account has been activated. You can now log in.
152 notice_successful_create: Successful creation.
152 notice_successful_create: Successful creation.
153 notice_successful_update: Successful update.
153 notice_successful_update: Successful update.
154 notice_successful_delete: Successful deletion.
154 notice_successful_delete: Successful deletion.
155 notice_successful_connection: Successful connection.
155 notice_successful_connection: Successful connection.
156 notice_file_not_found: The page you were trying to access doesn't exist or has been removed.
156 notice_file_not_found: The page you were trying to access doesn't exist or has been removed.
157 notice_locking_conflict: Data has been updated by another user.
157 notice_locking_conflict: Data has been updated by another user.
158 notice_not_authorized: You are not authorized to access this page.
158 notice_not_authorized: You are not authorized to access this page.
159 notice_not_authorized_archived_project: The project you're trying to access has been archived.
159 notice_not_authorized_archived_project: The project you're trying to access has been archived.
160 notice_email_sent: "An email was sent to %{value}"
160 notice_email_sent: "An email was sent to %{value}"
161 notice_email_error: "An error occurred while sending mail (%{value})"
161 notice_email_error: "An error occurred while sending mail (%{value})"
162 notice_feeds_access_key_reseted: Your RSS access key was reset.
162 notice_feeds_access_key_reseted: Your RSS access key was reset.
163 notice_api_access_key_reseted: Your API access key was reset.
163 notice_api_access_key_reseted: Your API access key was reset.
164 notice_failed_to_save_issues: "Failed to save %{count} issue(s) on %{total} selected: %{ids}."
164 notice_failed_to_save_issues: "Failed to save %{count} issue(s) on %{total} selected: %{ids}."
165 notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}."
165 notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}."
166 notice_failed_to_save_members: "Failed to save member(s): %{errors}."
166 notice_failed_to_save_members: "Failed to save member(s): %{errors}."
167 notice_no_issue_selected: "No issue is selected! Please, check the issues you want to edit."
167 notice_no_issue_selected: "No issue is selected! Please, check the issues you want to edit."
168 notice_account_pending: "Your account was created and is now pending administrator approval."
168 notice_account_pending: "Your account was created and is now pending administrator approval."
169 notice_default_data_loaded: Default configuration successfully loaded.
169 notice_default_data_loaded: Default configuration successfully loaded.
170 notice_unable_delete_version: Unable to delete version.
170 notice_unable_delete_version: Unable to delete version.
171 notice_unable_delete_time_entry: Unable to delete time log entry.
171 notice_unable_delete_time_entry: Unable to delete time log entry.
172 notice_issue_done_ratios_updated: Issue done ratios updated.
172 notice_issue_done_ratios_updated: Issue done ratios updated.
173 notice_gantt_chart_truncated: "The chart was truncated because it exceeds the maximum number of items that can be displayed (%{max})"
173 notice_gantt_chart_truncated: "The chart was truncated because it exceeds the maximum number of items that can be displayed (%{max})"
174 notice_issue_successful_create: "Issue %{id} created."
174 notice_issue_successful_create: "Issue %{id} created."
175 notice_issue_update_conflict: "The issue has been updated by an other user while you were editing it."
175 notice_issue_update_conflict: "The issue has been updated by an other user while you were editing it."
176 notice_account_deleted: "Your account has been permanently deleted."
176
177
177 error_can_t_load_default_data: "Default configuration could not be loaded: %{value}"
178 error_can_t_load_default_data: "Default configuration could not be loaded: %{value}"
178 error_scm_not_found: "The entry or revision was not found in the repository."
179 error_scm_not_found: "The entry or revision was not found in the repository."
179 error_scm_command_failed: "An error occurred when trying to access the repository: %{value}"
180 error_scm_command_failed: "An error occurred when trying to access the repository: %{value}"
180 error_scm_annotate: "The entry does not exist or cannot be annotated."
181 error_scm_annotate: "The entry does not exist or cannot be annotated."
181 error_scm_annotate_big_text_file: "The entry cannot be annotated, as it exceeds the maximum text file size."
182 error_scm_annotate_big_text_file: "The entry cannot be annotated, as it exceeds the maximum text file size."
182 error_issue_not_found_in_project: 'The issue was not found or does not belong to this project'
183 error_issue_not_found_in_project: 'The issue was not found or does not belong to this project'
183 error_no_tracker_in_project: 'No tracker is associated to this project. Please check the Project settings.'
184 error_no_tracker_in_project: 'No tracker is associated to this project. Please check the Project settings.'
184 error_no_default_issue_status: 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").'
185 error_no_default_issue_status: 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").'
185 error_can_not_delete_custom_field: Unable to delete custom field
186 error_can_not_delete_custom_field: Unable to delete custom field
186 error_can_not_delete_tracker: "This tracker contains issues and cannot be deleted."
187 error_can_not_delete_tracker: "This tracker contains issues and cannot be deleted."
187 error_can_not_remove_role: "This role is in use and cannot be deleted."
188 error_can_not_remove_role: "This role is in use and cannot be deleted."
188 error_can_not_reopen_issue_on_closed_version: 'An issue assigned to a closed version cannot be reopened'
189 error_can_not_reopen_issue_on_closed_version: 'An issue assigned to a closed version cannot be reopened'
189 error_can_not_archive_project: This project cannot be archived
190 error_can_not_archive_project: This project cannot be archived
190 error_issue_done_ratios_not_updated: "Issue done ratios not updated."
191 error_issue_done_ratios_not_updated: "Issue done ratios not updated."
191 error_workflow_copy_source: 'Please select a source tracker or role'
192 error_workflow_copy_source: 'Please select a source tracker or role'
192 error_workflow_copy_target: 'Please select target tracker(s) and role(s)'
193 error_workflow_copy_target: 'Please select target tracker(s) and role(s)'
193 error_unable_delete_issue_status: 'Unable to delete issue status'
194 error_unable_delete_issue_status: 'Unable to delete issue status'
194 error_unable_to_connect: "Unable to connect (%{value})"
195 error_unable_to_connect: "Unable to connect (%{value})"
195 error_attachment_too_big: "This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size})"
196 error_attachment_too_big: "This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size})"
196 warning_attachments_not_saved: "%{count} file(s) could not be saved."
197 warning_attachments_not_saved: "%{count} file(s) could not be saved."
197
198
198 mail_subject_lost_password: "Your %{value} password"
199 mail_subject_lost_password: "Your %{value} password"
199 mail_body_lost_password: 'To change your password, click on the following link:'
200 mail_body_lost_password: 'To change your password, click on the following link:'
200 mail_subject_register: "Your %{value} account activation"
201 mail_subject_register: "Your %{value} account activation"
201 mail_body_register: 'To activate your account, click on the following link:'
202 mail_body_register: 'To activate your account, click on the following link:'
202 mail_body_account_information_external: "You can use your %{value} account to log in."
203 mail_body_account_information_external: "You can use your %{value} account to log in."
203 mail_body_account_information: Your account information
204 mail_body_account_information: Your account information
204 mail_subject_account_activation_request: "%{value} account activation request"
205 mail_subject_account_activation_request: "%{value} account activation request"
205 mail_body_account_activation_request: "A new user (%{value}) has registered. The account is pending your approval:"
206 mail_body_account_activation_request: "A new user (%{value}) has registered. The account is pending your approval:"
206 mail_subject_reminder: "%{count} issue(s) due in the next %{days} days"
207 mail_subject_reminder: "%{count} issue(s) due in the next %{days} days"
207 mail_body_reminder: "%{count} issue(s) that are assigned to you are due in the next %{days} days:"
208 mail_body_reminder: "%{count} issue(s) that are assigned to you are due in the next %{days} days:"
208 mail_subject_wiki_content_added: "'%{id}' wiki page has been added"
209 mail_subject_wiki_content_added: "'%{id}' wiki page has been added"
209 mail_body_wiki_content_added: "The '%{id}' wiki page has been added by %{author}."
210 mail_body_wiki_content_added: "The '%{id}' wiki page has been added by %{author}."
210 mail_subject_wiki_content_updated: "'%{id}' wiki page has been updated"
211 mail_subject_wiki_content_updated: "'%{id}' wiki page has been updated"
211 mail_body_wiki_content_updated: "The '%{id}' wiki page has been updated by %{author}."
212 mail_body_wiki_content_updated: "The '%{id}' wiki page has been updated by %{author}."
212
213
213 gui_validation_error: 1 error
214 gui_validation_error: 1 error
214 gui_validation_error_plural: "%{count} errors"
215 gui_validation_error_plural: "%{count} errors"
215
216
216 field_name: Name
217 field_name: Name
217 field_description: Description
218 field_description: Description
218 field_summary: Summary
219 field_summary: Summary
219 field_is_required: Required
220 field_is_required: Required
220 field_firstname: First name
221 field_firstname: First name
221 field_lastname: Last name
222 field_lastname: Last name
222 field_mail: Email
223 field_mail: Email
223 field_filename: File
224 field_filename: File
224 field_filesize: Size
225 field_filesize: Size
225 field_downloads: Downloads
226 field_downloads: Downloads
226 field_author: Author
227 field_author: Author
227 field_created_on: Created
228 field_created_on: Created
228 field_updated_on: Updated
229 field_updated_on: Updated
229 field_field_format: Format
230 field_field_format: Format
230 field_is_for_all: For all projects
231 field_is_for_all: For all projects
231 field_possible_values: Possible values
232 field_possible_values: Possible values
232 field_regexp: Regular expression
233 field_regexp: Regular expression
233 field_min_length: Minimum length
234 field_min_length: Minimum length
234 field_max_length: Maximum length
235 field_max_length: Maximum length
235 field_value: Value
236 field_value: Value
236 field_category: Category
237 field_category: Category
237 field_title: Title
238 field_title: Title
238 field_project: Project
239 field_project: Project
239 field_issue: Issue
240 field_issue: Issue
240 field_status: Status
241 field_status: Status
241 field_notes: Notes
242 field_notes: Notes
242 field_is_closed: Issue closed
243 field_is_closed: Issue closed
243 field_is_default: Default value
244 field_is_default: Default value
244 field_tracker: Tracker
245 field_tracker: Tracker
245 field_subject: Subject
246 field_subject: Subject
246 field_due_date: Due date
247 field_due_date: Due date
247 field_assigned_to: Assignee
248 field_assigned_to: Assignee
248 field_priority: Priority
249 field_priority: Priority
249 field_fixed_version: Target version
250 field_fixed_version: Target version
250 field_user: User
251 field_user: User
251 field_principal: Principal
252 field_principal: Principal
252 field_role: Role
253 field_role: Role
253 field_homepage: Homepage
254 field_homepage: Homepage
254 field_is_public: Public
255 field_is_public: Public
255 field_parent: Subproject of
256 field_parent: Subproject of
256 field_is_in_roadmap: Issues displayed in roadmap
257 field_is_in_roadmap: Issues displayed in roadmap
257 field_login: Login
258 field_login: Login
258 field_mail_notification: Email notifications
259 field_mail_notification: Email notifications
259 field_admin: Administrator
260 field_admin: Administrator
260 field_last_login_on: Last connection
261 field_last_login_on: Last connection
261 field_language: Language
262 field_language: Language
262 field_effective_date: Date
263 field_effective_date: Date
263 field_password: Password
264 field_password: Password
264 field_new_password: New password
265 field_new_password: New password
265 field_password_confirmation: Confirmation
266 field_password_confirmation: Confirmation
266 field_version: Version
267 field_version: Version
267 field_type: Type
268 field_type: Type
268 field_host: Host
269 field_host: Host
269 field_port: Port
270 field_port: Port
270 field_account: Account
271 field_account: Account
271 field_base_dn: Base DN
272 field_base_dn: Base DN
272 field_attr_login: Login attribute
273 field_attr_login: Login attribute
273 field_attr_firstname: Firstname attribute
274 field_attr_firstname: Firstname attribute
274 field_attr_lastname: Lastname attribute
275 field_attr_lastname: Lastname attribute
275 field_attr_mail: Email attribute
276 field_attr_mail: Email attribute
276 field_onthefly: On-the-fly user creation
277 field_onthefly: On-the-fly user creation
277 field_start_date: Start date
278 field_start_date: Start date
278 field_done_ratio: "% Done"
279 field_done_ratio: "% Done"
279 field_auth_source: Authentication mode
280 field_auth_source: Authentication mode
280 field_hide_mail: Hide my email address
281 field_hide_mail: Hide my email address
281 field_comments: Comment
282 field_comments: Comment
282 field_url: URL
283 field_url: URL
283 field_start_page: Start page
284 field_start_page: Start page
284 field_subproject: Subproject
285 field_subproject: Subproject
285 field_hours: Hours
286 field_hours: Hours
286 field_activity: Activity
287 field_activity: Activity
287 field_spent_on: Date
288 field_spent_on: Date
288 field_identifier: Identifier
289 field_identifier: Identifier
289 field_is_filter: Used as a filter
290 field_is_filter: Used as a filter
290 field_issue_to: Related issue
291 field_issue_to: Related issue
291 field_delay: Delay
292 field_delay: Delay
292 field_assignable: Issues can be assigned to this role
293 field_assignable: Issues can be assigned to this role
293 field_redirect_existing_links: Redirect existing links
294 field_redirect_existing_links: Redirect existing links
294 field_estimated_hours: Estimated time
295 field_estimated_hours: Estimated time
295 field_column_names: Columns
296 field_column_names: Columns
296 field_time_entries: Log time
297 field_time_entries: Log time
297 field_time_zone: Time zone
298 field_time_zone: Time zone
298 field_searchable: Searchable
299 field_searchable: Searchable
299 field_default_value: Default value
300 field_default_value: Default value
300 field_comments_sorting: Display comments
301 field_comments_sorting: Display comments
301 field_parent_title: Parent page
302 field_parent_title: Parent page
302 field_editable: Editable
303 field_editable: Editable
303 field_watcher: Watcher
304 field_watcher: Watcher
304 field_identity_url: OpenID URL
305 field_identity_url: OpenID URL
305 field_content: Content
306 field_content: Content
306 field_group_by: Group results by
307 field_group_by: Group results by
307 field_sharing: Sharing
308 field_sharing: Sharing
308 field_parent_issue: Parent task
309 field_parent_issue: Parent task
309 field_member_of_group: "Assignee's group"
310 field_member_of_group: "Assignee's group"
310 field_assigned_to_role: "Assignee's role"
311 field_assigned_to_role: "Assignee's role"
311 field_text: Text field
312 field_text: Text field
312 field_visible: Visible
313 field_visible: Visible
313 field_warn_on_leaving_unsaved: "Warn me when leaving a page with unsaved text"
314 field_warn_on_leaving_unsaved: "Warn me when leaving a page with unsaved text"
314 field_issues_visibility: Issues visibility
315 field_issues_visibility: Issues visibility
315 field_is_private: Private
316 field_is_private: Private
316 field_commit_logs_encoding: Commit messages encoding
317 field_commit_logs_encoding: Commit messages encoding
317 field_scm_path_encoding: Path encoding
318 field_scm_path_encoding: Path encoding
318 field_path_to_repository: Path to repository
319 field_path_to_repository: Path to repository
319 field_root_directory: Root directory
320 field_root_directory: Root directory
320 field_cvsroot: CVSROOT
321 field_cvsroot: CVSROOT
321 field_cvs_module: Module
322 field_cvs_module: Module
322 field_repository_is_default: Main repository
323 field_repository_is_default: Main repository
323 field_multiple: Multiple values
324 field_multiple: Multiple values
324 field_ldap_filter: LDAP filter
325 field_ldap_filter: LDAP filter
325
326
326 setting_app_title: Application title
327 setting_app_title: Application title
327 setting_app_subtitle: Application subtitle
328 setting_app_subtitle: Application subtitle
328 setting_welcome_text: Welcome text
329 setting_welcome_text: Welcome text
329 setting_default_language: Default language
330 setting_default_language: Default language
330 setting_login_required: Authentication required
331 setting_login_required: Authentication required
331 setting_self_registration: Self-registration
332 setting_self_registration: Self-registration
332 setting_attachment_max_size: Maximum attachment size
333 setting_attachment_max_size: Maximum attachment size
333 setting_issues_export_limit: Issues export limit
334 setting_issues_export_limit: Issues export limit
334 setting_mail_from: Emission email address
335 setting_mail_from: Emission email address
335 setting_bcc_recipients: Blind carbon copy recipients (bcc)
336 setting_bcc_recipients: Blind carbon copy recipients (bcc)
336 setting_plain_text_mail: Plain text mail (no HTML)
337 setting_plain_text_mail: Plain text mail (no HTML)
337 setting_host_name: Host name and path
338 setting_host_name: Host name and path
338 setting_text_formatting: Text formatting
339 setting_text_formatting: Text formatting
339 setting_wiki_compression: Wiki history compression
340 setting_wiki_compression: Wiki history compression
340 setting_feeds_limit: Maximum number of items in Atom feeds
341 setting_feeds_limit: Maximum number of items in Atom feeds
341 setting_default_projects_public: New projects are public by default
342 setting_default_projects_public: New projects are public by default
342 setting_autofetch_changesets: Fetch commits automatically
343 setting_autofetch_changesets: Fetch commits automatically
343 setting_sys_api_enabled: Enable WS for repository management
344 setting_sys_api_enabled: Enable WS for repository management
344 setting_commit_ref_keywords: Referencing keywords
345 setting_commit_ref_keywords: Referencing keywords
345 setting_commit_fix_keywords: Fixing keywords
346 setting_commit_fix_keywords: Fixing keywords
346 setting_autologin: Autologin
347 setting_autologin: Autologin
347 setting_date_format: Date format
348 setting_date_format: Date format
348 setting_time_format: Time format
349 setting_time_format: Time format
349 setting_cross_project_issue_relations: Allow cross-project issue relations
350 setting_cross_project_issue_relations: Allow cross-project issue relations
350 setting_issue_list_default_columns: Default columns displayed on the issue list
351 setting_issue_list_default_columns: Default columns displayed on the issue list
351 setting_repositories_encodings: Attachments and repositories encodings
352 setting_repositories_encodings: Attachments and repositories encodings
352 setting_emails_header: Emails header
353 setting_emails_header: Emails header
353 setting_emails_footer: Emails footer
354 setting_emails_footer: Emails footer
354 setting_protocol: Protocol
355 setting_protocol: Protocol
355 setting_per_page_options: Objects per page options
356 setting_per_page_options: Objects per page options
356 setting_user_format: Users display format
357 setting_user_format: Users display format
357 setting_activity_days_default: Days displayed on project activity
358 setting_activity_days_default: Days displayed on project activity
358 setting_display_subprojects_issues: Display subprojects issues on main projects by default
359 setting_display_subprojects_issues: Display subprojects issues on main projects by default
359 setting_enabled_scm: Enabled SCM
360 setting_enabled_scm: Enabled SCM
360 setting_mail_handler_body_delimiters: "Truncate emails after one of these lines"
361 setting_mail_handler_body_delimiters: "Truncate emails after one of these lines"
361 setting_mail_handler_api_enabled: Enable WS for incoming emails
362 setting_mail_handler_api_enabled: Enable WS for incoming emails
362 setting_mail_handler_api_key: API key
363 setting_mail_handler_api_key: API key
363 setting_sequential_project_identifiers: Generate sequential project identifiers
364 setting_sequential_project_identifiers: Generate sequential project identifiers
364 setting_gravatar_enabled: Use Gravatar user icons
365 setting_gravatar_enabled: Use Gravatar user icons
365 setting_gravatar_default: Default Gravatar image
366 setting_gravatar_default: Default Gravatar image
366 setting_diff_max_lines_displayed: Maximum number of diff lines displayed
367 setting_diff_max_lines_displayed: Maximum number of diff lines displayed
367 setting_file_max_size_displayed: Maximum size of text files displayed inline
368 setting_file_max_size_displayed: Maximum size of text files displayed inline
368 setting_repository_log_display_limit: Maximum number of revisions displayed on file log
369 setting_repository_log_display_limit: Maximum number of revisions displayed on file log
369 setting_openid: Allow OpenID login and registration
370 setting_openid: Allow OpenID login and registration
370 setting_password_min_length: Minimum password length
371 setting_password_min_length: Minimum password length
371 setting_new_project_user_role_id: Role given to a non-admin user who creates a project
372 setting_new_project_user_role_id: Role given to a non-admin user who creates a project
372 setting_default_projects_modules: Default enabled modules for new projects
373 setting_default_projects_modules: Default enabled modules for new projects
373 setting_issue_done_ratio: Calculate the issue done ratio with
374 setting_issue_done_ratio: Calculate the issue done ratio with
374 setting_issue_done_ratio_issue_field: Use the issue field
375 setting_issue_done_ratio_issue_field: Use the issue field
375 setting_issue_done_ratio_issue_status: Use the issue status
376 setting_issue_done_ratio_issue_status: Use the issue status
376 setting_start_of_week: Start calendars on
377 setting_start_of_week: Start calendars on
377 setting_rest_api_enabled: Enable REST web service
378 setting_rest_api_enabled: Enable REST web service
378 setting_cache_formatted_text: Cache formatted text
379 setting_cache_formatted_text: Cache formatted text
379 setting_default_notification_option: Default notification option
380 setting_default_notification_option: Default notification option
380 setting_commit_logtime_enabled: Enable time logging
381 setting_commit_logtime_enabled: Enable time logging
381 setting_commit_logtime_activity_id: Activity for logged time
382 setting_commit_logtime_activity_id: Activity for logged time
382 setting_gantt_items_limit: Maximum number of items displayed on the gantt chart
383 setting_gantt_items_limit: Maximum number of items displayed on the gantt chart
383 setting_issue_group_assignment: Allow issue assignment to groups
384 setting_issue_group_assignment: Allow issue assignment to groups
384 setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues
385 setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues
385 setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed
386 setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed
387 setting_unsubscribe: Allow users to unsubscribe
386
388
387 permission_add_project: Create project
389 permission_add_project: Create project
388 permission_add_subprojects: Create subprojects
390 permission_add_subprojects: Create subprojects
389 permission_edit_project: Edit project
391 permission_edit_project: Edit project
390 permission_select_project_modules: Select project modules
392 permission_select_project_modules: Select project modules
391 permission_manage_members: Manage members
393 permission_manage_members: Manage members
392 permission_manage_project_activities: Manage project activities
394 permission_manage_project_activities: Manage project activities
393 permission_manage_versions: Manage versions
395 permission_manage_versions: Manage versions
394 permission_manage_categories: Manage issue categories
396 permission_manage_categories: Manage issue categories
395 permission_view_issues: View Issues
397 permission_view_issues: View Issues
396 permission_add_issues: Add issues
398 permission_add_issues: Add issues
397 permission_edit_issues: Edit issues
399 permission_edit_issues: Edit issues
398 permission_manage_issue_relations: Manage issue relations
400 permission_manage_issue_relations: Manage issue relations
399 permission_set_issues_private: Set issues public or private
401 permission_set_issues_private: Set issues public or private
400 permission_set_own_issues_private: Set own issues public or private
402 permission_set_own_issues_private: Set own issues public or private
401 permission_add_issue_notes: Add notes
403 permission_add_issue_notes: Add notes
402 permission_edit_issue_notes: Edit notes
404 permission_edit_issue_notes: Edit notes
403 permission_edit_own_issue_notes: Edit own notes
405 permission_edit_own_issue_notes: Edit own notes
404 permission_move_issues: Move issues
406 permission_move_issues: Move issues
405 permission_delete_issues: Delete issues
407 permission_delete_issues: Delete issues
406 permission_manage_public_queries: Manage public queries
408 permission_manage_public_queries: Manage public queries
407 permission_save_queries: Save queries
409 permission_save_queries: Save queries
408 permission_view_gantt: View gantt chart
410 permission_view_gantt: View gantt chart
409 permission_view_calendar: View calendar
411 permission_view_calendar: View calendar
410 permission_view_issue_watchers: View watchers list
412 permission_view_issue_watchers: View watchers list
411 permission_add_issue_watchers: Add watchers
413 permission_add_issue_watchers: Add watchers
412 permission_delete_issue_watchers: Delete watchers
414 permission_delete_issue_watchers: Delete watchers
413 permission_log_time: Log spent time
415 permission_log_time: Log spent time
414 permission_view_time_entries: View spent time
416 permission_view_time_entries: View spent time
415 permission_edit_time_entries: Edit time logs
417 permission_edit_time_entries: Edit time logs
416 permission_edit_own_time_entries: Edit own time logs
418 permission_edit_own_time_entries: Edit own time logs
417 permission_manage_news: Manage news
419 permission_manage_news: Manage news
418 permission_comment_news: Comment news
420 permission_comment_news: Comment news
419 permission_manage_documents: Manage documents
421 permission_manage_documents: Manage documents
420 permission_view_documents: View documents
422 permission_view_documents: View documents
421 permission_manage_files: Manage files
423 permission_manage_files: Manage files
422 permission_view_files: View files
424 permission_view_files: View files
423 permission_manage_wiki: Manage wiki
425 permission_manage_wiki: Manage wiki
424 permission_rename_wiki_pages: Rename wiki pages
426 permission_rename_wiki_pages: Rename wiki pages
425 permission_delete_wiki_pages: Delete wiki pages
427 permission_delete_wiki_pages: Delete wiki pages
426 permission_view_wiki_pages: View wiki
428 permission_view_wiki_pages: View wiki
427 permission_view_wiki_edits: View wiki history
429 permission_view_wiki_edits: View wiki history
428 permission_edit_wiki_pages: Edit wiki pages
430 permission_edit_wiki_pages: Edit wiki pages
429 permission_delete_wiki_pages_attachments: Delete attachments
431 permission_delete_wiki_pages_attachments: Delete attachments
430 permission_protect_wiki_pages: Protect wiki pages
432 permission_protect_wiki_pages: Protect wiki pages
431 permission_manage_repository: Manage repository
433 permission_manage_repository: Manage repository
432 permission_browse_repository: Browse repository
434 permission_browse_repository: Browse repository
433 permission_view_changesets: View changesets
435 permission_view_changesets: View changesets
434 permission_commit_access: Commit access
436 permission_commit_access: Commit access
435 permission_manage_boards: Manage forums
437 permission_manage_boards: Manage forums
436 permission_view_messages: View messages
438 permission_view_messages: View messages
437 permission_add_messages: Post messages
439 permission_add_messages: Post messages
438 permission_edit_messages: Edit messages
440 permission_edit_messages: Edit messages
439 permission_edit_own_messages: Edit own messages
441 permission_edit_own_messages: Edit own messages
440 permission_delete_messages: Delete messages
442 permission_delete_messages: Delete messages
441 permission_delete_own_messages: Delete own messages
443 permission_delete_own_messages: Delete own messages
442 permission_export_wiki_pages: Export wiki pages
444 permission_export_wiki_pages: Export wiki pages
443 permission_manage_subtasks: Manage subtasks
445 permission_manage_subtasks: Manage subtasks
444 permission_manage_related_issues: Manage related issues
446 permission_manage_related_issues: Manage related issues
445
447
446 project_module_issue_tracking: Issue tracking
448 project_module_issue_tracking: Issue tracking
447 project_module_time_tracking: Time tracking
449 project_module_time_tracking: Time tracking
448 project_module_news: News
450 project_module_news: News
449 project_module_documents: Documents
451 project_module_documents: Documents
450 project_module_files: Files
452 project_module_files: Files
451 project_module_wiki: Wiki
453 project_module_wiki: Wiki
452 project_module_repository: Repository
454 project_module_repository: Repository
453 project_module_boards: Forums
455 project_module_boards: Forums
454 project_module_calendar: Calendar
456 project_module_calendar: Calendar
455 project_module_gantt: Gantt
457 project_module_gantt: Gantt
456
458
457 label_user: User
459 label_user: User
458 label_user_plural: Users
460 label_user_plural: Users
459 label_user_new: New user
461 label_user_new: New user
460 label_user_anonymous: Anonymous
462 label_user_anonymous: Anonymous
461 label_project: Project
463 label_project: Project
462 label_project_new: New project
464 label_project_new: New project
463 label_project_plural: Projects
465 label_project_plural: Projects
464 label_x_projects:
466 label_x_projects:
465 zero: no projects
467 zero: no projects
466 one: 1 project
468 one: 1 project
467 other: "%{count} projects"
469 other: "%{count} projects"
468 label_project_all: All Projects
470 label_project_all: All Projects
469 label_project_latest: Latest projects
471 label_project_latest: Latest projects
470 label_issue: Issue
472 label_issue: Issue
471 label_issue_new: New issue
473 label_issue_new: New issue
472 label_issue_plural: Issues
474 label_issue_plural: Issues
473 label_issue_view_all: View all issues
475 label_issue_view_all: View all issues
474 label_issues_by: "Issues by %{value}"
476 label_issues_by: "Issues by %{value}"
475 label_issue_added: Issue added
477 label_issue_added: Issue added
476 label_issue_updated: Issue updated
478 label_issue_updated: Issue updated
477 label_issue_note_added: Note added
479 label_issue_note_added: Note added
478 label_issue_status_updated: Status updated
480 label_issue_status_updated: Status updated
479 label_issue_priority_updated: Priority updated
481 label_issue_priority_updated: Priority updated
480 label_document: Document
482 label_document: Document
481 label_document_new: New document
483 label_document_new: New document
482 label_document_plural: Documents
484 label_document_plural: Documents
483 label_document_added: Document added
485 label_document_added: Document added
484 label_role: Role
486 label_role: Role
485 label_role_plural: Roles
487 label_role_plural: Roles
486 label_role_new: New role
488 label_role_new: New role
487 label_role_and_permissions: Roles and permissions
489 label_role_and_permissions: Roles and permissions
488 label_role_anonymous: Anonymous
490 label_role_anonymous: Anonymous
489 label_role_non_member: Non member
491 label_role_non_member: Non member
490 label_member: Member
492 label_member: Member
491 label_member_new: New member
493 label_member_new: New member
492 label_member_plural: Members
494 label_member_plural: Members
493 label_tracker: Tracker
495 label_tracker: Tracker
494 label_tracker_plural: Trackers
496 label_tracker_plural: Trackers
495 label_tracker_new: New tracker
497 label_tracker_new: New tracker
496 label_workflow: Workflow
498 label_workflow: Workflow
497 label_issue_status: Issue status
499 label_issue_status: Issue status
498 label_issue_status_plural: Issue statuses
500 label_issue_status_plural: Issue statuses
499 label_issue_status_new: New status
501 label_issue_status_new: New status
500 label_issue_category: Issue category
502 label_issue_category: Issue category
501 label_issue_category_plural: Issue categories
503 label_issue_category_plural: Issue categories
502 label_issue_category_new: New category
504 label_issue_category_new: New category
503 label_custom_field: Custom field
505 label_custom_field: Custom field
504 label_custom_field_plural: Custom fields
506 label_custom_field_plural: Custom fields
505 label_custom_field_new: New custom field
507 label_custom_field_new: New custom field
506 label_enumerations: Enumerations
508 label_enumerations: Enumerations
507 label_enumeration_new: New value
509 label_enumeration_new: New value
508 label_information: Information
510 label_information: Information
509 label_information_plural: Information
511 label_information_plural: Information
510 label_please_login: Please log in
512 label_please_login: Please log in
511 label_register: Register
513 label_register: Register
512 label_login_with_open_id_option: or login with OpenID
514 label_login_with_open_id_option: or login with OpenID
513 label_password_lost: Lost password
515 label_password_lost: Lost password
514 label_home: Home
516 label_home: Home
515 label_my_page: My page
517 label_my_page: My page
516 label_my_account: My account
518 label_my_account: My account
517 label_my_projects: My projects
519 label_my_projects: My projects
518 label_my_page_block: My page block
520 label_my_page_block: My page block
519 label_administration: Administration
521 label_administration: Administration
520 label_login: Sign in
522 label_login: Sign in
521 label_logout: Sign out
523 label_logout: Sign out
522 label_help: Help
524 label_help: Help
523 label_reported_issues: Reported issues
525 label_reported_issues: Reported issues
524 label_assigned_to_me_issues: Issues assigned to me
526 label_assigned_to_me_issues: Issues assigned to me
525 label_last_login: Last connection
527 label_last_login: Last connection
526 label_registered_on: Registered on
528 label_registered_on: Registered on
527 label_activity: Activity
529 label_activity: Activity
528 label_overall_activity: Overall activity
530 label_overall_activity: Overall activity
529 label_user_activity: "%{value}'s activity"
531 label_user_activity: "%{value}'s activity"
530 label_new: New
532 label_new: New
531 label_logged_as: Logged in as
533 label_logged_as: Logged in as
532 label_environment: Environment
534 label_environment: Environment
533 label_authentication: Authentication
535 label_authentication: Authentication
534 label_auth_source: Authentication mode
536 label_auth_source: Authentication mode
535 label_auth_source_new: New authentication mode
537 label_auth_source_new: New authentication mode
536 label_auth_source_plural: Authentication modes
538 label_auth_source_plural: Authentication modes
537 label_subproject_plural: Subprojects
539 label_subproject_plural: Subprojects
538 label_subproject_new: New subproject
540 label_subproject_new: New subproject
539 label_and_its_subprojects: "%{value} and its subprojects"
541 label_and_its_subprojects: "%{value} and its subprojects"
540 label_min_max_length: Min - Max length
542 label_min_max_length: Min - Max length
541 label_list: List
543 label_list: List
542 label_date: Date
544 label_date: Date
543 label_integer: Integer
545 label_integer: Integer
544 label_float: Float
546 label_float: Float
545 label_boolean: Boolean
547 label_boolean: Boolean
546 label_string: Text
548 label_string: Text
547 label_text: Long text
549 label_text: Long text
548 label_attribute: Attribute
550 label_attribute: Attribute
549 label_attribute_plural: Attributes
551 label_attribute_plural: Attributes
550 label_download: "%{count} Download"
552 label_download: "%{count} Download"
551 label_download_plural: "%{count} Downloads"
553 label_download_plural: "%{count} Downloads"
552 label_no_data: No data to display
554 label_no_data: No data to display
553 label_change_status: Change status
555 label_change_status: Change status
554 label_history: History
556 label_history: History
555 label_attachment: File
557 label_attachment: File
556 label_attachment_new: New file
558 label_attachment_new: New file
557 label_attachment_delete: Delete file
559 label_attachment_delete: Delete file
558 label_attachment_plural: Files
560 label_attachment_plural: Files
559 label_file_added: File added
561 label_file_added: File added
560 label_report: Report
562 label_report: Report
561 label_report_plural: Reports
563 label_report_plural: Reports
562 label_news: News
564 label_news: News
563 label_news_new: Add news
565 label_news_new: Add news
564 label_news_plural: News
566 label_news_plural: News
565 label_news_latest: Latest news
567 label_news_latest: Latest news
566 label_news_view_all: View all news
568 label_news_view_all: View all news
567 label_news_added: News added
569 label_news_added: News added
568 label_news_comment_added: Comment added to a news
570 label_news_comment_added: Comment added to a news
569 label_settings: Settings
571 label_settings: Settings
570 label_overview: Overview
572 label_overview: Overview
571 label_version: Version
573 label_version: Version
572 label_version_new: New version
574 label_version_new: New version
573 label_version_plural: Versions
575 label_version_plural: Versions
574 label_close_versions: Close completed versions
576 label_close_versions: Close completed versions
575 label_confirmation: Confirmation
577 label_confirmation: Confirmation
576 label_export_to: 'Also available in:'
578 label_export_to: 'Also available in:'
577 label_read: Read...
579 label_read: Read...
578 label_public_projects: Public projects
580 label_public_projects: Public projects
579 label_open_issues: open
581 label_open_issues: open
580 label_open_issues_plural: open
582 label_open_issues_plural: open
581 label_closed_issues: closed
583 label_closed_issues: closed
582 label_closed_issues_plural: closed
584 label_closed_issues_plural: closed
583 label_x_open_issues_abbr_on_total:
585 label_x_open_issues_abbr_on_total:
584 zero: 0 open / %{total}
586 zero: 0 open / %{total}
585 one: 1 open / %{total}
587 one: 1 open / %{total}
586 other: "%{count} open / %{total}"
588 other: "%{count} open / %{total}"
587 label_x_open_issues_abbr:
589 label_x_open_issues_abbr:
588 zero: 0 open
590 zero: 0 open
589 one: 1 open
591 one: 1 open
590 other: "%{count} open"
592 other: "%{count} open"
591 label_x_closed_issues_abbr:
593 label_x_closed_issues_abbr:
592 zero: 0 closed
594 zero: 0 closed
593 one: 1 closed
595 one: 1 closed
594 other: "%{count} closed"
596 other: "%{count} closed"
595 label_x_issues:
597 label_x_issues:
596 zero: 0 issues
598 zero: 0 issues
597 one: 1 issue
599 one: 1 issue
598 other: "%{count} issues"
600 other: "%{count} issues"
599 label_total: Total
601 label_total: Total
600 label_permissions: Permissions
602 label_permissions: Permissions
601 label_current_status: Current status
603 label_current_status: Current status
602 label_new_statuses_allowed: New statuses allowed
604 label_new_statuses_allowed: New statuses allowed
603 label_all: all
605 label_all: all
604 label_none: none
606 label_none: none
605 label_nobody: nobody
607 label_nobody: nobody
606 label_next: Next
608 label_next: Next
607 label_previous: Previous
609 label_previous: Previous
608 label_used_by: Used by
610 label_used_by: Used by
609 label_details: Details
611 label_details: Details
610 label_add_note: Add a note
612 label_add_note: Add a note
611 label_per_page: Per page
613 label_per_page: Per page
612 label_calendar: Calendar
614 label_calendar: Calendar
613 label_months_from: months from
615 label_months_from: months from
614 label_gantt: Gantt
616 label_gantt: Gantt
615 label_internal: Internal
617 label_internal: Internal
616 label_last_changes: "last %{count} changes"
618 label_last_changes: "last %{count} changes"
617 label_change_view_all: View all changes
619 label_change_view_all: View all changes
618 label_personalize_page: Personalize this page
620 label_personalize_page: Personalize this page
619 label_comment: Comment
621 label_comment: Comment
620 label_comment_plural: Comments
622 label_comment_plural: Comments
621 label_x_comments:
623 label_x_comments:
622 zero: no comments
624 zero: no comments
623 one: 1 comment
625 one: 1 comment
624 other: "%{count} comments"
626 other: "%{count} comments"
625 label_comment_add: Add a comment
627 label_comment_add: Add a comment
626 label_comment_added: Comment added
628 label_comment_added: Comment added
627 label_comment_delete: Delete comments
629 label_comment_delete: Delete comments
628 label_query: Custom query
630 label_query: Custom query
629 label_query_plural: Custom queries
631 label_query_plural: Custom queries
630 label_query_new: New query
632 label_query_new: New query
631 label_my_queries: My custom queries
633 label_my_queries: My custom queries
632 label_filter_add: Add filter
634 label_filter_add: Add filter
633 label_filter_plural: Filters
635 label_filter_plural: Filters
634 label_equals: is
636 label_equals: is
635 label_not_equals: is not
637 label_not_equals: is not
636 label_in_less_than: in less than
638 label_in_less_than: in less than
637 label_in_more_than: in more than
639 label_in_more_than: in more than
638 label_greater_or_equal: '>='
640 label_greater_or_equal: '>='
639 label_less_or_equal: '<='
641 label_less_or_equal: '<='
640 label_between: between
642 label_between: between
641 label_in: in
643 label_in: in
642 label_today: today
644 label_today: today
643 label_all_time: all time
645 label_all_time: all time
644 label_yesterday: yesterday
646 label_yesterday: yesterday
645 label_this_week: this week
647 label_this_week: this week
646 label_last_week: last week
648 label_last_week: last week
647 label_last_n_days: "last %{count} days"
649 label_last_n_days: "last %{count} days"
648 label_this_month: this month
650 label_this_month: this month
649 label_last_month: last month
651 label_last_month: last month
650 label_this_year: this year
652 label_this_year: this year
651 label_date_range: Date range
653 label_date_range: Date range
652 label_less_than_ago: less than days ago
654 label_less_than_ago: less than days ago
653 label_more_than_ago: more than days ago
655 label_more_than_ago: more than days ago
654 label_ago: days ago
656 label_ago: days ago
655 label_contains: contains
657 label_contains: contains
656 label_not_contains: doesn't contain
658 label_not_contains: doesn't contain
657 label_day_plural: days
659 label_day_plural: days
658 label_repository: Repository
660 label_repository: Repository
659 label_repository_new: New repository
661 label_repository_new: New repository
660 label_repository_plural: Repositories
662 label_repository_plural: Repositories
661 label_browse: Browse
663 label_browse: Browse
662 label_modification: "%{count} change"
664 label_modification: "%{count} change"
663 label_modification_plural: "%{count} changes"
665 label_modification_plural: "%{count} changes"
664 label_branch: Branch
666 label_branch: Branch
665 label_tag: Tag
667 label_tag: Tag
666 label_revision: Revision
668 label_revision: Revision
667 label_revision_plural: Revisions
669 label_revision_plural: Revisions
668 label_revision_id: "Revision %{value}"
670 label_revision_id: "Revision %{value}"
669 label_associated_revisions: Associated revisions
671 label_associated_revisions: Associated revisions
670 label_added: added
672 label_added: added
671 label_modified: modified
673 label_modified: modified
672 label_copied: copied
674 label_copied: copied
673 label_renamed: renamed
675 label_renamed: renamed
674 label_deleted: deleted
676 label_deleted: deleted
675 label_latest_revision: Latest revision
677 label_latest_revision: Latest revision
676 label_latest_revision_plural: Latest revisions
678 label_latest_revision_plural: Latest revisions
677 label_view_revisions: View revisions
679 label_view_revisions: View revisions
678 label_view_all_revisions: View all revisions
680 label_view_all_revisions: View all revisions
679 label_max_size: Maximum size
681 label_max_size: Maximum size
680 label_sort_highest: Move to top
682 label_sort_highest: Move to top
681 label_sort_higher: Move up
683 label_sort_higher: Move up
682 label_sort_lower: Move down
684 label_sort_lower: Move down
683 label_sort_lowest: Move to bottom
685 label_sort_lowest: Move to bottom
684 label_roadmap: Roadmap
686 label_roadmap: Roadmap
685 label_roadmap_due_in: "Due in %{value}"
687 label_roadmap_due_in: "Due in %{value}"
686 label_roadmap_overdue: "%{value} late"
688 label_roadmap_overdue: "%{value} late"
687 label_roadmap_no_issues: No issues for this version
689 label_roadmap_no_issues: No issues for this version
688 label_search: Search
690 label_search: Search
689 label_result_plural: Results
691 label_result_plural: Results
690 label_all_words: All words
692 label_all_words: All words
691 label_wiki: Wiki
693 label_wiki: Wiki
692 label_wiki_edit: Wiki edit
694 label_wiki_edit: Wiki edit
693 label_wiki_edit_plural: Wiki edits
695 label_wiki_edit_plural: Wiki edits
694 label_wiki_page: Wiki page
696 label_wiki_page: Wiki page
695 label_wiki_page_plural: Wiki pages
697 label_wiki_page_plural: Wiki pages
696 label_index_by_title: Index by title
698 label_index_by_title: Index by title
697 label_index_by_date: Index by date
699 label_index_by_date: Index by date
698 label_current_version: Current version
700 label_current_version: Current version
699 label_preview: Preview
701 label_preview: Preview
700 label_feed_plural: Feeds
702 label_feed_plural: Feeds
701 label_changes_details: Details of all changes
703 label_changes_details: Details of all changes
702 label_issue_tracking: Issue tracking
704 label_issue_tracking: Issue tracking
703 label_spent_time: Spent time
705 label_spent_time: Spent time
704 label_overall_spent_time: Overall spent time
706 label_overall_spent_time: Overall spent time
705 label_f_hour: "%{value} hour"
707 label_f_hour: "%{value} hour"
706 label_f_hour_plural: "%{value} hours"
708 label_f_hour_plural: "%{value} hours"
707 label_time_tracking: Time tracking
709 label_time_tracking: Time tracking
708 label_change_plural: Changes
710 label_change_plural: Changes
709 label_statistics: Statistics
711 label_statistics: Statistics
710 label_commits_per_month: Commits per month
712 label_commits_per_month: Commits per month
711 label_commits_per_author: Commits per author
713 label_commits_per_author: Commits per author
712 label_diff: diff
714 label_diff: diff
713 label_view_diff: View differences
715 label_view_diff: View differences
714 label_diff_inline: inline
716 label_diff_inline: inline
715 label_diff_side_by_side: side by side
717 label_diff_side_by_side: side by side
716 label_options: Options
718 label_options: Options
717 label_copy_workflow_from: Copy workflow from
719 label_copy_workflow_from: Copy workflow from
718 label_permissions_report: Permissions report
720 label_permissions_report: Permissions report
719 label_watched_issues: Watched issues
721 label_watched_issues: Watched issues
720 label_related_issues: Related issues
722 label_related_issues: Related issues
721 label_applied_status: Applied status
723 label_applied_status: Applied status
722 label_loading: Loading...
724 label_loading: Loading...
723 label_relation_new: New relation
725 label_relation_new: New relation
724 label_relation_delete: Delete relation
726 label_relation_delete: Delete relation
725 label_relates_to: related to
727 label_relates_to: related to
726 label_duplicates: duplicates
728 label_duplicates: duplicates
727 label_duplicated_by: duplicated by
729 label_duplicated_by: duplicated by
728 label_blocks: blocks
730 label_blocks: blocks
729 label_blocked_by: blocked by
731 label_blocked_by: blocked by
730 label_precedes: precedes
732 label_precedes: precedes
731 label_follows: follows
733 label_follows: follows
732 label_end_to_start: end to start
734 label_end_to_start: end to start
733 label_end_to_end: end to end
735 label_end_to_end: end to end
734 label_start_to_start: start to start
736 label_start_to_start: start to start
735 label_start_to_end: start to end
737 label_start_to_end: start to end
736 label_stay_logged_in: Stay logged in
738 label_stay_logged_in: Stay logged in
737 label_disabled: disabled
739 label_disabled: disabled
738 label_show_completed_versions: Show completed versions
740 label_show_completed_versions: Show completed versions
739 label_me: me
741 label_me: me
740 label_board: Forum
742 label_board: Forum
741 label_board_new: New forum
743 label_board_new: New forum
742 label_board_plural: Forums
744 label_board_plural: Forums
743 label_board_locked: Locked
745 label_board_locked: Locked
744 label_board_sticky: Sticky
746 label_board_sticky: Sticky
745 label_topic_plural: Topics
747 label_topic_plural: Topics
746 label_message_plural: Messages
748 label_message_plural: Messages
747 label_message_last: Last message
749 label_message_last: Last message
748 label_message_new: New message
750 label_message_new: New message
749 label_message_posted: Message added
751 label_message_posted: Message added
750 label_reply_plural: Replies
752 label_reply_plural: Replies
751 label_send_information: Send account information to the user
753 label_send_information: Send account information to the user
752 label_year: Year
754 label_year: Year
753 label_month: Month
755 label_month: Month
754 label_week: Week
756 label_week: Week
755 label_date_from: From
757 label_date_from: From
756 label_date_to: To
758 label_date_to: To
757 label_language_based: Based on user's language
759 label_language_based: Based on user's language
758 label_sort_by: "Sort by %{value}"
760 label_sort_by: "Sort by %{value}"
759 label_send_test_email: Send a test email
761 label_send_test_email: Send a test email
760 label_feeds_access_key: RSS access key
762 label_feeds_access_key: RSS access key
761 label_missing_feeds_access_key: Missing a RSS access key
763 label_missing_feeds_access_key: Missing a RSS access key
762 label_feeds_access_key_created_on: "RSS access key created %{value} ago"
764 label_feeds_access_key_created_on: "RSS access key created %{value} ago"
763 label_module_plural: Modules
765 label_module_plural: Modules
764 label_added_time_by: "Added by %{author} %{age} ago"
766 label_added_time_by: "Added by %{author} %{age} ago"
765 label_updated_time_by: "Updated by %{author} %{age} ago"
767 label_updated_time_by: "Updated by %{author} %{age} ago"
766 label_updated_time: "Updated %{value} ago"
768 label_updated_time: "Updated %{value} ago"
767 label_jump_to_a_project: Jump to a project...
769 label_jump_to_a_project: Jump to a project...
768 label_file_plural: Files
770 label_file_plural: Files
769 label_changeset_plural: Changesets
771 label_changeset_plural: Changesets
770 label_default_columns: Default columns
772 label_default_columns: Default columns
771 label_no_change_option: (No change)
773 label_no_change_option: (No change)
772 label_bulk_edit_selected_issues: Bulk edit selected issues
774 label_bulk_edit_selected_issues: Bulk edit selected issues
773 label_bulk_edit_selected_time_entries: Bulk edit selected time entries
775 label_bulk_edit_selected_time_entries: Bulk edit selected time entries
774 label_theme: Theme
776 label_theme: Theme
775 label_default: Default
777 label_default: Default
776 label_search_titles_only: Search titles only
778 label_search_titles_only: Search titles only
777 label_user_mail_option_all: "For any event on all my projects"
779 label_user_mail_option_all: "For any event on all my projects"
778 label_user_mail_option_selected: "For any event on the selected projects only..."
780 label_user_mail_option_selected: "For any event on the selected projects only..."
779 label_user_mail_option_none: "No events"
781 label_user_mail_option_none: "No events"
780 label_user_mail_option_only_my_events: "Only for things I watch or I'm involved in"
782 label_user_mail_option_only_my_events: "Only for things I watch or I'm involved in"
781 label_user_mail_option_only_assigned: "Only for things I am assigned to"
783 label_user_mail_option_only_assigned: "Only for things I am assigned to"
782 label_user_mail_option_only_owner: "Only for things I am the owner of"
784 label_user_mail_option_only_owner: "Only for things I am the owner of"
783 label_user_mail_no_self_notified: "I don't want to be notified of changes that I make myself"
785 label_user_mail_no_self_notified: "I don't want to be notified of changes that I make myself"
784 label_registration_activation_by_email: account activation by email
786 label_registration_activation_by_email: account activation by email
785 label_registration_manual_activation: manual account activation
787 label_registration_manual_activation: manual account activation
786 label_registration_automatic_activation: automatic account activation
788 label_registration_automatic_activation: automatic account activation
787 label_display_per_page: "Per page: %{value}"
789 label_display_per_page: "Per page: %{value}"
788 label_age: Age
790 label_age: Age
789 label_change_properties: Change properties
791 label_change_properties: Change properties
790 label_general: General
792 label_general: General
791 label_more: More
793 label_more: More
792 label_scm: SCM
794 label_scm: SCM
793 label_plugins: Plugins
795 label_plugins: Plugins
794 label_ldap_authentication: LDAP authentication
796 label_ldap_authentication: LDAP authentication
795 label_downloads_abbr: D/L
797 label_downloads_abbr: D/L
796 label_optional_description: Optional description
798 label_optional_description: Optional description
797 label_add_another_file: Add another file
799 label_add_another_file: Add another file
798 label_preferences: Preferences
800 label_preferences: Preferences
799 label_chronological_order: In chronological order
801 label_chronological_order: In chronological order
800 label_reverse_chronological_order: In reverse chronological order
802 label_reverse_chronological_order: In reverse chronological order
801 label_planning: Planning
803 label_planning: Planning
802 label_incoming_emails: Incoming emails
804 label_incoming_emails: Incoming emails
803 label_generate_key: Generate a key
805 label_generate_key: Generate a key
804 label_issue_watchers: Watchers
806 label_issue_watchers: Watchers
805 label_example: Example
807 label_example: Example
806 label_display: Display
808 label_display: Display
807 label_sort: Sort
809 label_sort: Sort
808 label_ascending: Ascending
810 label_ascending: Ascending
809 label_descending: Descending
811 label_descending: Descending
810 label_date_from_to: From %{start} to %{end}
812 label_date_from_to: From %{start} to %{end}
811 label_wiki_content_added: Wiki page added
813 label_wiki_content_added: Wiki page added
812 label_wiki_content_updated: Wiki page updated
814 label_wiki_content_updated: Wiki page updated
813 label_group: Group
815 label_group: Group
814 label_group_plural: Groups
816 label_group_plural: Groups
815 label_group_new: New group
817 label_group_new: New group
816 label_time_entry_plural: Spent time
818 label_time_entry_plural: Spent time
817 label_version_sharing_none: Not shared
819 label_version_sharing_none: Not shared
818 label_version_sharing_descendants: With subprojects
820 label_version_sharing_descendants: With subprojects
819 label_version_sharing_hierarchy: With project hierarchy
821 label_version_sharing_hierarchy: With project hierarchy
820 label_version_sharing_tree: With project tree
822 label_version_sharing_tree: With project tree
821 label_version_sharing_system: With all projects
823 label_version_sharing_system: With all projects
822 label_update_issue_done_ratios: Update issue done ratios
824 label_update_issue_done_ratios: Update issue done ratios
823 label_copy_source: Source
825 label_copy_source: Source
824 label_copy_target: Target
826 label_copy_target: Target
825 label_copy_same_as_target: Same as target
827 label_copy_same_as_target: Same as target
826 label_display_used_statuses_only: Only display statuses that are used by this tracker
828 label_display_used_statuses_only: Only display statuses that are used by this tracker
827 label_api_access_key: API access key
829 label_api_access_key: API access key
828 label_missing_api_access_key: Missing an API access key
830 label_missing_api_access_key: Missing an API access key
829 label_api_access_key_created_on: "API access key created %{value} ago"
831 label_api_access_key_created_on: "API access key created %{value} ago"
830 label_profile: Profile
832 label_profile: Profile
831 label_subtask_plural: Subtasks
833 label_subtask_plural: Subtasks
832 label_project_copy_notifications: Send email notifications during the project copy
834 label_project_copy_notifications: Send email notifications during the project copy
833 label_principal_search: "Search for user or group:"
835 label_principal_search: "Search for user or group:"
834 label_user_search: "Search for user:"
836 label_user_search: "Search for user:"
835 label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author
837 label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author
836 label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee
838 label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee
837 label_issues_visibility_all: All issues
839 label_issues_visibility_all: All issues
838 label_issues_visibility_public: All non private issues
840 label_issues_visibility_public: All non private issues
839 label_issues_visibility_own: Issues created by or assigned to the user
841 label_issues_visibility_own: Issues created by or assigned to the user
840 label_git_report_last_commit: Report last commit for files and directories
842 label_git_report_last_commit: Report last commit for files and directories
841 label_parent_revision: Parent
843 label_parent_revision: Parent
842 label_child_revision: Child
844 label_child_revision: Child
843 label_export_options: "%{export_format} export options"
845 label_export_options: "%{export_format} export options"
844 label_copy_attachments: Copy attachments
846 label_copy_attachments: Copy attachments
845 label_item_position: "%{position} of %{count}"
847 label_item_position: "%{position} of %{count}"
846 label_completed_versions: Completed versions
848 label_completed_versions: Completed versions
847 label_search_for_watchers: Search for watchers to add
849 label_search_for_watchers: Search for watchers to add
848
850
849 button_login: Login
851 button_login: Login
850 button_submit: Submit
852 button_submit: Submit
851 button_save: Save
853 button_save: Save
852 button_check_all: Check all
854 button_check_all: Check all
853 button_uncheck_all: Uncheck all
855 button_uncheck_all: Uncheck all
854 button_collapse_all: Collapse all
856 button_collapse_all: Collapse all
855 button_expand_all: Expand all
857 button_expand_all: Expand all
856 button_delete: Delete
858 button_delete: Delete
857 button_create: Create
859 button_create: Create
858 button_create_and_continue: Create and continue
860 button_create_and_continue: Create and continue
859 button_test: Test
861 button_test: Test
860 button_edit: Edit
862 button_edit: Edit
861 button_edit_associated_wikipage: "Edit associated Wiki page: %{page_title}"
863 button_edit_associated_wikipage: "Edit associated Wiki page: %{page_title}"
862 button_add: Add
864 button_add: Add
863 button_change: Change
865 button_change: Change
864 button_apply: Apply
866 button_apply: Apply
865 button_clear: Clear
867 button_clear: Clear
866 button_lock: Lock
868 button_lock: Lock
867 button_unlock: Unlock
869 button_unlock: Unlock
868 button_download: Download
870 button_download: Download
869 button_list: List
871 button_list: List
870 button_view: View
872 button_view: View
871 button_move: Move
873 button_move: Move
872 button_move_and_follow: Move and follow
874 button_move_and_follow: Move and follow
873 button_back: Back
875 button_back: Back
874 button_cancel: Cancel
876 button_cancel: Cancel
875 button_activate: Activate
877 button_activate: Activate
876 button_sort: Sort
878 button_sort: Sort
877 button_log_time: Log time
879 button_log_time: Log time
878 button_rollback: Rollback to this version
880 button_rollback: Rollback to this version
879 button_watch: Watch
881 button_watch: Watch
880 button_unwatch: Unwatch
882 button_unwatch: Unwatch
881 button_reply: Reply
883 button_reply: Reply
882 button_archive: Archive
884 button_archive: Archive
883 button_unarchive: Unarchive
885 button_unarchive: Unarchive
884 button_reset: Reset
886 button_reset: Reset
885 button_rename: Rename
887 button_rename: Rename
886 button_change_password: Change password
888 button_change_password: Change password
887 button_copy: Copy
889 button_copy: Copy
888 button_copy_and_follow: Copy and follow
890 button_copy_and_follow: Copy and follow
889 button_annotate: Annotate
891 button_annotate: Annotate
890 button_update: Update
892 button_update: Update
891 button_configure: Configure
893 button_configure: Configure
892 button_quote: Quote
894 button_quote: Quote
893 button_duplicate: Duplicate
895 button_duplicate: Duplicate
894 button_show: Show
896 button_show: Show
895 button_edit_section: Edit this section
897 button_edit_section: Edit this section
896 button_export: Export
898 button_export: Export
899 button_delete_my_account: Delete my account
897
900
898 status_active: active
901 status_active: active
899 status_registered: registered
902 status_registered: registered
900 status_locked: locked
903 status_locked: locked
901
904
902 version_status_open: open
905 version_status_open: open
903 version_status_locked: locked
906 version_status_locked: locked
904 version_status_closed: closed
907 version_status_closed: closed
905
908
906 field_active: Active
909 field_active: Active
907
910
908 text_select_mail_notifications: Select actions for which email notifications should be sent.
911 text_select_mail_notifications: Select actions for which email notifications should be sent.
909 text_regexp_info: eg. ^[A-Z0-9]+$
912 text_regexp_info: eg. ^[A-Z0-9]+$
910 text_min_max_length_info: 0 means no restriction
913 text_min_max_length_info: 0 means no restriction
911 text_project_destroy_confirmation: Are you sure you want to delete this project and related data?
914 text_project_destroy_confirmation: Are you sure you want to delete this project and related data?
912 text_subprojects_destroy_warning: "Its subproject(s): %{value} will be also deleted."
915 text_subprojects_destroy_warning: "Its subproject(s): %{value} will be also deleted."
913 text_workflow_edit: Select a role and a tracker to edit the workflow
916 text_workflow_edit: Select a role and a tracker to edit the workflow
914 text_are_you_sure: Are you sure?
917 text_are_you_sure: Are you sure?
915 text_are_you_sure_with_children: "Delete issue and all child issues?"
918 text_are_you_sure_with_children: "Delete issue and all child issues?"
916 text_journal_changed: "%{label} changed from %{old} to %{new}"
919 text_journal_changed: "%{label} changed from %{old} to %{new}"
917 text_journal_changed_no_detail: "%{label} updated"
920 text_journal_changed_no_detail: "%{label} updated"
918 text_journal_set_to: "%{label} set to %{value}"
921 text_journal_set_to: "%{label} set to %{value}"
919 text_journal_deleted: "%{label} deleted (%{old})"
922 text_journal_deleted: "%{label} deleted (%{old})"
920 text_journal_added: "%{label} %{value} added"
923 text_journal_added: "%{label} %{value} added"
921 text_tip_issue_begin_day: issue beginning this day
924 text_tip_issue_begin_day: issue beginning this day
922 text_tip_issue_end_day: issue ending this day
925 text_tip_issue_end_day: issue ending this day
923 text_tip_issue_begin_end_day: issue beginning and ending this day
926 text_tip_issue_begin_end_day: issue beginning and ending this day
924 text_project_identifier_info: 'Only lower case letters (a-z), numbers, dashes and underscores are allowed.<br />Once saved, the identifier cannot be changed.'
927 text_project_identifier_info: 'Only lower case letters (a-z), numbers, dashes and underscores are allowed.<br />Once saved, the identifier cannot be changed.'
925 text_caracters_maximum: "%{count} characters maximum."
928 text_caracters_maximum: "%{count} characters maximum."
926 text_caracters_minimum: "Must be at least %{count} characters long."
929 text_caracters_minimum: "Must be at least %{count} characters long."
927 text_length_between: "Length between %{min} and %{max} characters."
930 text_length_between: "Length between %{min} and %{max} characters."
928 text_tracker_no_workflow: No workflow defined for this tracker
931 text_tracker_no_workflow: No workflow defined for this tracker
929 text_unallowed_characters: Unallowed characters
932 text_unallowed_characters: Unallowed characters
930 text_comma_separated: Multiple values allowed (comma separated).
933 text_comma_separated: Multiple values allowed (comma separated).
931 text_line_separated: Multiple values allowed (one line for each value).
934 text_line_separated: Multiple values allowed (one line for each value).
932 text_issues_ref_in_commit_messages: Referencing and fixing issues in commit messages
935 text_issues_ref_in_commit_messages: Referencing and fixing issues in commit messages
933 text_issue_added: "Issue %{id} has been reported by %{author}."
936 text_issue_added: "Issue %{id} has been reported by %{author}."
934 text_issue_updated: "Issue %{id} has been updated by %{author}."
937 text_issue_updated: "Issue %{id} has been updated by %{author}."
935 text_wiki_destroy_confirmation: Are you sure you want to delete this wiki and all its content?
938 text_wiki_destroy_confirmation: Are you sure you want to delete this wiki and all its content?
936 text_issue_category_destroy_question: "Some issues (%{count}) are assigned to this category. What do you want to do?"
939 text_issue_category_destroy_question: "Some issues (%{count}) are assigned to this category. What do you want to do?"
937 text_issue_category_destroy_assignments: Remove category assignments
940 text_issue_category_destroy_assignments: Remove category assignments
938 text_issue_category_reassign_to: Reassign issues to this category
941 text_issue_category_reassign_to: Reassign issues to this category
939 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)."
942 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)."
940 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."
943 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."
941 text_load_default_configuration: Load the default configuration
944 text_load_default_configuration: Load the default configuration
942 text_status_changed_by_changeset: "Applied in changeset %{value}."
945 text_status_changed_by_changeset: "Applied in changeset %{value}."
943 text_time_logged_by_changeset: "Applied in changeset %{value}."
946 text_time_logged_by_changeset: "Applied in changeset %{value}."
944 text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s)?'
947 text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s)?'
945 text_issues_destroy_descendants_confirmation: "This will also delete %{count} subtask(s)."
948 text_issues_destroy_descendants_confirmation: "This will also delete %{count} subtask(s)."
946 text_time_entries_destroy_confirmation: 'Are you sure you want to delete the selected time entr(y/ies)?'
949 text_time_entries_destroy_confirmation: 'Are you sure you want to delete the selected time entr(y/ies)?'
947 text_select_project_modules: 'Select modules to enable for this project:'
950 text_select_project_modules: 'Select modules to enable for this project:'
948 text_default_administrator_account_changed: Default administrator account changed
951 text_default_administrator_account_changed: Default administrator account changed
949 text_file_repository_writable: Attachments directory writable
952 text_file_repository_writable: Attachments directory writable
950 text_plugin_assets_writable: Plugin assets directory writable
953 text_plugin_assets_writable: Plugin assets directory writable
951 text_rmagick_available: RMagick available (optional)
954 text_rmagick_available: RMagick available (optional)
952 text_destroy_time_entries_question: "%{hours} hours were reported on the issues you are about to delete. What do you want to do?"
955 text_destroy_time_entries_question: "%{hours} hours were reported on the issues you are about to delete. What do you want to do?"
953 text_destroy_time_entries: Delete reported hours
956 text_destroy_time_entries: Delete reported hours
954 text_assign_time_entries_to_project: Assign reported hours to the project
957 text_assign_time_entries_to_project: Assign reported hours to the project
955 text_reassign_time_entries: 'Reassign reported hours to this issue:'
958 text_reassign_time_entries: 'Reassign reported hours to this issue:'
956 text_user_wrote: "%{value} wrote:"
959 text_user_wrote: "%{value} wrote:"
957 text_enumeration_destroy_question: "%{count} objects are assigned to this value."
960 text_enumeration_destroy_question: "%{count} objects are assigned to this value."
958 text_enumeration_category_reassign_to: 'Reassign them to this value:'
961 text_enumeration_category_reassign_to: 'Reassign them to this value:'
959 text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/configuration.yml and restart the application to enable them."
962 text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/configuration.yml and restart the application to enable them."
960 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."
963 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."
961 text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.'
964 text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.'
962 text_custom_field_possible_values_info: 'One line for each value'
965 text_custom_field_possible_values_info: 'One line for each value'
963 text_wiki_page_destroy_question: "This page has %{descendants} child page(s) and descendant(s). What do you want to do?"
966 text_wiki_page_destroy_question: "This page has %{descendants} child page(s) and descendant(s). What do you want to do?"
964 text_wiki_page_nullify_children: "Keep child pages as root pages"
967 text_wiki_page_nullify_children: "Keep child pages as root pages"
965 text_wiki_page_destroy_children: "Delete child pages and all their descendants"
968 text_wiki_page_destroy_children: "Delete child pages and all their descendants"
966 text_wiki_page_reassign_children: "Reassign child pages to this parent page"
969 text_wiki_page_reassign_children: "Reassign child pages to this parent page"
967 text_own_membership_delete_confirmation: "You are about to remove some or all of your permissions and may no longer be able to edit this project after that.\nAre you sure you want to continue?"
970 text_own_membership_delete_confirmation: "You are about to remove some or all of your permissions and may no longer be able to edit this project after that.\nAre you sure you want to continue?"
968 text_zoom_in: Zoom in
971 text_zoom_in: Zoom in
969 text_zoom_out: Zoom out
972 text_zoom_out: Zoom out
970 text_warn_on_leaving_unsaved: "The current page contains unsaved text that will be lost if you leave this page."
973 text_warn_on_leaving_unsaved: "The current page contains unsaved text that will be lost if you leave this page."
971 text_scm_path_encoding_note: "Default: UTF-8"
974 text_scm_path_encoding_note: "Default: UTF-8"
972 text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo)
975 text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo)
973 text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo)
976 text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo)
974 text_scm_command: Command
977 text_scm_command: Command
975 text_scm_command_version: Version
978 text_scm_command_version: Version
976 text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it.
979 text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it.
977 text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel.
980 text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel.
978 text_issue_conflict_resolution_overwrite: "Apply my changes anyway (previous notes will be kept but some changes may be overwritten)"
981 text_issue_conflict_resolution_overwrite: "Apply my changes anyway (previous notes will be kept but some changes may be overwritten)"
979 text_issue_conflict_resolution_add_notes: "Add my notes and discard my other changes"
982 text_issue_conflict_resolution_add_notes: "Add my notes and discard my other changes"
980 text_issue_conflict_resolution_cancel: "Discard all my changes and redisplay %{link}"
983 text_issue_conflict_resolution_cancel: "Discard all my changes and redisplay %{link}"
984 text_account_destroy_confirmation: "Are you sure you want to proceed?\nYour account will be permanently deleted, with no way to reactivate it."
981
985
982 default_role_manager: Manager
986 default_role_manager: Manager
983 default_role_developer: Developer
987 default_role_developer: Developer
984 default_role_reporter: Reporter
988 default_role_reporter: Reporter
985 default_tracker_bug: Bug
989 default_tracker_bug: Bug
986 default_tracker_feature: Feature
990 default_tracker_feature: Feature
987 default_tracker_support: Support
991 default_tracker_support: Support
988 default_issue_status_new: New
992 default_issue_status_new: New
989 default_issue_status_in_progress: In Progress
993 default_issue_status_in_progress: In Progress
990 default_issue_status_resolved: Resolved
994 default_issue_status_resolved: Resolved
991 default_issue_status_feedback: Feedback
995 default_issue_status_feedback: Feedback
992 default_issue_status_closed: Closed
996 default_issue_status_closed: Closed
993 default_issue_status_rejected: Rejected
997 default_issue_status_rejected: Rejected
994 default_doc_category_user: User documentation
998 default_doc_category_user: User documentation
995 default_doc_category_tech: Technical documentation
999 default_doc_category_tech: Technical documentation
996 default_priority_low: Low
1000 default_priority_low: Low
997 default_priority_normal: Normal
1001 default_priority_normal: Normal
998 default_priority_high: High
1002 default_priority_high: High
999 default_priority_urgent: Urgent
1003 default_priority_urgent: Urgent
1000 default_priority_immediate: Immediate
1004 default_priority_immediate: Immediate
1001 default_activity_design: Design
1005 default_activity_design: Design
1002 default_activity_development: Development
1006 default_activity_development: Development
1003
1007
1004 enumeration_issue_priorities: Issue priorities
1008 enumeration_issue_priorities: Issue priorities
1005 enumeration_doc_categories: Document categories
1009 enumeration_doc_categories: Document categories
1006 enumeration_activities: Activities (time tracking)
1010 enumeration_activities: Activities (time tracking)
1007 enumeration_system_activity: System Activity
1011 enumeration_system_activity: System Activity
1008 description_filter: Filter
1012 description_filter: Filter
1009 description_search: Searchfield
1013 description_search: Searchfield
1010 description_choose_project: Projects
1014 description_choose_project: Projects
1011 description_project_scope: Search scope
1015 description_project_scope: Search scope
1012 description_notes: Notes
1016 description_notes: Notes
1013 description_message_content: Message content
1017 description_message_content: Message content
1014 description_query_sort_criteria_attribute: Sort attribute
1018 description_query_sort_criteria_attribute: Sort attribute
1015 description_query_sort_criteria_direction: Sort direction
1019 description_query_sort_criteria_direction: Sort direction
1016 description_user_mail_notification: Mail notification settings
1020 description_user_mail_notification: Mail notification settings
1017 description_available_columns: Available Columns
1021 description_available_columns: Available Columns
1018 description_selected_columns: Selected Columns
1022 description_selected_columns: Selected Columns
1019 description_all_columns: All Columns
1023 description_all_columns: All Columns
1020 description_issue_category_reassign: Choose issue category
1024 description_issue_category_reassign: Choose issue category
1021 description_wiki_subpages_reassign: Choose new parent page
1025 description_wiki_subpages_reassign: Choose new parent page
1022 description_date_range_list: Choose range from list
1026 description_date_range_list: Choose range from list
1023 description_date_range_interval: Choose range by selecting start and end date
1027 description_date_range_interval: Choose range by selecting start and end date
1024 description_date_from: Enter start date
1028 description_date_from: Enter start date
1025 description_date_to: Enter end date
1029 description_date_to: Enter end date
@@ -1,1042 +1,1046
1 # French translations for Ruby on Rails
1 # French translations for Ruby on Rails
2 # by Christian Lescuyer (christian@flyingcoders.com)
2 # by Christian Lescuyer (christian@flyingcoders.com)
3 # contributor: Sebastien Grosjean - ZenCocoon.com
3 # contributor: Sebastien Grosjean - ZenCocoon.com
4 # contributor: Thibaut Cuvelier - Developpez.com
4 # contributor: Thibaut Cuvelier - Developpez.com
5
5
6 fr:
6 fr:
7 direction: ltr
7 direction: ltr
8 date:
8 date:
9 formats:
9 formats:
10 default: "%d/%m/%Y"
10 default: "%d/%m/%Y"
11 short: "%e %b"
11 short: "%e %b"
12 long: "%e %B %Y"
12 long: "%e %B %Y"
13 long_ordinal: "%e %B %Y"
13 long_ordinal: "%e %B %Y"
14 only_day: "%e"
14 only_day: "%e"
15
15
16 day_names: [dimanche, lundi, mardi, mercredi, jeudi, vendredi, samedi]
16 day_names: [dimanche, lundi, mardi, mercredi, jeudi, vendredi, samedi]
17 abbr_day_names: [dim, lun, mar, mer, jeu, ven, sam]
17 abbr_day_names: [dim, lun, mar, mer, jeu, ven, sam]
18 month_names: [~, janvier, fΓ©vrier, mars, avril, mai, juin, juillet, aoΓ»t, septembre, octobre, novembre, dΓ©cembre]
18 month_names: [~, janvier, fΓ©vrier, mars, avril, mai, juin, juillet, aoΓ»t, septembre, octobre, novembre, dΓ©cembre]
19 abbr_month_names: [~, jan., fΓ©v., mar., avr., mai, juin, juil., aoΓ»t, sept., oct., nov., dΓ©c.]
19 abbr_month_names: [~, jan., fΓ©v., mar., avr., mai, juin, juil., aoΓ»t, sept., oct., nov., dΓ©c.]
20 order:
20 order:
21 - :day
21 - :day
22 - :month
22 - :month
23 - :year
23 - :year
24
24
25 time:
25 time:
26 formats:
26 formats:
27 default: "%d/%m/%Y %H:%M"
27 default: "%d/%m/%Y %H:%M"
28 time: "%H:%M"
28 time: "%H:%M"
29 short: "%d %b %H:%M"
29 short: "%d %b %H:%M"
30 long: "%A %d %B %Y %H:%M:%S %Z"
30 long: "%A %d %B %Y %H:%M:%S %Z"
31 long_ordinal: "%A %d %B %Y %H:%M:%S %Z"
31 long_ordinal: "%A %d %B %Y %H:%M:%S %Z"
32 only_second: "%S"
32 only_second: "%S"
33 am: 'am'
33 am: 'am'
34 pm: 'pm'
34 pm: 'pm'
35
35
36 datetime:
36 datetime:
37 distance_in_words:
37 distance_in_words:
38 half_a_minute: "30 secondes"
38 half_a_minute: "30 secondes"
39 less_than_x_seconds:
39 less_than_x_seconds:
40 zero: "moins d'une seconde"
40 zero: "moins d'une seconde"
41 one: "moins d'uneΒ seconde"
41 one: "moins d'uneΒ seconde"
42 other: "moins de %{count}Β secondes"
42 other: "moins de %{count}Β secondes"
43 x_seconds:
43 x_seconds:
44 one: "1Β seconde"
44 one: "1Β seconde"
45 other: "%{count}Β secondes"
45 other: "%{count}Β secondes"
46 less_than_x_minutes:
46 less_than_x_minutes:
47 zero: "moins d'une minute"
47 zero: "moins d'une minute"
48 one: "moins d'uneΒ minute"
48 one: "moins d'uneΒ minute"
49 other: "moins de %{count}Β minutes"
49 other: "moins de %{count}Β minutes"
50 x_minutes:
50 x_minutes:
51 one: "1Β minute"
51 one: "1Β minute"
52 other: "%{count}Β minutes"
52 other: "%{count}Β minutes"
53 about_x_hours:
53 about_x_hours:
54 one: "environ une heure"
54 one: "environ une heure"
55 other: "environ %{count}Β heures"
55 other: "environ %{count}Β heures"
56 x_days:
56 x_days:
57 one: "unΒ jour"
57 one: "unΒ jour"
58 other: "%{count}Β jours"
58 other: "%{count}Β jours"
59 about_x_months:
59 about_x_months:
60 one: "environ un mois"
60 one: "environ un mois"
61 other: "environ %{count}Β mois"
61 other: "environ %{count}Β mois"
62 x_months:
62 x_months:
63 one: "unΒ mois"
63 one: "unΒ mois"
64 other: "%{count}Β mois"
64 other: "%{count}Β mois"
65 about_x_years:
65 about_x_years:
66 one: "environ un an"
66 one: "environ un an"
67 other: "environ %{count}Β ans"
67 other: "environ %{count}Β ans"
68 over_x_years:
68 over_x_years:
69 one: "plus d'un an"
69 one: "plus d'un an"
70 other: "plus de %{count}Β ans"
70 other: "plus de %{count}Β ans"
71 almost_x_years:
71 almost_x_years:
72 one: "presqu'un an"
72 one: "presqu'un an"
73 other: "presque %{count} ans"
73 other: "presque %{count} ans"
74 prompts:
74 prompts:
75 year: "AnnΓ©e"
75 year: "AnnΓ©e"
76 month: "Mois"
76 month: "Mois"
77 day: "Jour"
77 day: "Jour"
78 hour: "Heure"
78 hour: "Heure"
79 minute: "Minute"
79 minute: "Minute"
80 second: "Seconde"
80 second: "Seconde"
81
81
82 number:
82 number:
83 format:
83 format:
84 precision: 3
84 precision: 3
85 separator: ','
85 separator: ','
86 delimiter: 'Β '
86 delimiter: 'Β '
87 currency:
87 currency:
88 format:
88 format:
89 unit: '€'
89 unit: '€'
90 precision: 2
90 precision: 2
91 format: '%nΒ %u'
91 format: '%nΒ %u'
92 human:
92 human:
93 format:
93 format:
94 precision: 2
94 precision: 2
95 storage_units:
95 storage_units:
96 format: "%n %u"
96 format: "%n %u"
97 units:
97 units:
98 byte:
98 byte:
99 one: "octet"
99 one: "octet"
100 other: "octet"
100 other: "octet"
101 kb: "ko"
101 kb: "ko"
102 mb: "Mo"
102 mb: "Mo"
103 gb: "Go"
103 gb: "Go"
104 tb: "To"
104 tb: "To"
105
105
106 support:
106 support:
107 array:
107 array:
108 sentence_connector: 'et'
108 sentence_connector: 'et'
109 skip_last_comma: true
109 skip_last_comma: true
110 word_connector: ", "
110 word_connector: ", "
111 two_words_connector: " et "
111 two_words_connector: " et "
112 last_word_connector: " et "
112 last_word_connector: " et "
113
113
114 activerecord:
114 activerecord:
115 errors:
115 errors:
116 template:
116 template:
117 header:
117 header:
118 one: "Impossible d'enregistrer %{model} : une erreur"
118 one: "Impossible d'enregistrer %{model} : une erreur"
119 other: "Impossible d'enregistrer %{model} : %{count} erreurs."
119 other: "Impossible d'enregistrer %{model} : %{count} erreurs."
120 body: "Veuillez vΓ©rifier les champs suivantsΒ :"
120 body: "Veuillez vΓ©rifier les champs suivantsΒ :"
121 messages:
121 messages:
122 inclusion: "n'est pas inclus(e) dans la liste"
122 inclusion: "n'est pas inclus(e) dans la liste"
123 exclusion: "n'est pas disponible"
123 exclusion: "n'est pas disponible"
124 invalid: "n'est pas valide"
124 invalid: "n'est pas valide"
125 confirmation: "ne concorde pas avec la confirmation"
125 confirmation: "ne concorde pas avec la confirmation"
126 accepted: "doit Γͺtre acceptΓ©(e)"
126 accepted: "doit Γͺtre acceptΓ©(e)"
127 empty: "doit Γͺtre renseignΓ©(e)"
127 empty: "doit Γͺtre renseignΓ©(e)"
128 blank: "doit Γͺtre renseignΓ©(e)"
128 blank: "doit Γͺtre renseignΓ©(e)"
129 too_long: "est trop long (pas plus de %{count} caractères)"
129 too_long: "est trop long (pas plus de %{count} caractères)"
130 too_short: "est trop court (au moins %{count} caractères)"
130 too_short: "est trop court (au moins %{count} caractères)"
131 wrong_length: "ne fait pas la bonne longueur (doit comporter %{count} caractères)"
131 wrong_length: "ne fait pas la bonne longueur (doit comporter %{count} caractères)"
132 taken: "est dΓ©jΓ  utilisΓ©"
132 taken: "est dΓ©jΓ  utilisΓ©"
133 not_a_number: "n'est pas un nombre"
133 not_a_number: "n'est pas un nombre"
134 not_a_date: "n'est pas une date valide"
134 not_a_date: "n'est pas une date valide"
135 greater_than: "doit Γͺtre supΓ©rieur Γ  %{count}"
135 greater_than: "doit Γͺtre supΓ©rieur Γ  %{count}"
136 greater_than_or_equal_to: "doit Γͺtre supΓ©rieur ou Γ©gal Γ  %{count}"
136 greater_than_or_equal_to: "doit Γͺtre supΓ©rieur ou Γ©gal Γ  %{count}"
137 equal_to: "doit Γͺtre Γ©gal Γ  %{count}"
137 equal_to: "doit Γͺtre Γ©gal Γ  %{count}"
138 less_than: "doit Γͺtre infΓ©rieur Γ  %{count}"
138 less_than: "doit Γͺtre infΓ©rieur Γ  %{count}"
139 less_than_or_equal_to: "doit Γͺtre infΓ©rieur ou Γ©gal Γ  %{count}"
139 less_than_or_equal_to: "doit Γͺtre infΓ©rieur ou Γ©gal Γ  %{count}"
140 odd: "doit Γͺtre impair"
140 odd: "doit Γͺtre impair"
141 even: "doit Γͺtre pair"
141 even: "doit Γͺtre pair"
142 greater_than_start_date: "doit Γͺtre postΓ©rieure Γ  la date de dΓ©but"
142 greater_than_start_date: "doit Γͺtre postΓ©rieure Γ  la date de dΓ©but"
143 not_same_project: "n'appartient pas au mΓͺme projet"
143 not_same_project: "n'appartient pas au mΓͺme projet"
144 circular_dependency: "Cette relation crΓ©erait une dΓ©pendance circulaire"
144 circular_dependency: "Cette relation crΓ©erait une dΓ©pendance circulaire"
145 cant_link_an_issue_with_a_descendant: "Une demande ne peut pas Γͺtre liΓ©e Γ  l'une de ses sous-tΓ’ches"
145 cant_link_an_issue_with_a_descendant: "Une demande ne peut pas Γͺtre liΓ©e Γ  l'une de ses sous-tΓ’ches"
146
146
147 actionview_instancetag_blank_option: Choisir
147 actionview_instancetag_blank_option: Choisir
148
148
149 general_text_No: 'Non'
149 general_text_No: 'Non'
150 general_text_Yes: 'Oui'
150 general_text_Yes: 'Oui'
151 general_text_no: 'non'
151 general_text_no: 'non'
152 general_text_yes: 'oui'
152 general_text_yes: 'oui'
153 general_lang_name: 'FranΓ§ais'
153 general_lang_name: 'FranΓ§ais'
154 general_csv_separator: ';'
154 general_csv_separator: ';'
155 general_csv_decimal_separator: ','
155 general_csv_decimal_separator: ','
156 general_csv_encoding: ISO-8859-1
156 general_csv_encoding: ISO-8859-1
157 general_pdf_encoding: UTF-8
157 general_pdf_encoding: UTF-8
158 general_first_day_of_week: '1'
158 general_first_day_of_week: '1'
159
159
160 notice_account_updated: Le compte a été mis à jour avec succès.
160 notice_account_updated: Le compte a été mis à jour avec succès.
161 notice_account_invalid_creditentials: Identifiant ou mot de passe invalide.
161 notice_account_invalid_creditentials: Identifiant ou mot de passe invalide.
162 notice_account_password_updated: Mot de passe mis à jour avec succès.
162 notice_account_password_updated: Mot de passe mis à jour avec succès.
163 notice_account_wrong_password: Mot de passe incorrect
163 notice_account_wrong_password: Mot de passe incorrect
164 notice_account_register_done: Un message contenant les instructions pour activer votre compte vous a Γ©tΓ© envoyΓ©.
164 notice_account_register_done: Un message contenant les instructions pour activer votre compte vous a Γ©tΓ© envoyΓ©.
165 notice_account_unknown_email: Aucun compte ne correspond Γ  cette adresse.
165 notice_account_unknown_email: Aucun compte ne correspond Γ  cette adresse.
166 notice_can_t_change_password: Ce compte utilise une authentification externe. Impossible de changer le mot de passe.
166 notice_can_t_change_password: Ce compte utilise une authentification externe. Impossible de changer le mot de passe.
167 notice_account_lost_email_sent: Un message contenant les instructions pour choisir un nouveau mot de passe vous a Γ©tΓ© envoyΓ©.
167 notice_account_lost_email_sent: Un message contenant les instructions pour choisir un nouveau mot de passe vous a Γ©tΓ© envoyΓ©.
168 notice_account_activated: Votre compte a Γ©tΓ© activΓ©. Vous pouvez Γ  prΓ©sent vous connecter.
168 notice_account_activated: Votre compte a Γ©tΓ© activΓ©. Vous pouvez Γ  prΓ©sent vous connecter.
169 notice_successful_create: Création effectuée avec succès.
169 notice_successful_create: Création effectuée avec succès.
170 notice_successful_update: Mise à jour effectuée avec succès.
170 notice_successful_update: Mise à jour effectuée avec succès.
171 notice_successful_delete: Suppression effectuée avec succès.
171 notice_successful_delete: Suppression effectuée avec succès.
172 notice_successful_connection: Connexion rΓ©ussie.
172 notice_successful_connection: Connexion rΓ©ussie.
173 notice_file_not_found: "La page Γ  laquelle vous souhaitez accΓ©der n'existe pas ou a Γ©tΓ© supprimΓ©e."
173 notice_file_not_found: "La page Γ  laquelle vous souhaitez accΓ©der n'existe pas ou a Γ©tΓ© supprimΓ©e."
174 notice_locking_conflict: Les donnΓ©es ont Γ©tΓ© mises Γ  jour par un autre utilisateur. Mise Γ  jour impossible.
174 notice_locking_conflict: Les donnΓ©es ont Γ©tΓ© mises Γ  jour par un autre utilisateur. Mise Γ  jour impossible.
175 notice_not_authorized: "Vous n'Γͺtes pas autorisΓ© Γ  accΓ©der Γ  cette page."
175 notice_not_authorized: "Vous n'Γͺtes pas autorisΓ© Γ  accΓ©der Γ  cette page."
176 notice_not_authorized_archived_project: Le projet auquel vous tentez d'accΓ©der a Γ©tΓ© archivΓ©.
176 notice_not_authorized_archived_project: Le projet auquel vous tentez d'accΓ©der a Γ©tΓ© archivΓ©.
177 notice_email_sent: "Un email a Γ©tΓ© envoyΓ© Γ  %{value}"
177 notice_email_sent: "Un email a Γ©tΓ© envoyΓ© Γ  %{value}"
178 notice_email_error: "Erreur lors de l'envoi de l'email (%{value})"
178 notice_email_error: "Erreur lors de l'envoi de l'email (%{value})"
179 notice_feeds_access_key_reseted: "Votre clé d'accès aux flux RSS a été réinitialisée."
179 notice_feeds_access_key_reseted: "Votre clé d'accès aux flux RSS a été réinitialisée."
180 notice_failed_to_save_issues: "%{count} demande(s) sur les %{total} sΓ©lectionnΓ©es n'ont pas pu Γͺtre mise(s) Γ  jour : %{ids}."
180 notice_failed_to_save_issues: "%{count} demande(s) sur les %{total} sΓ©lectionnΓ©es n'ont pas pu Γͺtre mise(s) Γ  jour : %{ids}."
181 notice_failed_to_save_time_entries: "%{count} temps passΓ©(s) sur les %{total} sΓ©lectionnΓ©s n'ont pas pu Γͺtre mis Γ  jour: %{ids}."
181 notice_failed_to_save_time_entries: "%{count} temps passΓ©(s) sur les %{total} sΓ©lectionnΓ©s n'ont pas pu Γͺtre mis Γ  jour: %{ids}."
182 notice_no_issue_selected: "Aucune demande sΓ©lectionnΓ©e ! Cochez les demandes que vous voulez mettre Γ  jour."
182 notice_no_issue_selected: "Aucune demande sΓ©lectionnΓ©e ! Cochez les demandes que vous voulez mettre Γ  jour."
183 notice_account_pending: "Votre compte a été créé et attend l'approbation de l'administrateur."
183 notice_account_pending: "Votre compte a été créé et attend l'approbation de l'administrateur."
184 notice_default_data_loaded: Paramétrage par défaut chargé avec succès.
184 notice_default_data_loaded: Paramétrage par défaut chargé avec succès.
185 notice_unable_delete_version: Impossible de supprimer cette version.
185 notice_unable_delete_version: Impossible de supprimer cette version.
186 notice_issue_done_ratios_updated: L'avancement des demandes a Γ©tΓ© mis Γ  jour.
186 notice_issue_done_ratios_updated: L'avancement des demandes a Γ©tΓ© mis Γ  jour.
187 notice_api_access_key_reseted: Votre clé d'accès API a été réinitialisée.
187 notice_api_access_key_reseted: Votre clé d'accès API a été réinitialisée.
188 notice_gantt_chart_truncated: "Le diagramme a Γ©tΓ© tronquΓ© car il excΓ¨de le nombre maximal d'Γ©lΓ©ments pouvant Γͺtre affichΓ©s (%{max})"
188 notice_gantt_chart_truncated: "Le diagramme a Γ©tΓ© tronquΓ© car il excΓ¨de le nombre maximal d'Γ©lΓ©ments pouvant Γͺtre affichΓ©s (%{max})"
189 notice_issue_successful_create: "La demande %{id} a été créée."
189 notice_issue_successful_create: "La demande %{id} a été créée."
190 notice_issue_update_conflict: "La demande a Γ©tΓ© mise Γ  jour par un autre utilisateur pendant que vous la modifiez."
190 notice_issue_update_conflict: "La demande a Γ©tΓ© mise Γ  jour par un autre utilisateur pendant que vous la modifiez."
191 notice_account_deleted: "Votre compte a Γ©tΓ© dΓ©finitivement supprimΓ©."
191
192
192 error_can_t_load_default_data: "Une erreur s'est produite lors du chargement du paramΓ©trage : %{value}"
193 error_can_t_load_default_data: "Une erreur s'est produite lors du chargement du paramΓ©trage : %{value}"
193 error_scm_not_found: "L'entrΓ©e et/ou la rΓ©vision demandΓ©e n'existe pas dans le dΓ©pΓ΄t."
194 error_scm_not_found: "L'entrΓ©e et/ou la rΓ©vision demandΓ©e n'existe pas dans le dΓ©pΓ΄t."
194 error_scm_command_failed: "Une erreur s'est produite lors de l'accès au dépôt : %{value}"
195 error_scm_command_failed: "Une erreur s'est produite lors de l'accès au dépôt : %{value}"
195 error_scm_annotate: "L'entrΓ©e n'existe pas ou ne peut pas Γͺtre annotΓ©e."
196 error_scm_annotate: "L'entrΓ©e n'existe pas ou ne peut pas Γͺtre annotΓ©e."
196 error_issue_not_found_in_project: "La demande n'existe pas ou n'appartient pas Γ  ce projet"
197 error_issue_not_found_in_project: "La demande n'existe pas ou n'appartient pas Γ  ce projet"
197 error_can_not_reopen_issue_on_closed_version: 'Une demande assignΓ©e Γ  une version fermΓ©e ne peut pas Γͺtre rΓ©ouverte'
198 error_can_not_reopen_issue_on_closed_version: 'Une demande assignΓ©e Γ  une version fermΓ©e ne peut pas Γͺtre rΓ©ouverte'
198 error_can_not_archive_project: "Ce projet ne peut pas Γͺtre archivΓ©"
199 error_can_not_archive_project: "Ce projet ne peut pas Γͺtre archivΓ©"
199 error_workflow_copy_source: 'Veuillez sΓ©lectionner un tracker et/ou un rΓ΄le source'
200 error_workflow_copy_source: 'Veuillez sΓ©lectionner un tracker et/ou un rΓ΄le source'
200 error_workflow_copy_target: 'Veuillez sΓ©lectionner les trackers et rΓ΄les cibles'
201 error_workflow_copy_target: 'Veuillez sΓ©lectionner les trackers et rΓ΄les cibles'
201 error_issue_done_ratios_not_updated: L'avancement des demandes n'a pas pu Γͺtre mis Γ  jour.
202 error_issue_done_ratios_not_updated: L'avancement des demandes n'a pas pu Γͺtre mis Γ  jour.
202 error_attachment_too_big: Ce fichier ne peut pas Γͺtre attachΓ© car il excΓ¨de la taille maximale autorisΓ©e (%{max_size})
203 error_attachment_too_big: Ce fichier ne peut pas Γͺtre attachΓ© car il excΓ¨de la taille maximale autorisΓ©e (%{max_size})
203
204
204 warning_attachments_not_saved: "%{count} fichier(s) n'ont pas pu Γͺtre sauvegardΓ©s."
205 warning_attachments_not_saved: "%{count} fichier(s) n'ont pas pu Γͺtre sauvegardΓ©s."
205
206
206 mail_subject_lost_password: "Votre mot de passe %{value}"
207 mail_subject_lost_password: "Votre mot de passe %{value}"
207 mail_body_lost_password: 'Pour changer votre mot de passe, cliquez sur le lien suivant :'
208 mail_body_lost_password: 'Pour changer votre mot de passe, cliquez sur le lien suivant :'
208 mail_subject_register: "Activation de votre compte %{value}"
209 mail_subject_register: "Activation de votre compte %{value}"
209 mail_body_register: 'Pour activer votre compte, cliquez sur le lien suivant :'
210 mail_body_register: 'Pour activer votre compte, cliquez sur le lien suivant :'
210 mail_body_account_information_external: "Vous pouvez utiliser votre compte %{value} pour vous connecter."
211 mail_body_account_information_external: "Vous pouvez utiliser votre compte %{value} pour vous connecter."
211 mail_body_account_information: Paramètres de connexion de votre compte
212 mail_body_account_information: Paramètres de connexion de votre compte
212 mail_subject_account_activation_request: "Demande d'activation d'un compte %{value}"
213 mail_subject_account_activation_request: "Demande d'activation d'un compte %{value}"
213 mail_body_account_activation_request: "Un nouvel utilisateur (%{value}) s'est inscrit. Son compte nΓ©cessite votre approbation :"
214 mail_body_account_activation_request: "Un nouvel utilisateur (%{value}) s'est inscrit. Son compte nΓ©cessite votre approbation :"
214 mail_subject_reminder: "%{count} demande(s) arrivent Γ  Γ©chΓ©ance (%{days})"
215 mail_subject_reminder: "%{count} demande(s) arrivent Γ  Γ©chΓ©ance (%{days})"
215 mail_body_reminder: "%{count} demande(s) qui vous sont assignΓ©es arrivent Γ  Γ©chΓ©ance dans les %{days} prochains jours :"
216 mail_body_reminder: "%{count} demande(s) qui vous sont assignΓ©es arrivent Γ  Γ©chΓ©ance dans les %{days} prochains jours :"
216 mail_subject_wiki_content_added: "Page wiki '%{id}' ajoutΓ©e"
217 mail_subject_wiki_content_added: "Page wiki '%{id}' ajoutΓ©e"
217 mail_body_wiki_content_added: "La page wiki '%{id}' a Γ©tΓ© ajoutΓ©e par %{author}."
218 mail_body_wiki_content_added: "La page wiki '%{id}' a Γ©tΓ© ajoutΓ©e par %{author}."
218 mail_subject_wiki_content_updated: "Page wiki '%{id}' mise Γ  jour"
219 mail_subject_wiki_content_updated: "Page wiki '%{id}' mise Γ  jour"
219 mail_body_wiki_content_updated: "La page wiki '%{id}' a Γ©tΓ© mise Γ  jour par %{author}."
220 mail_body_wiki_content_updated: "La page wiki '%{id}' a Γ©tΓ© mise Γ  jour par %{author}."
220
221
221 gui_validation_error: 1 erreur
222 gui_validation_error: 1 erreur
222 gui_validation_error_plural: "%{count} erreurs"
223 gui_validation_error_plural: "%{count} erreurs"
223
224
224 field_name: Nom
225 field_name: Nom
225 field_description: Description
226 field_description: Description
226 field_summary: RΓ©sumΓ©
227 field_summary: RΓ©sumΓ©
227 field_is_required: Obligatoire
228 field_is_required: Obligatoire
228 field_firstname: PrΓ©nom
229 field_firstname: PrΓ©nom
229 field_lastname: Nom
230 field_lastname: Nom
230 field_mail: "Email "
231 field_mail: "Email "
231 field_filename: Fichier
232 field_filename: Fichier
232 field_filesize: Taille
233 field_filesize: Taille
233 field_downloads: TΓ©lΓ©chargements
234 field_downloads: TΓ©lΓ©chargements
234 field_author: Auteur
235 field_author: Auteur
235 field_created_on: "Créé "
236 field_created_on: "Créé "
236 field_updated_on: "Mis-Γ -jour "
237 field_updated_on: "Mis-Γ -jour "
237 field_field_format: Format
238 field_field_format: Format
238 field_is_for_all: Pour tous les projets
239 field_is_for_all: Pour tous les projets
239 field_possible_values: Valeurs possibles
240 field_possible_values: Valeurs possibles
240 field_regexp: Expression régulière
241 field_regexp: Expression régulière
241 field_min_length: Longueur minimum
242 field_min_length: Longueur minimum
242 field_max_length: Longueur maximum
243 field_max_length: Longueur maximum
243 field_value: Valeur
244 field_value: Valeur
244 field_category: CatΓ©gorie
245 field_category: CatΓ©gorie
245 field_title: Titre
246 field_title: Titre
246 field_project: Projet
247 field_project: Projet
247 field_issue: Demande
248 field_issue: Demande
248 field_status: Statut
249 field_status: Statut
249 field_notes: Notes
250 field_notes: Notes
250 field_is_closed: Demande fermΓ©e
251 field_is_closed: Demande fermΓ©e
251 field_is_default: Valeur par dΓ©faut
252 field_is_default: Valeur par dΓ©faut
252 field_tracker: Tracker
253 field_tracker: Tracker
253 field_subject: Sujet
254 field_subject: Sujet
254 field_due_date: EchΓ©ance
255 field_due_date: EchΓ©ance
255 field_assigned_to: AssignΓ© Γ 
256 field_assigned_to: AssignΓ© Γ 
256 field_priority: PrioritΓ©
257 field_priority: PrioritΓ©
257 field_fixed_version: Version cible
258 field_fixed_version: Version cible
258 field_user: Utilisateur
259 field_user: Utilisateur
259 field_role: RΓ΄le
260 field_role: RΓ΄le
260 field_homepage: "Site web "
261 field_homepage: "Site web "
261 field_is_public: Public
262 field_is_public: Public
262 field_parent: Sous-projet de
263 field_parent: Sous-projet de
263 field_is_in_roadmap: Demandes affichΓ©es dans la roadmap
264 field_is_in_roadmap: Demandes affichΓ©es dans la roadmap
264 field_login: "Identifiant "
265 field_login: "Identifiant "
265 field_mail_notification: Notifications par mail
266 field_mail_notification: Notifications par mail
266 field_admin: Administrateur
267 field_admin: Administrateur
267 field_last_login_on: "Dernière connexion "
268 field_last_login_on: "Dernière connexion "
268 field_language: Langue
269 field_language: Langue
269 field_effective_date: Date
270 field_effective_date: Date
270 field_password: Mot de passe
271 field_password: Mot de passe
271 field_new_password: Nouveau mot de passe
272 field_new_password: Nouveau mot de passe
272 field_password_confirmation: Confirmation
273 field_password_confirmation: Confirmation
273 field_version: Version
274 field_version: Version
274 field_type: Type
275 field_type: Type
275 field_host: HΓ΄te
276 field_host: HΓ΄te
276 field_port: Port
277 field_port: Port
277 field_account: Compte
278 field_account: Compte
278 field_base_dn: Base DN
279 field_base_dn: Base DN
279 field_attr_login: Attribut Identifiant
280 field_attr_login: Attribut Identifiant
280 field_attr_firstname: Attribut PrΓ©nom
281 field_attr_firstname: Attribut PrΓ©nom
281 field_attr_lastname: Attribut Nom
282 field_attr_lastname: Attribut Nom
282 field_attr_mail: Attribut Email
283 field_attr_mail: Attribut Email
283 field_onthefly: CrΓ©ation des utilisateurs Γ  la volΓ©e
284 field_onthefly: CrΓ©ation des utilisateurs Γ  la volΓ©e
284 field_start_date: DΓ©but
285 field_start_date: DΓ©but
285 field_done_ratio: "% rΓ©alisΓ©"
286 field_done_ratio: "% rΓ©alisΓ©"
286 field_auth_source: Mode d'authentification
287 field_auth_source: Mode d'authentification
287 field_hide_mail: Cacher mon adresse mail
288 field_hide_mail: Cacher mon adresse mail
288 field_comments: Commentaire
289 field_comments: Commentaire
289 field_url: URL
290 field_url: URL
290 field_start_page: Page de dΓ©marrage
291 field_start_page: Page de dΓ©marrage
291 field_subproject: Sous-projet
292 field_subproject: Sous-projet
292 field_hours: Heures
293 field_hours: Heures
293 field_activity: ActivitΓ©
294 field_activity: ActivitΓ©
294 field_spent_on: Date
295 field_spent_on: Date
295 field_identifier: Identifiant
296 field_identifier: Identifiant
296 field_is_filter: UtilisΓ© comme filtre
297 field_is_filter: UtilisΓ© comme filtre
297 field_issue_to: Demande liΓ©e
298 field_issue_to: Demande liΓ©e
298 field_delay: Retard
299 field_delay: Retard
299 field_assignable: Demandes assignables Γ  ce rΓ΄le
300 field_assignable: Demandes assignables Γ  ce rΓ΄le
300 field_redirect_existing_links: Rediriger les liens existants
301 field_redirect_existing_links: Rediriger les liens existants
301 field_estimated_hours: Temps estimΓ©
302 field_estimated_hours: Temps estimΓ©
302 field_column_names: Colonnes
303 field_column_names: Colonnes
303 field_time_zone: Fuseau horaire
304 field_time_zone: Fuseau horaire
304 field_searchable: UtilisΓ© pour les recherches
305 field_searchable: UtilisΓ© pour les recherches
305 field_default_value: Valeur par dΓ©faut
306 field_default_value: Valeur par dΓ©faut
306 field_comments_sorting: Afficher les commentaires
307 field_comments_sorting: Afficher les commentaires
307 field_parent_title: Page parent
308 field_parent_title: Page parent
308 field_editable: Modifiable
309 field_editable: Modifiable
309 field_watcher: Observateur
310 field_watcher: Observateur
310 field_identity_url: URL OpenID
311 field_identity_url: URL OpenID
311 field_content: Contenu
312 field_content: Contenu
312 field_group_by: Grouper par
313 field_group_by: Grouper par
313 field_sharing: Partage
314 field_sharing: Partage
314 field_active: Actif
315 field_active: Actif
315 field_parent_issue: TΓ’che parente
316 field_parent_issue: TΓ’che parente
316 field_visible: Visible
317 field_visible: Visible
317 field_warn_on_leaving_unsaved: "M'avertir lorsque je quitte une page contenant du texte non sauvegardΓ©"
318 field_warn_on_leaving_unsaved: "M'avertir lorsque je quitte une page contenant du texte non sauvegardΓ©"
318 field_issues_visibility: VisibilitΓ© des demandes
319 field_issues_visibility: VisibilitΓ© des demandes
319 field_is_private: PrivΓ©e
320 field_is_private: PrivΓ©e
320 field_commit_logs_encoding: Encodage des messages de commit
321 field_commit_logs_encoding: Encodage des messages de commit
321 field_repository_is_default: DΓ©pΓ΄t principal
322 field_repository_is_default: DΓ©pΓ΄t principal
322 field_multiple: Valeurs multiples
323 field_multiple: Valeurs multiples
323 field_ldap_filter: Filtre LDAP
324 field_ldap_filter: Filtre LDAP
324
325
325 setting_app_title: Titre de l'application
326 setting_app_title: Titre de l'application
326 setting_app_subtitle: Sous-titre de l'application
327 setting_app_subtitle: Sous-titre de l'application
327 setting_welcome_text: Texte d'accueil
328 setting_welcome_text: Texte d'accueil
328 setting_default_language: Langue par dΓ©faut
329 setting_default_language: Langue par dΓ©faut
329 setting_login_required: Authentification obligatoire
330 setting_login_required: Authentification obligatoire
330 setting_self_registration: Inscription des nouveaux utilisateurs
331 setting_self_registration: Inscription des nouveaux utilisateurs
331 setting_attachment_max_size: Taille maximale des fichiers
332 setting_attachment_max_size: Taille maximale des fichiers
332 setting_issues_export_limit: Limite d'exportation des demandes
333 setting_issues_export_limit: Limite d'exportation des demandes
333 setting_mail_from: Adresse d'Γ©mission
334 setting_mail_from: Adresse d'Γ©mission
334 setting_bcc_recipients: Destinataires en copie cachΓ©e (cci)
335 setting_bcc_recipients: Destinataires en copie cachΓ©e (cci)
335 setting_plain_text_mail: Mail en texte brut (non HTML)
336 setting_plain_text_mail: Mail en texte brut (non HTML)
336 setting_host_name: Nom d'hΓ΄te et chemin
337 setting_host_name: Nom d'hΓ΄te et chemin
337 setting_text_formatting: Formatage du texte
338 setting_text_formatting: Formatage du texte
338 setting_wiki_compression: Compression de l'historique des pages wiki
339 setting_wiki_compression: Compression de l'historique des pages wiki
339 setting_feeds_limit: Nombre maximal d'Γ©lΓ©ments dans les flux Atom
340 setting_feeds_limit: Nombre maximal d'Γ©lΓ©ments dans les flux Atom
340 setting_default_projects_public: DΓ©finir les nouveaux projets comme publics par dΓ©faut
341 setting_default_projects_public: DΓ©finir les nouveaux projets comme publics par dΓ©faut
341 setting_autofetch_changesets: RΓ©cupΓ©ration automatique des commits
342 setting_autofetch_changesets: RΓ©cupΓ©ration automatique des commits
342 setting_sys_api_enabled: Activer les WS pour la gestion des dΓ©pΓ΄ts
343 setting_sys_api_enabled: Activer les WS pour la gestion des dΓ©pΓ΄ts
343 setting_commit_ref_keywords: Mots-clΓ©s de rΓ©fΓ©rencement
344 setting_commit_ref_keywords: Mots-clΓ©s de rΓ©fΓ©rencement
344 setting_commit_fix_keywords: Mots-clΓ©s de rΓ©solution
345 setting_commit_fix_keywords: Mots-clΓ©s de rΓ©solution
345 setting_autologin: DurΓ©e maximale de connexion automatique
346 setting_autologin: DurΓ©e maximale de connexion automatique
346 setting_date_format: Format de date
347 setting_date_format: Format de date
347 setting_time_format: Format d'heure
348 setting_time_format: Format d'heure
348 setting_cross_project_issue_relations: Autoriser les relations entre demandes de diffΓ©rents projets
349 setting_cross_project_issue_relations: Autoriser les relations entre demandes de diffΓ©rents projets
349 setting_issue_list_default_columns: Colonnes affichΓ©es par dΓ©faut sur la liste des demandes
350 setting_issue_list_default_columns: Colonnes affichΓ©es par dΓ©faut sur la liste des demandes
350 setting_emails_footer: Pied-de-page des emails
351 setting_emails_footer: Pied-de-page des emails
351 setting_protocol: Protocole
352 setting_protocol: Protocole
352 setting_per_page_options: Options d'objets affichΓ©s par page
353 setting_per_page_options: Options d'objets affichΓ©s par page
353 setting_user_format: Format d'affichage des utilisateurs
354 setting_user_format: Format d'affichage des utilisateurs
354 setting_activity_days_default: Nombre de jours affichΓ©s sur l'activitΓ© des projets
355 setting_activity_days_default: Nombre de jours affichΓ©s sur l'activitΓ© des projets
355 setting_display_subprojects_issues: Afficher par dΓ©faut les demandes des sous-projets sur les projets principaux
356 setting_display_subprojects_issues: Afficher par dΓ©faut les demandes des sous-projets sur les projets principaux
356 setting_enabled_scm: SCM activΓ©s
357 setting_enabled_scm: SCM activΓ©s
357 setting_mail_handler_body_delimiters: "Tronquer les emails après l'une de ces lignes"
358 setting_mail_handler_body_delimiters: "Tronquer les emails après l'une de ces lignes"
358 setting_mail_handler_api_enabled: "Activer le WS pour la rΓ©ception d'emails"
359 setting_mail_handler_api_enabled: "Activer le WS pour la rΓ©ception d'emails"
359 setting_mail_handler_api_key: ClΓ© de protection de l'API
360 setting_mail_handler_api_key: ClΓ© de protection de l'API
360 setting_sequential_project_identifiers: GΓ©nΓ©rer des identifiants de projet sΓ©quentiels
361 setting_sequential_project_identifiers: GΓ©nΓ©rer des identifiants de projet sΓ©quentiels
361 setting_gravatar_enabled: Afficher les Gravatar des utilisateurs
362 setting_gravatar_enabled: Afficher les Gravatar des utilisateurs
362 setting_diff_max_lines_displayed: Nombre maximum de lignes de diff affichΓ©es
363 setting_diff_max_lines_displayed: Nombre maximum de lignes de diff affichΓ©es
363 setting_file_max_size_displayed: Taille maximum des fichiers texte affichΓ©s en ligne
364 setting_file_max_size_displayed: Taille maximum des fichiers texte affichΓ©s en ligne
364 setting_repository_log_display_limit: "Nombre maximum de rΓ©visions affichΓ©es sur l'historique d'un fichier"
365 setting_repository_log_display_limit: "Nombre maximum de rΓ©visions affichΓ©es sur l'historique d'un fichier"
365 setting_openid: "Autoriser l'authentification et l'enregistrement OpenID"
366 setting_openid: "Autoriser l'authentification et l'enregistrement OpenID"
366 setting_password_min_length: Longueur minimum des mots de passe
367 setting_password_min_length: Longueur minimum des mots de passe
367 setting_new_project_user_role_id: RΓ΄le donnΓ© Γ  un utilisateur non-administrateur qui crΓ©e un projet
368 setting_new_project_user_role_id: RΓ΄le donnΓ© Γ  un utilisateur non-administrateur qui crΓ©e un projet
368 setting_default_projects_modules: Modules activΓ©s par dΓ©faut pour les nouveaux projets
369 setting_default_projects_modules: Modules activΓ©s par dΓ©faut pour les nouveaux projets
369 setting_issue_done_ratio: Calcul de l'avancement des demandes
370 setting_issue_done_ratio: Calcul de l'avancement des demandes
370 setting_issue_done_ratio_issue_status: Utiliser le statut
371 setting_issue_done_ratio_issue_status: Utiliser le statut
371 setting_issue_done_ratio_issue_field: 'Utiliser le champ % effectuΓ©'
372 setting_issue_done_ratio_issue_field: 'Utiliser le champ % effectuΓ©'
372 setting_rest_api_enabled: Activer l'API REST
373 setting_rest_api_enabled: Activer l'API REST
373 setting_gravatar_default: Image Gravatar par dΓ©faut
374 setting_gravatar_default: Image Gravatar par dΓ©faut
374 setting_start_of_week: Jour de dΓ©but des calendriers
375 setting_start_of_week: Jour de dΓ©but des calendriers
375 setting_cache_formatted_text: Mettre en cache le texte formatΓ©
376 setting_cache_formatted_text: Mettre en cache le texte formatΓ©
376 setting_commit_logtime_enabled: Permettre la saisie de temps
377 setting_commit_logtime_enabled: Permettre la saisie de temps
377 setting_commit_logtime_activity_id: ActivitΓ© pour le temps saisi
378 setting_commit_logtime_activity_id: ActivitΓ© pour le temps saisi
378 setting_gantt_items_limit: Nombre maximum d'Γ©lΓ©ments affichΓ©s sur le gantt
379 setting_gantt_items_limit: Nombre maximum d'Γ©lΓ©ments affichΓ©s sur le gantt
379 setting_issue_group_assignment: Permettre l'assignement des demandes aux groupes
380 setting_issue_group_assignment: Permettre l'assignement des demandes aux groupes
380 setting_default_issue_start_date_to_creation_date: Donner Γ  la date de dΓ©but d'une nouvelle demande la valeur de la date du jour
381 setting_default_issue_start_date_to_creation_date: Donner Γ  la date de dΓ©but d'une nouvelle demande la valeur de la date du jour
381 setting_commit_cross_project_ref: Permettre le rΓ©fΓ©rencement et la rΓ©solution des demandes de tous les autres projets
382 setting_commit_cross_project_ref: Permettre le rΓ©fΓ©rencement et la rΓ©solution des demandes de tous les autres projets
383 setting_unsubscribe: Permettre aux utilisateurs de se dΓ©sinscrire
382
384
383 permission_add_project: CrΓ©er un projet
385 permission_add_project: CrΓ©er un projet
384 permission_add_subprojects: CrΓ©er des sous-projets
386 permission_add_subprojects: CrΓ©er des sous-projets
385 permission_edit_project: Modifier le projet
387 permission_edit_project: Modifier le projet
386 permission_select_project_modules: Choisir les modules
388 permission_select_project_modules: Choisir les modules
387 permission_manage_members: GΓ©rer les membres
389 permission_manage_members: GΓ©rer les membres
388 permission_manage_versions: GΓ©rer les versions
390 permission_manage_versions: GΓ©rer les versions
389 permission_manage_categories: GΓ©rer les catΓ©gories de demandes
391 permission_manage_categories: GΓ©rer les catΓ©gories de demandes
390 permission_view_issues: Voir les demandes
392 permission_view_issues: Voir les demandes
391 permission_add_issues: CrΓ©er des demandes
393 permission_add_issues: CrΓ©er des demandes
392 permission_edit_issues: Modifier les demandes
394 permission_edit_issues: Modifier les demandes
393 permission_manage_issue_relations: GΓ©rer les relations
395 permission_manage_issue_relations: GΓ©rer les relations
394 permission_set_issues_private: Rendre les demandes publiques ou privΓ©es
396 permission_set_issues_private: Rendre les demandes publiques ou privΓ©es
395 permission_set_own_issues_private: Rendre ses propres demandes publiques ou privΓ©es
397 permission_set_own_issues_private: Rendre ses propres demandes publiques ou privΓ©es
396 permission_add_issue_notes: Ajouter des notes
398 permission_add_issue_notes: Ajouter des notes
397 permission_edit_issue_notes: Modifier les notes
399 permission_edit_issue_notes: Modifier les notes
398 permission_edit_own_issue_notes: Modifier ses propres notes
400 permission_edit_own_issue_notes: Modifier ses propres notes
399 permission_move_issues: DΓ©placer les demandes
401 permission_move_issues: DΓ©placer les demandes
400 permission_delete_issues: Supprimer les demandes
402 permission_delete_issues: Supprimer les demandes
401 permission_manage_public_queries: GΓ©rer les requΓͺtes publiques
403 permission_manage_public_queries: GΓ©rer les requΓͺtes publiques
402 permission_save_queries: Sauvegarder les requΓͺtes
404 permission_save_queries: Sauvegarder les requΓͺtes
403 permission_view_gantt: Voir le gantt
405 permission_view_gantt: Voir le gantt
404 permission_view_calendar: Voir le calendrier
406 permission_view_calendar: Voir le calendrier
405 permission_view_issue_watchers: Voir la liste des observateurs
407 permission_view_issue_watchers: Voir la liste des observateurs
406 permission_add_issue_watchers: Ajouter des observateurs
408 permission_add_issue_watchers: Ajouter des observateurs
407 permission_delete_issue_watchers: Supprimer des observateurs
409 permission_delete_issue_watchers: Supprimer des observateurs
408 permission_log_time: Saisir le temps passΓ©
410 permission_log_time: Saisir le temps passΓ©
409 permission_view_time_entries: Voir le temps passΓ©
411 permission_view_time_entries: Voir le temps passΓ©
410 permission_edit_time_entries: Modifier les temps passΓ©s
412 permission_edit_time_entries: Modifier les temps passΓ©s
411 permission_edit_own_time_entries: Modifier son propre temps passΓ©
413 permission_edit_own_time_entries: Modifier son propre temps passΓ©
412 permission_manage_news: GΓ©rer les annonces
414 permission_manage_news: GΓ©rer les annonces
413 permission_comment_news: Commenter les annonces
415 permission_comment_news: Commenter les annonces
414 permission_manage_documents: GΓ©rer les documents
416 permission_manage_documents: GΓ©rer les documents
415 permission_view_documents: Voir les documents
417 permission_view_documents: Voir les documents
416 permission_manage_files: GΓ©rer les fichiers
418 permission_manage_files: GΓ©rer les fichiers
417 permission_view_files: Voir les fichiers
419 permission_view_files: Voir les fichiers
418 permission_manage_wiki: GΓ©rer le wiki
420 permission_manage_wiki: GΓ©rer le wiki
419 permission_rename_wiki_pages: Renommer les pages
421 permission_rename_wiki_pages: Renommer les pages
420 permission_delete_wiki_pages: Supprimer les pages
422 permission_delete_wiki_pages: Supprimer les pages
421 permission_view_wiki_pages: Voir le wiki
423 permission_view_wiki_pages: Voir le wiki
422 permission_view_wiki_edits: "Voir l'historique des modifications"
424 permission_view_wiki_edits: "Voir l'historique des modifications"
423 permission_edit_wiki_pages: Modifier les pages
425 permission_edit_wiki_pages: Modifier les pages
424 permission_delete_wiki_pages_attachments: Supprimer les fichiers joints
426 permission_delete_wiki_pages_attachments: Supprimer les fichiers joints
425 permission_protect_wiki_pages: ProtΓ©ger les pages
427 permission_protect_wiki_pages: ProtΓ©ger les pages
426 permission_manage_repository: GΓ©rer le dΓ©pΓ΄t de sources
428 permission_manage_repository: GΓ©rer le dΓ©pΓ΄t de sources
427 permission_browse_repository: Parcourir les sources
429 permission_browse_repository: Parcourir les sources
428 permission_view_changesets: Voir les rΓ©visions
430 permission_view_changesets: Voir les rΓ©visions
429 permission_commit_access: Droit de commit
431 permission_commit_access: Droit de commit
430 permission_manage_boards: GΓ©rer les forums
432 permission_manage_boards: GΓ©rer les forums
431 permission_view_messages: Voir les messages
433 permission_view_messages: Voir les messages
432 permission_add_messages: Poster un message
434 permission_add_messages: Poster un message
433 permission_edit_messages: Modifier les messages
435 permission_edit_messages: Modifier les messages
434 permission_edit_own_messages: Modifier ses propres messages
436 permission_edit_own_messages: Modifier ses propres messages
435 permission_delete_messages: Supprimer les messages
437 permission_delete_messages: Supprimer les messages
436 permission_delete_own_messages: Supprimer ses propres messages
438 permission_delete_own_messages: Supprimer ses propres messages
437 permission_export_wiki_pages: Exporter les pages
439 permission_export_wiki_pages: Exporter les pages
438 permission_manage_project_activities: GΓ©rer les activitΓ©s
440 permission_manage_project_activities: GΓ©rer les activitΓ©s
439 permission_manage_subtasks: GΓ©rer les sous-tΓ’ches
441 permission_manage_subtasks: GΓ©rer les sous-tΓ’ches
440 permission_manage_related_issues: GΓ©rer les demandes associΓ©es
442 permission_manage_related_issues: GΓ©rer les demandes associΓ©es
441
443
442 project_module_issue_tracking: Suivi des demandes
444 project_module_issue_tracking: Suivi des demandes
443 project_module_time_tracking: Suivi du temps passΓ©
445 project_module_time_tracking: Suivi du temps passΓ©
444 project_module_news: Publication d'annonces
446 project_module_news: Publication d'annonces
445 project_module_documents: Publication de documents
447 project_module_documents: Publication de documents
446 project_module_files: Publication de fichiers
448 project_module_files: Publication de fichiers
447 project_module_wiki: Wiki
449 project_module_wiki: Wiki
448 project_module_repository: DΓ©pΓ΄t de sources
450 project_module_repository: DΓ©pΓ΄t de sources
449 project_module_boards: Forums de discussion
451 project_module_boards: Forums de discussion
450
452
451 label_user: Utilisateur
453 label_user: Utilisateur
452 label_user_plural: Utilisateurs
454 label_user_plural: Utilisateurs
453 label_user_new: Nouvel utilisateur
455 label_user_new: Nouvel utilisateur
454 label_user_anonymous: Anonyme
456 label_user_anonymous: Anonyme
455 label_project: Projet
457 label_project: Projet
456 label_project_new: Nouveau projet
458 label_project_new: Nouveau projet
457 label_project_plural: Projets
459 label_project_plural: Projets
458 label_x_projects:
460 label_x_projects:
459 zero: aucun projet
461 zero: aucun projet
460 one: un projet
462 one: un projet
461 other: "%{count} projets"
463 other: "%{count} projets"
462 label_project_all: Tous les projets
464 label_project_all: Tous les projets
463 label_project_latest: Derniers projets
465 label_project_latest: Derniers projets
464 label_issue: Demande
466 label_issue: Demande
465 label_issue_new: Nouvelle demande
467 label_issue_new: Nouvelle demande
466 label_issue_plural: Demandes
468 label_issue_plural: Demandes
467 label_issue_view_all: Voir toutes les demandes
469 label_issue_view_all: Voir toutes les demandes
468 label_issue_added: Demande ajoutΓ©e
470 label_issue_added: Demande ajoutΓ©e
469 label_issue_updated: Demande mise Γ  jour
471 label_issue_updated: Demande mise Γ  jour
470 label_issue_note_added: Note ajoutΓ©e
472 label_issue_note_added: Note ajoutΓ©e
471 label_issue_status_updated: Statut changΓ©
473 label_issue_status_updated: Statut changΓ©
472 label_issue_priority_updated: PrioritΓ© changΓ©e
474 label_issue_priority_updated: PrioritΓ© changΓ©e
473 label_issues_by: "Demandes par %{value}"
475 label_issues_by: "Demandes par %{value}"
474 label_document: Document
476 label_document: Document
475 label_document_new: Nouveau document
477 label_document_new: Nouveau document
476 label_document_plural: Documents
478 label_document_plural: Documents
477 label_document_added: Document ajoutΓ©
479 label_document_added: Document ajoutΓ©
478 label_role: RΓ΄le
480 label_role: RΓ΄le
479 label_role_plural: RΓ΄les
481 label_role_plural: RΓ΄les
480 label_role_new: Nouveau rΓ΄le
482 label_role_new: Nouveau rΓ΄le
481 label_role_and_permissions: RΓ΄les et permissions
483 label_role_and_permissions: RΓ΄les et permissions
482 label_role_anonymous: Anonyme
484 label_role_anonymous: Anonyme
483 label_role_non_member: Non membre
485 label_role_non_member: Non membre
484 label_member: Membre
486 label_member: Membre
485 label_member_new: Nouveau membre
487 label_member_new: Nouveau membre
486 label_member_plural: Membres
488 label_member_plural: Membres
487 label_tracker: Tracker
489 label_tracker: Tracker
488 label_tracker_plural: Trackers
490 label_tracker_plural: Trackers
489 label_tracker_new: Nouveau tracker
491 label_tracker_new: Nouveau tracker
490 label_workflow: Workflow
492 label_workflow: Workflow
491 label_issue_status: Statut de demandes
493 label_issue_status: Statut de demandes
492 label_issue_status_plural: Statuts de demandes
494 label_issue_status_plural: Statuts de demandes
493 label_issue_status_new: Nouveau statut
495 label_issue_status_new: Nouveau statut
494 label_issue_category: CatΓ©gorie de demandes
496 label_issue_category: CatΓ©gorie de demandes
495 label_issue_category_plural: CatΓ©gories de demandes
497 label_issue_category_plural: CatΓ©gories de demandes
496 label_issue_category_new: Nouvelle catΓ©gorie
498 label_issue_category_new: Nouvelle catΓ©gorie
497 label_custom_field: Champ personnalisΓ©
499 label_custom_field: Champ personnalisΓ©
498 label_custom_field_plural: Champs personnalisΓ©s
500 label_custom_field_plural: Champs personnalisΓ©s
499 label_custom_field_new: Nouveau champ personnalisΓ©
501 label_custom_field_new: Nouveau champ personnalisΓ©
500 label_enumerations: Listes de valeurs
502 label_enumerations: Listes de valeurs
501 label_enumeration_new: Nouvelle valeur
503 label_enumeration_new: Nouvelle valeur
502 label_information: Information
504 label_information: Information
503 label_information_plural: Informations
505 label_information_plural: Informations
504 label_please_login: Identification
506 label_please_login: Identification
505 label_register: S'enregistrer
507 label_register: S'enregistrer
506 label_login_with_open_id_option: S'authentifier avec OpenID
508 label_login_with_open_id_option: S'authentifier avec OpenID
507 label_password_lost: Mot de passe perdu
509 label_password_lost: Mot de passe perdu
508 label_home: Accueil
510 label_home: Accueil
509 label_my_page: Ma page
511 label_my_page: Ma page
510 label_my_account: Mon compte
512 label_my_account: Mon compte
511 label_my_projects: Mes projets
513 label_my_projects: Mes projets
512 label_my_page_block: Blocs disponibles
514 label_my_page_block: Blocs disponibles
513 label_administration: Administration
515 label_administration: Administration
514 label_login: Connexion
516 label_login: Connexion
515 label_logout: DΓ©connexion
517 label_logout: DΓ©connexion
516 label_help: Aide
518 label_help: Aide
517 label_reported_issues: "Demandes soumises "
519 label_reported_issues: "Demandes soumises "
518 label_assigned_to_me_issues: Demandes qui me sont assignΓ©es
520 label_assigned_to_me_issues: Demandes qui me sont assignΓ©es
519 label_last_login: "Dernière connexion "
521 label_last_login: "Dernière connexion "
520 label_registered_on: "Inscrit le "
522 label_registered_on: "Inscrit le "
521 label_activity: ActivitΓ©
523 label_activity: ActivitΓ©
522 label_overall_activity: ActivitΓ© globale
524 label_overall_activity: ActivitΓ© globale
523 label_user_activity: "ActivitΓ© de %{value}"
525 label_user_activity: "ActivitΓ© de %{value}"
524 label_new: Nouveau
526 label_new: Nouveau
525 label_logged_as: ConnectΓ© en tant que
527 label_logged_as: ConnectΓ© en tant que
526 label_environment: Environnement
528 label_environment: Environnement
527 label_authentication: Authentification
529 label_authentication: Authentification
528 label_auth_source: Mode d'authentification
530 label_auth_source: Mode d'authentification
529 label_auth_source_new: Nouveau mode d'authentification
531 label_auth_source_new: Nouveau mode d'authentification
530 label_auth_source_plural: Modes d'authentification
532 label_auth_source_plural: Modes d'authentification
531 label_subproject_plural: Sous-projets
533 label_subproject_plural: Sous-projets
532 label_subproject_new: Nouveau sous-projet
534 label_subproject_new: Nouveau sous-projet
533 label_and_its_subprojects: "%{value} et ses sous-projets"
535 label_and_its_subprojects: "%{value} et ses sous-projets"
534 label_min_max_length: Longueurs mini - maxi
536 label_min_max_length: Longueurs mini - maxi
535 label_list: Liste
537 label_list: Liste
536 label_date: Date
538 label_date: Date
537 label_integer: Entier
539 label_integer: Entier
538 label_float: Nombre dΓ©cimal
540 label_float: Nombre dΓ©cimal
539 label_boolean: BoolΓ©en
541 label_boolean: BoolΓ©en
540 label_string: Texte
542 label_string: Texte
541 label_text: Texte long
543 label_text: Texte long
542 label_attribute: Attribut
544 label_attribute: Attribut
543 label_attribute_plural: Attributs
545 label_attribute_plural: Attributs
544 label_download: "%{count} tΓ©lΓ©chargement"
546 label_download: "%{count} tΓ©lΓ©chargement"
545 label_download_plural: "%{count} tΓ©lΓ©chargements"
547 label_download_plural: "%{count} tΓ©lΓ©chargements"
546 label_no_data: Aucune donnΓ©e Γ  afficher
548 label_no_data: Aucune donnΓ©e Γ  afficher
547 label_change_status: Changer le statut
549 label_change_status: Changer le statut
548 label_history: Historique
550 label_history: Historique
549 label_attachment: Fichier
551 label_attachment: Fichier
550 label_attachment_new: Nouveau fichier
552 label_attachment_new: Nouveau fichier
551 label_attachment_delete: Supprimer le fichier
553 label_attachment_delete: Supprimer le fichier
552 label_attachment_plural: Fichiers
554 label_attachment_plural: Fichiers
553 label_file_added: Fichier ajoutΓ©
555 label_file_added: Fichier ajoutΓ©
554 label_report: Rapport
556 label_report: Rapport
555 label_report_plural: Rapports
557 label_report_plural: Rapports
556 label_news: Annonce
558 label_news: Annonce
557 label_news_new: Nouvelle annonce
559 label_news_new: Nouvelle annonce
558 label_news_plural: Annonces
560 label_news_plural: Annonces
559 label_news_latest: Dernières annonces
561 label_news_latest: Dernières annonces
560 label_news_view_all: Voir toutes les annonces
562 label_news_view_all: Voir toutes les annonces
561 label_news_added: Annonce ajoutΓ©e
563 label_news_added: Annonce ajoutΓ©e
562 label_news_comment_added: Commentaire ajoutΓ© Γ  une annonce
564 label_news_comment_added: Commentaire ajoutΓ© Γ  une annonce
563 label_settings: Configuration
565 label_settings: Configuration
564 label_overview: AperΓ§u
566 label_overview: AperΓ§u
565 label_version: Version
567 label_version: Version
566 label_version_new: Nouvelle version
568 label_version_new: Nouvelle version
567 label_version_plural: Versions
569 label_version_plural: Versions
568 label_confirmation: Confirmation
570 label_confirmation: Confirmation
569 label_export_to: 'Formats disponibles :'
571 label_export_to: 'Formats disponibles :'
570 label_read: Lire...
572 label_read: Lire...
571 label_public_projects: Projets publics
573 label_public_projects: Projets publics
572 label_open_issues: ouvert
574 label_open_issues: ouvert
573 label_open_issues_plural: ouverts
575 label_open_issues_plural: ouverts
574 label_closed_issues: fermΓ©
576 label_closed_issues: fermΓ©
575 label_closed_issues_plural: fermΓ©s
577 label_closed_issues_plural: fermΓ©s
576 label_x_open_issues_abbr_on_total:
578 label_x_open_issues_abbr_on_total:
577 zero: 0 ouverte sur %{total}
579 zero: 0 ouverte sur %{total}
578 one: 1 ouverte sur %{total}
580 one: 1 ouverte sur %{total}
579 other: "%{count} ouvertes sur %{total}"
581 other: "%{count} ouvertes sur %{total}"
580 label_x_open_issues_abbr:
582 label_x_open_issues_abbr:
581 zero: 0 ouverte
583 zero: 0 ouverte
582 one: 1 ouverte
584 one: 1 ouverte
583 other: "%{count} ouvertes"
585 other: "%{count} ouvertes"
584 label_x_closed_issues_abbr:
586 label_x_closed_issues_abbr:
585 zero: 0 fermΓ©e
587 zero: 0 fermΓ©e
586 one: 1 fermΓ©e
588 one: 1 fermΓ©e
587 other: "%{count} fermΓ©es"
589 other: "%{count} fermΓ©es"
588 label_x_issues:
590 label_x_issues:
589 zero: 0 demande
591 zero: 0 demande
590 one: 1 demande
592 one: 1 demande
591 other: "%{count} demandes"
593 other: "%{count} demandes"
592 label_total: Total
594 label_total: Total
593 label_permissions: Permissions
595 label_permissions: Permissions
594 label_current_status: Statut actuel
596 label_current_status: Statut actuel
595 label_new_statuses_allowed: Nouveaux statuts autorisΓ©s
597 label_new_statuses_allowed: Nouveaux statuts autorisΓ©s
596 label_all: tous
598 label_all: tous
597 label_none: aucun
599 label_none: aucun
598 label_nobody: personne
600 label_nobody: personne
599 label_next: Suivant
601 label_next: Suivant
600 label_previous: PrΓ©cΓ©dent
602 label_previous: PrΓ©cΓ©dent
601 label_used_by: UtilisΓ© par
603 label_used_by: UtilisΓ© par
602 label_details: DΓ©tails
604 label_details: DΓ©tails
603 label_add_note: Ajouter une note
605 label_add_note: Ajouter une note
604 label_per_page: Par page
606 label_per_page: Par page
605 label_calendar: Calendrier
607 label_calendar: Calendrier
606 label_months_from: mois depuis
608 label_months_from: mois depuis
607 label_gantt: Gantt
609 label_gantt: Gantt
608 label_internal: Interne
610 label_internal: Interne
609 label_last_changes: "%{count} derniers changements"
611 label_last_changes: "%{count} derniers changements"
610 label_change_view_all: Voir tous les changements
612 label_change_view_all: Voir tous les changements
611 label_personalize_page: Personnaliser cette page
613 label_personalize_page: Personnaliser cette page
612 label_comment: Commentaire
614 label_comment: Commentaire
613 label_comment_plural: Commentaires
615 label_comment_plural: Commentaires
614 label_x_comments:
616 label_x_comments:
615 zero: aucun commentaire
617 zero: aucun commentaire
616 one: un commentaire
618 one: un commentaire
617 other: "%{count} commentaires"
619 other: "%{count} commentaires"
618 label_comment_add: Ajouter un commentaire
620 label_comment_add: Ajouter un commentaire
619 label_comment_added: Commentaire ajoutΓ©
621 label_comment_added: Commentaire ajoutΓ©
620 label_comment_delete: Supprimer les commentaires
622 label_comment_delete: Supprimer les commentaires
621 label_query: Rapport personnalisΓ©
623 label_query: Rapport personnalisΓ©
622 label_query_plural: Rapports personnalisΓ©s
624 label_query_plural: Rapports personnalisΓ©s
623 label_query_new: Nouveau rapport
625 label_query_new: Nouveau rapport
624 label_my_queries: Mes rapports personnalisΓ©s
626 label_my_queries: Mes rapports personnalisΓ©s
625 label_filter_add: "Ajouter le filtre "
627 label_filter_add: "Ajouter le filtre "
626 label_filter_plural: Filtres
628 label_filter_plural: Filtres
627 label_equals: Γ©gal
629 label_equals: Γ©gal
628 label_not_equals: diffΓ©rent
630 label_not_equals: diffΓ©rent
629 label_in_less_than: dans moins de
631 label_in_less_than: dans moins de
630 label_in_more_than: dans plus de
632 label_in_more_than: dans plus de
631 label_in: dans
633 label_in: dans
632 label_today: aujourd'hui
634 label_today: aujourd'hui
633 label_all_time: toute la pΓ©riode
635 label_all_time: toute la pΓ©riode
634 label_yesterday: hier
636 label_yesterday: hier
635 label_this_week: cette semaine
637 label_this_week: cette semaine
636 label_last_week: la semaine dernière
638 label_last_week: la semaine dernière
637 label_last_n_days: "les %{count} derniers jours"
639 label_last_n_days: "les %{count} derniers jours"
638 label_this_month: ce mois-ci
640 label_this_month: ce mois-ci
639 label_last_month: le mois dernier
641 label_last_month: le mois dernier
640 label_this_year: cette annΓ©e
642 label_this_year: cette annΓ©e
641 label_date_range: PΓ©riode
643 label_date_range: PΓ©riode
642 label_less_than_ago: il y a moins de
644 label_less_than_ago: il y a moins de
643 label_more_than_ago: il y a plus de
645 label_more_than_ago: il y a plus de
644 label_ago: il y a
646 label_ago: il y a
645 label_contains: contient
647 label_contains: contient
646 label_not_contains: ne contient pas
648 label_not_contains: ne contient pas
647 label_day_plural: jours
649 label_day_plural: jours
648 label_repository: DΓ©pΓ΄t
650 label_repository: DΓ©pΓ΄t
649 label_repository_new: Nouveau dΓ©pΓ΄t
651 label_repository_new: Nouveau dΓ©pΓ΄t
650 label_repository_plural: DΓ©pΓ΄ts
652 label_repository_plural: DΓ©pΓ΄ts
651 label_browse: Parcourir
653 label_browse: Parcourir
652 label_modification: "%{count} modification"
654 label_modification: "%{count} modification"
653 label_modification_plural: "%{count} modifications"
655 label_modification_plural: "%{count} modifications"
654 label_revision: "RΓ©vision "
656 label_revision: "RΓ©vision "
655 label_revision_plural: RΓ©visions
657 label_revision_plural: RΓ©visions
656 label_associated_revisions: RΓ©visions associΓ©es
658 label_associated_revisions: RΓ©visions associΓ©es
657 label_added: ajoutΓ©
659 label_added: ajoutΓ©
658 label_modified: modifiΓ©
660 label_modified: modifiΓ©
659 label_copied: copiΓ©
661 label_copied: copiΓ©
660 label_renamed: renommΓ©
662 label_renamed: renommΓ©
661 label_deleted: supprimΓ©
663 label_deleted: supprimΓ©
662 label_latest_revision: Dernière révision
664 label_latest_revision: Dernière révision
663 label_latest_revision_plural: Dernières révisions
665 label_latest_revision_plural: Dernières révisions
664 label_view_revisions: Voir les rΓ©visions
666 label_view_revisions: Voir les rΓ©visions
665 label_max_size: Taille maximale
667 label_max_size: Taille maximale
666 label_sort_highest: Remonter en premier
668 label_sort_highest: Remonter en premier
667 label_sort_higher: Remonter
669 label_sort_higher: Remonter
668 label_sort_lower: Descendre
670 label_sort_lower: Descendre
669 label_sort_lowest: Descendre en dernier
671 label_sort_lowest: Descendre en dernier
670 label_roadmap: Roadmap
672 label_roadmap: Roadmap
671 label_roadmap_due_in: "Γ‰chΓ©ance dans %{value}"
673 label_roadmap_due_in: "Γ‰chΓ©ance dans %{value}"
672 label_roadmap_overdue: "En retard de %{value}"
674 label_roadmap_overdue: "En retard de %{value}"
673 label_roadmap_no_issues: Aucune demande pour cette version
675 label_roadmap_no_issues: Aucune demande pour cette version
674 label_search: "Recherche "
676 label_search: "Recherche "
675 label_result_plural: RΓ©sultats
677 label_result_plural: RΓ©sultats
676 label_all_words: Tous les mots
678 label_all_words: Tous les mots
677 label_wiki: Wiki
679 label_wiki: Wiki
678 label_wiki_edit: RΓ©vision wiki
680 label_wiki_edit: RΓ©vision wiki
679 label_wiki_edit_plural: RΓ©visions wiki
681 label_wiki_edit_plural: RΓ©visions wiki
680 label_wiki_page: Page wiki
682 label_wiki_page: Page wiki
681 label_wiki_page_plural: Pages wiki
683 label_wiki_page_plural: Pages wiki
682 label_index_by_title: Index par titre
684 label_index_by_title: Index par titre
683 label_index_by_date: Index par date
685 label_index_by_date: Index par date
684 label_current_version: Version actuelle
686 label_current_version: Version actuelle
685 label_preview: PrΓ©visualisation
687 label_preview: PrΓ©visualisation
686 label_feed_plural: Flux RSS
688 label_feed_plural: Flux RSS
687 label_changes_details: DΓ©tails de tous les changements
689 label_changes_details: DΓ©tails de tous les changements
688 label_issue_tracking: Suivi des demandes
690 label_issue_tracking: Suivi des demandes
689 label_spent_time: Temps passΓ©
691 label_spent_time: Temps passΓ©
690 label_f_hour: "%{value} heure"
692 label_f_hour: "%{value} heure"
691 label_f_hour_plural: "%{value} heures"
693 label_f_hour_plural: "%{value} heures"
692 label_time_tracking: Suivi du temps
694 label_time_tracking: Suivi du temps
693 label_change_plural: Changements
695 label_change_plural: Changements
694 label_statistics: Statistiques
696 label_statistics: Statistiques
695 label_commits_per_month: Commits par mois
697 label_commits_per_month: Commits par mois
696 label_commits_per_author: Commits par auteur
698 label_commits_per_author: Commits par auteur
697 label_view_diff: Voir les diffΓ©rences
699 label_view_diff: Voir les diffΓ©rences
698 label_diff_inline: en ligne
700 label_diff_inline: en ligne
699 label_diff_side_by_side: cΓ΄te Γ  cΓ΄te
701 label_diff_side_by_side: cΓ΄te Γ  cΓ΄te
700 label_options: Options
702 label_options: Options
701 label_copy_workflow_from: Copier le workflow de
703 label_copy_workflow_from: Copier le workflow de
702 label_permissions_report: Synthèse des permissions
704 label_permissions_report: Synthèse des permissions
703 label_watched_issues: Demandes surveillΓ©es
705 label_watched_issues: Demandes surveillΓ©es
704 label_related_issues: Demandes liΓ©es
706 label_related_issues: Demandes liΓ©es
705 label_applied_status: Statut appliquΓ©
707 label_applied_status: Statut appliquΓ©
706 label_loading: Chargement...
708 label_loading: Chargement...
707 label_relation_new: Nouvelle relation
709 label_relation_new: Nouvelle relation
708 label_relation_delete: Supprimer la relation
710 label_relation_delete: Supprimer la relation
709 label_relates_to: liΓ© Γ 
711 label_relates_to: liΓ© Γ 
710 label_duplicates: duplique
712 label_duplicates: duplique
711 label_duplicated_by: dupliquΓ© par
713 label_duplicated_by: dupliquΓ© par
712 label_blocks: bloque
714 label_blocks: bloque
713 label_blocked_by: bloquΓ© par
715 label_blocked_by: bloquΓ© par
714 label_precedes: précède
716 label_precedes: précède
715 label_follows: suit
717 label_follows: suit
716 label_end_to_start: fin Γ  dΓ©but
718 label_end_to_start: fin Γ  dΓ©but
717 label_end_to_end: fin Γ  fin
719 label_end_to_end: fin Γ  fin
718 label_start_to_start: dΓ©but Γ  dΓ©but
720 label_start_to_start: dΓ©but Γ  dΓ©but
719 label_start_to_end: dΓ©but Γ  fin
721 label_start_to_end: dΓ©but Γ  fin
720 label_stay_logged_in: Rester connectΓ©
722 label_stay_logged_in: Rester connectΓ©
721 label_disabled: dΓ©sactivΓ©
723 label_disabled: dΓ©sactivΓ©
722 label_show_completed_versions: Voir les versions passΓ©es
724 label_show_completed_versions: Voir les versions passΓ©es
723 label_me: moi
725 label_me: moi
724 label_board: Forum
726 label_board: Forum
725 label_board_new: Nouveau forum
727 label_board_new: Nouveau forum
726 label_board_plural: Forums
728 label_board_plural: Forums
727 label_topic_plural: Discussions
729 label_topic_plural: Discussions
728 label_message_plural: Messages
730 label_message_plural: Messages
729 label_message_last: Dernier message
731 label_message_last: Dernier message
730 label_message_new: Nouveau message
732 label_message_new: Nouveau message
731 label_message_posted: Message ajoutΓ©
733 label_message_posted: Message ajoutΓ©
732 label_reply_plural: RΓ©ponses
734 label_reply_plural: RΓ©ponses
733 label_send_information: Envoyer les informations Γ  l'utilisateur
735 label_send_information: Envoyer les informations Γ  l'utilisateur
734 label_year: AnnΓ©e
736 label_year: AnnΓ©e
735 label_month: Mois
737 label_month: Mois
736 label_week: Semaine
738 label_week: Semaine
737 label_date_from: Du
739 label_date_from: Du
738 label_date_to: Au
740 label_date_to: Au
739 label_language_based: BasΓ© sur la langue de l'utilisateur
741 label_language_based: BasΓ© sur la langue de l'utilisateur
740 label_sort_by: "Trier par %{value}"
742 label_sort_by: "Trier par %{value}"
741 label_send_test_email: Envoyer un email de test
743 label_send_test_email: Envoyer un email de test
742 label_feeds_access_key_created_on: "Clé d'accès RSS créée il y a %{value}"
744 label_feeds_access_key_created_on: "Clé d'accès RSS créée il y a %{value}"
743 label_module_plural: Modules
745 label_module_plural: Modules
744 label_added_time_by: "AjoutΓ© par %{author} il y a %{age}"
746 label_added_time_by: "AjoutΓ© par %{author} il y a %{age}"
745 label_updated_time_by: "Mis Γ  jour par %{author} il y a %{age}"
747 label_updated_time_by: "Mis Γ  jour par %{author} il y a %{age}"
746 label_updated_time: "Mis Γ  jour il y a %{value}"
748 label_updated_time: "Mis Γ  jour il y a %{value}"
747 label_jump_to_a_project: Aller Γ  un projet...
749 label_jump_to_a_project: Aller Γ  un projet...
748 label_file_plural: Fichiers
750 label_file_plural: Fichiers
749 label_changeset_plural: RΓ©visions
751 label_changeset_plural: RΓ©visions
750 label_default_columns: Colonnes par dΓ©faut
752 label_default_columns: Colonnes par dΓ©faut
751 label_no_change_option: (Pas de changement)
753 label_no_change_option: (Pas de changement)
752 label_bulk_edit_selected_issues: Modifier les demandes sΓ©lectionnΓ©es
754 label_bulk_edit_selected_issues: Modifier les demandes sΓ©lectionnΓ©es
753 label_theme: Thème
755 label_theme: Thème
754 label_default: DΓ©faut
756 label_default: DΓ©faut
755 label_search_titles_only: Uniquement dans les titres
757 label_search_titles_only: Uniquement dans les titres
756 label_user_mail_option_all: "Pour tous les Γ©vΓ©nements de tous mes projets"
758 label_user_mail_option_all: "Pour tous les Γ©vΓ©nements de tous mes projets"
757 label_user_mail_option_selected: "Pour tous les Γ©vΓ©nements des projets sΓ©lectionnΓ©s..."
759 label_user_mail_option_selected: "Pour tous les Γ©vΓ©nements des projets sΓ©lectionnΓ©s..."
758 label_user_mail_no_self_notified: "Je ne veux pas Γͺtre notifiΓ© des changements que j'effectue"
760 label_user_mail_no_self_notified: "Je ne veux pas Γͺtre notifiΓ© des changements que j'effectue"
759 label_registration_activation_by_email: activation du compte par email
761 label_registration_activation_by_email: activation du compte par email
760 label_registration_manual_activation: activation manuelle du compte
762 label_registration_manual_activation: activation manuelle du compte
761 label_registration_automatic_activation: activation automatique du compte
763 label_registration_automatic_activation: activation automatique du compte
762 label_display_per_page: "Par page : %{value}"
764 label_display_per_page: "Par page : %{value}"
763 label_age: Γ‚ge
765 label_age: Γ‚ge
764 label_change_properties: Changer les propriΓ©tΓ©s
766 label_change_properties: Changer les propriΓ©tΓ©s
765 label_general: GΓ©nΓ©ral
767 label_general: GΓ©nΓ©ral
766 label_more: Plus
768 label_more: Plus
767 label_scm: SCM
769 label_scm: SCM
768 label_plugins: Plugins
770 label_plugins: Plugins
769 label_ldap_authentication: Authentification LDAP
771 label_ldap_authentication: Authentification LDAP
770 label_downloads_abbr: D/L
772 label_downloads_abbr: D/L
771 label_optional_description: Description facultative
773 label_optional_description: Description facultative
772 label_add_another_file: Ajouter un autre fichier
774 label_add_another_file: Ajouter un autre fichier
773 label_preferences: PrΓ©fΓ©rences
775 label_preferences: PrΓ©fΓ©rences
774 label_chronological_order: Dans l'ordre chronologique
776 label_chronological_order: Dans l'ordre chronologique
775 label_reverse_chronological_order: Dans l'ordre chronologique inverse
777 label_reverse_chronological_order: Dans l'ordre chronologique inverse
776 label_planning: Planning
778 label_planning: Planning
777 label_incoming_emails: Emails entrants
779 label_incoming_emails: Emails entrants
778 label_generate_key: GΓ©nΓ©rer une clΓ©
780 label_generate_key: GΓ©nΓ©rer une clΓ©
779 label_issue_watchers: Observateurs
781 label_issue_watchers: Observateurs
780 label_example: Exemple
782 label_example: Exemple
781 label_display: Affichage
783 label_display: Affichage
782 label_sort: Tri
784 label_sort: Tri
783 label_ascending: Croissant
785 label_ascending: Croissant
784 label_descending: DΓ©croissant
786 label_descending: DΓ©croissant
785 label_date_from_to: Du %{start} au %{end}
787 label_date_from_to: Du %{start} au %{end}
786 label_wiki_content_added: Page wiki ajoutΓ©e
788 label_wiki_content_added: Page wiki ajoutΓ©e
787 label_wiki_content_updated: Page wiki mise Γ  jour
789 label_wiki_content_updated: Page wiki mise Γ  jour
788 label_group_plural: Groupes
790 label_group_plural: Groupes
789 label_group: Groupe
791 label_group: Groupe
790 label_group_new: Nouveau groupe
792 label_group_new: Nouveau groupe
791 label_time_entry_plural: Temps passΓ©
793 label_time_entry_plural: Temps passΓ©
792 label_version_sharing_none: Non partagΓ©
794 label_version_sharing_none: Non partagΓ©
793 label_version_sharing_descendants: Avec les sous-projets
795 label_version_sharing_descendants: Avec les sous-projets
794 label_version_sharing_hierarchy: Avec toute la hiΓ©rarchie
796 label_version_sharing_hierarchy: Avec toute la hiΓ©rarchie
795 label_version_sharing_tree: Avec tout l'arbre
797 label_version_sharing_tree: Avec tout l'arbre
796 label_version_sharing_system: Avec tous les projets
798 label_version_sharing_system: Avec tous les projets
797 label_copy_source: Source
799 label_copy_source: Source
798 label_copy_target: Cible
800 label_copy_target: Cible
799 label_copy_same_as_target: Comme la cible
801 label_copy_same_as_target: Comme la cible
800 label_update_issue_done_ratios: Mettre Γ  jour l'avancement des demandes
802 label_update_issue_done_ratios: Mettre Γ  jour l'avancement des demandes
801 label_display_used_statuses_only: N'afficher que les statuts utilisΓ©s dans ce tracker
803 label_display_used_statuses_only: N'afficher que les statuts utilisΓ©s dans ce tracker
802 label_api_access_key: Clé d'accès API
804 label_api_access_key: Clé d'accès API
803 label_api_access_key_created_on: Clé d'accès API créée il y a %{value}
805 label_api_access_key_created_on: Clé d'accès API créée il y a %{value}
804 label_feeds_access_key: Clé d'accès RSS
806 label_feeds_access_key: Clé d'accès RSS
805 label_missing_api_access_key: Clé d'accès API manquante
807 label_missing_api_access_key: Clé d'accès API manquante
806 label_missing_feeds_access_key: Clé d'accès RSS manquante
808 label_missing_feeds_access_key: Clé d'accès RSS manquante
807 label_close_versions: Fermer les versions terminΓ©es
809 label_close_versions: Fermer les versions terminΓ©es
808 label_revision_id: RΓ©vision %{value}
810 label_revision_id: RΓ©vision %{value}
809 label_profile: Profil
811 label_profile: Profil
810 label_subtask_plural: Sous-tΓ’ches
812 label_subtask_plural: Sous-tΓ’ches
811 label_project_copy_notifications: Envoyer les notifications durant la copie du projet
813 label_project_copy_notifications: Envoyer les notifications durant la copie du projet
812 label_principal_search: "Rechercher un utilisateur ou un groupe :"
814 label_principal_search: "Rechercher un utilisateur ou un groupe :"
813 label_user_search: "Rechercher un utilisateur :"
815 label_user_search: "Rechercher un utilisateur :"
814 label_additional_workflow_transitions_for_author: Autorisations supplémentaires lorsque l'utilisateur a créé la demande
816 label_additional_workflow_transitions_for_author: Autorisations supplémentaires lorsque l'utilisateur a créé la demande
815 label_additional_workflow_transitions_for_assignee: Autorisations supplΓ©mentaires lorsque la demande est assignΓ©e Γ  l'utilisateur
817 label_additional_workflow_transitions_for_assignee: Autorisations supplΓ©mentaires lorsque la demande est assignΓ©e Γ  l'utilisateur
816 label_issues_visibility_all: Toutes les demandes
818 label_issues_visibility_all: Toutes les demandes
817 label_issues_visibility_public: Toutes les demandes non privΓ©es
819 label_issues_visibility_public: Toutes les demandes non privΓ©es
818 label_issues_visibility_own: Demandes créées par ou assignées à l'utilisateur
820 label_issues_visibility_own: Demandes créées par ou assignées à l'utilisateur
819 label_export_options: Options d'exportation %{export_format}
821 label_export_options: Options d'exportation %{export_format}
820 label_copy_attachments: Copier les fichiers
822 label_copy_attachments: Copier les fichiers
821 label_item_position: "%{position} sur %{count}"
823 label_item_position: "%{position} sur %{count}"
822 label_completed_versions: Versions passΓ©es
824 label_completed_versions: Versions passΓ©es
823
825
824 button_login: Connexion
826 button_login: Connexion
825 button_submit: Soumettre
827 button_submit: Soumettre
826 button_save: Sauvegarder
828 button_save: Sauvegarder
827 button_check_all: Tout cocher
829 button_check_all: Tout cocher
828 button_uncheck_all: Tout dΓ©cocher
830 button_uncheck_all: Tout dΓ©cocher
829 button_collapse_all: Plier tout
831 button_collapse_all: Plier tout
830 button_expand_all: DΓ©plier tout
832 button_expand_all: DΓ©plier tout
831 button_delete: Supprimer
833 button_delete: Supprimer
832 button_create: CrΓ©er
834 button_create: CrΓ©er
833 button_create_and_continue: CrΓ©er et continuer
835 button_create_and_continue: CrΓ©er et continuer
834 button_test: Tester
836 button_test: Tester
835 button_edit: Modifier
837 button_edit: Modifier
836 button_add: Ajouter
838 button_add: Ajouter
837 button_change: Changer
839 button_change: Changer
838 button_apply: Appliquer
840 button_apply: Appliquer
839 button_clear: Effacer
841 button_clear: Effacer
840 button_lock: Verrouiller
842 button_lock: Verrouiller
841 button_unlock: DΓ©verrouiller
843 button_unlock: DΓ©verrouiller
842 button_download: TΓ©lΓ©charger
844 button_download: TΓ©lΓ©charger
843 button_list: Lister
845 button_list: Lister
844 button_view: Voir
846 button_view: Voir
845 button_move: DΓ©placer
847 button_move: DΓ©placer
846 button_move_and_follow: DΓ©placer et suivre
848 button_move_and_follow: DΓ©placer et suivre
847 button_back: Retour
849 button_back: Retour
848 button_cancel: Annuler
850 button_cancel: Annuler
849 button_activate: Activer
851 button_activate: Activer
850 button_sort: Trier
852 button_sort: Trier
851 button_log_time: Saisir temps
853 button_log_time: Saisir temps
852 button_rollback: Revenir Γ  cette version
854 button_rollback: Revenir Γ  cette version
853 button_watch: Surveiller
855 button_watch: Surveiller
854 button_unwatch: Ne plus surveiller
856 button_unwatch: Ne plus surveiller
855 button_reply: RΓ©pondre
857 button_reply: RΓ©pondre
856 button_archive: Archiver
858 button_archive: Archiver
857 button_unarchive: DΓ©sarchiver
859 button_unarchive: DΓ©sarchiver
858 button_reset: RΓ©initialiser
860 button_reset: RΓ©initialiser
859 button_rename: Renommer
861 button_rename: Renommer
860 button_change_password: Changer de mot de passe
862 button_change_password: Changer de mot de passe
861 button_copy: Copier
863 button_copy: Copier
862 button_copy_and_follow: Copier et suivre
864 button_copy_and_follow: Copier et suivre
863 button_annotate: Annoter
865 button_annotate: Annoter
864 button_update: Mettre Γ  jour
866 button_update: Mettre Γ  jour
865 button_configure: Configurer
867 button_configure: Configurer
866 button_quote: Citer
868 button_quote: Citer
867 button_duplicate: Dupliquer
869 button_duplicate: Dupliquer
868 button_show: Afficher
870 button_show: Afficher
869 button_edit_section: Modifier cette section
871 button_edit_section: Modifier cette section
870 button_export: Exporter
872 button_export: Exporter
873 button_delete_my_account: Supprimer mon compte
871
874
872 status_active: actif
875 status_active: actif
873 status_registered: enregistrΓ©
876 status_registered: enregistrΓ©
874 status_locked: verrouillΓ©
877 status_locked: verrouillΓ©
875
878
876 version_status_open: ouvert
879 version_status_open: ouvert
877 version_status_locked: verrouillΓ©
880 version_status_locked: verrouillΓ©
878 version_status_closed: fermΓ©
881 version_status_closed: fermΓ©
879
882
880 text_select_mail_notifications: Actions pour lesquelles une notification par e-mail est envoyΓ©e
883 text_select_mail_notifications: Actions pour lesquelles une notification par e-mail est envoyΓ©e
881 text_regexp_info: ex. ^[A-Z0-9]+$
884 text_regexp_info: ex. ^[A-Z0-9]+$
882 text_min_max_length_info: 0 pour aucune restriction
885 text_min_max_length_info: 0 pour aucune restriction
883 text_project_destroy_confirmation: Êtes-vous sûr de vouloir supprimer ce projet et toutes ses données ?
886 text_project_destroy_confirmation: Êtes-vous sûr de vouloir supprimer ce projet et toutes ses données ?
884 text_subprojects_destroy_warning: "Ses sous-projets : %{value} seront Γ©galement supprimΓ©s."
887 text_subprojects_destroy_warning: "Ses sous-projets : %{value} seront Γ©galement supprimΓ©s."
885 text_workflow_edit: SΓ©lectionner un tracker et un rΓ΄le pour Γ©diter le workflow
888 text_workflow_edit: SΓ©lectionner un tracker et un rΓ΄le pour Γ©diter le workflow
886 text_are_you_sure: Êtes-vous sûr ?
889 text_are_you_sure: Êtes-vous sûr ?
887 text_tip_issue_begin_day: tΓ’che commenΓ§ant ce jour
890 text_tip_issue_begin_day: tΓ’che commenΓ§ant ce jour
888 text_tip_issue_end_day: tΓ’che finissant ce jour
891 text_tip_issue_end_day: tΓ’che finissant ce jour
889 text_tip_issue_begin_end_day: tΓ’che commenΓ§ant et finissant ce jour
892 text_tip_issue_begin_end_day: tΓ’che commenΓ§ant et finissant ce jour
890 text_project_identifier_info: 'Seuls les lettres minuscules (a-z), chiffres, tirets et underscore sont autorisΓ©s.<br />Un fois sauvegardΓ©, l''identifiant ne pourra plus Γͺtre modifiΓ©.'
893 text_project_identifier_info: 'Seuls les lettres minuscules (a-z), chiffres, tirets et underscore sont autorisΓ©s.<br />Un fois sauvegardΓ©, l''identifiant ne pourra plus Γͺtre modifiΓ©.'
891 text_caracters_maximum: "%{count} caractères maximum."
894 text_caracters_maximum: "%{count} caractères maximum."
892 text_caracters_minimum: "%{count} caractères minimum."
895 text_caracters_minimum: "%{count} caractères minimum."
893 text_length_between: "Longueur comprise entre %{min} et %{max} caractères."
896 text_length_between: "Longueur comprise entre %{min} et %{max} caractères."
894 text_tracker_no_workflow: Aucun worflow n'est dΓ©fini pour ce tracker
897 text_tracker_no_workflow: Aucun worflow n'est dΓ©fini pour ce tracker
895 text_unallowed_characters: Caractères non autorisés
898 text_unallowed_characters: Caractères non autorisés
896 text_comma_separated: Plusieurs valeurs possibles (sΓ©parΓ©es par des virgules).
899 text_comma_separated: Plusieurs valeurs possibles (sΓ©parΓ©es par des virgules).
897 text_line_separated: Plusieurs valeurs possibles (une valeur par ligne).
900 text_line_separated: Plusieurs valeurs possibles (une valeur par ligne).
898 text_issues_ref_in_commit_messages: RΓ©fΓ©rencement et rΓ©solution des demandes dans les commentaires de commits
901 text_issues_ref_in_commit_messages: RΓ©fΓ©rencement et rΓ©solution des demandes dans les commentaires de commits
899 text_issue_added: "La demande %{id} a Γ©tΓ© soumise par %{author}."
902 text_issue_added: "La demande %{id} a Γ©tΓ© soumise par %{author}."
900 text_issue_updated: "La demande %{id} a Γ©tΓ© mise Γ  jour par %{author}."
903 text_issue_updated: "La demande %{id} a Γ©tΓ© mise Γ  jour par %{author}."
901 text_wiki_destroy_confirmation: Etes-vous sΓ»r de vouloir supprimer ce wiki et tout son contenu ?
904 text_wiki_destroy_confirmation: Etes-vous sΓ»r de vouloir supprimer ce wiki et tout son contenu ?
902 text_issue_category_destroy_question: "%{count} demandes sont affectΓ©es Γ  cette catΓ©gorie. Que voulez-vous faire ?"
905 text_issue_category_destroy_question: "%{count} demandes sont affectΓ©es Γ  cette catΓ©gorie. Que voulez-vous faire ?"
903 text_issue_category_destroy_assignments: N'affecter les demandes Γ  aucune autre catΓ©gorie
906 text_issue_category_destroy_assignments: N'affecter les demandes Γ  aucune autre catΓ©gorie
904 text_issue_category_reassign_to: RΓ©affecter les demandes Γ  cette catΓ©gorie
907 text_issue_category_reassign_to: RΓ©affecter les demandes Γ  cette catΓ©gorie
905 text_user_mail_option: "Pour les projets non sΓ©lectionnΓ©s, vous recevrez seulement des notifications pour ce que vous surveillez ou Γ  quoi vous participez (exemple: demandes dont vous Γͺtes l'auteur ou la personne assignΓ©e)."
908 text_user_mail_option: "Pour les projets non sΓ©lectionnΓ©s, vous recevrez seulement des notifications pour ce que vous surveillez ou Γ  quoi vous participez (exemple: demandes dont vous Γͺtes l'auteur ou la personne assignΓ©e)."
906 text_no_configuration_data: "Les rΓ΄les, trackers, statuts et le workflow ne sont pas encore paramΓ©trΓ©s.\nIl est vivement recommandΓ© de charger le paramΓ©trage par defaut. Vous pourrez le modifier une fois chargΓ©."
909 text_no_configuration_data: "Les rΓ΄les, trackers, statuts et le workflow ne sont pas encore paramΓ©trΓ©s.\nIl est vivement recommandΓ© de charger le paramΓ©trage par defaut. Vous pourrez le modifier une fois chargΓ©."
907 text_load_default_configuration: Charger le paramΓ©trage par dΓ©faut
910 text_load_default_configuration: Charger le paramΓ©trage par dΓ©faut
908 text_status_changed_by_changeset: "AppliquΓ© par commit %{value}."
911 text_status_changed_by_changeset: "AppliquΓ© par commit %{value}."
909 text_time_logged_by_changeset: "AppliquΓ© par commit %{value}"
912 text_time_logged_by_changeset: "AppliquΓ© par commit %{value}"
910 text_issues_destroy_confirmation: 'Êtes-vous sûr de vouloir supprimer la ou les demandes(s) selectionnée(s) ?'
913 text_issues_destroy_confirmation: 'Êtes-vous sûr de vouloir supprimer la ou les demandes(s) selectionnée(s) ?'
911 text_issues_destroy_descendants_confirmation: "Cela entrainera Γ©galement la suppression de %{count} sous-tΓ’che(s)."
914 text_issues_destroy_descendants_confirmation: "Cela entrainera Γ©galement la suppression de %{count} sous-tΓ’che(s)."
912 text_select_project_modules: 'SΓ©lectionner les modules Γ  activer pour ce projet :'
915 text_select_project_modules: 'SΓ©lectionner les modules Γ  activer pour ce projet :'
913 text_default_administrator_account_changed: Compte administrateur par dΓ©faut changΓ©
916 text_default_administrator_account_changed: Compte administrateur par dΓ©faut changΓ©
914 text_file_repository_writable: RΓ©pertoire de stockage des fichiers accessible en Γ©criture
917 text_file_repository_writable: RΓ©pertoire de stockage des fichiers accessible en Γ©criture
915 text_plugin_assets_writable: RΓ©pertoire public des plugins accessible en Γ©criture
918 text_plugin_assets_writable: RΓ©pertoire public des plugins accessible en Γ©criture
916 text_rmagick_available: Bibliothèque RMagick présente (optionnelle)
919 text_rmagick_available: Bibliothèque RMagick présente (optionnelle)
917 text_destroy_time_entries_question: "%{hours} heures ont Γ©tΓ© enregistrΓ©es sur les demandes Γ  supprimer. Que voulez-vous faire ?"
920 text_destroy_time_entries_question: "%{hours} heures ont Γ©tΓ© enregistrΓ©es sur les demandes Γ  supprimer. Que voulez-vous faire ?"
918 text_destroy_time_entries: Supprimer les heures
921 text_destroy_time_entries: Supprimer les heures
919 text_assign_time_entries_to_project: Reporter les heures sur le projet
922 text_assign_time_entries_to_project: Reporter les heures sur le projet
920 text_reassign_time_entries: 'Reporter les heures sur cette demande:'
923 text_reassign_time_entries: 'Reporter les heures sur cette demande:'
921 text_user_wrote: "%{value} a Γ©crit :"
924 text_user_wrote: "%{value} a Γ©crit :"
922 text_enumeration_destroy_question: "Cette valeur est affectΓ©e Γ  %{count} objets."
925 text_enumeration_destroy_question: "Cette valeur est affectΓ©e Γ  %{count} objets."
923 text_enumeration_category_reassign_to: 'RΓ©affecter les objets Γ  cette valeur:'
926 text_enumeration_category_reassign_to: 'RΓ©affecter les objets Γ  cette valeur:'
924 text_email_delivery_not_configured: "L'envoi de mail n'est pas configurΓ©, les notifications sont dΓ©sactivΓ©es.\nConfigurez votre serveur SMTP dans config/configuration.yml et redΓ©marrez l'application pour les activer."
927 text_email_delivery_not_configured: "L'envoi de mail n'est pas configurΓ©, les notifications sont dΓ©sactivΓ©es.\nConfigurez votre serveur SMTP dans config/configuration.yml et redΓ©marrez l'application pour les activer."
925 text_repository_usernames_mapping: "Vous pouvez sΓ©lectionner ou modifier l'utilisateur Redmine associΓ© Γ  chaque nom d'utilisateur figurant dans l'historique du dΓ©pΓ΄t.\nLes utilisateurs avec le mΓͺme identifiant ou la mΓͺme adresse mail seront automatiquement associΓ©s."
928 text_repository_usernames_mapping: "Vous pouvez sΓ©lectionner ou modifier l'utilisateur Redmine associΓ© Γ  chaque nom d'utilisateur figurant dans l'historique du dΓ©pΓ΄t.\nLes utilisateurs avec le mΓͺme identifiant ou la mΓͺme adresse mail seront automatiquement associΓ©s."
926 text_diff_truncated: '... Ce diffΓ©rentiel a Γ©tΓ© tronquΓ© car il excΓ¨de la taille maximale pouvant Γͺtre affichΓ©e.'
929 text_diff_truncated: '... Ce diffΓ©rentiel a Γ©tΓ© tronquΓ© car il excΓ¨de la taille maximale pouvant Γͺtre affichΓ©e.'
927 text_custom_field_possible_values_info: 'Une ligne par valeur'
930 text_custom_field_possible_values_info: 'Une ligne par valeur'
928 text_wiki_page_destroy_question: "Cette page possède %{descendants} sous-page(s) et descendante(s). Que voulez-vous faire ?"
931 text_wiki_page_destroy_question: "Cette page possède %{descendants} sous-page(s) et descendante(s). Que voulez-vous faire ?"
929 text_wiki_page_nullify_children: "Conserver les sous-pages en tant que pages racines"
932 text_wiki_page_nullify_children: "Conserver les sous-pages en tant que pages racines"
930 text_wiki_page_destroy_children: "Supprimer les sous-pages et toutes leurs descedantes"
933 text_wiki_page_destroy_children: "Supprimer les sous-pages et toutes leurs descedantes"
931 text_wiki_page_reassign_children: "RΓ©affecter les sous-pages Γ  cette page"
934 text_wiki_page_reassign_children: "RΓ©affecter les sous-pages Γ  cette page"
932 text_own_membership_delete_confirmation: "Vous allez supprimer tout ou partie de vos permissions sur ce projet et ne serez peut-Γͺtre plus autorisΓ© Γ  modifier ce projet.\nEtes-vous sΓ»r de vouloir continuer ?"
935 text_own_membership_delete_confirmation: "Vous allez supprimer tout ou partie de vos permissions sur ce projet et ne serez peut-Γͺtre plus autorisΓ© Γ  modifier ce projet.\nEtes-vous sΓ»r de vouloir continuer ?"
933 text_warn_on_leaving_unsaved: "Cette page contient du texte non sauvegardΓ© qui sera perdu si vous quittez la page."
936 text_warn_on_leaving_unsaved: "Cette page contient du texte non sauvegardΓ© qui sera perdu si vous quittez la page."
934 text_issue_conflict_resolution_overwrite: "Appliquer quand mΓͺme ma mise Γ  jour (les notes prΓ©cΓ©dentes seront conservΓ©es mais des changements pourront Γͺtre Γ©crasΓ©s)"
937 text_issue_conflict_resolution_overwrite: "Appliquer quand mΓͺme ma mise Γ  jour (les notes prΓ©cΓ©dentes seront conservΓ©es mais des changements pourront Γͺtre Γ©crasΓ©s)"
935 text_issue_conflict_resolution_add_notes: "Ajouter mes notes et ignorer mes autres changements"
938 text_issue_conflict_resolution_add_notes: "Ajouter mes notes et ignorer mes autres changements"
936 text_issue_conflict_resolution_cancel: "Annuler ma mise Γ  jour et rΓ©afficher %{link}"
939 text_issue_conflict_resolution_cancel: "Annuler ma mise Γ  jour et rΓ©afficher %{link}"
940 text_account_destroy_confirmation: "Êtes-vous sûr de vouloir continuer ?\nVotre compte sera définitivement supprimé, sans aucune possibilité de le réactiver."
937
941
938 default_role_manager: "Manager "
942 default_role_manager: "Manager "
939 default_role_developer: "DΓ©veloppeur "
943 default_role_developer: "DΓ©veloppeur "
940 default_role_reporter: "Rapporteur "
944 default_role_reporter: "Rapporteur "
941 default_tracker_bug: Anomalie
945 default_tracker_bug: Anomalie
942 default_tracker_feature: Evolution
946 default_tracker_feature: Evolution
943 default_tracker_support: Assistance
947 default_tracker_support: Assistance
944 default_issue_status_new: Nouveau
948 default_issue_status_new: Nouveau
945 default_issue_status_in_progress: En cours
949 default_issue_status_in_progress: En cours
946 default_issue_status_resolved: RΓ©solu
950 default_issue_status_resolved: RΓ©solu
947 default_issue_status_feedback: Commentaire
951 default_issue_status_feedback: Commentaire
948 default_issue_status_closed: FermΓ©
952 default_issue_status_closed: FermΓ©
949 default_issue_status_rejected: RejetΓ©
953 default_issue_status_rejected: RejetΓ©
950 default_doc_category_user: Documentation utilisateur
954 default_doc_category_user: Documentation utilisateur
951 default_doc_category_tech: Documentation technique
955 default_doc_category_tech: Documentation technique
952 default_priority_low: Bas
956 default_priority_low: Bas
953 default_priority_normal: Normal
957 default_priority_normal: Normal
954 default_priority_high: Haut
958 default_priority_high: Haut
955 default_priority_urgent: Urgent
959 default_priority_urgent: Urgent
956 default_priority_immediate: ImmΓ©diat
960 default_priority_immediate: ImmΓ©diat
957 default_activity_design: Conception
961 default_activity_design: Conception
958 default_activity_development: DΓ©veloppement
962 default_activity_development: DΓ©veloppement
959
963
960 enumeration_issue_priorities: PrioritΓ©s des demandes
964 enumeration_issue_priorities: PrioritΓ©s des demandes
961 enumeration_doc_categories: CatΓ©gories des documents
965 enumeration_doc_categories: CatΓ©gories des documents
962 enumeration_activities: ActivitΓ©s (suivi du temps)
966 enumeration_activities: ActivitΓ©s (suivi du temps)
963 label_greater_or_equal: ">="
967 label_greater_or_equal: ">="
964 label_less_or_equal: "<="
968 label_less_or_equal: "<="
965 label_between: entre
969 label_between: entre
966 label_view_all_revisions: Voir toutes les rΓ©visions
970 label_view_all_revisions: Voir toutes les rΓ©visions
967 label_tag: Tag
971 label_tag: Tag
968 label_branch: Branche
972 label_branch: Branche
969 error_no_tracker_in_project: "Aucun tracker n'est associΓ© Γ  ce projet. VΓ©rifier la configuration du projet."
973 error_no_tracker_in_project: "Aucun tracker n'est associΓ© Γ  ce projet. VΓ©rifier la configuration du projet."
970 error_no_default_issue_status: "Aucun statut de demande n'est dΓ©fini par dΓ©faut. VΓ©rifier votre configuration (Administration -> Statuts de demandes)."
974 error_no_default_issue_status: "Aucun statut de demande n'est dΓ©fini par dΓ©faut. VΓ©rifier votre configuration (Administration -> Statuts de demandes)."
971 text_journal_changed: "%{label} changΓ© de %{old} Γ  %{new}"
975 text_journal_changed: "%{label} changΓ© de %{old} Γ  %{new}"
972 text_journal_changed_no_detail: "%{label} mis Γ  jour"
976 text_journal_changed_no_detail: "%{label} mis Γ  jour"
973 text_journal_set_to: "%{label} mis Γ  %{value}"
977 text_journal_set_to: "%{label} mis Γ  %{value}"
974 text_journal_deleted: "%{label} %{old} supprimΓ©"
978 text_journal_deleted: "%{label} %{old} supprimΓ©"
975 text_journal_added: "%{label} %{value} ajoutΓ©"
979 text_journal_added: "%{label} %{value} ajoutΓ©"
976 enumeration_system_activity: Activité système
980 enumeration_system_activity: Activité système
977 label_board_sticky: Sticky
981 label_board_sticky: Sticky
978 label_board_locked: VerrouillΓ©
982 label_board_locked: VerrouillΓ©
979 error_unable_delete_issue_status: Impossible de supprimer le statut de demande
983 error_unable_delete_issue_status: Impossible de supprimer le statut de demande
980 error_can_not_delete_custom_field: Impossible de supprimer le champ personnalisΓ©
984 error_can_not_delete_custom_field: Impossible de supprimer le champ personnalisΓ©
981 error_unable_to_connect: Connexion impossible (%{value})
985 error_unable_to_connect: Connexion impossible (%{value})
982 error_can_not_remove_role: Ce rΓ΄le est utilisΓ© et ne peut pas Γͺtre supprimΓ©.
986 error_can_not_remove_role: Ce rΓ΄le est utilisΓ© et ne peut pas Γͺtre supprimΓ©.
983 error_can_not_delete_tracker: Ce tracker contient des demandes et ne peut pas Γͺtre supprimΓ©.
987 error_can_not_delete_tracker: Ce tracker contient des demandes et ne peut pas Γͺtre supprimΓ©.
984 field_principal: Principal
988 field_principal: Principal
985 notice_failed_to_save_members: "Erreur lors de la sauvegarde des membres: %{errors}."
989 notice_failed_to_save_members: "Erreur lors de la sauvegarde des membres: %{errors}."
986 text_zoom_out: Zoom arrière
990 text_zoom_out: Zoom arrière
987 text_zoom_in: Zoom avant
991 text_zoom_in: Zoom avant
988 notice_unable_delete_time_entry: Impossible de supprimer le temps passΓ©.
992 notice_unable_delete_time_entry: Impossible de supprimer le temps passΓ©.
989 label_overall_spent_time: Temps passΓ© global
993 label_overall_spent_time: Temps passΓ© global
990 field_time_entries: Temps passΓ©
994 field_time_entries: Temps passΓ©
991 project_module_gantt: Gantt
995 project_module_gantt: Gantt
992 project_module_calendar: Calendrier
996 project_module_calendar: Calendrier
993 button_edit_associated_wikipage: "Modifier la page wiki associΓ©e: %{page_title}"
997 button_edit_associated_wikipage: "Modifier la page wiki associΓ©e: %{page_title}"
994 text_are_you_sure_with_children: Supprimer la demande et toutes ses sous-demandes ?
998 text_are_you_sure_with_children: Supprimer la demande et toutes ses sous-demandes ?
995 field_text: Champ texte
999 field_text: Champ texte
996 label_user_mail_option_only_owner: Seulement pour ce que j'ai créé
1000 label_user_mail_option_only_owner: Seulement pour ce que j'ai créé
997 setting_default_notification_option: Option de notification par dΓ©faut
1001 setting_default_notification_option: Option de notification par dΓ©faut
998 label_user_mail_option_only_my_events: Seulement pour ce que je surveille
1002 label_user_mail_option_only_my_events: Seulement pour ce que je surveille
999 label_user_mail_option_only_assigned: Seulement pour ce qui m'est assignΓ©
1003 label_user_mail_option_only_assigned: Seulement pour ce qui m'est assignΓ©
1000 label_user_mail_option_none: Aucune notification
1004 label_user_mail_option_none: Aucune notification
1001 field_member_of_group: Groupe de l'assignΓ©
1005 field_member_of_group: Groupe de l'assignΓ©
1002 field_assigned_to_role: RΓ΄le de l'assignΓ©
1006 field_assigned_to_role: RΓ΄le de l'assignΓ©
1003 setting_emails_header: En-tΓͺte des emails
1007 setting_emails_header: En-tΓͺte des emails
1004 label_bulk_edit_selected_time_entries: Modifier les temps passΓ©s sΓ©lectionnΓ©s
1008 label_bulk_edit_selected_time_entries: Modifier les temps passΓ©s sΓ©lectionnΓ©s
1005 text_time_entries_destroy_confirmation: "Etes-vous sΓ»r de vouloir supprimer les temps passΓ©s sΓ©lectionnΓ©s ?"
1009 text_time_entries_destroy_confirmation: "Etes-vous sΓ»r de vouloir supprimer les temps passΓ©s sΓ©lectionnΓ©s ?"
1006 field_scm_path_encoding: Encodage des chemins
1010 field_scm_path_encoding: Encodage des chemins
1007 text_scm_path_encoding_note: "DΓ©faut : UTF-8"
1011 text_scm_path_encoding_note: "DΓ©faut : UTF-8"
1008 field_path_to_repository: Chemin du dΓ©pΓ΄t
1012 field_path_to_repository: Chemin du dΓ©pΓ΄t
1009 field_root_directory: RΓ©pertoire racine
1013 field_root_directory: RΓ©pertoire racine
1010 field_cvs_module: Module
1014 field_cvs_module: Module
1011 field_cvsroot: CVSROOT
1015 field_cvsroot: CVSROOT
1012 text_mercurial_repository_note: "DΓ©pΓ΄t local (exemples : /hgrepo, c:\\hgrepo)"
1016 text_mercurial_repository_note: "DΓ©pΓ΄t local (exemples : /hgrepo, c:\\hgrepo)"
1013 text_scm_command: Commande
1017 text_scm_command: Commande
1014 text_scm_command_version: Version
1018 text_scm_command_version: Version
1015 label_git_report_last_commit: Afficher le dernier commit des fichiers et rΓ©pertoires
1019 label_git_report_last_commit: Afficher le dernier commit des fichiers et rΓ©pertoires
1016 text_scm_config: Vous pouvez configurer les commandes des SCM dans config/configuration.yml. Redémarrer l'application après modification.
1020 text_scm_config: Vous pouvez configurer les commandes des SCM dans config/configuration.yml. Redémarrer l'application après modification.
1017 text_scm_command_not_available: Ce SCM n'est pas disponible. Vérifier les paramètres dans la section administration.
1021 text_scm_command_not_available: Ce SCM n'est pas disponible. Vérifier les paramètres dans la section administration.
1018 label_diff: diff
1022 label_diff: diff
1019 text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo)
1023 text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo)
1020 description_query_sort_criteria_direction: Ordre de tri
1024 description_query_sort_criteria_direction: Ordre de tri
1021 description_project_scope: Périmètre de recherche
1025 description_project_scope: Périmètre de recherche
1022 description_filter: Filtre
1026 description_filter: Filtre
1023 description_user_mail_notification: Option de notification
1027 description_user_mail_notification: Option de notification
1024 description_date_from: Date de dΓ©but
1028 description_date_from: Date de dΓ©but
1025 description_message_content: Contenu du message
1029 description_message_content: Contenu du message
1026 description_available_columns: Colonnes disponibles
1030 description_available_columns: Colonnes disponibles
1027 description_all_columns: Toutes les colonnes
1031 description_all_columns: Toutes les colonnes
1028 description_date_range_interval: Choisir une pΓ©riode
1032 description_date_range_interval: Choisir une pΓ©riode
1029 description_issue_category_reassign: Choisir une catΓ©gorie
1033 description_issue_category_reassign: Choisir une catΓ©gorie
1030 description_search: Champ de recherche
1034 description_search: Champ de recherche
1031 description_notes: Notes
1035 description_notes: Notes
1032 description_date_range_list: Choisir une pΓ©riode prΓ©dΓ©finie
1036 description_date_range_list: Choisir une pΓ©riode prΓ©dΓ©finie
1033 description_choose_project: Projets
1037 description_choose_project: Projets
1034 description_date_to: Date de fin
1038 description_date_to: Date de fin
1035 description_query_sort_criteria_attribute: Critère de tri
1039 description_query_sort_criteria_attribute: Critère de tri
1036 description_wiki_subpages_reassign: Choisir une nouvelle page parent
1040 description_wiki_subpages_reassign: Choisir une nouvelle page parent
1037 description_selected_columns: Colonnes sΓ©lectionnΓ©es
1041 description_selected_columns: Colonnes sΓ©lectionnΓ©es
1038 label_parent_revision: Parent
1042 label_parent_revision: Parent
1039 label_child_revision: Enfant
1043 label_child_revision: Enfant
1040 error_scm_annotate_big_text_file: Cette entrΓ©e ne peut pas Γͺtre annotΓ©e car elle excΓ¨de la taille maximale.
1044 error_scm_annotate_big_text_file: Cette entrΓ©e ne peut pas Γͺtre annotΓ©e car elle excΓ¨de la taille maximale.
1041 setting_repositories_encodings: Encodages des fichiers et des dΓ©pΓ΄ts
1045 setting_repositories_encodings: Encodages des fichiers et des dΓ©pΓ΄ts
1042 label_search_for_watchers: Rechercher des observateurs
1046 label_search_for_watchers: Rechercher des observateurs
@@ -1,396 +1,398
1 ActionController::Routing::Routes.draw do |map|
1 ActionController::Routing::Routes.draw do |map|
2 # Add your own custom routes here.
2 # Add your own custom routes here.
3 # The priority is based upon order of creation: first created -> highest priority.
3 # The priority is based upon order of creation: first created -> highest priority.
4
4
5 # Here's a sample route:
5 # Here's a sample route:
6 # map.connect 'products/:id', :controller => 'catalog', :action => 'view'
6 # map.connect 'products/:id', :controller => 'catalog', :action => 'view'
7 # Keep in mind you can assign values other than :controller and :action
7 # Keep in mind you can assign values other than :controller and :action
8
8
9 map.home '', :controller => 'welcome', :conditions => {:method => :get}
9 map.home '', :controller => 'welcome', :conditions => {:method => :get}
10
10
11 map.signin 'login', :controller => 'account', :action => 'login',
11 map.signin 'login', :controller => 'account', :action => 'login',
12 :conditions => {:method => [:get, :post]}
12 :conditions => {:method => [:get, :post]}
13 map.signout 'logout', :controller => 'account', :action => 'logout',
13 map.signout 'logout', :controller => 'account', :action => 'logout',
14 :conditions => {:method => :get}
14 :conditions => {:method => :get}
15 map.connect 'account/register', :controller => 'account', :action => 'register',
15 map.connect 'account/register', :controller => 'account', :action => 'register',
16 :conditions => {:method => [:get, :post]}
16 :conditions => {:method => [:get, :post]}
17 map.connect 'account/lost_password', :controller => 'account', :action => 'lost_password',
17 map.connect 'account/lost_password', :controller => 'account', :action => 'lost_password',
18 :conditions => {:method => [:get, :post]}
18 :conditions => {:method => [:get, :post]}
19 map.connect 'account/activate', :controller => 'account', :action => 'activate',
19 map.connect 'account/activate', :controller => 'account', :action => 'activate',
20 :conditions => {:method => :get}
20 :conditions => {:method => :get}
21
21
22 map.connect 'projects/:id/wiki', :controller => 'wikis',
22 map.connect 'projects/:id/wiki', :controller => 'wikis',
23 :action => 'edit', :conditions => {:method => :post}
23 :action => 'edit', :conditions => {:method => :post}
24 map.connect 'projects/:id/wiki/destroy', :controller => 'wikis',
24 map.connect 'projects/:id/wiki/destroy', :controller => 'wikis',
25 :action => 'destroy', :conditions => {:method => [:get, :post]}
25 :action => 'destroy', :conditions => {:method => [:get, :post]}
26
26
27 map.with_options :controller => 'messages' do |messages_routes|
27 map.with_options :controller => 'messages' do |messages_routes|
28 messages_routes.with_options :conditions => {:method => :get} do |messages_views|
28 messages_routes.with_options :conditions => {:method => :get} do |messages_views|
29 messages_views.connect 'boards/:board_id/topics/new', :action => 'new'
29 messages_views.connect 'boards/:board_id/topics/new', :action => 'new'
30 messages_views.connect 'boards/:board_id/topics/:id', :action => 'show'
30 messages_views.connect 'boards/:board_id/topics/:id', :action => 'show'
31 messages_views.connect 'boards/:board_id/topics/:id/edit', :action => 'edit'
31 messages_views.connect 'boards/:board_id/topics/:id/edit', :action => 'edit'
32 end
32 end
33 messages_routes.with_options :conditions => {:method => :post} do |messages_actions|
33 messages_routes.with_options :conditions => {:method => :post} do |messages_actions|
34 messages_actions.connect 'boards/:board_id/topics/new', :action => 'new'
34 messages_actions.connect 'boards/:board_id/topics/new', :action => 'new'
35 messages_actions.connect 'boards/:board_id/topics/preview', :action => 'preview'
35 messages_actions.connect 'boards/:board_id/topics/preview', :action => 'preview'
36 messages_actions.connect 'boards/:board_id/topics/quote/:id', :action => 'quote'
36 messages_actions.connect 'boards/:board_id/topics/quote/:id', :action => 'quote'
37 messages_actions.connect 'boards/:board_id/topics/:id/replies', :action => 'reply'
37 messages_actions.connect 'boards/:board_id/topics/:id/replies', :action => 'reply'
38 messages_actions.connect 'boards/:board_id/topics/:id/edit', :action => 'edit'
38 messages_actions.connect 'boards/:board_id/topics/:id/edit', :action => 'edit'
39 messages_actions.connect 'boards/:board_id/topics/:id/destroy', :action => 'destroy'
39 messages_actions.connect 'boards/:board_id/topics/:id/destroy', :action => 'destroy'
40 end
40 end
41 end
41 end
42
42
43 # Misc issue routes. TODO: move into resources
43 # Misc issue routes. TODO: move into resources
44 map.auto_complete_issues '/issues/auto_complete', :controller => 'auto_completes',
44 map.auto_complete_issues '/issues/auto_complete', :controller => 'auto_completes',
45 :action => 'issues', :conditions => { :method => :get }
45 :action => 'issues', :conditions => { :method => :get }
46 # TODO: would look nicer as /issues/:id/preview
46 # TODO: would look nicer as /issues/:id/preview
47 map.preview_new_issue '/issues/preview/new/:project_id', :controller => 'previews',
47 map.preview_new_issue '/issues/preview/new/:project_id', :controller => 'previews',
48 :action => 'issue'
48 :action => 'issue'
49 map.preview_edit_issue '/issues/preview/edit/:id', :controller => 'previews',
49 map.preview_edit_issue '/issues/preview/edit/:id', :controller => 'previews',
50 :action => 'issue'
50 :action => 'issue'
51 map.issues_context_menu '/issues/context_menu',
51 map.issues_context_menu '/issues/context_menu',
52 :controller => 'context_menus', :action => 'issues'
52 :controller => 'context_menus', :action => 'issues'
53
53
54 map.issue_changes '/issues/changes', :controller => 'journals', :action => 'index'
54 map.issue_changes '/issues/changes', :controller => 'journals', :action => 'index'
55 map.quoted_issue '/issues/:id/quoted', :controller => 'journals', :action => 'new',
55 map.quoted_issue '/issues/:id/quoted', :controller => 'journals', :action => 'new',
56 :id => /\d+/, :conditions => { :method => :post }
56 :id => /\d+/, :conditions => { :method => :post }
57
57
58 map.connect '/journals/diff/:id', :controller => 'journals', :action => 'diff',
58 map.connect '/journals/diff/:id', :controller => 'journals', :action => 'diff',
59 :id => /\d+/, :conditions => { :method => :get }
59 :id => /\d+/, :conditions => { :method => :get }
60 map.connect '/journals/edit/:id', :controller => 'journals', :action => 'edit',
60 map.connect '/journals/edit/:id', :controller => 'journals', :action => 'edit',
61 :id => /\d+/, :conditions => { :method => [:get, :post] }
61 :id => /\d+/, :conditions => { :method => [:get, :post] }
62
62
63 map.with_options :controller => 'gantts', :action => 'show' do |gantts_routes|
63 map.with_options :controller => 'gantts', :action => 'show' do |gantts_routes|
64 gantts_routes.connect '/projects/:project_id/issues/gantt'
64 gantts_routes.connect '/projects/:project_id/issues/gantt'
65 gantts_routes.connect '/projects/:project_id/issues/gantt.:format'
65 gantts_routes.connect '/projects/:project_id/issues/gantt.:format'
66 gantts_routes.connect '/issues/gantt.:format'
66 gantts_routes.connect '/issues/gantt.:format'
67 end
67 end
68
68
69 map.with_options :controller => 'calendars', :action => 'show' do |calendars_routes|
69 map.with_options :controller => 'calendars', :action => 'show' do |calendars_routes|
70 calendars_routes.connect '/projects/:project_id/issues/calendar'
70 calendars_routes.connect '/projects/:project_id/issues/calendar'
71 calendars_routes.connect '/issues/calendar'
71 calendars_routes.connect '/issues/calendar'
72 end
72 end
73
73
74 map.with_options :controller => 'reports', :conditions => {:method => :get} do |reports|
74 map.with_options :controller => 'reports', :conditions => {:method => :get} do |reports|
75 reports.connect 'projects/:id/issues/report', :action => 'issue_report'
75 reports.connect 'projects/:id/issues/report', :action => 'issue_report'
76 reports.connect 'projects/:id/issues/report/:detail', :action => 'issue_report_details'
76 reports.connect 'projects/:id/issues/report/:detail', :action => 'issue_report_details'
77 end
77 end
78
78
79 map.connect 'my/account', :controller => 'my', :action => 'account',
79 map.connect 'my/account', :controller => 'my', :action => 'account',
80 :conditions => {:method => [:get, :post]}
80 :conditions => {:method => [:get, :post]}
81 map.connect 'my/account/destroy', :controller => 'my', :action => 'destroy',
82 :conditions => {:method => [:get, :post]}
81 map.connect 'my/page', :controller => 'my', :action => 'page',
83 map.connect 'my/page', :controller => 'my', :action => 'page',
82 :conditions => {:method => :get}
84 :conditions => {:method => :get}
83 # Redirects to my/page
85 # Redirects to my/page
84 map.connect 'my', :controller => 'my', :action => 'index',
86 map.connect 'my', :controller => 'my', :action => 'index',
85 :conditions => {:method => :get}
87 :conditions => {:method => :get}
86 map.connect 'my/reset_rss_key', :controller => 'my', :action => 'reset_rss_key',
88 map.connect 'my/reset_rss_key', :controller => 'my', :action => 'reset_rss_key',
87 :conditions => {:method => :post}
89 :conditions => {:method => :post}
88 map.connect 'my/reset_api_key', :controller => 'my', :action => 'reset_api_key',
90 map.connect 'my/reset_api_key', :controller => 'my', :action => 'reset_api_key',
89 :conditions => {:method => :post}
91 :conditions => {:method => :post}
90 map.connect 'my/password', :controller => 'my', :action => 'password',
92 map.connect 'my/password', :controller => 'my', :action => 'password',
91 :conditions => {:method => [:get, :post]}
93 :conditions => {:method => [:get, :post]}
92 map.connect 'my/page_layout', :controller => 'my', :action => 'page_layout',
94 map.connect 'my/page_layout', :controller => 'my', :action => 'page_layout',
93 :conditions => {:method => :get}
95 :conditions => {:method => :get}
94 map.connect 'my/add_block', :controller => 'my', :action => 'add_block',
96 map.connect 'my/add_block', :controller => 'my', :action => 'add_block',
95 :conditions => {:method => :post}
97 :conditions => {:method => :post}
96 map.connect 'my/remove_block', :controller => 'my', :action => 'remove_block',
98 map.connect 'my/remove_block', :controller => 'my', :action => 'remove_block',
97 :conditions => {:method => :post}
99 :conditions => {:method => :post}
98 map.connect 'my/order_blocks', :controller => 'my', :action => 'order_blocks',
100 map.connect 'my/order_blocks', :controller => 'my', :action => 'order_blocks',
99 :conditions => {:method => :post}
101 :conditions => {:method => :post}
100
102
101 map.with_options :controller => 'users' do |users|
103 map.with_options :controller => 'users' do |users|
102 users.user_membership 'users/:id/memberships/:membership_id',
104 users.user_membership 'users/:id/memberships/:membership_id',
103 :action => 'edit_membership',
105 :action => 'edit_membership',
104 :conditions => {:method => :put}
106 :conditions => {:method => :put}
105 users.connect 'users/:id/memberships/:membership_id',
107 users.connect 'users/:id/memberships/:membership_id',
106 :action => 'destroy_membership',
108 :action => 'destroy_membership',
107 :conditions => {:method => :delete}
109 :conditions => {:method => :delete}
108 users.user_memberships 'users/:id/memberships',
110 users.user_memberships 'users/:id/memberships',
109 :action => 'edit_membership',
111 :action => 'edit_membership',
110 :conditions => {:method => :post}
112 :conditions => {:method => :post}
111 end
113 end
112 map.resources :users
114 map.resources :users
113
115
114 # For nice "roadmap" in the url for the index action
116 # For nice "roadmap" in the url for the index action
115 map.connect 'projects/:project_id/roadmap', :controller => 'versions', :action => 'index'
117 map.connect 'projects/:project_id/roadmap', :controller => 'versions', :action => 'index'
116
118
117 map.preview_news '/news/preview', :controller => 'previews', :action => 'news'
119 map.preview_news '/news/preview', :controller => 'previews', :action => 'news'
118 map.connect 'news/:id/comments', :controller => 'comments',
120 map.connect 'news/:id/comments', :controller => 'comments',
119 :action => 'create', :conditions => {:method => :post}
121 :action => 'create', :conditions => {:method => :post}
120 map.connect 'news/:id/comments/:comment_id', :controller => 'comments',
122 map.connect 'news/:id/comments/:comment_id', :controller => 'comments',
121 :action => 'destroy', :conditions => {:method => :delete}
123 :action => 'destroy', :conditions => {:method => :delete}
122
124
123 map.connect 'watchers/new', :controller=> 'watchers', :action => 'new',
125 map.connect 'watchers/new', :controller=> 'watchers', :action => 'new',
124 :conditions => {:method => :get}
126 :conditions => {:method => :get}
125 map.connect 'watchers', :controller=> 'watchers', :action => 'create',
127 map.connect 'watchers', :controller=> 'watchers', :action => 'create',
126 :conditions => {:method => :post}
128 :conditions => {:method => :post}
127 map.connect 'watchers/append', :controller=> 'watchers', :action => 'append',
129 map.connect 'watchers/append', :controller=> 'watchers', :action => 'append',
128 :conditions => {:method => :post}
130 :conditions => {:method => :post}
129 map.connect 'watchers/destroy', :controller=> 'watchers', :action => 'destroy',
131 map.connect 'watchers/destroy', :controller=> 'watchers', :action => 'destroy',
130 :conditions => {:method => :post}
132 :conditions => {:method => :post}
131 map.connect 'watchers/watch', :controller=> 'watchers', :action => 'watch',
133 map.connect 'watchers/watch', :controller=> 'watchers', :action => 'watch',
132 :conditions => {:method => :post}
134 :conditions => {:method => :post}
133 map.connect 'watchers/unwatch', :controller=> 'watchers', :action => 'unwatch',
135 map.connect 'watchers/unwatch', :controller=> 'watchers', :action => 'unwatch',
134 :conditions => {:method => :post}
136 :conditions => {:method => :post}
135 map.connect 'watchers/autocomplete_for_user', :controller=> 'watchers', :action => 'autocomplete_for_user',
137 map.connect 'watchers/autocomplete_for_user', :controller=> 'watchers', :action => 'autocomplete_for_user',
136 :conditions => {:method => :get}
138 :conditions => {:method => :get}
137
139
138 # TODO: port to be part of the resources route(s)
140 # TODO: port to be part of the resources route(s)
139 map.with_options :conditions => {:method => :get} do |project_views|
141 map.with_options :conditions => {:method => :get} do |project_views|
140 project_views.connect 'projects/:id/settings/:tab',
142 project_views.connect 'projects/:id/settings/:tab',
141 :controller => 'projects', :action => 'settings'
143 :controller => 'projects', :action => 'settings'
142 project_views.connect 'projects/:project_id/issues/:copy_from/copy',
144 project_views.connect 'projects/:project_id/issues/:copy_from/copy',
143 :controller => 'issues', :action => 'new'
145 :controller => 'issues', :action => 'new'
144 end
146 end
145
147
146 map.resources :projects, :member => {
148 map.resources :projects, :member => {
147 :copy => [:get, :post],
149 :copy => [:get, :post],
148 :settings => :get,
150 :settings => :get,
149 :modules => :post,
151 :modules => :post,
150 :archive => :post,
152 :archive => :post,
151 :unarchive => :post
153 :unarchive => :post
152 } do |project|
154 } do |project|
153 project.resource :enumerations, :controller => 'project_enumerations',
155 project.resource :enumerations, :controller => 'project_enumerations',
154 :only => [:update, :destroy]
156 :only => [:update, :destroy]
155 # issue form update
157 # issue form update
156 project.issue_form 'issues/new', :controller => 'issues',
158 project.issue_form 'issues/new', :controller => 'issues',
157 :action => 'new', :conditions => {:method => [:post, :put]}
159 :action => 'new', :conditions => {:method => [:post, :put]}
158 project.resources :issues, :only => [:index, :new, :create] do |issues|
160 project.resources :issues, :only => [:index, :new, :create] do |issues|
159 issues.resources :time_entries, :controller => 'timelog',
161 issues.resources :time_entries, :controller => 'timelog',
160 :collection => {:report => :get}
162 :collection => {:report => :get}
161 end
163 end
162
164
163 project.resources :files, :only => [:index, :new, :create]
165 project.resources :files, :only => [:index, :new, :create]
164 project.resources :versions, :shallow => true,
166 project.resources :versions, :shallow => true,
165 :collection => {:close_completed => :put},
167 :collection => {:close_completed => :put},
166 :member => {:status_by => :post}
168 :member => {:status_by => :post}
167 project.resources :news, :shallow => true
169 project.resources :news, :shallow => true
168 project.resources :time_entries, :controller => 'timelog',
170 project.resources :time_entries, :controller => 'timelog',
169 :collection => {:report => :get}
171 :collection => {:report => :get}
170 project.resources :queries, :only => [:new, :create]
172 project.resources :queries, :only => [:new, :create]
171 project.resources :issue_categories, :shallow => true
173 project.resources :issue_categories, :shallow => true
172 project.resources :documents, :shallow => true, :member => {:add_attachment => :post}
174 project.resources :documents, :shallow => true, :member => {:add_attachment => :post}
173 project.resources :boards
175 project.resources :boards
174 project.resources :repositories, :shallow => true, :except => [:index, :show],
176 project.resources :repositories, :shallow => true, :except => [:index, :show],
175 :member => {:committers => [:get, :post]}
177 :member => {:committers => [:get, :post]}
176 project.resources :memberships, :shallow => true, :controller => 'members',
178 project.resources :memberships, :shallow => true, :controller => 'members',
177 :only => [:index, :show, :create, :update, :destroy],
179 :only => [:index, :show, :create, :update, :destroy],
178 :collection => {:autocomplete => :get}
180 :collection => {:autocomplete => :get}
179
181
180 project.wiki_start_page 'wiki', :controller => 'wiki', :action => 'show', :conditions => {:method => :get}
182 project.wiki_start_page 'wiki', :controller => 'wiki', :action => 'show', :conditions => {:method => :get}
181 project.wiki_index 'wiki/index', :controller => 'wiki', :action => 'index', :conditions => {:method => :get}
183 project.wiki_index 'wiki/index', :controller => 'wiki', :action => 'index', :conditions => {:method => :get}
182 project.wiki_diff 'wiki/:id/diff/:version', :controller => 'wiki', :action => 'diff', :version => nil
184 project.wiki_diff 'wiki/:id/diff/:version', :controller => 'wiki', :action => 'diff', :version => nil
183 project.wiki_diff 'wiki/:id/diff/:version/vs/:version_from', :controller => 'wiki', :action => 'diff'
185 project.wiki_diff 'wiki/:id/diff/:version/vs/:version_from', :controller => 'wiki', :action => 'diff'
184 project.wiki_annotate 'wiki/:id/annotate/:version', :controller => 'wiki', :action => 'annotate'
186 project.wiki_annotate 'wiki/:id/annotate/:version', :controller => 'wiki', :action => 'annotate'
185 project.resources :wiki, :except => [:new, :create], :member => {
187 project.resources :wiki, :except => [:new, :create], :member => {
186 :rename => [:get, :post],
188 :rename => [:get, :post],
187 :history => :get,
189 :history => :get,
188 :preview => :any,
190 :preview => :any,
189 :protect => :post,
191 :protect => :post,
190 :add_attachment => :post
192 :add_attachment => :post
191 }, :collection => {
193 }, :collection => {
192 :export => :get,
194 :export => :get,
193 :date_index => :get
195 :date_index => :get
194 }
196 }
195 end
197 end
196
198
197 map.connect 'news', :controller => 'news', :action => 'index'
199 map.connect 'news', :controller => 'news', :action => 'index'
198 map.connect 'news.:format', :controller => 'news', :action => 'index'
200 map.connect 'news.:format', :controller => 'news', :action => 'index'
199
201
200 map.resources :queries, :except => [:show]
202 map.resources :queries, :except => [:show]
201 map.resources :issues,
203 map.resources :issues,
202 :collection => {:bulk_edit => [:get, :post], :bulk_update => :post} do |issues|
204 :collection => {:bulk_edit => [:get, :post], :bulk_update => :post} do |issues|
203 issues.resources :time_entries, :controller => 'timelog',
205 issues.resources :time_entries, :controller => 'timelog',
204 :collection => {:report => :get}
206 :collection => {:report => :get}
205 issues.resources :relations, :shallow => true,
207 issues.resources :relations, :shallow => true,
206 :controller => 'issue_relations',
208 :controller => 'issue_relations',
207 :only => [:index, :show, :create, :destroy]
209 :only => [:index, :show, :create, :destroy]
208 end
210 end
209 # Bulk deletion
211 # Bulk deletion
210 map.connect '/issues', :controller => 'issues', :action => 'destroy',
212 map.connect '/issues', :controller => 'issues', :action => 'destroy',
211 :conditions => {:method => :delete}
213 :conditions => {:method => :delete}
212
214
213 map.connect '/time_entries/destroy',
215 map.connect '/time_entries/destroy',
214 :controller => 'timelog', :action => 'destroy',
216 :controller => 'timelog', :action => 'destroy',
215 :conditions => { :method => :delete }
217 :conditions => { :method => :delete }
216 map.time_entries_context_menu '/time_entries/context_menu',
218 map.time_entries_context_menu '/time_entries/context_menu',
217 :controller => 'context_menus', :action => 'time_entries'
219 :controller => 'context_menus', :action => 'time_entries'
218
220
219 map.resources :time_entries, :controller => 'timelog',
221 map.resources :time_entries, :controller => 'timelog',
220 :collection => {:report => :get, :bulk_edit => :get, :bulk_update => :post}
222 :collection => {:report => :get, :bulk_edit => :get, :bulk_update => :post}
221
223
222 map.with_options :controller => 'activities', :action => 'index',
224 map.with_options :controller => 'activities', :action => 'index',
223 :conditions => {:method => :get} do |activity|
225 :conditions => {:method => :get} do |activity|
224 activity.connect 'projects/:id/activity'
226 activity.connect 'projects/:id/activity'
225 activity.connect 'projects/:id/activity.:format'
227 activity.connect 'projects/:id/activity.:format'
226 activity.connect 'activity', :id => nil
228 activity.connect 'activity', :id => nil
227 activity.connect 'activity.:format', :id => nil
229 activity.connect 'activity.:format', :id => nil
228 end
230 end
229
231
230 map.with_options :controller => 'repositories' do |repositories|
232 map.with_options :controller => 'repositories' do |repositories|
231 repositories.with_options :conditions => {:method => :get} do |repository_views|
233 repositories.with_options :conditions => {:method => :get} do |repository_views|
232 repository_views.connect 'projects/:id/repository',
234 repository_views.connect 'projects/:id/repository',
233 :action => 'show'
235 :action => 'show'
234
236
235 repository_views.connect 'projects/:id/repository/:repository_id/statistics',
237 repository_views.connect 'projects/:id/repository/:repository_id/statistics',
236 :action => 'stats'
238 :action => 'stats'
237 repository_views.connect 'projects/:id/repository/:repository_id/graph',
239 repository_views.connect 'projects/:id/repository/:repository_id/graph',
238 :action => 'graph'
240 :action => 'graph'
239
241
240 repository_views.connect 'projects/:id/repository/statistics',
242 repository_views.connect 'projects/:id/repository/statistics',
241 :action => 'stats'
243 :action => 'stats'
242 repository_views.connect 'projects/:id/repository/graph',
244 repository_views.connect 'projects/:id/repository/graph',
243 :action => 'graph'
245 :action => 'graph'
244
246
245 repository_views.connect 'projects/:id/repository/:repository_id/revisions',
247 repository_views.connect 'projects/:id/repository/:repository_id/revisions',
246 :action => 'revisions'
248 :action => 'revisions'
247 repository_views.connect 'projects/:id/repository/:repository_id/revisions.:format',
249 repository_views.connect 'projects/:id/repository/:repository_id/revisions.:format',
248 :action => 'revisions'
250 :action => 'revisions'
249 repository_views.connect 'projects/:id/repository/:repository_id/revisions/:rev',
251 repository_views.connect 'projects/:id/repository/:repository_id/revisions/:rev',
250 :action => 'revision'
252 :action => 'revision'
251 repository_views.connect 'projects/:id/repository/:repository_id/revisions/:rev/issues',
253 repository_views.connect 'projects/:id/repository/:repository_id/revisions/:rev/issues',
252 :action => 'add_related_issue', :conditions => {:method => :post}
254 :action => 'add_related_issue', :conditions => {:method => :post}
253 repository_views.connect 'projects/:id/repository/:repository_id/revisions/:rev/issues/:issue_id',
255 repository_views.connect 'projects/:id/repository/:repository_id/revisions/:rev/issues/:issue_id',
254 :action => 'remove_related_issue', :conditions => {:method => :delete}
256 :action => 'remove_related_issue', :conditions => {:method => :delete}
255 repository_views.connect 'projects/:id/repository/:repository_id/revisions/:rev/diff',
257 repository_views.connect 'projects/:id/repository/:repository_id/revisions/:rev/diff',
256 :action => 'diff'
258 :action => 'diff'
257 repository_views.connect 'projects/:id/repository/:repository_id/revisions/:rev/diff.:format',
259 repository_views.connect 'projects/:id/repository/:repository_id/revisions/:rev/diff.:format',
258 :action => 'diff'
260 :action => 'diff'
259 repository_views.connect 'projects/:id/repository/:repository_id/revisions/:rev/raw/*path',
261 repository_views.connect 'projects/:id/repository/:repository_id/revisions/:rev/raw/*path',
260 :action => 'entry', :format => 'raw'
262 :action => 'entry', :format => 'raw'
261 repository_views.connect 'projects/:id/repository/:repository_id/revisions/:rev/:action/*path',
263 repository_views.connect 'projects/:id/repository/:repository_id/revisions/:rev/:action/*path',
262 :requirements => {
264 :requirements => {
263 :action => /(browse|show|entry|changes|annotate|diff)/,
265 :action => /(browse|show|entry|changes|annotate|diff)/,
264 :rev => /[a-z0-9\.\-_]+/
266 :rev => /[a-z0-9\.\-_]+/
265 }
267 }
266 repository_views.connect 'projects/:id/repository/:repository_id/raw/*path',
268 repository_views.connect 'projects/:id/repository/:repository_id/raw/*path',
267 :action => 'entry', :format => 'raw'
269 :action => 'entry', :format => 'raw'
268 repository_views.connect 'projects/:id/repository/:repository_id/:action/*path',
270 repository_views.connect 'projects/:id/repository/:repository_id/:action/*path',
269 :requirements => { :action => /(browse|entry|changes|annotate|diff)/ }
271 :requirements => { :action => /(browse|entry|changes|annotate|diff)/ }
270 repository_views.connect 'projects/:id/repository/:repository_id/show/*path',
272 repository_views.connect 'projects/:id/repository/:repository_id/show/*path',
271 :requirements => { :path => /.+/ }
273 :requirements => { :path => /.+/ }
272
274
273 repository_views.connect 'projects/:id/repository/:repository_id/revision',
275 repository_views.connect 'projects/:id/repository/:repository_id/revision',
274 :action => 'revision'
276 :action => 'revision'
275
277
276 repository_views.connect 'projects/:id/repository/revisions',
278 repository_views.connect 'projects/:id/repository/revisions',
277 :action => 'revisions'
279 :action => 'revisions'
278 repository_views.connect 'projects/:id/repository/revisions.:format',
280 repository_views.connect 'projects/:id/repository/revisions.:format',
279 :action => 'revisions'
281 :action => 'revisions'
280 repository_views.connect 'projects/:id/repository/revisions/:rev',
282 repository_views.connect 'projects/:id/repository/revisions/:rev',
281 :action => 'revision'
283 :action => 'revision'
282 repository_views.connect 'projects/:id/repository/revisions/:rev/issues',
284 repository_views.connect 'projects/:id/repository/revisions/:rev/issues',
283 :action => 'add_related_issue', :conditions => {:method => :post}
285 :action => 'add_related_issue', :conditions => {:method => :post}
284 repository_views.connect 'projects/:id/repository/revisions/:rev/issues/:issue_id',
286 repository_views.connect 'projects/:id/repository/revisions/:rev/issues/:issue_id',
285 :action => 'remove_related_issue', :conditions => {:method => :delete}
287 :action => 'remove_related_issue', :conditions => {:method => :delete}
286 repository_views.connect 'projects/:id/repository/revisions/:rev/diff',
288 repository_views.connect 'projects/:id/repository/revisions/:rev/diff',
287 :action => 'diff'
289 :action => 'diff'
288 repository_views.connect 'projects/:id/repository/revisions/:rev/diff.:format',
290 repository_views.connect 'projects/:id/repository/revisions/:rev/diff.:format',
289 :action => 'diff'
291 :action => 'diff'
290 repository_views.connect 'projects/:id/repository/revisions/:rev/raw/*path',
292 repository_views.connect 'projects/:id/repository/revisions/:rev/raw/*path',
291 :action => 'entry', :format => 'raw'
293 :action => 'entry', :format => 'raw'
292 repository_views.connect 'projects/:id/repository/revisions/:rev/:action/*path',
294 repository_views.connect 'projects/:id/repository/revisions/:rev/:action/*path',
293 :requirements => {
295 :requirements => {
294 :action => /(browse|show|entry|changes|annotate|diff)/,
296 :action => /(browse|show|entry|changes|annotate|diff)/,
295 :rev => /[a-z0-9\.\-_]+/
297 :rev => /[a-z0-9\.\-_]+/
296 }
298 }
297 repository_views.connect 'projects/:id/repository/raw/*path',
299 repository_views.connect 'projects/:id/repository/raw/*path',
298 :action => 'entry', :format => 'raw'
300 :action => 'entry', :format => 'raw'
299 repository_views.connect 'projects/:id/repository/:action/*path',
301 repository_views.connect 'projects/:id/repository/:action/*path',
300 :requirements => { :action => /(browse|show|entry|changes|annotate|diff)/ }
302 :requirements => { :action => /(browse|show|entry|changes|annotate|diff)/ }
301
303
302 repository_views.connect 'projects/:id/repository/revision',
304 repository_views.connect 'projects/:id/repository/revision',
303 :action => 'revision'
305 :action => 'revision'
304
306
305 repository_views.connect 'projects/:id/repository/:repository_id',
307 repository_views.connect 'projects/:id/repository/:repository_id',
306 :action => 'show'
308 :action => 'show'
307 end
309 end
308 end
310 end
309
311
310 # additional routes for having the file name at the end of url
312 # additional routes for having the file name at the end of url
311 map.connect 'attachments/:id/:filename', :controller => 'attachments',
313 map.connect 'attachments/:id/:filename', :controller => 'attachments',
312 :action => 'show', :id => /\d+/, :filename => /.*/,
314 :action => 'show', :id => /\d+/, :filename => /.*/,
313 :conditions => {:method => :get}
315 :conditions => {:method => :get}
314 map.connect 'attachments/download/:id/:filename', :controller => 'attachments',
316 map.connect 'attachments/download/:id/:filename', :controller => 'attachments',
315 :action => 'download', :id => /\d+/, :filename => /.*/,
317 :action => 'download', :id => /\d+/, :filename => /.*/,
316 :conditions => {:method => :get}
318 :conditions => {:method => :get}
317 map.connect 'attachments/download/:id', :controller => 'attachments',
319 map.connect 'attachments/download/:id', :controller => 'attachments',
318 :action => 'download', :id => /\d+/,
320 :action => 'download', :id => /\d+/,
319 :conditions => {:method => :get}
321 :conditions => {:method => :get}
320 map.resources :attachments, :only => [:show, :destroy]
322 map.resources :attachments, :only => [:show, :destroy]
321
323
322 map.resources :groups, :member => {:autocomplete_for_user => :get}
324 map.resources :groups, :member => {:autocomplete_for_user => :get}
323 map.group_users 'groups/:id/users', :controller => 'groups',
325 map.group_users 'groups/:id/users', :controller => 'groups',
324 :action => 'add_users', :id => /\d+/,
326 :action => 'add_users', :id => /\d+/,
325 :conditions => {:method => :post}
327 :conditions => {:method => :post}
326 map.group_user 'groups/:id/users/:user_id', :controller => 'groups',
328 map.group_user 'groups/:id/users/:user_id', :controller => 'groups',
327 :action => 'remove_user', :id => /\d+/,
329 :action => 'remove_user', :id => /\d+/,
328 :conditions => {:method => :delete}
330 :conditions => {:method => :delete}
329 map.connect 'groups/destroy_membership/:id', :controller => 'groups',
331 map.connect 'groups/destroy_membership/:id', :controller => 'groups',
330 :action => 'destroy_membership', :id => /\d+/,
332 :action => 'destroy_membership', :id => /\d+/,
331 :conditions => {:method => :post}
333 :conditions => {:method => :post}
332 map.connect 'groups/edit_membership/:id', :controller => 'groups',
334 map.connect 'groups/edit_membership/:id', :controller => 'groups',
333 :action => 'edit_membership', :id => /\d+/,
335 :action => 'edit_membership', :id => /\d+/,
334 :conditions => {:method => :post}
336 :conditions => {:method => :post}
335
337
336 map.resources :trackers, :except => :show
338 map.resources :trackers, :except => :show
337 map.resources :issue_statuses, :except => :show, :collection => {:update_issue_done_ratio => :post}
339 map.resources :issue_statuses, :except => :show, :collection => {:update_issue_done_ratio => :post}
338 map.resources :custom_fields, :except => :show
340 map.resources :custom_fields, :except => :show
339 map.resources :roles, :except => :show, :collection => {:permissions => [:get, :post]}
341 map.resources :roles, :except => :show, :collection => {:permissions => [:get, :post]}
340 map.resources :enumerations, :except => :show
342 map.resources :enumerations, :except => :show
341
343
342 map.connect 'projects/:id/search', :controller => 'search', :action => 'index', :conditions => {:method => :get}
344 map.connect 'projects/:id/search', :controller => 'search', :action => 'index', :conditions => {:method => :get}
343 map.connect 'search', :controller => 'search', :action => 'index', :conditions => {:method => :get}
345 map.connect 'search', :controller => 'search', :action => 'index', :conditions => {:method => :get}
344
346
345 map.connect 'mail_handler', :controller => 'mail_handler',
347 map.connect 'mail_handler', :controller => 'mail_handler',
346 :action => 'index', :conditions => {:method => :post}
348 :action => 'index', :conditions => {:method => :post}
347
349
348 map.connect 'admin', :controller => 'admin', :action => 'index',
350 map.connect 'admin', :controller => 'admin', :action => 'index',
349 :conditions => {:method => :get}
351 :conditions => {:method => :get}
350 map.connect 'admin/projects', :controller => 'admin', :action => 'projects',
352 map.connect 'admin/projects', :controller => 'admin', :action => 'projects',
351 :conditions => {:method => :get}
353 :conditions => {:method => :get}
352 map.connect 'admin/plugins', :controller => 'admin', :action => 'plugins',
354 map.connect 'admin/plugins', :controller => 'admin', :action => 'plugins',
353 :conditions => {:method => :get}
355 :conditions => {:method => :get}
354 map.connect 'admin/info', :controller => 'admin', :action => 'info',
356 map.connect 'admin/info', :controller => 'admin', :action => 'info',
355 :conditions => {:method => :get}
357 :conditions => {:method => :get}
356 map.connect 'admin/test_email', :controller => 'admin', :action => 'test_email',
358 map.connect 'admin/test_email', :controller => 'admin', :action => 'test_email',
357 :conditions => {:method => :get}
359 :conditions => {:method => :get}
358 map.connect 'admin/default_configuration', :controller => 'admin',
360 map.connect 'admin/default_configuration', :controller => 'admin',
359 :action => 'default_configuration', :conditions => {:method => :post}
361 :action => 'default_configuration', :conditions => {:method => :post}
360
362
361 map.resources :auth_sources, :member => {:test_connection => :get}
363 map.resources :auth_sources, :member => {:test_connection => :get}
362
364
363 map.connect 'workflows', :controller => 'workflows',
365 map.connect 'workflows', :controller => 'workflows',
364 :action => 'index', :conditions => {:method => :get}
366 :action => 'index', :conditions => {:method => :get}
365 map.connect 'workflows/edit', :controller => 'workflows',
367 map.connect 'workflows/edit', :controller => 'workflows',
366 :action => 'edit', :conditions => {:method => [:get, :post]}
368 :action => 'edit', :conditions => {:method => [:get, :post]}
367 map.connect 'workflows/copy', :controller => 'workflows',
369 map.connect 'workflows/copy', :controller => 'workflows',
368 :action => 'copy', :conditions => {:method => [:get, :post]}
370 :action => 'copy', :conditions => {:method => [:get, :post]}
369
371
370 map.connect 'settings', :controller => 'settings',
372 map.connect 'settings', :controller => 'settings',
371 :action => 'index', :conditions => {:method => :get}
373 :action => 'index', :conditions => {:method => :get}
372 map.connect 'settings/edit', :controller => 'settings',
374 map.connect 'settings/edit', :controller => 'settings',
373 :action => 'edit', :conditions => {:method => [:get, :post]}
375 :action => 'edit', :conditions => {:method => [:get, :post]}
374 map.connect 'settings/plugin/:id', :controller => 'settings',
376 map.connect 'settings/plugin/:id', :controller => 'settings',
375 :action => 'plugin', :conditions => {:method => [:get, :post]}
377 :action => 'plugin', :conditions => {:method => [:get, :post]}
376
378
377 map.with_options :controller => 'sys' do |sys|
379 map.with_options :controller => 'sys' do |sys|
378 sys.connect 'sys/projects.:format',
380 sys.connect 'sys/projects.:format',
379 :action => 'projects',
381 :action => 'projects',
380 :conditions => {:method => :get}
382 :conditions => {:method => :get}
381 sys.connect 'sys/projects/:id/repository.:format',
383 sys.connect 'sys/projects/:id/repository.:format',
382 :action => 'create_project_repository',
384 :action => 'create_project_repository',
383 :conditions => {:method => :post}
385 :conditions => {:method => :post}
384 sys.connect 'sys/fetch_changesets',
386 sys.connect 'sys/fetch_changesets',
385 :action => 'fetch_changesets',
387 :action => 'fetch_changesets',
386 :conditions => {:method => :get}
388 :conditions => {:method => :get}
387 end
389 end
388
390
389 map.connect 'uploads.:format', :controller => 'attachments', :action => 'upload', :conditions => {:method => :post}
391 map.connect 'uploads.:format', :controller => 'attachments', :action => 'upload', :conditions => {:method => :post}
390
392
391 map.connect 'robots.txt', :controller => 'welcome',
393 map.connect 'robots.txt', :controller => 'welcome',
392 :action => 'robots', :conditions => {:method => :get}
394 :action => 'robots', :conditions => {:method => :get}
393
395
394 # Used for OpenID
396 # Used for OpenID
395 map.root :controller => 'account', :action => 'login'
397 map.root :controller => 'account', :action => 'login'
396 end
398 end
@@ -1,204 +1,206
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
18
19 # DO NOT MODIFY THIS FILE !!!
19 # DO NOT MODIFY THIS FILE !!!
20 # Settings can be defined through the application in Admin -> Settings
20 # Settings can be defined through the application in Admin -> Settings
21
21
22 app_title:
22 app_title:
23 default: Redmine
23 default: Redmine
24 app_subtitle:
24 app_subtitle:
25 default: Project management
25 default: Project management
26 welcome_text:
26 welcome_text:
27 default:
27 default:
28 login_required:
28 login_required:
29 default: 0
29 default: 0
30 self_registration:
30 self_registration:
31 default: '2'
31 default: '2'
32 lost_password:
32 lost_password:
33 default: 1
33 default: 1
34 unsubscribe:
35 default: 1
34 password_min_length:
36 password_min_length:
35 format: int
37 format: int
36 default: 4
38 default: 4
37 attachment_max_size:
39 attachment_max_size:
38 format: int
40 format: int
39 default: 5120
41 default: 5120
40 issues_export_limit:
42 issues_export_limit:
41 format: int
43 format: int
42 default: 500
44 default: 500
43 activity_days_default:
45 activity_days_default:
44 format: int
46 format: int
45 default: 30
47 default: 30
46 per_page_options:
48 per_page_options:
47 default: '25,50,100'
49 default: '25,50,100'
48 mail_from:
50 mail_from:
49 default: redmine@example.net
51 default: redmine@example.net
50 bcc_recipients:
52 bcc_recipients:
51 default: 1
53 default: 1
52 plain_text_mail:
54 plain_text_mail:
53 default: 0
55 default: 0
54 text_formatting:
56 text_formatting:
55 default: textile
57 default: textile
56 cache_formatted_text:
58 cache_formatted_text:
57 default: 0
59 default: 0
58 wiki_compression:
60 wiki_compression:
59 default: ""
61 default: ""
60 default_language:
62 default_language:
61 default: en
63 default: en
62 host_name:
64 host_name:
63 default: localhost:3000
65 default: localhost:3000
64 protocol:
66 protocol:
65 default: http
67 default: http
66 feeds_limit:
68 feeds_limit:
67 format: int
69 format: int
68 default: 15
70 default: 15
69 gantt_items_limit:
71 gantt_items_limit:
70 format: int
72 format: int
71 default: 500
73 default: 500
72 # Maximum size of files that can be displayed
74 # Maximum size of files that can be displayed
73 # inline through the file viewer (in KB)
75 # inline through the file viewer (in KB)
74 file_max_size_displayed:
76 file_max_size_displayed:
75 format: int
77 format: int
76 default: 512
78 default: 512
77 diff_max_lines_displayed:
79 diff_max_lines_displayed:
78 format: int
80 format: int
79 default: 1500
81 default: 1500
80 enabled_scm:
82 enabled_scm:
81 serialized: true
83 serialized: true
82 default:
84 default:
83 - Subversion
85 - Subversion
84 - Darcs
86 - Darcs
85 - Mercurial
87 - Mercurial
86 - Cvs
88 - Cvs
87 - Bazaar
89 - Bazaar
88 - Git
90 - Git
89 autofetch_changesets:
91 autofetch_changesets:
90 default: 1
92 default: 1
91 sys_api_enabled:
93 sys_api_enabled:
92 default: 0
94 default: 0
93 sys_api_key:
95 sys_api_key:
94 default: ''
96 default: ''
95 commit_cross_project_ref:
97 commit_cross_project_ref:
96 default: 0
98 default: 0
97 commit_ref_keywords:
99 commit_ref_keywords:
98 default: 'refs,references,IssueID'
100 default: 'refs,references,IssueID'
99 commit_fix_keywords:
101 commit_fix_keywords:
100 default: 'fixes,closes'
102 default: 'fixes,closes'
101 commit_fix_status_id:
103 commit_fix_status_id:
102 format: int
104 format: int
103 default: 0
105 default: 0
104 commit_fix_done_ratio:
106 commit_fix_done_ratio:
105 default: 100
107 default: 100
106 commit_logtime_enabled:
108 commit_logtime_enabled:
107 default: 0
109 default: 0
108 commit_logtime_activity_id:
110 commit_logtime_activity_id:
109 format: int
111 format: int
110 default: 0
112 default: 0
111 # autologin duration in days
113 # autologin duration in days
112 # 0 means autologin is disabled
114 # 0 means autologin is disabled
113 autologin:
115 autologin:
114 format: int
116 format: int
115 default: 0
117 default: 0
116 # date format
118 # date format
117 date_format:
119 date_format:
118 default: ''
120 default: ''
119 time_format:
121 time_format:
120 default: ''
122 default: ''
121 user_format:
123 user_format:
122 default: :firstname_lastname
124 default: :firstname_lastname
123 format: symbol
125 format: symbol
124 cross_project_issue_relations:
126 cross_project_issue_relations:
125 default: 0
127 default: 0
126 issue_group_assignment:
128 issue_group_assignment:
127 default: 0
129 default: 0
128 default_issue_start_date_to_creation_date:
130 default_issue_start_date_to_creation_date:
129 default: 1
131 default: 1
130 notified_events:
132 notified_events:
131 serialized: true
133 serialized: true
132 default:
134 default:
133 - issue_added
135 - issue_added
134 - issue_updated
136 - issue_updated
135 mail_handler_body_delimiters:
137 mail_handler_body_delimiters:
136 default: ''
138 default: ''
137 mail_handler_api_enabled:
139 mail_handler_api_enabled:
138 default: 0
140 default: 0
139 mail_handler_api_key:
141 mail_handler_api_key:
140 default:
142 default:
141 issue_list_default_columns:
143 issue_list_default_columns:
142 serialized: true
144 serialized: true
143 default:
145 default:
144 - tracker
146 - tracker
145 - status
147 - status
146 - priority
148 - priority
147 - subject
149 - subject
148 - assigned_to
150 - assigned_to
149 - updated_on
151 - updated_on
150 display_subprojects_issues:
152 display_subprojects_issues:
151 default: 1
153 default: 1
152 issue_done_ratio:
154 issue_done_ratio:
153 default: 'issue_field'
155 default: 'issue_field'
154 default_projects_public:
156 default_projects_public:
155 default: 1
157 default: 1
156 default_projects_modules:
158 default_projects_modules:
157 serialized: true
159 serialized: true
158 default:
160 default:
159 - issue_tracking
161 - issue_tracking
160 - time_tracking
162 - time_tracking
161 - news
163 - news
162 - documents
164 - documents
163 - files
165 - files
164 - wiki
166 - wiki
165 - repository
167 - repository
166 - boards
168 - boards
167 - calendar
169 - calendar
168 - gantt
170 - gantt
169 # Role given to a non-admin user who creates a project
171 # Role given to a non-admin user who creates a project
170 new_project_user_role_id:
172 new_project_user_role_id:
171 format: int
173 format: int
172 default: ''
174 default: ''
173 sequential_project_identifiers:
175 sequential_project_identifiers:
174 default: 0
176 default: 0
175 # encodings used to convert repository files content to UTF-8
177 # encodings used to convert repository files content to UTF-8
176 # multiple values accepted, comma separated
178 # multiple values accepted, comma separated
177 repositories_encodings:
179 repositories_encodings:
178 default: ''
180 default: ''
179 # encoding used to convert commit logs to UTF-8
181 # encoding used to convert commit logs to UTF-8
180 commit_logs_encoding:
182 commit_logs_encoding:
181 default: 'UTF-8'
183 default: 'UTF-8'
182 repository_log_display_limit:
184 repository_log_display_limit:
183 format: int
185 format: int
184 default: 100
186 default: 100
185 ui_theme:
187 ui_theme:
186 default: ''
188 default: ''
187 emails_footer:
189 emails_footer:
188 default: |-
190 default: |-
189 You have received this notification because you have either subscribed to it, or are involved in it.
191 You have received this notification because you have either subscribed to it, or are involved in it.
190 To change your notification preferences, please click here: http://hostname/my/account
192 To change your notification preferences, please click here: http://hostname/my/account
191 gravatar_enabled:
193 gravatar_enabled:
192 default: 0
194 default: 0
193 openid:
195 openid:
194 default: 0
196 default: 0
195 gravatar_default:
197 gravatar_default:
196 default: ''
198 default: ''
197 start_of_week:
199 start_of_week:
198 default: ''
200 default: ''
199 rest_api_enabled:
201 rest_api_enabled:
200 default: 0
202 default: 0
201 default_notification_option:
203 default_notification_option:
202 default: 'only_my_events'
204 default: 'only_my_events'
203 emails_header:
205 emails_header:
204 default: ''
206 default: ''
@@ -1,177 +1,216
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19 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,
67 post :account,
68 :user => {
68 :user => {
69 :firstname => "Joe",
69 :firstname => "Joe",
70 :login => "root",
70 :login => "root",
71 :admin => 1,
71 :admin => 1,
72 :group_ids => ['10'],
72 :group_ids => ['10'],
73 :custom_field_values => {"4" => "0100562500"}
73 :custom_field_values => {"4" => "0100562500"}
74 }
74 }
75
75
76 assert_redirected_to '/my/account'
76 assert_redirected_to '/my/account'
77 user = User.find(2)
77 user = User.find(2)
78 assert_equal user, assigns(:user)
78 assert_equal user, assigns(:user)
79 assert_equal "Joe", user.firstname
79 assert_equal "Joe", user.firstname
80 assert_equal "jsmith", user.login
80 assert_equal "jsmith", user.login
81 assert_equal "0100562500", user.custom_value_for(4).value
81 assert_equal "0100562500", user.custom_value_for(4).value
82 # ignored
82 # ignored
83 assert !user.admin?
83 assert !user.admin?
84 assert user.groups.empty?
84 assert user.groups.empty?
85 end
85 end
86
86
87 def test_my_account_should_show_destroy_link
88 get :account
89 assert_select 'a[href=/my/account/destroy]'
90 end
91
92 def test_get_destroy_should_display_the_destroy_confirmation
93 get :destroy
94 assert_response :success
95 assert_template 'destroy'
96 assert_select 'form[action=/my/account/destroy]' do
97 assert_select 'input[name=confirm]'
98 end
99 end
100
101 def test_post_destroy_without_confirmation_should_not_destroy_account
102 assert_no_difference 'User.count' do
103 post :destroy
104 end
105 assert_response :success
106 assert_template 'destroy'
107 end
108
109 def test_post_destroy_without_confirmation_should_destroy_account
110 assert_difference 'User.count', -1 do
111 post :destroy, :confirm => '1'
112 end
113 assert_redirected_to '/'
114 assert_match /deleted/i, flash[:notice]
115 end
116
117 def test_post_destroy_with_unsubscribe_not_allowed_should_not_destroy_account
118 User.any_instance.stubs(:own_account_deletable?).returns(false)
119
120 assert_no_difference 'User.count' do
121 post :destroy, :confirm => '1'
122 end
123 assert_redirected_to '/my/account'
124 end
125
87 def test_change_password
126 def test_change_password
88 get :password
127 get :password
89 assert_response :success
128 assert_response :success
90 assert_template 'password'
129 assert_template 'password'
91
130
92 # non matching password confirmation
131 # non matching password confirmation
93 post :password, :password => 'jsmith',
132 post :password, :password => 'jsmith',
94 :new_password => 'hello',
133 :new_password => 'hello',
95 :new_password_confirmation => 'hello2'
134 :new_password_confirmation => 'hello2'
96 assert_response :success
135 assert_response :success
97 assert_template 'password'
136 assert_template 'password'
98 assert_error_tag :content => /Password doesn't match confirmation/
137 assert_error_tag :content => /Password doesn't match confirmation/
99
138
100 # wrong password
139 # wrong password
101 post :password, :password => 'wrongpassword',
140 post :password, :password => 'wrongpassword',
102 :new_password => 'hello',
141 :new_password => 'hello',
103 :new_password_confirmation => 'hello'
142 :new_password_confirmation => 'hello'
104 assert_response :success
143 assert_response :success
105 assert_template 'password'
144 assert_template 'password'
106 assert_equal 'Wrong password', flash[:error]
145 assert_equal 'Wrong password', flash[:error]
107
146
108 # good password
147 # good password
109 post :password, :password => 'jsmith',
148 post :password, :password => 'jsmith',
110 :new_password => 'hello',
149 :new_password => 'hello',
111 :new_password_confirmation => 'hello'
150 :new_password_confirmation => 'hello'
112 assert_redirected_to '/my/account'
151 assert_redirected_to '/my/account'
113 assert User.try_to_login('jsmith', 'hello')
152 assert User.try_to_login('jsmith', 'hello')
114 end
153 end
115
154
116 def test_page_layout
155 def test_page_layout
117 get :page_layout
156 get :page_layout
118 assert_response :success
157 assert_response :success
119 assert_template 'page_layout'
158 assert_template 'page_layout'
120 end
159 end
121
160
122 def test_add_block
161 def test_add_block
123 xhr :post, :add_block, :block => 'issuesreportedbyme'
162 xhr :post, :add_block, :block => 'issuesreportedbyme'
124 assert_response :success
163 assert_response :success
125 assert User.find(2).pref[:my_page_layout]['top'].include?('issuesreportedbyme')
164 assert User.find(2).pref[:my_page_layout]['top'].include?('issuesreportedbyme')
126 end
165 end
127
166
128 def test_remove_block
167 def test_remove_block
129 xhr :post, :remove_block, :block => 'issuesassignedtome'
168 xhr :post, :remove_block, :block => 'issuesassignedtome'
130 assert_response :success
169 assert_response :success
131 assert !User.find(2).pref[:my_page_layout].values.flatten.include?('issuesassignedtome')
170 assert !User.find(2).pref[:my_page_layout].values.flatten.include?('issuesassignedtome')
132 end
171 end
133
172
134 def test_order_blocks
173 def test_order_blocks
135 xhr :post, :order_blocks, :group => 'left', 'list-left' => ['documents', 'calendar', 'latestnews']
174 xhr :post, :order_blocks, :group => 'left', 'list-left' => ['documents', 'calendar', 'latestnews']
136 assert_response :success
175 assert_response :success
137 assert_equal ['documents', 'calendar', 'latestnews'], User.find(2).pref[:my_page_layout]['left']
176 assert_equal ['documents', 'calendar', 'latestnews'], User.find(2).pref[:my_page_layout]['left']
138 end
177 end
139
178
140 def test_reset_rss_key_with_existing_key
179 def test_reset_rss_key_with_existing_key
141 @previous_token_value = User.find(2).rss_key # Will generate one if it's missing
180 @previous_token_value = User.find(2).rss_key # Will generate one if it's missing
142 post :reset_rss_key
181 post :reset_rss_key
143
182
144 assert_not_equal @previous_token_value, User.find(2).rss_key
183 assert_not_equal @previous_token_value, User.find(2).rss_key
145 assert User.find(2).rss_token
184 assert User.find(2).rss_token
146 assert_match /reset/, flash[:notice]
185 assert_match /reset/, flash[:notice]
147 assert_redirected_to '/my/account'
186 assert_redirected_to '/my/account'
148 end
187 end
149
188
150 def test_reset_rss_key_without_existing_key
189 def test_reset_rss_key_without_existing_key
151 assert_nil User.find(2).rss_token
190 assert_nil User.find(2).rss_token
152 post :reset_rss_key
191 post :reset_rss_key
153
192
154 assert User.find(2).rss_token
193 assert User.find(2).rss_token
155 assert_match /reset/, flash[:notice]
194 assert_match /reset/, flash[:notice]
156 assert_redirected_to '/my/account'
195 assert_redirected_to '/my/account'
157 end
196 end
158
197
159 def test_reset_api_key_with_existing_key
198 def test_reset_api_key_with_existing_key
160 @previous_token_value = User.find(2).api_key # Will generate one if it's missing
199 @previous_token_value = User.find(2).api_key # Will generate one if it's missing
161 post :reset_api_key
200 post :reset_api_key
162
201
163 assert_not_equal @previous_token_value, User.find(2).api_key
202 assert_not_equal @previous_token_value, User.find(2).api_key
164 assert User.find(2).api_token
203 assert User.find(2).api_token
165 assert_match /reset/, flash[:notice]
204 assert_match /reset/, flash[:notice]
166 assert_redirected_to '/my/account'
205 assert_redirected_to '/my/account'
167 end
206 end
168
207
169 def test_reset_api_key_without_existing_key
208 def test_reset_api_key_without_existing_key
170 assert_nil User.find(2).api_token
209 assert_nil User.find(2).api_token
171 post :reset_api_key
210 post :reset_api_key
172
211
173 assert User.find(2).api_token
212 assert User.find(2).api_token
174 assert_match /reset/, flash[:notice]
213 assert_match /reset/, flash[:notice]
175 assert_redirected_to '/my/account'
214 assert_redirected_to '/my/account'
176 end
215 end
177 end
216 end
@@ -1,67 +1,73
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.expand_path('../../../test_helper', __FILE__)
18 require File.expand_path('../../../test_helper', __FILE__)
19
19
20 class RoutingMyTest < ActionController::IntegrationTest
20 class RoutingMyTest < ActionController::IntegrationTest
21 def test_my
21 def test_my
22 ["get", "post"].each do |method|
22 ["get", "post"].each do |method|
23 assert_routing(
23 assert_routing(
24 { :method => method, :path => "/my/account" },
24 { :method => method, :path => "/my/account" },
25 { :controller => 'my', :action => 'account' }
25 { :controller => 'my', :action => 'account' }
26 )
26 )
27 end
27 end
28 ["get", "post"].each do |method|
29 assert_routing(
30 { :method => method, :path => "/my/account/destroy" },
31 { :controller => 'my', :action => 'destroy' }
32 )
33 end
28 assert_routing(
34 assert_routing(
29 { :method => 'get', :path => "/my/page" },
35 { :method => 'get', :path => "/my/page" },
30 { :controller => 'my', :action => 'page' }
36 { :controller => 'my', :action => 'page' }
31 )
37 )
32 assert_routing(
38 assert_routing(
33 { :method => 'get', :path => "/my" },
39 { :method => 'get', :path => "/my" },
34 { :controller => 'my', :action => 'index' }
40 { :controller => 'my', :action => 'index' }
35 )
41 )
36 assert_routing(
42 assert_routing(
37 { :method => 'post', :path => "/my/reset_rss_key" },
43 { :method => 'post', :path => "/my/reset_rss_key" },
38 { :controller => 'my', :action => 'reset_rss_key' }
44 { :controller => 'my', :action => 'reset_rss_key' }
39 )
45 )
40 assert_routing(
46 assert_routing(
41 { :method => 'post', :path => "/my/reset_api_key" },
47 { :method => 'post', :path => "/my/reset_api_key" },
42 { :controller => 'my', :action => 'reset_api_key' }
48 { :controller => 'my', :action => 'reset_api_key' }
43 )
49 )
44 ["get", "post"].each do |method|
50 ["get", "post"].each do |method|
45 assert_routing(
51 assert_routing(
46 { :method => method, :path => "/my/password" },
52 { :method => method, :path => "/my/password" },
47 { :controller => 'my', :action => 'password' }
53 { :controller => 'my', :action => 'password' }
48 )
54 )
49 end
55 end
50 assert_routing(
56 assert_routing(
51 { :method => 'get', :path => "/my/page_layout" },
57 { :method => 'get', :path => "/my/page_layout" },
52 { :controller => 'my', :action => 'page_layout' }
58 { :controller => 'my', :action => 'page_layout' }
53 )
59 )
54 assert_routing(
60 assert_routing(
55 { :method => 'post', :path => "/my/add_block" },
61 { :method => 'post', :path => "/my/add_block" },
56 { :controller => 'my', :action => 'add_block' }
62 { :controller => 'my', :action => 'add_block' }
57 )
63 )
58 assert_routing(
64 assert_routing(
59 { :method => 'post', :path => "/my/remove_block" },
65 { :method => 'post', :path => "/my/remove_block" },
60 { :controller => 'my', :action => 'remove_block' }
66 { :controller => 'my', :action => 'remove_block' }
61 )
67 )
62 assert_routing(
68 assert_routing(
63 { :method => 'post', :path => "/my/order_blocks" },
69 { :method => 'post', :path => "/my/order_blocks" },
64 { :controller => 'my', :action => 'order_blocks' }
70 { :controller => 'my', :action => 'order_blocks' }
65 )
71 )
66 end
72 end
67 end
73 end
@@ -1,965 +1,992
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19
19
20 class UserTest < ActiveSupport::TestCase
20 class UserTest < ActiveSupport::TestCase
21 fixtures :users, :members, :projects, :roles, :member_roles, :auth_sources,
21 fixtures :users, :members, :projects, :roles, :member_roles, :auth_sources,
22 :trackers, :issue_statuses,
22 :trackers, :issue_statuses,
23 :projects_trackers,
23 :projects_trackers,
24 :watchers,
24 :watchers,
25 :issue_categories, :enumerations, :issues,
25 :issue_categories, :enumerations, :issues,
26 :journals, :journal_details,
26 :journals, :journal_details,
27 :groups_users,
27 :groups_users,
28 :enabled_modules,
28 :enabled_modules,
29 :workflows
29 :workflows
30
30
31 def setup
31 def setup
32 @admin = User.find(1)
32 @admin = User.find(1)
33 @jsmith = User.find(2)
33 @jsmith = User.find(2)
34 @dlopper = User.find(3)
34 @dlopper = User.find(3)
35 end
35 end
36
36
37 test 'object_daddy creation' do
37 test 'object_daddy creation' do
38 User.generate_with_protected!(:firstname => 'Testing connection')
38 User.generate_with_protected!(:firstname => 'Testing connection')
39 User.generate_with_protected!(:firstname => 'Testing connection')
39 User.generate_with_protected!(:firstname => 'Testing connection')
40 assert_equal 2, User.count(:all, :conditions => {:firstname => 'Testing connection'})
40 assert_equal 2, User.count(:all, :conditions => {:firstname => 'Testing connection'})
41 end
41 end
42
42
43 def test_truth
43 def test_truth
44 assert_kind_of User, @jsmith
44 assert_kind_of User, @jsmith
45 end
45 end
46
46
47 def test_mail_should_be_stripped
47 def test_mail_should_be_stripped
48 u = User.new
48 u = User.new
49 u.mail = " foo@bar.com "
49 u.mail = " foo@bar.com "
50 assert_equal "foo@bar.com", u.mail
50 assert_equal "foo@bar.com", u.mail
51 end
51 end
52
52
53 def test_mail_validation
53 def test_mail_validation
54 u = User.new
54 u = User.new
55 u.mail = ''
55 u.mail = ''
56 assert !u.valid?
56 assert !u.valid?
57 assert_include I18n.translate('activerecord.errors.messages.blank'), u.errors[:mail]
57 assert_include I18n.translate('activerecord.errors.messages.blank'), u.errors[:mail]
58 end
58 end
59
59
60 def test_login_length_validation
60 def test_login_length_validation
61 user = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
61 user = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
62 user.login = "x" * (User::LOGIN_LENGTH_LIMIT+1)
62 user.login = "x" * (User::LOGIN_LENGTH_LIMIT+1)
63 assert !user.valid?
63 assert !user.valid?
64
64
65 user.login = "x" * (User::LOGIN_LENGTH_LIMIT)
65 user.login = "x" * (User::LOGIN_LENGTH_LIMIT)
66 assert user.valid?
66 assert user.valid?
67 assert user.save
67 assert user.save
68 end
68 end
69
69
70 def test_create
70 def test_create
71 user = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
71 user = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
72
72
73 user.login = "jsmith"
73 user.login = "jsmith"
74 user.password, user.password_confirmation = "password", "password"
74 user.password, user.password_confirmation = "password", "password"
75 # login uniqueness
75 # login uniqueness
76 assert !user.save
76 assert !user.save
77 assert_equal 1, user.errors.count
77 assert_equal 1, user.errors.count
78
78
79 user.login = "newuser"
79 user.login = "newuser"
80 user.password, user.password_confirmation = "passwd", "password"
80 user.password, user.password_confirmation = "passwd", "password"
81 # password confirmation
81 # password confirmation
82 assert !user.save
82 assert !user.save
83 assert_equal 1, user.errors.count
83 assert_equal 1, user.errors.count
84
84
85 user.password, user.password_confirmation = "password", "password"
85 user.password, user.password_confirmation = "password", "password"
86 assert user.save
86 assert user.save
87 end
87 end
88
88
89 context "User#before_create" do
89 context "User#before_create" do
90 should "set the mail_notification to the default Setting" do
90 should "set the mail_notification to the default Setting" do
91 @user1 = User.generate_with_protected!
91 @user1 = User.generate_with_protected!
92 assert_equal 'only_my_events', @user1.mail_notification
92 assert_equal 'only_my_events', @user1.mail_notification
93
93
94 with_settings :default_notification_option => 'all' do
94 with_settings :default_notification_option => 'all' do
95 @user2 = User.generate_with_protected!
95 @user2 = User.generate_with_protected!
96 assert_equal 'all', @user2.mail_notification
96 assert_equal 'all', @user2.mail_notification
97 end
97 end
98 end
98 end
99 end
99 end
100
100
101 context "User.login" do
101 context "User.login" do
102 should "be case-insensitive." do
102 should "be case-insensitive." do
103 u = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
103 u = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
104 u.login = 'newuser'
104 u.login = 'newuser'
105 u.password, u.password_confirmation = "password", "password"
105 u.password, u.password_confirmation = "password", "password"
106 assert u.save
106 assert u.save
107
107
108 u = User.new(:firstname => "Similar", :lastname => "User", :mail => "similaruser@somenet.foo")
108 u = User.new(:firstname => "Similar", :lastname => "User", :mail => "similaruser@somenet.foo")
109 u.login = 'NewUser'
109 u.login = 'NewUser'
110 u.password, u.password_confirmation = "password", "password"
110 u.password, u.password_confirmation = "password", "password"
111 assert !u.save
111 assert !u.save
112 assert_include I18n.translate('activerecord.errors.messages.taken'), u.errors[:login]
112 assert_include I18n.translate('activerecord.errors.messages.taken'), u.errors[:login]
113 end
113 end
114 end
114 end
115
115
116 def test_mail_uniqueness_should_not_be_case_sensitive
116 def test_mail_uniqueness_should_not_be_case_sensitive
117 u = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
117 u = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
118 u.login = 'newuser1'
118 u.login = 'newuser1'
119 u.password, u.password_confirmation = "password", "password"
119 u.password, u.password_confirmation = "password", "password"
120 assert u.save
120 assert u.save
121
121
122 u = User.new(:firstname => "new", :lastname => "user", :mail => "newUser@Somenet.foo")
122 u = User.new(:firstname => "new", :lastname => "user", :mail => "newUser@Somenet.foo")
123 u.login = 'newuser2'
123 u.login = 'newuser2'
124 u.password, u.password_confirmation = "password", "password"
124 u.password, u.password_confirmation = "password", "password"
125 assert !u.save
125 assert !u.save
126 assert_include I18n.translate('activerecord.errors.messages.taken'), u.errors[:mail]
126 assert_include I18n.translate('activerecord.errors.messages.taken'), u.errors[:mail]
127 end
127 end
128
128
129 def test_update
129 def test_update
130 assert_equal "admin", @admin.login
130 assert_equal "admin", @admin.login
131 @admin.login = "john"
131 @admin.login = "john"
132 assert @admin.save, @admin.errors.full_messages.join("; ")
132 assert @admin.save, @admin.errors.full_messages.join("; ")
133 @admin.reload
133 @admin.reload
134 assert_equal "john", @admin.login
134 assert_equal "john", @admin.login
135 end
135 end
136
136
137 def test_destroy_should_delete_members_and_roles
137 def test_destroy_should_delete_members_and_roles
138 members = Member.find_all_by_user_id(2)
138 members = Member.find_all_by_user_id(2)
139 ms = members.size
139 ms = members.size
140 rs = members.collect(&:roles).flatten.size
140 rs = members.collect(&:roles).flatten.size
141
141
142 assert_difference 'Member.count', - ms do
142 assert_difference 'Member.count', - ms do
143 assert_difference 'MemberRole.count', - rs do
143 assert_difference 'MemberRole.count', - rs do
144 User.find(2).destroy
144 User.find(2).destroy
145 end
145 end
146 end
146 end
147
147
148 assert_nil User.find_by_id(2)
148 assert_nil User.find_by_id(2)
149 assert Member.find_all_by_user_id(2).empty?
149 assert Member.find_all_by_user_id(2).empty?
150 end
150 end
151
151
152 def test_destroy_should_update_attachments
152 def test_destroy_should_update_attachments
153 attachment = Attachment.create!(:container => Project.find(1),
153 attachment = Attachment.create!(:container => Project.find(1),
154 :file => uploaded_test_file("testfile.txt", "text/plain"),
154 :file => uploaded_test_file("testfile.txt", "text/plain"),
155 :author_id => 2)
155 :author_id => 2)
156
156
157 User.find(2).destroy
157 User.find(2).destroy
158 assert_nil User.find_by_id(2)
158 assert_nil User.find_by_id(2)
159 assert_equal User.anonymous, attachment.reload.author
159 assert_equal User.anonymous, attachment.reload.author
160 end
160 end
161
161
162 def test_destroy_should_update_comments
162 def test_destroy_should_update_comments
163 comment = Comment.create!(
163 comment = Comment.create!(
164 :commented => News.create!(:project_id => 1, :author_id => 1, :title => 'foo', :description => 'foo'),
164 :commented => News.create!(:project_id => 1, :author_id => 1, :title => 'foo', :description => 'foo'),
165 :author => User.find(2),
165 :author => User.find(2),
166 :comments => 'foo'
166 :comments => 'foo'
167 )
167 )
168
168
169 User.find(2).destroy
169 User.find(2).destroy
170 assert_nil User.find_by_id(2)
170 assert_nil User.find_by_id(2)
171 assert_equal User.anonymous, comment.reload.author
171 assert_equal User.anonymous, comment.reload.author
172 end
172 end
173
173
174 def test_destroy_should_update_issues
174 def test_destroy_should_update_issues
175 issue = Issue.create!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'foo')
175 issue = Issue.create!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'foo')
176
176
177 User.find(2).destroy
177 User.find(2).destroy
178 assert_nil User.find_by_id(2)
178 assert_nil User.find_by_id(2)
179 assert_equal User.anonymous, issue.reload.author
179 assert_equal User.anonymous, issue.reload.author
180 end
180 end
181
181
182 def test_destroy_should_unassign_issues
182 def test_destroy_should_unassign_issues
183 issue = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'foo', :assigned_to_id => 2)
183 issue = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'foo', :assigned_to_id => 2)
184
184
185 User.find(2).destroy
185 User.find(2).destroy
186 assert_nil User.find_by_id(2)
186 assert_nil User.find_by_id(2)
187 assert_nil issue.reload.assigned_to
187 assert_nil issue.reload.assigned_to
188 end
188 end
189
189
190 def test_destroy_should_update_journals
190 def test_destroy_should_update_journals
191 issue = Issue.create!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'foo')
191 issue = Issue.create!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'foo')
192 issue.init_journal(User.find(2), "update")
192 issue.init_journal(User.find(2), "update")
193 issue.save!
193 issue.save!
194
194
195 User.find(2).destroy
195 User.find(2).destroy
196 assert_nil User.find_by_id(2)
196 assert_nil User.find_by_id(2)
197 assert_equal User.anonymous, issue.journals.first.reload.user
197 assert_equal User.anonymous, issue.journals.first.reload.user
198 end
198 end
199
199
200 def test_destroy_should_update_journal_details_old_value
200 def test_destroy_should_update_journal_details_old_value
201 issue = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'foo', :assigned_to_id => 2)
201 issue = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'foo', :assigned_to_id => 2)
202 issue.init_journal(User.find(1), "update")
202 issue.init_journal(User.find(1), "update")
203 issue.assigned_to_id = nil
203 issue.assigned_to_id = nil
204 assert_difference 'JournalDetail.count' do
204 assert_difference 'JournalDetail.count' do
205 issue.save!
205 issue.save!
206 end
206 end
207 journal_detail = JournalDetail.first(:order => 'id DESC')
207 journal_detail = JournalDetail.first(:order => 'id DESC')
208 assert_equal '2', journal_detail.old_value
208 assert_equal '2', journal_detail.old_value
209
209
210 User.find(2).destroy
210 User.find(2).destroy
211 assert_nil User.find_by_id(2)
211 assert_nil User.find_by_id(2)
212 assert_equal User.anonymous.id.to_s, journal_detail.reload.old_value
212 assert_equal User.anonymous.id.to_s, journal_detail.reload.old_value
213 end
213 end
214
214
215 def test_destroy_should_update_journal_details_value
215 def test_destroy_should_update_journal_details_value
216 issue = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'foo')
216 issue = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'foo')
217 issue.init_journal(User.find(1), "update")
217 issue.init_journal(User.find(1), "update")
218 issue.assigned_to_id = 2
218 issue.assigned_to_id = 2
219 assert_difference 'JournalDetail.count' do
219 assert_difference 'JournalDetail.count' do
220 issue.save!
220 issue.save!
221 end
221 end
222 journal_detail = JournalDetail.first(:order => 'id DESC')
222 journal_detail = JournalDetail.first(:order => 'id DESC')
223 assert_equal '2', journal_detail.value
223 assert_equal '2', journal_detail.value
224
224
225 User.find(2).destroy
225 User.find(2).destroy
226 assert_nil User.find_by_id(2)
226 assert_nil User.find_by_id(2)
227 assert_equal User.anonymous.id.to_s, journal_detail.reload.value
227 assert_equal User.anonymous.id.to_s, journal_detail.reload.value
228 end
228 end
229
229
230 def test_destroy_should_update_messages
230 def test_destroy_should_update_messages
231 board = Board.create!(:project_id => 1, :name => 'Board', :description => 'Board')
231 board = Board.create!(:project_id => 1, :name => 'Board', :description => 'Board')
232 message = Message.create!(:board_id => board.id, :author_id => 2, :subject => 'foo', :content => 'foo')
232 message = Message.create!(:board_id => board.id, :author_id => 2, :subject => 'foo', :content => 'foo')
233
233
234 User.find(2).destroy
234 User.find(2).destroy
235 assert_nil User.find_by_id(2)
235 assert_nil User.find_by_id(2)
236 assert_equal User.anonymous, message.reload.author
236 assert_equal User.anonymous, message.reload.author
237 end
237 end
238
238
239 def test_destroy_should_update_news
239 def test_destroy_should_update_news
240 news = News.create!(:project_id => 1, :author_id => 2, :title => 'foo', :description => 'foo')
240 news = News.create!(:project_id => 1, :author_id => 2, :title => 'foo', :description => 'foo')
241
241
242 User.find(2).destroy
242 User.find(2).destroy
243 assert_nil User.find_by_id(2)
243 assert_nil User.find_by_id(2)
244 assert_equal User.anonymous, news.reload.author
244 assert_equal User.anonymous, news.reload.author
245 end
245 end
246
246
247 def test_destroy_should_delete_private_queries
247 def test_destroy_should_delete_private_queries
248 query = Query.new(:name => 'foo', :is_public => false)
248 query = Query.new(:name => 'foo', :is_public => false)
249 query.project_id = 1
249 query.project_id = 1
250 query.user_id = 2
250 query.user_id = 2
251 query.save!
251 query.save!
252
252
253 User.find(2).destroy
253 User.find(2).destroy
254 assert_nil User.find_by_id(2)
254 assert_nil User.find_by_id(2)
255 assert_nil Query.find_by_id(query.id)
255 assert_nil Query.find_by_id(query.id)
256 end
256 end
257
257
258 def test_destroy_should_update_public_queries
258 def test_destroy_should_update_public_queries
259 query = Query.new(:name => 'foo', :is_public => true)
259 query = Query.new(:name => 'foo', :is_public => true)
260 query.project_id = 1
260 query.project_id = 1
261 query.user_id = 2
261 query.user_id = 2
262 query.save!
262 query.save!
263
263
264 User.find(2).destroy
264 User.find(2).destroy
265 assert_nil User.find_by_id(2)
265 assert_nil User.find_by_id(2)
266 assert_equal User.anonymous, query.reload.user
266 assert_equal User.anonymous, query.reload.user
267 end
267 end
268
268
269 def test_destroy_should_update_time_entries
269 def test_destroy_should_update_time_entries
270 entry = TimeEntry.new(:hours => '2', :spent_on => Date.today, :activity => TimeEntryActivity.create!(:name => 'foo'))
270 entry = TimeEntry.new(:hours => '2', :spent_on => Date.today, :activity => TimeEntryActivity.create!(:name => 'foo'))
271 entry.project_id = 1
271 entry.project_id = 1
272 entry.user_id = 2
272 entry.user_id = 2
273 entry.save!
273 entry.save!
274
274
275 User.find(2).destroy
275 User.find(2).destroy
276 assert_nil User.find_by_id(2)
276 assert_nil User.find_by_id(2)
277 assert_equal User.anonymous, entry.reload.user
277 assert_equal User.anonymous, entry.reload.user
278 end
278 end
279
279
280 def test_destroy_should_delete_tokens
280 def test_destroy_should_delete_tokens
281 token = Token.create!(:user_id => 2, :value => 'foo')
281 token = Token.create!(:user_id => 2, :value => 'foo')
282
282
283 User.find(2).destroy
283 User.find(2).destroy
284 assert_nil User.find_by_id(2)
284 assert_nil User.find_by_id(2)
285 assert_nil Token.find_by_id(token.id)
285 assert_nil Token.find_by_id(token.id)
286 end
286 end
287
287
288 def test_destroy_should_delete_watchers
288 def test_destroy_should_delete_watchers
289 issue = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'foo')
289 issue = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'foo')
290 watcher = Watcher.create!(:user_id => 2, :watchable => issue)
290 watcher = Watcher.create!(:user_id => 2, :watchable => issue)
291
291
292 User.find(2).destroy
292 User.find(2).destroy
293 assert_nil User.find_by_id(2)
293 assert_nil User.find_by_id(2)
294 assert_nil Watcher.find_by_id(watcher.id)
294 assert_nil Watcher.find_by_id(watcher.id)
295 end
295 end
296
296
297 def test_destroy_should_update_wiki_contents
297 def test_destroy_should_update_wiki_contents
298 wiki_content = WikiContent.create!(
298 wiki_content = WikiContent.create!(
299 :text => 'foo',
299 :text => 'foo',
300 :author_id => 2,
300 :author_id => 2,
301 :page => WikiPage.create!(:title => 'Foo', :wiki => Wiki.create!(:project_id => 1, :start_page => 'Start'))
301 :page => WikiPage.create!(:title => 'Foo', :wiki => Wiki.create!(:project_id => 1, :start_page => 'Start'))
302 )
302 )
303 wiki_content.text = 'bar'
303 wiki_content.text = 'bar'
304 assert_difference 'WikiContent::Version.count' do
304 assert_difference 'WikiContent::Version.count' do
305 wiki_content.save!
305 wiki_content.save!
306 end
306 end
307
307
308 User.find(2).destroy
308 User.find(2).destroy
309 assert_nil User.find_by_id(2)
309 assert_nil User.find_by_id(2)
310 assert_equal User.anonymous, wiki_content.reload.author
310 assert_equal User.anonymous, wiki_content.reload.author
311 wiki_content.versions.each do |version|
311 wiki_content.versions.each do |version|
312 assert_equal User.anonymous, version.reload.author
312 assert_equal User.anonymous, version.reload.author
313 end
313 end
314 end
314 end
315
315
316 def test_destroy_should_nullify_issue_categories
316 def test_destroy_should_nullify_issue_categories
317 category = IssueCategory.create!(:project_id => 1, :assigned_to_id => 2, :name => 'foo')
317 category = IssueCategory.create!(:project_id => 1, :assigned_to_id => 2, :name => 'foo')
318
318
319 User.find(2).destroy
319 User.find(2).destroy
320 assert_nil User.find_by_id(2)
320 assert_nil User.find_by_id(2)
321 assert_nil category.reload.assigned_to_id
321 assert_nil category.reload.assigned_to_id
322 end
322 end
323
323
324 def test_destroy_should_nullify_changesets
324 def test_destroy_should_nullify_changesets
325 changeset = Changeset.create!(
325 changeset = Changeset.create!(
326 :repository => Repository::Subversion.generate!(
326 :repository => Repository::Subversion.generate!(
327 :project_id => 1
327 :project_id => 1
328 ),
328 ),
329 :revision => '12',
329 :revision => '12',
330 :committed_on => Time.now,
330 :committed_on => Time.now,
331 :committer => 'jsmith'
331 :committer => 'jsmith'
332 )
332 )
333 assert_equal 2, changeset.user_id
333 assert_equal 2, changeset.user_id
334
334
335 User.find(2).destroy
335 User.find(2).destroy
336 assert_nil User.find_by_id(2)
336 assert_nil User.find_by_id(2)
337 assert_nil changeset.reload.user_id
337 assert_nil changeset.reload.user_id
338 end
338 end
339
339
340 def test_anonymous_user_should_not_be_destroyable
340 def test_anonymous_user_should_not_be_destroyable
341 assert_no_difference 'User.count' do
341 assert_no_difference 'User.count' do
342 assert_equal false, User.anonymous.destroy
342 assert_equal false, User.anonymous.destroy
343 end
343 end
344 end
344 end
345
345
346 def test_validate_login_presence
346 def test_validate_login_presence
347 @admin.login = ""
347 @admin.login = ""
348 assert !@admin.save
348 assert !@admin.save
349 assert_equal 1, @admin.errors.count
349 assert_equal 1, @admin.errors.count
350 end
350 end
351
351
352 def test_validate_mail_notification_inclusion
352 def test_validate_mail_notification_inclusion
353 u = User.new
353 u = User.new
354 u.mail_notification = 'foo'
354 u.mail_notification = 'foo'
355 u.save
355 u.save
356 assert_not_nil u.errors[:mail_notification]
356 assert_not_nil u.errors[:mail_notification]
357 end
357 end
358
358
359 context "User#try_to_login" do
359 context "User#try_to_login" do
360 should "fall-back to case-insensitive if user login is not found as-typed." do
360 should "fall-back to case-insensitive if user login is not found as-typed." do
361 user = User.try_to_login("AdMin", "admin")
361 user = User.try_to_login("AdMin", "admin")
362 assert_kind_of User, user
362 assert_kind_of User, user
363 assert_equal "admin", user.login
363 assert_equal "admin", user.login
364 end
364 end
365
365
366 should "select the exact matching user first" do
366 should "select the exact matching user first" do
367 case_sensitive_user = User.generate_with_protected!(
367 case_sensitive_user = User.generate_with_protected!(
368 :login => 'changed', :password => 'admin',
368 :login => 'changed', :password => 'admin',
369 :password_confirmation => 'admin')
369 :password_confirmation => 'admin')
370 # bypass validations to make it appear like existing data
370 # bypass validations to make it appear like existing data
371 case_sensitive_user.update_attribute(:login, 'ADMIN')
371 case_sensitive_user.update_attribute(:login, 'ADMIN')
372
372
373 user = User.try_to_login("ADMIN", "admin")
373 user = User.try_to_login("ADMIN", "admin")
374 assert_kind_of User, user
374 assert_kind_of User, user
375 assert_equal "ADMIN", user.login
375 assert_equal "ADMIN", user.login
376
376
377 end
377 end
378 end
378 end
379
379
380 def test_password
380 def test_password
381 user = User.try_to_login("admin", "admin")
381 user = User.try_to_login("admin", "admin")
382 assert_kind_of User, user
382 assert_kind_of User, user
383 assert_equal "admin", user.login
383 assert_equal "admin", user.login
384 user.password = "hello"
384 user.password = "hello"
385 assert user.save
385 assert user.save
386
386
387 user = User.try_to_login("admin", "hello")
387 user = User.try_to_login("admin", "hello")
388 assert_kind_of User, user
388 assert_kind_of User, user
389 assert_equal "admin", user.login
389 assert_equal "admin", user.login
390 end
390 end
391
391
392 def test_validate_password_length
392 def test_validate_password_length
393 with_settings :password_min_length => '100' do
393 with_settings :password_min_length => '100' do
394 user = User.new(:firstname => "new100", :lastname => "user100", :mail => "newuser100@somenet.foo")
394 user = User.new(:firstname => "new100", :lastname => "user100", :mail => "newuser100@somenet.foo")
395 user.login = "newuser100"
395 user.login = "newuser100"
396 user.password, user.password_confirmation = "password100", "password100"
396 user.password, user.password_confirmation = "password100", "password100"
397 assert !user.save
397 assert !user.save
398 assert_equal 1, user.errors.count
398 assert_equal 1, user.errors.count
399 end
399 end
400 end
400 end
401
401
402 def test_name_format
402 def test_name_format
403 assert_equal 'Smith, John', @jsmith.name(:lastname_coma_firstname)
403 assert_equal 'Smith, John', @jsmith.name(:lastname_coma_firstname)
404 with_settings :user_format => :firstname_lastname do
404 with_settings :user_format => :firstname_lastname do
405 assert_equal 'John Smith', @jsmith.reload.name
405 assert_equal 'John Smith', @jsmith.reload.name
406 end
406 end
407 with_settings :user_format => :username do
407 with_settings :user_format => :username do
408 assert_equal 'jsmith', @jsmith.reload.name
408 assert_equal 'jsmith', @jsmith.reload.name
409 end
409 end
410 end
410 end
411
411
412 def test_fields_for_order_statement_should_return_fields_according_user_format_setting
412 def test_fields_for_order_statement_should_return_fields_according_user_format_setting
413 with_settings :user_format => 'lastname_coma_firstname' do
413 with_settings :user_format => 'lastname_coma_firstname' do
414 assert_equal ['users.lastname', 'users.firstname', 'users.id'], User.fields_for_order_statement
414 assert_equal ['users.lastname', 'users.firstname', 'users.id'], User.fields_for_order_statement
415 end
415 end
416 end
416 end
417
417
418 def test_fields_for_order_statement_width_table_name_should_prepend_table_name
418 def test_fields_for_order_statement_width_table_name_should_prepend_table_name
419 with_settings :user_format => 'lastname_firstname' do
419 with_settings :user_format => 'lastname_firstname' do
420 assert_equal ['authors.lastname', 'authors.firstname', 'authors.id'], User.fields_for_order_statement('authors')
420 assert_equal ['authors.lastname', 'authors.firstname', 'authors.id'], User.fields_for_order_statement('authors')
421 end
421 end
422 end
422 end
423
423
424 def test_fields_for_order_statement_with_blank_format_should_return_default
424 def test_fields_for_order_statement_with_blank_format_should_return_default
425 with_settings :user_format => '' do
425 with_settings :user_format => '' do
426 assert_equal ['users.firstname', 'users.lastname', 'users.id'], User.fields_for_order_statement
426 assert_equal ['users.firstname', 'users.lastname', 'users.id'], User.fields_for_order_statement
427 end
427 end
428 end
428 end
429
429
430 def test_fields_for_order_statement_with_invalid_format_should_return_default
430 def test_fields_for_order_statement_with_invalid_format_should_return_default
431 with_settings :user_format => 'foo' do
431 with_settings :user_format => 'foo' do
432 assert_equal ['users.firstname', 'users.lastname', 'users.id'], User.fields_for_order_statement
432 assert_equal ['users.firstname', 'users.lastname', 'users.id'], User.fields_for_order_statement
433 end
433 end
434 end
434 end
435
435
436 def test_lock
436 def test_lock
437 user = User.try_to_login("jsmith", "jsmith")
437 user = User.try_to_login("jsmith", "jsmith")
438 assert_equal @jsmith, user
438 assert_equal @jsmith, user
439
439
440 @jsmith.status = User::STATUS_LOCKED
440 @jsmith.status = User::STATUS_LOCKED
441 assert @jsmith.save
441 assert @jsmith.save
442
442
443 user = User.try_to_login("jsmith", "jsmith")
443 user = User.try_to_login("jsmith", "jsmith")
444 assert_equal nil, user
444 assert_equal nil, user
445 end
445 end
446
446
447 context ".try_to_login" do
447 context ".try_to_login" do
448 context "with good credentials" do
448 context "with good credentials" do
449 should "return the user" do
449 should "return the user" do
450 user = User.try_to_login("admin", "admin")
450 user = User.try_to_login("admin", "admin")
451 assert_kind_of User, user
451 assert_kind_of User, user
452 assert_equal "admin", user.login
452 assert_equal "admin", user.login
453 end
453 end
454 end
454 end
455
455
456 context "with wrong credentials" do
456 context "with wrong credentials" do
457 should "return nil" do
457 should "return nil" do
458 assert_nil User.try_to_login("admin", "foo")
458 assert_nil User.try_to_login("admin", "foo")
459 end
459 end
460 end
460 end
461 end
461 end
462
462
463 if ldap_configured?
463 if ldap_configured?
464 context "#try_to_login using LDAP" do
464 context "#try_to_login using LDAP" do
465 context "with failed connection to the LDAP server" do
465 context "with failed connection to the LDAP server" do
466 should "return nil" do
466 should "return nil" do
467 @auth_source = AuthSourceLdap.find(1)
467 @auth_source = AuthSourceLdap.find(1)
468 AuthSource.any_instance.stubs(:initialize_ldap_con).raises(Net::LDAP::LdapError, 'Cannot connect')
468 AuthSource.any_instance.stubs(:initialize_ldap_con).raises(Net::LDAP::LdapError, 'Cannot connect')
469
469
470 assert_equal nil, User.try_to_login('edavis', 'wrong')
470 assert_equal nil, User.try_to_login('edavis', 'wrong')
471 end
471 end
472 end
472 end
473
473
474 context "with an unsuccessful authentication" do
474 context "with an unsuccessful authentication" do
475 should "return nil" do
475 should "return nil" do
476 assert_equal nil, User.try_to_login('edavis', 'wrong')
476 assert_equal nil, User.try_to_login('edavis', 'wrong')
477 end
477 end
478 end
478 end
479
479
480 context "binding with user's account" do
480 context "binding with user's account" do
481 setup do
481 setup do
482 @auth_source = AuthSourceLdap.find(1)
482 @auth_source = AuthSourceLdap.find(1)
483 @auth_source.account = "uid=$login,ou=Person,dc=redmine,dc=org"
483 @auth_source.account = "uid=$login,ou=Person,dc=redmine,dc=org"
484 @auth_source.account_password = ''
484 @auth_source.account_password = ''
485 @auth_source.save!
485 @auth_source.save!
486
486
487 @ldap_user = User.new(:mail => 'example1@redmine.org', :firstname => 'LDAP', :lastname => 'user', :auth_source_id => 1)
487 @ldap_user = User.new(:mail => 'example1@redmine.org', :firstname => 'LDAP', :lastname => 'user', :auth_source_id => 1)
488 @ldap_user.login = 'example1'
488 @ldap_user.login = 'example1'
489 @ldap_user.save!
489 @ldap_user.save!
490 end
490 end
491
491
492 context "with a successful authentication" do
492 context "with a successful authentication" do
493 should "return the user" do
493 should "return the user" do
494 assert_equal @ldap_user, User.try_to_login('example1', '123456')
494 assert_equal @ldap_user, User.try_to_login('example1', '123456')
495 end
495 end
496 end
496 end
497
497
498 context "with an unsuccessful authentication" do
498 context "with an unsuccessful authentication" do
499 should "return nil" do
499 should "return nil" do
500 assert_nil User.try_to_login('example1', '11111')
500 assert_nil User.try_to_login('example1', '11111')
501 end
501 end
502 end
502 end
503 end
503 end
504
504
505 context "on the fly registration" do
505 context "on the fly registration" do
506 setup do
506 setup do
507 @auth_source = AuthSourceLdap.find(1)
507 @auth_source = AuthSourceLdap.find(1)
508 @auth_source.update_attribute :onthefly_register, true
508 @auth_source.update_attribute :onthefly_register, true
509 end
509 end
510
510
511 context "with a successful authentication" do
511 context "with a successful authentication" do
512 should "create a new user account if it doesn't exist" do
512 should "create a new user account if it doesn't exist" do
513 assert_difference('User.count') do
513 assert_difference('User.count') do
514 user = User.try_to_login('edavis', '123456')
514 user = User.try_to_login('edavis', '123456')
515 assert !user.admin?
515 assert !user.admin?
516 end
516 end
517 end
517 end
518
518
519 should "retrieve existing user" do
519 should "retrieve existing user" do
520 user = User.try_to_login('edavis', '123456')
520 user = User.try_to_login('edavis', '123456')
521 user.admin = true
521 user.admin = true
522 user.save!
522 user.save!
523
523
524 assert_no_difference('User.count') do
524 assert_no_difference('User.count') do
525 user = User.try_to_login('edavis', '123456')
525 user = User.try_to_login('edavis', '123456')
526 assert user.admin?
526 assert user.admin?
527 end
527 end
528 end
528 end
529 end
529 end
530
530
531 context "binding with user's account" do
531 context "binding with user's account" do
532 setup do
532 setup do
533 @auth_source = AuthSourceLdap.find(1)
533 @auth_source = AuthSourceLdap.find(1)
534 @auth_source.account = "uid=$login,ou=Person,dc=redmine,dc=org"
534 @auth_source.account = "uid=$login,ou=Person,dc=redmine,dc=org"
535 @auth_source.account_password = ''
535 @auth_source.account_password = ''
536 @auth_source.save!
536 @auth_source.save!
537 end
537 end
538
538
539 context "with a successful authentication" do
539 context "with a successful authentication" do
540 should "create a new user account if it doesn't exist" do
540 should "create a new user account if it doesn't exist" do
541 assert_difference('User.count') do
541 assert_difference('User.count') do
542 user = User.try_to_login('example1', '123456')
542 user = User.try_to_login('example1', '123456')
543 assert_kind_of User, user
543 assert_kind_of User, user
544 end
544 end
545 end
545 end
546 end
546 end
547
547
548 context "with an unsuccessful authentication" do
548 context "with an unsuccessful authentication" do
549 should "return nil" do
549 should "return nil" do
550 assert_nil User.try_to_login('example1', '11111')
550 assert_nil User.try_to_login('example1', '11111')
551 end
551 end
552 end
552 end
553 end
553 end
554 end
554 end
555 end
555 end
556
556
557 else
557 else
558 puts "Skipping LDAP tests."
558 puts "Skipping LDAP tests."
559 end
559 end
560
560
561 def test_create_anonymous
561 def test_create_anonymous
562 AnonymousUser.delete_all
562 AnonymousUser.delete_all
563 anon = User.anonymous
563 anon = User.anonymous
564 assert !anon.new_record?
564 assert !anon.new_record?
565 assert_kind_of AnonymousUser, anon
565 assert_kind_of AnonymousUser, anon
566 end
566 end
567
567
568 def test_ensure_single_anonymous_user
568 def test_ensure_single_anonymous_user
569 AnonymousUser.delete_all
569 AnonymousUser.delete_all
570 anon1 = User.anonymous
570 anon1 = User.anonymous
571 assert !anon1.new_record?
571 assert !anon1.new_record?
572 assert_kind_of AnonymousUser, anon1
572 assert_kind_of AnonymousUser, anon1
573 anon2 = AnonymousUser.create(
573 anon2 = AnonymousUser.create(
574 :lastname => 'Anonymous', :firstname => '',
574 :lastname => 'Anonymous', :firstname => '',
575 :mail => '', :login => '', :status => 0)
575 :mail => '', :login => '', :status => 0)
576 assert_equal 1, anon2.errors.count
576 assert_equal 1, anon2.errors.count
577 end
577 end
578
578
579 def test_rss_key
579 def test_rss_key
580 assert_nil @jsmith.rss_token
580 assert_nil @jsmith.rss_token
581 key = @jsmith.rss_key
581 key = @jsmith.rss_key
582 assert_equal 40, key.length
582 assert_equal 40, key.length
583
583
584 @jsmith.reload
584 @jsmith.reload
585 assert_equal key, @jsmith.rss_key
585 assert_equal key, @jsmith.rss_key
586 end
586 end
587
587
588 context "User#api_key" do
588 context "User#api_key" do
589 should "generate a new one if the user doesn't have one" do
589 should "generate a new one if the user doesn't have one" do
590 user = User.generate_with_protected!(:api_token => nil)
590 user = User.generate_with_protected!(:api_token => nil)
591 assert_nil user.api_token
591 assert_nil user.api_token
592
592
593 key = user.api_key
593 key = user.api_key
594 assert_equal 40, key.length
594 assert_equal 40, key.length
595 user.reload
595 user.reload
596 assert_equal key, user.api_key
596 assert_equal key, user.api_key
597 end
597 end
598
598
599 should "return the existing api token value" do
599 should "return the existing api token value" do
600 user = User.generate_with_protected!
600 user = User.generate_with_protected!
601 token = Token.generate!(:action => 'api')
601 token = Token.generate!(:action => 'api')
602 user.api_token = token
602 user.api_token = token
603 assert user.save
603 assert user.save
604
604
605 assert_equal token.value, user.api_key
605 assert_equal token.value, user.api_key
606 end
606 end
607 end
607 end
608
608
609 context "User#find_by_api_key" do
609 context "User#find_by_api_key" do
610 should "return nil if no matching key is found" do
610 should "return nil if no matching key is found" do
611 assert_nil User.find_by_api_key('zzzzzzzzz')
611 assert_nil User.find_by_api_key('zzzzzzzzz')
612 end
612 end
613
613
614 should "return nil if the key is found for an inactive user" do
614 should "return nil if the key is found for an inactive user" do
615 user = User.generate_with_protected!(:status => User::STATUS_LOCKED)
615 user = User.generate_with_protected!(:status => User::STATUS_LOCKED)
616 token = Token.generate!(:action => 'api')
616 token = Token.generate!(:action => 'api')
617 user.api_token = token
617 user.api_token = token
618 user.save
618 user.save
619
619
620 assert_nil User.find_by_api_key(token.value)
620 assert_nil User.find_by_api_key(token.value)
621 end
621 end
622
622
623 should "return the user if the key is found for an active user" do
623 should "return the user if the key is found for an active user" do
624 user = User.generate_with_protected!(:status => User::STATUS_ACTIVE)
624 user = User.generate_with_protected!(:status => User::STATUS_ACTIVE)
625 token = Token.generate!(:action => 'api')
625 token = Token.generate!(:action => 'api')
626 user.api_token = token
626 user.api_token = token
627 user.save
627 user.save
628
628
629 assert_equal user, User.find_by_api_key(token.value)
629 assert_equal user, User.find_by_api_key(token.value)
630 end
630 end
631 end
631 end
632
632
633 def test_default_admin_account_changed_should_return_false_if_account_was_not_changed
633 def test_default_admin_account_changed_should_return_false_if_account_was_not_changed
634 user = User.find_by_login("admin")
634 user = User.find_by_login("admin")
635 user.password = "admin"
635 user.password = "admin"
636 user.save!
636 user.save!
637
637
638 assert_equal false, User.default_admin_account_changed?
638 assert_equal false, User.default_admin_account_changed?
639 end
639 end
640
640
641 def test_default_admin_account_changed_should_return_true_if_password_was_changed
641 def test_default_admin_account_changed_should_return_true_if_password_was_changed
642 user = User.find_by_login("admin")
642 user = User.find_by_login("admin")
643 user.password = "newpassword"
643 user.password = "newpassword"
644 user.save!
644 user.save!
645
645
646 assert_equal true, User.default_admin_account_changed?
646 assert_equal true, User.default_admin_account_changed?
647 end
647 end
648
648
649 def test_default_admin_account_changed_should_return_true_if_account_is_disabled
649 def test_default_admin_account_changed_should_return_true_if_account_is_disabled
650 user = User.find_by_login("admin")
650 user = User.find_by_login("admin")
651 user.password = "admin"
651 user.password = "admin"
652 user.status = User::STATUS_LOCKED
652 user.status = User::STATUS_LOCKED
653 user.save!
653 user.save!
654
654
655 assert_equal true, User.default_admin_account_changed?
655 assert_equal true, User.default_admin_account_changed?
656 end
656 end
657
657
658 def test_default_admin_account_changed_should_return_true_if_account_does_not_exist
658 def test_default_admin_account_changed_should_return_true_if_account_does_not_exist
659 user = User.find_by_login("admin")
659 user = User.find_by_login("admin")
660 user.destroy
660 user.destroy
661
661
662 assert_equal true, User.default_admin_account_changed?
662 assert_equal true, User.default_admin_account_changed?
663 end
663 end
664
664
665 def test_roles_for_project
665 def test_roles_for_project
666 # user with a role
666 # user with a role
667 roles = @jsmith.roles_for_project(Project.find(1))
667 roles = @jsmith.roles_for_project(Project.find(1))
668 assert_kind_of Role, roles.first
668 assert_kind_of Role, roles.first
669 assert_equal "Manager", roles.first.name
669 assert_equal "Manager", roles.first.name
670
670
671 # user with no role
671 # user with no role
672 assert_nil @dlopper.roles_for_project(Project.find(2)).detect {|role| role.member?}
672 assert_nil @dlopper.roles_for_project(Project.find(2)).detect {|role| role.member?}
673 end
673 end
674
674
675 def test_projects_by_role_for_user_with_role
675 def test_projects_by_role_for_user_with_role
676 user = User.find(2)
676 user = User.find(2)
677 assert_kind_of Hash, user.projects_by_role
677 assert_kind_of Hash, user.projects_by_role
678 assert_equal 2, user.projects_by_role.size
678 assert_equal 2, user.projects_by_role.size
679 assert_equal [1,5], user.projects_by_role[Role.find(1)].collect(&:id).sort
679 assert_equal [1,5], user.projects_by_role[Role.find(1)].collect(&:id).sort
680 assert_equal [2], user.projects_by_role[Role.find(2)].collect(&:id).sort
680 assert_equal [2], user.projects_by_role[Role.find(2)].collect(&:id).sort
681 end
681 end
682
682
683 def test_projects_by_role_for_user_with_no_role
683 def test_projects_by_role_for_user_with_no_role
684 user = User.generate!
684 user = User.generate!
685 assert_equal({}, user.projects_by_role)
685 assert_equal({}, user.projects_by_role)
686 end
686 end
687
687
688 def test_projects_by_role_for_anonymous
688 def test_projects_by_role_for_anonymous
689 assert_equal({}, User.anonymous.projects_by_role)
689 assert_equal({}, User.anonymous.projects_by_role)
690 end
690 end
691
691
692 def test_valid_notification_options
692 def test_valid_notification_options
693 # without memberships
693 # without memberships
694 assert_equal 5, User.find(7).valid_notification_options.size
694 assert_equal 5, User.find(7).valid_notification_options.size
695 # with memberships
695 # with memberships
696 assert_equal 6, User.find(2).valid_notification_options.size
696 assert_equal 6, User.find(2).valid_notification_options.size
697 end
697 end
698
698
699 def test_valid_notification_options_class_method
699 def test_valid_notification_options_class_method
700 assert_equal 5, User.valid_notification_options.size
700 assert_equal 5, User.valid_notification_options.size
701 assert_equal 5, User.valid_notification_options(User.find(7)).size
701 assert_equal 5, User.valid_notification_options(User.find(7)).size
702 assert_equal 6, User.valid_notification_options(User.find(2)).size
702 assert_equal 6, User.valid_notification_options(User.find(2)).size
703 end
703 end
704
704
705 def test_mail_notification_all
705 def test_mail_notification_all
706 @jsmith.mail_notification = 'all'
706 @jsmith.mail_notification = 'all'
707 @jsmith.notified_project_ids = []
707 @jsmith.notified_project_ids = []
708 @jsmith.save
708 @jsmith.save
709 @jsmith.reload
709 @jsmith.reload
710 assert @jsmith.projects.first.recipients.include?(@jsmith.mail)
710 assert @jsmith.projects.first.recipients.include?(@jsmith.mail)
711 end
711 end
712
712
713 def test_mail_notification_selected
713 def test_mail_notification_selected
714 @jsmith.mail_notification = 'selected'
714 @jsmith.mail_notification = 'selected'
715 @jsmith.notified_project_ids = [1]
715 @jsmith.notified_project_ids = [1]
716 @jsmith.save
716 @jsmith.save
717 @jsmith.reload
717 @jsmith.reload
718 assert Project.find(1).recipients.include?(@jsmith.mail)
718 assert Project.find(1).recipients.include?(@jsmith.mail)
719 end
719 end
720
720
721 def test_mail_notification_only_my_events
721 def test_mail_notification_only_my_events
722 @jsmith.mail_notification = 'only_my_events'
722 @jsmith.mail_notification = 'only_my_events'
723 @jsmith.notified_project_ids = []
723 @jsmith.notified_project_ids = []
724 @jsmith.save
724 @jsmith.save
725 @jsmith.reload
725 @jsmith.reload
726 assert !@jsmith.projects.first.recipients.include?(@jsmith.mail)
726 assert !@jsmith.projects.first.recipients.include?(@jsmith.mail)
727 end
727 end
728
728
729 def test_comments_sorting_preference
729 def test_comments_sorting_preference
730 assert !@jsmith.wants_comments_in_reverse_order?
730 assert !@jsmith.wants_comments_in_reverse_order?
731 @jsmith.pref.comments_sorting = 'asc'
731 @jsmith.pref.comments_sorting = 'asc'
732 assert !@jsmith.wants_comments_in_reverse_order?
732 assert !@jsmith.wants_comments_in_reverse_order?
733 @jsmith.pref.comments_sorting = 'desc'
733 @jsmith.pref.comments_sorting = 'desc'
734 assert @jsmith.wants_comments_in_reverse_order?
734 assert @jsmith.wants_comments_in_reverse_order?
735 end
735 end
736
736
737 def test_find_by_mail_should_be_case_insensitive
737 def test_find_by_mail_should_be_case_insensitive
738 u = User.find_by_mail('JSmith@somenet.foo')
738 u = User.find_by_mail('JSmith@somenet.foo')
739 assert_not_nil u
739 assert_not_nil u
740 assert_equal 'jsmith@somenet.foo', u.mail
740 assert_equal 'jsmith@somenet.foo', u.mail
741 end
741 end
742
742
743 def test_random_password
743 def test_random_password
744 u = User.new
744 u = User.new
745 u.random_password
745 u.random_password
746 assert !u.password.blank?
746 assert !u.password.blank?
747 assert !u.password_confirmation.blank?
747 assert !u.password_confirmation.blank?
748 end
748 end
749
749
750 context "#change_password_allowed?" do
750 context "#change_password_allowed?" do
751 should "be allowed if no auth source is set" do
751 should "be allowed if no auth source is set" do
752 user = User.generate_with_protected!
752 user = User.generate_with_protected!
753 assert user.change_password_allowed?
753 assert user.change_password_allowed?
754 end
754 end
755
755
756 should "delegate to the auth source" do
756 should "delegate to the auth source" do
757 user = User.generate_with_protected!
757 user = User.generate_with_protected!
758
758
759 allowed_auth_source = AuthSource.generate!
759 allowed_auth_source = AuthSource.generate!
760 def allowed_auth_source.allow_password_changes?; true; end
760 def allowed_auth_source.allow_password_changes?; true; end
761
761
762 denied_auth_source = AuthSource.generate!
762 denied_auth_source = AuthSource.generate!
763 def denied_auth_source.allow_password_changes?; false; end
763 def denied_auth_source.allow_password_changes?; false; end
764
764
765 assert user.change_password_allowed?
765 assert user.change_password_allowed?
766
766
767 user.auth_source = allowed_auth_source
767 user.auth_source = allowed_auth_source
768 assert user.change_password_allowed?, "User not allowed to change password, though auth source does"
768 assert user.change_password_allowed?, "User not allowed to change password, though auth source does"
769
769
770 user.auth_source = denied_auth_source
770 user.auth_source = denied_auth_source
771 assert !user.change_password_allowed?, "User allowed to change password, though auth source does not"
771 assert !user.change_password_allowed?, "User allowed to change password, though auth source does not"
772 end
772 end
773 end
774
775 def test_own_account_deletable_should_be_true_with_unsubscrive_enabled
776 with_settings :unsubscribe => '1' do
777 assert_equal true, User.find(2).own_account_deletable?
778 end
779 end
780
781 def test_own_account_deletable_should_be_false_with_unsubscrive_disabled
782 with_settings :unsubscribe => '0' do
783 assert_equal false, User.find(2).own_account_deletable?
784 end
785 end
773
786
787 def test_own_account_deletable_should_be_false_for_a_single_admin
788 User.delete_all(["admin = ? AND id <> ?", true, 1])
789
790 with_settings :unsubscribe => '1' do
791 assert_equal false, User.find(1).own_account_deletable?
792 end
793 end
794
795 def test_own_account_deletable_should_be_true_for_an_admin_if_other_admin_exists
796 User.generate_with_protected(:admin => true)
797
798 with_settings :unsubscribe => '1' do
799 assert_equal true, User.find(1).own_account_deletable?
800 end
774 end
801 end
775
802
776 context "#allowed_to?" do
803 context "#allowed_to?" do
777 context "with a unique project" do
804 context "with a unique project" do
778 should "return false if project is archived" do
805 should "return false if project is archived" do
779 project = Project.find(1)
806 project = Project.find(1)
780 Project.any_instance.stubs(:status).returns(Project::STATUS_ARCHIVED)
807 Project.any_instance.stubs(:status).returns(Project::STATUS_ARCHIVED)
781 assert ! @admin.allowed_to?(:view_issues, Project.find(1))
808 assert ! @admin.allowed_to?(:view_issues, Project.find(1))
782 end
809 end
783
810
784 should "return false if related module is disabled" do
811 should "return false if related module is disabled" do
785 project = Project.find(1)
812 project = Project.find(1)
786 project.enabled_module_names = ["issue_tracking"]
813 project.enabled_module_names = ["issue_tracking"]
787 assert @admin.allowed_to?(:add_issues, project)
814 assert @admin.allowed_to?(:add_issues, project)
788 assert ! @admin.allowed_to?(:view_wiki_pages, project)
815 assert ! @admin.allowed_to?(:view_wiki_pages, project)
789 end
816 end
790
817
791 should "authorize nearly everything for admin users" do
818 should "authorize nearly everything for admin users" do
792 project = Project.find(1)
819 project = Project.find(1)
793 assert ! @admin.member_of?(project)
820 assert ! @admin.member_of?(project)
794 %w(edit_issues delete_issues manage_news manage_documents manage_wiki).each do |p|
821 %w(edit_issues delete_issues manage_news manage_documents manage_wiki).each do |p|
795 assert @admin.allowed_to?(p.to_sym, project)
822 assert @admin.allowed_to?(p.to_sym, project)
796 end
823 end
797 end
824 end
798
825
799 should "authorize normal users depending on their roles" do
826 should "authorize normal users depending on their roles" do
800 project = Project.find(1)
827 project = Project.find(1)
801 assert @jsmith.allowed_to?(:delete_messages, project) #Manager
828 assert @jsmith.allowed_to?(:delete_messages, project) #Manager
802 assert ! @dlopper.allowed_to?(:delete_messages, project) #Developper
829 assert ! @dlopper.allowed_to?(:delete_messages, project) #Developper
803 end
830 end
804 end
831 end
805
832
806 context "with multiple projects" do
833 context "with multiple projects" do
807 should "return false if array is empty" do
834 should "return false if array is empty" do
808 assert ! @admin.allowed_to?(:view_project, [])
835 assert ! @admin.allowed_to?(:view_project, [])
809 end
836 end
810
837
811 should "return true only if user has permission on all these projects" do
838 should "return true only if user has permission on all these projects" do
812 assert @admin.allowed_to?(:view_project, Project.all)
839 assert @admin.allowed_to?(:view_project, Project.all)
813 assert ! @dlopper.allowed_to?(:view_project, Project.all) #cannot see Project(2)
840 assert ! @dlopper.allowed_to?(:view_project, Project.all) #cannot see Project(2)
814 assert @jsmith.allowed_to?(:edit_issues, @jsmith.projects) #Manager or Developer everywhere
841 assert @jsmith.allowed_to?(:edit_issues, @jsmith.projects) #Manager or Developer everywhere
815 assert ! @jsmith.allowed_to?(:delete_issue_watchers, @jsmith.projects) #Dev cannot delete_issue_watchers
842 assert ! @jsmith.allowed_to?(:delete_issue_watchers, @jsmith.projects) #Dev cannot delete_issue_watchers
816 end
843 end
817
844
818 should "behave correctly with arrays of 1 project" do
845 should "behave correctly with arrays of 1 project" do
819 assert ! User.anonymous.allowed_to?(:delete_issues, [Project.first])
846 assert ! User.anonymous.allowed_to?(:delete_issues, [Project.first])
820 end
847 end
821 end
848 end
822
849
823 context "with options[:global]" do
850 context "with options[:global]" do
824 should "authorize if user has at least one role that has this permission" do
851 should "authorize if user has at least one role that has this permission" do
825 @dlopper2 = User.find(5) #only Developper on a project, not Manager anywhere
852 @dlopper2 = User.find(5) #only Developper on a project, not Manager anywhere
826 @anonymous = User.find(6)
853 @anonymous = User.find(6)
827 assert @jsmith.allowed_to?(:delete_issue_watchers, nil, :global => true)
854 assert @jsmith.allowed_to?(:delete_issue_watchers, nil, :global => true)
828 assert ! @dlopper2.allowed_to?(:delete_issue_watchers, nil, :global => true)
855 assert ! @dlopper2.allowed_to?(:delete_issue_watchers, nil, :global => true)
829 assert @dlopper2.allowed_to?(:add_issues, nil, :global => true)
856 assert @dlopper2.allowed_to?(:add_issues, nil, :global => true)
830 assert ! @anonymous.allowed_to?(:add_issues, nil, :global => true)
857 assert ! @anonymous.allowed_to?(:add_issues, nil, :global => true)
831 assert @anonymous.allowed_to?(:view_issues, nil, :global => true)
858 assert @anonymous.allowed_to?(:view_issues, nil, :global => true)
832 end
859 end
833 end
860 end
834 end
861 end
835
862
836 context "User#notify_about?" do
863 context "User#notify_about?" do
837 context "Issues" do
864 context "Issues" do
838 setup do
865 setup do
839 @project = Project.find(1)
866 @project = Project.find(1)
840 @author = User.generate_with_protected!
867 @author = User.generate_with_protected!
841 @assignee = User.generate_with_protected!
868 @assignee = User.generate_with_protected!
842 @issue = Issue.generate_for_project!(@project, :assigned_to => @assignee, :author => @author)
869 @issue = Issue.generate_for_project!(@project, :assigned_to => @assignee, :author => @author)
843 end
870 end
844
871
845 should "be true for a user with :all" do
872 should "be true for a user with :all" do
846 @author.update_attribute(:mail_notification, 'all')
873 @author.update_attribute(:mail_notification, 'all')
847 assert @author.notify_about?(@issue)
874 assert @author.notify_about?(@issue)
848 end
875 end
849
876
850 should "be false for a user with :none" do
877 should "be false for a user with :none" do
851 @author.update_attribute(:mail_notification, 'none')
878 @author.update_attribute(:mail_notification, 'none')
852 assert ! @author.notify_about?(@issue)
879 assert ! @author.notify_about?(@issue)
853 end
880 end
854
881
855 should "be false for a user with :only_my_events and isn't an author, creator, or assignee" do
882 should "be false for a user with :only_my_events and isn't an author, creator, or assignee" do
856 @user = User.generate_with_protected!(:mail_notification => 'only_my_events')
883 @user = User.generate_with_protected!(:mail_notification => 'only_my_events')
857 Member.create!(:user => @user, :project => @project, :role_ids => [1])
884 Member.create!(:user => @user, :project => @project, :role_ids => [1])
858 assert ! @user.notify_about?(@issue)
885 assert ! @user.notify_about?(@issue)
859 end
886 end
860
887
861 should "be true for a user with :only_my_events and is the author" do
888 should "be true for a user with :only_my_events and is the author" do
862 @author.update_attribute(:mail_notification, 'only_my_events')
889 @author.update_attribute(:mail_notification, 'only_my_events')
863 assert @author.notify_about?(@issue)
890 assert @author.notify_about?(@issue)
864 end
891 end
865
892
866 should "be true for a user with :only_my_events and is the assignee" do
893 should "be true for a user with :only_my_events and is the assignee" do
867 @assignee.update_attribute(:mail_notification, 'only_my_events')
894 @assignee.update_attribute(:mail_notification, 'only_my_events')
868 assert @assignee.notify_about?(@issue)
895 assert @assignee.notify_about?(@issue)
869 end
896 end
870
897
871 should "be true for a user with :only_assigned and is the assignee" do
898 should "be true for a user with :only_assigned and is the assignee" do
872 @assignee.update_attribute(:mail_notification, 'only_assigned')
899 @assignee.update_attribute(:mail_notification, 'only_assigned')
873 assert @assignee.notify_about?(@issue)
900 assert @assignee.notify_about?(@issue)
874 end
901 end
875
902
876 should "be false for a user with :only_assigned and is not the assignee" do
903 should "be false for a user with :only_assigned and is not the assignee" do
877 @author.update_attribute(:mail_notification, 'only_assigned')
904 @author.update_attribute(:mail_notification, 'only_assigned')
878 assert ! @author.notify_about?(@issue)
905 assert ! @author.notify_about?(@issue)
879 end
906 end
880
907
881 should "be true for a user with :only_owner and is the author" do
908 should "be true for a user with :only_owner and is the author" do
882 @author.update_attribute(:mail_notification, 'only_owner')
909 @author.update_attribute(:mail_notification, 'only_owner')
883 assert @author.notify_about?(@issue)
910 assert @author.notify_about?(@issue)
884 end
911 end
885
912
886 should "be false for a user with :only_owner and is not the author" do
913 should "be false for a user with :only_owner and is not the author" do
887 @assignee.update_attribute(:mail_notification, 'only_owner')
914 @assignee.update_attribute(:mail_notification, 'only_owner')
888 assert ! @assignee.notify_about?(@issue)
915 assert ! @assignee.notify_about?(@issue)
889 end
916 end
890
917
891 should "be true for a user with :selected and is the author" do
918 should "be true for a user with :selected and is the author" do
892 @author.update_attribute(:mail_notification, 'selected')
919 @author.update_attribute(:mail_notification, 'selected')
893 assert @author.notify_about?(@issue)
920 assert @author.notify_about?(@issue)
894 end
921 end
895
922
896 should "be true for a user with :selected and is the assignee" do
923 should "be true for a user with :selected and is the assignee" do
897 @assignee.update_attribute(:mail_notification, 'selected')
924 @assignee.update_attribute(:mail_notification, 'selected')
898 assert @assignee.notify_about?(@issue)
925 assert @assignee.notify_about?(@issue)
899 end
926 end
900
927
901 should "be false for a user with :selected and is not the author or assignee" do
928 should "be false for a user with :selected and is not the author or assignee" do
902 @user = User.generate_with_protected!(:mail_notification => 'selected')
929 @user = User.generate_with_protected!(:mail_notification => 'selected')
903 Member.create!(:user => @user, :project => @project, :role_ids => [1])
930 Member.create!(:user => @user, :project => @project, :role_ids => [1])
904 assert ! @user.notify_about?(@issue)
931 assert ! @user.notify_about?(@issue)
905 end
932 end
906 end
933 end
907
934
908 context "other events" do
935 context "other events" do
909 should 'be added and tested'
936 should 'be added and tested'
910 end
937 end
911 end
938 end
912
939
913 def test_salt_unsalted_passwords
940 def test_salt_unsalted_passwords
914 # Restore a user with an unsalted password
941 # Restore a user with an unsalted password
915 user = User.find(1)
942 user = User.find(1)
916 user.salt = nil
943 user.salt = nil
917 user.hashed_password = User.hash_password("unsalted")
944 user.hashed_password = User.hash_password("unsalted")
918 user.save!
945 user.save!
919
946
920 User.salt_unsalted_passwords!
947 User.salt_unsalted_passwords!
921
948
922 user.reload
949 user.reload
923 # Salt added
950 # Salt added
924 assert !user.salt.blank?
951 assert !user.salt.blank?
925 # Password still valid
952 # Password still valid
926 assert user.check_password?("unsalted")
953 assert user.check_password?("unsalted")
927 assert_equal user, User.try_to_login(user.login, "unsalted")
954 assert_equal user, User.try_to_login(user.login, "unsalted")
928 end
955 end
929
956
930 if Object.const_defined?(:OpenID)
957 if Object.const_defined?(:OpenID)
931
958
932 def test_setting_identity_url
959 def test_setting_identity_url
933 normalized_open_id_url = 'http://example.com/'
960 normalized_open_id_url = 'http://example.com/'
934 u = User.new( :identity_url => 'http://example.com/' )
961 u = User.new( :identity_url => 'http://example.com/' )
935 assert_equal normalized_open_id_url, u.identity_url
962 assert_equal normalized_open_id_url, u.identity_url
936 end
963 end
937
964
938 def test_setting_identity_url_without_trailing_slash
965 def test_setting_identity_url_without_trailing_slash
939 normalized_open_id_url = 'http://example.com/'
966 normalized_open_id_url = 'http://example.com/'
940 u = User.new( :identity_url => 'http://example.com' )
967 u = User.new( :identity_url => 'http://example.com' )
941 assert_equal normalized_open_id_url, u.identity_url
968 assert_equal normalized_open_id_url, u.identity_url
942 end
969 end
943
970
944 def test_setting_identity_url_without_protocol
971 def test_setting_identity_url_without_protocol
945 normalized_open_id_url = 'http://example.com/'
972 normalized_open_id_url = 'http://example.com/'
946 u = User.new( :identity_url => 'example.com' )
973 u = User.new( :identity_url => 'example.com' )
947 assert_equal normalized_open_id_url, u.identity_url
974 assert_equal normalized_open_id_url, u.identity_url
948 end
975 end
949
976
950 def test_setting_blank_identity_url
977 def test_setting_blank_identity_url
951 u = User.new( :identity_url => 'example.com' )
978 u = User.new( :identity_url => 'example.com' )
952 u.identity_url = ''
979 u.identity_url = ''
953 assert u.identity_url.blank?
980 assert u.identity_url.blank?
954 end
981 end
955
982
956 def test_setting_invalid_identity_url
983 def test_setting_invalid_identity_url
957 u = User.new( :identity_url => 'this is not an openid url' )
984 u = User.new( :identity_url => 'this is not an openid url' )
958 assert u.identity_url.blank?
985 assert u.identity_url.blank?
959 end
986 end
960
987
961 else
988 else
962 puts "Skipping openid tests."
989 puts "Skipping openid tests."
963 end
990 end
964
991
965 end
992 end
General Comments 0
You need to be logged in to leave comments. Login now