##// END OF EJS Templates
Merged r16292 (#20661)....
Jean-Philippe Lang -
r15924:f37d1a568805
parent child
Show More
@@ -1,557 +1,562
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
2 # Copyright (C) 2006-2016 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 default_search_scope :issues
19 default_search_scope :issues
20
20
21 before_filter :find_issue, :only => [:show, :edit, :update]
21 before_filter :find_issue, :only => [:show, :edit, :update]
22 before_filter :find_issues, :only => [:bulk_edit, :bulk_update, :destroy]
22 before_filter :find_issues, :only => [:bulk_edit, :bulk_update, :destroy]
23 before_filter :authorize, :except => [:index, :new, :create]
23 before_filter :authorize, :except => [:index, :new, :create]
24 before_filter :find_optional_project, :only => [:index, :new, :create]
24 before_filter :find_optional_project, :only => [:index, :new, :create]
25 before_filter :build_new_issue_from_params, :only => [:new, :create]
25 before_filter :build_new_issue_from_params, :only => [:new, :create]
26 accept_rss_auth :index, :show
26 accept_rss_auth :index, :show
27 accept_api_auth :index, :show, :create, :update, :destroy
27 accept_api_auth :index, :show, :create, :update, :destroy
28
28
29 rescue_from Query::StatementInvalid, :with => :query_statement_invalid
29 rescue_from Query::StatementInvalid, :with => :query_statement_invalid
30
30
31 helper :journals
31 helper :journals
32 helper :projects
32 helper :projects
33 helper :custom_fields
33 helper :custom_fields
34 helper :issue_relations
34 helper :issue_relations
35 helper :watchers
35 helper :watchers
36 helper :attachments
36 helper :attachments
37 helper :queries
37 helper :queries
38 include QueriesHelper
38 include QueriesHelper
39 helper :repositories
39 helper :repositories
40 helper :sort
40 helper :sort
41 include SortHelper
41 include SortHelper
42 helper :timelog
42 helper :timelog
43
43
44 def index
44 def index
45 retrieve_query
45 retrieve_query
46 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
46 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
47 sort_update(@query.sortable_columns)
47 sort_update(@query.sortable_columns)
48 @query.sort_criteria = sort_criteria.to_a
48 @query.sort_criteria = sort_criteria.to_a
49
49
50 if @query.valid?
50 if @query.valid?
51 case params[:format]
51 case params[:format]
52 when 'csv', 'pdf'
52 when 'csv', 'pdf'
53 @limit = Setting.issues_export_limit.to_i
53 @limit = Setting.issues_export_limit.to_i
54 if params[:columns] == 'all'
54 if params[:columns] == 'all'
55 @query.column_names = @query.available_inline_columns.map(&:name)
55 @query.column_names = @query.available_inline_columns.map(&:name)
56 end
56 end
57 when 'atom'
57 when 'atom'
58 @limit = Setting.feeds_limit.to_i
58 @limit = Setting.feeds_limit.to_i
59 when 'xml', 'json'
59 when 'xml', 'json'
60 @offset, @limit = api_offset_and_limit
60 @offset, @limit = api_offset_and_limit
61 @query.column_names = %w(author)
61 @query.column_names = %w(author)
62 else
62 else
63 @limit = per_page_option
63 @limit = per_page_option
64 end
64 end
65
65
66 @issue_count = @query.issue_count
66 @issue_count = @query.issue_count
67 @issue_pages = Paginator.new @issue_count, @limit, params['page']
67 @issue_pages = Paginator.new @issue_count, @limit, params['page']
68 @offset ||= @issue_pages.offset
68 @offset ||= @issue_pages.offset
69 @issues = @query.issues(:include => [:assigned_to, :tracker, :priority, :category, :fixed_version],
69 @issues = @query.issues(:include => [:assigned_to, :tracker, :priority, :category, :fixed_version],
70 :order => sort_clause,
70 :order => sort_clause,
71 :offset => @offset,
71 :offset => @offset,
72 :limit => @limit)
72 :limit => @limit)
73 @issue_count_by_group = @query.issue_count_by_group
73 @issue_count_by_group = @query.issue_count_by_group
74
74
75 respond_to do |format|
75 respond_to do |format|
76 format.html { render :template => 'issues/index', :layout => !request.xhr? }
76 format.html { render :template => 'issues/index', :layout => !request.xhr? }
77 format.api {
77 format.api {
78 Issue.load_visible_relations(@issues) if include_in_api_response?('relations')
78 Issue.load_visible_relations(@issues) if include_in_api_response?('relations')
79 }
79 }
80 format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
80 format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
81 format.csv { send_data(query_to_csv(@issues, @query, params[:csv]), :type => 'text/csv; header=present', :filename => 'issues.csv') }
81 format.csv { send_data(query_to_csv(@issues, @query, params[:csv]), :type => 'text/csv; header=present', :filename => 'issues.csv') }
82 format.pdf { send_file_headers! :type => 'application/pdf', :filename => 'issues.pdf' }
82 format.pdf { send_file_headers! :type => 'application/pdf', :filename => 'issues.pdf' }
83 end
83 end
84 else
84 else
85 respond_to do |format|
85 respond_to do |format|
86 format.html { render(:template => 'issues/index', :layout => !request.xhr?) }
86 format.html { render(:template => 'issues/index', :layout => !request.xhr?) }
87 format.any(:atom, :csv, :pdf) { render(:nothing => true) }
87 format.any(:atom, :csv, :pdf) { render(:nothing => true) }
88 format.api { render_validation_errors(@query) }
88 format.api { render_validation_errors(@query) }
89 end
89 end
90 end
90 end
91 rescue ActiveRecord::RecordNotFound
91 rescue ActiveRecord::RecordNotFound
92 render_404
92 render_404
93 end
93 end
94
94
95 def show
95 def show
96 @journals = @issue.journals.includes(:user, :details).
96 @journals = @issue.journals.includes(:user, :details).
97 references(:user, :details).
97 references(:user, :details).
98 reorder(:created_on, :id).to_a
98 reorder(:created_on, :id).to_a
99 @journals.each_with_index {|j,i| j.indice = i+1}
99 @journals.each_with_index {|j,i| j.indice = i+1}
100 @journals.reject!(&:private_notes?) unless User.current.allowed_to?(:view_private_notes, @issue.project)
100 @journals.reject!(&:private_notes?) unless User.current.allowed_to?(:view_private_notes, @issue.project)
101 Journal.preload_journals_details_custom_fields(@journals)
101 Journal.preload_journals_details_custom_fields(@journals)
102 @journals.select! {|journal| journal.notes? || journal.visible_details.any?}
102 @journals.select! {|journal| journal.notes? || journal.visible_details.any?}
103 @journals.reverse! if User.current.wants_comments_in_reverse_order?
103 @journals.reverse! if User.current.wants_comments_in_reverse_order?
104
104
105 @changesets = @issue.changesets.visible.preload(:repository, :user).to_a
105 @changesets = @issue.changesets.visible.preload(:repository, :user).to_a
106 @changesets.reverse! if User.current.wants_comments_in_reverse_order?
106 @changesets.reverse! if User.current.wants_comments_in_reverse_order?
107
107
108 @relations = @issue.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? }
108 @relations = @issue.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? }
109 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
109 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
110 @priorities = IssuePriority.active
110 @priorities = IssuePriority.active
111 @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
111 @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
112 @relation = IssueRelation.new
112 @relation = IssueRelation.new
113
113
114 if User.current.allowed_to?(:view_time_entries, @project)
115 Issue.load_visible_spent_hours([@issue])
116 Issue.load_visible_total_spent_hours([@issue])
117 end
118
114 respond_to do |format|
119 respond_to do |format|
115 format.html {
120 format.html {
116 retrieve_previous_and_next_issue_ids
121 retrieve_previous_and_next_issue_ids
117 render :template => 'issues/show'
122 render :template => 'issues/show'
118 }
123 }
119 format.api
124 format.api
120 format.atom { render :template => 'journals/index', :layout => false, :content_type => 'application/atom+xml' }
125 format.atom { render :template => 'journals/index', :layout => false, :content_type => 'application/atom+xml' }
121 format.pdf {
126 format.pdf {
122 send_file_headers! :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf"
127 send_file_headers! :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf"
123 }
128 }
124 end
129 end
125 end
130 end
126
131
127 def new
132 def new
128 respond_to do |format|
133 respond_to do |format|
129 format.html { render :action => 'new', :layout => !request.xhr? }
134 format.html { render :action => 'new', :layout => !request.xhr? }
130 format.js
135 format.js
131 end
136 end
132 end
137 end
133
138
134 def create
139 def create
135 unless User.current.allowed_to?(:add_issues, @issue.project, :global => true)
140 unless User.current.allowed_to?(:add_issues, @issue.project, :global => true)
136 raise ::Unauthorized
141 raise ::Unauthorized
137 end
142 end
138 call_hook(:controller_issues_new_before_save, { :params => params, :issue => @issue })
143 call_hook(:controller_issues_new_before_save, { :params => params, :issue => @issue })
139 @issue.save_attachments(params[:attachments] || (params[:issue] && params[:issue][:uploads]))
144 @issue.save_attachments(params[:attachments] || (params[:issue] && params[:issue][:uploads]))
140 if @issue.save
145 if @issue.save
141 call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue})
146 call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue})
142 respond_to do |format|
147 respond_to do |format|
143 format.html {
148 format.html {
144 render_attachment_warning_if_needed(@issue)
149 render_attachment_warning_if_needed(@issue)
145 flash[:notice] = l(:notice_issue_successful_create, :id => view_context.link_to("##{@issue.id}", issue_path(@issue), :title => @issue.subject))
150 flash[:notice] = l(:notice_issue_successful_create, :id => view_context.link_to("##{@issue.id}", issue_path(@issue), :title => @issue.subject))
146 redirect_after_create
151 redirect_after_create
147 }
152 }
148 format.api { render :action => 'show', :status => :created, :location => issue_url(@issue) }
153 format.api { render :action => 'show', :status => :created, :location => issue_url(@issue) }
149 end
154 end
150 return
155 return
151 else
156 else
152 respond_to do |format|
157 respond_to do |format|
153 format.html {
158 format.html {
154 if @issue.project.nil?
159 if @issue.project.nil?
155 render_error :status => 422
160 render_error :status => 422
156 else
161 else
157 render :action => 'new'
162 render :action => 'new'
158 end
163 end
159 }
164 }
160 format.api { render_validation_errors(@issue) }
165 format.api { render_validation_errors(@issue) }
161 end
166 end
162 end
167 end
163 end
168 end
164
169
165 def edit
170 def edit
166 return unless update_issue_from_params
171 return unless update_issue_from_params
167
172
168 respond_to do |format|
173 respond_to do |format|
169 format.html { }
174 format.html { }
170 format.js
175 format.js
171 end
176 end
172 end
177 end
173
178
174 def update
179 def update
175 return unless update_issue_from_params
180 return unless update_issue_from_params
176 @issue.save_attachments(params[:attachments] || (params[:issue] && params[:issue][:uploads]))
181 @issue.save_attachments(params[:attachments] || (params[:issue] && params[:issue][:uploads]))
177 saved = false
182 saved = false
178 begin
183 begin
179 saved = save_issue_with_child_records
184 saved = save_issue_with_child_records
180 rescue ActiveRecord::StaleObjectError
185 rescue ActiveRecord::StaleObjectError
181 @conflict = true
186 @conflict = true
182 if params[:last_journal_id]
187 if params[:last_journal_id]
183 @conflict_journals = @issue.journals_after(params[:last_journal_id]).to_a
188 @conflict_journals = @issue.journals_after(params[:last_journal_id]).to_a
184 @conflict_journals.reject!(&:private_notes?) unless User.current.allowed_to?(:view_private_notes, @issue.project)
189 @conflict_journals.reject!(&:private_notes?) unless User.current.allowed_to?(:view_private_notes, @issue.project)
185 end
190 end
186 end
191 end
187
192
188 if saved
193 if saved
189 render_attachment_warning_if_needed(@issue)
194 render_attachment_warning_if_needed(@issue)
190 flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record?
195 flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record?
191
196
192 respond_to do |format|
197 respond_to do |format|
193 format.html { redirect_back_or_default issue_path(@issue, previous_and_next_issue_ids_params) }
198 format.html { redirect_back_or_default issue_path(@issue, previous_and_next_issue_ids_params) }
194 format.api { render_api_ok }
199 format.api { render_api_ok }
195 end
200 end
196 else
201 else
197 respond_to do |format|
202 respond_to do |format|
198 format.html { render :action => 'edit' }
203 format.html { render :action => 'edit' }
199 format.api { render_validation_errors(@issue) }
204 format.api { render_validation_errors(@issue) }
200 end
205 end
201 end
206 end
202 end
207 end
203
208
204 # Bulk edit/copy a set of issues
209 # Bulk edit/copy a set of issues
205 def bulk_edit
210 def bulk_edit
206 @issues.sort!
211 @issues.sort!
207 @copy = params[:copy].present?
212 @copy = params[:copy].present?
208 @notes = params[:notes]
213 @notes = params[:notes]
209
214
210 if @copy
215 if @copy
211 unless User.current.allowed_to?(:copy_issues, @projects)
216 unless User.current.allowed_to?(:copy_issues, @projects)
212 raise ::Unauthorized
217 raise ::Unauthorized
213 end
218 end
214 else
219 else
215 unless @issues.all?(&:attributes_editable?)
220 unless @issues.all?(&:attributes_editable?)
216 raise ::Unauthorized
221 raise ::Unauthorized
217 end
222 end
218 end
223 end
219
224
220 @allowed_projects = Issue.allowed_target_projects
225 @allowed_projects = Issue.allowed_target_projects
221 if params[:issue]
226 if params[:issue]
222 @target_project = @allowed_projects.detect {|p| p.id.to_s == params[:issue][:project_id].to_s}
227 @target_project = @allowed_projects.detect {|p| p.id.to_s == params[:issue][:project_id].to_s}
223 if @target_project
228 if @target_project
224 target_projects = [@target_project]
229 target_projects = [@target_project]
225 end
230 end
226 end
231 end
227 target_projects ||= @projects
232 target_projects ||= @projects
228
233
229 if @copy
234 if @copy
230 # Copied issues will get their default statuses
235 # Copied issues will get their default statuses
231 @available_statuses = []
236 @available_statuses = []
232 else
237 else
233 @available_statuses = @issues.map(&:new_statuses_allowed_to).reduce(:&)
238 @available_statuses = @issues.map(&:new_statuses_allowed_to).reduce(:&)
234 end
239 end
235 @custom_fields = @issues.map{|i|i.editable_custom_fields}.reduce(:&)
240 @custom_fields = @issues.map{|i|i.editable_custom_fields}.reduce(:&)
236 @assignables = target_projects.map(&:assignable_users).reduce(:&)
241 @assignables = target_projects.map(&:assignable_users).reduce(:&)
237 @trackers = target_projects.map {|p| Issue.allowed_target_trackers(p) }.reduce(:&)
242 @trackers = target_projects.map {|p| Issue.allowed_target_trackers(p) }.reduce(:&)
238 @versions = target_projects.map {|p| p.shared_versions.open}.reduce(:&)
243 @versions = target_projects.map {|p| p.shared_versions.open}.reduce(:&)
239 @categories = target_projects.map {|p| p.issue_categories}.reduce(:&)
244 @categories = target_projects.map {|p| p.issue_categories}.reduce(:&)
240 if @copy
245 if @copy
241 @attachments_present = @issues.detect {|i| i.attachments.any?}.present?
246 @attachments_present = @issues.detect {|i| i.attachments.any?}.present?
242 @subtasks_present = @issues.detect {|i| !i.leaf?}.present?
247 @subtasks_present = @issues.detect {|i| !i.leaf?}.present?
243 end
248 end
244
249
245 @safe_attributes = @issues.map(&:safe_attribute_names).reduce(:&)
250 @safe_attributes = @issues.map(&:safe_attribute_names).reduce(:&)
246
251
247 @issue_params = params[:issue] || {}
252 @issue_params = params[:issue] || {}
248 @issue_params[:custom_field_values] ||= {}
253 @issue_params[:custom_field_values] ||= {}
249 end
254 end
250
255
251 def bulk_update
256 def bulk_update
252 @issues.sort!
257 @issues.sort!
253 @copy = params[:copy].present?
258 @copy = params[:copy].present?
254
259
255 attributes = parse_params_for_bulk_update(params[:issue])
260 attributes = parse_params_for_bulk_update(params[:issue])
256 copy_subtasks = (params[:copy_subtasks] == '1')
261 copy_subtasks = (params[:copy_subtasks] == '1')
257 copy_attachments = (params[:copy_attachments] == '1')
262 copy_attachments = (params[:copy_attachments] == '1')
258
263
259 if @copy
264 if @copy
260 unless User.current.allowed_to?(:copy_issues, @projects)
265 unless User.current.allowed_to?(:copy_issues, @projects)
261 raise ::Unauthorized
266 raise ::Unauthorized
262 end
267 end
263 target_projects = @projects
268 target_projects = @projects
264 if attributes['project_id'].present?
269 if attributes['project_id'].present?
265 target_projects = Project.where(:id => attributes['project_id']).to_a
270 target_projects = Project.where(:id => attributes['project_id']).to_a
266 end
271 end
267 unless User.current.allowed_to?(:add_issues, target_projects)
272 unless User.current.allowed_to?(:add_issues, target_projects)
268 raise ::Unauthorized
273 raise ::Unauthorized
269 end
274 end
270 else
275 else
271 unless @issues.all?(&:attributes_editable?)
276 unless @issues.all?(&:attributes_editable?)
272 raise ::Unauthorized
277 raise ::Unauthorized
273 end
278 end
274 end
279 end
275
280
276 unsaved_issues = []
281 unsaved_issues = []
277 saved_issues = []
282 saved_issues = []
278
283
279 if @copy && copy_subtasks
284 if @copy && copy_subtasks
280 # Descendant issues will be copied with the parent task
285 # Descendant issues will be copied with the parent task
281 # Don't copy them twice
286 # Don't copy them twice
282 @issues.reject! {|issue| @issues.detect {|other| issue.is_descendant_of?(other)}}
287 @issues.reject! {|issue| @issues.detect {|other| issue.is_descendant_of?(other)}}
283 end
288 end
284
289
285 @issues.each do |orig_issue|
290 @issues.each do |orig_issue|
286 orig_issue.reload
291 orig_issue.reload
287 if @copy
292 if @copy
288 issue = orig_issue.copy({},
293 issue = orig_issue.copy({},
289 :attachments => copy_attachments,
294 :attachments => copy_attachments,
290 :subtasks => copy_subtasks,
295 :subtasks => copy_subtasks,
291 :link => link_copy?(params[:link_copy])
296 :link => link_copy?(params[:link_copy])
292 )
297 )
293 else
298 else
294 issue = orig_issue
299 issue = orig_issue
295 end
300 end
296 journal = issue.init_journal(User.current, params[:notes])
301 journal = issue.init_journal(User.current, params[:notes])
297 issue.safe_attributes = attributes
302 issue.safe_attributes = attributes
298 call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
303 call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
299 if issue.save
304 if issue.save
300 saved_issues << issue
305 saved_issues << issue
301 else
306 else
302 unsaved_issues << orig_issue
307 unsaved_issues << orig_issue
303 end
308 end
304 end
309 end
305
310
306 if unsaved_issues.empty?
311 if unsaved_issues.empty?
307 flash[:notice] = l(:notice_successful_update) unless saved_issues.empty?
312 flash[:notice] = l(:notice_successful_update) unless saved_issues.empty?
308 if params[:follow]
313 if params[:follow]
309 if @issues.size == 1 && saved_issues.size == 1
314 if @issues.size == 1 && saved_issues.size == 1
310 redirect_to issue_path(saved_issues.first)
315 redirect_to issue_path(saved_issues.first)
311 elsif saved_issues.map(&:project).uniq.size == 1
316 elsif saved_issues.map(&:project).uniq.size == 1
312 redirect_to project_issues_path(saved_issues.map(&:project).first)
317 redirect_to project_issues_path(saved_issues.map(&:project).first)
313 end
318 end
314 else
319 else
315 redirect_back_or_default _project_issues_path(@project)
320 redirect_back_or_default _project_issues_path(@project)
316 end
321 end
317 else
322 else
318 @saved_issues = @issues
323 @saved_issues = @issues
319 @unsaved_issues = unsaved_issues
324 @unsaved_issues = unsaved_issues
320 @issues = Issue.visible.where(:id => @unsaved_issues.map(&:id)).to_a
325 @issues = Issue.visible.where(:id => @unsaved_issues.map(&:id)).to_a
321 bulk_edit
326 bulk_edit
322 render :action => 'bulk_edit'
327 render :action => 'bulk_edit'
323 end
328 end
324 end
329 end
325
330
326 def destroy
331 def destroy
327 raise Unauthorized unless @issues.all?(&:deletable?)
332 raise Unauthorized unless @issues.all?(&:deletable?)
328
333
329 # all issues and their descendants are about to be deleted
334 # all issues and their descendants are about to be deleted
330 issues_and_descendants_ids = Issue.self_and_descendants(@issues).pluck(:id)
335 issues_and_descendants_ids = Issue.self_and_descendants(@issues).pluck(:id)
331 time_entries = TimeEntry.where(:issue_id => issues_and_descendants_ids)
336 time_entries = TimeEntry.where(:issue_id => issues_and_descendants_ids)
332 @hours = time_entries.sum(:hours).to_f
337 @hours = time_entries.sum(:hours).to_f
333
338
334 if @hours > 0
339 if @hours > 0
335 case params[:todo]
340 case params[:todo]
336 when 'destroy'
341 when 'destroy'
337 # nothing to do
342 # nothing to do
338 when 'nullify'
343 when 'nullify'
339 time_entries.update_all(:issue_id => nil)
344 time_entries.update_all(:issue_id => nil)
340 when 'reassign'
345 when 'reassign'
341 reassign_to = @project && @project.issues.find_by_id(params[:reassign_to_id])
346 reassign_to = @project && @project.issues.find_by_id(params[:reassign_to_id])
342 if reassign_to.nil?
347 if reassign_to.nil?
343 flash.now[:error] = l(:error_issue_not_found_in_project)
348 flash.now[:error] = l(:error_issue_not_found_in_project)
344 return
349 return
345 elsif issues_and_descendants_ids.include?(reassign_to.id)
350 elsif issues_and_descendants_ids.include?(reassign_to.id)
346 flash.now[:error] = l(:error_cannot_reassign_time_entries_to_an_issue_about_to_be_deleted)
351 flash.now[:error] = l(:error_cannot_reassign_time_entries_to_an_issue_about_to_be_deleted)
347 return
352 return
348 else
353 else
349 time_entries.update_all(:issue_id => reassign_to.id, :project_id => reassign_to.project_id)
354 time_entries.update_all(:issue_id => reassign_to.id, :project_id => reassign_to.project_id)
350 end
355 end
351 else
356 else
352 # display the destroy form if it's a user request
357 # display the destroy form if it's a user request
353 return unless api_request?
358 return unless api_request?
354 end
359 end
355 end
360 end
356 @issues.each do |issue|
361 @issues.each do |issue|
357 begin
362 begin
358 issue.reload.destroy
363 issue.reload.destroy
359 rescue ::ActiveRecord::RecordNotFound # raised by #reload if issue no longer exists
364 rescue ::ActiveRecord::RecordNotFound # raised by #reload if issue no longer exists
360 # nothing to do, issue was already deleted (eg. by a parent)
365 # nothing to do, issue was already deleted (eg. by a parent)
361 end
366 end
362 end
367 end
363 respond_to do |format|
368 respond_to do |format|
364 format.html { redirect_back_or_default _project_issues_path(@project) }
369 format.html { redirect_back_or_default _project_issues_path(@project) }
365 format.api { render_api_ok }
370 format.api { render_api_ok }
366 end
371 end
367 end
372 end
368
373
369 # Overrides Redmine::MenuManager::MenuController::ClassMethods for
374 # Overrides Redmine::MenuManager::MenuController::ClassMethods for
370 # when the "New issue" tab is enabled
375 # when the "New issue" tab is enabled
371 def current_menu_item
376 def current_menu_item
372 if Setting.new_item_menu_tab == '1' && [:new, :create].include?(action_name.to_sym)
377 if Setting.new_item_menu_tab == '1' && [:new, :create].include?(action_name.to_sym)
373 :new_issue
378 :new_issue
374 else
379 else
375 super
380 super
376 end
381 end
377 end
382 end
378
383
379 private
384 private
380
385
381 def retrieve_previous_and_next_issue_ids
386 def retrieve_previous_and_next_issue_ids
382 if params[:prev_issue_id].present? || params[:next_issue_id].present?
387 if params[:prev_issue_id].present? || params[:next_issue_id].present?
383 @prev_issue_id = params[:prev_issue_id].presence.try(:to_i)
388 @prev_issue_id = params[:prev_issue_id].presence.try(:to_i)
384 @next_issue_id = params[:next_issue_id].presence.try(:to_i)
389 @next_issue_id = params[:next_issue_id].presence.try(:to_i)
385 @issue_position = params[:issue_position].presence.try(:to_i)
390 @issue_position = params[:issue_position].presence.try(:to_i)
386 @issue_count = params[:issue_count].presence.try(:to_i)
391 @issue_count = params[:issue_count].presence.try(:to_i)
387 else
392 else
388 retrieve_query_from_session
393 retrieve_query_from_session
389 if @query
394 if @query
390 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
395 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
391 sort_update(@query.sortable_columns, 'issues_index_sort')
396 sort_update(@query.sortable_columns, 'issues_index_sort')
392 limit = 500
397 limit = 500
393 issue_ids = @query.issue_ids(:order => sort_clause, :limit => (limit + 1), :include => [:assigned_to, :tracker, :priority, :category, :fixed_version])
398 issue_ids = @query.issue_ids(:order => sort_clause, :limit => (limit + 1), :include => [:assigned_to, :tracker, :priority, :category, :fixed_version])
394 if (idx = issue_ids.index(@issue.id)) && idx < limit
399 if (idx = issue_ids.index(@issue.id)) && idx < limit
395 if issue_ids.size < 500
400 if issue_ids.size < 500
396 @issue_position = idx + 1
401 @issue_position = idx + 1
397 @issue_count = issue_ids.size
402 @issue_count = issue_ids.size
398 end
403 end
399 @prev_issue_id = issue_ids[idx - 1] if idx > 0
404 @prev_issue_id = issue_ids[idx - 1] if idx > 0
400 @next_issue_id = issue_ids[idx + 1] if idx < (issue_ids.size - 1)
405 @next_issue_id = issue_ids[idx + 1] if idx < (issue_ids.size - 1)
401 end
406 end
402 end
407 end
403 end
408 end
404 end
409 end
405
410
406 def previous_and_next_issue_ids_params
411 def previous_and_next_issue_ids_params
407 {
412 {
408 :prev_issue_id => params[:prev_issue_id],
413 :prev_issue_id => params[:prev_issue_id],
409 :next_issue_id => params[:next_issue_id],
414 :next_issue_id => params[:next_issue_id],
410 :issue_position => params[:issue_position],
415 :issue_position => params[:issue_position],
411 :issue_count => params[:issue_count]
416 :issue_count => params[:issue_count]
412 }.reject {|k,v| k.blank?}
417 }.reject {|k,v| k.blank?}
413 end
418 end
414
419
415 # Used by #edit and #update to set some common instance variables
420 # Used by #edit and #update to set some common instance variables
416 # from the params
421 # from the params
417 def update_issue_from_params
422 def update_issue_from_params
418 @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
423 @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
419 if params[:time_entry]
424 if params[:time_entry]
420 @time_entry.safe_attributes = params[:time_entry]
425 @time_entry.safe_attributes = params[:time_entry]
421 end
426 end
422
427
423 @issue.init_journal(User.current)
428 @issue.init_journal(User.current)
424
429
425 issue_attributes = params[:issue]
430 issue_attributes = params[:issue]
426 if issue_attributes && params[:conflict_resolution]
431 if issue_attributes && params[:conflict_resolution]
427 case params[:conflict_resolution]
432 case params[:conflict_resolution]
428 when 'overwrite'
433 when 'overwrite'
429 issue_attributes = issue_attributes.dup
434 issue_attributes = issue_attributes.dup
430 issue_attributes.delete(:lock_version)
435 issue_attributes.delete(:lock_version)
431 when 'add_notes'
436 when 'add_notes'
432 issue_attributes = issue_attributes.slice(:notes, :private_notes)
437 issue_attributes = issue_attributes.slice(:notes, :private_notes)
433 when 'cancel'
438 when 'cancel'
434 redirect_to issue_path(@issue)
439 redirect_to issue_path(@issue)
435 return false
440 return false
436 end
441 end
437 end
442 end
438 @issue.safe_attributes = issue_attributes
443 @issue.safe_attributes = issue_attributes
439 @priorities = IssuePriority.active
444 @priorities = IssuePriority.active
440 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
445 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
441 true
446 true
442 end
447 end
443
448
444 # Used by #new and #create to build a new issue from the params
449 # Used by #new and #create to build a new issue from the params
445 # The new issue will be copied from an existing one if copy_from parameter is given
450 # The new issue will be copied from an existing one if copy_from parameter is given
446 def build_new_issue_from_params
451 def build_new_issue_from_params
447 @issue = Issue.new
452 @issue = Issue.new
448 if params[:copy_from]
453 if params[:copy_from]
449 begin
454 begin
450 @issue.init_journal(User.current)
455 @issue.init_journal(User.current)
451 @copy_from = Issue.visible.find(params[:copy_from])
456 @copy_from = Issue.visible.find(params[:copy_from])
452 unless User.current.allowed_to?(:copy_issues, @copy_from.project)
457 unless User.current.allowed_to?(:copy_issues, @copy_from.project)
453 raise ::Unauthorized
458 raise ::Unauthorized
454 end
459 end
455 @link_copy = link_copy?(params[:link_copy]) || request.get?
460 @link_copy = link_copy?(params[:link_copy]) || request.get?
456 @copy_attachments = params[:copy_attachments].present? || request.get?
461 @copy_attachments = params[:copy_attachments].present? || request.get?
457 @copy_subtasks = params[:copy_subtasks].present? || request.get?
462 @copy_subtasks = params[:copy_subtasks].present? || request.get?
458 @issue.copy_from(@copy_from, :attachments => @copy_attachments, :subtasks => @copy_subtasks, :link => @link_copy)
463 @issue.copy_from(@copy_from, :attachments => @copy_attachments, :subtasks => @copy_subtasks, :link => @link_copy)
459 @issue.parent_issue_id = @copy_from.parent_id
464 @issue.parent_issue_id = @copy_from.parent_id
460 rescue ActiveRecord::RecordNotFound
465 rescue ActiveRecord::RecordNotFound
461 render_404
466 render_404
462 return
467 return
463 end
468 end
464 end
469 end
465 @issue.project = @project
470 @issue.project = @project
466 if request.get?
471 if request.get?
467 @issue.project ||= @issue.allowed_target_projects.first
472 @issue.project ||= @issue.allowed_target_projects.first
468 end
473 end
469 @issue.author ||= User.current
474 @issue.author ||= User.current
470 @issue.start_date ||= User.current.today if Setting.default_issue_start_date_to_creation_date?
475 @issue.start_date ||= User.current.today if Setting.default_issue_start_date_to_creation_date?
471
476
472 attrs = (params[:issue] || {}).deep_dup
477 attrs = (params[:issue] || {}).deep_dup
473 if action_name == 'new' && params[:was_default_status] == attrs[:status_id]
478 if action_name == 'new' && params[:was_default_status] == attrs[:status_id]
474 attrs.delete(:status_id)
479 attrs.delete(:status_id)
475 end
480 end
476 if action_name == 'new' && params[:form_update_triggered_by] == 'issue_project_id'
481 if action_name == 'new' && params[:form_update_triggered_by] == 'issue_project_id'
477 # Discard submitted version when changing the project on the issue form
482 # Discard submitted version when changing the project on the issue form
478 # so we can use the default version for the new project
483 # so we can use the default version for the new project
479 attrs.delete(:fixed_version_id)
484 attrs.delete(:fixed_version_id)
480 end
485 end
481 @issue.safe_attributes = attrs
486 @issue.safe_attributes = attrs
482
487
483 if @issue.project
488 if @issue.project
484 @issue.tracker ||= @issue.allowed_target_trackers.first
489 @issue.tracker ||= @issue.allowed_target_trackers.first
485 if @issue.tracker.nil?
490 if @issue.tracker.nil?
486 if @issue.project.trackers.any?
491 if @issue.project.trackers.any?
487 # None of the project trackers is allowed to the user
492 # None of the project trackers is allowed to the user
488 render_error :message => l(:error_no_tracker_allowed_for_new_issue_in_project), :status => 403
493 render_error :message => l(:error_no_tracker_allowed_for_new_issue_in_project), :status => 403
489 else
494 else
490 # Project has no trackers
495 # Project has no trackers
491 render_error l(:error_no_tracker_in_project)
496 render_error l(:error_no_tracker_in_project)
492 end
497 end
493 return false
498 return false
494 end
499 end
495 if @issue.status.nil?
500 if @issue.status.nil?
496 render_error l(:error_no_default_issue_status)
501 render_error l(:error_no_default_issue_status)
497 return false
502 return false
498 end
503 end
499 elsif request.get?
504 elsif request.get?
500 render_error :message => l(:error_no_projects_with_tracker_allowed_for_new_issue), :status => 403
505 render_error :message => l(:error_no_projects_with_tracker_allowed_for_new_issue), :status => 403
501 return false
506 return false
502 end
507 end
503
508
504 @priorities = IssuePriority.active
509 @priorities = IssuePriority.active
505 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
510 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
506 end
511 end
507
512
508 # Saves @issue and a time_entry from the parameters
513 # Saves @issue and a time_entry from the parameters
509 def save_issue_with_child_records
514 def save_issue_with_child_records
510 Issue.transaction do
515 Issue.transaction do
511 if params[:time_entry] && (params[:time_entry][:hours].present? || params[:time_entry][:comments].present?) && User.current.allowed_to?(:log_time, @issue.project)
516 if params[:time_entry] && (params[:time_entry][:hours].present? || params[:time_entry][:comments].present?) && User.current.allowed_to?(:log_time, @issue.project)
512 time_entry = @time_entry || TimeEntry.new
517 time_entry = @time_entry || TimeEntry.new
513 time_entry.project = @issue.project
518 time_entry.project = @issue.project
514 time_entry.issue = @issue
519 time_entry.issue = @issue
515 time_entry.user = User.current
520 time_entry.user = User.current
516 time_entry.spent_on = User.current.today
521 time_entry.spent_on = User.current.today
517 time_entry.attributes = params[:time_entry]
522 time_entry.attributes = params[:time_entry]
518 @issue.time_entries << time_entry
523 @issue.time_entries << time_entry
519 end
524 end
520
525
521 call_hook(:controller_issues_edit_before_save, { :params => params, :issue => @issue, :time_entry => time_entry, :journal => @issue.current_journal})
526 call_hook(:controller_issues_edit_before_save, { :params => params, :issue => @issue, :time_entry => time_entry, :journal => @issue.current_journal})
522 if @issue.save
527 if @issue.save
523 call_hook(:controller_issues_edit_after_save, { :params => params, :issue => @issue, :time_entry => time_entry, :journal => @issue.current_journal})
528 call_hook(:controller_issues_edit_after_save, { :params => params, :issue => @issue, :time_entry => time_entry, :journal => @issue.current_journal})
524 else
529 else
525 raise ActiveRecord::Rollback
530 raise ActiveRecord::Rollback
526 end
531 end
527 end
532 end
528 end
533 end
529
534
530 # Returns true if the issue copy should be linked
535 # Returns true if the issue copy should be linked
531 # to the original issue
536 # to the original issue
532 def link_copy?(param)
537 def link_copy?(param)
533 case Setting.link_copied_issue
538 case Setting.link_copied_issue
534 when 'yes'
539 when 'yes'
535 true
540 true
536 when 'no'
541 when 'no'
537 false
542 false
538 when 'ask'
543 when 'ask'
539 param == '1'
544 param == '1'
540 end
545 end
541 end
546 end
542
547
543 # Redirects user after a successful issue creation
548 # Redirects user after a successful issue creation
544 def redirect_after_create
549 def redirect_after_create
545 if params[:continue]
550 if params[:continue]
546 attrs = {:tracker_id => @issue.tracker, :parent_issue_id => @issue.parent_issue_id}.reject {|k,v| v.nil?}
551 attrs = {:tracker_id => @issue.tracker, :parent_issue_id => @issue.parent_issue_id}.reject {|k,v| v.nil?}
547 if params[:project_id]
552 if params[:project_id]
548 redirect_to new_project_issue_path(@issue.project, :issue => attrs)
553 redirect_to new_project_issue_path(@issue.project, :issue => attrs)
549 else
554 else
550 attrs.merge! :project_id => @issue.project_id
555 attrs.merge! :project_id => @issue.project_id
551 redirect_to new_issue_path(:issue => attrs)
556 redirect_to new_issue_path(:issue => attrs)
552 end
557 end
553 else
558 else
554 redirect_to issue_path(@issue)
559 redirect_to issue_path(@issue)
555 end
560 end
556 end
561 end
557 end
562 end
@@ -1,162 +1,162
1 <%= render :partial => 'action_menu' %>
1 <%= render :partial => 'action_menu' %>
2
2
3 <h2><%= issue_heading(@issue) %></h2>
3 <h2><%= issue_heading(@issue) %></h2>
4
4
5 <div class="<%= @issue.css_classes %> details">
5 <div class="<%= @issue.css_classes %> details">
6 <% if @prev_issue_id || @next_issue_id %>
6 <% if @prev_issue_id || @next_issue_id %>
7 <div class="next-prev-links contextual">
7 <div class="next-prev-links contextual">
8 <%= link_to_if @prev_issue_id,
8 <%= link_to_if @prev_issue_id,
9 "\xc2\xab #{l(:label_previous)}",
9 "\xc2\xab #{l(:label_previous)}",
10 (@prev_issue_id ? issue_path(@prev_issue_id) : nil),
10 (@prev_issue_id ? issue_path(@prev_issue_id) : nil),
11 :title => "##{@prev_issue_id}",
11 :title => "##{@prev_issue_id}",
12 :accesskey => accesskey(:previous) %> |
12 :accesskey => accesskey(:previous) %> |
13 <% if @issue_position && @issue_count %>
13 <% if @issue_position && @issue_count %>
14 <span class="position"><%= l(:label_item_position, :position => @issue_position, :count => @issue_count) %></span> |
14 <span class="position"><%= l(:label_item_position, :position => @issue_position, :count => @issue_count) %></span> |
15 <% end %>
15 <% end %>
16 <%= link_to_if @next_issue_id,
16 <%= link_to_if @next_issue_id,
17 "#{l(:label_next)} \xc2\xbb",
17 "#{l(:label_next)} \xc2\xbb",
18 (@next_issue_id ? issue_path(@next_issue_id) : nil),
18 (@next_issue_id ? issue_path(@next_issue_id) : nil),
19 :title => "##{@next_issue_id}",
19 :title => "##{@next_issue_id}",
20 :accesskey => accesskey(:next) %>
20 :accesskey => accesskey(:next) %>
21 </div>
21 </div>
22 <% end %>
22 <% end %>
23
23
24 <%= avatar(@issue.author, :size => "50") %>
24 <%= avatar(@issue.author, :size => "50") %>
25
25
26 <div class="subject">
26 <div class="subject">
27 <%= render_issue_subject_with_tree(@issue) %>
27 <%= render_issue_subject_with_tree(@issue) %>
28 </div>
28 </div>
29 <p class="author">
29 <p class="author">
30 <%= authoring @issue.created_on, @issue.author %>.
30 <%= authoring @issue.created_on, @issue.author %>.
31 <% if @issue.created_on != @issue.updated_on %>
31 <% if @issue.created_on != @issue.updated_on %>
32 <%= l(:label_updated_time, time_tag(@issue.updated_on)).html_safe %>.
32 <%= l(:label_updated_time, time_tag(@issue.updated_on)).html_safe %>.
33 <% end %>
33 <% end %>
34 </p>
34 </p>
35
35
36 <div class="attributes">
36 <div class="attributes">
37 <%= issue_fields_rows do |rows|
37 <%= issue_fields_rows do |rows|
38 rows.left l(:field_status), @issue.status.name, :class => 'status'
38 rows.left l(:field_status), @issue.status.name, :class => 'status'
39 rows.left l(:field_priority), @issue.priority.name, :class => 'priority'
39 rows.left l(:field_priority), @issue.priority.name, :class => 'priority'
40
40
41 unless @issue.disabled_core_fields.include?('assigned_to_id')
41 unless @issue.disabled_core_fields.include?('assigned_to_id')
42 rows.left l(:field_assigned_to), avatar(@issue.assigned_to, :size => "14").to_s.html_safe + (@issue.assigned_to ? link_to_user(@issue.assigned_to) : "-"), :class => 'assigned-to'
42 rows.left l(:field_assigned_to), avatar(@issue.assigned_to, :size => "14").to_s.html_safe + (@issue.assigned_to ? link_to_user(@issue.assigned_to) : "-"), :class => 'assigned-to'
43 end
43 end
44 unless @issue.disabled_core_fields.include?('category_id') || (@issue.category.nil? && @issue.project.issue_categories.none?)
44 unless @issue.disabled_core_fields.include?('category_id') || (@issue.category.nil? && @issue.project.issue_categories.none?)
45 rows.left l(:field_category), (@issue.category ? @issue.category.name : "-"), :class => 'category'
45 rows.left l(:field_category), (@issue.category ? @issue.category.name : "-"), :class => 'category'
46 end
46 end
47 unless @issue.disabled_core_fields.include?('fixed_version_id') || (@issue.fixed_version.nil? && @issue.assignable_versions.none?)
47 unless @issue.disabled_core_fields.include?('fixed_version_id') || (@issue.fixed_version.nil? && @issue.assignable_versions.none?)
48 rows.left l(:field_fixed_version), (@issue.fixed_version ? link_to_version(@issue.fixed_version) : "-"), :class => 'fixed-version'
48 rows.left l(:field_fixed_version), (@issue.fixed_version ? link_to_version(@issue.fixed_version) : "-"), :class => 'fixed-version'
49 end
49 end
50
50
51 unless @issue.disabled_core_fields.include?('start_date')
51 unless @issue.disabled_core_fields.include?('start_date')
52 rows.right l(:field_start_date), format_date(@issue.start_date), :class => 'start-date'
52 rows.right l(:field_start_date), format_date(@issue.start_date), :class => 'start-date'
53 end
53 end
54 unless @issue.disabled_core_fields.include?('due_date')
54 unless @issue.disabled_core_fields.include?('due_date')
55 rows.right l(:field_due_date), format_date(@issue.due_date), :class => 'due-date'
55 rows.right l(:field_due_date), format_date(@issue.due_date), :class => 'due-date'
56 end
56 end
57 unless @issue.disabled_core_fields.include?('done_ratio')
57 unless @issue.disabled_core_fields.include?('done_ratio')
58 rows.right l(:field_done_ratio), progress_bar(@issue.done_ratio, :legend => "#{@issue.done_ratio}%"), :class => 'progress'
58 rows.right l(:field_done_ratio), progress_bar(@issue.done_ratio, :legend => "#{@issue.done_ratio}%"), :class => 'progress'
59 end
59 end
60 unless @issue.disabled_core_fields.include?('estimated_hours')
60 unless @issue.disabled_core_fields.include?('estimated_hours')
61 if @issue.estimated_hours.present? || @issue.total_estimated_hours.to_f > 0
61 if @issue.estimated_hours.present? || @issue.total_estimated_hours.to_f > 0
62 rows.right l(:field_estimated_hours), issue_estimated_hours_details(@issue), :class => 'estimated-hours'
62 rows.right l(:field_estimated_hours), issue_estimated_hours_details(@issue), :class => 'estimated-hours'
63 end
63 end
64 end
64 end
65 if User.current.allowed_to_view_all_time_entries?(@project)
65 if User.current.allowed_to?(:view_time_entries, @project)
66 if @issue.total_spent_hours > 0
66 if @issue.total_spent_hours > 0
67 rows.right l(:label_spent_time), issue_spent_hours_details(@issue), :class => 'spent-time'
67 rows.right l(:label_spent_time), issue_spent_hours_details(@issue), :class => 'spent-time'
68 end
68 end
69 end
69 end
70 end %>
70 end %>
71 <%= render_custom_fields_rows(@issue) %>
71 <%= render_custom_fields_rows(@issue) %>
72 <%= call_hook(:view_issues_show_details_bottom, :issue => @issue) %>
72 <%= call_hook(:view_issues_show_details_bottom, :issue => @issue) %>
73 </div>
73 </div>
74
74
75 <% if @issue.description? || @issue.attachments.any? -%>
75 <% if @issue.description? || @issue.attachments.any? -%>
76 <hr />
76 <hr />
77 <% if @issue.description? %>
77 <% if @issue.description? %>
78 <div class="description">
78 <div class="description">
79 <div class="contextual">
79 <div class="contextual">
80 <%= link_to l(:button_quote), quoted_issue_path(@issue), :remote => true, :method => 'post', :class => 'icon icon-comment' if @issue.notes_addable? %>
80 <%= link_to l(:button_quote), quoted_issue_path(@issue), :remote => true, :method => 'post', :class => 'icon icon-comment' if @issue.notes_addable? %>
81 </div>
81 </div>
82
82
83 <p><strong><%=l(:field_description)%></strong></p>
83 <p><strong><%=l(:field_description)%></strong></p>
84 <div class="wiki">
84 <div class="wiki">
85 <%= textilizable @issue, :description, :attachments => @issue.attachments %>
85 <%= textilizable @issue, :description, :attachments => @issue.attachments %>
86 </div>
86 </div>
87 </div>
87 </div>
88 <% end %>
88 <% end %>
89 <%= link_to_attachments @issue, :thumbnails => true %>
89 <%= link_to_attachments @issue, :thumbnails => true %>
90 <% end -%>
90 <% end -%>
91
91
92 <%= call_hook(:view_issues_show_description_bottom, :issue => @issue) %>
92 <%= call_hook(:view_issues_show_description_bottom, :issue => @issue) %>
93
93
94 <% if !@issue.leaf? || User.current.allowed_to?(:manage_subtasks, @project) %>
94 <% if !@issue.leaf? || User.current.allowed_to?(:manage_subtasks, @project) %>
95 <hr />
95 <hr />
96 <div id="issue_tree">
96 <div id="issue_tree">
97 <div class="contextual">
97 <div class="contextual">
98 <%= link_to_new_subtask(@issue) if User.current.allowed_to?(:manage_subtasks, @project) %>
98 <%= link_to_new_subtask(@issue) if User.current.allowed_to?(:manage_subtasks, @project) %>
99 </div>
99 </div>
100 <p><strong><%=l(:label_subtask_plural)%></strong></p>
100 <p><strong><%=l(:label_subtask_plural)%></strong></p>
101 <%= render_descendants_tree(@issue) unless @issue.leaf? %>
101 <%= render_descendants_tree(@issue) unless @issue.leaf? %>
102 </div>
102 </div>
103 <% end %>
103 <% end %>
104
104
105 <% if @relations.present? || User.current.allowed_to?(:manage_issue_relations, @project) %>
105 <% if @relations.present? || User.current.allowed_to?(:manage_issue_relations, @project) %>
106 <hr />
106 <hr />
107 <div id="relations">
107 <div id="relations">
108 <%= render :partial => 'relations' %>
108 <%= render :partial => 'relations' %>
109 </div>
109 </div>
110 <% end %>
110 <% end %>
111
111
112 </div>
112 </div>
113
113
114 <% if @changesets.present? %>
114 <% if @changesets.present? %>
115 <div id="issue-changesets">
115 <div id="issue-changesets">
116 <h3><%=l(:label_associated_revisions)%></h3>
116 <h3><%=l(:label_associated_revisions)%></h3>
117 <%= render :partial => 'changesets', :locals => { :changesets => @changesets} %>
117 <%= render :partial => 'changesets', :locals => { :changesets => @changesets} %>
118 </div>
118 </div>
119 <% end %>
119 <% end %>
120
120
121 <% if @journals.present? %>
121 <% if @journals.present? %>
122 <div id="history">
122 <div id="history">
123 <h3><%=l(:label_history)%></h3>
123 <h3><%=l(:label_history)%></h3>
124 <%= render :partial => 'history', :locals => { :issue => @issue, :journals => @journals } %>
124 <%= render :partial => 'history', :locals => { :issue => @issue, :journals => @journals } %>
125 </div>
125 </div>
126 <% end %>
126 <% end %>
127
127
128
128
129 <div style="clear: both;"></div>
129 <div style="clear: both;"></div>
130 <%= render :partial => 'action_menu' %>
130 <%= render :partial => 'action_menu' %>
131
131
132 <div style="clear: both;"></div>
132 <div style="clear: both;"></div>
133 <% if @issue.editable? %>
133 <% if @issue.editable? %>
134 <div id="update" style="display:none;">
134 <div id="update" style="display:none;">
135 <h3><%= l(:button_edit) %></h3>
135 <h3><%= l(:button_edit) %></h3>
136 <%= render :partial => 'edit' %>
136 <%= render :partial => 'edit' %>
137 </div>
137 </div>
138 <% end %>
138 <% end %>
139
139
140 <% other_formats_links do |f| %>
140 <% other_formats_links do |f| %>
141 <%= f.link_to 'Atom', :url => {:key => User.current.rss_key} %>
141 <%= f.link_to 'Atom', :url => {:key => User.current.rss_key} %>
142 <%= f.link_to 'PDF' %>
142 <%= f.link_to 'PDF' %>
143 <% end %>
143 <% end %>
144
144
145 <% html_title "#{@issue.tracker.name} ##{@issue.id}: #{@issue.subject}" %>
145 <% html_title "#{@issue.tracker.name} ##{@issue.id}: #{@issue.subject}" %>
146
146
147 <% content_for :sidebar do %>
147 <% content_for :sidebar do %>
148 <%= render :partial => 'issues/sidebar' %>
148 <%= render :partial => 'issues/sidebar' %>
149
149
150 <% if User.current.allowed_to?(:add_issue_watchers, @project) ||
150 <% if User.current.allowed_to?(:add_issue_watchers, @project) ||
151 (@issue.watchers.present? && User.current.allowed_to?(:view_issue_watchers, @project)) %>
151 (@issue.watchers.present? && User.current.allowed_to?(:view_issue_watchers, @project)) %>
152 <div id="watchers">
152 <div id="watchers">
153 <%= render :partial => 'watchers/watchers', :locals => {:watched => @issue} %>
153 <%= render :partial => 'watchers/watchers', :locals => {:watched => @issue} %>
154 </div>
154 </div>
155 <% end %>
155 <% end %>
156 <% end %>
156 <% end %>
157
157
158 <% content_for :header_tags do %>
158 <% content_for :header_tags do %>
159 <%= auto_discovery_link_tag(:atom, {:format => 'atom', :key => User.current.rss_key}, :title => "#{@issue.project} - #{@issue.tracker} ##{@issue.id}: #{@issue.subject}") %>
159 <%= auto_discovery_link_tag(:atom, {:format => 'atom', :key => User.current.rss_key}, :title => "#{@issue.project} - #{@issue.tracker} ##{@issue.id}: #{@issue.subject}") %>
160 <% end %>
160 <% end %>
161
161
162 <%= context_menu issues_context_menu_path %>
162 <%= context_menu issues_context_menu_path %>
General Comments 0
You need to be logged in to leave comments. Login now