annotate app/controllers/issues_controller.rb @ 45:65d9e2cabaa3 luisf

Added tipoftheday to the config/settings in order to correct previous issues. Tip of the day is now working correctly. Added the heading strings to the locales files.
author luisf
date Tue, 23 Nov 2010 11:50:01 +0000
parents 94944d00e43c
children 35c1d1c098e6 af80e5618e9b
rev   line source
Chris@0 1 # Redmine - project management software
Chris@0 2 # Copyright (C) 2006-2008 Jean-Philippe Lang
Chris@0 3 #
Chris@0 4 # This program is free software; you can redistribute it and/or
Chris@0 5 # modify it under the terms of the GNU General Public License
Chris@0 6 # as published by the Free Software Foundation; either version 2
Chris@0 7 # of the License, or (at your option) any later version.
Chris@0 8 #
Chris@0 9 # This program is distributed in the hope that it will be useful,
Chris@0 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
Chris@0 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Chris@0 12 # GNU General Public License for more details.
Chris@0 13 #
Chris@0 14 # You should have received a copy of the GNU General Public License
Chris@0 15 # along with this program; if not, write to the Free Software
Chris@0 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
Chris@0 17
Chris@0 18 class IssuesController < ApplicationController
Chris@0 19 menu_item :new_issue, :only => [:new, :create]
Chris@0 20 default_search_scope :issues
Chris@0 21
Chris@14 22 before_filter :find_issue, :only => [:show, :edit, :update]
Chris@14 23 before_filter :find_issues, :only => [:bulk_edit, :bulk_update, :move, :perform_move, :destroy]
chris@37 24 before_filter :check_project_uniqueness, :only => [:move, :perform_move]
Chris@14 25 before_filter :find_project, :only => [:new, :create]
Chris@14 26 before_filter :authorize, :except => [:index]
Chris@14 27 before_filter :find_optional_project, :only => [:index]
Chris@0 28 before_filter :check_for_default_issue_status, :only => [:new, :create]
Chris@0 29 before_filter :build_new_issue_from_params, :only => [:new, :create]
chris@37 30 accept_key_auth :index, :show, :create, :update, :destroy
Chris@0 31
Chris@0 32 rescue_from Query::StatementInvalid, :with => :query_statement_invalid
Chris@0 33
Chris@0 34 helper :journals
Chris@0 35 helper :projects
Chris@0 36 include ProjectsHelper
Chris@0 37 helper :custom_fields
Chris@0 38 include CustomFieldsHelper
Chris@0 39 helper :issue_relations
Chris@0 40 include IssueRelationsHelper
Chris@0 41 helper :watchers
Chris@0 42 include WatchersHelper
Chris@0 43 helper :attachments
Chris@0 44 include AttachmentsHelper
Chris@0 45 helper :queries
Chris@0 46 include QueriesHelper
Chris@0 47 helper :sort
Chris@0 48 include SortHelper
Chris@0 49 include IssuesHelper
Chris@0 50 helper :timelog
chris@22 51 helper :gantt
Chris@0 52 include Redmine::Export::PDF
Chris@0 53
Chris@0 54 verify :method => [:post, :delete],
Chris@0 55 :only => :destroy,
Chris@0 56 :render => { :nothing => true, :status => :method_not_allowed }
Chris@0 57
Chris@0 58 verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
Chris@14 59 verify :method => :post, :only => :bulk_update, :render => {:nothing => true, :status => :method_not_allowed }
Chris@0 60 verify :method => :put, :only => :update, :render => {:nothing => true, :status => :method_not_allowed }
Chris@0 61
Chris@0 62 def index
Chris@0 63 retrieve_query
Chris@0 64 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
Chris@0 65 sort_update(@query.sortable_columns)
Chris@0 66
Chris@0 67 if @query.valid?
Chris@0 68 limit = case params[:format]
Chris@0 69 when 'csv', 'pdf'
Chris@0 70 Setting.issues_export_limit.to_i
Chris@0 71 when 'atom'
Chris@0 72 Setting.feeds_limit.to_i
Chris@0 73 else
Chris@0 74 per_page_option
Chris@0 75 end
Chris@0 76
Chris@0 77 @issue_count = @query.issue_count
Chris@0 78 @issue_pages = Paginator.new self, @issue_count, limit, params['page']
Chris@0 79 @issues = @query.issues(:include => [:assigned_to, :tracker, :priority, :category, :fixed_version],
Chris@0 80 :order => sort_clause,
Chris@0 81 :offset => @issue_pages.current.offset,
Chris@0 82 :limit => limit)
Chris@0 83 @issue_count_by_group = @query.issue_count_by_group
Chris@0 84
Chris@0 85 respond_to do |format|
Chris@0 86 format.html { render :template => 'issues/index.rhtml', :layout => !request.xhr? }
Chris@0 87 format.xml { render :layout => false }
Chris@0 88 format.json { render :text => @issues.to_json, :layout => false }
Chris@0 89 format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
Chris@0 90 format.csv { send_data(issues_to_csv(@issues, @project), :type => 'text/csv; header=present', :filename => 'export.csv') }
Chris@0 91 format.pdf { send_data(issues_to_pdf(@issues, @project, @query), :type => 'application/pdf', :filename => 'export.pdf') }
Chris@0 92 end
Chris@0 93 else
Chris@0 94 # Send html if the query is not valid
Chris@0 95 render(:template => 'issues/index.rhtml', :layout => !request.xhr?)
Chris@0 96 end
Chris@0 97 rescue ActiveRecord::RecordNotFound
Chris@0 98 render_404
Chris@0 99 end
Chris@0 100
Chris@0 101 def show
Chris@0 102 @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
Chris@0 103 @journals.each_with_index {|j,i| j.indice = i+1}
Chris@0 104 @journals.reverse! if User.current.wants_comments_in_reverse_order?
Chris@0 105 @changesets = @issue.changesets.visible.all
Chris@0 106 @changesets.reverse! if User.current.wants_comments_in_reverse_order?
Chris@0 107 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
Chris@0 108 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
Chris@0 109 @priorities = IssuePriority.all
Chris@0 110 @time_entry = TimeEntry.new
Chris@0 111 respond_to do |format|
Chris@0 112 format.html { render :template => 'issues/show.rhtml' }
Chris@0 113 format.xml { render :layout => false }
Chris@0 114 format.json { render :text => @issue.to_json, :layout => false }
Chris@14 115 format.atom { render :template => 'journals/index', :layout => false, :content_type => 'application/atom+xml' }
Chris@0 116 format.pdf { send_data(issue_to_pdf(@issue), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") }
Chris@0 117 end
Chris@0 118 end
Chris@0 119
Chris@0 120 # Add a new issue
Chris@0 121 # The new issue will be created from an existing one if copy_from parameter is given
Chris@0 122 def new
Chris@14 123 respond_to do |format|
Chris@14 124 format.html { render :action => 'new', :layout => !request.xhr? }
Chris@14 125 format.js { render :partial => 'attributes' }
Chris@14 126 end
Chris@0 127 end
Chris@0 128
Chris@0 129 def create
Chris@0 130 call_hook(:controller_issues_new_before_save, { :params => params, :issue => @issue })
Chris@0 131 if @issue.save
Chris@0 132 attachments = Attachment.attach_files(@issue, params[:attachments])
Chris@0 133 render_attachment_warning_if_needed(@issue)
Chris@0 134 flash[:notice] = l(:notice_successful_create)
Chris@0 135 call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue})
Chris@0 136 respond_to do |format|
Chris@0 137 format.html {
chris@22 138 redirect_to(params[:continue] ? { :action => 'new', :project_id => @project, :issue => {:tracker_id => @issue.tracker, :parent_issue_id => @issue.parent_issue_id}.reject {|k,v| v.nil?} } :
Chris@0 139 { :action => 'show', :id => @issue })
Chris@0 140 }
Chris@0 141 format.xml { render :action => 'show', :status => :created, :location => url_for(:controller => 'issues', :action => 'show', :id => @issue) }
Chris@0 142 format.json { render :text => @issue.to_json, :status => :created, :location => url_for(:controller => 'issues', :action => 'show'), :layout => false }
Chris@0 143 end
Chris@0 144 return
Chris@0 145 else
Chris@0 146 respond_to do |format|
Chris@0 147 format.html { render :action => 'new' }
Chris@0 148 format.xml { render(:xml => @issue.errors, :status => :unprocessable_entity); return }
Chris@0 149 format.json { render :text => object_errors_to_json(@issue), :status => :unprocessable_entity, :layout => false }
Chris@0 150 end
Chris@0 151 end
Chris@0 152 end
chris@37 153
Chris@0 154 def edit
Chris@0 155 update_issue_from_params
Chris@0 156
Chris@0 157 @journal = @issue.current_journal
Chris@0 158
Chris@0 159 respond_to do |format|
Chris@0 160 format.html { }
Chris@0 161 format.xml { }
Chris@0 162 end
Chris@0 163 end
Chris@0 164
Chris@0 165 def update
Chris@0 166 update_issue_from_params
Chris@0 167
Chris@0 168 if @issue.save_issue_with_child_records(params, @time_entry)
Chris@0 169 render_attachment_warning_if_needed(@issue)
Chris@0 170 flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record?
Chris@0 171
Chris@0 172 respond_to do |format|
Chris@0 173 format.html { redirect_back_or_default({:action => 'show', :id => @issue}) }
Chris@0 174 format.xml { head :ok }
Chris@0 175 format.json { head :ok }
Chris@0 176 end
Chris@0 177 else
Chris@0 178 render_attachment_warning_if_needed(@issue)
Chris@0 179 flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record?
Chris@0 180 @journal = @issue.current_journal
Chris@0 181
Chris@0 182 respond_to do |format|
Chris@0 183 format.html { render :action => 'edit' }
Chris@0 184 format.xml { render :xml => @issue.errors, :status => :unprocessable_entity }
Chris@0 185 format.json { render :text => object_errors_to_json(@issue), :status => :unprocessable_entity, :layout => false }
Chris@0 186 end
Chris@0 187 end
Chris@0 188 end
Chris@0 189
Chris@0 190 # Bulk edit a set of issues
Chris@0 191 def bulk_edit
Chris@0 192 @issues.sort!
chris@37 193 @available_statuses = @projects.map{|p|Workflow.available_statuses(p)}.inject{|memo,w|memo & w}
chris@37 194 @custom_fields = @projects.map{|p|p.all_issue_custom_fields}.inject{|memo,c|memo & c}
chris@37 195 @assignables = @projects.map(&:assignable_users).inject{|memo,a| memo & a}
chris@37 196 @trackers = @projects.map(&:trackers).inject{|memo,t| memo & t}
Chris@0 197 end
Chris@0 198
Chris@14 199 def bulk_update
Chris@0 200 @issues.sort!
Chris@14 201 attributes = parse_params_for_bulk_issue_attributes(params)
Chris@14 202
Chris@14 203 unsaved_issue_ids = []
Chris@14 204 @issues.each do |issue|
Chris@14 205 issue.reload
Chris@14 206 journal = issue.init_journal(User.current, params[:notes])
Chris@14 207 issue.safe_attributes = attributes
Chris@14 208 call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
Chris@14 209 unless issue.save
Chris@14 210 # Keep unsaved issue ids to display them in flash error
Chris@14 211 unsaved_issue_ids << issue.id
Chris@0 212 end
Chris@0 213 end
Chris@14 214 set_flash_from_bulk_issue_save(@issues, unsaved_issue_ids)
Chris@14 215 redirect_back_or_default({:controller => 'issues', :action => 'index', :project_id => @project})
Chris@0 216 end
Chris@0 217
Chris@0 218 def destroy
Chris@0 219 @hours = TimeEntry.sum(:hours, :conditions => ['issue_id IN (?)', @issues]).to_f
Chris@0 220 if @hours > 0
Chris@0 221 case params[:todo]
Chris@0 222 when 'destroy'
Chris@0 223 # nothing to do
Chris@0 224 when 'nullify'
Chris@0 225 TimeEntry.update_all('issue_id = NULL', ['issue_id IN (?)', @issues])
Chris@0 226 when 'reassign'
Chris@0 227 reassign_to = @project.issues.find_by_id(params[:reassign_to_id])
Chris@0 228 if reassign_to.nil?
Chris@0 229 flash.now[:error] = l(:error_issue_not_found_in_project)
Chris@0 230 return
Chris@0 231 else
Chris@0 232 TimeEntry.update_all("issue_id = #{reassign_to.id}", ['issue_id IN (?)', @issues])
Chris@0 233 end
Chris@0 234 else
Chris@0 235 unless params[:format] == 'xml' || params[:format] == 'json'
Chris@0 236 # display the destroy form if it's a user request
Chris@0 237 return
Chris@0 238 end
Chris@0 239 end
Chris@0 240 end
Chris@0 241 @issues.each(&:destroy)
Chris@0 242 respond_to do |format|
chris@37 243 format.html { redirect_back_or_default(:action => 'index', :project_id => @project) }
Chris@0 244 format.xml { head :ok }
Chris@0 245 format.json { head :ok }
Chris@0 246 end
Chris@0 247 end
Chris@0 248
Chris@0 249 private
Chris@0 250 def find_issue
Chris@0 251 @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
Chris@0 252 @project = @issue.project
Chris@0 253 rescue ActiveRecord::RecordNotFound
Chris@0 254 render_404
Chris@0 255 end
Chris@0 256
Chris@0 257 def find_project
Chris@0 258 project_id = (params[:issue] && params[:issue][:project_id]) || params[:project_id]
Chris@0 259 @project = Project.find(project_id)
Chris@0 260 rescue ActiveRecord::RecordNotFound
Chris@0 261 render_404
Chris@0 262 end
Chris@0 263
Chris@0 264 # Used by #edit and #update to set some common instance variables
Chris@0 265 # from the params
Chris@0 266 # TODO: Refactor, not everything in here is needed by #edit
Chris@0 267 def update_issue_from_params
Chris@0 268 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
Chris@0 269 @priorities = IssuePriority.all
Chris@0 270 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
Chris@0 271 @time_entry = TimeEntry.new
chris@37 272 @time_entry.attributes = params[:time_entry]
Chris@0 273
chris@22 274 @notes = params[:notes] || (params[:issue].present? ? params[:issue][:notes] : nil)
Chris@0 275 @issue.init_journal(User.current, @notes)
chris@37 276 @issue.safe_attributes = params[:issue]
Chris@0 277 end
Chris@0 278
Chris@0 279 # TODO: Refactor, lots of extra code in here
chris@37 280 # TODO: Changing tracker on an existing issue should not trigger this
Chris@0 281 def build_new_issue_from_params
Chris@14 282 if params[:id].blank?
Chris@14 283 @issue = Issue.new
Chris@14 284 @issue.copy_from(params[:copy_from]) if params[:copy_from]
Chris@14 285 @issue.project = @project
Chris@14 286 else
Chris@14 287 @issue = @project.issues.visible.find(params[:id])
Chris@14 288 end
Chris@14 289
Chris@0 290 @issue.project = @project
Chris@0 291 # Tracker must be set before custom field values
Chris@0 292 @issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first)
Chris@0 293 if @issue.tracker.nil?
Chris@0 294 render_error l(:error_no_tracker_in_project)
Chris@0 295 return false
Chris@0 296 end
chris@37 297 @issue.start_date ||= Date.today
Chris@0 298 if params[:issue].is_a?(Hash)
Chris@0 299 @issue.safe_attributes = params[:issue]
chris@37 300 if User.current.allowed_to?(:add_issue_watchers, @project) && @issue.new_record?
chris@37 301 @issue.watcher_user_ids = params[:issue]['watcher_user_ids']
chris@37 302 end
Chris@0 303 end
Chris@0 304 @issue.author = User.current
Chris@0 305 @priorities = IssuePriority.all
Chris@0 306 @allowed_statuses = @issue.new_statuses_allowed_to(User.current, true)
Chris@0 307 end
Chris@0 308
Chris@0 309 def check_for_default_issue_status
Chris@0 310 if IssueStatus.default.nil?
Chris@0 311 render_error l(:error_no_default_issue_status)
Chris@0 312 return false
Chris@0 313 end
Chris@0 314 end
Chris@14 315
Chris@14 316 def parse_params_for_bulk_issue_attributes(params)
Chris@14 317 attributes = (params[:issue] || {}).reject {|k,v| v.blank?}
Chris@14 318 attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
Chris@14 319 attributes[:custom_field_values].reject! {|k,v| v.blank?} if attributes[:custom_field_values]
Chris@14 320 attributes
Chris@14 321 end
Chris@0 322 end