##// END OF EJS Templates
Added the hability to copy an issue....
Jean-Philippe Lang -
r860:0af6f347580d
parent child
Show More
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
@@ -1,216 +1,217
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class IssuesController < ApplicationController
18 class IssuesController < ApplicationController
19 layout 'base', :except => :export_pdf
19 layout 'base', :except => :export_pdf
20 before_filter :find_project, :authorize, :except => [:index, :preview]
20 before_filter :find_project, :authorize, :except => [:index, :preview]
21 accept_key_auth :index
21 accept_key_auth :index
22
22
23 cache_sweeper :issue_sweeper, :only => [ :edit, :change_status, :destroy ]
23 cache_sweeper :issue_sweeper, :only => [ :edit, :change_status, :destroy ]
24
24
25 helper :projects
25 helper :projects
26 include ProjectsHelper
26 include ProjectsHelper
27 helper :custom_fields
27 helper :custom_fields
28 include CustomFieldsHelper
28 include CustomFieldsHelper
29 helper :ifpdf
29 helper :ifpdf
30 include IfpdfHelper
30 include IfpdfHelper
31 helper :issue_relations
31 helper :issue_relations
32 include IssueRelationsHelper
32 include IssueRelationsHelper
33 helper :watchers
33 helper :watchers
34 include WatchersHelper
34 include WatchersHelper
35 helper :attachments
35 helper :attachments
36 include AttachmentsHelper
36 include AttachmentsHelper
37 helper :queries
37 helper :queries
38 helper :sort
38 helper :sort
39 include SortHelper
39 include SortHelper
40
40
41 def index
41 def index
42 sort_init "#{Issue.table_name}.id", "desc"
42 sort_init "#{Issue.table_name}.id", "desc"
43 sort_update
43 sort_update
44 retrieve_query
44 retrieve_query
45 if @query.valid?
45 if @query.valid?
46 @issue_count = Issue.count(:include => [:status, :project], :conditions => @query.statement)
46 @issue_count = Issue.count(:include => [:status, :project], :conditions => @query.statement)
47 @issue_pages = Paginator.new self, @issue_count, 25, params['page']
47 @issue_pages = Paginator.new self, @issue_count, 25, params['page']
48 @issues = Issue.find :all, :order => sort_clause,
48 @issues = Issue.find :all, :order => sort_clause,
49 :include => [ :assigned_to, :status, :tracker, :project, :priority, :category ],
49 :include => [ :assigned_to, :status, :tracker, :project, :priority, :category ],
50 :conditions => @query.statement,
50 :conditions => @query.statement,
51 :limit => @issue_pages.items_per_page,
51 :limit => @issue_pages.items_per_page,
52 :offset => @issue_pages.current.offset
52 :offset => @issue_pages.current.offset
53 end
53 end
54 respond_to do |format|
54 respond_to do |format|
55 format.html { render :layout => false if request.xhr? }
55 format.html { render :layout => false if request.xhr? }
56 format.atom { render_feed(@issues, :title => l(:label_issue_plural)) }
56 format.atom { render_feed(@issues, :title => l(:label_issue_plural)) }
57 end
57 end
58 end
58 end
59
59
60 def show
60 def show
61 @custom_values = @issue.custom_values.find(:all, :include => :custom_field)
61 @custom_values = @issue.custom_values.find(:all, :include => :custom_field)
62 @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
62 @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
63
63
64 if params[:format]=='pdf'
64 if params[:format]=='pdf'
65 @options_for_rfpdf ||= {}
65 @options_for_rfpdf ||= {}
66 @options_for_rfpdf[:file_name] = "#{@project.identifier}-#{@issue.id}.pdf"
66 @options_for_rfpdf[:file_name] = "#{@project.identifier}-#{@issue.id}.pdf"
67 render :template => 'issues/show.rfpdf', :layout => false
67 render :template => 'issues/show.rfpdf', :layout => false
68 else
68 else
69 @status_options = @issue.status.find_new_statuses_allowed_to(logged_in_user.role_for_project(@project), @issue.tracker) if logged_in_user
69 @status_options = @issue.status.find_new_statuses_allowed_to(logged_in_user.role_for_project(@project), @issue.tracker) if logged_in_user
70 render :template => 'issues/show.rhtml'
70 render :template => 'issues/show.rhtml'
71 end
71 end
72 end
72 end
73
73
74 def edit
74 def edit
75 @priorities = Enumeration::get_values('IPRI')
75 @priorities = Enumeration::get_values('IPRI')
76 if request.get?
76 if request.get?
77 @custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| @issue.custom_values.find_by_custom_field_id(x.id) || CustomValue.new(:custom_field => x, :customized => @issue) }
77 @custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| @issue.custom_values.find_by_custom_field_id(x.id) || CustomValue.new(:custom_field => x, :customized => @issue) }
78 else
78 else
79 begin
79 begin
80 @issue.init_journal(self.logged_in_user)
80 @issue.init_journal(self.logged_in_user)
81 # Retrieve custom fields and values
81 # Retrieve custom fields and values
82 if params["custom_fields"]
82 if params["custom_fields"]
83 @custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue, :value => params["custom_fields"][x.id.to_s]) }
83 @custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue, :value => params["custom_fields"][x.id.to_s]) }
84 @issue.custom_values = @custom_values
84 @issue.custom_values = @custom_values
85 end
85 end
86 @issue.attributes = params[:issue]
86 @issue.attributes = params[:issue]
87 if @issue.save
87 if @issue.save
88 flash[:notice] = l(:notice_successful_update)
88 flash[:notice] = l(:notice_successful_update)
89 redirect_to(params[:back_to] || {:action => 'show', :id => @issue})
89 redirect_to(params[:back_to] || {:action => 'show', :id => @issue})
90 end
90 end
91 rescue ActiveRecord::StaleObjectError
91 rescue ActiveRecord::StaleObjectError
92 # Optimistic locking exception
92 # Optimistic locking exception
93 flash[:error] = l(:notice_locking_conflict)
93 flash[:error] = l(:notice_locking_conflict)
94 end
94 end
95 end
95 end
96 end
96 end
97
97
98 def add_note
98 def add_note
99 journal = @issue.init_journal(User.current, params[:notes])
99 journal = @issue.init_journal(User.current, params[:notes])
100 params[:attachments].each { |file|
100 params[:attachments].each { |file|
101 next unless file.size > 0
101 next unless file.size > 0
102 a = Attachment.create(:container => @issue, :file => file, :author => logged_in_user)
102 a = Attachment.create(:container => @issue, :file => file, :author => logged_in_user)
103 journal.details << JournalDetail.new(:property => 'attachment',
103 journal.details << JournalDetail.new(:property => 'attachment',
104 :prop_key => a.id,
104 :prop_key => a.id,
105 :value => a.filename) unless a.new_record?
105 :value => a.filename) unless a.new_record?
106 } if params[:attachments] and params[:attachments].is_a? Array
106 } if params[:attachments] and params[:attachments].is_a? Array
107 if journal.save
107 if journal.save
108 flash[:notice] = l(:notice_successful_update)
108 flash[:notice] = l(:notice_successful_update)
109 Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
109 Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
110 redirect_to :action => 'show', :id => @issue
110 redirect_to :action => 'show', :id => @issue
111 return
111 return
112 end
112 end
113 show
113 show
114 end
114 end
115
115
116 def change_status
116 def change_status
117 @status_options = @issue.status.find_new_statuses_allowed_to(logged_in_user.role_for_project(@project), @issue.tracker) if logged_in_user
117 @status_options = @issue.status.find_new_statuses_allowed_to(logged_in_user.role_for_project(@project), @issue.tracker) if logged_in_user
118 @new_status = IssueStatus.find(params[:new_status_id])
118 @new_status = IssueStatus.find(params[:new_status_id])
119 if params[:confirm]
119 if params[:confirm]
120 begin
120 begin
121 journal = @issue.init_journal(self.logged_in_user, params[:notes])
121 journal = @issue.init_journal(self.logged_in_user, params[:notes])
122 @issue.status = @new_status
122 @issue.status = @new_status
123 if @issue.update_attributes(params[:issue])
123 if @issue.update_attributes(params[:issue])
124 # Save attachments
124 # Save attachments
125 params[:attachments].each { |file|
125 params[:attachments].each { |file|
126 next unless file.size > 0
126 next unless file.size > 0
127 a = Attachment.create(:container => @issue, :file => file, :author => logged_in_user)
127 a = Attachment.create(:container => @issue, :file => file, :author => logged_in_user)
128 journal.details << JournalDetail.new(:property => 'attachment',
128 journal.details << JournalDetail.new(:property => 'attachment',
129 :prop_key => a.id,
129 :prop_key => a.id,
130 :value => a.filename) unless a.new_record?
130 :value => a.filename) unless a.new_record?
131 } if params[:attachments] and params[:attachments].is_a? Array
131 } if params[:attachments] and params[:attachments].is_a? Array
132
132
133 # Log time
133 # Log time
134 if current_role.allowed_to?(:log_time)
134 if current_role.allowed_to?(:log_time)
135 @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => logged_in_user, :spent_on => Date.today)
135 @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => logged_in_user, :spent_on => Date.today)
136 @time_entry.attributes = params[:time_entry]
136 @time_entry.attributes = params[:time_entry]
137 @time_entry.save
137 @time_entry.save
138 end
138 end
139
139
140 flash[:notice] = l(:notice_successful_update)
140 flash[:notice] = l(:notice_successful_update)
141 Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
141 Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
142 redirect_to :action => 'show', :id => @issue
142 redirect_to :action => 'show', :id => @issue
143 end
143 end
144 rescue ActiveRecord::StaleObjectError
144 rescue ActiveRecord::StaleObjectError
145 # Optimistic locking exception
145 # Optimistic locking exception
146 flash[:error] = l(:notice_locking_conflict)
146 flash[:error] = l(:notice_locking_conflict)
147 end
147 end
148 end
148 end
149 @assignable_to = @project.members.find(:all, :include => :user).collect{ |m| m.user }
149 @assignable_to = @project.members.find(:all, :include => :user).collect{ |m| m.user }
150 @activities = Enumeration::get_values('ACTI')
150 @activities = Enumeration::get_values('ACTI')
151 end
151 end
152
152
153 def destroy
153 def destroy
154 @issue.destroy
154 @issue.destroy
155 redirect_to :controller => 'projects', :action => 'list_issues', :id => @project
155 redirect_to :controller => 'projects', :action => 'list_issues', :id => @project
156 end
156 end
157
157
158 def destroy_attachment
158 def destroy_attachment
159 a = @issue.attachments.find(params[:attachment_id])
159 a = @issue.attachments.find(params[:attachment_id])
160 a.destroy
160 a.destroy
161 journal = @issue.init_journal(self.logged_in_user)
161 journal = @issue.init_journal(self.logged_in_user)
162 journal.details << JournalDetail.new(:property => 'attachment',
162 journal.details << JournalDetail.new(:property => 'attachment',
163 :prop_key => a.id,
163 :prop_key => a.id,
164 :old_value => a.filename)
164 :old_value => a.filename)
165 journal.save
165 journal.save
166 redirect_to :action => 'show', :id => @issue
166 redirect_to :action => 'show', :id => @issue
167 end
167 end
168
168
169 def context_menu
169 def context_menu
170 @priorities = Enumeration.get_values('IPRI').reverse
170 @priorities = Enumeration.get_values('IPRI').reverse
171 @statuses = IssueStatus.find(:all, :order => 'position')
171 @statuses = IssueStatus.find(:all, :order => 'position')
172 @allowed_statuses = @issue.status.find_new_statuses_allowed_to(User.current.role_for_project(@project), @issue.tracker)
172 @allowed_statuses = @issue.status.find_new_statuses_allowed_to(User.current.role_for_project(@project), @issue.tracker)
173 @assignables = @issue.assignable_users
173 @assignables = @issue.assignable_users
174 @assignables << @issue.assigned_to if @issue.assigned_to && !@assignables.include?(@issue.assigned_to)
174 @assignables << @issue.assigned_to if @issue.assigned_to && !@assignables.include?(@issue.assigned_to)
175 @can = {:edit => User.current.allowed_to?(:edit_issues, @project),
175 @can = {:edit => User.current.allowed_to?(:edit_issues, @project),
176 :change_status => User.current.allowed_to?(:change_issue_status, @project),
176 :change_status => User.current.allowed_to?(:change_issue_status, @project),
177 :add => User.current.allowed_to?(:add_issues, @project),
177 :move => User.current.allowed_to?(:move_issues, @project),
178 :move => User.current.allowed_to?(:move_issues, @project),
178 :delete => User.current.allowed_to?(:delete_issues, @project)}
179 :delete => User.current.allowed_to?(:delete_issues, @project)}
179 render :layout => false
180 render :layout => false
180 end
181 end
181
182
182 def preview
183 def preview
183 issue = Issue.find_by_id(params[:id])
184 issue = Issue.find_by_id(params[:id])
184 @attachements = issue.attachments if issue
185 @attachements = issue.attachments if issue
185 @text = params[:issue][:description]
186 @text = params[:issue][:description]
186 render :partial => 'common/preview'
187 render :partial => 'common/preview'
187 end
188 end
188
189
189 private
190 private
190 def find_project
191 def find_project
191 @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
192 @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
192 @project = @issue.project
193 @project = @issue.project
193 rescue ActiveRecord::RecordNotFound
194 rescue ActiveRecord::RecordNotFound
194 render_404
195 render_404
195 end
196 end
196
197
197 # Retrieve query from session or build a new query
198 # Retrieve query from session or build a new query
198 def retrieve_query
199 def retrieve_query
199 if params[:set_filter] or !session[:query] or session[:query].project_id
200 if params[:set_filter] or !session[:query] or session[:query].project_id
200 # Give it a name, required to be valid
201 # Give it a name, required to be valid
201 @query = Query.new(:name => "_", :executed_by => logged_in_user)
202 @query = Query.new(:name => "_", :executed_by => logged_in_user)
202 if params[:fields] and params[:fields].is_a? Array
203 if params[:fields] and params[:fields].is_a? Array
203 params[:fields].each do |field|
204 params[:fields].each do |field|
204 @query.add_filter(field, params[:operators][field], params[:values][field])
205 @query.add_filter(field, params[:operators][field], params[:values][field])
205 end
206 end
206 else
207 else
207 @query.available_filters.keys.each do |field|
208 @query.available_filters.keys.each do |field|
208 @query.add_short_filter(field, params[field]) if params[field]
209 @query.add_short_filter(field, params[field]) if params[field]
209 end
210 end
210 end
211 end
211 session[:query] = @query
212 session[:query] = @query
212 else
213 else
213 @query = session[:query]
214 @query = session[:query]
214 end
215 end
215 end
216 end
216 end
217 end
@@ -1,675 +1,677
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require 'csv'
18 require 'csv'
19
19
20 class ProjectsController < ApplicationController
20 class ProjectsController < ApplicationController
21 layout 'base'
21 layout 'base'
22 before_filter :find_project, :except => [ :index, :list, :add ]
22 before_filter :find_project, :except => [ :index, :list, :add ]
23 before_filter :authorize, :except => [ :index, :list, :add, :archive, :unarchive, :destroy ]
23 before_filter :authorize, :except => [ :index, :list, :add, :archive, :unarchive, :destroy ]
24 before_filter :require_admin, :only => [ :add, :archive, :unarchive, :destroy ]
24 before_filter :require_admin, :only => [ :add, :archive, :unarchive, :destroy ]
25 accept_key_auth :activity, :calendar
25 accept_key_auth :activity, :calendar
26
26
27 cache_sweeper :project_sweeper, :only => [ :add, :edit, :archive, :unarchive, :destroy ]
27 cache_sweeper :project_sweeper, :only => [ :add, :edit, :archive, :unarchive, :destroy ]
28 cache_sweeper :issue_sweeper, :only => [ :add_issue ]
28 cache_sweeper :issue_sweeper, :only => [ :add_issue ]
29 cache_sweeper :version_sweeper, :only => [ :add_version ]
29 cache_sweeper :version_sweeper, :only => [ :add_version ]
30
30
31 helper :sort
31 helper :sort
32 include SortHelper
32 include SortHelper
33 helper :custom_fields
33 helper :custom_fields
34 include CustomFieldsHelper
34 include CustomFieldsHelper
35 helper :ifpdf
35 helper :ifpdf
36 include IfpdfHelper
36 include IfpdfHelper
37 helper :issues
37 helper :issues
38 helper IssuesHelper
38 helper IssuesHelper
39 helper :queries
39 helper :queries
40 include QueriesHelper
40 include QueriesHelper
41 helper :repositories
41 helper :repositories
42 include RepositoriesHelper
42 include RepositoriesHelper
43 include ProjectsHelper
43 include ProjectsHelper
44
44
45 def index
45 def index
46 list
46 list
47 render :action => 'list' unless request.xhr?
47 render :action => 'list' unless request.xhr?
48 end
48 end
49
49
50 # Lists visible projects
50 # Lists visible projects
51 def list
51 def list
52 projects = Project.find :all,
52 projects = Project.find :all,
53 :conditions => Project.visible_by(logged_in_user),
53 :conditions => Project.visible_by(logged_in_user),
54 :include => :parent
54 :include => :parent
55 @project_tree = projects.group_by {|p| p.parent || p}
55 @project_tree = projects.group_by {|p| p.parent || p}
56 @project_tree.each_key {|p| @project_tree[p] -= [p]}
56 @project_tree.each_key {|p| @project_tree[p] -= [p]}
57 end
57 end
58
58
59 # Add a new project
59 # Add a new project
60 def add
60 def add
61 @custom_fields = IssueCustomField.find(:all)
61 @custom_fields = IssueCustomField.find(:all)
62 @root_projects = Project.find(:all, :conditions => "parent_id IS NULL AND status = #{Project::STATUS_ACTIVE}")
62 @root_projects = Project.find(:all, :conditions => "parent_id IS NULL AND status = #{Project::STATUS_ACTIVE}")
63 @project = Project.new(params[:project])
63 @project = Project.new(params[:project])
64 @project.enabled_module_names = Redmine::AccessControl.available_project_modules
64 @project.enabled_module_names = Redmine::AccessControl.available_project_modules
65 if request.get?
65 if request.get?
66 @custom_values = ProjectCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @project) }
66 @custom_values = ProjectCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @project) }
67 else
67 else
68 @project.custom_fields = CustomField.find(params[:custom_field_ids]) if params[:custom_field_ids]
68 @project.custom_fields = CustomField.find(params[:custom_field_ids]) if params[:custom_field_ids]
69 @custom_values = ProjectCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @project, :value => (params[:custom_fields] ? params["custom_fields"][x.id.to_s] : nil)) }
69 @custom_values = ProjectCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @project, :value => (params[:custom_fields] ? params["custom_fields"][x.id.to_s] : nil)) }
70 @project.custom_values = @custom_values
70 @project.custom_values = @custom_values
71 if @project.save
71 if @project.save
72 @project.enabled_module_names = params[:enabled_modules]
72 @project.enabled_module_names = params[:enabled_modules]
73 flash[:notice] = l(:notice_successful_create)
73 flash[:notice] = l(:notice_successful_create)
74 redirect_to :controller => 'admin', :action => 'projects'
74 redirect_to :controller => 'admin', :action => 'projects'
75 end
75 end
76 end
76 end
77 end
77 end
78
78
79 # Show @project
79 # Show @project
80 def show
80 def show
81 @custom_values = @project.custom_values.find(:all, :include => :custom_field)
81 @custom_values = @project.custom_values.find(:all, :include => :custom_field)
82 @members_by_role = @project.members.find(:all, :include => [:user, :role], :order => 'position').group_by {|m| m.role}
82 @members_by_role = @project.members.find(:all, :include => [:user, :role], :order => 'position').group_by {|m| m.role}
83 @subprojects = @project.active_children
83 @subprojects = @project.active_children
84 @news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
84 @news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
85 @trackers = Tracker.find(:all, :order => 'position')
85 @trackers = Tracker.find(:all, :order => 'position')
86 @open_issues_by_tracker = Issue.count(:group => :tracker, :joins => "INNER JOIN #{IssueStatus.table_name} ON #{IssueStatus.table_name}.id = #{Issue.table_name}.status_id", :conditions => ["project_id=? and #{IssueStatus.table_name}.is_closed=?", @project.id, false])
86 @open_issues_by_tracker = Issue.count(:group => :tracker, :joins => "INNER JOIN #{IssueStatus.table_name} ON #{IssueStatus.table_name}.id = #{Issue.table_name}.status_id", :conditions => ["project_id=? and #{IssueStatus.table_name}.is_closed=?", @project.id, false])
87 @total_issues_by_tracker = Issue.count(:group => :tracker, :conditions => ["project_id=?", @project.id])
87 @total_issues_by_tracker = Issue.count(:group => :tracker, :conditions => ["project_id=?", @project.id])
88 @total_hours = @project.time_entries.sum(:hours)
88 @total_hours = @project.time_entries.sum(:hours)
89 @key = User.current.rss_key
89 @key = User.current.rss_key
90 end
90 end
91
91
92 def settings
92 def settings
93 @root_projects = Project::find(:all, :conditions => ["parent_id IS NULL AND status = #{Project::STATUS_ACTIVE} AND id <> ?", @project.id])
93 @root_projects = Project::find(:all, :conditions => ["parent_id IS NULL AND status = #{Project::STATUS_ACTIVE} AND id <> ?", @project.id])
94 @custom_fields = IssueCustomField.find(:all)
94 @custom_fields = IssueCustomField.find(:all)
95 @issue_category ||= IssueCategory.new
95 @issue_category ||= IssueCategory.new
96 @member ||= @project.members.new
96 @member ||= @project.members.new
97 @custom_values ||= ProjectCustomField.find(:all).collect { |x| @project.custom_values.find_by_custom_field_id(x.id) || CustomValue.new(:custom_field => x) }
97 @custom_values ||= ProjectCustomField.find(:all).collect { |x| @project.custom_values.find_by_custom_field_id(x.id) || CustomValue.new(:custom_field => x) }
98 @repository ||= @project.repository
98 @repository ||= @project.repository
99 @wiki ||= @project.wiki
99 @wiki ||= @project.wiki
100 end
100 end
101
101
102 # Edit @project
102 # Edit @project
103 def edit
103 def edit
104 if request.post?
104 if request.post?
105 @project.custom_fields = IssueCustomField.find(params[:custom_field_ids]) if params[:custom_field_ids]
105 @project.custom_fields = IssueCustomField.find(params[:custom_field_ids]) if params[:custom_field_ids]
106 if params[:custom_fields]
106 if params[:custom_fields]
107 @custom_values = ProjectCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @project, :value => params["custom_fields"][x.id.to_s]) }
107 @custom_values = ProjectCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @project, :value => params["custom_fields"][x.id.to_s]) }
108 @project.custom_values = @custom_values
108 @project.custom_values = @custom_values
109 end
109 end
110 @project.attributes = params[:project]
110 @project.attributes = params[:project]
111 if @project.save
111 if @project.save
112 flash[:notice] = l(:notice_successful_update)
112 flash[:notice] = l(:notice_successful_update)
113 redirect_to :action => 'settings', :id => @project
113 redirect_to :action => 'settings', :id => @project
114 else
114 else
115 settings
115 settings
116 render :action => 'settings'
116 render :action => 'settings'
117 end
117 end
118 end
118 end
119 end
119 end
120
120
121 def modules
121 def modules
122 @project.enabled_module_names = params[:enabled_modules]
122 @project.enabled_module_names = params[:enabled_modules]
123 redirect_to :action => 'settings', :id => @project, :tab => 'modules'
123 redirect_to :action => 'settings', :id => @project, :tab => 'modules'
124 end
124 end
125
125
126 def archive
126 def archive
127 @project.archive if request.post? && @project.active?
127 @project.archive if request.post? && @project.active?
128 redirect_to :controller => 'admin', :action => 'projects'
128 redirect_to :controller => 'admin', :action => 'projects'
129 end
129 end
130
130
131 def unarchive
131 def unarchive
132 @project.unarchive if request.post? && !@project.active?
132 @project.unarchive if request.post? && !@project.active?
133 redirect_to :controller => 'admin', :action => 'projects'
133 redirect_to :controller => 'admin', :action => 'projects'
134 end
134 end
135
135
136 # Delete @project
136 # Delete @project
137 def destroy
137 def destroy
138 @project_to_destroy = @project
138 @project_to_destroy = @project
139 if request.post? and params[:confirm]
139 if request.post? and params[:confirm]
140 @project_to_destroy.destroy
140 @project_to_destroy.destroy
141 redirect_to :controller => 'admin', :action => 'projects'
141 redirect_to :controller => 'admin', :action => 'projects'
142 end
142 end
143 # hide project in layout
143 # hide project in layout
144 @project = nil
144 @project = nil
145 end
145 end
146
146
147 # Add a new issue category to @project
147 # Add a new issue category to @project
148 def add_issue_category
148 def add_issue_category
149 @category = @project.issue_categories.build(params[:category])
149 @category = @project.issue_categories.build(params[:category])
150 if request.post? and @category.save
150 if request.post? and @category.save
151 respond_to do |format|
151 respond_to do |format|
152 format.html do
152 format.html do
153 flash[:notice] = l(:notice_successful_create)
153 flash[:notice] = l(:notice_successful_create)
154 redirect_to :action => 'settings', :tab => 'categories', :id => @project
154 redirect_to :action => 'settings', :tab => 'categories', :id => @project
155 end
155 end
156 format.js do
156 format.js do
157 # IE doesn't support the replace_html rjs method for select box options
157 # IE doesn't support the replace_html rjs method for select box options
158 render(:update) {|page| page.replace "issue_category_id",
158 render(:update) {|page| page.replace "issue_category_id",
159 content_tag('select', '<option></option>' + options_from_collection_for_select(@project.issue_categories, 'id', 'name', @category.id), :id => 'issue_category_id', :name => 'issue[category_id]')
159 content_tag('select', '<option></option>' + options_from_collection_for_select(@project.issue_categories, 'id', 'name', @category.id), :id => 'issue_category_id', :name => 'issue[category_id]')
160 }
160 }
161 end
161 end
162 end
162 end
163 end
163 end
164 end
164 end
165
165
166 # Add a new version to @project
166 # Add a new version to @project
167 def add_version
167 def add_version
168 @version = @project.versions.build(params[:version])
168 @version = @project.versions.build(params[:version])
169 if request.post? and @version.save
169 if request.post? and @version.save
170 flash[:notice] = l(:notice_successful_create)
170 flash[:notice] = l(:notice_successful_create)
171 redirect_to :action => 'settings', :tab => 'versions', :id => @project
171 redirect_to :action => 'settings', :tab => 'versions', :id => @project
172 end
172 end
173 end
173 end
174
174
175 # Add a new document to @project
175 # Add a new document to @project
176 def add_document
176 def add_document
177 @document = @project.documents.build(params[:document])
177 @document = @project.documents.build(params[:document])
178 if request.post? and @document.save
178 if request.post? and @document.save
179 # Save the attachments
179 # Save the attachments
180 params[:attachments].each { |a|
180 params[:attachments].each { |a|
181 Attachment.create(:container => @document, :file => a, :author => logged_in_user) unless a.size == 0
181 Attachment.create(:container => @document, :file => a, :author => logged_in_user) unless a.size == 0
182 } if params[:attachments] and params[:attachments].is_a? Array
182 } if params[:attachments] and params[:attachments].is_a? Array
183 flash[:notice] = l(:notice_successful_create)
183 flash[:notice] = l(:notice_successful_create)
184 Mailer.deliver_document_added(@document) if Setting.notified_events.include?('document_added')
184 Mailer.deliver_document_added(@document) if Setting.notified_events.include?('document_added')
185 redirect_to :action => 'list_documents', :id => @project
185 redirect_to :action => 'list_documents', :id => @project
186 end
186 end
187 end
187 end
188
188
189 # Show documents list of @project
189 # Show documents list of @project
190 def list_documents
190 def list_documents
191 @documents = @project.documents.find :all, :include => :category
191 @documents = @project.documents.find :all, :include => :category
192 end
192 end
193
193
194 # Add a new issue to @project
194 # Add a new issue to @project
195 # The new issue will be created from an existing one if copy_from parameter is given
195 def add_issue
196 def add_issue
196 @tracker = Tracker.find(params[:tracker_id])
197 @issue = params[:copy_from] ? Issue.new.copy_from(params[:copy_from]) : Issue.new(params[:issue])
197 @priorities = Enumeration::get_values('IPRI')
198 @issue.project = @project
199 @issue.author = User.current
200 @issue.tracker ||= Tracker.find(params[:tracker_id])
198
201
199 default_status = IssueStatus.default
202 default_status = IssueStatus.default
200 unless default_status
203 unless default_status
201 flash.now[:error] = 'No default issue status defined. Please check your configuration.'
204 flash.now[:error] = 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").'
202 render :nothing => true, :layout => true
205 render :nothing => true, :layout => true
203 return
206 return
204 end
207 end
205 @issue = Issue.new(:project => @project, :tracker => @tracker)
206 @issue.status = default_status
208 @issue.status = default_status
207 @allowed_statuses = ([default_status] + default_status.find_new_statuses_allowed_to(logged_in_user.role_for_project(@project), @issue.tracker))if logged_in_user
209 @allowed_statuses = ([default_status] + default_status.find_new_statuses_allowed_to(logged_in_user.role_for_project(@project), @issue.tracker))if logged_in_user
210
208 if request.get?
211 if request.get?
209 @issue.start_date = Date.today
212 @issue.start_date ||= Date.today
210 @custom_values = @project.custom_fields_for_issues(@tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue) }
213 @custom_values = @issue.custom_values.empty? ?
214 @project.custom_fields_for_issues(@issue.tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue) } :
215 @issue.custom_values
211 else
216 else
212 @issue.attributes = params[:issue]
213
214 requested_status = IssueStatus.find_by_id(params[:issue][:status_id])
217 requested_status = IssueStatus.find_by_id(params[:issue][:status_id])
218 # Check that the user is allowed to apply the requested status
215 @issue.status = (@allowed_statuses.include? requested_status) ? requested_status : default_status
219 @issue.status = (@allowed_statuses.include? requested_status) ? requested_status : default_status
216
220 @custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue, :value => params["custom_fields"][x.id.to_s]) }
217 @issue.author_id = self.logged_in_user.id if self.logged_in_user
218 # Multiple file upload
219 @attachments = []
220 params[:attachments].each { |a|
221 @attachments << Attachment.new(:container => @issue, :file => a, :author => logged_in_user) unless a.size == 0
222 } if params[:attachments] and params[:attachments].is_a? Array
223 @custom_values = @project.custom_fields_for_issues(@tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue, :value => params["custom_fields"][x.id.to_s]) }
224 @issue.custom_values = @custom_values
221 @issue.custom_values = @custom_values
225 if @issue.save
222 if @issue.save
226 @attachments.each(&:save)
223 if params[:attachments] && params[:attachments].is_a?(Array)
224 # Save attachments
225 params[:attachments].each {|a| Attachment.create(:container => @issue, :file => a, :author => User.current) unless a.size == 0}
226 end
227 flash[:notice] = l(:notice_successful_create)
227 flash[:notice] = l(:notice_successful_create)
228 Mailer.deliver_issue_add(@issue) if Setting.notified_events.include?('issue_added')
228 Mailer.deliver_issue_add(@issue) if Setting.notified_events.include?('issue_added')
229 redirect_to :action => 'list_issues', :id => @project
229 redirect_to :action => 'list_issues', :id => @project
230 return
230 end
231 end
231 end
232 end
233 @priorities = Enumeration::get_values('IPRI')
232 end
234 end
233
235
234 # Show filtered/sorted issues list of @project
236 # Show filtered/sorted issues list of @project
235 def list_issues
237 def list_issues
236 sort_init "#{Issue.table_name}.id", "desc"
238 sort_init "#{Issue.table_name}.id", "desc"
237 sort_update
239 sort_update
238
240
239 retrieve_query
241 retrieve_query
240
242
241 @results_per_page_options = [ 15, 25, 50, 100 ]
243 @results_per_page_options = [ 15, 25, 50, 100 ]
242 if params[:per_page] and @results_per_page_options.include? params[:per_page].to_i
244 if params[:per_page] and @results_per_page_options.include? params[:per_page].to_i
243 @results_per_page = params[:per_page].to_i
245 @results_per_page = params[:per_page].to_i
244 session[:results_per_page] = @results_per_page
246 session[:results_per_page] = @results_per_page
245 else
247 else
246 @results_per_page = session[:results_per_page] || 25
248 @results_per_page = session[:results_per_page] || 25
247 end
249 end
248
250
249 if @query.valid?
251 if @query.valid?
250 @issue_count = Issue.count(:include => [:status, :project], :conditions => @query.statement)
252 @issue_count = Issue.count(:include => [:status, :project], :conditions => @query.statement)
251 @issue_pages = Paginator.new self, @issue_count, @results_per_page, params['page']
253 @issue_pages = Paginator.new self, @issue_count, @results_per_page, params['page']
252 @issues = Issue.find :all, :order => sort_clause,
254 @issues = Issue.find :all, :order => sort_clause,
253 :include => [ :assigned_to, :status, :tracker, :project, :priority, :category ],
255 :include => [ :assigned_to, :status, :tracker, :project, :priority, :category ],
254 :conditions => @query.statement,
256 :conditions => @query.statement,
255 :limit => @issue_pages.items_per_page,
257 :limit => @issue_pages.items_per_page,
256 :offset => @issue_pages.current.offset
258 :offset => @issue_pages.current.offset
257 end
259 end
258
260
259 render :layout => false if request.xhr?
261 render :layout => false if request.xhr?
260 end
262 end
261
263
262 # Export filtered/sorted issues list to CSV
264 # Export filtered/sorted issues list to CSV
263 def export_issues_csv
265 def export_issues_csv
264 sort_init "#{Issue.table_name}.id", "desc"
266 sort_init "#{Issue.table_name}.id", "desc"
265 sort_update
267 sort_update
266
268
267 retrieve_query
269 retrieve_query
268 render :action => 'list_issues' and return unless @query.valid?
270 render :action => 'list_issues' and return unless @query.valid?
269
271
270 @issues = Issue.find :all, :order => sort_clause,
272 @issues = Issue.find :all, :order => sort_clause,
271 :include => [ :assigned_to, :author, :status, :tracker, :priority, :project, {:custom_values => :custom_field} ],
273 :include => [ :assigned_to, :author, :status, :tracker, :priority, :project, {:custom_values => :custom_field} ],
272 :conditions => @query.statement,
274 :conditions => @query.statement,
273 :limit => Setting.issues_export_limit.to_i
275 :limit => Setting.issues_export_limit.to_i
274
276
275 ic = Iconv.new(l(:general_csv_encoding), 'UTF-8')
277 ic = Iconv.new(l(:general_csv_encoding), 'UTF-8')
276 export = StringIO.new
278 export = StringIO.new
277 CSV::Writer.generate(export, l(:general_csv_separator)) do |csv|
279 CSV::Writer.generate(export, l(:general_csv_separator)) do |csv|
278 # csv header fields
280 # csv header fields
279 headers = [ "#", l(:field_status),
281 headers = [ "#", l(:field_status),
280 l(:field_project),
282 l(:field_project),
281 l(:field_tracker),
283 l(:field_tracker),
282 l(:field_priority),
284 l(:field_priority),
283 l(:field_subject),
285 l(:field_subject),
284 l(:field_assigned_to),
286 l(:field_assigned_to),
285 l(:field_author),
287 l(:field_author),
286 l(:field_start_date),
288 l(:field_start_date),
287 l(:field_due_date),
289 l(:field_due_date),
288 l(:field_done_ratio),
290 l(:field_done_ratio),
289 l(:field_created_on),
291 l(:field_created_on),
290 l(:field_updated_on)
292 l(:field_updated_on)
291 ]
293 ]
292 for custom_field in @project.all_custom_fields
294 for custom_field in @project.all_custom_fields
293 headers << custom_field.name
295 headers << custom_field.name
294 end
296 end
295 csv << headers.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
297 csv << headers.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
296 # csv lines
298 # csv lines
297 @issues.each do |issue|
299 @issues.each do |issue|
298 fields = [issue.id, issue.status.name,
300 fields = [issue.id, issue.status.name,
299 issue.project.name,
301 issue.project.name,
300 issue.tracker.name,
302 issue.tracker.name,
301 issue.priority.name,
303 issue.priority.name,
302 issue.subject,
304 issue.subject,
303 (issue.assigned_to ? issue.assigned_to.name : ""),
305 (issue.assigned_to ? issue.assigned_to.name : ""),
304 issue.author.name,
306 issue.author.name,
305 issue.start_date ? l_date(issue.start_date) : nil,
307 issue.start_date ? l_date(issue.start_date) : nil,
306 issue.due_date ? l_date(issue.due_date) : nil,
308 issue.due_date ? l_date(issue.due_date) : nil,
307 issue.done_ratio,
309 issue.done_ratio,
308 l_datetime(issue.created_on),
310 l_datetime(issue.created_on),
309 l_datetime(issue.updated_on)
311 l_datetime(issue.updated_on)
310 ]
312 ]
311 for custom_field in @project.all_custom_fields
313 for custom_field in @project.all_custom_fields
312 fields << (show_value issue.custom_value_for(custom_field))
314 fields << (show_value issue.custom_value_for(custom_field))
313 end
315 end
314 csv << fields.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
316 csv << fields.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
315 end
317 end
316 end
318 end
317 export.rewind
319 export.rewind
318 send_data(export.read, :type => 'text/csv; header=present', :filename => 'export.csv')
320 send_data(export.read, :type => 'text/csv; header=present', :filename => 'export.csv')
319 end
321 end
320
322
321 # Export filtered/sorted issues to PDF
323 # Export filtered/sorted issues to PDF
322 def export_issues_pdf
324 def export_issues_pdf
323 sort_init "#{Issue.table_name}.id", "desc"
325 sort_init "#{Issue.table_name}.id", "desc"
324 sort_update
326 sort_update
325
327
326 retrieve_query
328 retrieve_query
327 render :action => 'list_issues' and return unless @query.valid?
329 render :action => 'list_issues' and return unless @query.valid?
328
330
329 @issues = Issue.find :all, :order => sort_clause,
331 @issues = Issue.find :all, :order => sort_clause,
330 :include => [ :author, :status, :tracker, :priority, :project ],
332 :include => [ :author, :status, :tracker, :priority, :project ],
331 :conditions => @query.statement,
333 :conditions => @query.statement,
332 :limit => Setting.issues_export_limit.to_i
334 :limit => Setting.issues_export_limit.to_i
333
335
334 @options_for_rfpdf ||= {}
336 @options_for_rfpdf ||= {}
335 @options_for_rfpdf[:file_name] = "export.pdf"
337 @options_for_rfpdf[:file_name] = "export.pdf"
336 render :layout => false
338 render :layout => false
337 end
339 end
338
340
339 # Bulk edit issues
341 # Bulk edit issues
340 def bulk_edit_issues
342 def bulk_edit_issues
341 if request.post?
343 if request.post?
342 status = params[:status_id].blank? ? nil : IssueStatus.find_by_id(params[:status_id])
344 status = params[:status_id].blank? ? nil : IssueStatus.find_by_id(params[:status_id])
343 priority = params[:priority_id].blank? ? nil : Enumeration.find_by_id(params[:priority_id])
345 priority = params[:priority_id].blank? ? nil : Enumeration.find_by_id(params[:priority_id])
344 assigned_to = params[:assigned_to_id].blank? ? nil : User.find_by_id(params[:assigned_to_id])
346 assigned_to = params[:assigned_to_id].blank? ? nil : User.find_by_id(params[:assigned_to_id])
345 category = params[:category_id].blank? ? nil : @project.issue_categories.find_by_id(params[:category_id])
347 category = params[:category_id].blank? ? nil : @project.issue_categories.find_by_id(params[:category_id])
346 fixed_version = params[:fixed_version_id].blank? ? nil : @project.versions.find_by_id(params[:fixed_version_id])
348 fixed_version = params[:fixed_version_id].blank? ? nil : @project.versions.find_by_id(params[:fixed_version_id])
347 issues = @project.issues.find_all_by_id(params[:issue_ids])
349 issues = @project.issues.find_all_by_id(params[:issue_ids])
348 unsaved_issue_ids = []
350 unsaved_issue_ids = []
349 issues.each do |issue|
351 issues.each do |issue|
350 journal = issue.init_journal(User.current, params[:notes])
352 journal = issue.init_journal(User.current, params[:notes])
351 issue.priority = priority if priority
353 issue.priority = priority if priority
352 issue.assigned_to = assigned_to if assigned_to || params[:assigned_to_id] == 'none'
354 issue.assigned_to = assigned_to if assigned_to || params[:assigned_to_id] == 'none'
353 issue.category = category if category
355 issue.category = category if category
354 issue.fixed_version = fixed_version if fixed_version
356 issue.fixed_version = fixed_version if fixed_version
355 issue.start_date = params[:start_date] unless params[:start_date].blank?
357 issue.start_date = params[:start_date] unless params[:start_date].blank?
356 issue.due_date = params[:due_date] unless params[:due_date].blank?
358 issue.due_date = params[:due_date] unless params[:due_date].blank?
357 issue.done_ratio = params[:done_ratio] unless params[:done_ratio].blank?
359 issue.done_ratio = params[:done_ratio] unless params[:done_ratio].blank?
358 # Don't save any change to the issue if the user is not authorized to apply the requested status
360 # Don't save any change to the issue if the user is not authorized to apply the requested status
359 if (status.nil? || (issue.status.new_status_allowed_to?(status, current_role, issue.tracker) && issue.status = status)) && issue.save
361 if (status.nil? || (issue.status.new_status_allowed_to?(status, current_role, issue.tracker) && issue.status = status)) && issue.save
360 # Send notification for each issue (if changed)
362 # Send notification for each issue (if changed)
361 Mailer.deliver_issue_edit(journal) if journal.details.any? && Setting.notified_events.include?('issue_updated')
363 Mailer.deliver_issue_edit(journal) if journal.details.any? && Setting.notified_events.include?('issue_updated')
362 else
364 else
363 # Keep unsaved issue ids to display them in flash error
365 # Keep unsaved issue ids to display them in flash error
364 unsaved_issue_ids << issue.id
366 unsaved_issue_ids << issue.id
365 end
367 end
366 end
368 end
367 if unsaved_issue_ids.empty?
369 if unsaved_issue_ids.empty?
368 flash[:notice] = l(:notice_successful_update) unless issues.empty?
370 flash[:notice] = l(:notice_successful_update) unless issues.empty?
369 else
371 else
370 flash[:error] = l(:notice_failed_to_save_issues, unsaved_issue_ids.size, issues.size, '#' + unsaved_issue_ids.join(', #'))
372 flash[:error] = l(:notice_failed_to_save_issues, unsaved_issue_ids.size, issues.size, '#' + unsaved_issue_ids.join(', #'))
371 end
373 end
372 redirect_to :action => 'list_issues', :id => @project
374 redirect_to :action => 'list_issues', :id => @project
373 return
375 return
374 end
376 end
375 if current_role && User.current.allowed_to?(:change_issue_status, @project)
377 if current_role && User.current.allowed_to?(:change_issue_status, @project)
376 # Find potential statuses the user could be allowed to switch issues to
378 # Find potential statuses the user could be allowed to switch issues to
377 @available_statuses = Workflow.find(:all, :include => :new_status,
379 @available_statuses = Workflow.find(:all, :include => :new_status,
378 :conditions => {:role_id => current_role.id}).collect(&:new_status).compact.uniq
380 :conditions => {:role_id => current_role.id}).collect(&:new_status).compact.uniq
379 end
381 end
380 render :update do |page|
382 render :update do |page|
381 page.hide 'query_form'
383 page.hide 'query_form'
382 page.replace_html 'bulk-edit', :partial => 'issues/bulk_edit_form'
384 page.replace_html 'bulk-edit', :partial => 'issues/bulk_edit_form'
383 end
385 end
384 end
386 end
385
387
386 def move_issues
388 def move_issues
387 @issues = @project.issues.find(params[:issue_ids]) if params[:issue_ids]
389 @issues = @project.issues.find(params[:issue_ids]) if params[:issue_ids]
388 redirect_to :action => 'list_issues', :id => @project and return unless @issues
390 redirect_to :action => 'list_issues', :id => @project and return unless @issues
389 @projects = []
391 @projects = []
390 # find projects to which the user is allowed to move the issue
392 # find projects to which the user is allowed to move the issue
391 User.current.memberships.each {|m| @projects << m.project if m.role.allowed_to?(:controller => 'projects', :action => 'move_issues')}
393 User.current.memberships.each {|m| @projects << m.project if m.role.allowed_to?(:controller => 'projects', :action => 'move_issues')}
392 # issue can be moved to any tracker
394 # issue can be moved to any tracker
393 @trackers = Tracker.find(:all)
395 @trackers = Tracker.find(:all)
394 if request.post? and params[:new_project_id] and params[:new_tracker_id]
396 if request.post? and params[:new_project_id] and params[:new_tracker_id]
395 new_project = Project.find_by_id(params[:new_project_id])
397 new_project = Project.find_by_id(params[:new_project_id])
396 new_tracker = Tracker.find_by_id(params[:new_tracker_id])
398 new_tracker = Tracker.find_by_id(params[:new_tracker_id])
397 @issues.each do |i|
399 @issues.each do |i|
398 if new_project && i.project_id != new_project.id
400 if new_project && i.project_id != new_project.id
399 # issue is moved to another project
401 # issue is moved to another project
400 i.category = nil
402 i.category = nil
401 i.fixed_version = nil
403 i.fixed_version = nil
402 # delete issue relations
404 # delete issue relations
403 i.relations_from.clear
405 i.relations_from.clear
404 i.relations_to.clear
406 i.relations_to.clear
405 i.project = new_project
407 i.project = new_project
406 end
408 end
407 if new_tracker
409 if new_tracker
408 i.tracker = new_tracker
410 i.tracker = new_tracker
409 end
411 end
410 i.save
412 i.save
411 end
413 end
412 flash[:notice] = l(:notice_successful_update)
414 flash[:notice] = l(:notice_successful_update)
413 redirect_to :action => 'list_issues', :id => @project
415 redirect_to :action => 'list_issues', :id => @project
414 end
416 end
415 end
417 end
416
418
417 # Add a news to @project
419 # Add a news to @project
418 def add_news
420 def add_news
419 @news = News.new(:project => @project)
421 @news = News.new(:project => @project)
420 if request.post?
422 if request.post?
421 @news.attributes = params[:news]
423 @news.attributes = params[:news]
422 @news.author_id = self.logged_in_user.id if self.logged_in_user
424 @news.author_id = self.logged_in_user.id if self.logged_in_user
423 if @news.save
425 if @news.save
424 flash[:notice] = l(:notice_successful_create)
426 flash[:notice] = l(:notice_successful_create)
425 Mailer.deliver_news_added(@news) if Setting.notified_events.include?('news_added')
427 Mailer.deliver_news_added(@news) if Setting.notified_events.include?('news_added')
426 redirect_to :action => 'list_news', :id => @project
428 redirect_to :action => 'list_news', :id => @project
427 end
429 end
428 end
430 end
429 end
431 end
430
432
431 # Show news list of @project
433 # Show news list of @project
432 def list_news
434 def list_news
433 @news_pages, @newss = paginate :news, :per_page => 10, :conditions => ["project_id=?", @project.id], :include => :author, :order => "#{News.table_name}.created_on DESC"
435 @news_pages, @newss = paginate :news, :per_page => 10, :conditions => ["project_id=?", @project.id], :include => :author, :order => "#{News.table_name}.created_on DESC"
434
436
435 respond_to do |format|
437 respond_to do |format|
436 format.html { render :layout => false if request.xhr? }
438 format.html { render :layout => false if request.xhr? }
437 format.atom { render_feed(@newss, :title => "#{@project.name}: #{l(:label_news_plural)}") }
439 format.atom { render_feed(@newss, :title => "#{@project.name}: #{l(:label_news_plural)}") }
438 end
440 end
439 end
441 end
440
442
441 def add_file
443 def add_file
442 if request.post?
444 if request.post?
443 @version = @project.versions.find_by_id(params[:version_id])
445 @version = @project.versions.find_by_id(params[:version_id])
444 # Save the attachments
446 # Save the attachments
445 @attachments = []
447 @attachments = []
446 params[:attachments].each { |file|
448 params[:attachments].each { |file|
447 next unless file.size > 0
449 next unless file.size > 0
448 a = Attachment.create(:container => @version, :file => file, :author => logged_in_user)
450 a = Attachment.create(:container => @version, :file => file, :author => logged_in_user)
449 @attachments << a unless a.new_record?
451 @attachments << a unless a.new_record?
450 } if params[:attachments] and params[:attachments].is_a? Array
452 } if params[:attachments] and params[:attachments].is_a? Array
451 Mailer.deliver_attachments_added(@attachments) if !@attachments.empty? && Setting.notified_events.include?('file_added')
453 Mailer.deliver_attachments_added(@attachments) if !@attachments.empty? && Setting.notified_events.include?('file_added')
452 redirect_to :controller => 'projects', :action => 'list_files', :id => @project
454 redirect_to :controller => 'projects', :action => 'list_files', :id => @project
453 end
455 end
454 @versions = @project.versions.sort
456 @versions = @project.versions.sort
455 end
457 end
456
458
457 def list_files
459 def list_files
458 @versions = @project.versions.sort
460 @versions = @project.versions.sort
459 end
461 end
460
462
461 # Show changelog for @project
463 # Show changelog for @project
462 def changelog
464 def changelog
463 @trackers = Tracker.find(:all, :conditions => ["is_in_chlog=?", true], :order => 'position')
465 @trackers = Tracker.find(:all, :conditions => ["is_in_chlog=?", true], :order => 'position')
464 retrieve_selected_tracker_ids(@trackers)
466 retrieve_selected_tracker_ids(@trackers)
465 @versions = @project.versions.sort
467 @versions = @project.versions.sort
466 end
468 end
467
469
468 def roadmap
470 def roadmap
469 @trackers = Tracker.find(:all, :conditions => ["is_in_roadmap=?", true], :order => 'position')
471 @trackers = Tracker.find(:all, :conditions => ["is_in_roadmap=?", true], :order => 'position')
470 retrieve_selected_tracker_ids(@trackers)
472 retrieve_selected_tracker_ids(@trackers)
471 @versions = @project.versions.sort
473 @versions = @project.versions.sort
472 @versions = @versions.select {|v| !v.completed? } unless params[:completed]
474 @versions = @versions.select {|v| !v.completed? } unless params[:completed]
473 end
475 end
474
476
475 def activity
477 def activity
476 if params[:year] and params[:year].to_i > 1900
478 if params[:year] and params[:year].to_i > 1900
477 @year = params[:year].to_i
479 @year = params[:year].to_i
478 if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13
480 if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13
479 @month = params[:month].to_i
481 @month = params[:month].to_i
480 end
482 end
481 end
483 end
482 @year ||= Date.today.year
484 @year ||= Date.today.year
483 @month ||= Date.today.month
485 @month ||= Date.today.month
484
486
485 case params[:format]
487 case params[:format]
486 when 'atom'
488 when 'atom'
487 # 30 last days
489 # 30 last days
488 @date_from = Date.today - 30
490 @date_from = Date.today - 30
489 @date_to = Date.today + 1
491 @date_to = Date.today + 1
490 else
492 else
491 # current month
493 # current month
492 @date_from = Date.civil(@year, @month, 1)
494 @date_from = Date.civil(@year, @month, 1)
493 @date_to = @date_from >> 1
495 @date_to = @date_from >> 1
494 end
496 end
495
497
496 @event_types = %w(issues news files documents wiki_pages changesets)
498 @event_types = %w(issues news files documents wiki_pages changesets)
497 @event_types.delete('wiki_pages') unless @project.wiki
499 @event_types.delete('wiki_pages') unless @project.wiki
498 @event_types.delete('changesets') unless @project.repository
500 @event_types.delete('changesets') unless @project.repository
499 # only show what the user is allowed to view
501 # only show what the user is allowed to view
500 @event_types = @event_types.select {|o| User.current.allowed_to?("view_#{o}".to_sym, @project)}
502 @event_types = @event_types.select {|o| User.current.allowed_to?("view_#{o}".to_sym, @project)}
501
503
502 @scope = @event_types.select {|t| params["show_#{t}"]}
504 @scope = @event_types.select {|t| params["show_#{t}"]}
503 # default events if none is specified in parameters
505 # default events if none is specified in parameters
504 @scope = (@event_types - %w(wiki_pages))if @scope.empty?
506 @scope = (@event_types - %w(wiki_pages))if @scope.empty?
505
507
506 @events = []
508 @events = []
507
509
508 if @scope.include?('issues')
510 if @scope.include?('issues')
509 @events += @project.issues.find(:all, :include => [:author, :tracker], :conditions => ["#{Issue.table_name}.created_on>=? and #{Issue.table_name}.created_on<=?", @date_from, @date_to] )
511 @events += @project.issues.find(:all, :include => [:author, :tracker], :conditions => ["#{Issue.table_name}.created_on>=? and #{Issue.table_name}.created_on<=?", @date_from, @date_to] )
510 end
512 end
511
513
512 if @scope.include?('news')
514 if @scope.include?('news')
513 @events += @project.news.find(:all, :conditions => ["#{News.table_name}.created_on>=? and #{News.table_name}.created_on<=?", @date_from, @date_to], :include => :author )
515 @events += @project.news.find(:all, :conditions => ["#{News.table_name}.created_on>=? and #{News.table_name}.created_on<=?", @date_from, @date_to], :include => :author )
514 end
516 end
515
517
516 if @scope.include?('files')
518 if @scope.include?('files')
517 @events += Attachment.find(:all, :select => "#{Attachment.table_name}.*", :joins => "LEFT JOIN #{Version.table_name} ON #{Version.table_name}.id = #{Attachment.table_name}.container_id", :conditions => ["#{Attachment.table_name}.container_type='Version' and #{Version.table_name}.project_id=? and #{Attachment.table_name}.created_on>=? and #{Attachment.table_name}.created_on<=?", @project.id, @date_from, @date_to], :include => :author )
519 @events += Attachment.find(:all, :select => "#{Attachment.table_name}.*", :joins => "LEFT JOIN #{Version.table_name} ON #{Version.table_name}.id = #{Attachment.table_name}.container_id", :conditions => ["#{Attachment.table_name}.container_type='Version' and #{Version.table_name}.project_id=? and #{Attachment.table_name}.created_on>=? and #{Attachment.table_name}.created_on<=?", @project.id, @date_from, @date_to], :include => :author )
518 end
520 end
519
521
520 if @scope.include?('documents')
522 if @scope.include?('documents')
521 @events += @project.documents.find(:all, :conditions => ["#{Document.table_name}.created_on>=? and #{Document.table_name}.created_on<=?", @date_from, @date_to] )
523 @events += @project.documents.find(:all, :conditions => ["#{Document.table_name}.created_on>=? and #{Document.table_name}.created_on<=?", @date_from, @date_to] )
522 @events += Attachment.find(:all, :select => "attachments.*", :joins => "LEFT JOIN #{Document.table_name} ON #{Document.table_name}.id = #{Attachment.table_name}.container_id", :conditions => ["#{Attachment.table_name}.container_type='Document' and #{Document.table_name}.project_id=? and #{Attachment.table_name}.created_on>=? and #{Attachment.table_name}.created_on<=?", @project.id, @date_from, @date_to], :include => :author )
524 @events += Attachment.find(:all, :select => "attachments.*", :joins => "LEFT JOIN #{Document.table_name} ON #{Document.table_name}.id = #{Attachment.table_name}.container_id", :conditions => ["#{Attachment.table_name}.container_type='Document' and #{Document.table_name}.project_id=? and #{Attachment.table_name}.created_on>=? and #{Attachment.table_name}.created_on<=?", @project.id, @date_from, @date_to], :include => :author )
523 end
525 end
524
526
525 if @scope.include?('wiki_pages')
527 if @scope.include?('wiki_pages')
526 select = "#{WikiContent.versioned_table_name}.updated_on, #{WikiContent.versioned_table_name}.comments, " +
528 select = "#{WikiContent.versioned_table_name}.updated_on, #{WikiContent.versioned_table_name}.comments, " +
527 "#{WikiContent.versioned_table_name}.#{WikiContent.version_column}, #{WikiPage.table_name}.title, " +
529 "#{WikiContent.versioned_table_name}.#{WikiContent.version_column}, #{WikiPage.table_name}.title, " +
528 "#{WikiContent.versioned_table_name}.page_id, #{WikiContent.versioned_table_name}.author_id, " +
530 "#{WikiContent.versioned_table_name}.page_id, #{WikiContent.versioned_table_name}.author_id, " +
529 "#{WikiContent.versioned_table_name}.id"
531 "#{WikiContent.versioned_table_name}.id"
530 joins = "LEFT JOIN #{WikiPage.table_name} ON #{WikiPage.table_name}.id = #{WikiContent.versioned_table_name}.page_id " +
532 joins = "LEFT JOIN #{WikiPage.table_name} ON #{WikiPage.table_name}.id = #{WikiContent.versioned_table_name}.page_id " +
531 "LEFT JOIN #{Wiki.table_name} ON #{Wiki.table_name}.id = #{WikiPage.table_name}.wiki_id "
533 "LEFT JOIN #{Wiki.table_name} ON #{Wiki.table_name}.id = #{WikiPage.table_name}.wiki_id "
532 conditions = ["#{Wiki.table_name}.project_id = ? AND #{WikiContent.versioned_table_name}.updated_on BETWEEN ? AND ?",
534 conditions = ["#{Wiki.table_name}.project_id = ? AND #{WikiContent.versioned_table_name}.updated_on BETWEEN ? AND ?",
533 @project.id, @date_from, @date_to]
535 @project.id, @date_from, @date_to]
534
536
535 @events += WikiContent.versioned_class.find(:all, :select => select, :joins => joins, :conditions => conditions)
537 @events += WikiContent.versioned_class.find(:all, :select => select, :joins => joins, :conditions => conditions)
536 end
538 end
537
539
538 if @scope.include?('changesets')
540 if @scope.include?('changesets')
539 @events += @project.repository.changesets.find(:all, :conditions => ["#{Changeset.table_name}.committed_on BETWEEN ? AND ?", @date_from, @date_to])
541 @events += @project.repository.changesets.find(:all, :conditions => ["#{Changeset.table_name}.committed_on BETWEEN ? AND ?", @date_from, @date_to])
540 end
542 end
541
543
542 @events_by_day = @events.group_by(&:event_date)
544 @events_by_day = @events.group_by(&:event_date)
543
545
544 respond_to do |format|
546 respond_to do |format|
545 format.html { render :layout => false if request.xhr? }
547 format.html { render :layout => false if request.xhr? }
546 format.atom { render_feed(@events, :title => "#{@project.name}: #{l(:label_activity)}") }
548 format.atom { render_feed(@events, :title => "#{@project.name}: #{l(:label_activity)}") }
547 end
549 end
548 end
550 end
549
551
550 def calendar
552 def calendar
551 @trackers = Tracker.find(:all, :order => 'position')
553 @trackers = Tracker.find(:all, :order => 'position')
552 retrieve_selected_tracker_ids(@trackers)
554 retrieve_selected_tracker_ids(@trackers)
553
555
554 if params[:year] and params[:year].to_i > 1900
556 if params[:year] and params[:year].to_i > 1900
555 @year = params[:year].to_i
557 @year = params[:year].to_i
556 if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13
558 if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13
557 @month = params[:month].to_i
559 @month = params[:month].to_i
558 end
560 end
559 end
561 end
560 @year ||= Date.today.year
562 @year ||= Date.today.year
561 @month ||= Date.today.month
563 @month ||= Date.today.month
562 @calendar = Redmine::Helpers::Calendar.new(Date.civil(@year, @month, 1), current_language, :month)
564 @calendar = Redmine::Helpers::Calendar.new(Date.civil(@year, @month, 1), current_language, :month)
563
565
564 events = []
566 events = []
565 @project.issues_with_subprojects(params[:with_subprojects]) do
567 @project.issues_with_subprojects(params[:with_subprojects]) do
566 events += Issue.find(:all,
568 events += Issue.find(:all,
567 :include => [:tracker, :status, :assigned_to, :priority, :project],
569 :include => [:tracker, :status, :assigned_to, :priority, :project],
568 :conditions => ["((start_date BETWEEN ? AND ?) OR (due_date BETWEEN ? AND ?)) AND #{Issue.table_name}.tracker_id IN (#{@selected_tracker_ids.join(',')})", @calendar.startdt, @calendar.enddt, @calendar.startdt, @calendar.enddt]
570 :conditions => ["((start_date BETWEEN ? AND ?) OR (due_date BETWEEN ? AND ?)) AND #{Issue.table_name}.tracker_id IN (#{@selected_tracker_ids.join(',')})", @calendar.startdt, @calendar.enddt, @calendar.startdt, @calendar.enddt]
569 ) unless @selected_tracker_ids.empty?
571 ) unless @selected_tracker_ids.empty?
570 end
572 end
571 events += @project.versions.find(:all, :conditions => ["effective_date BETWEEN ? AND ?", @calendar.startdt, @calendar.enddt])
573 events += @project.versions.find(:all, :conditions => ["effective_date BETWEEN ? AND ?", @calendar.startdt, @calendar.enddt])
572 @calendar.events = events
574 @calendar.events = events
573
575
574 render :layout => false if request.xhr?
576 render :layout => false if request.xhr?
575 end
577 end
576
578
577 def gantt
579 def gantt
578 @trackers = Tracker.find(:all, :order => 'position')
580 @trackers = Tracker.find(:all, :order => 'position')
579 retrieve_selected_tracker_ids(@trackers)
581 retrieve_selected_tracker_ids(@trackers)
580
582
581 if params[:year] and params[:year].to_i >0
583 if params[:year] and params[:year].to_i >0
582 @year_from = params[:year].to_i
584 @year_from = params[:year].to_i
583 if params[:month] and params[:month].to_i >=1 and params[:month].to_i <= 12
585 if params[:month] and params[:month].to_i >=1 and params[:month].to_i <= 12
584 @month_from = params[:month].to_i
586 @month_from = params[:month].to_i
585 else
587 else
586 @month_from = 1
588 @month_from = 1
587 end
589 end
588 else
590 else
589 @month_from ||= Date.today.month
591 @month_from ||= Date.today.month
590 @year_from ||= Date.today.year
592 @year_from ||= Date.today.year
591 end
593 end
592
594
593 zoom = (params[:zoom] || User.current.pref[:gantt_zoom]).to_i
595 zoom = (params[:zoom] || User.current.pref[:gantt_zoom]).to_i
594 @zoom = (zoom > 0 && zoom < 5) ? zoom : 2
596 @zoom = (zoom > 0 && zoom < 5) ? zoom : 2
595 months = (params[:months] || User.current.pref[:gantt_months]).to_i
597 months = (params[:months] || User.current.pref[:gantt_months]).to_i
596 @months = (months > 0 && months < 25) ? months : 6
598 @months = (months > 0 && months < 25) ? months : 6
597
599
598 # Save gantt paramters as user preference (zoom and months count)
600 # Save gantt paramters as user preference (zoom and months count)
599 if (User.current.logged? && (@zoom != User.current.pref[:gantt_zoom] || @months != User.current.pref[:gantt_months]))
601 if (User.current.logged? && (@zoom != User.current.pref[:gantt_zoom] || @months != User.current.pref[:gantt_months]))
600 User.current.pref[:gantt_zoom], User.current.pref[:gantt_months] = @zoom, @months
602 User.current.pref[:gantt_zoom], User.current.pref[:gantt_months] = @zoom, @months
601 User.current.preference.save
603 User.current.preference.save
602 end
604 end
603
605
604 @date_from = Date.civil(@year_from, @month_from, 1)
606 @date_from = Date.civil(@year_from, @month_from, 1)
605 @date_to = (@date_from >> @months) - 1
607 @date_to = (@date_from >> @months) - 1
606
608
607 @events = []
609 @events = []
608 @project.issues_with_subprojects(params[:with_subprojects]) do
610 @project.issues_with_subprojects(params[:with_subprojects]) do
609 @events += Issue.find(:all,
611 @events += Issue.find(:all,
610 :order => "start_date, due_date",
612 :order => "start_date, due_date",
611 :include => [:tracker, :status, :assigned_to, :priority, :project],
613 :include => [:tracker, :status, :assigned_to, :priority, :project],
612 :conditions => ["(((start_date>=? and start_date<=?) or (due_date>=? and due_date<=?) or (start_date<? and due_date>?)) and start_date is not null and due_date is not null and #{Issue.table_name}.tracker_id in (#{@selected_tracker_ids.join(',')}))", @date_from, @date_to, @date_from, @date_to, @date_from, @date_to]
614 :conditions => ["(((start_date>=? and start_date<=?) or (due_date>=? and due_date<=?) or (start_date<? and due_date>?)) and start_date is not null and due_date is not null and #{Issue.table_name}.tracker_id in (#{@selected_tracker_ids.join(',')}))", @date_from, @date_to, @date_from, @date_to, @date_from, @date_to]
613 ) unless @selected_tracker_ids.empty?
615 ) unless @selected_tracker_ids.empty?
614 end
616 end
615 @events += @project.versions.find(:all, :conditions => ["effective_date BETWEEN ? AND ?", @date_from, @date_to])
617 @events += @project.versions.find(:all, :conditions => ["effective_date BETWEEN ? AND ?", @date_from, @date_to])
616 @events.sort! {|x,y| x.start_date <=> y.start_date }
618 @events.sort! {|x,y| x.start_date <=> y.start_date }
617
619
618 if params[:format]=='pdf'
620 if params[:format]=='pdf'
619 @options_for_rfpdf ||= {}
621 @options_for_rfpdf ||= {}
620 @options_for_rfpdf[:file_name] = "#{@project.identifier}-gantt.pdf"
622 @options_for_rfpdf[:file_name] = "#{@project.identifier}-gantt.pdf"
621 render :template => "projects/gantt.rfpdf", :layout => false
623 render :template => "projects/gantt.rfpdf", :layout => false
622 elsif params[:format]=='png' && respond_to?('gantt_image')
624 elsif params[:format]=='png' && respond_to?('gantt_image')
623 image = gantt_image(@events, @date_from, @months, @zoom)
625 image = gantt_image(@events, @date_from, @months, @zoom)
624 image.format = 'PNG'
626 image.format = 'PNG'
625 send_data(image.to_blob, :disposition => 'inline', :type => 'image/png', :filename => "#{@project.identifier}-gantt.png")
627 send_data(image.to_blob, :disposition => 'inline', :type => 'image/png', :filename => "#{@project.identifier}-gantt.png")
626 else
628 else
627 render :template => "projects/gantt.rhtml"
629 render :template => "projects/gantt.rhtml"
628 end
630 end
629 end
631 end
630
632
631 private
633 private
632 # Find project of id params[:id]
634 # Find project of id params[:id]
633 # if not found, redirect to project list
635 # if not found, redirect to project list
634 # Used as a before_filter
636 # Used as a before_filter
635 def find_project
637 def find_project
636 @project = Project.find(params[:id])
638 @project = Project.find(params[:id])
637 rescue ActiveRecord::RecordNotFound
639 rescue ActiveRecord::RecordNotFound
638 render_404
640 render_404
639 end
641 end
640
642
641 def retrieve_selected_tracker_ids(selectable_trackers)
643 def retrieve_selected_tracker_ids(selectable_trackers)
642 if ids = params[:tracker_ids]
644 if ids = params[:tracker_ids]
643 @selected_tracker_ids = (ids.is_a? Array) ? ids.collect { |id| id.to_i.to_s } : ids.split('/').collect { |id| id.to_i.to_s }
645 @selected_tracker_ids = (ids.is_a? Array) ? ids.collect { |id| id.to_i.to_s } : ids.split('/').collect { |id| id.to_i.to_s }
644 else
646 else
645 @selected_tracker_ids = selectable_trackers.collect {|t| t.id.to_s }
647 @selected_tracker_ids = selectable_trackers.collect {|t| t.id.to_s }
646 end
648 end
647 end
649 end
648
650
649 # Retrieve query from session or build a new query
651 # Retrieve query from session or build a new query
650 def retrieve_query
652 def retrieve_query
651 if params[:query_id]
653 if params[:query_id]
652 @query = @project.queries.find(params[:query_id])
654 @query = @project.queries.find(params[:query_id])
653 @query.executed_by = logged_in_user
655 @query.executed_by = logged_in_user
654 session[:query] = @query
656 session[:query] = @query
655 else
657 else
656 if params[:set_filter] or !session[:query] or session[:query].project_id != @project.id
658 if params[:set_filter] or !session[:query] or session[:query].project_id != @project.id
657 # Give it a name, required to be valid
659 # Give it a name, required to be valid
658 @query = Query.new(:name => "_", :executed_by => logged_in_user)
660 @query = Query.new(:name => "_", :executed_by => logged_in_user)
659 @query.project = @project
661 @query.project = @project
660 if params[:fields] and params[:fields].is_a? Array
662 if params[:fields] and params[:fields].is_a? Array
661 params[:fields].each do |field|
663 params[:fields].each do |field|
662 @query.add_filter(field, params[:operators][field], params[:values][field])
664 @query.add_filter(field, params[:operators][field], params[:values][field])
663 end
665 end
664 else
666 else
665 @query.available_filters.keys.each do |field|
667 @query.available_filters.keys.each do |field|
666 @query.add_short_filter(field, params[field]) if params[field]
668 @query.add_short_filter(field, params[field]) if params[field]
667 end
669 end
668 end
670 end
669 session[:query] = @query
671 session[:query] = @query
670 else
672 else
671 @query = session[:query]
673 @query = session[:query]
672 end
674 end
673 end
675 end
674 end
676 end
675 end
677 end
@@ -1,184 +1,191
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class Issue < ActiveRecord::Base
18 class Issue < ActiveRecord::Base
19 belongs_to :project
19 belongs_to :project
20 belongs_to :tracker
20 belongs_to :tracker
21 belongs_to :status, :class_name => 'IssueStatus', :foreign_key => 'status_id'
21 belongs_to :status, :class_name => 'IssueStatus', :foreign_key => 'status_id'
22 belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
22 belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
23 belongs_to :assigned_to, :class_name => 'User', :foreign_key => 'assigned_to_id'
23 belongs_to :assigned_to, :class_name => 'User', :foreign_key => 'assigned_to_id'
24 belongs_to :fixed_version, :class_name => 'Version', :foreign_key => 'fixed_version_id'
24 belongs_to :fixed_version, :class_name => 'Version', :foreign_key => 'fixed_version_id'
25 belongs_to :priority, :class_name => 'Enumeration', :foreign_key => 'priority_id'
25 belongs_to :priority, :class_name => 'Enumeration', :foreign_key => 'priority_id'
26 belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id'
26 belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id'
27
27
28 has_many :journals, :as => :journalized, :dependent => :destroy
28 has_many :journals, :as => :journalized, :dependent => :destroy
29 has_many :attachments, :as => :container, :dependent => :destroy
29 has_many :attachments, :as => :container, :dependent => :destroy
30 has_many :time_entries, :dependent => :nullify
30 has_many :time_entries, :dependent => :nullify
31 has_many :custom_values, :dependent => :delete_all, :as => :customized
31 has_many :custom_values, :dependent => :delete_all, :as => :customized
32 has_many :custom_fields, :through => :custom_values
32 has_many :custom_fields, :through => :custom_values
33 has_and_belongs_to_many :changesets, :order => "revision ASC"
33 has_and_belongs_to_many :changesets, :order => "revision ASC"
34
34
35 has_many :relations_from, :class_name => 'IssueRelation', :foreign_key => 'issue_from_id', :dependent => :delete_all
35 has_many :relations_from, :class_name => 'IssueRelation', :foreign_key => 'issue_from_id', :dependent => :delete_all
36 has_many :relations_to, :class_name => 'IssueRelation', :foreign_key => 'issue_to_id', :dependent => :delete_all
36 has_many :relations_to, :class_name => 'IssueRelation', :foreign_key => 'issue_to_id', :dependent => :delete_all
37
37
38 acts_as_watchable
38 acts_as_watchable
39 acts_as_searchable :columns => ['subject', 'description'], :with => {:journal => :issue}
39 acts_as_searchable :columns => ['subject', 'description'], :with => {:journal => :issue}
40 acts_as_event :title => Proc.new {|o| "#{o.tracker.name} ##{o.id}: #{o.subject}"},
40 acts_as_event :title => Proc.new {|o| "#{o.tracker.name} ##{o.id}: #{o.subject}"},
41 :url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.id}}
41 :url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.id}}
42
42
43 validates_presence_of :subject, :description, :priority, :tracker, :author, :status
43 validates_presence_of :subject, :description, :priority, :tracker, :author, :status
44 validates_length_of :subject, :maximum => 255
44 validates_length_of :subject, :maximum => 255
45 validates_inclusion_of :done_ratio, :in => 0..100
45 validates_inclusion_of :done_ratio, :in => 0..100
46 validates_numericality_of :estimated_hours, :allow_nil => true
46 validates_numericality_of :estimated_hours, :allow_nil => true
47 validates_associated :custom_values, :on => :update
47 validates_associated :custom_values, :on => :update
48
48
49 def after_initialize
49 def after_initialize
50 if new_record?
50 if new_record?
51 # set default values for new records only
51 # set default values for new records only
52 self.status ||= IssueStatus.default
52 self.status ||= IssueStatus.default
53 self.priority ||= Enumeration.default('IPRI')
53 self.priority ||= Enumeration.default('IPRI')
54 end
54 end
55 end
55 end
56
56
57 def copy_from(arg)
58 issue = arg.is_a?(Issue) ? arg : Issue.find(arg)
59 self.attributes = issue.attributes.dup
60 self.custom_values = issue.custom_values.collect {|v| v.clone}
61 self
62 end
63
57 def priority_id=(pid)
64 def priority_id=(pid)
58 self.priority = nil
65 self.priority = nil
59 write_attribute(:priority_id, pid)
66 write_attribute(:priority_id, pid)
60 end
67 end
61
68
62 def validate
69 def validate
63 if self.due_date.nil? && @attributes['due_date'] && !@attributes['due_date'].empty?
70 if self.due_date.nil? && @attributes['due_date'] && !@attributes['due_date'].empty?
64 errors.add :due_date, :activerecord_error_not_a_date
71 errors.add :due_date, :activerecord_error_not_a_date
65 end
72 end
66
73
67 if self.due_date and self.start_date and self.due_date < self.start_date
74 if self.due_date and self.start_date and self.due_date < self.start_date
68 errors.add :due_date, :activerecord_error_greater_than_start_date
75 errors.add :due_date, :activerecord_error_greater_than_start_date
69 end
76 end
70
77
71 if start_date && soonest_start && start_date < soonest_start
78 if start_date && soonest_start && start_date < soonest_start
72 errors.add :start_date, :activerecord_error_invalid
79 errors.add :start_date, :activerecord_error_invalid
73 end
80 end
74 end
81 end
75
82
76 def before_create
83 def before_create
77 # default assignment based on category
84 # default assignment based on category
78 if assigned_to.nil? && category && category.assigned_to
85 if assigned_to.nil? && category && category.assigned_to
79 self.assigned_to = category.assigned_to
86 self.assigned_to = category.assigned_to
80 end
87 end
81 end
88 end
82
89
83 def before_save
90 def before_save
84 if @current_journal
91 if @current_journal
85 # attributes changes
92 # attributes changes
86 (Issue.column_names - %w(id description)).each {|c|
93 (Issue.column_names - %w(id description)).each {|c|
87 @current_journal.details << JournalDetail.new(:property => 'attr',
94 @current_journal.details << JournalDetail.new(:property => 'attr',
88 :prop_key => c,
95 :prop_key => c,
89 :old_value => @issue_before_change.send(c),
96 :old_value => @issue_before_change.send(c),
90 :value => send(c)) unless send(c)==@issue_before_change.send(c)
97 :value => send(c)) unless send(c)==@issue_before_change.send(c)
91 }
98 }
92 # custom fields changes
99 # custom fields changes
93 custom_values.each {|c|
100 custom_values.each {|c|
94 next if (@custom_values_before_change[c.custom_field_id]==c.value ||
101 next if (@custom_values_before_change[c.custom_field_id]==c.value ||
95 (@custom_values_before_change[c.custom_field_id].blank? && c.value.blank?))
102 (@custom_values_before_change[c.custom_field_id].blank? && c.value.blank?))
96 @current_journal.details << JournalDetail.new(:property => 'cf',
103 @current_journal.details << JournalDetail.new(:property => 'cf',
97 :prop_key => c.custom_field_id,
104 :prop_key => c.custom_field_id,
98 :old_value => @custom_values_before_change[c.custom_field_id],
105 :old_value => @custom_values_before_change[c.custom_field_id],
99 :value => c.value)
106 :value => c.value)
100 }
107 }
101 @current_journal.save
108 @current_journal.save
102 end
109 end
103 # Save the issue even if the journal is not saved (because empty)
110 # Save the issue even if the journal is not saved (because empty)
104 true
111 true
105 end
112 end
106
113
107 def after_save
114 def after_save
108 # Update start/due dates of following issues
115 # Update start/due dates of following issues
109 relations_from.each(&:set_issue_to_dates)
116 relations_from.each(&:set_issue_to_dates)
110
117
111 # Close duplicates if the issue was closed
118 # Close duplicates if the issue was closed
112 if @issue_before_change && !@issue_before_change.closed? && self.closed?
119 if @issue_before_change && !@issue_before_change.closed? && self.closed?
113 duplicates.each do |duplicate|
120 duplicates.each do |duplicate|
114 # Don't re-close it if it's already closed
121 # Don't re-close it if it's already closed
115 next if duplicate.closed?
122 next if duplicate.closed?
116 # Same user and notes
123 # Same user and notes
117 duplicate.init_journal(@current_journal.user, @current_journal.notes)
124 duplicate.init_journal(@current_journal.user, @current_journal.notes)
118 duplicate.update_attribute :status, self.status
125 duplicate.update_attribute :status, self.status
119 end
126 end
120 end
127 end
121 end
128 end
122
129
123 def custom_value_for(custom_field)
130 def custom_value_for(custom_field)
124 self.custom_values.each {|v| return v if v.custom_field_id == custom_field.id }
131 self.custom_values.each {|v| return v if v.custom_field_id == custom_field.id }
125 return nil
132 return nil
126 end
133 end
127
134
128 def init_journal(user, notes = "")
135 def init_journal(user, notes = "")
129 @current_journal ||= Journal.new(:journalized => self, :user => user, :notes => notes)
136 @current_journal ||= Journal.new(:journalized => self, :user => user, :notes => notes)
130 @issue_before_change = self.clone
137 @issue_before_change = self.clone
131 @custom_values_before_change = {}
138 @custom_values_before_change = {}
132 self.custom_values.each {|c| @custom_values_before_change.store c.custom_field_id, c.value }
139 self.custom_values.each {|c| @custom_values_before_change.store c.custom_field_id, c.value }
133 @current_journal
140 @current_journal
134 end
141 end
135
142
136 # Return true if the issue is closed, otherwise false
143 # Return true if the issue is closed, otherwise false
137 def closed?
144 def closed?
138 self.status.is_closed?
145 self.status.is_closed?
139 end
146 end
140
147
141 # Users the issue can be assigned to
148 # Users the issue can be assigned to
142 def assignable_users
149 def assignable_users
143 project.assignable_users
150 project.assignable_users
144 end
151 end
145
152
146 # Returns the mail adresses of users that should be notified for the issue
153 # Returns the mail adresses of users that should be notified for the issue
147 def recipients
154 def recipients
148 recipients = project.recipients
155 recipients = project.recipients
149 # Author and assignee are always notified
156 # Author and assignee are always notified
150 recipients << author.mail if author
157 recipients << author.mail if author
151 recipients << assigned_to.mail if assigned_to
158 recipients << assigned_to.mail if assigned_to
152 recipients.compact.uniq
159 recipients.compact.uniq
153 end
160 end
154
161
155 def spent_hours
162 def spent_hours
156 @spent_hours ||= time_entries.sum(:hours) || 0
163 @spent_hours ||= time_entries.sum(:hours) || 0
157 end
164 end
158
165
159 def relations
166 def relations
160 (relations_from + relations_to).sort
167 (relations_from + relations_to).sort
161 end
168 end
162
169
163 def all_dependent_issues
170 def all_dependent_issues
164 dependencies = []
171 dependencies = []
165 relations_from.each do |relation|
172 relations_from.each do |relation|
166 dependencies << relation.issue_to
173 dependencies << relation.issue_to
167 dependencies += relation.issue_to.all_dependent_issues
174 dependencies += relation.issue_to.all_dependent_issues
168 end
175 end
169 dependencies
176 dependencies
170 end
177 end
171
178
172 # Returns an array of the duplicate issues
179 # Returns an array of the duplicate issues
173 def duplicates
180 def duplicates
174 relations.select {|r| r.relation_type == IssueRelation::TYPE_DUPLICATES}.collect {|r| r.other_issue(self)}
181 relations.select {|r| r.relation_type == IssueRelation::TYPE_DUPLICATES}.collect {|r| r.other_issue(self)}
175 end
182 end
176
183
177 def duration
184 def duration
178 (start_date && due_date) ? due_date - start_date : 0
185 (start_date && due_date) ? due_date - start_date : 0
179 end
186 end
180
187
181 def soonest_start
188 def soonest_start
182 @soonest_start ||= relations_to.collect{|relation| relation.successor_soonest_start}.compact.min
189 @soonest_start ||= relations_to.collect{|relation| relation.successor_soonest_start}.compact.min
183 end
190 end
184 end
191 end
@@ -1,38 +1,40
1 <% back_to = url_for(:controller => 'projects', :action => 'list_issues', :id => @project) %>
1 <% back_to = url_for(:controller => 'projects', :action => 'list_issues', :id => @project) %>
2 <ul>
2 <ul>
3 <li><%= context_menu_link l(:button_edit), {:controller => 'issues', :action => 'edit', :id => @issue},
3 <li><%= context_menu_link l(:button_edit), {:controller => 'issues', :action => 'edit', :id => @issue},
4 :class => 'icon-edit', :disabled => !@can[:edit] %></li>
4 :class => 'icon-edit', :disabled => !@can[:edit] %></li>
5 <li class="folder">
5 <li class="folder">
6 <a href="#" class="submenu" onclick="return false;"><%= l(:field_status) %></a>
6 <a href="#" class="submenu" onclick="return false;"><%= l(:field_status) %></a>
7 <ul>
7 <ul>
8 <% @statuses.each do |s| %>
8 <% @statuses.each do |s| %>
9 <li><%= context_menu_link s.name, {:controller => 'issues', :action => 'change_status', :id => @issue, :new_status_id => s},
9 <li><%= context_menu_link s.name, {:controller => 'issues', :action => 'change_status', :id => @issue, :new_status_id => s},
10 :selected => (s == @issue.status), :disabled => !(@can[:change_status] && @allowed_statuses.include?(s)) %></li>
10 :selected => (s == @issue.status), :disabled => !(@can[:change_status] && @allowed_statuses.include?(s)) %></li>
11 <% end %>
11 <% end %>
12 </ul>
12 </ul>
13 </li>
13 </li>
14 <li class="folder">
14 <li class="folder">
15 <a href="#" class="submenu"><%= l(:field_priority) %></a>
15 <a href="#" class="submenu"><%= l(:field_priority) %></a>
16 <ul>
16 <ul>
17 <% @priorities.each do |p| %>
17 <% @priorities.each do |p| %>
18 <li><%= context_menu_link p.name, {:controller => 'issues', :action => 'edit', :id => @issue, 'issue[priority_id]' => p, :back_to => back_to}, :method => :post,
18 <li><%= context_menu_link p.name, {:controller => 'issues', :action => 'edit', :id => @issue, 'issue[priority_id]' => p, :back_to => back_to}, :method => :post,
19 :selected => (p == @issue.priority), :disabled => !@can[:edit] %></li>
19 :selected => (p == @issue.priority), :disabled => !@can[:edit] %></li>
20 <% end %>
20 <% end %>
21 </ul>
21 </ul>
22 </li>
22 </li>
23 <li class="folder">
23 <li class="folder">
24 <a href="#" class="submenu"><%= l(:field_assigned_to) %></a>
24 <a href="#" class="submenu"><%= l(:field_assigned_to) %></a>
25 <ul>
25 <ul>
26 <% @assignables.each do |u| %>
26 <% @assignables.each do |u| %>
27 <li><%= context_menu_link u.name, {:controller => 'issues', :action => 'edit', :id => @issue, 'issue[assigned_to_id]' => u, :back_to => back_to}, :method => :post,
27 <li><%= context_menu_link u.name, {:controller => 'issues', :action => 'edit', :id => @issue, 'issue[assigned_to_id]' => u, :back_to => back_to}, :method => :post,
28 :selected => (u == @issue.assigned_to), :disabled => !(@can[:edit] || @can[:change_status]) %></li>
28 :selected => (u == @issue.assigned_to), :disabled => !(@can[:edit] || @can[:change_status]) %></li>
29 <% end %>
29 <% end %>
30 <li><%= context_menu_link l(:label_nobody), {:controller => 'issues', :action => 'edit', :id => @issue, 'issue[assigned_to_id]' => '', :back_to => back_to}, :method => :post,
30 <li><%= context_menu_link l(:label_nobody), {:controller => 'issues', :action => 'edit', :id => @issue, 'issue[assigned_to_id]' => '', :back_to => back_to}, :method => :post,
31 :selected => @issue.assigned_to.nil?, :disabled => !(@can[:edit] || @can[:change_status]) %></li>
31 :selected => @issue.assigned_to.nil?, :disabled => !(@can[:edit] || @can[:change_status]) %></li>
32 </ul>
32 </ul>
33 </li>
33 </li>
34 <li><%= context_menu_link l(:button_copy), {:controller => 'projects', :action => 'add_issue', :id => @project, :copy_from => @issue},
35 :class => 'icon-copy', :disabled => !@can[:add] %></li>
34 <li><%= context_menu_link l(:button_move), {:controller => 'projects', :action => 'move_issues', :id => @project, "issue_ids[]" => @issue.id },
36 <li><%= context_menu_link l(:button_move), {:controller => 'projects', :action => 'move_issues', :id => @project, "issue_ids[]" => @issue.id },
35 :class => 'icon-move', :disabled => !@can[:move] %>
37 :class => 'icon-move', :disabled => !@can[:move] %>
36 <li><%= context_menu_link l(:button_delete), {:controller => 'issues', :action => 'destroy', :id => @issue},
38 <li><%= context_menu_link l(:button_delete), {:controller => 'issues', :action => 'destroy', :id => @issue},
37 :method => :post, :confirm => l(:text_are_you_sure), :class => 'icon-del', :disabled => !@can[:delete] %></li>
39 :method => :post, :confirm => l(:text_are_you_sure), :class => 'icon-del', :disabled => !@can[:delete] %></li>
38 </ul>
40 </ul>
@@ -1,122 +1,123
1 <div class="contextual">
1 <div class="contextual">
2 <%= show_and_goto_link(l(:label_add_note), 'add-note', :class => 'icon icon-note') if authorize_for('issues', 'add_note') %>
2 <%= show_and_goto_link(l(:label_add_note), 'add-note', :class => 'icon icon-note') if authorize_for('issues', 'add_note') %>
3 <%= link_to_if_authorized l(:button_edit), {:controller => 'issues', :action => 'edit', :id => @issue}, :class => 'icon icon-edit', :accesskey => accesskey(:edit) %>
3 <%= link_to_if_authorized l(:button_edit), {:controller => 'issues', :action => 'edit', :id => @issue}, :class => 'icon icon-edit', :accesskey => accesskey(:edit) %>
4 <%= link_to_if_authorized l(:button_log_time), {:controller => 'timelog', :action => 'edit', :issue_id => @issue}, :class => 'icon icon-time' %>
4 <%= link_to_if_authorized l(:button_log_time), {:controller => 'timelog', :action => 'edit', :issue_id => @issue}, :class => 'icon icon-time' %>
5 <%= watcher_tag(@issue, User.current) %>
5 <%= watcher_tag(@issue, User.current) %>
6 <%= link_to_if_authorized l(:button_copy), {:controller => 'projects', :action => 'add_issue', :id => @project, :copy_from => @issue }, :class => 'icon icon-copy' %>
6 <%= link_to_if_authorized l(:button_move), {:controller => 'projects', :action => 'move_issues', :id => @project, "issue_ids[]" => @issue.id }, :class => 'icon icon-move' %>
7 <%= link_to_if_authorized l(:button_move), {:controller => 'projects', :action => 'move_issues', :id => @project, "issue_ids[]" => @issue.id }, :class => 'icon icon-move' %>
7 <%= link_to_if_authorized l(:button_delete), {:controller => 'issues', :action => 'destroy', :id => @issue}, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-del' %>
8 <%= link_to_if_authorized l(:button_delete), {:controller => 'issues', :action => 'destroy', :id => @issue}, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-del' %>
8 </div>
9 </div>
9
10
10 <h2><%= @issue.tracker.name %> #<%= @issue.id %></h2>
11 <h2><%= @issue.tracker.name %> #<%= @issue.id %></h2>
11
12
12 <div class="issue">
13 <div class="issue">
13 <h3><%=h @issue.subject %></h3>
14 <h3><%=h @issue.subject %></h3>
14 <p class="author">
15 <p class="author">
15 <%= authoring @issue.created_on, @issue.author %>.
16 <%= authoring @issue.created_on, @issue.author %>.
16 <%= l(:label_updated_time, distance_of_time_in_words(Time.now, @issue.updated_on)) if @issue.created_on != @issue.updated_on %>.
17 <%= l(:label_updated_time, distance_of_time_in_words(Time.now, @issue.updated_on)) if @issue.created_on != @issue.updated_on %>.
17 </p>
18 </p>
18
19
19 <table width="100%">
20 <table width="100%">
20 <tr>
21 <tr>
21 <td style="width:15%"><b><%=l(:field_status)%> :</b></td><td style="width:35%"><%= @issue.status.name %></td>
22 <td style="width:15%"><b><%=l(:field_status)%> :</b></td><td style="width:35%"><%= @issue.status.name %></td>
22 <td style="width:15%"><b><%=l(:field_start_date)%> :</b></td><td style="width:35%"><%= format_date(@issue.start_date) %></td>
23 <td style="width:15%"><b><%=l(:field_start_date)%> :</b></td><td style="width:35%"><%= format_date(@issue.start_date) %></td>
23 </tr>
24 </tr>
24 <tr>
25 <tr>
25 <td><b><%=l(:field_priority)%> :</b></td><td><%= @issue.priority.name %></td>
26 <td><b><%=l(:field_priority)%> :</b></td><td><%= @issue.priority.name %></td>
26 <td><b><%=l(:field_due_date)%> :</b></td><td><%= format_date(@issue.due_date) %></td>
27 <td><b><%=l(:field_due_date)%> :</b></td><td><%= format_date(@issue.due_date) %></td>
27 </tr>
28 </tr>
28 <tr>
29 <tr>
29 <td><b><%=l(:field_assigned_to)%> :</b></td><td><%= @issue.assigned_to ? link_to_user(@issue.assigned_to) : "-" %></td>
30 <td><b><%=l(:field_assigned_to)%> :</b></td><td><%= @issue.assigned_to ? link_to_user(@issue.assigned_to) : "-" %></td>
30 <td><b><%=l(:field_done_ratio)%> :</b></td><td><%= @issue.done_ratio %> %</td>
31 <td><b><%=l(:field_done_ratio)%> :</b></td><td><%= @issue.done_ratio %> %</td>
31 </tr>
32 </tr>
32 <tr>
33 <tr>
33 <td><b><%=l(:field_category)%> :</b></td><td><%=h @issue.category ? @issue.category.name : "-" %></td>
34 <td><b><%=l(:field_category)%> :</b></td><td><%=h @issue.category ? @issue.category.name : "-" %></td>
34 <% if User.current.allowed_to?(:view_time_entries, @project) %>
35 <% if User.current.allowed_to?(:view_time_entries, @project) %>
35 <td><b><%=l(:label_spent_time)%> :</b></td>
36 <td><b><%=l(:label_spent_time)%> :</b></td>
36 <td><%= @issue.spent_hours > 0 ? (link_to lwr(:label_f_hour, @issue.spent_hours), {:controller => 'timelog', :action => 'details', :issue_id => @issue}, :class => 'icon icon-time') : "-" %></td>
37 <td><%= @issue.spent_hours > 0 ? (link_to lwr(:label_f_hour, @issue.spent_hours), {:controller => 'timelog', :action => 'details', :issue_id => @issue}, :class => 'icon icon-time') : "-" %></td>
37 <% end %>
38 <% end %>
38 </tr>
39 </tr>
39 <tr>
40 <tr>
40 <td><b><%=l(:field_fixed_version)%> :</b></td><td><%= @issue.fixed_version ? link_to_version(@issue.fixed_version) : "-" %></td>
41 <td><b><%=l(:field_fixed_version)%> :</b></td><td><%= @issue.fixed_version ? link_to_version(@issue.fixed_version) : "-" %></td>
41 <% if @issue.estimated_hours %>
42 <% if @issue.estimated_hours %>
42 <td><b><%=l(:field_estimated_hours)%> :</b></td><td><%= lwr(:label_f_hour, @issue.estimated_hours) %></td>
43 <td><b><%=l(:field_estimated_hours)%> :</b></td><td><%= lwr(:label_f_hour, @issue.estimated_hours) %></td>
43 <% end %>
44 <% end %>
44 </tr>
45 </tr>
45 <tr>
46 <tr>
46 <% n = 0
47 <% n = 0
47 for custom_value in @custom_values %>
48 for custom_value in @custom_values %>
48 <td valign="top"><b><%= custom_value.custom_field.name %> :</b></td><td valign="top"><%= simple_format(h(show_value(custom_value))) %></td>
49 <td valign="top"><b><%= custom_value.custom_field.name %> :</b></td><td valign="top"><%= simple_format(h(show_value(custom_value))) %></td>
49 <% n = n + 1
50 <% n = n + 1
50 if (n > 1)
51 if (n > 1)
51 n = 0 %>
52 n = 0 %>
52 </tr><tr>
53 </tr><tr>
53 <%end
54 <%end
54 end %>
55 end %>
55 </tr>
56 </tr>
56 </table>
57 </table>
57 <hr />
58 <hr />
58
59
59 <% if @issue.changesets.any? %>
60 <% if @issue.changesets.any? %>
60 <div style="float:right;">
61 <div style="float:right;">
61 <em><%= l(:label_revision_plural) %>: <%= @issue.changesets.collect{|changeset| link_to(changeset.revision, :controller => 'repositories', :action => 'revision', :id => @project, :rev => changeset.revision)}.join(", ") %></em>
62 <em><%= l(:label_revision_plural) %>: <%= @issue.changesets.collect{|changeset| link_to(changeset.revision, :controller => 'repositories', :action => 'revision', :id => @project, :rev => changeset.revision)}.join(", ") %></em>
62 </div>
63 </div>
63 <% end %>
64 <% end %>
64
65
65 <p><strong><%=l(:field_description)%></strong></p>
66 <p><strong><%=l(:field_description)%></strong></p>
66 <%= textilizable @issue.description, :attachments => @issue.attachments %>
67 <%= textilizable @issue.description, :attachments => @issue.attachments %>
67
68
68 <% if @issue.attachments.any? %>
69 <% if @issue.attachments.any? %>
69 <%= link_to_attachments @issue.attachments, :delete_url => (authorize_for('issues', 'destroy_attachment') ? {:controller => 'issues', :action => 'destroy_attachment', :id => @issue} : nil) %>
70 <%= link_to_attachments @issue.attachments, :delete_url => (authorize_for('issues', 'destroy_attachment') ? {:controller => 'issues', :action => 'destroy_attachment', :id => @issue} : nil) %>
70 <% end %>
71 <% end %>
71
72
72 <% if authorize_for('issue_relations', 'new') || @issue.relations.any? %>
73 <% if authorize_for('issue_relations', 'new') || @issue.relations.any? %>
73 <hr />
74 <hr />
74 <div id="relations">
75 <div id="relations">
75 <%= render :partial => 'relations' %>
76 <%= render :partial => 'relations' %>
76 </div>
77 </div>
77 <% end %>
78 <% end %>
78
79
79 </div>
80 </div>
80
81
81 <% if authorize_for('issues', 'change_status') and @status_options and !@status_options.empty? %>
82 <% if authorize_for('issues', 'change_status') and @status_options and !@status_options.empty? %>
82 <% form_tag({:controller => 'issues', :action => 'change_status', :id => @issue}) do %>
83 <% form_tag({:controller => 'issues', :action => 'change_status', :id => @issue}) do %>
83 <p><%=l(:label_change_status)%> :
84 <p><%=l(:label_change_status)%> :
84 <select name="new_status_id">
85 <select name="new_status_id">
85 <%= options_from_collection_for_select @status_options, "id", "name" %>
86 <%= options_from_collection_for_select @status_options, "id", "name" %>
86 </select>
87 </select>
87 <%= submit_tag l(:button_change) %></p>
88 <%= submit_tag l(:button_change) %></p>
88 <% end %>
89 <% end %>
89 <% end %>
90 <% end %>
90
91
91 <% if @journals.any? %>
92 <% if @journals.any? %>
92 <div id="history">
93 <div id="history">
93 <h3><%=l(:label_history)%></h3>
94 <h3><%=l(:label_history)%></h3>
94 <%= render :partial => 'history', :locals => { :journals => @journals } %>
95 <%= render :partial => 'history', :locals => { :journals => @journals } %>
95 </div>
96 </div>
96 <% end %>
97 <% end %>
97
98
98 <% if authorize_for('issues', 'add_note') %>
99 <% if authorize_for('issues', 'add_note') %>
99 <a name="add-note-anchor"></a>
100 <a name="add-note-anchor"></a>
100 <div id="add-note" class="box" style="display:none;">
101 <div id="add-note" class="box" style="display:none;">
101 <h3><%= l(:label_add_note) %></h3>
102 <h3><%= l(:label_add_note) %></h3>
102 <% form_tag({:controller => 'issues', :action => 'add_note', :id => @issue}, :class => "tabular", :multipart => true) do %>
103 <% form_tag({:controller => 'issues', :action => 'add_note', :id => @issue}, :class => "tabular", :multipart => true) do %>
103 <p><label for="notes"><%=l(:field_notes)%></label>
104 <p><label for="notes"><%=l(:field_notes)%></label>
104 <%= text_area_tag 'notes', '', :cols => 60, :rows => 10, :class => 'wiki-edit' %></p>
105 <%= text_area_tag 'notes', '', :cols => 60, :rows => 10, :class => 'wiki-edit' %></p>
105 <%= wikitoolbar_for 'notes' %>
106 <%= wikitoolbar_for 'notes' %>
106 <%= render :partial => 'attachments/form' %>
107 <%= render :partial => 'attachments/form' %>
107 <%= submit_tag l(:button_add) %>
108 <%= submit_tag l(:button_add) %>
108 <%= toggle_link l(:button_cancel), 'add-note' %>
109 <%= toggle_link l(:button_cancel), 'add-note' %>
109 <% end %>
110 <% end %>
110 </div>
111 </div>
111 <% end %>
112 <% end %>
112
113
113 <div class="contextual">
114 <div class="contextual">
114 <%= l(:label_export_to) %><%= link_to 'PDF', {:format => 'pdf'}, :class => 'icon icon-pdf' %>
115 <%= l(:label_export_to) %><%= link_to 'PDF', {:format => 'pdf'}, :class => 'icon icon-pdf' %>
115 </div>
116 </div>
116 &nbsp;
117 &nbsp;
117
118
118 <% set_html_title "#{@issue.tracker.name} ##{@issue.id}: #{@issue.subject}" %>
119 <% set_html_title "#{@issue.tracker.name} ##{@issue.id}: #{@issue.subject}" %>
119
120
120 <% content_for :sidebar do %>
121 <% content_for :sidebar do %>
121 <%= render :partial => 'issues/sidebar' %>
122 <%= render :partial => 'issues/sidebar' %>
122 <% end %>
123 <% end %>
@@ -1,19 +1,19
1 <h2><%=l(:label_issue_new)%>: <%= @tracker.name %></h2>
1 <h2><%=l(:label_issue_new)%>: <%= @issue.tracker %></h2>
2
2
3 <% labelled_tabular_form_for :issue, @issue,
3 <% labelled_tabular_form_for :issue, @issue,
4 :url => {:action => 'add_issue'},
4 :url => {:action => 'add_issue'},
5 :html => {:multipart => true, :id => 'issue-form'} do |f| %>
5 :html => {:multipart => true, :id => 'issue-form'} do |f| %>
6 <%= hidden_field_tag 'tracker_id', @tracker.id %>
6 <%= f.hidden_field :tracker_id %>
7 <%= render :partial => 'issues/form', :locals => {:f => f} %>
7 <%= render :partial => 'issues/form', :locals => {:f => f} %>
8 <%= submit_tag l(:button_create) %>
8 <%= submit_tag l(:button_create) %>
9 <%= link_to_remote l(:label_preview),
9 <%= link_to_remote l(:label_preview),
10 { :url => { :controller => 'issues', :action => 'preview', :id => @issue },
10 { :url => { :controller => 'issues', :action => 'preview', :id => @issue },
11 :method => 'post',
11 :method => 'post',
12 :update => 'preview',
12 :update => 'preview',
13 :with => "Form.serialize('issue-form')",
13 :with => "Form.serialize('issue-form')",
14 :complete => "location.href='#preview-top'"
14 :complete => "location.href='#preview-top'"
15 }, :accesskey => accesskey(:preview) %>
15 }, :accesskey => accesskey(:preview) %>
16 <% end %>
16 <% end %>
17
17
18 <a name="preview-top"></a>
18 <a name="preview-top"></a>
19 <div id="preview" class="wiki"></div>
19 <div id="preview" class="wiki"></div>
@@ -1,535 +1,536
1 _gloc_rule_default: '|n| n==1 ? "" : "_plural" '
1 _gloc_rule_default: '|n| n==1 ? "" : "_plural" '
2
2
3 actionview_datehelper_select_day_prefix:
3 actionview_datehelper_select_day_prefix:
4 actionview_datehelper_select_month_names: January,February,March,April,May,June,July,August,September,October,November,December
4 actionview_datehelper_select_month_names: January,February,March,April,May,June,July,August,September,October,November,December
5 actionview_datehelper_select_month_names_abbr: Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec
5 actionview_datehelper_select_month_names_abbr: Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec
6 actionview_datehelper_select_month_prefix:
6 actionview_datehelper_select_month_prefix:
7 actionview_datehelper_select_year_prefix:
7 actionview_datehelper_select_year_prefix:
8 actionview_datehelper_time_in_words_day: 1 day
8 actionview_datehelper_time_in_words_day: 1 day
9 actionview_datehelper_time_in_words_day_plural: %d days
9 actionview_datehelper_time_in_words_day_plural: %d days
10 actionview_datehelper_time_in_words_hour_about: about an hour
10 actionview_datehelper_time_in_words_hour_about: about an hour
11 actionview_datehelper_time_in_words_hour_about_plural: about %d hours
11 actionview_datehelper_time_in_words_hour_about_plural: about %d hours
12 actionview_datehelper_time_in_words_hour_about_single: about an hour
12 actionview_datehelper_time_in_words_hour_about_single: about an hour
13 actionview_datehelper_time_in_words_minute: 1 minute
13 actionview_datehelper_time_in_words_minute: 1 minute
14 actionview_datehelper_time_in_words_minute_half: half a minute
14 actionview_datehelper_time_in_words_minute_half: half a minute
15 actionview_datehelper_time_in_words_minute_less_than: less than a minute
15 actionview_datehelper_time_in_words_minute_less_than: less than a minute
16 actionview_datehelper_time_in_words_minute_plural: %d minutes
16 actionview_datehelper_time_in_words_minute_plural: %d minutes
17 actionview_datehelper_time_in_words_minute_single: 1 minute
17 actionview_datehelper_time_in_words_minute_single: 1 minute
18 actionview_datehelper_time_in_words_second_less_than: less than a second
18 actionview_datehelper_time_in_words_second_less_than: less than a second
19 actionview_datehelper_time_in_words_second_less_than_plural: less than %d seconds
19 actionview_datehelper_time_in_words_second_less_than_plural: less than %d seconds
20 actionview_instancetag_blank_option: Please select
20 actionview_instancetag_blank_option: Please select
21
21
22 activerecord_error_inclusion: is not included in the list
22 activerecord_error_inclusion: is not included in the list
23 activerecord_error_exclusion: is reserved
23 activerecord_error_exclusion: is reserved
24 activerecord_error_invalid: is invalid
24 activerecord_error_invalid: is invalid
25 activerecord_error_confirmation: doesn't match confirmation
25 activerecord_error_confirmation: doesn't match confirmation
26 activerecord_error_accepted: must be accepted
26 activerecord_error_accepted: must be accepted
27 activerecord_error_empty: can't be empty
27 activerecord_error_empty: can't be empty
28 activerecord_error_blank: can't be blank
28 activerecord_error_blank: can't be blank
29 activerecord_error_too_long: is too long
29 activerecord_error_too_long: is too long
30 activerecord_error_too_short: is too short
30 activerecord_error_too_short: is too short
31 activerecord_error_wrong_length: is the wrong length
31 activerecord_error_wrong_length: is the wrong length
32 activerecord_error_taken: has already been taken
32 activerecord_error_taken: has already been taken
33 activerecord_error_not_a_number: is not a number
33 activerecord_error_not_a_number: is not a number
34 activerecord_error_not_a_date: is not a valid date
34 activerecord_error_not_a_date: is not a valid date
35 activerecord_error_greater_than_start_date: must be greater than start date
35 activerecord_error_greater_than_start_date: must be greater than start date
36 activerecord_error_not_same_project: doesn't belong to the same project
36 activerecord_error_not_same_project: doesn't belong to the same project
37 activerecord_error_circular_dependency: This relation would create a circular dependency
37 activerecord_error_circular_dependency: This relation would create a circular dependency
38
38
39 general_fmt_age: %d yr
39 general_fmt_age: %d yr
40 general_fmt_age_plural: %d yrs
40 general_fmt_age_plural: %d yrs
41 general_fmt_date: %%m/%%d/%%Y
41 general_fmt_date: %%m/%%d/%%Y
42 general_fmt_datetime: %%m/%%d/%%Y %%I:%%M %%p
42 general_fmt_datetime: %%m/%%d/%%Y %%I:%%M %%p
43 general_fmt_datetime_short: %%b %%d, %%I:%%M %%p
43 general_fmt_datetime_short: %%b %%d, %%I:%%M %%p
44 general_fmt_time: %%I:%%M %%p
44 general_fmt_time: %%I:%%M %%p
45 general_text_No: 'No'
45 general_text_No: 'No'
46 general_text_Yes: 'Yes'
46 general_text_Yes: 'Yes'
47 general_text_no: 'no'
47 general_text_no: 'no'
48 general_text_yes: 'yes'
48 general_text_yes: 'yes'
49 general_lang_name: 'English'
49 general_lang_name: 'English'
50 general_csv_separator: ','
50 general_csv_separator: ','
51 general_csv_encoding: ISO-8859-1
51 general_csv_encoding: ISO-8859-1
52 general_pdf_encoding: ISO-8859-1
52 general_pdf_encoding: ISO-8859-1
53 general_day_names: Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday
53 general_day_names: Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday
54 general_first_day_of_week: '7'
54 general_first_day_of_week: '7'
55
55
56 notice_account_updated: Account was successfully updated.
56 notice_account_updated: Account was successfully updated.
57 notice_account_invalid_creditentials: Invalid user or password
57 notice_account_invalid_creditentials: Invalid user or password
58 notice_account_password_updated: Password was successfully updated.
58 notice_account_password_updated: Password was successfully updated.
59 notice_account_wrong_password: Wrong password
59 notice_account_wrong_password: Wrong password
60 notice_account_register_done: Account was successfully created. To activate your account, click on the link that was emailed to you.
60 notice_account_register_done: Account was successfully created. To activate your account, click on the link that was emailed to you.
61 notice_account_unknown_email: Unknown user.
61 notice_account_unknown_email: Unknown user.
62 notice_can_t_change_password: This account uses an external authentication source. Impossible to change the password.
62 notice_can_t_change_password: This account uses an external authentication source. Impossible to change the password.
63 notice_account_lost_email_sent: An email with instructions to choose a new password has been sent to you.
63 notice_account_lost_email_sent: An email with instructions to choose a new password has been sent to you.
64 notice_account_activated: Your account has been activated. You can now log in.
64 notice_account_activated: Your account has been activated. You can now log in.
65 notice_successful_create: Successful creation.
65 notice_successful_create: Successful creation.
66 notice_successful_update: Successful update.
66 notice_successful_update: Successful update.
67 notice_successful_delete: Successful deletion.
67 notice_successful_delete: Successful deletion.
68 notice_successful_connection: Successful connection.
68 notice_successful_connection: Successful connection.
69 notice_file_not_found: The page you were trying to access doesn't exist or has been removed.
69 notice_file_not_found: The page you were trying to access doesn't exist or has been removed.
70 notice_locking_conflict: Data have been updated by another user.
70 notice_locking_conflict: Data have been updated by another user.
71 notice_scm_error: Entry and/or revision doesn't exist in the repository.
71 notice_scm_error: Entry and/or revision doesn't exist in the repository.
72 notice_not_authorized: You are not authorized to access this page.
72 notice_not_authorized: You are not authorized to access this page.
73 notice_email_sent: An email was sent to %s
73 notice_email_sent: An email was sent to %s
74 notice_email_error: An error occurred while sending mail (%s)
74 notice_email_error: An error occurred while sending mail (%s)
75 notice_feeds_access_key_reseted: Your RSS access key was reseted.
75 notice_feeds_access_key_reseted: Your RSS access key was reseted.
76 notice_failed_to_save_issues: "Failed to save %d issue(s) on %d selected: %s."
76 notice_failed_to_save_issues: "Failed to save %d issue(s) on %d selected: %s."
77 notice_no_issue_selected: "No issue is selected! Please, check the issues you want to edit."
77 notice_no_issue_selected: "No issue is selected! Please, check the issues you want to edit."
78
78
79 mail_subject_lost_password: Your Redmine password
79 mail_subject_lost_password: Your Redmine password
80 mail_body_lost_password: 'To change your Redmine password, click on the following link:'
80 mail_body_lost_password: 'To change your Redmine password, click on the following link:'
81 mail_subject_register: Redmine account activation
81 mail_subject_register: Redmine account activation
82 mail_body_register: 'To activate your Redmine account, click on the following link:'
82 mail_body_register: 'To activate your Redmine account, click on the following link:'
83
83
84 gui_validation_error: 1 error
84 gui_validation_error: 1 error
85 gui_validation_error_plural: %d errors
85 gui_validation_error_plural: %d errors
86
86
87 field_name: Name
87 field_name: Name
88 field_description: Description
88 field_description: Description
89 field_summary: Summary
89 field_summary: Summary
90 field_is_required: Required
90 field_is_required: Required
91 field_firstname: Firstname
91 field_firstname: Firstname
92 field_lastname: Lastname
92 field_lastname: Lastname
93 field_mail: Email
93 field_mail: Email
94 field_filename: File
94 field_filename: File
95 field_filesize: Size
95 field_filesize: Size
96 field_downloads: Downloads
96 field_downloads: Downloads
97 field_author: Author
97 field_author: Author
98 field_created_on: Created
98 field_created_on: Created
99 field_updated_on: Updated
99 field_updated_on: Updated
100 field_field_format: Format
100 field_field_format: Format
101 field_is_for_all: For all projects
101 field_is_for_all: For all projects
102 field_possible_values: Possible values
102 field_possible_values: Possible values
103 field_regexp: Regular expression
103 field_regexp: Regular expression
104 field_min_length: Minimum length
104 field_min_length: Minimum length
105 field_max_length: Maximum length
105 field_max_length: Maximum length
106 field_value: Value
106 field_value: Value
107 field_category: Category
107 field_category: Category
108 field_title: Title
108 field_title: Title
109 field_project: Project
109 field_project: Project
110 field_issue: Issue
110 field_issue: Issue
111 field_status: Status
111 field_status: Status
112 field_notes: Notes
112 field_notes: Notes
113 field_is_closed: Issue closed
113 field_is_closed: Issue closed
114 field_is_default: Default value
114 field_is_default: Default value
115 field_html_color: Color
115 field_html_color: Color
116 field_tracker: Tracker
116 field_tracker: Tracker
117 field_subject: Subject
117 field_subject: Subject
118 field_due_date: Due date
118 field_due_date: Due date
119 field_assigned_to: Assigned to
119 field_assigned_to: Assigned to
120 field_priority: Priority
120 field_priority: Priority
121 field_fixed_version: Fixed version
121 field_fixed_version: Fixed version
122 field_user: User
122 field_user: User
123 field_role: Role
123 field_role: Role
124 field_homepage: Homepage
124 field_homepage: Homepage
125 field_is_public: Public
125 field_is_public: Public
126 field_parent: Subproject of
126 field_parent: Subproject of
127 field_is_in_chlog: Issues displayed in changelog
127 field_is_in_chlog: Issues displayed in changelog
128 field_is_in_roadmap: Issues displayed in roadmap
128 field_is_in_roadmap: Issues displayed in roadmap
129 field_login: Login
129 field_login: Login
130 field_mail_notification: Email notifications
130 field_mail_notification: Email notifications
131 field_admin: Administrator
131 field_admin: Administrator
132 field_last_login_on: Last connection
132 field_last_login_on: Last connection
133 field_language: Language
133 field_language: Language
134 field_effective_date: Date
134 field_effective_date: Date
135 field_password: Password
135 field_password: Password
136 field_new_password: New password
136 field_new_password: New password
137 field_password_confirmation: Confirmation
137 field_password_confirmation: Confirmation
138 field_version: Version
138 field_version: Version
139 field_type: Type
139 field_type: Type
140 field_host: Host
140 field_host: Host
141 field_port: Port
141 field_port: Port
142 field_account: Account
142 field_account: Account
143 field_base_dn: Base DN
143 field_base_dn: Base DN
144 field_attr_login: Login attribute
144 field_attr_login: Login attribute
145 field_attr_firstname: Firstname attribute
145 field_attr_firstname: Firstname attribute
146 field_attr_lastname: Lastname attribute
146 field_attr_lastname: Lastname attribute
147 field_attr_mail: Email attribute
147 field_attr_mail: Email attribute
148 field_onthefly: On-the-fly user creation
148 field_onthefly: On-the-fly user creation
149 field_start_date: Start
149 field_start_date: Start
150 field_done_ratio: %% Done
150 field_done_ratio: %% Done
151 field_auth_source: Authentication mode
151 field_auth_source: Authentication mode
152 field_hide_mail: Hide my email address
152 field_hide_mail: Hide my email address
153 field_comments: Comment
153 field_comments: Comment
154 field_url: URL
154 field_url: URL
155 field_start_page: Start page
155 field_start_page: Start page
156 field_subproject: Subproject
156 field_subproject: Subproject
157 field_hours: Hours
157 field_hours: Hours
158 field_activity: Activity
158 field_activity: Activity
159 field_spent_on: Date
159 field_spent_on: Date
160 field_identifier: Identifier
160 field_identifier: Identifier
161 field_is_filter: Used as a filter
161 field_is_filter: Used as a filter
162 field_issue_to_id: Related issue
162 field_issue_to_id: Related issue
163 field_delay: Delay
163 field_delay: Delay
164 field_assignable: Issues can be assigned to this role
164 field_assignable: Issues can be assigned to this role
165 field_redirect_existing_links: Redirect existing links
165 field_redirect_existing_links: Redirect existing links
166 field_estimated_hours: Estimated time
166 field_estimated_hours: Estimated time
167 field_column_names: Columns
167 field_column_names: Columns
168
168
169 setting_app_title: Application title
169 setting_app_title: Application title
170 setting_app_subtitle: Application subtitle
170 setting_app_subtitle: Application subtitle
171 setting_welcome_text: Welcome text
171 setting_welcome_text: Welcome text
172 setting_default_language: Default language
172 setting_default_language: Default language
173 setting_login_required: Authent. required
173 setting_login_required: Authent. required
174 setting_self_registration: Self-registration enabled
174 setting_self_registration: Self-registration enabled
175 setting_attachment_max_size: Attachment max. size
175 setting_attachment_max_size: Attachment max. size
176 setting_issues_export_limit: Issues export limit
176 setting_issues_export_limit: Issues export limit
177 setting_mail_from: Emission email address
177 setting_mail_from: Emission email address
178 setting_host_name: Host name
178 setting_host_name: Host name
179 setting_text_formatting: Text formatting
179 setting_text_formatting: Text formatting
180 setting_wiki_compression: Wiki history compression
180 setting_wiki_compression: Wiki history compression
181 setting_feeds_limit: Feed content limit
181 setting_feeds_limit: Feed content limit
182 setting_autofetch_changesets: Autofetch commits
182 setting_autofetch_changesets: Autofetch commits
183 setting_sys_api_enabled: Enable WS for repository management
183 setting_sys_api_enabled: Enable WS for repository management
184 setting_commit_ref_keywords: Referencing keywords
184 setting_commit_ref_keywords: Referencing keywords
185 setting_commit_fix_keywords: Fixing keywords
185 setting_commit_fix_keywords: Fixing keywords
186 setting_autologin: Autologin
186 setting_autologin: Autologin
187 setting_date_format: Date format
187 setting_date_format: Date format
188 setting_cross_project_issue_relations: Allow cross-project issue relations
188 setting_cross_project_issue_relations: Allow cross-project issue relations
189 setting_issue_list_default_columns: Default columns displayed on the issue list
189 setting_issue_list_default_columns: Default columns displayed on the issue list
190 setting_repositories_encodings: Repositories encodings
190 setting_repositories_encodings: Repositories encodings
191 setting_emails_footer: Emails footer
191 setting_emails_footer: Emails footer
192
192
193 label_user: User
193 label_user: User
194 label_user_plural: Users
194 label_user_plural: Users
195 label_user_new: New user
195 label_user_new: New user
196 label_project: Project
196 label_project: Project
197 label_project_new: New project
197 label_project_new: New project
198 label_project_plural: Projects
198 label_project_plural: Projects
199 label_project_all: All Projects
199 label_project_all: All Projects
200 label_project_latest: Latest projects
200 label_project_latest: Latest projects
201 label_issue: Issue
201 label_issue: Issue
202 label_issue_new: New issue
202 label_issue_new: New issue
203 label_issue_plural: Issues
203 label_issue_plural: Issues
204 label_issue_view_all: View all issues
204 label_issue_view_all: View all issues
205 label_document: Document
205 label_document: Document
206 label_document_new: New document
206 label_document_new: New document
207 label_document_plural: Documents
207 label_document_plural: Documents
208 label_role: Role
208 label_role: Role
209 label_role_plural: Roles
209 label_role_plural: Roles
210 label_role_new: New role
210 label_role_new: New role
211 label_role_and_permissions: Roles and permissions
211 label_role_and_permissions: Roles and permissions
212 label_member: Member
212 label_member: Member
213 label_member_new: New member
213 label_member_new: New member
214 label_member_plural: Members
214 label_member_plural: Members
215 label_tracker: Tracker
215 label_tracker: Tracker
216 label_tracker_plural: Trackers
216 label_tracker_plural: Trackers
217 label_tracker_new: New tracker
217 label_tracker_new: New tracker
218 label_workflow: Workflow
218 label_workflow: Workflow
219 label_issue_status: Issue status
219 label_issue_status: Issue status
220 label_issue_status_plural: Issue statuses
220 label_issue_status_plural: Issue statuses
221 label_issue_status_new: New status
221 label_issue_status_new: New status
222 label_issue_category: Issue category
222 label_issue_category: Issue category
223 label_issue_category_plural: Issue categories
223 label_issue_category_plural: Issue categories
224 label_issue_category_new: New category
224 label_issue_category_new: New category
225 label_custom_field: Custom field
225 label_custom_field: Custom field
226 label_custom_field_plural: Custom fields
226 label_custom_field_plural: Custom fields
227 label_custom_field_new: New custom field
227 label_custom_field_new: New custom field
228 label_enumerations: Enumerations
228 label_enumerations: Enumerations
229 label_enumeration_new: New value
229 label_enumeration_new: New value
230 label_information: Information
230 label_information: Information
231 label_information_plural: Information
231 label_information_plural: Information
232 label_please_login: Please login
232 label_please_login: Please login
233 label_register: Register
233 label_register: Register
234 label_password_lost: Lost password
234 label_password_lost: Lost password
235 label_home: Home
235 label_home: Home
236 label_my_page: My page
236 label_my_page: My page
237 label_my_account: My account
237 label_my_account: My account
238 label_my_projects: My projects
238 label_my_projects: My projects
239 label_administration: Administration
239 label_administration: Administration
240 label_login: Sign in
240 label_login: Sign in
241 label_logout: Sign out
241 label_logout: Sign out
242 label_help: Help
242 label_help: Help
243 label_reported_issues: Reported issues
243 label_reported_issues: Reported issues
244 label_assigned_to_me_issues: Issues assigned to me
244 label_assigned_to_me_issues: Issues assigned to me
245 label_last_login: Last connection
245 label_last_login: Last connection
246 label_last_updates: Last updated
246 label_last_updates: Last updated
247 label_last_updates_plural: %d last updated
247 label_last_updates_plural: %d last updated
248 label_registered_on: Registered on
248 label_registered_on: Registered on
249 label_activity: Activity
249 label_activity: Activity
250 label_new: New
250 label_new: New
251 label_logged_as: Logged as
251 label_logged_as: Logged as
252 label_environment: Environment
252 label_environment: Environment
253 label_authentication: Authentication
253 label_authentication: Authentication
254 label_auth_source: Authentication mode
254 label_auth_source: Authentication mode
255 label_auth_source_new: New authentication mode
255 label_auth_source_new: New authentication mode
256 label_auth_source_plural: Authentication modes
256 label_auth_source_plural: Authentication modes
257 label_subproject_plural: Subprojects
257 label_subproject_plural: Subprojects
258 label_min_max_length: Min - Max length
258 label_min_max_length: Min - Max length
259 label_list: List
259 label_list: List
260 label_date: Date
260 label_date: Date
261 label_integer: Integer
261 label_integer: Integer
262 label_float: Float
262 label_float: Float
263 label_boolean: Boolean
263 label_boolean: Boolean
264 label_string: Text
264 label_string: Text
265 label_text: Long text
265 label_text: Long text
266 label_attribute: Attribute
266 label_attribute: Attribute
267 label_attribute_plural: Attributes
267 label_attribute_plural: Attributes
268 label_download: %d Download
268 label_download: %d Download
269 label_download_plural: %d Downloads
269 label_download_plural: %d Downloads
270 label_no_data: No data to display
270 label_no_data: No data to display
271 label_change_status: Change status
271 label_change_status: Change status
272 label_history: History
272 label_history: History
273 label_attachment: File
273 label_attachment: File
274 label_attachment_new: New file
274 label_attachment_new: New file
275 label_attachment_delete: Delete file
275 label_attachment_delete: Delete file
276 label_attachment_plural: Files
276 label_attachment_plural: Files
277 label_report: Report
277 label_report: Report
278 label_report_plural: Reports
278 label_report_plural: Reports
279 label_news: News
279 label_news: News
280 label_news_new: Add news
280 label_news_new: Add news
281 label_news_plural: News
281 label_news_plural: News
282 label_news_latest: Latest news
282 label_news_latest: Latest news
283 label_news_view_all: View all news
283 label_news_view_all: View all news
284 label_change_log: Change log
284 label_change_log: Change log
285 label_settings: Settings
285 label_settings: Settings
286 label_overview: Overview
286 label_overview: Overview
287 label_version: Version
287 label_version: Version
288 label_version_new: New version
288 label_version_new: New version
289 label_version_plural: Versions
289 label_version_plural: Versions
290 label_confirmation: Confirmation
290 label_confirmation: Confirmation
291 label_export_to: Export to
291 label_export_to: Export to
292 label_read: Read...
292 label_read: Read...
293 label_public_projects: Public projects
293 label_public_projects: Public projects
294 label_open_issues: open
294 label_open_issues: open
295 label_open_issues_plural: open
295 label_open_issues_plural: open
296 label_closed_issues: closed
296 label_closed_issues: closed
297 label_closed_issues_plural: closed
297 label_closed_issues_plural: closed
298 label_total: Total
298 label_total: Total
299 label_permissions: Permissions
299 label_permissions: Permissions
300 label_current_status: Current status
300 label_current_status: Current status
301 label_new_statuses_allowed: New statuses allowed
301 label_new_statuses_allowed: New statuses allowed
302 label_all: all
302 label_all: all
303 label_none: none
303 label_none: none
304 label_nobody: nobody
304 label_nobody: nobody
305 label_next: Next
305 label_next: Next
306 label_previous: Previous
306 label_previous: Previous
307 label_used_by: Used by
307 label_used_by: Used by
308 label_details: Details
308 label_details: Details
309 label_add_note: Add a note
309 label_add_note: Add a note
310 label_per_page: Per page
310 label_per_page: Per page
311 label_calendar: Calendar
311 label_calendar: Calendar
312 label_months_from: months from
312 label_months_from: months from
313 label_gantt: Gantt
313 label_gantt: Gantt
314 label_internal: Internal
314 label_internal: Internal
315 label_last_changes: last %d changes
315 label_last_changes: last %d changes
316 label_change_view_all: View all changes
316 label_change_view_all: View all changes
317 label_personalize_page: Personalize this page
317 label_personalize_page: Personalize this page
318 label_comment: Comment
318 label_comment: Comment
319 label_comment_plural: Comments
319 label_comment_plural: Comments
320 label_comment_add: Add a comment
320 label_comment_add: Add a comment
321 label_comment_added: Comment added
321 label_comment_added: Comment added
322 label_comment_delete: Delete comments
322 label_comment_delete: Delete comments
323 label_query: Custom query
323 label_query: Custom query
324 label_query_plural: Custom queries
324 label_query_plural: Custom queries
325 label_query_new: New query
325 label_query_new: New query
326 label_filter_add: Add filter
326 label_filter_add: Add filter
327 label_filter_plural: Filters
327 label_filter_plural: Filters
328 label_equals: is
328 label_equals: is
329 label_not_equals: is not
329 label_not_equals: is not
330 label_in_less_than: in less than
330 label_in_less_than: in less than
331 label_in_more_than: in more than
331 label_in_more_than: in more than
332 label_in: in
332 label_in: in
333 label_today: today
333 label_today: today
334 label_this_week: this week
334 label_this_week: this week
335 label_less_than_ago: less than days ago
335 label_less_than_ago: less than days ago
336 label_more_than_ago: more than days ago
336 label_more_than_ago: more than days ago
337 label_ago: days ago
337 label_ago: days ago
338 label_contains: contains
338 label_contains: contains
339 label_not_contains: doesn't contain
339 label_not_contains: doesn't contain
340 label_day_plural: days
340 label_day_plural: days
341 label_repository: Repository
341 label_repository: Repository
342 label_browse: Browse
342 label_browse: Browse
343 label_modification: %d change
343 label_modification: %d change
344 label_modification_plural: %d changes
344 label_modification_plural: %d changes
345 label_revision: Revision
345 label_revision: Revision
346 label_revision_plural: Revisions
346 label_revision_plural: Revisions
347 label_added: added
347 label_added: added
348 label_modified: modified
348 label_modified: modified
349 label_deleted: deleted
349 label_deleted: deleted
350 label_latest_revision: Latest revision
350 label_latest_revision: Latest revision
351 label_latest_revision_plural: Latest revisions
351 label_latest_revision_plural: Latest revisions
352 label_view_revisions: View revisions
352 label_view_revisions: View revisions
353 label_max_size: Maximum size
353 label_max_size: Maximum size
354 label_on: 'on'
354 label_on: 'on'
355 label_sort_highest: Move to top
355 label_sort_highest: Move to top
356 label_sort_higher: Move up
356 label_sort_higher: Move up
357 label_sort_lower: Move down
357 label_sort_lower: Move down
358 label_sort_lowest: Move to bottom
358 label_sort_lowest: Move to bottom
359 label_roadmap: Roadmap
359 label_roadmap: Roadmap
360 label_roadmap_due_in: Due in
360 label_roadmap_due_in: Due in
361 label_roadmap_overdue: %s late
361 label_roadmap_overdue: %s late
362 label_roadmap_no_issues: No issues for this version
362 label_roadmap_no_issues: No issues for this version
363 label_search: Search
363 label_search: Search
364 label_result_plural: Results
364 label_result_plural: Results
365 label_all_words: All words
365 label_all_words: All words
366 label_wiki: Wiki
366 label_wiki: Wiki
367 label_wiki_edit: Wiki edit
367 label_wiki_edit: Wiki edit
368 label_wiki_edit_plural: Wiki edits
368 label_wiki_edit_plural: Wiki edits
369 label_wiki_page: Wiki page
369 label_wiki_page: Wiki page
370 label_wiki_page_plural: Wiki pages
370 label_wiki_page_plural: Wiki pages
371 label_index_by_title: Index by title
371 label_index_by_title: Index by title
372 label_index_by_date: Index by date
372 label_index_by_date: Index by date
373 label_current_version: Current version
373 label_current_version: Current version
374 label_preview: Preview
374 label_preview: Preview
375 label_feed_plural: Feeds
375 label_feed_plural: Feeds
376 label_changes_details: Details of all changes
376 label_changes_details: Details of all changes
377 label_issue_tracking: Issue tracking
377 label_issue_tracking: Issue tracking
378 label_spent_time: Spent time
378 label_spent_time: Spent time
379 label_f_hour: %.2f hour
379 label_f_hour: %.2f hour
380 label_f_hour_plural: %.2f hours
380 label_f_hour_plural: %.2f hours
381 label_time_tracking: Time tracking
381 label_time_tracking: Time tracking
382 label_change_plural: Changes
382 label_change_plural: Changes
383 label_statistics: Statistics
383 label_statistics: Statistics
384 label_commits_per_month: Commits per month
384 label_commits_per_month: Commits per month
385 label_commits_per_author: Commits per author
385 label_commits_per_author: Commits per author
386 label_view_diff: View differences
386 label_view_diff: View differences
387 label_diff_inline: inline
387 label_diff_inline: inline
388 label_diff_side_by_side: side by side
388 label_diff_side_by_side: side by side
389 label_options: Options
389 label_options: Options
390 label_copy_workflow_from: Copy workflow from
390 label_copy_workflow_from: Copy workflow from
391 label_permissions_report: Permissions report
391 label_permissions_report: Permissions report
392 label_watched_issues: Watched issues
392 label_watched_issues: Watched issues
393 label_related_issues: Related issues
393 label_related_issues: Related issues
394 label_applied_status: Applied status
394 label_applied_status: Applied status
395 label_loading: Loading...
395 label_loading: Loading...
396 label_relation_new: New relation
396 label_relation_new: New relation
397 label_relation_delete: Delete relation
397 label_relation_delete: Delete relation
398 label_relates_to: related to
398 label_relates_to: related to
399 label_duplicates: duplicates
399 label_duplicates: duplicates
400 label_blocks: blocks
400 label_blocks: blocks
401 label_blocked_by: blocked by
401 label_blocked_by: blocked by
402 label_precedes: precedes
402 label_precedes: precedes
403 label_follows: follows
403 label_follows: follows
404 label_end_to_start: end to start
404 label_end_to_start: end to start
405 label_end_to_end: end to end
405 label_end_to_end: end to end
406 label_start_to_start: start to start
406 label_start_to_start: start to start
407 label_start_to_end: start to end
407 label_start_to_end: start to end
408 label_stay_logged_in: Stay logged in
408 label_stay_logged_in: Stay logged in
409 label_disabled: disabled
409 label_disabled: disabled
410 label_show_completed_versions: Show completed versions
410 label_show_completed_versions: Show completed versions
411 label_me: me
411 label_me: me
412 label_board: Forum
412 label_board: Forum
413 label_board_new: New forum
413 label_board_new: New forum
414 label_board_plural: Forums
414 label_board_plural: Forums
415 label_topic_plural: Topics
415 label_topic_plural: Topics
416 label_message_plural: Messages
416 label_message_plural: Messages
417 label_message_last: Last message
417 label_message_last: Last message
418 label_message_new: New message
418 label_message_new: New message
419 label_reply_plural: Replies
419 label_reply_plural: Replies
420 label_send_information: Send account information to the user
420 label_send_information: Send account information to the user
421 label_year: Year
421 label_year: Year
422 label_month: Month
422 label_month: Month
423 label_week: Week
423 label_week: Week
424 label_date_from: From
424 label_date_from: From
425 label_date_to: To
425 label_date_to: To
426 label_language_based: Language based
426 label_language_based: Language based
427 label_sort_by: Sort by "%s"
427 label_sort_by: Sort by "%s"
428 label_send_test_email: Send a test email
428 label_send_test_email: Send a test email
429 label_feeds_access_key_created_on: RSS access key created %s ago
429 label_feeds_access_key_created_on: RSS access key created %s ago
430 label_module_plural: Modules
430 label_module_plural: Modules
431 label_added_time_by: Added by %s %s ago
431 label_added_time_by: Added by %s %s ago
432 label_updated_time: Updated %s ago
432 label_updated_time: Updated %s ago
433 label_jump_to_a_project: Jump to a project...
433 label_jump_to_a_project: Jump to a project...
434 label_file_plural: Files
434 label_file_plural: Files
435 label_changeset_plural: Changesets
435 label_changeset_plural: Changesets
436 label_default_columns: Default columns
436 label_default_columns: Default columns
437 label_no_change_option: (No change)
437 label_no_change_option: (No change)
438 label_bulk_edit_selected_issues: Bulk edit selected issues
438 label_bulk_edit_selected_issues: Bulk edit selected issues
439 label_theme: Theme
439 label_theme: Theme
440 label_default: Default
440 label_default: Default
441 label_search_titles_only: Search titles only
441 label_search_titles_only: Search titles only
442 label_user_mail_option_all: "For any event on all my projects"
442 label_user_mail_option_all: "For any event on all my projects"
443 label_user_mail_option_selected: "For any event on the selected projects only..."
443 label_user_mail_option_selected: "For any event on the selected projects only..."
444 label_user_mail_option_none: "Only for things I watch or I'm involved in"
444 label_user_mail_option_none: "Only for things I watch or I'm involved in"
445
445
446 button_login: Login
446 button_login: Login
447 button_submit: Submit
447 button_submit: Submit
448 button_save: Save
448 button_save: Save
449 button_check_all: Check all
449 button_check_all: Check all
450 button_uncheck_all: Uncheck all
450 button_uncheck_all: Uncheck all
451 button_delete: Delete
451 button_delete: Delete
452 button_create: Create
452 button_create: Create
453 button_test: Test
453 button_test: Test
454 button_edit: Edit
454 button_edit: Edit
455 button_add: Add
455 button_add: Add
456 button_change: Change
456 button_change: Change
457 button_apply: Apply
457 button_apply: Apply
458 button_clear: Clear
458 button_clear: Clear
459 button_lock: Lock
459 button_lock: Lock
460 button_unlock: Unlock
460 button_unlock: Unlock
461 button_download: Download
461 button_download: Download
462 button_list: List
462 button_list: List
463 button_view: View
463 button_view: View
464 button_move: Move
464 button_move: Move
465 button_back: Back
465 button_back: Back
466 button_cancel: Cancel
466 button_cancel: Cancel
467 button_activate: Activate
467 button_activate: Activate
468 button_sort: Sort
468 button_sort: Sort
469 button_log_time: Log time
469 button_log_time: Log time
470 button_rollback: Rollback to this version
470 button_rollback: Rollback to this version
471 button_watch: Watch
471 button_watch: Watch
472 button_unwatch: Unwatch
472 button_unwatch: Unwatch
473 button_reply: Reply
473 button_reply: Reply
474 button_archive: Archive
474 button_archive: Archive
475 button_unarchive: Unarchive
475 button_unarchive: Unarchive
476 button_reset: Reset
476 button_reset: Reset
477 button_rename: Rename
477 button_rename: Rename
478 button_change_password: Change password
478 button_change_password: Change password
479 button_copy: Copy
479
480
480 status_active: active
481 status_active: active
481 status_registered: registered
482 status_registered: registered
482 status_locked: locked
483 status_locked: locked
483
484
484 text_select_mail_notifications: Select actions for which email notifications should be sent.
485 text_select_mail_notifications: Select actions for which email notifications should be sent.
485 text_regexp_info: eg. ^[A-Z0-9]+$
486 text_regexp_info: eg. ^[A-Z0-9]+$
486 text_min_max_length_info: 0 means no restriction
487 text_min_max_length_info: 0 means no restriction
487 text_project_destroy_confirmation: Are you sure you want to delete this project and all related data ?
488 text_project_destroy_confirmation: Are you sure you want to delete this project and all related data ?
488 text_workflow_edit: Select a role and a tracker to edit the workflow
489 text_workflow_edit: Select a role and a tracker to edit the workflow
489 text_are_you_sure: Are you sure ?
490 text_are_you_sure: Are you sure ?
490 text_journal_changed: changed from %s to %s
491 text_journal_changed: changed from %s to %s
491 text_journal_set_to: set to %s
492 text_journal_set_to: set to %s
492 text_journal_deleted: deleted
493 text_journal_deleted: deleted
493 text_tip_task_begin_day: task beginning this day
494 text_tip_task_begin_day: task beginning this day
494 text_tip_task_end_day: task ending this day
495 text_tip_task_end_day: task ending this day
495 text_tip_task_begin_end_day: task beginning and ending this day
496 text_tip_task_begin_end_day: task beginning and ending this day
496 text_project_identifier_info: 'Lower case letters (a-z), numbers and dashes allowed.<br />Once saved, the identifier can not be changed.'
497 text_project_identifier_info: 'Lower case letters (a-z), numbers and dashes allowed.<br />Once saved, the identifier can not be changed.'
497 text_caracters_maximum: %d characters maximum.
498 text_caracters_maximum: %d characters maximum.
498 text_length_between: Length between %d and %d characters.
499 text_length_between: Length between %d and %d characters.
499 text_tracker_no_workflow: No workflow defined for this tracker
500 text_tracker_no_workflow: No workflow defined for this tracker
500 text_unallowed_characters: Unallowed characters
501 text_unallowed_characters: Unallowed characters
501 text_comma_separated: Multiple values allowed (comma separated).
502 text_comma_separated: Multiple values allowed (comma separated).
502 text_issues_ref_in_commit_messages: Referencing and fixing issues in commit messages
503 text_issues_ref_in_commit_messages: Referencing and fixing issues in commit messages
503 text_issue_added: Issue %s has been reported.
504 text_issue_added: Issue %s has been reported.
504 text_issue_updated: Issue %s has been updated.
505 text_issue_updated: Issue %s has been updated.
505 text_wiki_destroy_confirmation: Are you sure you want to delete this wiki and all its content ?
506 text_wiki_destroy_confirmation: Are you sure you want to delete this wiki and all its content ?
506 text_issue_category_destroy_question: Some issues (%d) are assigned to this category. What do you want to do ?
507 text_issue_category_destroy_question: Some issues (%d) are assigned to this category. What do you want to do ?
507 text_issue_category_destroy_assignments: Remove category assignments
508 text_issue_category_destroy_assignments: Remove category assignments
508 text_issue_category_reassign_to: Reassign issues to this category
509 text_issue_category_reassign_to: Reassign issues to this category
509 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)."
510 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)."
510
511
511 default_role_manager: Manager
512 default_role_manager: Manager
512 default_role_developper: Developer
513 default_role_developper: Developer
513 default_role_reporter: Reporter
514 default_role_reporter: Reporter
514 default_tracker_bug: Bug
515 default_tracker_bug: Bug
515 default_tracker_feature: Feature
516 default_tracker_feature: Feature
516 default_tracker_support: Support
517 default_tracker_support: Support
517 default_issue_status_new: New
518 default_issue_status_new: New
518 default_issue_status_assigned: Assigned
519 default_issue_status_assigned: Assigned
519 default_issue_status_resolved: Resolved
520 default_issue_status_resolved: Resolved
520 default_issue_status_feedback: Feedback
521 default_issue_status_feedback: Feedback
521 default_issue_status_closed: Closed
522 default_issue_status_closed: Closed
522 default_issue_status_rejected: Rejected
523 default_issue_status_rejected: Rejected
523 default_doc_category_user: User documentation
524 default_doc_category_user: User documentation
524 default_doc_category_tech: Technical documentation
525 default_doc_category_tech: Technical documentation
525 default_priority_low: Low
526 default_priority_low: Low
526 default_priority_normal: Normal
527 default_priority_normal: Normal
527 default_priority_high: High
528 default_priority_high: High
528 default_priority_urgent: Urgent
529 default_priority_urgent: Urgent
529 default_priority_immediate: Immediate
530 default_priority_immediate: Immediate
530 default_activity_design: Design
531 default_activity_design: Design
531 default_activity_development: Development
532 default_activity_development: Development
532
533
533 enumeration_issue_priorities: Issue priorities
534 enumeration_issue_priorities: Issue priorities
534 enumeration_doc_categories: Document categories
535 enumeration_doc_categories: Document categories
535 enumeration_activities: Activities (time tracking)
536 enumeration_activities: Activities (time tracking)
@@ -1,535 +1,536
1 _gloc_rule_default: '|n| n==1 ? "" : "_plural" '
1 _gloc_rule_default: '|n| n==1 ? "" : "_plural" '
2
2
3 actionview_datehelper_select_day_prefix:
3 actionview_datehelper_select_day_prefix:
4 actionview_datehelper_select_month_names: Janvier,Février,Mars,Avril,Mai,Juin,Juillet,Août,Septembre,Octobre,Novembre,Décembre
4 actionview_datehelper_select_month_names: Janvier,Février,Mars,Avril,Mai,Juin,Juillet,Août,Septembre,Octobre,Novembre,Décembre
5 actionview_datehelper_select_month_names_abbr: Jan,Fév,Mars,Avril,Mai,Juin,Juil,Août,Sept,Oct,Nov,Déc
5 actionview_datehelper_select_month_names_abbr: Jan,Fév,Mars,Avril,Mai,Juin,Juil,Août,Sept,Oct,Nov,Déc
6 actionview_datehelper_select_month_prefix:
6 actionview_datehelper_select_month_prefix:
7 actionview_datehelper_select_year_prefix:
7 actionview_datehelper_select_year_prefix:
8 actionview_datehelper_time_in_words_day: 1 jour
8 actionview_datehelper_time_in_words_day: 1 jour
9 actionview_datehelper_time_in_words_day_plural: %d jours
9 actionview_datehelper_time_in_words_day_plural: %d jours
10 actionview_datehelper_time_in_words_hour_about: environ une heure
10 actionview_datehelper_time_in_words_hour_about: environ une heure
11 actionview_datehelper_time_in_words_hour_about_plural: environ %d heures
11 actionview_datehelper_time_in_words_hour_about_plural: environ %d heures
12 actionview_datehelper_time_in_words_hour_about_single: environ une heure
12 actionview_datehelper_time_in_words_hour_about_single: environ une heure
13 actionview_datehelper_time_in_words_minute: 1 minute
13 actionview_datehelper_time_in_words_minute: 1 minute
14 actionview_datehelper_time_in_words_minute_half: 30 secondes
14 actionview_datehelper_time_in_words_minute_half: 30 secondes
15 actionview_datehelper_time_in_words_minute_less_than: moins d'une minute
15 actionview_datehelper_time_in_words_minute_less_than: moins d'une minute
16 actionview_datehelper_time_in_words_minute_plural: %d minutes
16 actionview_datehelper_time_in_words_minute_plural: %d minutes
17 actionview_datehelper_time_in_words_minute_single: 1 minute
17 actionview_datehelper_time_in_words_minute_single: 1 minute
18 actionview_datehelper_time_in_words_second_less_than: moins d'une seconde
18 actionview_datehelper_time_in_words_second_less_than: moins d'une seconde
19 actionview_datehelper_time_in_words_second_less_than_plural: moins de %d secondes
19 actionview_datehelper_time_in_words_second_less_than_plural: moins de %d secondes
20 actionview_instancetag_blank_option: Choisir
20 actionview_instancetag_blank_option: Choisir
21
21
22 activerecord_error_inclusion: n'est pas inclus dans la liste
22 activerecord_error_inclusion: n'est pas inclus dans la liste
23 activerecord_error_exclusion: est reservé
23 activerecord_error_exclusion: est reservé
24 activerecord_error_invalid: est invalide
24 activerecord_error_invalid: est invalide
25 activerecord_error_confirmation: ne correspond pas à la confirmation
25 activerecord_error_confirmation: ne correspond pas à la confirmation
26 activerecord_error_accepted: doit être accepté
26 activerecord_error_accepted: doit être accepté
27 activerecord_error_empty: doit être renseigné
27 activerecord_error_empty: doit être renseigné
28 activerecord_error_blank: doit être renseigné
28 activerecord_error_blank: doit être renseigné
29 activerecord_error_too_long: est trop long
29 activerecord_error_too_long: est trop long
30 activerecord_error_too_short: est trop court
30 activerecord_error_too_short: est trop court
31 activerecord_error_wrong_length: n'est pas de la bonne longueur
31 activerecord_error_wrong_length: n'est pas de la bonne longueur
32 activerecord_error_taken: est déjà utilisé
32 activerecord_error_taken: est déjà utilisé
33 activerecord_error_not_a_number: n'est pas un nombre
33 activerecord_error_not_a_number: n'est pas un nombre
34 activerecord_error_not_a_date: n'est pas une date valide
34 activerecord_error_not_a_date: n'est pas une date valide
35 activerecord_error_greater_than_start_date: doit être postérieur à la date de début
35 activerecord_error_greater_than_start_date: doit être postérieur à la date de début
36 activerecord_error_not_same_project: n'appartient pas au même projet
36 activerecord_error_not_same_project: n'appartient pas au même projet
37 activerecord_error_circular_dependency: Cette relation créerait une dépendance circulaire
37 activerecord_error_circular_dependency: Cette relation créerait une dépendance circulaire
38
38
39 general_fmt_age: %d an
39 general_fmt_age: %d an
40 general_fmt_age_plural: %d ans
40 general_fmt_age_plural: %d ans
41 general_fmt_date: %%d/%%m/%%Y
41 general_fmt_date: %%d/%%m/%%Y
42 general_fmt_datetime: %%d/%%m/%%Y %%H:%%M
42 general_fmt_datetime: %%d/%%m/%%Y %%H:%%M
43 general_fmt_datetime_short: %%d/%%m %%H:%%M
43 general_fmt_datetime_short: %%d/%%m %%H:%%M
44 general_fmt_time: %%H:%%M
44 general_fmt_time: %%H:%%M
45 general_text_No: 'Non'
45 general_text_No: 'Non'
46 general_text_Yes: 'Oui'
46 general_text_Yes: 'Oui'
47 general_text_no: 'non'
47 general_text_no: 'non'
48 general_text_yes: 'oui'
48 general_text_yes: 'oui'
49 general_lang_name: 'Français'
49 general_lang_name: 'Français'
50 general_csv_separator: ';'
50 general_csv_separator: ';'
51 general_csv_encoding: ISO-8859-1
51 general_csv_encoding: ISO-8859-1
52 general_pdf_encoding: ISO-8859-1
52 general_pdf_encoding: ISO-8859-1
53 general_day_names: Lundi,Mardi,Mercredi,Jeudi,Vendredi,Samedi,Dimanche
53 general_day_names: Lundi,Mardi,Mercredi,Jeudi,Vendredi,Samedi,Dimanche
54 general_first_day_of_week: '1'
54 general_first_day_of_week: '1'
55
55
56 notice_account_updated: Le compte a été mis à jour avec succès.
56 notice_account_updated: Le compte a été mis à jour avec succès.
57 notice_account_invalid_creditentials: Identifiant ou mot de passe invalide.
57 notice_account_invalid_creditentials: Identifiant ou mot de passe invalide.
58 notice_account_password_updated: Mot de passe mis à jour avec succès.
58 notice_account_password_updated: Mot de passe mis à jour avec succès.
59 notice_account_wrong_password: Mot de passe incorrect
59 notice_account_wrong_password: Mot de passe incorrect
60 notice_account_register_done: Un message contenant les instructions pour activer votre compte vous a été envoyé.
60 notice_account_register_done: Un message contenant les instructions pour activer votre compte vous a été envoyé.
61 notice_account_unknown_email: Aucun compte ne correspond à cette adresse.
61 notice_account_unknown_email: Aucun compte ne correspond à cette adresse.
62 notice_can_t_change_password: Ce compte utilise une authentification externe. Impossible de changer le mot de passe.
62 notice_can_t_change_password: Ce compte utilise une authentification externe. Impossible de changer le mot de passe.
63 notice_account_lost_email_sent: Un message contenant les instructions pour choisir un nouveau mot de passe vous a été envoyé.
63 notice_account_lost_email_sent: Un message contenant les instructions pour choisir un nouveau mot de passe vous a été envoyé.
64 notice_account_activated: Votre compte a été activé. Vous pouvez à présent vous connecter.
64 notice_account_activated: Votre compte a été activé. Vous pouvez à présent vous connecter.
65 notice_successful_create: Création effectuée avec succès.
65 notice_successful_create: Création effectuée avec succès.
66 notice_successful_update: Mise à jour effectuée avec succès.
66 notice_successful_update: Mise à jour effectuée avec succès.
67 notice_successful_delete: Suppression effectuée avec succès.
67 notice_successful_delete: Suppression effectuée avec succès.
68 notice_successful_connection: Connection réussie.
68 notice_successful_connection: Connection réussie.
69 notice_file_not_found: "La page à laquelle vous souhaitez accéder n'existe pas ou a été supprimée."
69 notice_file_not_found: "La page à laquelle vous souhaitez accéder n'existe pas ou a été supprimée."
70 notice_locking_conflict: Les données ont été mises à jour par un autre utilisateur. Mise à jour impossible.
70 notice_locking_conflict: Les données ont été mises à jour par un autre utilisateur. Mise à jour impossible.
71 notice_scm_error: "L'entrée et/ou la révision demandée n'existe pas dans le dépôt."
71 notice_scm_error: "L'entrée et/ou la révision demandée n'existe pas dans le dépôt."
72 notice_not_authorized: "Vous n'êtes pas autorisés à accéder à cette page."
72 notice_not_authorized: "Vous n'êtes pas autorisés à accéder à cette page."
73 notice_email_sent: "Un email a été envoyé à %s"
73 notice_email_sent: "Un email a été envoyé à %s"
74 notice_email_error: "Erreur lors de l'envoi de l'email (%s)"
74 notice_email_error: "Erreur lors de l'envoi de l'email (%s)"
75 notice_feeds_access_key_reseted: Votre clé d'accès aux flux RSS a été réinitialisée.
75 notice_feeds_access_key_reseted: Votre clé d'accès aux flux RSS a été réinitialisée.
76 notice_failed_to_save_issues: "%d demande(s) sur les %d sélectionnées n'ont pas pu être mise(s) à jour: %s."
76 notice_failed_to_save_issues: "%d demande(s) sur les %d sélectionnées n'ont pas pu être mise(s) à jour: %s."
77 notice_no_issue_selected: "Aucune demande sélectionnée ! Cochez les demandes que vous voulez mettre à jour."
77 notice_no_issue_selected: "Aucune demande sélectionnée ! Cochez les demandes que vous voulez mettre à jour."
78
78
79 mail_subject_lost_password: Votre mot de passe redMine
79 mail_subject_lost_password: Votre mot de passe redMine
80 mail_body_lost_password: 'Pour changer votre mot de passe Redmine, cliquez sur le lien suivant:'
80 mail_body_lost_password: 'Pour changer votre mot de passe Redmine, cliquez sur le lien suivant:'
81 mail_subject_register: Activation de votre compte redMine
81 mail_subject_register: Activation de votre compte redMine
82 mail_body_register: 'Pour activer votre compte Redmine, cliquez sur le lien suivant:'
82 mail_body_register: 'Pour activer votre compte Redmine, cliquez sur le lien suivant:'
83
83
84 gui_validation_error: 1 erreur
84 gui_validation_error: 1 erreur
85 gui_validation_error_plural: %d erreurs
85 gui_validation_error_plural: %d erreurs
86
86
87 field_name: Nom
87 field_name: Nom
88 field_description: Description
88 field_description: Description
89 field_summary: Résumé
89 field_summary: Résumé
90 field_is_required: Obligatoire
90 field_is_required: Obligatoire
91 field_firstname: Prénom
91 field_firstname: Prénom
92 field_lastname: Nom
92 field_lastname: Nom
93 field_mail: Email
93 field_mail: Email
94 field_filename: Fichier
94 field_filename: Fichier
95 field_filesize: Taille
95 field_filesize: Taille
96 field_downloads: Téléchargements
96 field_downloads: Téléchargements
97 field_author: Auteur
97 field_author: Auteur
98 field_created_on: Créé
98 field_created_on: Créé
99 field_updated_on: Mis à jour
99 field_updated_on: Mis à jour
100 field_field_format: Format
100 field_field_format: Format
101 field_is_for_all: Pour tous les projets
101 field_is_for_all: Pour tous les projets
102 field_possible_values: Valeurs possibles
102 field_possible_values: Valeurs possibles
103 field_regexp: Expression régulière
103 field_regexp: Expression régulière
104 field_min_length: Longueur minimum
104 field_min_length: Longueur minimum
105 field_max_length: Longueur maximum
105 field_max_length: Longueur maximum
106 field_value: Valeur
106 field_value: Valeur
107 field_category: Catégorie
107 field_category: Catégorie
108 field_title: Titre
108 field_title: Titre
109 field_project: Projet
109 field_project: Projet
110 field_issue: Demande
110 field_issue: Demande
111 field_status: Statut
111 field_status: Statut
112 field_notes: Notes
112 field_notes: Notes
113 field_is_closed: Demande fermée
113 field_is_closed: Demande fermée
114 field_is_default: Valeur par défaut
114 field_is_default: Valeur par défaut
115 field_html_color: Couleur
115 field_html_color: Couleur
116 field_tracker: Tracker
116 field_tracker: Tracker
117 field_subject: Sujet
117 field_subject: Sujet
118 field_due_date: Date d'échéance
118 field_due_date: Date d'échéance
119 field_assigned_to: Assigné à
119 field_assigned_to: Assigné à
120 field_priority: Priorité
120 field_priority: Priorité
121 field_fixed_version: Version corrigée
121 field_fixed_version: Version corrigée
122 field_user: Utilisateur
122 field_user: Utilisateur
123 field_role: Rôle
123 field_role: Rôle
124 field_homepage: Site web
124 field_homepage: Site web
125 field_is_public: Public
125 field_is_public: Public
126 field_parent: Sous-projet de
126 field_parent: Sous-projet de
127 field_is_in_chlog: Demandes affichées dans l'historique
127 field_is_in_chlog: Demandes affichées dans l'historique
128 field_is_in_roadmap: Demandes affichées dans la roadmap
128 field_is_in_roadmap: Demandes affichées dans la roadmap
129 field_login: Identifiant
129 field_login: Identifiant
130 field_mail_notification: Notifications par mail
130 field_mail_notification: Notifications par mail
131 field_admin: Administrateur
131 field_admin: Administrateur
132 field_last_login_on: Dernière connexion
132 field_last_login_on: Dernière connexion
133 field_language: Langue
133 field_language: Langue
134 field_effective_date: Date
134 field_effective_date: Date
135 field_password: Mot de passe
135 field_password: Mot de passe
136 field_new_password: Nouveau mot de passe
136 field_new_password: Nouveau mot de passe
137 field_password_confirmation: Confirmation
137 field_password_confirmation: Confirmation
138 field_version: Version
138 field_version: Version
139 field_type: Type
139 field_type: Type
140 field_host: Hôte
140 field_host: Hôte
141 field_port: Port
141 field_port: Port
142 field_account: Compte
142 field_account: Compte
143 field_base_dn: Base DN
143 field_base_dn: Base DN
144 field_attr_login: Attribut Identifiant
144 field_attr_login: Attribut Identifiant
145 field_attr_firstname: Attribut Prénom
145 field_attr_firstname: Attribut Prénom
146 field_attr_lastname: Attribut Nom
146 field_attr_lastname: Attribut Nom
147 field_attr_mail: Attribut Email
147 field_attr_mail: Attribut Email
148 field_onthefly: Création des utilisateurs à la volée
148 field_onthefly: Création des utilisateurs à la volée
149 field_start_date: Début
149 field_start_date: Début
150 field_done_ratio: %% Réalisé
150 field_done_ratio: %% Réalisé
151 field_auth_source: Mode d'authentification
151 field_auth_source: Mode d'authentification
152 field_hide_mail: Cacher mon adresse mail
152 field_hide_mail: Cacher mon adresse mail
153 field_comments: Commentaire
153 field_comments: Commentaire
154 field_url: URL
154 field_url: URL
155 field_start_page: Page de démarrage
155 field_start_page: Page de démarrage
156 field_subproject: Sous-projet
156 field_subproject: Sous-projet
157 field_hours: Heures
157 field_hours: Heures
158 field_activity: Activité
158 field_activity: Activité
159 field_spent_on: Date
159 field_spent_on: Date
160 field_identifier: Identifiant
160 field_identifier: Identifiant
161 field_is_filter: Utilisé comme filtre
161 field_is_filter: Utilisé comme filtre
162 field_issue_to_id: Demande liée
162 field_issue_to_id: Demande liée
163 field_delay: Retard
163 field_delay: Retard
164 field_assignable: Demandes assignables à ce rôle
164 field_assignable: Demandes assignables à ce rôle
165 field_redirect_existing_links: Rediriger les liens existants
165 field_redirect_existing_links: Rediriger les liens existants
166 field_estimated_hours: Temps estimé
166 field_estimated_hours: Temps estimé
167 field_column_names: Colonnes
167 field_column_names: Colonnes
168
168
169 setting_app_title: Titre de l'application
169 setting_app_title: Titre de l'application
170 setting_app_subtitle: Sous-titre de l'application
170 setting_app_subtitle: Sous-titre de l'application
171 setting_welcome_text: Texte d'accueil
171 setting_welcome_text: Texte d'accueil
172 setting_default_language: Langue par défaut
172 setting_default_language: Langue par défaut
173 setting_login_required: Authentif. obligatoire
173 setting_login_required: Authentif. obligatoire
174 setting_self_registration: Enregistrement autorisé
174 setting_self_registration: Enregistrement autorisé
175 setting_attachment_max_size: Taille max des fichiers
175 setting_attachment_max_size: Taille max des fichiers
176 setting_issues_export_limit: Limite export demandes
176 setting_issues_export_limit: Limite export demandes
177 setting_mail_from: Adresse d'émission
177 setting_mail_from: Adresse d'émission
178 setting_host_name: Nom d'hôte
178 setting_host_name: Nom d'hôte
179 setting_text_formatting: Formatage du texte
179 setting_text_formatting: Formatage du texte
180 setting_wiki_compression: Compression historique wiki
180 setting_wiki_compression: Compression historique wiki
181 setting_feeds_limit: Limite du contenu des flux RSS
181 setting_feeds_limit: Limite du contenu des flux RSS
182 setting_autofetch_changesets: Récupération auto. des commits
182 setting_autofetch_changesets: Récupération auto. des commits
183 setting_sys_api_enabled: Activer les WS pour la gestion des dépôts
183 setting_sys_api_enabled: Activer les WS pour la gestion des dépôts
184 setting_commit_ref_keywords: Mot-clés de référencement
184 setting_commit_ref_keywords: Mot-clés de référencement
185 setting_commit_fix_keywords: Mot-clés de résolution
185 setting_commit_fix_keywords: Mot-clés de résolution
186 setting_autologin: Autologin
186 setting_autologin: Autologin
187 setting_date_format: Format de date
187 setting_date_format: Format de date
188 setting_cross_project_issue_relations: Autoriser les relations entre demandes de différents projets
188 setting_cross_project_issue_relations: Autoriser les relations entre demandes de différents projets
189 setting_issue_list_default_columns: Colonnes affichées par défaut sur la liste des demandes
189 setting_issue_list_default_columns: Colonnes affichées par défaut sur la liste des demandes
190 setting_repositories_encodings: Encodages des dépôts
190 setting_repositories_encodings: Encodages des dépôts
191 setting_emails_footer: Pied-de-page des emails
191 setting_emails_footer: Pied-de-page des emails
192
192
193 label_user: Utilisateur
193 label_user: Utilisateur
194 label_user_plural: Utilisateurs
194 label_user_plural: Utilisateurs
195 label_user_new: Nouvel utilisateur
195 label_user_new: Nouvel utilisateur
196 label_project: Projet
196 label_project: Projet
197 label_project_new: Nouveau projet
197 label_project_new: Nouveau projet
198 label_project_plural: Projets
198 label_project_plural: Projets
199 label_project_all: Tous les projets
199 label_project_all: Tous les projets
200 label_project_latest: Derniers projets
200 label_project_latest: Derniers projets
201 label_issue: Demande
201 label_issue: Demande
202 label_issue_new: Nouvelle demande
202 label_issue_new: Nouvelle demande
203 label_issue_plural: Demandes
203 label_issue_plural: Demandes
204 label_issue_view_all: Voir toutes les demandes
204 label_issue_view_all: Voir toutes les demandes
205 label_document: Document
205 label_document: Document
206 label_document_new: Nouveau document
206 label_document_new: Nouveau document
207 label_document_plural: Documents
207 label_document_plural: Documents
208 label_role: Rôle
208 label_role: Rôle
209 label_role_plural: Rôles
209 label_role_plural: Rôles
210 label_role_new: Nouveau rôle
210 label_role_new: Nouveau rôle
211 label_role_and_permissions: Rôles et permissions
211 label_role_and_permissions: Rôles et permissions
212 label_member: Membre
212 label_member: Membre
213 label_member_new: Nouveau membre
213 label_member_new: Nouveau membre
214 label_member_plural: Membres
214 label_member_plural: Membres
215 label_tracker: Tracker
215 label_tracker: Tracker
216 label_tracker_plural: Trackers
216 label_tracker_plural: Trackers
217 label_tracker_new: Nouveau tracker
217 label_tracker_new: Nouveau tracker
218 label_workflow: Workflow
218 label_workflow: Workflow
219 label_issue_status: Statut de demandes
219 label_issue_status: Statut de demandes
220 label_issue_status_plural: Statuts de demandes
220 label_issue_status_plural: Statuts de demandes
221 label_issue_status_new: Nouveau statut
221 label_issue_status_new: Nouveau statut
222 label_issue_category: Catégorie de demandes
222 label_issue_category: Catégorie de demandes
223 label_issue_category_plural: Catégories de demandes
223 label_issue_category_plural: Catégories de demandes
224 label_issue_category_new: Nouvelle catégorie
224 label_issue_category_new: Nouvelle catégorie
225 label_custom_field: Champ personnalisé
225 label_custom_field: Champ personnalisé
226 label_custom_field_plural: Champs personnalisés
226 label_custom_field_plural: Champs personnalisés
227 label_custom_field_new: Nouveau champ personnalisé
227 label_custom_field_new: Nouveau champ personnalisé
228 label_enumerations: Listes de valeurs
228 label_enumerations: Listes de valeurs
229 label_enumeration_new: Nouvelle valeur
229 label_enumeration_new: Nouvelle valeur
230 label_information: Information
230 label_information: Information
231 label_information_plural: Informations
231 label_information_plural: Informations
232 label_please_login: Identification
232 label_please_login: Identification
233 label_register: S'enregistrer
233 label_register: S'enregistrer
234 label_password_lost: Mot de passe perdu
234 label_password_lost: Mot de passe perdu
235 label_home: Accueil
235 label_home: Accueil
236 label_my_page: Ma page
236 label_my_page: Ma page
237 label_my_account: Mon compte
237 label_my_account: Mon compte
238 label_my_projects: Mes projets
238 label_my_projects: Mes projets
239 label_administration: Administration
239 label_administration: Administration
240 label_login: Connexion
240 label_login: Connexion
241 label_logout: Déconnexion
241 label_logout: Déconnexion
242 label_help: Aide
242 label_help: Aide
243 label_reported_issues: Demandes soumises
243 label_reported_issues: Demandes soumises
244 label_assigned_to_me_issues: Demandes qui me sont assignées
244 label_assigned_to_me_issues: Demandes qui me sont assignées
245 label_last_login: Dernière connexion
245 label_last_login: Dernière connexion
246 label_last_updates: Dernière mise à jour
246 label_last_updates: Dernière mise à jour
247 label_last_updates_plural: %d dernières mises à jour
247 label_last_updates_plural: %d dernières mises à jour
248 label_registered_on: Inscrit le
248 label_registered_on: Inscrit le
249 label_activity: Activité
249 label_activity: Activité
250 label_new: Nouveau
250 label_new: Nouveau
251 label_logged_as: Connecté en tant que
251 label_logged_as: Connecté en tant que
252 label_environment: Environnement
252 label_environment: Environnement
253 label_authentication: Authentification
253 label_authentication: Authentification
254 label_auth_source: Mode d'authentification
254 label_auth_source: Mode d'authentification
255 label_auth_source_new: Nouveau mode d'authentification
255 label_auth_source_new: Nouveau mode d'authentification
256 label_auth_source_plural: Modes d'authentification
256 label_auth_source_plural: Modes d'authentification
257 label_subproject_plural: Sous-projets
257 label_subproject_plural: Sous-projets
258 label_min_max_length: Longueurs mini - maxi
258 label_min_max_length: Longueurs mini - maxi
259 label_list: Liste
259 label_list: Liste
260 label_date: Date
260 label_date: Date
261 label_integer: Entier
261 label_integer: Entier
262 label_float: Nombre décimal
262 label_float: Nombre décimal
263 label_boolean: Booléen
263 label_boolean: Booléen
264 label_string: Texte
264 label_string: Texte
265 label_text: Texte long
265 label_text: Texte long
266 label_attribute: Attribut
266 label_attribute: Attribut
267 label_attribute_plural: Attributs
267 label_attribute_plural: Attributs
268 label_download: %d Téléchargement
268 label_download: %d Téléchargement
269 label_download_plural: %d Téléchargements
269 label_download_plural: %d Téléchargements
270 label_no_data: Aucune donnée à afficher
270 label_no_data: Aucune donnée à afficher
271 label_change_status: Changer le statut
271 label_change_status: Changer le statut
272 label_history: Historique
272 label_history: Historique
273 label_attachment: Fichier
273 label_attachment: Fichier
274 label_attachment_new: Nouveau fichier
274 label_attachment_new: Nouveau fichier
275 label_attachment_delete: Supprimer le fichier
275 label_attachment_delete: Supprimer le fichier
276 label_attachment_plural: Fichiers
276 label_attachment_plural: Fichiers
277 label_report: Rapport
277 label_report: Rapport
278 label_report_plural: Rapports
278 label_report_plural: Rapports
279 label_news: Annonce
279 label_news: Annonce
280 label_news_new: Nouvelle annonce
280 label_news_new: Nouvelle annonce
281 label_news_plural: Annonces
281 label_news_plural: Annonces
282 label_news_latest: Dernières annonces
282 label_news_latest: Dernières annonces
283 label_news_view_all: Voir toutes les annonces
283 label_news_view_all: Voir toutes les annonces
284 label_change_log: Historique
284 label_change_log: Historique
285 label_settings: Configuration
285 label_settings: Configuration
286 label_overview: Aperçu
286 label_overview: Aperçu
287 label_version: Version
287 label_version: Version
288 label_version_new: Nouvelle version
288 label_version_new: Nouvelle version
289 label_version_plural: Versions
289 label_version_plural: Versions
290 label_confirmation: Confirmation
290 label_confirmation: Confirmation
291 label_export_to: Exporter en
291 label_export_to: Exporter en
292 label_read: Lire...
292 label_read: Lire...
293 label_public_projects: Projets publics
293 label_public_projects: Projets publics
294 label_open_issues: ouvert
294 label_open_issues: ouvert
295 label_open_issues_plural: ouverts
295 label_open_issues_plural: ouverts
296 label_closed_issues: fermé
296 label_closed_issues: fermé
297 label_closed_issues_plural: fermés
297 label_closed_issues_plural: fermés
298 label_total: Total
298 label_total: Total
299 label_permissions: Permissions
299 label_permissions: Permissions
300 label_current_status: Statut actuel
300 label_current_status: Statut actuel
301 label_new_statuses_allowed: Nouveaux statuts autorisés
301 label_new_statuses_allowed: Nouveaux statuts autorisés
302 label_all: tous
302 label_all: tous
303 label_none: aucun
303 label_none: aucun
304 label_nobody: personne
304 label_nobody: personne
305 label_next: Suivant
305 label_next: Suivant
306 label_previous: Précédent
306 label_previous: Précédent
307 label_used_by: Utilisé par
307 label_used_by: Utilisé par
308 label_details: Détails
308 label_details: Détails
309 label_add_note: Ajouter une note
309 label_add_note: Ajouter une note
310 label_per_page: Par page
310 label_per_page: Par page
311 label_calendar: Calendrier
311 label_calendar: Calendrier
312 label_months_from: mois depuis
312 label_months_from: mois depuis
313 label_gantt: Gantt
313 label_gantt: Gantt
314 label_internal: Interne
314 label_internal: Interne
315 label_last_changes: %d derniers changements
315 label_last_changes: %d derniers changements
316 label_change_view_all: Voir tous les changements
316 label_change_view_all: Voir tous les changements
317 label_personalize_page: Personnaliser cette page
317 label_personalize_page: Personnaliser cette page
318 label_comment: Commentaire
318 label_comment: Commentaire
319 label_comment_plural: Commentaires
319 label_comment_plural: Commentaires
320 label_comment_add: Ajouter un commentaire
320 label_comment_add: Ajouter un commentaire
321 label_comment_added: Commentaire ajouté
321 label_comment_added: Commentaire ajouté
322 label_comment_delete: Supprimer les commentaires
322 label_comment_delete: Supprimer les commentaires
323 label_query: Rapport personnalisé
323 label_query: Rapport personnalisé
324 label_query_plural: Rapports personnalisés
324 label_query_plural: Rapports personnalisés
325 label_query_new: Nouveau rapport
325 label_query_new: Nouveau rapport
326 label_filter_add: Ajouter le filtre
326 label_filter_add: Ajouter le filtre
327 label_filter_plural: Filtres
327 label_filter_plural: Filtres
328 label_equals: égal
328 label_equals: égal
329 label_not_equals: différent
329 label_not_equals: différent
330 label_in_less_than: dans moins de
330 label_in_less_than: dans moins de
331 label_in_more_than: dans plus de
331 label_in_more_than: dans plus de
332 label_in: dans
332 label_in: dans
333 label_today: aujourd'hui
333 label_today: aujourd'hui
334 label_this_week: cette semaine
334 label_this_week: cette semaine
335 label_less_than_ago: il y a moins de
335 label_less_than_ago: il y a moins de
336 label_more_than_ago: il y a plus de
336 label_more_than_ago: il y a plus de
337 label_ago: il y a
337 label_ago: il y a
338 label_contains: contient
338 label_contains: contient
339 label_not_contains: ne contient pas
339 label_not_contains: ne contient pas
340 label_day_plural: jours
340 label_day_plural: jours
341 label_repository: Dépôt
341 label_repository: Dépôt
342 label_browse: Parcourir
342 label_browse: Parcourir
343 label_modification: %d modification
343 label_modification: %d modification
344 label_modification_plural: %d modifications
344 label_modification_plural: %d modifications
345 label_revision: Révision
345 label_revision: Révision
346 label_revision_plural: Révisions
346 label_revision_plural: Révisions
347 label_added: ajouté
347 label_added: ajouté
348 label_modified: modifié
348 label_modified: modifié
349 label_deleted: supprimé
349 label_deleted: supprimé
350 label_latest_revision: Dernière révision
350 label_latest_revision: Dernière révision
351 label_latest_revision_plural: Dernières révisions
351 label_latest_revision_plural: Dernières révisions
352 label_view_revisions: Voir les révisions
352 label_view_revisions: Voir les révisions
353 label_max_size: Taille maximale
353 label_max_size: Taille maximale
354 label_on: sur
354 label_on: sur
355 label_sort_highest: Remonter en premier
355 label_sort_highest: Remonter en premier
356 label_sort_higher: Remonter
356 label_sort_higher: Remonter
357 label_sort_lower: Descendre
357 label_sort_lower: Descendre
358 label_sort_lowest: Descendre en dernier
358 label_sort_lowest: Descendre en dernier
359 label_roadmap: Roadmap
359 label_roadmap: Roadmap
360 label_roadmap_due_in: Echéance dans
360 label_roadmap_due_in: Echéance dans
361 label_roadmap_overdue: En retard de %s
361 label_roadmap_overdue: En retard de %s
362 label_roadmap_no_issues: Aucune demande pour cette version
362 label_roadmap_no_issues: Aucune demande pour cette version
363 label_search: Recherche
363 label_search: Recherche
364 label_result_plural: Résultats
364 label_result_plural: Résultats
365 label_all_words: Tous les mots
365 label_all_words: Tous les mots
366 label_wiki: Wiki
366 label_wiki: Wiki
367 label_wiki_edit: Révision wiki
367 label_wiki_edit: Révision wiki
368 label_wiki_edit_plural: Révisions wiki
368 label_wiki_edit_plural: Révisions wiki
369 label_wiki_page: Page wiki
369 label_wiki_page: Page wiki
370 label_wiki_page_plural: Pages wiki
370 label_wiki_page_plural: Pages wiki
371 label_index_by_title: Index par titre
371 label_index_by_title: Index par titre
372 label_index_by_date: Index par date
372 label_index_by_date: Index par date
373 label_current_version: Version actuelle
373 label_current_version: Version actuelle
374 label_preview: Prévisualisation
374 label_preview: Prévisualisation
375 label_feed_plural: Flux RSS
375 label_feed_plural: Flux RSS
376 label_changes_details: Détails de tous les changements
376 label_changes_details: Détails de tous les changements
377 label_issue_tracking: Suivi des demandes
377 label_issue_tracking: Suivi des demandes
378 label_spent_time: Temps passé
378 label_spent_time: Temps passé
379 label_f_hour: %.2f heure
379 label_f_hour: %.2f heure
380 label_f_hour_plural: %.2f heures
380 label_f_hour_plural: %.2f heures
381 label_time_tracking: Suivi du temps
381 label_time_tracking: Suivi du temps
382 label_change_plural: Changements
382 label_change_plural: Changements
383 label_statistics: Statistiques
383 label_statistics: Statistiques
384 label_commits_per_month: Commits par mois
384 label_commits_per_month: Commits par mois
385 label_commits_per_author: Commits par auteur
385 label_commits_per_author: Commits par auteur
386 label_view_diff: Voir les différences
386 label_view_diff: Voir les différences
387 label_diff_inline: en ligne
387 label_diff_inline: en ligne
388 label_diff_side_by_side: côte à côte
388 label_diff_side_by_side: côte à côte
389 label_options: Options
389 label_options: Options
390 label_copy_workflow_from: Copier le workflow de
390 label_copy_workflow_from: Copier le workflow de
391 label_permissions_report: Synthèse des permissions
391 label_permissions_report: Synthèse des permissions
392 label_watched_issues: Demandes surveillées
392 label_watched_issues: Demandes surveillées
393 label_related_issues: Demandes liées
393 label_related_issues: Demandes liées
394 label_applied_status: Statut appliqué
394 label_applied_status: Statut appliqué
395 label_loading: Chargement...
395 label_loading: Chargement...
396 label_relation_new: Nouvelle relation
396 label_relation_new: Nouvelle relation
397 label_relation_delete: Supprimer la relation
397 label_relation_delete: Supprimer la relation
398 label_relates_to: lié à
398 label_relates_to: lié à
399 label_duplicates: doublon de
399 label_duplicates: doublon de
400 label_blocks: bloque
400 label_blocks: bloque
401 label_blocked_by: bloqué par
401 label_blocked_by: bloqué par
402 label_precedes: précède
402 label_precedes: précède
403 label_follows: suit
403 label_follows: suit
404 label_end_to_start: fin à début
404 label_end_to_start: fin à début
405 label_end_to_end: fin à fin
405 label_end_to_end: fin à fin
406 label_start_to_start: début à début
406 label_start_to_start: début à début
407 label_start_to_end: début à fin
407 label_start_to_end: début à fin
408 label_stay_logged_in: Rester connecté
408 label_stay_logged_in: Rester connecté
409 label_disabled: désactivé
409 label_disabled: désactivé
410 label_show_completed_versions: Voire les versions passées
410 label_show_completed_versions: Voire les versions passées
411 label_me: moi
411 label_me: moi
412 label_board: Forum
412 label_board: Forum
413 label_board_new: Nouveau forum
413 label_board_new: Nouveau forum
414 label_board_plural: Forums
414 label_board_plural: Forums
415 label_topic_plural: Discussions
415 label_topic_plural: Discussions
416 label_message_plural: Messages
416 label_message_plural: Messages
417 label_message_last: Dernier message
417 label_message_last: Dernier message
418 label_message_new: Nouveau message
418 label_message_new: Nouveau message
419 label_reply_plural: Réponses
419 label_reply_plural: Réponses
420 label_send_information: Envoyer les informations à l'utilisateur
420 label_send_information: Envoyer les informations à l'utilisateur
421 label_year: Année
421 label_year: Année
422 label_month: Mois
422 label_month: Mois
423 label_week: Semaine
423 label_week: Semaine
424 label_date_from: Du
424 label_date_from: Du
425 label_date_to: Au
425 label_date_to: Au
426 label_language_based: Basé sur la langue
426 label_language_based: Basé sur la langue
427 label_sort_by: Trier par "%s"
427 label_sort_by: Trier par "%s"
428 label_send_test_email: Envoyer un email de test
428 label_send_test_email: Envoyer un email de test
429 label_feeds_access_key_created_on: Clé d'accès RSS créée il y a %s
429 label_feeds_access_key_created_on: Clé d'accès RSS créée il y a %s
430 label_module_plural: Modules
430 label_module_plural: Modules
431 label_added_time_by: Ajouté par %s il y a %s
431 label_added_time_by: Ajouté par %s il y a %s
432 label_updated_time: Mis à jour il y a %s
432 label_updated_time: Mis à jour il y a %s
433 label_jump_to_a_project: Aller à un projet...
433 label_jump_to_a_project: Aller à un projet...
434 label_file_plural: Fichiers
434 label_file_plural: Fichiers
435 label_changeset_plural: Révisions
435 label_changeset_plural: Révisions
436 label_default_columns: Colonnes par défaut
436 label_default_columns: Colonnes par défaut
437 label_no_change_option: (Pas de changement)
437 label_no_change_option: (Pas de changement)
438 label_bulk_edit_selected_issues: Modifier les demandes sélectionnées
438 label_bulk_edit_selected_issues: Modifier les demandes sélectionnées
439 label_theme: Thème
439 label_theme: Thème
440 label_default: Défaut
440 label_default: Défaut
441 label_search_titles_only: Uniquement dans les titres
441 label_search_titles_only: Uniquement dans les titres
442 label_user_mail_option_all: "Pour tous les événements de tous mes projets"
442 label_user_mail_option_all: "Pour tous les événements de tous mes projets"
443 label_user_mail_option_selected: "Pour tous les événements des projets sélectionnés..."
443 label_user_mail_option_selected: "Pour tous les événements des projets sélectionnés..."
444 label_user_mail_option_none: "Seulement pour ce que je surveille ou à quoi je participe"
444 label_user_mail_option_none: "Seulement pour ce que je surveille ou à quoi je participe"
445
445
446 button_login: Connexion
446 button_login: Connexion
447 button_submit: Soumettre
447 button_submit: Soumettre
448 button_save: Sauvegarder
448 button_save: Sauvegarder
449 button_check_all: Tout cocher
449 button_check_all: Tout cocher
450 button_uncheck_all: Tout décocher
450 button_uncheck_all: Tout décocher
451 button_delete: Supprimer
451 button_delete: Supprimer
452 button_create: Créer
452 button_create: Créer
453 button_test: Tester
453 button_test: Tester
454 button_edit: Modifier
454 button_edit: Modifier
455 button_add: Ajouter
455 button_add: Ajouter
456 button_change: Changer
456 button_change: Changer
457 button_apply: Appliquer
457 button_apply: Appliquer
458 button_clear: Effacer
458 button_clear: Effacer
459 button_lock: Verrouiller
459 button_lock: Verrouiller
460 button_unlock: Déverrouiller
460 button_unlock: Déverrouiller
461 button_download: Télécharger
461 button_download: Télécharger
462 button_list: Lister
462 button_list: Lister
463 button_view: Voir
463 button_view: Voir
464 button_move: Déplacer
464 button_move: Déplacer
465 button_back: Retour
465 button_back: Retour
466 button_cancel: Annuler
466 button_cancel: Annuler
467 button_activate: Activer
467 button_activate: Activer
468 button_sort: Trier
468 button_sort: Trier
469 button_log_time: Saisir temps
469 button_log_time: Saisir temps
470 button_rollback: Revenir à cette version
470 button_rollback: Revenir à cette version
471 button_watch: Surveiller
471 button_watch: Surveiller
472 button_unwatch: Ne plus surveiller
472 button_unwatch: Ne plus surveiller
473 button_reply: Répondre
473 button_reply: Répondre
474 button_archive: Archiver
474 button_archive: Archiver
475 button_unarchive: Désarchiver
475 button_unarchive: Désarchiver
476 button_reset: Réinitialiser
476 button_reset: Réinitialiser
477 button_rename: Renommer
477 button_rename: Renommer
478 button_change_password: Changer de mot de passe
478 button_change_password: Changer de mot de passe
479 button_copy: Copier
479
480
480 status_active: actif
481 status_active: actif
481 status_registered: enregistré
482 status_registered: enregistré
482 status_locked: vérouillé
483 status_locked: vérouillé
483
484
484 text_select_mail_notifications: Sélectionner les actions pour lesquelles la notification par mail doit être activée.
485 text_select_mail_notifications: Sélectionner les actions pour lesquelles la notification par mail doit être activée.
485 text_regexp_info: ex. ^[A-Z0-9]+$
486 text_regexp_info: ex. ^[A-Z0-9]+$
486 text_min_max_length_info: 0 pour aucune restriction
487 text_min_max_length_info: 0 pour aucune restriction
487 text_project_destroy_confirmation: Etes-vous sûr de vouloir supprimer ce projet et tout ce qui lui est rattaché ?
488 text_project_destroy_confirmation: Etes-vous sûr de vouloir supprimer ce projet et tout ce qui lui est rattaché ?
488 text_workflow_edit: Sélectionner un tracker et un rôle pour éditer le workflow
489 text_workflow_edit: Sélectionner un tracker et un rôle pour éditer le workflow
489 text_are_you_sure: Etes-vous sûr ?
490 text_are_you_sure: Etes-vous sûr ?
490 text_journal_changed: changé de %s à %s
491 text_journal_changed: changé de %s à %s
491 text_journal_set_to: mis à %s
492 text_journal_set_to: mis à %s
492 text_journal_deleted: supprimé
493 text_journal_deleted: supprimé
493 text_tip_task_begin_day: tâche commençant ce jour
494 text_tip_task_begin_day: tâche commençant ce jour
494 text_tip_task_end_day: tâche finissant ce jour
495 text_tip_task_end_day: tâche finissant ce jour
495 text_tip_task_begin_end_day: tâche commençant et finissant ce jour
496 text_tip_task_begin_end_day: tâche commençant et finissant ce jour
496 text_project_identifier_info: 'Lettres minuscules (a-z), chiffres et tirets autorisés.<br />Un fois sauvegardé, l''identifiant ne pourra plus être modifié.'
497 text_project_identifier_info: 'Lettres minuscules (a-z), chiffres et tirets autorisés.<br />Un fois sauvegardé, l''identifiant ne pourra plus être modifié.'
497 text_caracters_maximum: %d caractères maximum.
498 text_caracters_maximum: %d caractères maximum.
498 text_length_between: Longueur comprise entre %d et %d caractères.
499 text_length_between: Longueur comprise entre %d et %d caractères.
499 text_tracker_no_workflow: Aucun worflow n'est défini pour ce tracker
500 text_tracker_no_workflow: Aucun worflow n'est défini pour ce tracker
500 text_unallowed_characters: Caractères non autorisés
501 text_unallowed_characters: Caractères non autorisés
501 text_comma_separated: Plusieurs valeurs possibles (séparées par des virgules).
502 text_comma_separated: Plusieurs valeurs possibles (séparées par des virgules).
502 text_issues_ref_in_commit_messages: Référencement et résolution des demandes dans les commentaires de commits
503 text_issues_ref_in_commit_messages: Référencement et résolution des demandes dans les commentaires de commits
503 text_issue_added: La demande %s a été soumise.
504 text_issue_added: La demande %s a été soumise.
504 text_issue_updated: La demande %s a été mise à jour.
505 text_issue_updated: La demande %s a été mise à jour.
505 text_wiki_destroy_confirmation: Etes-vous sûr de vouloir supprimer ce wiki et tout son contenu ?
506 text_wiki_destroy_confirmation: Etes-vous sûr de vouloir supprimer ce wiki et tout son contenu ?
506 text_issue_category_destroy_question: Des demandes (%d) sont affectées à cette catégories. Que voulez-vous faire ?
507 text_issue_category_destroy_question: Des demandes (%d) sont affectées à cette catégories. Que voulez-vous faire ?
507 text_issue_category_destroy_assignments: N'affecter les demandes à aucune autre catégorie
508 text_issue_category_destroy_assignments: N'affecter les demandes à aucune autre catégorie
508 text_issue_category_reassign_to: Réaffecter les demandes à cette catégorie
509 text_issue_category_reassign_to: Réaffecter les demandes à cette catégorie
509 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)."
510 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)."
510
511
511 default_role_manager: Manager
512 default_role_manager: Manager
512 default_role_developper: Développeur
513 default_role_developper: Développeur
513 default_role_reporter: Rapporteur
514 default_role_reporter: Rapporteur
514 default_tracker_bug: Anomalie
515 default_tracker_bug: Anomalie
515 default_tracker_feature: Evolution
516 default_tracker_feature: Evolution
516 default_tracker_support: Assistance
517 default_tracker_support: Assistance
517 default_issue_status_new: Nouveau
518 default_issue_status_new: Nouveau
518 default_issue_status_assigned: Assigné
519 default_issue_status_assigned: Assigné
519 default_issue_status_resolved: Résolu
520 default_issue_status_resolved: Résolu
520 default_issue_status_feedback: Commentaire
521 default_issue_status_feedback: Commentaire
521 default_issue_status_closed: Fermé
522 default_issue_status_closed: Fermé
522 default_issue_status_rejected: Rejeté
523 default_issue_status_rejected: Rejeté
523 default_doc_category_user: Documentation utilisateur
524 default_doc_category_user: Documentation utilisateur
524 default_doc_category_tech: Documentation technique
525 default_doc_category_tech: Documentation technique
525 default_priority_low: Bas
526 default_priority_low: Bas
526 default_priority_normal: Normal
527 default_priority_normal: Normal
527 default_priority_high: Haut
528 default_priority_high: Haut
528 default_priority_urgent: Urgent
529 default_priority_urgent: Urgent
529 default_priority_immediate: Immédiat
530 default_priority_immediate: Immédiat
530 default_activity_design: Conception
531 default_activity_design: Conception
531 default_activity_development: Développement
532 default_activity_development: Développement
532
533
533 enumeration_issue_priorities: Priorités des demandes
534 enumeration_issue_priorities: Priorités des demandes
534 enumeration_doc_categories: Catégories des documents
535 enumeration_doc_categories: Catégories des documents
535 enumeration_activities: Activités (suivi du temps)
536 enumeration_activities: Activités (suivi du temps)
@@ -1,478 +1,479
1 body { font-family: Verdana, sans-serif; font-size: 12px; color:#484848; margin: 0; padding: 0; min-width: 900px; }
1 body { font-family: Verdana, sans-serif; font-size: 12px; color:#484848; margin: 0; padding: 0; min-width: 900px; }
2
2
3 h1, h2, h3, h4 { font-family: "Trebuchet MS", Verdana, sans-serif;}
3 h1, h2, h3, h4 { font-family: "Trebuchet MS", Verdana, sans-serif;}
4 h1 {margin:0; padding:0; font-size: 24px;}
4 h1 {margin:0; padding:0; font-size: 24px;}
5 h2, .wiki h1 {font-size: 20px;padding: 2px 10px 1px 0px;margin: 0 0 10px 0; border-bottom: 1px solid #bbbbbb; color: #444;}
5 h2, .wiki h1 {font-size: 20px;padding: 2px 10px 1px 0px;margin: 0 0 10px 0; border-bottom: 1px solid #bbbbbb; color: #444;}
6 h3, .wiki h2 {font-size: 16px;padding: 2px 10px 1px 0px;margin: 0 0 10px 0; border-bottom: 1px solid #bbbbbb; color: #444;}
6 h3, .wiki h2 {font-size: 16px;padding: 2px 10px 1px 0px;margin: 0 0 10px 0; border-bottom: 1px solid #bbbbbb; color: #444;}
7 h4, .wiki h3 {font-size: 12px;padding: 2px 10px 1px 0px;margin-bottom: 5px; border-bottom: 1px dotted #bbbbbb; color: #444;}
7 h4, .wiki h3 {font-size: 12px;padding: 2px 10px 1px 0px;margin-bottom: 5px; border-bottom: 1px dotted #bbbbbb; color: #444;}
8
8
9 /***** Layout *****/
9 /***** Layout *****/
10 #top-menu {background: #2C4056;color: #fff;height:1.5em; padding: 2px 6px 0px 6px;}
10 #top-menu {background: #2C4056;color: #fff;height:1.5em; padding: 2px 6px 0px 6px;}
11 #top-menu a {color: #fff; padding-right: 4px;}
11 #top-menu a {color: #fff; padding-right: 4px;}
12 #account {float:right;}
12 #account {float:right;}
13
13
14 #header {height:5.3em;margin:0;background-color:#507AAA;color:#f8f8f8; padding: 4px 8px 0px 6px; position:relative;}
14 #header {height:5.3em;margin:0;background-color:#507AAA;color:#f8f8f8; padding: 4px 8px 0px 6px; position:relative;}
15 #header a {color:#f8f8f8;}
15 #header a {color:#f8f8f8;}
16 #quick-search {float:right;}
16 #quick-search {float:right;}
17
17
18 #main-menu {position: absolute; bottom: 0px; left:6px;}
18 #main-menu {position: absolute; bottom: 0px; left:6px;}
19 #main-menu ul {margin: 0; padding: 0;}
19 #main-menu ul {margin: 0; padding: 0;}
20 #main-menu li {
20 #main-menu li {
21 float:left;
21 float:left;
22 list-style-type:none;
22 list-style-type:none;
23 margin: 0px 10px 0px 0px;
23 margin: 0px 10px 0px 0px;
24 padding: 0px 0px 0px 0px;
24 padding: 0px 0px 0px 0px;
25 white-space:nowrap;
25 white-space:nowrap;
26 }
26 }
27 #main-menu li a {
27 #main-menu li a {
28 display: block;
28 display: block;
29 color: #fff;
29 color: #fff;
30 text-decoration: none;
30 text-decoration: none;
31 margin: 0;
31 margin: 0;
32 padding: 4px 4px 4px 4px;
32 padding: 4px 4px 4px 4px;
33 background: #2C4056;
33 background: #2C4056;
34 }
34 }
35 #main-menu li a:hover {background:#759FCF;}
35 #main-menu li a:hover {background:#759FCF;}
36
36
37 #main {background: url(../images/mainbg.png) repeat-x; background-color:#EEEEEE;}
37 #main {background: url(../images/mainbg.png) repeat-x; background-color:#EEEEEE;}
38
38
39 #sidebar{ float: right; width: 17%; position: relative; z-index: 9; min-height: 600px; padding: 0; margin: 0;}
39 #sidebar{ float: right; width: 17%; position: relative; z-index: 9; min-height: 600px; padding: 0; margin: 0;}
40 * html #sidebar{ width: 17%; }
40 * html #sidebar{ width: 17%; }
41 #sidebar h3{ font-size: 14px; margin-top:14px; color: #666; }
41 #sidebar h3{ font-size: 14px; margin-top:14px; color: #666; }
42 #sidebar hr{ width: 100%; margin: 0 auto; height: 1px; background: #ccc; border: 0; }
42 #sidebar hr{ width: 100%; margin: 0 auto; height: 1px; background: #ccc; border: 0; }
43 * html #sidebar hr{ width: 95%; position: relative; left: -6px; color: #ccc; }
43 * html #sidebar hr{ width: 95%; position: relative; left: -6px; color: #ccc; }
44
44
45 #content { width: 80%; background: url(../images/contentbg.png) repeat-x; background-color: #fff; margin: 0px; border-right: 1px solid #ddd; padding: 6px 10px 10px 10px; z-index: 10; height:600px; min-height: 600px;}
45 #content { width: 80%; background: url(../images/contentbg.png) repeat-x; background-color: #fff; margin: 0px; border-right: 1px solid #ddd; padding: 6px 10px 10px 10px; z-index: 10; height:600px; min-height: 600px;}
46 * html #content{ width: 80%; padding-left: 0; margin-top: 0px; padding: 6px 10px 10px 10px;}
46 * html #content{ width: 80%; padding-left: 0; margin-top: 0px; padding: 6px 10px 10px 10px;}
47 html>body #content {
47 html>body #content {
48 height: auto;
48 height: auto;
49 min-height: 600px;
49 min-height: 600px;
50 }
50 }
51
51
52 #main.nosidebar #sidebar{ display: none; }
52 #main.nosidebar #sidebar{ display: none; }
53 #main.nosidebar #content{ width: auto; border-right: 0; }
53 #main.nosidebar #content{ width: auto; border-right: 0; }
54
54
55 #footer {clear: both; border-top: 1px solid #bbb; font-size: 0.9em; color: #aaa; padding: 5px; text-align:center; background:#fff;}
55 #footer {clear: both; border-top: 1px solid #bbb; font-size: 0.9em; color: #aaa; padding: 5px; text-align:center; background:#fff;}
56
56
57 #login-form table {margin-top:5em; padding:1em; margin-left: auto; margin-right: auto; border: 2px solid #FDBF3B; background-color:#FFEBC1; }
57 #login-form table {margin-top:5em; padding:1em; margin-left: auto; margin-right: auto; border: 2px solid #FDBF3B; background-color:#FFEBC1; }
58 #login-form table td {padding: 6px;}
58 #login-form table td {padding: 6px;}
59 #login-form label {font-weight: bold;}
59 #login-form label {font-weight: bold;}
60
60
61 .clear:after{ content: "."; display: block; height: 0; clear: both; visibility: hidden; }
61 .clear:after{ content: "."; display: block; height: 0; clear: both; visibility: hidden; }
62
62
63 /***** Links *****/
63 /***** Links *****/
64 a, a:link, a:visited{ color: #2A5685; text-decoration: none; }
64 a, a:link, a:visited{ color: #2A5685; text-decoration: none; }
65 a:hover, a:active{ color: #c61a1a; text-decoration: underline;}
65 a:hover, a:active{ color: #c61a1a; text-decoration: underline;}
66 a img{ border: 0; }
66 a img{ border: 0; }
67
67
68 /***** Tables *****/
68 /***** Tables *****/
69 table.list { border: 1px solid #e4e4e4; border-collapse: collapse; width: 100%; margin-bottom: 4px; }
69 table.list { border: 1px solid #e4e4e4; border-collapse: collapse; width: 100%; margin-bottom: 4px; }
70 table.list th { background-color:#EEEEEE; padding: 4px; white-space:nowrap; }
70 table.list th { background-color:#EEEEEE; padding: 4px; white-space:nowrap; }
71 table.list td { overflow: hidden; text-overflow: ellipsis; vertical-align: top;}
71 table.list td { overflow: hidden; text-overflow: ellipsis; vertical-align: top;}
72 table.list td.id { width: 2%; text-align: center;}
72 table.list td.id { width: 2%; text-align: center;}
73 table.list td.checkbox { width: 15px; padding: 0px;}
73 table.list td.checkbox { width: 15px; padding: 0px;}
74
74
75 tr.issue { text-align: center; white-space: nowrap; }
75 tr.issue { text-align: center; white-space: nowrap; }
76 tr.issue td.subject, tr.issue td.category { white-space: normal; }
76 tr.issue td.subject, tr.issue td.category { white-space: normal; }
77 tr.issue td.subject { text-align: left; }
77 tr.issue td.subject { text-align: left; }
78
78
79 table.list tbody tr:hover { background-color:#ffffdd; }
79 table.list tbody tr:hover { background-color:#ffffdd; }
80 table td {padding:2px;}
80 table td {padding:2px;}
81 table p {margin:0;}
81 table p {margin:0;}
82 .odd {background-color:#f6f7f8;}
82 .odd {background-color:#f6f7f8;}
83 .even {background-color: #fff;}
83 .even {background-color: #fff;}
84
84
85 .highlight { background-color: #FCFD8D;}
85 .highlight { background-color: #FCFD8D;}
86 .highlight.token-1 { background-color: #faa;}
86 .highlight.token-1 { background-color: #faa;}
87 .highlight.token-2 { background-color: #afa;}
87 .highlight.token-2 { background-color: #afa;}
88 .highlight.token-3 { background-color: #aaf;}
88 .highlight.token-3 { background-color: #aaf;}
89
89
90 .box{
90 .box{
91 padding:6px;
91 padding:6px;
92 margin-bottom: 10px;
92 margin-bottom: 10px;
93 background-color:#f6f6f6;
93 background-color:#f6f6f6;
94 color:#505050;
94 color:#505050;
95 line-height:1.5em;
95 line-height:1.5em;
96 border: 1px solid #e4e4e4;
96 border: 1px solid #e4e4e4;
97 }
97 }
98
98
99 div.square {
99 div.square {
100 border: 1px solid #999;
100 border: 1px solid #999;
101 float: left;
101 float: left;
102 margin: .3em .4em 0 .4em;
102 margin: .3em .4em 0 .4em;
103 overflow: hidden;
103 overflow: hidden;
104 width: .6em; height: .6em;
104 width: .6em; height: .6em;
105 }
105 }
106
106
107 .contextual {float:right; white-space: nowrap; line-height:1.4em;margin-top:5px;font-size:0.9em;}
107 .contextual {float:right; white-space: nowrap; line-height:1.4em;margin-top:5px;font-size:0.9em;}
108 .splitcontentleft{float:left; width:49%;}
108 .splitcontentleft{float:left; width:49%;}
109 .splitcontentright{float:right; width:49%;}
109 .splitcontentright{float:right; width:49%;}
110 form {display: inline;}
110 form {display: inline;}
111 input, select {vertical-align: middle; margin-top: 1px; margin-bottom: 1px;}
111 input, select {vertical-align: middle; margin-top: 1px; margin-bottom: 1px;}
112 fieldset {border: 1px solid #e4e4e4; margin:0;}
112 fieldset {border: 1px solid #e4e4e4; margin:0;}
113 legend {color: #484848;}
113 legend {color: #484848;}
114 hr { width: 100%; height: 1px; background: #ccc; border: 0;}
114 hr { width: 100%; height: 1px; background: #ccc; border: 0;}
115 textarea.wiki-edit { width: 99%; }
115 textarea.wiki-edit { width: 99%; }
116 li p {margin-top: 0;}
116 li p {margin-top: 0;}
117 div.issue {background:#ffffdd; padding:6px; margin-bottom:6px;border: 1px solid #d7d7d7;}
117 div.issue {background:#ffffdd; padding:6px; margin-bottom:6px;border: 1px solid #d7d7d7;}
118 .autoscroll {overflow-x: auto; padding:1px; width:100%;}
118 .autoscroll {overflow-x: auto; padding:1px; width:100%;}
119 #user_firstname, #user_lastname, #user_mail, #notification_option { width: 90%; }
119 #user_firstname, #user_lastname, #user_mail, #notification_option { width: 90%; }
120
120
121 /***** Tabular forms ******/
121 /***** Tabular forms ******/
122 .tabular p{
122 .tabular p{
123 margin: 0;
123 margin: 0;
124 padding: 5px 0 8px 0;
124 padding: 5px 0 8px 0;
125 padding-left: 180px; /*width of left column containing the label elements*/
125 padding-left: 180px; /*width of left column containing the label elements*/
126 height: 1%;
126 height: 1%;
127 clear:left;
127 clear:left;
128 }
128 }
129
129
130 .tabular label{
130 .tabular label{
131 font-weight: bold;
131 font-weight: bold;
132 float: left;
132 float: left;
133 text-align: right;
133 text-align: right;
134 margin-left: -180px; /*width of left column*/
134 margin-left: -180px; /*width of left column*/
135 width: 175px; /*width of labels. Should be smaller than left column to create some right
135 width: 175px; /*width of labels. Should be smaller than left column to create some right
136 margin*/
136 margin*/
137 }
137 }
138
138
139 .tabular label.floating{
139 .tabular label.floating{
140 font-weight: normal;
140 font-weight: normal;
141 margin-left: 0px;
141 margin-left: 0px;
142 text-align: left;
142 text-align: left;
143 width: 200px;
143 width: 200px;
144 }
144 }
145
145
146 #preview fieldset {margin-top: 1em; background: url(../images/draft.png)}
146 #preview fieldset {margin-top: 1em; background: url(../images/draft.png)}
147
147
148 #settings .tabular p{ padding-left: 300px; }
148 #settings .tabular p{ padding-left: 300px; }
149 #settings .tabular label{ margin-left: -300px; width: 295px; }
149 #settings .tabular label{ margin-left: -300px; width: 295px; }
150
150
151 .required {color: #bb0000;}
151 .required {color: #bb0000;}
152 .summary {font-style: italic;}
152 .summary {font-style: italic;}
153
153
154 div.attachments p { margin:4px 0 2px 0; }
154 div.attachments p { margin:4px 0 2px 0; }
155
155
156 /***** Flash & error messages ****/
156 /***** Flash & error messages ****/
157 #errorExplanation, div.flash, .nodata {
157 #errorExplanation, div.flash, .nodata {
158 padding: 4px 4px 4px 30px;
158 padding: 4px 4px 4px 30px;
159 margin-bottom: 12px;
159 margin-bottom: 12px;
160 font-size: 1.1em;
160 font-size: 1.1em;
161 border: 2px solid;
161 border: 2px solid;
162 }
162 }
163
163
164 div.flash {margin-top: 8px;}
164 div.flash {margin-top: 8px;}
165
165
166 div.flash.error, #errorExplanation {
166 div.flash.error, #errorExplanation {
167 background: url(../images/false.png) 8px 5px no-repeat;
167 background: url(../images/false.png) 8px 5px no-repeat;
168 background-color: #ffe3e3;
168 background-color: #ffe3e3;
169 border-color: #dd0000;
169 border-color: #dd0000;
170 color: #550000;
170 color: #550000;
171 }
171 }
172
172
173 div.flash.notice {
173 div.flash.notice {
174 background: url(../images/true.png) 8px 5px no-repeat;
174 background: url(../images/true.png) 8px 5px no-repeat;
175 background-color: #dfffdf;
175 background-color: #dfffdf;
176 border-color: #9fcf9f;
176 border-color: #9fcf9f;
177 color: #005f00;
177 color: #005f00;
178 }
178 }
179
179
180 .nodata {
180 .nodata {
181 text-align: center;
181 text-align: center;
182 background-color: #FFEBC1;
182 background-color: #FFEBC1;
183 border-color: #FDBF3B;
183 border-color: #FDBF3B;
184 color: #A6750C;
184 color: #A6750C;
185 }
185 }
186
186
187 #errorExplanation ul { font-size: 0.9em;}
187 #errorExplanation ul { font-size: 0.9em;}
188
188
189 /***** Ajax indicator ******/
189 /***** Ajax indicator ******/
190 #ajax-indicator {
190 #ajax-indicator {
191 position: absolute; /* fixed not supported by IE */
191 position: absolute; /* fixed not supported by IE */
192 background-color:#eee;
192 background-color:#eee;
193 border: 1px solid #bbb;
193 border: 1px solid #bbb;
194 top:35%;
194 top:35%;
195 left:40%;
195 left:40%;
196 width:20%;
196 width:20%;
197 font-weight:bold;
197 font-weight:bold;
198 text-align:center;
198 text-align:center;
199 padding:0.6em;
199 padding:0.6em;
200 z-index:100;
200 z-index:100;
201 filter:alpha(opacity=50);
201 filter:alpha(opacity=50);
202 -moz-opacity:0.5;
202 -moz-opacity:0.5;
203 opacity: 0.5;
203 opacity: 0.5;
204 -khtml-opacity: 0.5;
204 -khtml-opacity: 0.5;
205 }
205 }
206
206
207 html>body #ajax-indicator { position: fixed; }
207 html>body #ajax-indicator { position: fixed; }
208
208
209 #ajax-indicator span {
209 #ajax-indicator span {
210 background-position: 0% 40%;
210 background-position: 0% 40%;
211 background-repeat: no-repeat;
211 background-repeat: no-repeat;
212 background-image: url(../images/loading.gif);
212 background-image: url(../images/loading.gif);
213 padding-left: 26px;
213 padding-left: 26px;
214 vertical-align: bottom;
214 vertical-align: bottom;
215 }
215 }
216
216
217 /***** Calendar *****/
217 /***** Calendar *****/
218 table.cal {border-collapse: collapse; width: 100%; margin: 8px 0 6px 0;border: 1px solid #d7d7d7;}
218 table.cal {border-collapse: collapse; width: 100%; margin: 8px 0 6px 0;border: 1px solid #d7d7d7;}
219 table.cal thead th {width: 14%;}
219 table.cal thead th {width: 14%;}
220 table.cal tbody tr {height: 100px;}
220 table.cal tbody tr {height: 100px;}
221 table.cal th { background-color:#EEEEEE; padding: 4px; }
221 table.cal th { background-color:#EEEEEE; padding: 4px; }
222 table.cal td {border: 1px solid #d7d7d7; vertical-align: top; font-size: 0.9em;}
222 table.cal td {border: 1px solid #d7d7d7; vertical-align: top; font-size: 0.9em;}
223 table.cal td p.day-num {font-size: 1.1em; text-align:right;}
223 table.cal td p.day-num {font-size: 1.1em; text-align:right;}
224 table.cal td.odd p.day-num {color: #bbb;}
224 table.cal td.odd p.day-num {color: #bbb;}
225 table.cal td.today {background:#ffffdd;}
225 table.cal td.today {background:#ffffdd;}
226 table.cal td.today p.day-num {font-weight: bold;}
226 table.cal td.today p.day-num {font-weight: bold;}
227
227
228 /***** Tooltips ******/
228 /***** Tooltips ******/
229 .tooltip{position:relative;z-index:24;}
229 .tooltip{position:relative;z-index:24;}
230 .tooltip:hover{z-index:25;color:#000;}
230 .tooltip:hover{z-index:25;color:#000;}
231 .tooltip span.tip{display: none; text-align:left;}
231 .tooltip span.tip{display: none; text-align:left;}
232
232
233 div.tooltip:hover span.tip{
233 div.tooltip:hover span.tip{
234 display:block;
234 display:block;
235 position:absolute;
235 position:absolute;
236 top:12px; left:24px; width:270px;
236 top:12px; left:24px; width:270px;
237 border:1px solid #555;
237 border:1px solid #555;
238 background-color:#fff;
238 background-color:#fff;
239 padding: 4px;
239 padding: 4px;
240 font-size: 0.8em;
240 font-size: 0.8em;
241 color:#505050;
241 color:#505050;
242 }
242 }
243
243
244 /***** Progress bar *****/
244 /***** Progress bar *****/
245 .progress {
245 .progress {
246 border: 1px solid #D7D7D7;
246 border: 1px solid #D7D7D7;
247 border-collapse: collapse;
247 border-collapse: collapse;
248 border-spacing: 0pt;
248 border-spacing: 0pt;
249 empty-cells: show;
249 empty-cells: show;
250 padding: 3px;
250 padding: 3px;
251 width: 40em;
251 width: 40em;
252 text-align: center;
252 text-align: center;
253 }
253 }
254
254
255 .progress td { height: 1em; }
255 .progress td { height: 1em; }
256 .progress .closed { background: #BAE0BA none repeat scroll 0%; }
256 .progress .closed { background: #BAE0BA none repeat scroll 0%; }
257 .progress .open { background: #FFF none repeat scroll 0%; }
257 .progress .open { background: #FFF none repeat scroll 0%; }
258
258
259 /***** Tabs *****/
259 /***** Tabs *****/
260 #content .tabs{height: 2.6em;}
260 #content .tabs{height: 2.6em;}
261 #content .tabs ul{margin:0;}
261 #content .tabs ul{margin:0;}
262 #content .tabs ul li{
262 #content .tabs ul li{
263 float:left;
263 float:left;
264 list-style-type:none;
264 list-style-type:none;
265 white-space:nowrap;
265 white-space:nowrap;
266 margin-right:8px;
266 margin-right:8px;
267 background:#fff;
267 background:#fff;
268 }
268 }
269 #content .tabs ul li a{
269 #content .tabs ul li a{
270 display:block;
270 display:block;
271 font-size: 0.9em;
271 font-size: 0.9em;
272 text-decoration:none;
272 text-decoration:none;
273 line-height:1em;
273 line-height:1em;
274 padding:4px;
274 padding:4px;
275 border: 1px solid #c0c0c0;
275 border: 1px solid #c0c0c0;
276 }
276 }
277
277
278 #content .tabs ul li a.selected, #content .tabs ul li a:hover{
278 #content .tabs ul li a.selected, #content .tabs ul li a:hover{
279 background-color: #507AAA;
279 background-color: #507AAA;
280 border: 1px solid #507AAA;
280 border: 1px solid #507AAA;
281 color: #fff;
281 color: #fff;
282 text-decoration:none;
282 text-decoration:none;
283 }
283 }
284
284
285 /***** Diff *****/
285 /***** Diff *****/
286 .diff_out { background: #fcc; }
286 .diff_out { background: #fcc; }
287 .diff_in { background: #cfc; }
287 .diff_in { background: #cfc; }
288
288
289 /***** Wiki *****/
289 /***** Wiki *****/
290 div.wiki table {
290 div.wiki table {
291 border: 1px solid #505050;
291 border: 1px solid #505050;
292 border-collapse: collapse;
292 border-collapse: collapse;
293 }
293 }
294
294
295 div.wiki table, div.wiki td, div.wiki th {
295 div.wiki table, div.wiki td, div.wiki th {
296 border: 1px solid #bbb;
296 border: 1px solid #bbb;
297 padding: 4px;
297 padding: 4px;
298 }
298 }
299
299
300 div.wiki .external {
300 div.wiki .external {
301 background-position: 0% 60%;
301 background-position: 0% 60%;
302 background-repeat: no-repeat;
302 background-repeat: no-repeat;
303 padding-left: 12px;
303 padding-left: 12px;
304 background-image: url(../images/external.png);
304 background-image: url(../images/external.png);
305 }
305 }
306
306
307 div.wiki a.new {
307 div.wiki a.new {
308 color: #b73535;
308 color: #b73535;
309 }
309 }
310
310
311 div.wiki pre {
311 div.wiki pre {
312 margin: 1em 1em 1em 1.6em;
312 margin: 1em 1em 1em 1.6em;
313 padding: 2px;
313 padding: 2px;
314 background-color: #fafafa;
314 background-color: #fafafa;
315 border: 1px solid #dadada;
315 border: 1px solid #dadada;
316 width:95%;
316 width:95%;
317 overflow-x: auto;
317 overflow-x: auto;
318 }
318 }
319
319
320 div.wiki div.toc {
320 div.wiki div.toc {
321 background-color: #ffffdd;
321 background-color: #ffffdd;
322 border: 1px solid #e4e4e4;
322 border: 1px solid #e4e4e4;
323 padding: 4px;
323 padding: 4px;
324 line-height: 1.2em;
324 line-height: 1.2em;
325 margin-bottom: 12px;
325 margin-bottom: 12px;
326 margin-right: 12px;
326 margin-right: 12px;
327 display: table
327 display: table
328 }
328 }
329 * html div.wiki div.toc { width: 50%; } /* IE6 doesn't autosize div */
329 * html div.wiki div.toc { width: 50%; } /* IE6 doesn't autosize div */
330
330
331 div.wiki div.toc.right { float: right; margin-left: 12px; margin-right: 0; width: auto; }
331 div.wiki div.toc.right { float: right; margin-left: 12px; margin-right: 0; width: auto; }
332 div.wiki div.toc.left { float: left; margin-right: 12px; margin-left: 0; width: auto; }
332 div.wiki div.toc.left { float: left; margin-right: 12px; margin-left: 0; width: auto; }
333
333
334 div.wiki div.toc a {
334 div.wiki div.toc a {
335 display: block;
335 display: block;
336 font-size: 0.9em;
336 font-size: 0.9em;
337 font-weight: normal;
337 font-weight: normal;
338 text-decoration: none;
338 text-decoration: none;
339 color: #606060;
339 color: #606060;
340 }
340 }
341 div.wiki div.toc a:hover { color: #c61a1a; text-decoration: underline;}
341 div.wiki div.toc a:hover { color: #c61a1a; text-decoration: underline;}
342
342
343 div.wiki div.toc a.heading2 { margin-left: 6px; }
343 div.wiki div.toc a.heading2 { margin-left: 6px; }
344 div.wiki div.toc a.heading3 { margin-left: 12px; font-size: 0.8em; }
344 div.wiki div.toc a.heading3 { margin-left: 12px; font-size: 0.8em; }
345
345
346 /***** My page layout *****/
346 /***** My page layout *****/
347 .block-receiver {
347 .block-receiver {
348 border:1px dashed #c0c0c0;
348 border:1px dashed #c0c0c0;
349 margin-bottom: 20px;
349 margin-bottom: 20px;
350 padding: 15px 0 15px 0;
350 padding: 15px 0 15px 0;
351 }
351 }
352
352
353 .mypage-box {
353 .mypage-box {
354 margin:0 0 20px 0;
354 margin:0 0 20px 0;
355 color:#505050;
355 color:#505050;
356 line-height:1.5em;
356 line-height:1.5em;
357 }
357 }
358
358
359 .handle {
359 .handle {
360 cursor: move;
360 cursor: move;
361 }
361 }
362
362
363 a.close-icon {
363 a.close-icon {
364 display:block;
364 display:block;
365 margin-top:3px;
365 margin-top:3px;
366 overflow:hidden;
366 overflow:hidden;
367 width:12px;
367 width:12px;
368 height:12px;
368 height:12px;
369 background-repeat: no-repeat;
369 background-repeat: no-repeat;
370 cursor:pointer;
370 cursor:pointer;
371 background-image:url('../images/close.png');
371 background-image:url('../images/close.png');
372 }
372 }
373
373
374 a.close-icon:hover {
374 a.close-icon:hover {
375 background-image:url('../images/close_hl.png');
375 background-image:url('../images/close_hl.png');
376 }
376 }
377
377
378 /***** Gantt chart *****/
378 /***** Gantt chart *****/
379 .gantt_hdr {
379 .gantt_hdr {
380 position:absolute;
380 position:absolute;
381 top:0;
381 top:0;
382 height:16px;
382 height:16px;
383 border-top: 1px solid #c0c0c0;
383 border-top: 1px solid #c0c0c0;
384 border-bottom: 1px solid #c0c0c0;
384 border-bottom: 1px solid #c0c0c0;
385 border-right: 1px solid #c0c0c0;
385 border-right: 1px solid #c0c0c0;
386 text-align: center;
386 text-align: center;
387 overflow: hidden;
387 overflow: hidden;
388 }
388 }
389
389
390 .task {
390 .task {
391 position: absolute;
391 position: absolute;
392 height:8px;
392 height:8px;
393 font-size:0.8em;
393 font-size:0.8em;
394 color:#888;
394 color:#888;
395 padding:0;
395 padding:0;
396 margin:0;
396 margin:0;
397 line-height:0.8em;
397 line-height:0.8em;
398 }
398 }
399
399
400 .task_late { background:#f66 url(../images/task_late.png); border: 1px solid #f66; }
400 .task_late { background:#f66 url(../images/task_late.png); border: 1px solid #f66; }
401 .task_done { background:#66f url(../images/task_done.png); border: 1px solid #66f; }
401 .task_done { background:#66f url(../images/task_done.png); border: 1px solid #66f; }
402 .task_todo { background:#aaa url(../images/task_todo.png); border: 1px solid #aaa; }
402 .task_todo { background:#aaa url(../images/task_todo.png); border: 1px solid #aaa; }
403 .milestone { background-image:url(../images/milestone.png); background-repeat: no-repeat; border: 0; }
403 .milestone { background-image:url(../images/milestone.png); background-repeat: no-repeat; border: 0; }
404
404
405 /***** Icons *****/
405 /***** Icons *****/
406 .icon {
406 .icon {
407 background-position: 0% 40%;
407 background-position: 0% 40%;
408 background-repeat: no-repeat;
408 background-repeat: no-repeat;
409 padding-left: 20px;
409 padding-left: 20px;
410 padding-top: 2px;
410 padding-top: 2px;
411 padding-bottom: 3px;
411 padding-bottom: 3px;
412 }
412 }
413
413
414 .icon22 {
414 .icon22 {
415 background-position: 0% 40%;
415 background-position: 0% 40%;
416 background-repeat: no-repeat;
416 background-repeat: no-repeat;
417 padding-left: 26px;
417 padding-left: 26px;
418 line-height: 22px;
418 line-height: 22px;
419 vertical-align: middle;
419 vertical-align: middle;
420 }
420 }
421
421
422 .icon-add { background-image: url(../images/add.png); }
422 .icon-add { background-image: url(../images/add.png); }
423 .icon-edit { background-image: url(../images/edit.png); }
423 .icon-edit { background-image: url(../images/edit.png); }
424 .icon-copy { background-image: url(../images/copy.png); }
424 .icon-del { background-image: url(../images/delete.png); }
425 .icon-del { background-image: url(../images/delete.png); }
425 .icon-move { background-image: url(../images/move.png); }
426 .icon-move { background-image: url(../images/move.png); }
426 .icon-save { background-image: url(../images/save.png); }
427 .icon-save { background-image: url(../images/save.png); }
427 .icon-cancel { background-image: url(../images/cancel.png); }
428 .icon-cancel { background-image: url(../images/cancel.png); }
428 .icon-pdf { background-image: url(../images/pdf.png); }
429 .icon-pdf { background-image: url(../images/pdf.png); }
429 .icon-csv { background-image: url(../images/csv.png); }
430 .icon-csv { background-image: url(../images/csv.png); }
430 .icon-html { background-image: url(../images/html.png); }
431 .icon-html { background-image: url(../images/html.png); }
431 .icon-image { background-image: url(../images/image.png); }
432 .icon-image { background-image: url(../images/image.png); }
432 .icon-txt { background-image: url(../images/txt.png); }
433 .icon-txt { background-image: url(../images/txt.png); }
433 .icon-file { background-image: url(../images/file.png); }
434 .icon-file { background-image: url(../images/file.png); }
434 .icon-folder { background-image: url(../images/folder.png); }
435 .icon-folder { background-image: url(../images/folder.png); }
435 .open .icon-folder { background-image: url(../images/folder_open.png); }
436 .open .icon-folder { background-image: url(../images/folder_open.png); }
436 .icon-package { background-image: url(../images/package.png); }
437 .icon-package { background-image: url(../images/package.png); }
437 .icon-home { background-image: url(../images/home.png); }
438 .icon-home { background-image: url(../images/home.png); }
438 .icon-user { background-image: url(../images/user.png); }
439 .icon-user { background-image: url(../images/user.png); }
439 .icon-mypage { background-image: url(../images/user_page.png); }
440 .icon-mypage { background-image: url(../images/user_page.png); }
440 .icon-admin { background-image: url(../images/admin.png); }
441 .icon-admin { background-image: url(../images/admin.png); }
441 .icon-projects { background-image: url(../images/projects.png); }
442 .icon-projects { background-image: url(../images/projects.png); }
442 .icon-logout { background-image: url(../images/logout.png); }
443 .icon-logout { background-image: url(../images/logout.png); }
443 .icon-help { background-image: url(../images/help.png); }
444 .icon-help { background-image: url(../images/help.png); }
444 .icon-attachment { background-image: url(../images/attachment.png); }
445 .icon-attachment { background-image: url(../images/attachment.png); }
445 .icon-index { background-image: url(../images/index.png); }
446 .icon-index { background-image: url(../images/index.png); }
446 .icon-history { background-image: url(../images/history.png); }
447 .icon-history { background-image: url(../images/history.png); }
447 .icon-feed { background-image: url(../images/feed.png); }
448 .icon-feed { background-image: url(../images/feed.png); }
448 .icon-time { background-image: url(../images/time.png); }
449 .icon-time { background-image: url(../images/time.png); }
449 .icon-stats { background-image: url(../images/stats.png); }
450 .icon-stats { background-image: url(../images/stats.png); }
450 .icon-warning { background-image: url(../images/warning.png); }
451 .icon-warning { background-image: url(../images/warning.png); }
451 .icon-fav { background-image: url(../images/fav.png); }
452 .icon-fav { background-image: url(../images/fav.png); }
452 .icon-fav-off { background-image: url(../images/fav_off.png); }
453 .icon-fav-off { background-image: url(../images/fav_off.png); }
453 .icon-reload { background-image: url(../images/reload.png); }
454 .icon-reload { background-image: url(../images/reload.png); }
454 .icon-lock { background-image: url(../images/locked.png); }
455 .icon-lock { background-image: url(../images/locked.png); }
455 .icon-unlock { background-image: url(../images/unlock.png); }
456 .icon-unlock { background-image: url(../images/unlock.png); }
456 .icon-note { background-image: url(../images/note.png); }
457 .icon-note { background-image: url(../images/note.png); }
457 .icon-checked { background-image: url(../images/true.png); }
458 .icon-checked { background-image: url(../images/true.png); }
458
459
459 .icon22-projects { background-image: url(../images/22x22/projects.png); }
460 .icon22-projects { background-image: url(../images/22x22/projects.png); }
460 .icon22-users { background-image: url(../images/22x22/users.png); }
461 .icon22-users { background-image: url(../images/22x22/users.png); }
461 .icon22-tracker { background-image: url(../images/22x22/tracker.png); }
462 .icon22-tracker { background-image: url(../images/22x22/tracker.png); }
462 .icon22-role { background-image: url(../images/22x22/role.png); }
463 .icon22-role { background-image: url(../images/22x22/role.png); }
463 .icon22-workflow { background-image: url(../images/22x22/workflow.png); }
464 .icon22-workflow { background-image: url(../images/22x22/workflow.png); }
464 .icon22-options { background-image: url(../images/22x22/options.png); }
465 .icon22-options { background-image: url(../images/22x22/options.png); }
465 .icon22-notifications { background-image: url(../images/22x22/notifications.png); }
466 .icon22-notifications { background-image: url(../images/22x22/notifications.png); }
466 .icon22-authent { background-image: url(../images/22x22/authent.png); }
467 .icon22-authent { background-image: url(../images/22x22/authent.png); }
467 .icon22-info { background-image: url(../images/22x22/info.png); }
468 .icon22-info { background-image: url(../images/22x22/info.png); }
468 .icon22-comment { background-image: url(../images/22x22/comment.png); }
469 .icon22-comment { background-image: url(../images/22x22/comment.png); }
469 .icon22-package { background-image: url(../images/22x22/package.png); }
470 .icon22-package { background-image: url(../images/22x22/package.png); }
470 .icon22-settings { background-image: url(../images/22x22/settings.png); }
471 .icon22-settings { background-image: url(../images/22x22/settings.png); }
471 .icon22-plugin { background-image: url(../images/22x22/plugin.png); }
472 .icon22-plugin { background-image: url(../images/22x22/plugin.png); }
472
473
473 /***** Media print specific styles *****/
474 /***** Media print specific styles *****/
474 @media print {
475 @media print {
475 #top-menu, #header, #main-menu, #sidebar, #footer, .contextual { display:none; }
476 #top-menu, #header, #main-menu, #sidebar, #footer, .contextual { display:none; }
476 #main { background: #fff; }
477 #main { background: #fff; }
477 #content { width: 99%; margin: 0; padding: 0; border: 0; background: #fff; }
478 #content { width: 99%; margin: 0; padding: 0; border: 0; background: #fff; }
478 }
479 }
@@ -1,146 +1,165
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
19 require 'projects_controller'
19 require 'projects_controller'
20
20
21 # Re-raise errors caught by the controller.
21 # Re-raise errors caught by the controller.
22 class ProjectsController; def rescue_action(e) raise e end; end
22 class ProjectsController; def rescue_action(e) raise e end; end
23
23
24 class ProjectsControllerTest < Test::Unit::TestCase
24 class ProjectsControllerTest < Test::Unit::TestCase
25 fixtures :projects, :users, :roles, :enabled_modules, :enumerations
25 fixtures :projects, :users, :roles, :members, :issues, :enabled_modules, :enumerations
26
26
27 def setup
27 def setup
28 @controller = ProjectsController.new
28 @controller = ProjectsController.new
29 @request = ActionController::TestRequest.new
29 @request = ActionController::TestRequest.new
30 @response = ActionController::TestResponse.new
30 @response = ActionController::TestResponse.new
31 end
31 end
32
32
33 def test_index
33 def test_index
34 get :index
34 get :index
35 assert_response :success
35 assert_response :success
36 assert_template 'list'
36 assert_template 'list'
37 end
37 end
38
38
39 def test_list
39 def test_list
40 get :list
40 get :list
41 assert_response :success
41 assert_response :success
42 assert_template 'list'
42 assert_template 'list'
43 assert_not_nil assigns(:project_tree)
43 assert_not_nil assigns(:project_tree)
44 end
44 end
45
45
46 def test_show
46 def test_show
47 get :show, :id => 1
47 get :show, :id => 1
48 assert_response :success
48 assert_response :success
49 assert_template 'show'
49 assert_template 'show'
50 assert_not_nil assigns(:project)
50 assert_not_nil assigns(:project)
51 end
51 end
52
52
53 def test_list_documents
53 def test_list_documents
54 get :list_documents, :id => 1
54 get :list_documents, :id => 1
55 assert_response :success
55 assert_response :success
56 assert_template 'list_documents'
56 assert_template 'list_documents'
57 assert_not_nil assigns(:documents)
57 assert_not_nil assigns(:documents)
58 end
58 end
59
59
60 def test_list_issues
60 def test_list_issues
61 get :list_issues, :id => 1
61 get :list_issues, :id => 1
62 assert_response :success
62 assert_response :success
63 assert_template 'list_issues'
63 assert_template 'list_issues'
64 assert_not_nil assigns(:issues)
64 assert_not_nil assigns(:issues)
65 end
65 end
66
66
67 def test_list_issues_with_filter
67 def test_list_issues_with_filter
68 get :list_issues, :id => 1, :set_filter => 1
68 get :list_issues, :id => 1, :set_filter => 1
69 assert_response :success
69 assert_response :success
70 assert_template 'list_issues'
70 assert_template 'list_issues'
71 assert_not_nil assigns(:issues)
71 assert_not_nil assigns(:issues)
72 end
72 end
73
73
74 def test_list_issues_reset_filter
74 def test_list_issues_reset_filter
75 post :list_issues, :id => 1
75 post :list_issues, :id => 1
76 assert_response :success
76 assert_response :success
77 assert_template 'list_issues'
77 assert_template 'list_issues'
78 assert_not_nil assigns(:issues)
78 assert_not_nil assigns(:issues)
79 end
79 end
80
80
81 def test_export_issues_csv
81 def test_export_issues_csv
82 get :export_issues_csv, :id => 1
82 get :export_issues_csv, :id => 1
83 assert_response :success
83 assert_response :success
84 assert_not_nil assigns(:issues)
84 assert_not_nil assigns(:issues)
85 end
85 end
86
86
87 def test_bulk_edit_issues
87 def test_bulk_edit_issues
88 @request.session[:user_id] = 2
88 @request.session[:user_id] = 2
89 # update issues priority
89 # update issues priority
90 post :bulk_edit_issues, :id => 1, :issue_ids => [1, 2], :priority_id => 7, :notes => 'Bulk editing', :assigned_to_id => ''
90 post :bulk_edit_issues, :id => 1, :issue_ids => [1, 2], :priority_id => 7, :notes => 'Bulk editing', :assigned_to_id => ''
91 assert_response 302
91 assert_response 302
92 # check that the issues were updated
92 # check that the issues were updated
93 assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
93 assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
94 assert_equal 'Bulk editing', Issue.find(1).journals.find(:first, :order => 'created_on DESC').notes
94 assert_equal 'Bulk editing', Issue.find(1).journals.find(:first, :order => 'created_on DESC').notes
95 end
95 end
96
96
97 def test_list_news
97 def test_list_news
98 get :list_news, :id => 1
98 get :list_news, :id => 1
99 assert_response :success
99 assert_response :success
100 assert_template 'list_news'
100 assert_template 'list_news'
101 assert_not_nil assigns(:newss)
101 assert_not_nil assigns(:newss)
102 end
102 end
103
103
104 def test_list_files
104 def test_list_files
105 get :list_files, :id => 1
105 get :list_files, :id => 1
106 assert_response :success
106 assert_response :success
107 assert_template 'list_files'
107 assert_template 'list_files'
108 assert_not_nil assigns(:versions)
108 assert_not_nil assigns(:versions)
109 end
109 end
110
110
111 def test_changelog
111 def test_changelog
112 get :changelog, :id => 1
112 get :changelog, :id => 1
113 assert_response :success
113 assert_response :success
114 assert_template 'changelog'
114 assert_template 'changelog'
115 assert_not_nil assigns(:versions)
115 assert_not_nil assigns(:versions)
116 end
116 end
117
117
118 def test_roadmap
118 def test_roadmap
119 get :roadmap, :id => 1
119 get :roadmap, :id => 1
120 assert_response :success
120 assert_response :success
121 assert_template 'roadmap'
121 assert_template 'roadmap'
122 assert_not_nil assigns(:versions)
122 assert_not_nil assigns(:versions)
123 end
123 end
124
124
125 def test_activity
125 def test_activity
126 get :activity, :id => 1
126 get :activity, :id => 1
127 assert_response :success
127 assert_response :success
128 assert_template 'activity'
128 assert_template 'activity'
129 assert_not_nil assigns(:events_by_day)
129 assert_not_nil assigns(:events_by_day)
130 end
130 end
131
131
132 def test_archive
132 def test_archive
133 @request.session[:user_id] = 1 # admin
133 @request.session[:user_id] = 1 # admin
134 post :archive, :id => 1
134 post :archive, :id => 1
135 assert_redirected_to 'admin/projects'
135 assert_redirected_to 'admin/projects'
136 assert !Project.find(1).active?
136 assert !Project.find(1).active?
137 end
137 end
138
138
139 def test_unarchive
139 def test_unarchive
140 @request.session[:user_id] = 1 # admin
140 @request.session[:user_id] = 1 # admin
141 Project.find(1).archive
141 Project.find(1).archive
142 post :unarchive, :id => 1
142 post :unarchive, :id => 1
143 assert_redirected_to 'admin/projects'
143 assert_redirected_to 'admin/projects'
144 assert Project.find(1).active?
144 assert Project.find(1).active?
145 end
145 end
146
147 def test_add_issue
148 @request.session[:user_id] = 2
149 get :add_issue, :id => 1, :tracker_id => 1
150 assert_response :success
151 assert_template 'add_issue'
152 post :add_issue, :id => 1, :issue => {:tracker_id => 1, :subject => 'This is the test_add_issue issue', :description => 'This is the description', :priority_id => 5}
153 assert_redirected_to 'projects/list_issues'
154 assert Issue.find_by_subject('This is the test_add_issue issue')
155 end
156
157 def test_copy_issue
158 @request.session[:user_id] = 2
159 get :add_issue, :id => 1, :copy_from => 1
160 assert_template 'add_issue'
161 assert_not_nil assigns(:issue)
162 orig = Issue.find(1)
163 assert_equal orig.subject, assigns(:issue).subject
164 end
146 end
165 end
@@ -1,52 +1,62
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
19
19
20 class IssueTest < Test::Unit::TestCase
20 class IssueTest < Test::Unit::TestCase
21 fixtures :projects, :users, :members, :trackers, :issue_statuses, :issue_categories, :enumerations, :issues
21 fixtures :projects, :users, :members, :trackers, :issue_statuses, :issue_categories, :enumerations, :issues, :custom_fields, :custom_values
22
22
23 def test_category_based_assignment
23 def test_category_based_assignment
24 issue = Issue.create(:project_id => 1, :tracker_id => 1, :author_id => 3, :status_id => 1, :priority => Enumeration.get_values('IPRI').first, :subject => 'Assignment test', :description => 'Assignment test', :category_id => 1)
24 issue = Issue.create(:project_id => 1, :tracker_id => 1, :author_id => 3, :status_id => 1, :priority => Enumeration.get_values('IPRI').first, :subject => 'Assignment test', :description => 'Assignment test', :category_id => 1)
25 assert_equal IssueCategory.find(1).assigned_to, issue.assigned_to
25 assert_equal IssueCategory.find(1).assigned_to, issue.assigned_to
26 end
26 end
27
27
28 def test_copy
29 issue = Issue.new.copy_from(1)
30 assert issue.save
31 issue.reload
32 orig = Issue.find(1)
33 assert_equal orig.subject, issue.subject
34 assert_equal orig.tracker, issue.tracker
35 assert_equal orig.custom_values.first.value, issue.custom_values.first.value
36 end
37
28 def test_close_duplicates
38 def test_close_duplicates
29 # Create 3 issues
39 # Create 3 issues
30 issue1 = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :priority => Enumeration.get_values('IPRI').first, :subject => 'Duplicates test', :description => 'Duplicates test')
40 issue1 = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :priority => Enumeration.get_values('IPRI').first, :subject => 'Duplicates test', :description => 'Duplicates test')
31 assert issue1.save
41 assert issue1.save
32 issue2 = issue1.clone
42 issue2 = issue1.clone
33 assert issue2.save
43 assert issue2.save
34 issue3 = issue1.clone
44 issue3 = issue1.clone
35 assert issue3.save
45 assert issue3.save
36
46
37 # 2 is a dupe of 1
47 # 2 is a dupe of 1
38 IssueRelation.create(:issue_from => issue1, :issue_to => issue2, :relation_type => IssueRelation::TYPE_DUPLICATES)
48 IssueRelation.create(:issue_from => issue1, :issue_to => issue2, :relation_type => IssueRelation::TYPE_DUPLICATES)
39 # And 3 is a dupe of 2
49 # And 3 is a dupe of 2
40 IssueRelation.create(:issue_from => issue2, :issue_to => issue3, :relation_type => IssueRelation::TYPE_DUPLICATES)
50 IssueRelation.create(:issue_from => issue2, :issue_to => issue3, :relation_type => IssueRelation::TYPE_DUPLICATES)
41
51
42 assert issue1.reload.duplicates.include?(issue2)
52 assert issue1.reload.duplicates.include?(issue2)
43
53
44 # Closing issue 1
54 # Closing issue 1
45 issue1.init_journal(User.find(:first), "Closing issue1")
55 issue1.init_journal(User.find(:first), "Closing issue1")
46 issue1.status = IssueStatus.find :first, :conditions => {:is_closed => true}
56 issue1.status = IssueStatus.find :first, :conditions => {:is_closed => true}
47 assert issue1.save
57 assert issue1.save
48 # 2 and 3 should be also closed
58 # 2 and 3 should be also closed
49 assert issue2.reload.closed?
59 assert issue2.reload.closed?
50 assert issue3.reload.closed?
60 assert issue3.reload.closed?
51 end
61 end
52 end
62 end
General Comments 0
You need to be logged in to leave comments. Login now