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