##// END OF EJS Templates
Refactor: Extracted the duplication from the last commit into a new method...
Eric Davis -
r3373:3de1f2e15748
parent child
Show More
@@ -1,587 +1,582
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
2 # Copyright (C) 2006-2008 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 menu_item :new_issue, :only => :new
19 menu_item :new_issue, :only => :new
20 default_search_scope :issues
20 default_search_scope :issues
21
21
22 before_filter :find_issue, :only => [:show, :edit, :update, :reply]
22 before_filter :find_issue, :only => [:show, :edit, :update, :reply]
23 before_filter :find_issues, :only => [:bulk_edit, :move, :destroy]
23 before_filter :find_issues, :only => [:bulk_edit, :move, :destroy]
24 before_filter :find_project, :only => [:new, :update_form, :preview]
24 before_filter :find_project, :only => [:new, :update_form, :preview]
25 before_filter :authorize, :except => [:index, :changes, :gantt, :calendar, :preview, :context_menu]
25 before_filter :authorize, :except => [:index, :changes, :gantt, :calendar, :preview, :context_menu]
26 before_filter :find_optional_project, :only => [:index, :changes, :gantt, :calendar]
26 before_filter :find_optional_project, :only => [:index, :changes, :gantt, :calendar]
27 accept_key_auth :index, :show, :changes
27 accept_key_auth :index, :show, :changes
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 include ProjectsHelper
33 include ProjectsHelper
34 helper :custom_fields
34 helper :custom_fields
35 include CustomFieldsHelper
35 include CustomFieldsHelper
36 helper :issue_relations
36 helper :issue_relations
37 include IssueRelationsHelper
37 include IssueRelationsHelper
38 helper :watchers
38 helper :watchers
39 include WatchersHelper
39 include WatchersHelper
40 helper :attachments
40 helper :attachments
41 include AttachmentsHelper
41 include AttachmentsHelper
42 helper :queries
42 helper :queries
43 include QueriesHelper
43 include QueriesHelper
44 helper :sort
44 helper :sort
45 include SortHelper
45 include SortHelper
46 include IssuesHelper
46 include IssuesHelper
47 helper :timelog
47 helper :timelog
48 include Redmine::Export::PDF
48 include Redmine::Export::PDF
49
49
50 verify :method => [:post, :delete],
50 verify :method => [:post, :delete],
51 :only => :destroy,
51 :only => :destroy,
52 :render => { :nothing => true, :status => :method_not_allowed }
52 :render => { :nothing => true, :status => :method_not_allowed }
53
53
54 def index
54 def index
55 retrieve_query
55 retrieve_query
56 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
56 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
57 sort_update({'id' => "#{Issue.table_name}.id"}.merge(@query.available_columns.inject({}) {|h, c| h[c.name.to_s] = c.sortable; h}))
57 sort_update({'id' => "#{Issue.table_name}.id"}.merge(@query.available_columns.inject({}) {|h, c| h[c.name.to_s] = c.sortable; h}))
58
58
59 if @query.valid?
59 if @query.valid?
60 limit = case params[:format]
60 limit = case params[:format]
61 when 'csv', 'pdf'
61 when 'csv', 'pdf'
62 Setting.issues_export_limit.to_i
62 Setting.issues_export_limit.to_i
63 when 'atom'
63 when 'atom'
64 Setting.feeds_limit.to_i
64 Setting.feeds_limit.to_i
65 else
65 else
66 per_page_option
66 per_page_option
67 end
67 end
68
68
69 @issue_count = @query.issue_count
69 @issue_count = @query.issue_count
70 @issue_pages = Paginator.new self, @issue_count, limit, params['page']
70 @issue_pages = Paginator.new self, @issue_count, limit, params['page']
71 @issues = @query.issues(:include => [:assigned_to, :tracker, :priority, :category, :fixed_version],
71 @issues = @query.issues(:include => [:assigned_to, :tracker, :priority, :category, :fixed_version],
72 :order => sort_clause,
72 :order => sort_clause,
73 :offset => @issue_pages.current.offset,
73 :offset => @issue_pages.current.offset,
74 :limit => limit)
74 :limit => limit)
75 @issue_count_by_group = @query.issue_count_by_group
75 @issue_count_by_group = @query.issue_count_by_group
76
76
77 respond_to do |format|
77 respond_to do |format|
78 format.html { render :template => 'issues/index.rhtml', :layout => !request.xhr? }
78 format.html { render :template => 'issues/index.rhtml', :layout => !request.xhr? }
79 format.xml { render :layout => false }
79 format.xml { render :layout => false }
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(issues_to_csv(@issues, @project), :type => 'text/csv; header=present', :filename => 'export.csv') }
81 format.csv { send_data(issues_to_csv(@issues, @project), :type => 'text/csv; header=present', :filename => 'export.csv') }
82 format.pdf { send_data(issues_to_pdf(@issues, @project, @query), :type => 'application/pdf', :filename => 'export.pdf') }
82 format.pdf { send_data(issues_to_pdf(@issues, @project, @query), :type => 'application/pdf', :filename => 'export.pdf') }
83 end
83 end
84 else
84 else
85 # Send html if the query is not valid
85 # Send html if the query is not valid
86 render(:template => 'issues/index.rhtml', :layout => !request.xhr?)
86 render(:template => 'issues/index.rhtml', :layout => !request.xhr?)
87 end
87 end
88 rescue ActiveRecord::RecordNotFound
88 rescue ActiveRecord::RecordNotFound
89 render_404
89 render_404
90 end
90 end
91
91
92 def changes
92 def changes
93 retrieve_query
93 retrieve_query
94 sort_init 'id', 'desc'
94 sort_init 'id', 'desc'
95 sort_update({'id' => "#{Issue.table_name}.id"}.merge(@query.available_columns.inject({}) {|h, c| h[c.name.to_s] = c.sortable; h}))
95 sort_update({'id' => "#{Issue.table_name}.id"}.merge(@query.available_columns.inject({}) {|h, c| h[c.name.to_s] = c.sortable; h}))
96
96
97 if @query.valid?
97 if @query.valid?
98 @journals = @query.journals(:order => "#{Journal.table_name}.created_on DESC",
98 @journals = @query.journals(:order => "#{Journal.table_name}.created_on DESC",
99 :limit => 25)
99 :limit => 25)
100 end
100 end
101 @title = (@project ? @project.name : Setting.app_title) + ": " + (@query.new_record? ? l(:label_changes_details) : @query.name)
101 @title = (@project ? @project.name : Setting.app_title) + ": " + (@query.new_record? ? l(:label_changes_details) : @query.name)
102 render :layout => false, :content_type => 'application/atom+xml'
102 render :layout => false, :content_type => 'application/atom+xml'
103 rescue ActiveRecord::RecordNotFound
103 rescue ActiveRecord::RecordNotFound
104 render_404
104 render_404
105 end
105 end
106
106
107 def show
107 def show
108 @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
108 @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
109 @journals.each_with_index {|j,i| j.indice = i+1}
109 @journals.each_with_index {|j,i| j.indice = i+1}
110 @journals.reverse! if User.current.wants_comments_in_reverse_order?
110 @journals.reverse! if User.current.wants_comments_in_reverse_order?
111 @changesets = @issue.changesets.visible.all
111 @changesets = @issue.changesets.visible.all
112 @changesets.reverse! if User.current.wants_comments_in_reverse_order?
112 @changesets.reverse! if User.current.wants_comments_in_reverse_order?
113 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
113 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
114 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
114 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
115 @priorities = IssuePriority.all
115 @priorities = IssuePriority.all
116 @time_entry = TimeEntry.new
116 @time_entry = TimeEntry.new
117 respond_to do |format|
117 respond_to do |format|
118 format.html { render :template => 'issues/show.rhtml' }
118 format.html { render :template => 'issues/show.rhtml' }
119 format.xml { render :layout => false }
119 format.xml { render :layout => false }
120 format.atom { render :action => 'changes', :layout => false, :content_type => 'application/atom+xml' }
120 format.atom { render :action => 'changes', :layout => false, :content_type => 'application/atom+xml' }
121 format.pdf { send_data(issue_to_pdf(@issue), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") }
121 format.pdf { send_data(issue_to_pdf(@issue), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") }
122 end
122 end
123 end
123 end
124
124
125 # Add a new issue
125 # Add a new issue
126 # The new issue will be created from an existing one if copy_from parameter is given
126 # The new issue will be created from an existing one if copy_from parameter is given
127 def new
127 def new
128 @issue = Issue.new
128 @issue = Issue.new
129 @issue.copy_from(params[:copy_from]) if params[:copy_from]
129 @issue.copy_from(params[:copy_from]) if params[:copy_from]
130 @issue.project = @project
130 @issue.project = @project
131 # Tracker must be set before custom field values
131 # Tracker must be set before custom field values
132 @issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first)
132 @issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first)
133 if @issue.tracker.nil?
133 if @issue.tracker.nil?
134 render_error l(:error_no_tracker_in_project)
134 render_error l(:error_no_tracker_in_project)
135 return
135 return
136 end
136 end
137 if params[:issue].is_a?(Hash)
137 if params[:issue].is_a?(Hash)
138 @issue.safe_attributes = params[:issue]
138 @issue.safe_attributes = params[:issue]
139 @issue.watcher_user_ids = params[:issue]['watcher_user_ids'] if User.current.allowed_to?(:add_issue_watchers, @project)
139 @issue.watcher_user_ids = params[:issue]['watcher_user_ids'] if User.current.allowed_to?(:add_issue_watchers, @project)
140 end
140 end
141 @issue.author = User.current
141 @issue.author = User.current
142
142
143 default_status = IssueStatus.default
143 default_status = IssueStatus.default
144 unless default_status
144 unless default_status
145 render_error l(:error_no_default_issue_status)
145 render_error l(:error_no_default_issue_status)
146 return
146 return
147 end
147 end
148 @issue.status = default_status
148 @issue.status = default_status
149 @allowed_statuses = ([default_status] + default_status.find_new_statuses_allowed_to(User.current.roles_for_project(@project), @issue.tracker)).uniq
149 @allowed_statuses = ([default_status] + default_status.find_new_statuses_allowed_to(User.current.roles_for_project(@project), @issue.tracker)).uniq
150
150
151 if request.get? || request.xhr?
151 if request.get? || request.xhr?
152 @issue.start_date ||= Date.today
152 @issue.start_date ||= Date.today
153 else
153 else
154 requested_status = IssueStatus.find_by_id(params[:issue][:status_id])
154 requested_status = IssueStatus.find_by_id(params[:issue][:status_id])
155 # Check that the user is allowed to apply the requested status
155 # Check that the user is allowed to apply the requested status
156 @issue.status = (@allowed_statuses.include? requested_status) ? requested_status : default_status
156 @issue.status = (@allowed_statuses.include? requested_status) ? requested_status : default_status
157 call_hook(:controller_issues_new_before_save, { :params => params, :issue => @issue })
157 call_hook(:controller_issues_new_before_save, { :params => params, :issue => @issue })
158 if @issue.save
158 if @issue.save
159 attach_files(@issue, params[:attachments])
159 attach_files(@issue, params[:attachments])
160 flash[:notice] = l(:notice_successful_create)
160 flash[:notice] = l(:notice_successful_create)
161 call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue})
161 call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue})
162 respond_to do |format|
162 respond_to do |format|
163 format.html {
163 format.html {
164 redirect_to(params[:continue] ? { :action => 'new', :tracker_id => @issue.tracker } :
164 redirect_to(params[:continue] ? { :action => 'new', :tracker_id => @issue.tracker } :
165 { :action => 'show', :id => @issue })
165 { :action => 'show', :id => @issue })
166 }
166 }
167 format.xml { render :action => 'show', :status => :created, :location => url_for(:controller => 'issues', :action => 'show', :id => @issue) }
167 format.xml { render :action => 'show', :status => :created, :location => url_for(:controller => 'issues', :action => 'show', :id => @issue) }
168 end
168 end
169 return
169 return
170 else
170 else
171 respond_to do |format|
171 respond_to do |format|
172 format.html { }
172 format.html { }
173 format.xml { render(:xml => @issue.errors, :status => :unprocessable_entity); return }
173 format.xml { render(:xml => @issue.errors, :status => :unprocessable_entity); return }
174 end
174 end
175 end
175 end
176 end
176 end
177 @priorities = IssuePriority.all
177 @priorities = IssuePriority.all
178 render :layout => !request.xhr?
178 render :layout => !request.xhr?
179 end
179 end
180
180
181 # Attributes that can be updated on workflow transition (without :edit permission)
181 # Attributes that can be updated on workflow transition (without :edit permission)
182 # TODO: make it configurable (at least per role)
182 # TODO: make it configurable (at least per role)
183 UPDATABLE_ATTRS_ON_TRANSITION = %w(status_id assigned_to_id fixed_version_id done_ratio) unless const_defined?(:UPDATABLE_ATTRS_ON_TRANSITION)
183 UPDATABLE_ATTRS_ON_TRANSITION = %w(status_id assigned_to_id fixed_version_id done_ratio) unless const_defined?(:UPDATABLE_ATTRS_ON_TRANSITION)
184
184
185 def edit
185 def edit
186 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
186 update_issue_from_params
187 @priorities = IssuePriority.all
188 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
189 @time_entry = TimeEntry.new
190
191 @notes = params[:notes]
192 journal = @issue.init_journal(User.current, @notes)
193 # User can change issue attributes only if he has :edit permission or if a workflow transition is allowed
194 if (@edit_allowed || !@allowed_statuses.empty?) && params[:issue]
195 attrs = params[:issue].dup
196 attrs.delete_if {|k,v| !UPDATABLE_ATTRS_ON_TRANSITION.include?(k) } unless @edit_allowed
197 attrs.delete(:status_id) unless @allowed_statuses.detect {|s| s.id.to_s == attrs[:status_id].to_s}
198 @issue.safe_attributes = attrs
199 end
200
187
201 respond_to do |format|
188 respond_to do |format|
202 format.html { }
189 format.html { }
203 format.xml { }
190 format.xml { }
204 end
191 end
205 end
192 end
206
193
207 #--
194 #--
208 # Start converting to the Rails REST controllers
195 # Start converting to the Rails REST controllers
209 #++
196 #++
210 def update
197 def update
211 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
198 update_issue_from_params
212 @priorities = IssuePriority.all
213 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
214 @time_entry = TimeEntry.new
215
216 @notes = params[:notes]
217 journal = @issue.init_journal(User.current, @notes)
218 # User can change issue attributes only if he has :edit permission or if a workflow transition is allowed
219 if (@edit_allowed || !@allowed_statuses.empty?) && params[:issue]
220 attrs = params[:issue].dup
221 attrs.delete_if {|k,v| !UPDATABLE_ATTRS_ON_TRANSITION.include?(k) } unless @edit_allowed
222 attrs.delete(:status_id) unless @allowed_statuses.detect {|s| s.id.to_s == attrs[:status_id].to_s}
223 @issue.safe_attributes = attrs
224 end
225
199
226 if request.get?
200 if request.get?
227 # nop
201 # nop
228 else
202 else
229 @time_entry = TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => Date.today)
203 @time_entry = TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => Date.today)
230 @time_entry.attributes = params[:time_entry]
204 @time_entry.attributes = params[:time_entry]
231 if (@time_entry.hours.nil? || @time_entry.valid?) && @issue.valid?
205 if (@time_entry.hours.nil? || @time_entry.valid?) && @issue.valid?
232 attachments = attach_files(@issue, params[:attachments])
206 attachments = attach_files(@issue, params[:attachments])
233 attachments.each {|a| journal.details << JournalDetail.new(:property => 'attachment', :prop_key => a.id, :value => a.filename)}
207 attachments.each {|a| @journal.details << JournalDetail.new(:property => 'attachment', :prop_key => a.id, :value => a.filename)}
234 call_hook(:controller_issues_edit_before_save, { :params => params, :issue => @issue, :time_entry => @time_entry, :journal => journal})
208 call_hook(:controller_issues_edit_before_save, { :params => params, :issue => @issue, :time_entry => @time_entry, :journal => @journal})
235 if @issue.save
209 if @issue.save
236 # Log spend time
210 # Log spend time
237 if User.current.allowed_to?(:log_time, @project)
211 if User.current.allowed_to?(:log_time, @project)
238 @time_entry.save
212 @time_entry.save
239 end
213 end
240 if !journal.new_record?
214 if !@journal.new_record?
241 # Only send notification if something was actually changed
215 # Only send notification if something was actually changed
242 flash[:notice] = l(:notice_successful_update)
216 flash[:notice] = l(:notice_successful_update)
243 end
217 end
244 call_hook(:controller_issues_edit_after_save, { :params => params, :issue => @issue, :time_entry => @time_entry, :journal => journal})
218 call_hook(:controller_issues_edit_after_save, { :params => params, :issue => @issue, :time_entry => @time_entry, :journal => @journal})
245 respond_to do |format|
219 respond_to do |format|
246 format.html { redirect_back_or_default({:action => 'show', :id => @issue}) }
220 format.html { redirect_back_or_default({:action => 'show', :id => @issue}) }
247 format.xml { head :ok }
221 format.xml { head :ok }
248 end
222 end
249 return
223 return
250 end
224 end
251 end
225 end
252 # failure
226 # failure
253 respond_to do |format|
227 respond_to do |format|
254 format.html { render :action => 'edit' }
228 format.html { render :action => 'edit' }
255 format.xml { render :xml => @issue.errors, :status => :unprocessable_entity }
229 format.xml { render :xml => @issue.errors, :status => :unprocessable_entity }
256 end
230 end
257 end
231 end
258 rescue ActiveRecord::StaleObjectError
232 rescue ActiveRecord::StaleObjectError
259 # Optimistic locking exception
233 # Optimistic locking exception
260 flash.now[:error] = l(:notice_locking_conflict)
234 flash.now[:error] = l(:notice_locking_conflict)
261 # Remove the previously added attachments if issue was not updated
235 # Remove the previously added attachments if issue was not updated
262 attachments.each(&:destroy)
236 attachments.each(&:destroy)
263 end
237 end
264
238
265 def reply
239 def reply
266 journal = Journal.find(params[:journal_id]) if params[:journal_id]
240 journal = Journal.find(params[:journal_id]) if params[:journal_id]
267 if journal
241 if journal
268 user = journal.user
242 user = journal.user
269 text = journal.notes
243 text = journal.notes
270 else
244 else
271 user = @issue.author
245 user = @issue.author
272 text = @issue.description
246 text = @issue.description
273 end
247 end
274 content = "#{ll(Setting.default_language, :text_user_wrote, user)}\\n> "
248 content = "#{ll(Setting.default_language, :text_user_wrote, user)}\\n> "
275 content << text.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]').gsub('"', '\"').gsub(/(\r?\n|\r\n?)/, "\\n> ") + "\\n\\n"
249 content << text.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]').gsub('"', '\"').gsub(/(\r?\n|\r\n?)/, "\\n> ") + "\\n\\n"
276 render(:update) { |page|
250 render(:update) { |page|
277 page.<< "$('notes').value = \"#{content}\";"
251 page.<< "$('notes').value = \"#{content}\";"
278 page.show 'update'
252 page.show 'update'
279 page << "Form.Element.focus('notes');"
253 page << "Form.Element.focus('notes');"
280 page << "Element.scrollTo('update');"
254 page << "Element.scrollTo('update');"
281 page << "$('notes').scrollTop = $('notes').scrollHeight - $('notes').clientHeight;"
255 page << "$('notes').scrollTop = $('notes').scrollHeight - $('notes').clientHeight;"
282 }
256 }
283 end
257 end
284
258
285 # Bulk edit a set of issues
259 # Bulk edit a set of issues
286 def bulk_edit
260 def bulk_edit
287 if request.post?
261 if request.post?
288 attributes = (params[:issue] || {}).reject {|k,v| v.blank?}
262 attributes = (params[:issue] || {}).reject {|k,v| v.blank?}
289 attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
263 attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
290 attributes[:custom_field_values].reject! {|k,v| v.blank?} if attributes[:custom_field_values]
264 attributes[:custom_field_values].reject! {|k,v| v.blank?} if attributes[:custom_field_values]
291
265
292 unsaved_issue_ids = []
266 unsaved_issue_ids = []
293 @issues.each do |issue|
267 @issues.each do |issue|
294 journal = issue.init_journal(User.current, params[:notes])
268 journal = issue.init_journal(User.current, params[:notes])
295 issue.safe_attributes = attributes
269 issue.safe_attributes = attributes
296 call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
270 call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
297 unless issue.save
271 unless issue.save
298 # Keep unsaved issue ids to display them in flash error
272 # Keep unsaved issue ids to display them in flash error
299 unsaved_issue_ids << issue.id
273 unsaved_issue_ids << issue.id
300 end
274 end
301 end
275 end
302 if unsaved_issue_ids.empty?
276 if unsaved_issue_ids.empty?
303 flash[:notice] = l(:notice_successful_update) unless @issues.empty?
277 flash[:notice] = l(:notice_successful_update) unless @issues.empty?
304 else
278 else
305 flash[:error] = l(:notice_failed_to_save_issues, :count => unsaved_issue_ids.size,
279 flash[:error] = l(:notice_failed_to_save_issues, :count => unsaved_issue_ids.size,
306 :total => @issues.size,
280 :total => @issues.size,
307 :ids => '#' + unsaved_issue_ids.join(', #'))
281 :ids => '#' + unsaved_issue_ids.join(', #'))
308 end
282 end
309 redirect_back_or_default({:controller => 'issues', :action => 'index', :project_id => @project})
283 redirect_back_or_default({:controller => 'issues', :action => 'index', :project_id => @project})
310 return
284 return
311 end
285 end
312 @available_statuses = Workflow.available_statuses(@project)
286 @available_statuses = Workflow.available_statuses(@project)
313 @custom_fields = @project.all_issue_custom_fields
287 @custom_fields = @project.all_issue_custom_fields
314 end
288 end
315
289
316 def move
290 def move
317 @copy = params[:copy_options] && params[:copy_options][:copy]
291 @copy = params[:copy_options] && params[:copy_options][:copy]
318 @allowed_projects = []
292 @allowed_projects = []
319 # find projects to which the user is allowed to move the issue
293 # find projects to which the user is allowed to move the issue
320 if User.current.admin?
294 if User.current.admin?
321 # admin is allowed to move issues to any active (visible) project
295 # admin is allowed to move issues to any active (visible) project
322 @allowed_projects = Project.find(:all, :conditions => Project.visible_by(User.current))
296 @allowed_projects = Project.find(:all, :conditions => Project.visible_by(User.current))
323 else
297 else
324 User.current.memberships.each {|m| @allowed_projects << m.project if m.roles.detect {|r| r.allowed_to?(:move_issues)}}
298 User.current.memberships.each {|m| @allowed_projects << m.project if m.roles.detect {|r| r.allowed_to?(:move_issues)}}
325 end
299 end
326 @target_project = @allowed_projects.detect {|p| p.id.to_s == params[:new_project_id]} if params[:new_project_id]
300 @target_project = @allowed_projects.detect {|p| p.id.to_s == params[:new_project_id]} if params[:new_project_id]
327 @target_project ||= @project
301 @target_project ||= @project
328 @trackers = @target_project.trackers
302 @trackers = @target_project.trackers
329 @available_statuses = Workflow.available_statuses(@project)
303 @available_statuses = Workflow.available_statuses(@project)
330 if request.post?
304 if request.post?
331 new_tracker = params[:new_tracker_id].blank? ? nil : @target_project.trackers.find_by_id(params[:new_tracker_id])
305 new_tracker = params[:new_tracker_id].blank? ? nil : @target_project.trackers.find_by_id(params[:new_tracker_id])
332 unsaved_issue_ids = []
306 unsaved_issue_ids = []
333 moved_issues = []
307 moved_issues = []
334 @issues.each do |issue|
308 @issues.each do |issue|
335 changed_attributes = {}
309 changed_attributes = {}
336 [:assigned_to_id, :status_id, :start_date, :due_date].each do |valid_attribute|
310 [:assigned_to_id, :status_id, :start_date, :due_date].each do |valid_attribute|
337 unless params[valid_attribute].blank?
311 unless params[valid_attribute].blank?
338 changed_attributes[valid_attribute] = (params[valid_attribute] == 'none' ? nil : params[valid_attribute])
312 changed_attributes[valid_attribute] = (params[valid_attribute] == 'none' ? nil : params[valid_attribute])
339 end
313 end
340 end
314 end
341 issue.init_journal(User.current)
315 issue.init_journal(User.current)
342 call_hook(:controller_issues_move_before_save, { :params => params, :issue => issue, :target_project => @target_project, :copy => !!@copy })
316 call_hook(:controller_issues_move_before_save, { :params => params, :issue => issue, :target_project => @target_project, :copy => !!@copy })
343 if r = issue.move_to(@target_project, new_tracker, {:copy => @copy, :attributes => changed_attributes})
317 if r = issue.move_to(@target_project, new_tracker, {:copy => @copy, :attributes => changed_attributes})
344 moved_issues << r
318 moved_issues << r
345 else
319 else
346 unsaved_issue_ids << issue.id
320 unsaved_issue_ids << issue.id
347 end
321 end
348 end
322 end
349 if unsaved_issue_ids.empty?
323 if unsaved_issue_ids.empty?
350 flash[:notice] = l(:notice_successful_update) unless @issues.empty?
324 flash[:notice] = l(:notice_successful_update) unless @issues.empty?
351 else
325 else
352 flash[:error] = l(:notice_failed_to_save_issues, :count => unsaved_issue_ids.size,
326 flash[:error] = l(:notice_failed_to_save_issues, :count => unsaved_issue_ids.size,
353 :total => @issues.size,
327 :total => @issues.size,
354 :ids => '#' + unsaved_issue_ids.join(', #'))
328 :ids => '#' + unsaved_issue_ids.join(', #'))
355 end
329 end
356 if params[:follow]
330 if params[:follow]
357 if @issues.size == 1 && moved_issues.size == 1
331 if @issues.size == 1 && moved_issues.size == 1
358 redirect_to :controller => 'issues', :action => 'show', :id => moved_issues.first
332 redirect_to :controller => 'issues', :action => 'show', :id => moved_issues.first
359 else
333 else
360 redirect_to :controller => 'issues', :action => 'index', :project_id => (@target_project || @project)
334 redirect_to :controller => 'issues', :action => 'index', :project_id => (@target_project || @project)
361 end
335 end
362 else
336 else
363 redirect_to :controller => 'issues', :action => 'index', :project_id => @project
337 redirect_to :controller => 'issues', :action => 'index', :project_id => @project
364 end
338 end
365 return
339 return
366 end
340 end
367 render :layout => false if request.xhr?
341 render :layout => false if request.xhr?
368 end
342 end
369
343
370 def destroy
344 def destroy
371 @hours = TimeEntry.sum(:hours, :conditions => ['issue_id IN (?)', @issues]).to_f
345 @hours = TimeEntry.sum(:hours, :conditions => ['issue_id IN (?)', @issues]).to_f
372 if @hours > 0
346 if @hours > 0
373 case params[:todo]
347 case params[:todo]
374 when 'destroy'
348 when 'destroy'
375 # nothing to do
349 # nothing to do
376 when 'nullify'
350 when 'nullify'
377 TimeEntry.update_all('issue_id = NULL', ['issue_id IN (?)', @issues])
351 TimeEntry.update_all('issue_id = NULL', ['issue_id IN (?)', @issues])
378 when 'reassign'
352 when 'reassign'
379 reassign_to = @project.issues.find_by_id(params[:reassign_to_id])
353 reassign_to = @project.issues.find_by_id(params[:reassign_to_id])
380 if reassign_to.nil?
354 if reassign_to.nil?
381 flash.now[:error] = l(:error_issue_not_found_in_project)
355 flash.now[:error] = l(:error_issue_not_found_in_project)
382 return
356 return
383 else
357 else
384 TimeEntry.update_all("issue_id = #{reassign_to.id}", ['issue_id IN (?)', @issues])
358 TimeEntry.update_all("issue_id = #{reassign_to.id}", ['issue_id IN (?)', @issues])
385 end
359 end
386 else
360 else
387 unless params[:format] == 'xml'
361 unless params[:format] == 'xml'
388 # display the destroy form if it's a user request
362 # display the destroy form if it's a user request
389 return
363 return
390 end
364 end
391 end
365 end
392 end
366 end
393 @issues.each(&:destroy)
367 @issues.each(&:destroy)
394 respond_to do |format|
368 respond_to do |format|
395 format.html { redirect_to :action => 'index', :project_id => @project }
369 format.html { redirect_to :action => 'index', :project_id => @project }
396 format.xml { head :ok }
370 format.xml { head :ok }
397 end
371 end
398 end
372 end
399
373
400 def gantt
374 def gantt
401 @gantt = Redmine::Helpers::Gantt.new(params)
375 @gantt = Redmine::Helpers::Gantt.new(params)
402 retrieve_query
376 retrieve_query
403 @query.group_by = nil
377 @query.group_by = nil
404 if @query.valid?
378 if @query.valid?
405 events = []
379 events = []
406 # Issues that have start and due dates
380 # Issues that have start and due dates
407 events += @query.issues(:include => [:tracker, :assigned_to, :priority],
381 events += @query.issues(:include => [:tracker, :assigned_to, :priority],
408 :order => "start_date, due_date",
382 :order => "start_date, due_date",
409 :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)", @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to]
383 :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)", @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to]
410 )
384 )
411 # Issues that don't have a due date but that are assigned to a version with a date
385 # Issues that don't have a due date but that are assigned to a version with a date
412 events += @query.issues(:include => [:tracker, :assigned_to, :priority, :fixed_version],
386 events += @query.issues(:include => [:tracker, :assigned_to, :priority, :fixed_version],
413 :order => "start_date, effective_date",
387 :order => "start_date, effective_date",
414 :conditions => ["(((start_date>=? and start_date<=?) or (effective_date>=? and effective_date<=?) or (start_date<? and effective_date>?)) and start_date is not null and due_date is null and effective_date is not null)", @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to]
388 :conditions => ["(((start_date>=? and start_date<=?) or (effective_date>=? and effective_date<=?) or (start_date<? and effective_date>?)) and start_date is not null and due_date is null and effective_date is not null)", @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to]
415 )
389 )
416 # Versions
390 # Versions
417 events += @query.versions(:conditions => ["effective_date BETWEEN ? AND ?", @gantt.date_from, @gantt.date_to])
391 events += @query.versions(:conditions => ["effective_date BETWEEN ? AND ?", @gantt.date_from, @gantt.date_to])
418
392
419 @gantt.events = events
393 @gantt.events = events
420 end
394 end
421
395
422 basename = (@project ? "#{@project.identifier}-" : '') + 'gantt'
396 basename = (@project ? "#{@project.identifier}-" : '') + 'gantt'
423
397
424 respond_to do |format|
398 respond_to do |format|
425 format.html { render :template => "issues/gantt.rhtml", :layout => !request.xhr? }
399 format.html { render :template => "issues/gantt.rhtml", :layout => !request.xhr? }
426 format.png { send_data(@gantt.to_image, :disposition => 'inline', :type => 'image/png', :filename => "#{basename}.png") } if @gantt.respond_to?('to_image')
400 format.png { send_data(@gantt.to_image, :disposition => 'inline', :type => 'image/png', :filename => "#{basename}.png") } if @gantt.respond_to?('to_image')
427 format.pdf { send_data(gantt_to_pdf(@gantt, @project), :type => 'application/pdf', :filename => "#{basename}.pdf") }
401 format.pdf { send_data(gantt_to_pdf(@gantt, @project), :type => 'application/pdf', :filename => "#{basename}.pdf") }
428 end
402 end
429 end
403 end
430
404
431 def calendar
405 def calendar
432 if params[:year] and params[:year].to_i > 1900
406 if params[:year] and params[:year].to_i > 1900
433 @year = params[:year].to_i
407 @year = params[:year].to_i
434 if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13
408 if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13
435 @month = params[:month].to_i
409 @month = params[:month].to_i
436 end
410 end
437 end
411 end
438 @year ||= Date.today.year
412 @year ||= Date.today.year
439 @month ||= Date.today.month
413 @month ||= Date.today.month
440
414
441 @calendar = Redmine::Helpers::Calendar.new(Date.civil(@year, @month, 1), current_language, :month)
415 @calendar = Redmine::Helpers::Calendar.new(Date.civil(@year, @month, 1), current_language, :month)
442 retrieve_query
416 retrieve_query
443 @query.group_by = nil
417 @query.group_by = nil
444 if @query.valid?
418 if @query.valid?
445 events = []
419 events = []
446 events += @query.issues(:include => [:tracker, :assigned_to, :priority],
420 events += @query.issues(:include => [:tracker, :assigned_to, :priority],
447 :conditions => ["((start_date BETWEEN ? AND ?) OR (due_date BETWEEN ? AND ?))", @calendar.startdt, @calendar.enddt, @calendar.startdt, @calendar.enddt]
421 :conditions => ["((start_date BETWEEN ? AND ?) OR (due_date BETWEEN ? AND ?))", @calendar.startdt, @calendar.enddt, @calendar.startdt, @calendar.enddt]
448 )
422 )
449 events += @query.versions(:conditions => ["effective_date BETWEEN ? AND ?", @calendar.startdt, @calendar.enddt])
423 events += @query.versions(:conditions => ["effective_date BETWEEN ? AND ?", @calendar.startdt, @calendar.enddt])
450
424
451 @calendar.events = events
425 @calendar.events = events
452 end
426 end
453
427
454 render :layout => false if request.xhr?
428 render :layout => false if request.xhr?
455 end
429 end
456
430
457 def context_menu
431 def context_menu
458 @issues = Issue.find_all_by_id(params[:ids], :include => :project)
432 @issues = Issue.find_all_by_id(params[:ids], :include => :project)
459 if (@issues.size == 1)
433 if (@issues.size == 1)
460 @issue = @issues.first
434 @issue = @issues.first
461 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
435 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
462 end
436 end
463 projects = @issues.collect(&:project).compact.uniq
437 projects = @issues.collect(&:project).compact.uniq
464 @project = projects.first if projects.size == 1
438 @project = projects.first if projects.size == 1
465
439
466 @can = {:edit => (@project && User.current.allowed_to?(:edit_issues, @project)),
440 @can = {:edit => (@project && User.current.allowed_to?(:edit_issues, @project)),
467 :log_time => (@project && User.current.allowed_to?(:log_time, @project)),
441 :log_time => (@project && User.current.allowed_to?(:log_time, @project)),
468 :update => (@project && (User.current.allowed_to?(:edit_issues, @project) || (User.current.allowed_to?(:change_status, @project) && @allowed_statuses && !@allowed_statuses.empty?))),
442 :update => (@project && (User.current.allowed_to?(:edit_issues, @project) || (User.current.allowed_to?(:change_status, @project) && @allowed_statuses && !@allowed_statuses.empty?))),
469 :move => (@project && User.current.allowed_to?(:move_issues, @project)),
443 :move => (@project && User.current.allowed_to?(:move_issues, @project)),
470 :copy => (@issue && @project.trackers.include?(@issue.tracker) && User.current.allowed_to?(:add_issues, @project)),
444 :copy => (@issue && @project.trackers.include?(@issue.tracker) && User.current.allowed_to?(:add_issues, @project)),
471 :delete => (@project && User.current.allowed_to?(:delete_issues, @project))
445 :delete => (@project && User.current.allowed_to?(:delete_issues, @project))
472 }
446 }
473 if @project
447 if @project
474 @assignables = @project.assignable_users
448 @assignables = @project.assignable_users
475 @assignables << @issue.assigned_to if @issue && @issue.assigned_to && !@assignables.include?(@issue.assigned_to)
449 @assignables << @issue.assigned_to if @issue && @issue.assigned_to && !@assignables.include?(@issue.assigned_to)
476 @trackers = @project.trackers
450 @trackers = @project.trackers
477 end
451 end
478
452
479 @priorities = IssuePriority.all.reverse
453 @priorities = IssuePriority.all.reverse
480 @statuses = IssueStatus.find(:all, :order => 'position')
454 @statuses = IssueStatus.find(:all, :order => 'position')
481 @back = params[:back_url] || request.env['HTTP_REFERER']
455 @back = params[:back_url] || request.env['HTTP_REFERER']
482
456
483 render :layout => false
457 render :layout => false
484 end
458 end
485
459
486 def update_form
460 def update_form
487 if params[:id].blank?
461 if params[:id].blank?
488 @issue = Issue.new
462 @issue = Issue.new
489 @issue.project = @project
463 @issue.project = @project
490 else
464 else
491 @issue = @project.issues.visible.find(params[:id])
465 @issue = @project.issues.visible.find(params[:id])
492 end
466 end
493 @issue.attributes = params[:issue]
467 @issue.attributes = params[:issue]
494 @allowed_statuses = ([@issue.status] + @issue.status.find_new_statuses_allowed_to(User.current.roles_for_project(@project), @issue.tracker)).uniq
468 @allowed_statuses = ([@issue.status] + @issue.status.find_new_statuses_allowed_to(User.current.roles_for_project(@project), @issue.tracker)).uniq
495 @priorities = IssuePriority.all
469 @priorities = IssuePriority.all
496
470
497 render :partial => 'attributes'
471 render :partial => 'attributes'
498 end
472 end
499
473
500 def preview
474 def preview
501 @issue = @project.issues.find_by_id(params[:id]) unless params[:id].blank?
475 @issue = @project.issues.find_by_id(params[:id]) unless params[:id].blank?
502 @attachements = @issue.attachments if @issue
476 @attachements = @issue.attachments if @issue
503 @text = params[:notes] || (params[:issue] ? params[:issue][:description] : nil)
477 @text = params[:notes] || (params[:issue] ? params[:issue][:description] : nil)
504 render :partial => 'common/preview'
478 render :partial => 'common/preview'
505 end
479 end
506
480
507 private
481 private
508 def find_issue
482 def find_issue
509 @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
483 @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
510 @project = @issue.project
484 @project = @issue.project
511 rescue ActiveRecord::RecordNotFound
485 rescue ActiveRecord::RecordNotFound
512 render_404
486 render_404
513 end
487 end
514
488
515 # Filter for bulk operations
489 # Filter for bulk operations
516 def find_issues
490 def find_issues
517 @issues = Issue.find_all_by_id(params[:id] || params[:ids])
491 @issues = Issue.find_all_by_id(params[:id] || params[:ids])
518 raise ActiveRecord::RecordNotFound if @issues.empty?
492 raise ActiveRecord::RecordNotFound if @issues.empty?
519 projects = @issues.collect(&:project).compact.uniq
493 projects = @issues.collect(&:project).compact.uniq
520 if projects.size == 1
494 if projects.size == 1
521 @project = projects.first
495 @project = projects.first
522 else
496 else
523 # TODO: let users bulk edit/move/destroy issues from different projects
497 # TODO: let users bulk edit/move/destroy issues from different projects
524 render_error 'Can not bulk edit/move/destroy issues from different projects'
498 render_error 'Can not bulk edit/move/destroy issues from different projects'
525 return false
499 return false
526 end
500 end
527 rescue ActiveRecord::RecordNotFound
501 rescue ActiveRecord::RecordNotFound
528 render_404
502 render_404
529 end
503 end
530
504
531 def find_project
505 def find_project
532 project_id = (params[:issue] && params[:issue][:project_id]) || params[:project_id]
506 project_id = (params[:issue] && params[:issue][:project_id]) || params[:project_id]
533 @project = Project.find(project_id)
507 @project = Project.find(project_id)
534 rescue ActiveRecord::RecordNotFound
508 rescue ActiveRecord::RecordNotFound
535 render_404
509 render_404
536 end
510 end
537
511
538 def find_optional_project
512 def find_optional_project
539 @project = Project.find(params[:project_id]) unless params[:project_id].blank?
513 @project = Project.find(params[:project_id]) unless params[:project_id].blank?
540 allowed = User.current.allowed_to?({:controller => params[:controller], :action => params[:action]}, @project, :global => true)
514 allowed = User.current.allowed_to?({:controller => params[:controller], :action => params[:action]}, @project, :global => true)
541 allowed ? true : deny_access
515 allowed ? true : deny_access
542 rescue ActiveRecord::RecordNotFound
516 rescue ActiveRecord::RecordNotFound
543 render_404
517 render_404
544 end
518 end
545
519
546 # Retrieve query from session or build a new query
520 # Retrieve query from session or build a new query
547 def retrieve_query
521 def retrieve_query
548 if !params[:query_id].blank?
522 if !params[:query_id].blank?
549 cond = "project_id IS NULL"
523 cond = "project_id IS NULL"
550 cond << " OR project_id = #{@project.id}" if @project
524 cond << " OR project_id = #{@project.id}" if @project
551 @query = Query.find(params[:query_id], :conditions => cond)
525 @query = Query.find(params[:query_id], :conditions => cond)
552 @query.project = @project
526 @query.project = @project
553 session[:query] = {:id => @query.id, :project_id => @query.project_id}
527 session[:query] = {:id => @query.id, :project_id => @query.project_id}
554 sort_clear
528 sort_clear
555 else
529 else
556 if api_request? || params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil)
530 if api_request? || params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil)
557 # Give it a name, required to be valid
531 # Give it a name, required to be valid
558 @query = Query.new(:name => "_")
532 @query = Query.new(:name => "_")
559 @query.project = @project
533 @query.project = @project
560 if params[:fields] and params[:fields].is_a? Array
534 if params[:fields] and params[:fields].is_a? Array
561 params[:fields].each do |field|
535 params[:fields].each do |field|
562 @query.add_filter(field, params[:operators][field], params[:values][field])
536 @query.add_filter(field, params[:operators][field], params[:values][field])
563 end
537 end
564 else
538 else
565 @query.available_filters.keys.each do |field|
539 @query.available_filters.keys.each do |field|
566 @query.add_short_filter(field, params[field]) if params[field]
540 @query.add_short_filter(field, params[field]) if params[field]
567 end
541 end
568 end
542 end
569 @query.group_by = params[:group_by]
543 @query.group_by = params[:group_by]
570 @query.column_names = params[:query] && params[:query][:column_names]
544 @query.column_names = params[:query] && params[:query][:column_names]
571 session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by, :column_names => @query.column_names}
545 session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by, :column_names => @query.column_names}
572 else
546 else
573 @query = Query.find_by_id(session[:query][:id]) if session[:query][:id]
547 @query = Query.find_by_id(session[:query][:id]) if session[:query][:id]
574 @query ||= Query.new(:name => "_", :project => @project, :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names])
548 @query ||= Query.new(:name => "_", :project => @project, :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names])
575 @query.project = @project
549 @query.project = @project
576 end
550 end
577 end
551 end
578 end
552 end
579
553
580 # Rescues an invalid query statement. Just in case...
554 # Rescues an invalid query statement. Just in case...
581 def query_statement_invalid(exception)
555 def query_statement_invalid(exception)
582 logger.error "Query::StatementInvalid: #{exception.message}" if logger
556 logger.error "Query::StatementInvalid: #{exception.message}" if logger
583 session.delete(:query)
557 session.delete(:query)
584 sort_clear
558 sort_clear
585 render_error "An error occurred while executing the query and has been logged. Please report this error to your Redmine administrator."
559 render_error "An error occurred while executing the query and has been logged. Please report this error to your Redmine administrator."
586 end
560 end
561
562 # Used by #edit and #update to set some common instance variables
563 # from the params
564 # TODO: Refactor, not everything in here is needed by #edit
565 def update_issue_from_params
566 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
567 @priorities = IssuePriority.all
568 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
569 @time_entry = TimeEntry.new
570
571 @notes = params[:notes]
572 @journal = @issue.init_journal(User.current, @notes)
573 # User can change issue attributes only if he has :edit permission or if a workflow transition is allowed
574 if (@edit_allowed || !@allowed_statuses.empty?) && params[:issue]
575 attrs = params[:issue].dup
576 attrs.delete_if {|k,v| !UPDATABLE_ATTRS_ON_TRANSITION.include?(k) } unless @edit_allowed
577 attrs.delete(:status_id) unless @allowed_statuses.detect {|s| s.id.to_s == attrs[:status_id].to_s}
578 @issue.safe_attributes = attrs
579 end
580
581 end
587 end
582 end
General Comments 0
You need to be logged in to leave comments. Login now