##// END OF EJS Templates
Fixed that updating the issue form was broken by r4011 when user is not allowed to add issues (#13188)....
Jean-Philippe Lang -
r11175:7799788b3de6
parent child
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -1,435 +1,439
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2013 Jean-Philippe Lang
2 # Copyright (C) 2006-2013 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, :create]
19 menu_item :new_issue, :only => [:new, :create]
20 default_search_scope :issues
20 default_search_scope :issues
21
21
22 before_filter :find_issue, :only => [:show, :edit, :update]
22 before_filter :find_issue, :only => [:show, :edit, :update]
23 before_filter :find_issues, :only => [:bulk_edit, :bulk_update, :destroy]
23 before_filter :find_issues, :only => [:bulk_edit, :bulk_update, :destroy]
24 before_filter :find_project, :only => [:new, :create]
24 before_filter :find_project, :only => [:new, :create, :update_form]
25 before_filter :authorize, :except => [:index]
25 before_filter :authorize, :except => [:index]
26 before_filter :find_optional_project, :only => [:index]
26 before_filter :find_optional_project, :only => [:index]
27 before_filter :check_for_default_issue_status, :only => [:new, :create]
27 before_filter :check_for_default_issue_status, :only => [:new, :create]
28 before_filter :build_new_issue_from_params, :only => [:new, :create]
28 before_filter :build_new_issue_from_params, :only => [:new, :create, :update_form]
29 accept_rss_auth :index, :show
29 accept_rss_auth :index, :show
30 accept_api_auth :index, :show, :create, :update, :destroy
30 accept_api_auth :index, :show, :create, :update, :destroy
31
31
32 rescue_from Query::StatementInvalid, :with => :query_statement_invalid
32 rescue_from Query::StatementInvalid, :with => :query_statement_invalid
33
33
34 helper :journals
34 helper :journals
35 helper :projects
35 helper :projects
36 include ProjectsHelper
36 include ProjectsHelper
37 helper :custom_fields
37 helper :custom_fields
38 include CustomFieldsHelper
38 include CustomFieldsHelper
39 helper :issue_relations
39 helper :issue_relations
40 include IssueRelationsHelper
40 include IssueRelationsHelper
41 helper :watchers
41 helper :watchers
42 include WatchersHelper
42 include WatchersHelper
43 helper :attachments
43 helper :attachments
44 include AttachmentsHelper
44 include AttachmentsHelper
45 helper :queries
45 helper :queries
46 include QueriesHelper
46 include QueriesHelper
47 helper :repositories
47 helper :repositories
48 include RepositoriesHelper
48 include RepositoriesHelper
49 helper :sort
49 helper :sort
50 include SortHelper
50 include SortHelper
51 include IssuesHelper
51 include IssuesHelper
52 helper :timelog
52 helper :timelog
53 include Redmine::Export::PDF
53 include Redmine::Export::PDF
54
54
55 def index
55 def index
56 retrieve_query
56 retrieve_query
57 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
57 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
58 sort_update(@query.sortable_columns)
58 sort_update(@query.sortable_columns)
59 @query.sort_criteria = sort_criteria.to_a
59 @query.sort_criteria = sort_criteria.to_a
60
60
61 if @query.valid?
61 if @query.valid?
62 case params[:format]
62 case params[:format]
63 when 'csv', 'pdf'
63 when 'csv', 'pdf'
64 @limit = Setting.issues_export_limit.to_i
64 @limit = Setting.issues_export_limit.to_i
65 when 'atom'
65 when 'atom'
66 @limit = Setting.feeds_limit.to_i
66 @limit = Setting.feeds_limit.to_i
67 when 'xml', 'json'
67 when 'xml', 'json'
68 @offset, @limit = api_offset_and_limit
68 @offset, @limit = api_offset_and_limit
69 else
69 else
70 @limit = per_page_option
70 @limit = per_page_option
71 end
71 end
72
72
73 @issue_count = @query.issue_count
73 @issue_count = @query.issue_count
74 @issue_pages = Paginator.new @issue_count, @limit, params['page']
74 @issue_pages = Paginator.new @issue_count, @limit, params['page']
75 @offset ||= @issue_pages.offset
75 @offset ||= @issue_pages.offset
76 @issues = @query.issues(:include => [:assigned_to, :tracker, :priority, :category, :fixed_version],
76 @issues = @query.issues(:include => [:assigned_to, :tracker, :priority, :category, :fixed_version],
77 :order => sort_clause,
77 :order => sort_clause,
78 :offset => @offset,
78 :offset => @offset,
79 :limit => @limit)
79 :limit => @limit)
80 @issue_count_by_group = @query.issue_count_by_group
80 @issue_count_by_group = @query.issue_count_by_group
81
81
82 respond_to do |format|
82 respond_to do |format|
83 format.html { render :template => 'issues/index', :layout => !request.xhr? }
83 format.html { render :template => 'issues/index', :layout => !request.xhr? }
84 format.api {
84 format.api {
85 Issue.load_visible_relations(@issues) if include_in_api_response?('relations')
85 Issue.load_visible_relations(@issues) if include_in_api_response?('relations')
86 }
86 }
87 format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
87 format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
88 format.csv { send_data(issues_to_csv(@issues, @project, @query, params), :type => 'text/csv; header=present', :filename => 'export.csv') }
88 format.csv { send_data(issues_to_csv(@issues, @project, @query, params), :type => 'text/csv; header=present', :filename => 'export.csv') }
89 format.pdf { send_data(issues_to_pdf(@issues, @project, @query), :type => 'application/pdf', :filename => 'export.pdf') }
89 format.pdf { send_data(issues_to_pdf(@issues, @project, @query), :type => 'application/pdf', :filename => 'export.pdf') }
90 end
90 end
91 else
91 else
92 respond_to do |format|
92 respond_to do |format|
93 format.html { render(:template => 'issues/index', :layout => !request.xhr?) }
93 format.html { render(:template => 'issues/index', :layout => !request.xhr?) }
94 format.any(:atom, :csv, :pdf) { render(:nothing => true) }
94 format.any(:atom, :csv, :pdf) { render(:nothing => true) }
95 format.api { render_validation_errors(@query) }
95 format.api { render_validation_errors(@query) }
96 end
96 end
97 end
97 end
98 rescue ActiveRecord::RecordNotFound
98 rescue ActiveRecord::RecordNotFound
99 render_404
99 render_404
100 end
100 end
101
101
102 def show
102 def show
103 @journals = @issue.journals.includes(:user, :details).reorder("#{Journal.table_name}.id ASC").all
103 @journals = @issue.journals.includes(:user, :details).reorder("#{Journal.table_name}.id ASC").all
104 @journals.each_with_index {|j,i| j.indice = i+1}
104 @journals.each_with_index {|j,i| j.indice = i+1}
105 @journals.reject!(&:private_notes?) unless User.current.allowed_to?(:view_private_notes, @issue.project)
105 @journals.reject!(&:private_notes?) unless User.current.allowed_to?(:view_private_notes, @issue.project)
106 @journals.reverse! if User.current.wants_comments_in_reverse_order?
106 @journals.reverse! if User.current.wants_comments_in_reverse_order?
107
107
108 @changesets = @issue.changesets.visible.all
108 @changesets = @issue.changesets.visible.all
109 @changesets.reverse! if User.current.wants_comments_in_reverse_order?
109 @changesets.reverse! if User.current.wants_comments_in_reverse_order?
110
110
111 @relations = @issue.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? }
111 @relations = @issue.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? }
112 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
112 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
113 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
113 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
114 @priorities = IssuePriority.active
114 @priorities = IssuePriority.active
115 @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
115 @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
116 respond_to do |format|
116 respond_to do |format|
117 format.html {
117 format.html {
118 retrieve_previous_and_next_issue_ids
118 retrieve_previous_and_next_issue_ids
119 render :template => 'issues/show'
119 render :template => 'issues/show'
120 }
120 }
121 format.api
121 format.api
122 format.atom { render :template => 'journals/index', :layout => false, :content_type => 'application/atom+xml' }
122 format.atom { render :template => 'journals/index', :layout => false, :content_type => 'application/atom+xml' }
123 format.pdf {
123 format.pdf {
124 pdf = issue_to_pdf(@issue, :journals => @journals)
124 pdf = issue_to_pdf(@issue, :journals => @journals)
125 send_data(pdf, :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf")
125 send_data(pdf, :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf")
126 }
126 }
127 end
127 end
128 end
128 end
129
129
130 # Add a new issue
130 # Add a new issue
131 # The new issue will be created from an existing one if copy_from parameter is given
131 # The new issue will be created from an existing one if copy_from parameter is given
132 def new
132 def new
133 respond_to do |format|
133 respond_to do |format|
134 format.html { render :action => 'new', :layout => !request.xhr? }
134 format.html { render :action => 'new', :layout => !request.xhr? }
135 format.js { render :partial => 'update_form' }
136 end
135 end
137 end
136 end
138
137
139 def create
138 def create
140 call_hook(:controller_issues_new_before_save, { :params => params, :issue => @issue })
139 call_hook(:controller_issues_new_before_save, { :params => params, :issue => @issue })
141 @issue.save_attachments(params[:attachments] || (params[:issue] && params[:issue][:uploads]))
140 @issue.save_attachments(params[:attachments] || (params[:issue] && params[:issue][:uploads]))
142 if @issue.save
141 if @issue.save
143 call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue})
142 call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue})
144 respond_to do |format|
143 respond_to do |format|
145 format.html {
144 format.html {
146 render_attachment_warning_if_needed(@issue)
145 render_attachment_warning_if_needed(@issue)
147 flash[:notice] = l(:notice_issue_successful_create, :id => view_context.link_to("##{@issue.id}", issue_path(@issue), :title => @issue.subject))
146 flash[:notice] = l(:notice_issue_successful_create, :id => view_context.link_to("##{@issue.id}", issue_path(@issue), :title => @issue.subject))
148 if params[:continue]
147 if params[:continue]
149 attrs = {:tracker_id => @issue.tracker, :parent_issue_id => @issue.parent_issue_id}.reject {|k,v| v.nil?}
148 attrs = {:tracker_id => @issue.tracker, :parent_issue_id => @issue.parent_issue_id}.reject {|k,v| v.nil?}
150 redirect_to new_project_issue_path(@issue.project, :issue => attrs)
149 redirect_to new_project_issue_path(@issue.project, :issue => attrs)
151 else
150 else
152 redirect_to issue_path(@issue)
151 redirect_to issue_path(@issue)
153 end
152 end
154 }
153 }
155 format.api { render :action => 'show', :status => :created, :location => issue_url(@issue) }
154 format.api { render :action => 'show', :status => :created, :location => issue_url(@issue) }
156 end
155 end
157 return
156 return
158 else
157 else
159 respond_to do |format|
158 respond_to do |format|
160 format.html { render :action => 'new' }
159 format.html { render :action => 'new' }
161 format.api { render_validation_errors(@issue) }
160 format.api { render_validation_errors(@issue) }
162 end
161 end
163 end
162 end
164 end
163 end
165
164
166 def edit
165 def edit
167 return unless update_issue_from_params
166 return unless update_issue_from_params
168
167
169 respond_to do |format|
168 respond_to do |format|
170 format.html { }
169 format.html { }
171 format.xml { }
170 format.xml { }
172 end
171 end
173 end
172 end
174
173
175 def update
174 def update
176 return unless update_issue_from_params
175 return unless update_issue_from_params
177 @issue.save_attachments(params[:attachments] || (params[:issue] && params[:issue][:uploads]))
176 @issue.save_attachments(params[:attachments] || (params[:issue] && params[:issue][:uploads]))
178 saved = false
177 saved = false
179 begin
178 begin
180 saved = @issue.save_issue_with_child_records(params, @time_entry)
179 saved = @issue.save_issue_with_child_records(params, @time_entry)
181 rescue ActiveRecord::StaleObjectError
180 rescue ActiveRecord::StaleObjectError
182 @conflict = true
181 @conflict = true
183 if params[:last_journal_id]
182 if params[:last_journal_id]
184 @conflict_journals = @issue.journals_after(params[:last_journal_id]).all
183 @conflict_journals = @issue.journals_after(params[:last_journal_id]).all
185 @conflict_journals.reject!(&:private_notes?) unless User.current.allowed_to?(:view_private_notes, @issue.project)
184 @conflict_journals.reject!(&:private_notes?) unless User.current.allowed_to?(:view_private_notes, @issue.project)
186 end
185 end
187 end
186 end
188
187
189 if saved
188 if saved
190 render_attachment_warning_if_needed(@issue)
189 render_attachment_warning_if_needed(@issue)
191 flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record?
190 flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record?
192
191
193 respond_to do |format|
192 respond_to do |format|
194 format.html { redirect_back_or_default issue_path(@issue) }
193 format.html { redirect_back_or_default issue_path(@issue) }
195 format.api { render_api_ok }
194 format.api { render_api_ok }
196 end
195 end
197 else
196 else
198 respond_to do |format|
197 respond_to do |format|
199 format.html { render :action => 'edit' }
198 format.html { render :action => 'edit' }
200 format.api { render_validation_errors(@issue) }
199 format.api { render_validation_errors(@issue) }
201 end
200 end
202 end
201 end
203 end
202 end
204
203
204 # Updates the issue form when changing the project, status or tracker
205 # on issue creation/update
206 def update_form
207 end
208
205 # Bulk edit/copy a set of issues
209 # Bulk edit/copy a set of issues
206 def bulk_edit
210 def bulk_edit
207 @issues.sort!
211 @issues.sort!
208 @copy = params[:copy].present?
212 @copy = params[:copy].present?
209 @notes = params[:notes]
213 @notes = params[:notes]
210
214
211 if User.current.allowed_to?(:move_issues, @projects)
215 if User.current.allowed_to?(:move_issues, @projects)
212 @allowed_projects = Issue.allowed_target_projects_on_move
216 @allowed_projects = Issue.allowed_target_projects_on_move
213 if params[:issue]
217 if params[:issue]
214 @target_project = @allowed_projects.detect {|p| p.id.to_s == params[:issue][:project_id].to_s}
218 @target_project = @allowed_projects.detect {|p| p.id.to_s == params[:issue][:project_id].to_s}
215 if @target_project
219 if @target_project
216 target_projects = [@target_project]
220 target_projects = [@target_project]
217 end
221 end
218 end
222 end
219 end
223 end
220 target_projects ||= @projects
224 target_projects ||= @projects
221
225
222 if @copy
226 if @copy
223 @available_statuses = [IssueStatus.default]
227 @available_statuses = [IssueStatus.default]
224 else
228 else
225 @available_statuses = @issues.map(&:new_statuses_allowed_to).reduce(:&)
229 @available_statuses = @issues.map(&:new_statuses_allowed_to).reduce(:&)
226 end
230 end
227 @custom_fields = target_projects.map{|p|p.all_issue_custom_fields}.reduce(:&)
231 @custom_fields = target_projects.map{|p|p.all_issue_custom_fields}.reduce(:&)
228 @assignables = target_projects.map(&:assignable_users).reduce(:&)
232 @assignables = target_projects.map(&:assignable_users).reduce(:&)
229 @trackers = target_projects.map(&:trackers).reduce(:&)
233 @trackers = target_projects.map(&:trackers).reduce(:&)
230 @versions = target_projects.map {|p| p.shared_versions.open}.reduce(:&)
234 @versions = target_projects.map {|p| p.shared_versions.open}.reduce(:&)
231 @categories = target_projects.map {|p| p.issue_categories}.reduce(:&)
235 @categories = target_projects.map {|p| p.issue_categories}.reduce(:&)
232 if @copy
236 if @copy
233 @attachments_present = @issues.detect {|i| i.attachments.any?}.present?
237 @attachments_present = @issues.detect {|i| i.attachments.any?}.present?
234 @subtasks_present = @issues.detect {|i| !i.leaf?}.present?
238 @subtasks_present = @issues.detect {|i| !i.leaf?}.present?
235 end
239 end
236
240
237 @safe_attributes = @issues.map(&:safe_attribute_names).reduce(:&)
241 @safe_attributes = @issues.map(&:safe_attribute_names).reduce(:&)
238 render :layout => false if request.xhr?
242 render :layout => false if request.xhr?
239 end
243 end
240
244
241 def bulk_update
245 def bulk_update
242 @issues.sort!
246 @issues.sort!
243 @copy = params[:copy].present?
247 @copy = params[:copy].present?
244 attributes = parse_params_for_bulk_issue_attributes(params)
248 attributes = parse_params_for_bulk_issue_attributes(params)
245
249
246 unsaved_issue_ids = []
250 unsaved_issue_ids = []
247 moved_issues = []
251 moved_issues = []
248
252
249 if @copy && params[:copy_subtasks].present?
253 if @copy && params[:copy_subtasks].present?
250 # Descendant issues will be copied with the parent task
254 # Descendant issues will be copied with the parent task
251 # Don't copy them twice
255 # Don't copy them twice
252 @issues.reject! {|issue| @issues.detect {|other| issue.is_descendant_of?(other)}}
256 @issues.reject! {|issue| @issues.detect {|other| issue.is_descendant_of?(other)}}
253 end
257 end
254
258
255 @issues.each do |issue|
259 @issues.each do |issue|
256 issue.reload
260 issue.reload
257 if @copy
261 if @copy
258 issue = issue.copy({},
262 issue = issue.copy({},
259 :attachments => params[:copy_attachments].present?,
263 :attachments => params[:copy_attachments].present?,
260 :subtasks => params[:copy_subtasks].present?
264 :subtasks => params[:copy_subtasks].present?
261 )
265 )
262 end
266 end
263 journal = issue.init_journal(User.current, params[:notes])
267 journal = issue.init_journal(User.current, params[:notes])
264 issue.safe_attributes = attributes
268 issue.safe_attributes = attributes
265 call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
269 call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
266 if issue.save
270 if issue.save
267 moved_issues << issue
271 moved_issues << issue
268 else
272 else
269 # Keep unsaved issue ids to display them in flash error
273 # Keep unsaved issue ids to display them in flash error
270 unsaved_issue_ids << issue.id
274 unsaved_issue_ids << issue.id
271 end
275 end
272 end
276 end
273 set_flash_from_bulk_issue_save(@issues, unsaved_issue_ids)
277 set_flash_from_bulk_issue_save(@issues, unsaved_issue_ids)
274
278
275 if params[:follow]
279 if params[:follow]
276 if @issues.size == 1 && moved_issues.size == 1
280 if @issues.size == 1 && moved_issues.size == 1
277 redirect_to issue_path(moved_issues.first)
281 redirect_to issue_path(moved_issues.first)
278 elsif moved_issues.map(&:project).uniq.size == 1
282 elsif moved_issues.map(&:project).uniq.size == 1
279 redirect_to project_issues_path(moved_issues.map(&:project).first)
283 redirect_to project_issues_path(moved_issues.map(&:project).first)
280 end
284 end
281 else
285 else
282 redirect_back_or_default _project_issues_path(@project)
286 redirect_back_or_default _project_issues_path(@project)
283 end
287 end
284 end
288 end
285
289
286 def destroy
290 def destroy
287 @hours = TimeEntry.sum(:hours, :conditions => ['issue_id IN (?)', @issues]).to_f
291 @hours = TimeEntry.sum(:hours, :conditions => ['issue_id IN (?)', @issues]).to_f
288 if @hours > 0
292 if @hours > 0
289 case params[:todo]
293 case params[:todo]
290 when 'destroy'
294 when 'destroy'
291 # nothing to do
295 # nothing to do
292 when 'nullify'
296 when 'nullify'
293 TimeEntry.update_all('issue_id = NULL', ['issue_id IN (?)', @issues])
297 TimeEntry.update_all('issue_id = NULL', ['issue_id IN (?)', @issues])
294 when 'reassign'
298 when 'reassign'
295 reassign_to = @project.issues.find_by_id(params[:reassign_to_id])
299 reassign_to = @project.issues.find_by_id(params[:reassign_to_id])
296 if reassign_to.nil?
300 if reassign_to.nil?
297 flash.now[:error] = l(:error_issue_not_found_in_project)
301 flash.now[:error] = l(:error_issue_not_found_in_project)
298 return
302 return
299 else
303 else
300 TimeEntry.update_all("issue_id = #{reassign_to.id}", ['issue_id IN (?)', @issues])
304 TimeEntry.update_all("issue_id = #{reassign_to.id}", ['issue_id IN (?)', @issues])
301 end
305 end
302 else
306 else
303 # display the destroy form if it's a user request
307 # display the destroy form if it's a user request
304 return unless api_request?
308 return unless api_request?
305 end
309 end
306 end
310 end
307 @issues.each do |issue|
311 @issues.each do |issue|
308 begin
312 begin
309 issue.reload.destroy
313 issue.reload.destroy
310 rescue ::ActiveRecord::RecordNotFound # raised by #reload if issue no longer exists
314 rescue ::ActiveRecord::RecordNotFound # raised by #reload if issue no longer exists
311 # nothing to do, issue was already deleted (eg. by a parent)
315 # nothing to do, issue was already deleted (eg. by a parent)
312 end
316 end
313 end
317 end
314 respond_to do |format|
318 respond_to do |format|
315 format.html { redirect_back_or_default _project_issues_path(@project) }
319 format.html { redirect_back_or_default _project_issues_path(@project) }
316 format.api { render_api_ok }
320 format.api { render_api_ok }
317 end
321 end
318 end
322 end
319
323
320 private
324 private
321
325
322 def find_project
326 def find_project
323 project_id = params[:project_id] || (params[:issue] && params[:issue][:project_id])
327 project_id = params[:project_id] || (params[:issue] && params[:issue][:project_id])
324 @project = Project.find(project_id)
328 @project = Project.find(project_id)
325 rescue ActiveRecord::RecordNotFound
329 rescue ActiveRecord::RecordNotFound
326 render_404
330 render_404
327 end
331 end
328
332
329 def retrieve_previous_and_next_issue_ids
333 def retrieve_previous_and_next_issue_ids
330 retrieve_query_from_session
334 retrieve_query_from_session
331 if @query
335 if @query
332 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
336 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
333 sort_update(@query.sortable_columns, 'issues_index_sort')
337 sort_update(@query.sortable_columns, 'issues_index_sort')
334 limit = 500
338 limit = 500
335 issue_ids = @query.issue_ids(:order => sort_clause, :limit => (limit + 1), :include => [:assigned_to, :tracker, :priority, :category, :fixed_version])
339 issue_ids = @query.issue_ids(:order => sort_clause, :limit => (limit + 1), :include => [:assigned_to, :tracker, :priority, :category, :fixed_version])
336 if (idx = issue_ids.index(@issue.id)) && idx < limit
340 if (idx = issue_ids.index(@issue.id)) && idx < limit
337 if issue_ids.size < 500
341 if issue_ids.size < 500
338 @issue_position = idx + 1
342 @issue_position = idx + 1
339 @issue_count = issue_ids.size
343 @issue_count = issue_ids.size
340 end
344 end
341 @prev_issue_id = issue_ids[idx - 1] if idx > 0
345 @prev_issue_id = issue_ids[idx - 1] if idx > 0
342 @next_issue_id = issue_ids[idx + 1] if idx < (issue_ids.size - 1)
346 @next_issue_id = issue_ids[idx + 1] if idx < (issue_ids.size - 1)
343 end
347 end
344 end
348 end
345 end
349 end
346
350
347 # Used by #edit and #update to set some common instance variables
351 # Used by #edit and #update to set some common instance variables
348 # from the params
352 # from the params
349 # TODO: Refactor, not everything in here is needed by #edit
353 # TODO: Refactor, not everything in here is needed by #edit
350 def update_issue_from_params
354 def update_issue_from_params
351 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
355 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
352 @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
356 @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
353 @time_entry.attributes = params[:time_entry]
357 @time_entry.attributes = params[:time_entry]
354
358
355 @issue.init_journal(User.current)
359 @issue.init_journal(User.current)
356
360
357 issue_attributes = params[:issue]
361 issue_attributes = params[:issue]
358 if issue_attributes && params[:conflict_resolution]
362 if issue_attributes && params[:conflict_resolution]
359 case params[:conflict_resolution]
363 case params[:conflict_resolution]
360 when 'overwrite'
364 when 'overwrite'
361 issue_attributes = issue_attributes.dup
365 issue_attributes = issue_attributes.dup
362 issue_attributes.delete(:lock_version)
366 issue_attributes.delete(:lock_version)
363 when 'add_notes'
367 when 'add_notes'
364 issue_attributes = issue_attributes.slice(:notes)
368 issue_attributes = issue_attributes.slice(:notes)
365 when 'cancel'
369 when 'cancel'
366 redirect_to issue_path(@issue)
370 redirect_to issue_path(@issue)
367 return false
371 return false
368 end
372 end
369 end
373 end
370 @issue.safe_attributes = issue_attributes
374 @issue.safe_attributes = issue_attributes
371 @priorities = IssuePriority.active
375 @priorities = IssuePriority.active
372 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
376 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
373 true
377 true
374 end
378 end
375
379
376 # TODO: Refactor, lots of extra code in here
380 # TODO: Refactor, lots of extra code in here
377 # TODO: Changing tracker on an existing issue should not trigger this
381 # TODO: Changing tracker on an existing issue should not trigger this
378 def build_new_issue_from_params
382 def build_new_issue_from_params
379 if params[:id].blank?
383 if params[:id].blank?
380 @issue = Issue.new
384 @issue = Issue.new
381 if params[:copy_from]
385 if params[:copy_from]
382 begin
386 begin
383 @copy_from = Issue.visible.find(params[:copy_from])
387 @copy_from = Issue.visible.find(params[:copy_from])
384 @copy_attachments = params[:copy_attachments].present? || request.get?
388 @copy_attachments = params[:copy_attachments].present? || request.get?
385 @copy_subtasks = params[:copy_subtasks].present? || request.get?
389 @copy_subtasks = params[:copy_subtasks].present? || request.get?
386 @issue.copy_from(@copy_from, :attachments => @copy_attachments, :subtasks => @copy_subtasks)
390 @issue.copy_from(@copy_from, :attachments => @copy_attachments, :subtasks => @copy_subtasks)
387 rescue ActiveRecord::RecordNotFound
391 rescue ActiveRecord::RecordNotFound
388 render_404
392 render_404
389 return
393 return
390 end
394 end
391 end
395 end
392 @issue.project = @project
396 @issue.project = @project
393 else
397 else
394 @issue = @project.issues.visible.find(params[:id])
398 @issue = @project.issues.visible.find(params[:id])
395 end
399 end
396
400
397 @issue.project = @project
401 @issue.project = @project
398 @issue.author ||= User.current
402 @issue.author ||= User.current
399 # Tracker must be set before custom field values
403 # Tracker must be set before custom field values
400 @issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first)
404 @issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first)
401 if @issue.tracker.nil?
405 if @issue.tracker.nil?
402 render_error l(:error_no_tracker_in_project)
406 render_error l(:error_no_tracker_in_project)
403 return false
407 return false
404 end
408 end
405 @issue.start_date ||= Date.today if Setting.default_issue_start_date_to_creation_date?
409 @issue.start_date ||= Date.today if Setting.default_issue_start_date_to_creation_date?
406 @issue.safe_attributes = params[:issue]
410 @issue.safe_attributes = params[:issue]
407
411
408 @priorities = IssuePriority.active
412 @priorities = IssuePriority.active
409 @allowed_statuses = @issue.new_statuses_allowed_to(User.current, true)
413 @allowed_statuses = @issue.new_statuses_allowed_to(User.current, true)
410 @available_watchers = (@issue.project.users.sort + @issue.watcher_users).uniq
414 @available_watchers = (@issue.project.users.sort + @issue.watcher_users).uniq
411 end
415 end
412
416
413 def check_for_default_issue_status
417 def check_for_default_issue_status
414 if IssueStatus.default.nil?
418 if IssueStatus.default.nil?
415 render_error l(:error_no_default_issue_status)
419 render_error l(:error_no_default_issue_status)
416 return false
420 return false
417 end
421 end
418 end
422 end
419
423
420 def parse_params_for_bulk_issue_attributes(params)
424 def parse_params_for_bulk_issue_attributes(params)
421 attributes = (params[:issue] || {}).reject {|k,v| v.blank?}
425 attributes = (params[:issue] || {}).reject {|k,v| v.blank?}
422 attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
426 attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
423 if custom = attributes[:custom_field_values]
427 if custom = attributes[:custom_field_values]
424 custom.reject! {|k,v| v.blank?}
428 custom.reject! {|k,v| v.blank?}
425 custom.keys.each do |k|
429 custom.keys.each do |k|
426 if custom[k].is_a?(Array)
430 if custom[k].is_a?(Array)
427 custom[k] << '' if custom[k].delete('__none__')
431 custom[k] << '' if custom[k].delete('__none__')
428 else
432 else
429 custom[k] = '' if custom[k] == '__none__'
433 custom[k] = '' if custom[k] == '__none__'
430 end
434 end
431 end
435 end
432 end
436 end
433 attributes
437 attributes
434 end
438 end
435 end
439 end
1 NO CONTENT: file renamed from app/views/issues/_update_form.js.erb to app/views/issues/update_form.js.erb
NO CONTENT: file renamed from app/views/issues/_update_form.js.erb to app/views/issues/update_form.js.erb
@@ -1,347 +1,347
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2013 Jean-Philippe Lang
2 # Copyright (C) 2006-2013 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 RedmineApp::Application.routes.draw do
18 RedmineApp::Application.routes.draw do
19 root :to => 'welcome#index', :as => 'home'
19 root :to => 'welcome#index', :as => 'home'
20
20
21 match 'login', :to => 'account#login', :as => 'signin', :via => [:get, :post]
21 match 'login', :to => 'account#login', :as => 'signin', :via => [:get, :post]
22 match 'logout', :to => 'account#logout', :as => 'signout', :via => [:get, :post]
22 match 'logout', :to => 'account#logout', :as => 'signout', :via => [:get, :post]
23 match 'account/register', :to => 'account#register', :via => [:get, :post], :as => 'register'
23 match 'account/register', :to => 'account#register', :via => [:get, :post], :as => 'register'
24 match 'account/lost_password', :to => 'account#lost_password', :via => [:get, :post], :as => 'lost_password'
24 match 'account/lost_password', :to => 'account#lost_password', :via => [:get, :post], :as => 'lost_password'
25 match 'account/activate', :to => 'account#activate', :via => :get
25 match 'account/activate', :to => 'account#activate', :via => :get
26
26
27 match '/news/preview', :controller => 'previews', :action => 'news', :as => 'preview_news', :via => [:get, :post, :put]
27 match '/news/preview', :controller => 'previews', :action => 'news', :as => 'preview_news', :via => [:get, :post, :put]
28 match '/issues/preview/new/:project_id', :to => 'previews#issue', :as => 'preview_new_issue', :via => [:get, :post, :put]
28 match '/issues/preview/new/:project_id', :to => 'previews#issue', :as => 'preview_new_issue', :via => [:get, :post, :put]
29 match '/issues/preview/edit/:id', :to => 'previews#issue', :as => 'preview_edit_issue', :via => [:get, :post, :put]
29 match '/issues/preview/edit/:id', :to => 'previews#issue', :as => 'preview_edit_issue', :via => [:get, :post, :put]
30 match '/issues/preview', :to => 'previews#issue', :as => 'preview_issue', :via => [:get, :post, :put]
30 match '/issues/preview', :to => 'previews#issue', :as => 'preview_issue', :via => [:get, :post, :put]
31
31
32 match 'projects/:id/wiki', :to => 'wikis#edit', :via => :post
32 match 'projects/:id/wiki', :to => 'wikis#edit', :via => :post
33 match 'projects/:id/wiki/destroy', :to => 'wikis#destroy', :via => [:get, :post]
33 match 'projects/:id/wiki/destroy', :to => 'wikis#destroy', :via => [:get, :post]
34
34
35 match 'boards/:board_id/topics/new', :to => 'messages#new', :via => [:get, :post], :as => 'new_board_message'
35 match 'boards/:board_id/topics/new', :to => 'messages#new', :via => [:get, :post], :as => 'new_board_message'
36 get 'boards/:board_id/topics/:id', :to => 'messages#show', :as => 'board_message'
36 get 'boards/:board_id/topics/:id', :to => 'messages#show', :as => 'board_message'
37 match 'boards/:board_id/topics/quote/:id', :to => 'messages#quote', :via => [:get, :post]
37 match 'boards/:board_id/topics/quote/:id', :to => 'messages#quote', :via => [:get, :post]
38 get 'boards/:board_id/topics/:id/edit', :to => 'messages#edit'
38 get 'boards/:board_id/topics/:id/edit', :to => 'messages#edit'
39
39
40 post 'boards/:board_id/topics/preview', :to => 'messages#preview', :as => 'preview_board_message'
40 post 'boards/:board_id/topics/preview', :to => 'messages#preview', :as => 'preview_board_message'
41 post 'boards/:board_id/topics/:id/replies', :to => 'messages#reply'
41 post 'boards/:board_id/topics/:id/replies', :to => 'messages#reply'
42 post 'boards/:board_id/topics/:id/edit', :to => 'messages#edit'
42 post 'boards/:board_id/topics/:id/edit', :to => 'messages#edit'
43 post 'boards/:board_id/topics/:id/destroy', :to => 'messages#destroy'
43 post 'boards/:board_id/topics/:id/destroy', :to => 'messages#destroy'
44
44
45 # Misc issue routes. TODO: move into resources
45 # Misc issue routes. TODO: move into resources
46 match '/issues/auto_complete', :to => 'auto_completes#issues', :via => :get, :as => 'auto_complete_issues'
46 match '/issues/auto_complete', :to => 'auto_completes#issues', :via => :get, :as => 'auto_complete_issues'
47 match '/issues/context_menu', :to => 'context_menus#issues', :as => 'issues_context_menu', :via => [:get, :post]
47 match '/issues/context_menu', :to => 'context_menus#issues', :as => 'issues_context_menu', :via => [:get, :post]
48 match '/issues/changes', :to => 'journals#index', :as => 'issue_changes', :via => :get
48 match '/issues/changes', :to => 'journals#index', :as => 'issue_changes', :via => :get
49 match '/issues/:id/quoted', :to => 'journals#new', :id => /\d+/, :via => :post, :as => 'quoted_issue'
49 match '/issues/:id/quoted', :to => 'journals#new', :id => /\d+/, :via => :post, :as => 'quoted_issue'
50
50
51 match '/journals/diff/:id', :to => 'journals#diff', :id => /\d+/, :via => :get
51 match '/journals/diff/:id', :to => 'journals#diff', :id => /\d+/, :via => :get
52 match '/journals/edit/:id', :to => 'journals#edit', :id => /\d+/, :via => [:get, :post]
52 match '/journals/edit/:id', :to => 'journals#edit', :id => /\d+/, :via => [:get, :post]
53
53
54 get '/projects/:project_id/issues/gantt', :to => 'gantts#show', :as => 'project_gantt'
54 get '/projects/:project_id/issues/gantt', :to => 'gantts#show', :as => 'project_gantt'
55 get '/issues/gantt', :to => 'gantts#show'
55 get '/issues/gantt', :to => 'gantts#show'
56
56
57 get '/projects/:project_id/issues/calendar', :to => 'calendars#show', :as => 'project_calendar'
57 get '/projects/:project_id/issues/calendar', :to => 'calendars#show', :as => 'project_calendar'
58 get '/issues/calendar', :to => 'calendars#show'
58 get '/issues/calendar', :to => 'calendars#show'
59
59
60 get 'projects/:id/issues/report', :to => 'reports#issue_report', :as => 'project_issues_report'
60 get 'projects/:id/issues/report', :to => 'reports#issue_report', :as => 'project_issues_report'
61 get 'projects/:id/issues/report/:detail', :to => 'reports#issue_report_details', :as => 'project_issues_report_details'
61 get 'projects/:id/issues/report/:detail', :to => 'reports#issue_report_details', :as => 'project_issues_report_details'
62
62
63 match 'my/account', :controller => 'my', :action => 'account', :via => [:get, :post]
63 match 'my/account', :controller => 'my', :action => 'account', :via => [:get, :post]
64 match 'my/account/destroy', :controller => 'my', :action => 'destroy', :via => [:get, :post]
64 match 'my/account/destroy', :controller => 'my', :action => 'destroy', :via => [:get, :post]
65 match 'my/page', :controller => 'my', :action => 'page', :via => :get
65 match 'my/page', :controller => 'my', :action => 'page', :via => :get
66 match 'my', :controller => 'my', :action => 'index', :via => :get # Redirects to my/page
66 match 'my', :controller => 'my', :action => 'index', :via => :get # Redirects to my/page
67 match 'my/reset_rss_key', :controller => 'my', :action => 'reset_rss_key', :via => :post
67 match 'my/reset_rss_key', :controller => 'my', :action => 'reset_rss_key', :via => :post
68 match 'my/reset_api_key', :controller => 'my', :action => 'reset_api_key', :via => :post
68 match 'my/reset_api_key', :controller => 'my', :action => 'reset_api_key', :via => :post
69 match 'my/password', :controller => 'my', :action => 'password', :via => [:get, :post]
69 match 'my/password', :controller => 'my', :action => 'password', :via => [:get, :post]
70 match 'my/page_layout', :controller => 'my', :action => 'page_layout', :via => :get
70 match 'my/page_layout', :controller => 'my', :action => 'page_layout', :via => :get
71 match 'my/add_block', :controller => 'my', :action => 'add_block', :via => :post
71 match 'my/add_block', :controller => 'my', :action => 'add_block', :via => :post
72 match 'my/remove_block', :controller => 'my', :action => 'remove_block', :via => :post
72 match 'my/remove_block', :controller => 'my', :action => 'remove_block', :via => :post
73 match 'my/order_blocks', :controller => 'my', :action => 'order_blocks', :via => :post
73 match 'my/order_blocks', :controller => 'my', :action => 'order_blocks', :via => :post
74
74
75 resources :users
75 resources :users
76 match 'users/:id/memberships/:membership_id', :to => 'users#edit_membership', :via => :put, :as => 'user_membership'
76 match 'users/:id/memberships/:membership_id', :to => 'users#edit_membership', :via => :put, :as => 'user_membership'
77 match 'users/:id/memberships/:membership_id', :to => 'users#destroy_membership', :via => :delete
77 match 'users/:id/memberships/:membership_id', :to => 'users#destroy_membership', :via => :delete
78 match 'users/:id/memberships', :to => 'users#edit_membership', :via => :post, :as => 'user_memberships'
78 match 'users/:id/memberships', :to => 'users#edit_membership', :via => :post, :as => 'user_memberships'
79
79
80 post 'watchers/watch', :to => 'watchers#watch', :as => 'watch'
80 post 'watchers/watch', :to => 'watchers#watch', :as => 'watch'
81 delete 'watchers/watch', :to => 'watchers#unwatch'
81 delete 'watchers/watch', :to => 'watchers#unwatch'
82 get 'watchers/new', :to => 'watchers#new'
82 get 'watchers/new', :to => 'watchers#new'
83 post 'watchers', :to => 'watchers#create'
83 post 'watchers', :to => 'watchers#create'
84 post 'watchers/append', :to => 'watchers#append'
84 post 'watchers/append', :to => 'watchers#append'
85 post 'watchers/destroy', :to => 'watchers#destroy'
85 post 'watchers/destroy', :to => 'watchers#destroy'
86 get 'watchers/autocomplete_for_user', :to => 'watchers#autocomplete_for_user'
86 get 'watchers/autocomplete_for_user', :to => 'watchers#autocomplete_for_user'
87 # Specific routes for issue watchers API
87 # Specific routes for issue watchers API
88 post 'issues/:object_id/watchers', :to => 'watchers#create', :object_type => 'issue'
88 post 'issues/:object_id/watchers', :to => 'watchers#create', :object_type => 'issue'
89 delete 'issues/:object_id/watchers/:user_id' => 'watchers#destroy', :object_type => 'issue'
89 delete 'issues/:object_id/watchers/:user_id' => 'watchers#destroy', :object_type => 'issue'
90
90
91 resources :projects do
91 resources :projects do
92 member do
92 member do
93 get 'settings(/:tab)', :action => 'settings', :as => 'settings'
93 get 'settings(/:tab)', :action => 'settings', :as => 'settings'
94 post 'modules'
94 post 'modules'
95 post 'archive'
95 post 'archive'
96 post 'unarchive'
96 post 'unarchive'
97 post 'close'
97 post 'close'
98 post 'reopen'
98 post 'reopen'
99 match 'copy', :via => [:get, :post]
99 match 'copy', :via => [:get, :post]
100 end
100 end
101
101
102 resources :memberships, :shallow => true, :controller => 'members', :only => [:index, :show, :new, :create, :update, :destroy] do
102 resources :memberships, :shallow => true, :controller => 'members', :only => [:index, :show, :new, :create, :update, :destroy] do
103 collection do
103 collection do
104 get 'autocomplete'
104 get 'autocomplete'
105 end
105 end
106 end
106 end
107
107
108 resource :enumerations, :controller => 'project_enumerations', :only => [:update, :destroy]
108 resource :enumerations, :controller => 'project_enumerations', :only => [:update, :destroy]
109
109
110 get 'issues/:copy_from/copy', :to => 'issues#new', :as => 'copy_issue'
110 get 'issues/:copy_from/copy', :to => 'issues#new', :as => 'copy_issue'
111 resources :issues, :only => [:index, :new, :create] do
111 resources :issues, :only => [:index, :new, :create] do
112 resources :time_entries, :controller => 'timelog' do
112 resources :time_entries, :controller => 'timelog' do
113 collection do
113 collection do
114 get 'report'
114 get 'report'
115 end
115 end
116 end
116 end
117 end
117 end
118 # issue form update
118 # issue form update
119 match 'issues/new', :controller => 'issues', :action => 'new', :via => [:put, :post], :as => 'issue_form'
119 match 'issues/update_form', :controller => 'issues', :action => 'update_form', :via => [:put, :post], :as => 'issue_form'
120
120
121 resources :files, :only => [:index, :new, :create]
121 resources :files, :only => [:index, :new, :create]
122
122
123 resources :versions, :except => [:index, :show, :edit, :update, :destroy] do
123 resources :versions, :except => [:index, :show, :edit, :update, :destroy] do
124 collection do
124 collection do
125 put 'close_completed'
125 put 'close_completed'
126 end
126 end
127 end
127 end
128 get 'versions.:format', :to => 'versions#index'
128 get 'versions.:format', :to => 'versions#index'
129 get 'roadmap', :to => 'versions#index', :format => false
129 get 'roadmap', :to => 'versions#index', :format => false
130 get 'versions', :to => 'versions#index'
130 get 'versions', :to => 'versions#index'
131
131
132 resources :news, :except => [:show, :edit, :update, :destroy]
132 resources :news, :except => [:show, :edit, :update, :destroy]
133 resources :time_entries, :controller => 'timelog' do
133 resources :time_entries, :controller => 'timelog' do
134 get 'report', :on => :collection
134 get 'report', :on => :collection
135 end
135 end
136 resources :queries, :only => [:new, :create]
136 resources :queries, :only => [:new, :create]
137 resources :issue_categories, :shallow => true
137 resources :issue_categories, :shallow => true
138 resources :documents, :except => [:show, :edit, :update, :destroy]
138 resources :documents, :except => [:show, :edit, :update, :destroy]
139 resources :boards
139 resources :boards
140 resources :repositories, :shallow => true, :except => [:index, :show] do
140 resources :repositories, :shallow => true, :except => [:index, :show] do
141 member do
141 member do
142 match 'committers', :via => [:get, :post]
142 match 'committers', :via => [:get, :post]
143 end
143 end
144 end
144 end
145
145
146 match 'wiki/index', :controller => 'wiki', :action => 'index', :via => :get
146 match 'wiki/index', :controller => 'wiki', :action => 'index', :via => :get
147 resources :wiki, :except => [:index, :new, :create], :as => 'wiki_page' do
147 resources :wiki, :except => [:index, :new, :create], :as => 'wiki_page' do
148 member do
148 member do
149 get 'rename'
149 get 'rename'
150 post 'rename'
150 post 'rename'
151 get 'history'
151 get 'history'
152 get 'diff'
152 get 'diff'
153 match 'preview', :via => [:post, :put]
153 match 'preview', :via => [:post, :put]
154 post 'protect'
154 post 'protect'
155 post 'add_attachment'
155 post 'add_attachment'
156 end
156 end
157 collection do
157 collection do
158 get 'export'
158 get 'export'
159 get 'date_index'
159 get 'date_index'
160 end
160 end
161 end
161 end
162 match 'wiki', :controller => 'wiki', :action => 'show', :via => :get
162 match 'wiki', :controller => 'wiki', :action => 'show', :via => :get
163 get 'wiki/:id/:version', :to => 'wiki#show', :constraints => {:version => /\d+/}
163 get 'wiki/:id/:version', :to => 'wiki#show', :constraints => {:version => /\d+/}
164 delete 'wiki/:id/:version', :to => 'wiki#destroy_version'
164 delete 'wiki/:id/:version', :to => 'wiki#destroy_version'
165 get 'wiki/:id/:version/annotate', :to => 'wiki#annotate'
165 get 'wiki/:id/:version/annotate', :to => 'wiki#annotate'
166 get 'wiki/:id/:version/diff', :to => 'wiki#diff'
166 get 'wiki/:id/:version/diff', :to => 'wiki#diff'
167 end
167 end
168
168
169 resources :issues do
169 resources :issues do
170 collection do
170 collection do
171 match 'bulk_edit', :via => [:get, :post]
171 match 'bulk_edit', :via => [:get, :post]
172 post 'bulk_update'
172 post 'bulk_update'
173 end
173 end
174 resources :time_entries, :controller => 'timelog' do
174 resources :time_entries, :controller => 'timelog' do
175 collection do
175 collection do
176 get 'report'
176 get 'report'
177 end
177 end
178 end
178 end
179 resources :relations, :shallow => true, :controller => 'issue_relations', :only => [:index, :show, :create, :destroy]
179 resources :relations, :shallow => true, :controller => 'issue_relations', :only => [:index, :show, :create, :destroy]
180 end
180 end
181 match '/issues', :controller => 'issues', :action => 'destroy', :via => :delete
181 match '/issues', :controller => 'issues', :action => 'destroy', :via => :delete
182
182
183 resources :queries, :except => [:show]
183 resources :queries, :except => [:show]
184
184
185 resources :news, :only => [:index, :show, :edit, :update, :destroy]
185 resources :news, :only => [:index, :show, :edit, :update, :destroy]
186 match '/news/:id/comments', :to => 'comments#create', :via => :post
186 match '/news/:id/comments', :to => 'comments#create', :via => :post
187 match '/news/:id/comments/:comment_id', :to => 'comments#destroy', :via => :delete
187 match '/news/:id/comments/:comment_id', :to => 'comments#destroy', :via => :delete
188
188
189 resources :versions, :only => [:show, :edit, :update, :destroy] do
189 resources :versions, :only => [:show, :edit, :update, :destroy] do
190 post 'status_by', :on => :member
190 post 'status_by', :on => :member
191 end
191 end
192
192
193 resources :documents, :only => [:show, :edit, :update, :destroy] do
193 resources :documents, :only => [:show, :edit, :update, :destroy] do
194 post 'add_attachment', :on => :member
194 post 'add_attachment', :on => :member
195 end
195 end
196
196
197 match '/time_entries/context_menu', :to => 'context_menus#time_entries', :as => :time_entries_context_menu, :via => [:get, :post]
197 match '/time_entries/context_menu', :to => 'context_menus#time_entries', :as => :time_entries_context_menu, :via => [:get, :post]
198
198
199 resources :time_entries, :controller => 'timelog', :except => :destroy do
199 resources :time_entries, :controller => 'timelog', :except => :destroy do
200 collection do
200 collection do
201 get 'report'
201 get 'report'
202 get 'bulk_edit'
202 get 'bulk_edit'
203 post 'bulk_update'
203 post 'bulk_update'
204 end
204 end
205 end
205 end
206 match '/time_entries/:id', :to => 'timelog#destroy', :via => :delete, :id => /\d+/
206 match '/time_entries/:id', :to => 'timelog#destroy', :via => :delete, :id => /\d+/
207 # TODO: delete /time_entries for bulk deletion
207 # TODO: delete /time_entries for bulk deletion
208 match '/time_entries/destroy', :to => 'timelog#destroy', :via => :delete
208 match '/time_entries/destroy', :to => 'timelog#destroy', :via => :delete
209
209
210 get 'projects/:id/activity', :to => 'activities#index'
210 get 'projects/:id/activity', :to => 'activities#index'
211 get 'projects/:id/activity.:format', :to => 'activities#index'
211 get 'projects/:id/activity.:format', :to => 'activities#index'
212 get 'activity', :to => 'activities#index'
212 get 'activity', :to => 'activities#index'
213
213
214 # repositories routes
214 # repositories routes
215 get 'projects/:id/repository/:repository_id/statistics', :to => 'repositories#stats'
215 get 'projects/:id/repository/:repository_id/statistics', :to => 'repositories#stats'
216 get 'projects/:id/repository/:repository_id/graph', :to => 'repositories#graph'
216 get 'projects/:id/repository/:repository_id/graph', :to => 'repositories#graph'
217
217
218 get 'projects/:id/repository/:repository_id/changes(/*path(.:ext))',
218 get 'projects/:id/repository/:repository_id/changes(/*path(.:ext))',
219 :to => 'repositories#changes'
219 :to => 'repositories#changes'
220
220
221 get 'projects/:id/repository/:repository_id/revisions/:rev', :to => 'repositories#revision'
221 get 'projects/:id/repository/:repository_id/revisions/:rev', :to => 'repositories#revision'
222 get 'projects/:id/repository/:repository_id/revision', :to => 'repositories#revision'
222 get 'projects/:id/repository/:repository_id/revision', :to => 'repositories#revision'
223 post 'projects/:id/repository/:repository_id/revisions/:rev/issues', :to => 'repositories#add_related_issue'
223 post 'projects/:id/repository/:repository_id/revisions/:rev/issues', :to => 'repositories#add_related_issue'
224 delete 'projects/:id/repository/:repository_id/revisions/:rev/issues/:issue_id', :to => 'repositories#remove_related_issue'
224 delete 'projects/:id/repository/:repository_id/revisions/:rev/issues/:issue_id', :to => 'repositories#remove_related_issue'
225 get 'projects/:id/repository/:repository_id/revisions', :to => 'repositories#revisions'
225 get 'projects/:id/repository/:repository_id/revisions', :to => 'repositories#revisions'
226 get 'projects/:id/repository/:repository_id/revisions/:rev/:action(/*path(.:ext))',
226 get 'projects/:id/repository/:repository_id/revisions/:rev/:action(/*path(.:ext))',
227 :controller => 'repositories',
227 :controller => 'repositories',
228 :format => false,
228 :format => false,
229 :constraints => {
229 :constraints => {
230 :action => /(browse|show|entry|raw|annotate|diff)/,
230 :action => /(browse|show|entry|raw|annotate|diff)/,
231 :rev => /[a-z0-9\.\-_]+/
231 :rev => /[a-z0-9\.\-_]+/
232 }
232 }
233
233
234 get 'projects/:id/repository/statistics', :to => 'repositories#stats'
234 get 'projects/:id/repository/statistics', :to => 'repositories#stats'
235 get 'projects/:id/repository/graph', :to => 'repositories#graph'
235 get 'projects/:id/repository/graph', :to => 'repositories#graph'
236
236
237 get 'projects/:id/repository/changes(/*path(.:ext))',
237 get 'projects/:id/repository/changes(/*path(.:ext))',
238 :to => 'repositories#changes'
238 :to => 'repositories#changes'
239
239
240 get 'projects/:id/repository/revisions', :to => 'repositories#revisions'
240 get 'projects/:id/repository/revisions', :to => 'repositories#revisions'
241 get 'projects/:id/repository/revisions/:rev', :to => 'repositories#revision'
241 get 'projects/:id/repository/revisions/:rev', :to => 'repositories#revision'
242 get 'projects/:id/repository/revision', :to => 'repositories#revision'
242 get 'projects/:id/repository/revision', :to => 'repositories#revision'
243 post 'projects/:id/repository/revisions/:rev/issues', :to => 'repositories#add_related_issue'
243 post 'projects/:id/repository/revisions/:rev/issues', :to => 'repositories#add_related_issue'
244 delete 'projects/:id/repository/revisions/:rev/issues/:issue_id', :to => 'repositories#remove_related_issue'
244 delete 'projects/:id/repository/revisions/:rev/issues/:issue_id', :to => 'repositories#remove_related_issue'
245 get 'projects/:id/repository/revisions/:rev/:action(/*path(.:ext))',
245 get 'projects/:id/repository/revisions/:rev/:action(/*path(.:ext))',
246 :controller => 'repositories',
246 :controller => 'repositories',
247 :format => false,
247 :format => false,
248 :constraints => {
248 :constraints => {
249 :action => /(browse|show|entry|raw|annotate|diff)/,
249 :action => /(browse|show|entry|raw|annotate|diff)/,
250 :rev => /[a-z0-9\.\-_]+/
250 :rev => /[a-z0-9\.\-_]+/
251 }
251 }
252 get 'projects/:id/repository/:repository_id/:action(/*path(.:ext))',
252 get 'projects/:id/repository/:repository_id/:action(/*path(.:ext))',
253 :controller => 'repositories',
253 :controller => 'repositories',
254 :action => /(browse|show|entry|raw|changes|annotate|diff)/
254 :action => /(browse|show|entry|raw|changes|annotate|diff)/
255 get 'projects/:id/repository/:action(/*path(.:ext))',
255 get 'projects/:id/repository/:action(/*path(.:ext))',
256 :controller => 'repositories',
256 :controller => 'repositories',
257 :action => /(browse|show|entry|raw|changes|annotate|diff)/
257 :action => /(browse|show|entry|raw|changes|annotate|diff)/
258
258
259 get 'projects/:id/repository/:repository_id', :to => 'repositories#show', :path => nil
259 get 'projects/:id/repository/:repository_id', :to => 'repositories#show', :path => nil
260 get 'projects/:id/repository', :to => 'repositories#show', :path => nil
260 get 'projects/:id/repository', :to => 'repositories#show', :path => nil
261
261
262 # additional routes for having the file name at the end of url
262 # additional routes for having the file name at the end of url
263 get 'attachments/:id/:filename', :to => 'attachments#show', :id => /\d+/, :filename => /.*/, :as => 'named_attachment'
263 get 'attachments/:id/:filename', :to => 'attachments#show', :id => /\d+/, :filename => /.*/, :as => 'named_attachment'
264 get 'attachments/download/:id/:filename', :to => 'attachments#download', :id => /\d+/, :filename => /.*/, :as => 'download_named_attachment'
264 get 'attachments/download/:id/:filename', :to => 'attachments#download', :id => /\d+/, :filename => /.*/, :as => 'download_named_attachment'
265 get 'attachments/download/:id', :to => 'attachments#download', :id => /\d+/
265 get 'attachments/download/:id', :to => 'attachments#download', :id => /\d+/
266 get 'attachments/thumbnail/:id(/:size)', :to => 'attachments#thumbnail', :id => /\d+/, :size => /\d+/, :as => 'thumbnail'
266 get 'attachments/thumbnail/:id(/:size)', :to => 'attachments#thumbnail', :id => /\d+/, :size => /\d+/, :as => 'thumbnail'
267 resources :attachments, :only => [:show, :destroy]
267 resources :attachments, :only => [:show, :destroy]
268
268
269 resources :groups do
269 resources :groups do
270 member do
270 member do
271 get 'autocomplete_for_user'
271 get 'autocomplete_for_user'
272 end
272 end
273 end
273 end
274
274
275 match 'groups/:id/users', :controller => 'groups', :action => 'add_users', :id => /\d+/, :via => :post, :as => 'group_users'
275 match 'groups/:id/users', :controller => 'groups', :action => 'add_users', :id => /\d+/, :via => :post, :as => 'group_users'
276 match 'groups/:id/users/:user_id', :controller => 'groups', :action => 'remove_user', :id => /\d+/, :via => :delete, :as => 'group_user'
276 match 'groups/:id/users/:user_id', :controller => 'groups', :action => 'remove_user', :id => /\d+/, :via => :delete, :as => 'group_user'
277 match 'groups/destroy_membership/:id', :controller => 'groups', :action => 'destroy_membership', :id => /\d+/, :via => :post
277 match 'groups/destroy_membership/:id', :controller => 'groups', :action => 'destroy_membership', :id => /\d+/, :via => :post
278 match 'groups/edit_membership/:id', :controller => 'groups', :action => 'edit_membership', :id => /\d+/, :via => :post
278 match 'groups/edit_membership/:id', :controller => 'groups', :action => 'edit_membership', :id => /\d+/, :via => :post
279
279
280 resources :trackers, :except => :show do
280 resources :trackers, :except => :show do
281 collection do
281 collection do
282 match 'fields', :via => [:get, :post]
282 match 'fields', :via => [:get, :post]
283 end
283 end
284 end
284 end
285 resources :issue_statuses, :except => :show do
285 resources :issue_statuses, :except => :show do
286 collection do
286 collection do
287 post 'update_issue_done_ratio'
287 post 'update_issue_done_ratio'
288 end
288 end
289 end
289 end
290 resources :custom_fields, :except => :show
290 resources :custom_fields, :except => :show
291 resources :roles do
291 resources :roles do
292 collection do
292 collection do
293 match 'permissions', :via => [:get, :post]
293 match 'permissions', :via => [:get, :post]
294 end
294 end
295 end
295 end
296 resources :enumerations, :except => :show
296 resources :enumerations, :except => :show
297 match 'enumerations/:type', :to => 'enumerations#index', :via => :get
297 match 'enumerations/:type', :to => 'enumerations#index', :via => :get
298
298
299 get 'projects/:id/search', :controller => 'search', :action => 'index'
299 get 'projects/:id/search', :controller => 'search', :action => 'index'
300 get 'search', :controller => 'search', :action => 'index'
300 get 'search', :controller => 'search', :action => 'index'
301
301
302 match 'mail_handler', :controller => 'mail_handler', :action => 'index', :via => :post
302 match 'mail_handler', :controller => 'mail_handler', :action => 'index', :via => :post
303
303
304 match 'admin', :controller => 'admin', :action => 'index', :via => :get
304 match 'admin', :controller => 'admin', :action => 'index', :via => :get
305 match 'admin/projects', :controller => 'admin', :action => 'projects', :via => :get
305 match 'admin/projects', :controller => 'admin', :action => 'projects', :via => :get
306 match 'admin/plugins', :controller => 'admin', :action => 'plugins', :via => :get
306 match 'admin/plugins', :controller => 'admin', :action => 'plugins', :via => :get
307 match 'admin/info', :controller => 'admin', :action => 'info', :via => :get
307 match 'admin/info', :controller => 'admin', :action => 'info', :via => :get
308 match 'admin/test_email', :controller => 'admin', :action => 'test_email', :via => :get
308 match 'admin/test_email', :controller => 'admin', :action => 'test_email', :via => :get
309 match 'admin/default_configuration', :controller => 'admin', :action => 'default_configuration', :via => :post
309 match 'admin/default_configuration', :controller => 'admin', :action => 'default_configuration', :via => :post
310
310
311 resources :auth_sources do
311 resources :auth_sources do
312 member do
312 member do
313 get 'test_connection', :as => 'try_connection'
313 get 'test_connection', :as => 'try_connection'
314 end
314 end
315 collection do
315 collection do
316 get 'autocomplete_for_new_user'
316 get 'autocomplete_for_new_user'
317 end
317 end
318 end
318 end
319
319
320 match 'workflows', :controller => 'workflows', :action => 'index', :via => :get
320 match 'workflows', :controller => 'workflows', :action => 'index', :via => :get
321 match 'workflows/edit', :controller => 'workflows', :action => 'edit', :via => [:get, :post]
321 match 'workflows/edit', :controller => 'workflows', :action => 'edit', :via => [:get, :post]
322 match 'workflows/permissions', :controller => 'workflows', :action => 'permissions', :via => [:get, :post]
322 match 'workflows/permissions', :controller => 'workflows', :action => 'permissions', :via => [:get, :post]
323 match 'workflows/copy', :controller => 'workflows', :action => 'copy', :via => [:get, :post]
323 match 'workflows/copy', :controller => 'workflows', :action => 'copy', :via => [:get, :post]
324 match 'settings', :controller => 'settings', :action => 'index', :via => :get
324 match 'settings', :controller => 'settings', :action => 'index', :via => :get
325 match 'settings/edit', :controller => 'settings', :action => 'edit', :via => [:get, :post]
325 match 'settings/edit', :controller => 'settings', :action => 'edit', :via => [:get, :post]
326 match 'settings/plugin/:id', :controller => 'settings', :action => 'plugin', :via => [:get, :post], :as => 'plugin_settings'
326 match 'settings/plugin/:id', :controller => 'settings', :action => 'plugin', :via => [:get, :post], :as => 'plugin_settings'
327
327
328 match 'sys/projects', :to => 'sys#projects', :via => :get
328 match 'sys/projects', :to => 'sys#projects', :via => :get
329 match 'sys/projects/:id/repository', :to => 'sys#create_project_repository', :via => :post
329 match 'sys/projects/:id/repository', :to => 'sys#create_project_repository', :via => :post
330 match 'sys/fetch_changesets', :to => 'sys#fetch_changesets', :via => :get
330 match 'sys/fetch_changesets', :to => 'sys#fetch_changesets', :via => :get
331
331
332 match 'uploads', :to => 'attachments#upload', :via => :post
332 match 'uploads', :to => 'attachments#upload', :via => :post
333
333
334 get 'robots.txt', :to => 'welcome#robots'
334 get 'robots.txt', :to => 'welcome#robots'
335
335
336 Dir.glob File.expand_path("plugins/*", Rails.root) do |plugin_dir|
336 Dir.glob File.expand_path("plugins/*", Rails.root) do |plugin_dir|
337 file = File.join(plugin_dir, "config/routes.rb")
337 file = File.join(plugin_dir, "config/routes.rb")
338 if File.exists?(file)
338 if File.exists?(file)
339 begin
339 begin
340 instance_eval File.read(file)
340 instance_eval File.read(file)
341 rescue Exception => e
341 rescue Exception => e
342 puts "An error occurred while loading the routes definition of #{File.basename(plugin_dir)} plugin (#{file}): #{e.message}."
342 puts "An error occurred while loading the routes definition of #{File.basename(plugin_dir)} plugin (#{file}): #{e.message}."
343 exit 1
343 exit 1
344 end
344 end
345 end
345 end
346 end
346 end
347 end
347 end
@@ -1,284 +1,284
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2013 Jean-Philippe Lang
2 # Copyright (C) 2006-2013 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require 'redmine/core_ext'
18 require 'redmine/core_ext'
19
19
20 begin
20 begin
21 require 'RMagick' unless Object.const_defined?(:Magick)
21 require 'RMagick' unless Object.const_defined?(:Magick)
22 rescue LoadError
22 rescue LoadError
23 # RMagick is not available
23 # RMagick is not available
24 end
24 end
25
25
26 require 'redmine/scm/base'
26 require 'redmine/scm/base'
27 require 'redmine/access_control'
27 require 'redmine/access_control'
28 require 'redmine/access_keys'
28 require 'redmine/access_keys'
29 require 'redmine/activity'
29 require 'redmine/activity'
30 require 'redmine/activity/fetcher'
30 require 'redmine/activity/fetcher'
31 require 'redmine/ciphering'
31 require 'redmine/ciphering'
32 require 'redmine/codeset_util'
32 require 'redmine/codeset_util'
33 require 'redmine/custom_field_format'
33 require 'redmine/custom_field_format'
34 require 'redmine/i18n'
34 require 'redmine/i18n'
35 require 'redmine/menu_manager'
35 require 'redmine/menu_manager'
36 require 'redmine/notifiable'
36 require 'redmine/notifiable'
37 require 'redmine/platform'
37 require 'redmine/platform'
38 require 'redmine/mime_type'
38 require 'redmine/mime_type'
39 require 'redmine/notifiable'
39 require 'redmine/notifiable'
40 require 'redmine/search'
40 require 'redmine/search'
41 require 'redmine/syntax_highlighting'
41 require 'redmine/syntax_highlighting'
42 require 'redmine/thumbnail'
42 require 'redmine/thumbnail'
43 require 'redmine/unified_diff'
43 require 'redmine/unified_diff'
44 require 'redmine/utils'
44 require 'redmine/utils'
45 require 'redmine/version'
45 require 'redmine/version'
46 require 'redmine/wiki_formatting'
46 require 'redmine/wiki_formatting'
47
47
48 require 'redmine/default_data/loader'
48 require 'redmine/default_data/loader'
49 require 'redmine/helpers/calendar'
49 require 'redmine/helpers/calendar'
50 require 'redmine/helpers/diff'
50 require 'redmine/helpers/diff'
51 require 'redmine/helpers/gantt'
51 require 'redmine/helpers/gantt'
52 require 'redmine/helpers/time_report'
52 require 'redmine/helpers/time_report'
53 require 'redmine/views/other_formats_builder'
53 require 'redmine/views/other_formats_builder'
54 require 'redmine/views/labelled_form_builder'
54 require 'redmine/views/labelled_form_builder'
55 require 'redmine/views/builders'
55 require 'redmine/views/builders'
56
56
57 require 'redmine/themes'
57 require 'redmine/themes'
58 require 'redmine/hook'
58 require 'redmine/hook'
59 require 'redmine/plugin'
59 require 'redmine/plugin'
60
60
61 if RUBY_VERSION < '1.9'
61 if RUBY_VERSION < '1.9'
62 require 'fastercsv'
62 require 'fastercsv'
63 else
63 else
64 require 'csv'
64 require 'csv'
65 FCSV = CSV
65 FCSV = CSV
66 end
66 end
67
67
68 Redmine::Scm::Base.add "Subversion"
68 Redmine::Scm::Base.add "Subversion"
69 Redmine::Scm::Base.add "Darcs"
69 Redmine::Scm::Base.add "Darcs"
70 Redmine::Scm::Base.add "Mercurial"
70 Redmine::Scm::Base.add "Mercurial"
71 Redmine::Scm::Base.add "Cvs"
71 Redmine::Scm::Base.add "Cvs"
72 Redmine::Scm::Base.add "Bazaar"
72 Redmine::Scm::Base.add "Bazaar"
73 Redmine::Scm::Base.add "Git"
73 Redmine::Scm::Base.add "Git"
74 Redmine::Scm::Base.add "Filesystem"
74 Redmine::Scm::Base.add "Filesystem"
75
75
76 Redmine::CustomFieldFormat.map do |fields|
76 Redmine::CustomFieldFormat.map do |fields|
77 fields.register 'string'
77 fields.register 'string'
78 fields.register 'text'
78 fields.register 'text'
79 fields.register 'int', :label => :label_integer
79 fields.register 'int', :label => :label_integer
80 fields.register 'float'
80 fields.register 'float'
81 fields.register 'list'
81 fields.register 'list'
82 fields.register 'date'
82 fields.register 'date'
83 fields.register 'bool', :label => :label_boolean
83 fields.register 'bool', :label => :label_boolean
84 fields.register 'user', :only => %w(Issue TimeEntry Version Project), :edit_as => 'list'
84 fields.register 'user', :only => %w(Issue TimeEntry Version Project), :edit_as => 'list'
85 fields.register 'version', :only => %w(Issue TimeEntry Version Project), :edit_as => 'list'
85 fields.register 'version', :only => %w(Issue TimeEntry Version Project), :edit_as => 'list'
86 end
86 end
87
87
88 # Permissions
88 # Permissions
89 Redmine::AccessControl.map do |map|
89 Redmine::AccessControl.map do |map|
90 map.permission :view_project, {:projects => [:show], :activities => [:index]}, :public => true, :read => true
90 map.permission :view_project, {:projects => [:show], :activities => [:index]}, :public => true, :read => true
91 map.permission :search_project, {:search => :index}, :public => true, :read => true
91 map.permission :search_project, {:search => :index}, :public => true, :read => true
92 map.permission :add_project, {:projects => [:new, :create]}, :require => :loggedin
92 map.permission :add_project, {:projects => [:new, :create]}, :require => :loggedin
93 map.permission :edit_project, {:projects => [:settings, :edit, :update]}, :require => :member
93 map.permission :edit_project, {:projects => [:settings, :edit, :update]}, :require => :member
94 map.permission :close_project, {:projects => [:close, :reopen]}, :require => :member, :read => true
94 map.permission :close_project, {:projects => [:close, :reopen]}, :require => :member, :read => true
95 map.permission :select_project_modules, {:projects => :modules}, :require => :member
95 map.permission :select_project_modules, {:projects => :modules}, :require => :member
96 map.permission :manage_members, {:projects => :settings, :members => [:index, :show, :create, :update, :destroy, :autocomplete]}, :require => :member
96 map.permission :manage_members, {:projects => :settings, :members => [:index, :show, :create, :update, :destroy, :autocomplete]}, :require => :member
97 map.permission :manage_versions, {:projects => :settings, :versions => [:new, :create, :edit, :update, :close_completed, :destroy]}, :require => :member
97 map.permission :manage_versions, {:projects => :settings, :versions => [:new, :create, :edit, :update, :close_completed, :destroy]}, :require => :member
98 map.permission :add_subprojects, {:projects => [:new, :create]}, :require => :member
98 map.permission :add_subprojects, {:projects => [:new, :create]}, :require => :member
99
99
100 map.project_module :issue_tracking do |map|
100 map.project_module :issue_tracking do |map|
101 # Issue categories
101 # Issue categories
102 map.permission :manage_categories, {:projects => :settings, :issue_categories => [:index, :show, :new, :create, :edit, :update, :destroy]}, :require => :member
102 map.permission :manage_categories, {:projects => :settings, :issue_categories => [:index, :show, :new, :create, :edit, :update, :destroy]}, :require => :member
103 # Issues
103 # Issues
104 map.permission :view_issues, {:issues => [:index, :show],
104 map.permission :view_issues, {:issues => [:index, :show],
105 :auto_complete => [:issues],
105 :auto_complete => [:issues],
106 :context_menus => [:issues],
106 :context_menus => [:issues],
107 :versions => [:index, :show, :status_by],
107 :versions => [:index, :show, :status_by],
108 :journals => [:index, :diff],
108 :journals => [:index, :diff],
109 :queries => :index,
109 :queries => :index,
110 :reports => [:issue_report, :issue_report_details]},
110 :reports => [:issue_report, :issue_report_details]},
111 :read => true
111 :read => true
112 map.permission :add_issues, {:issues => [:new, :create, :update_form], :attachments => :upload}
112 map.permission :add_issues, {:issues => [:new, :create, :update_form], :attachments => :upload}
113 map.permission :edit_issues, {:issues => [:edit, :update, :bulk_edit, :bulk_update, :update_form], :journals => [:new], :attachments => :upload}
113 map.permission :edit_issues, {:issues => [:edit, :update, :bulk_edit, :bulk_update, :update_form], :journals => [:new], :attachments => :upload}
114 map.permission :manage_issue_relations, {:issue_relations => [:index, :show, :create, :destroy]}
114 map.permission :manage_issue_relations, {:issue_relations => [:index, :show, :create, :destroy]}
115 map.permission :manage_subtasks, {}
115 map.permission :manage_subtasks, {}
116 map.permission :set_issues_private, {}
116 map.permission :set_issues_private, {}
117 map.permission :set_own_issues_private, {}, :require => :loggedin
117 map.permission :set_own_issues_private, {}, :require => :loggedin
118 map.permission :add_issue_notes, {:issues => [:edit, :update], :journals => [:new], :attachments => :upload}
118 map.permission :add_issue_notes, {:issues => [:edit, :update, :update_form], :journals => [:new], :attachments => :upload}
119 map.permission :edit_issue_notes, {:journals => :edit}, :require => :loggedin
119 map.permission :edit_issue_notes, {:journals => :edit}, :require => :loggedin
120 map.permission :edit_own_issue_notes, {:journals => :edit}, :require => :loggedin
120 map.permission :edit_own_issue_notes, {:journals => :edit}, :require => :loggedin
121 map.permission :view_private_notes, {}, :read => true, :require => :member
121 map.permission :view_private_notes, {}, :read => true, :require => :member
122 map.permission :set_notes_private, {}, :require => :member
122 map.permission :set_notes_private, {}, :require => :member
123 map.permission :move_issues, {:issues => [:bulk_edit, :bulk_update]}, :require => :loggedin
123 map.permission :move_issues, {:issues => [:bulk_edit, :bulk_update]}, :require => :loggedin
124 map.permission :delete_issues, {:issues => :destroy}, :require => :member
124 map.permission :delete_issues, {:issues => :destroy}, :require => :member
125 # Queries
125 # Queries
126 map.permission :manage_public_queries, {:queries => [:new, :create, :edit, :update, :destroy]}, :require => :member
126 map.permission :manage_public_queries, {:queries => [:new, :create, :edit, :update, :destroy]}, :require => :member
127 map.permission :save_queries, {:queries => [:new, :create, :edit, :update, :destroy]}, :require => :loggedin
127 map.permission :save_queries, {:queries => [:new, :create, :edit, :update, :destroy]}, :require => :loggedin
128 # Watchers
128 # Watchers
129 map.permission :view_issue_watchers, {}, :read => true
129 map.permission :view_issue_watchers, {}, :read => true
130 map.permission :add_issue_watchers, {:watchers => [:new, :create, :append, :autocomplete_for_user]}
130 map.permission :add_issue_watchers, {:watchers => [:new, :create, :append, :autocomplete_for_user]}
131 map.permission :delete_issue_watchers, {:watchers => :destroy}
131 map.permission :delete_issue_watchers, {:watchers => :destroy}
132 end
132 end
133
133
134 map.project_module :time_tracking do |map|
134 map.project_module :time_tracking do |map|
135 map.permission :log_time, {:timelog => [:new, :create]}, :require => :loggedin
135 map.permission :log_time, {:timelog => [:new, :create]}, :require => :loggedin
136 map.permission :view_time_entries, {:timelog => [:index, :report, :show]}, :read => true
136 map.permission :view_time_entries, {:timelog => [:index, :report, :show]}, :read => true
137 map.permission :edit_time_entries, {:timelog => [:edit, :update, :destroy, :bulk_edit, :bulk_update]}, :require => :member
137 map.permission :edit_time_entries, {:timelog => [:edit, :update, :destroy, :bulk_edit, :bulk_update]}, :require => :member
138 map.permission :edit_own_time_entries, {:timelog => [:edit, :update, :destroy,:bulk_edit, :bulk_update]}, :require => :loggedin
138 map.permission :edit_own_time_entries, {:timelog => [:edit, :update, :destroy,:bulk_edit, :bulk_update]}, :require => :loggedin
139 map.permission :manage_project_activities, {:project_enumerations => [:update, :destroy]}, :require => :member
139 map.permission :manage_project_activities, {:project_enumerations => [:update, :destroy]}, :require => :member
140 end
140 end
141
141
142 map.project_module :news do |map|
142 map.project_module :news do |map|
143 map.permission :manage_news, {:news => [:new, :create, :edit, :update, :destroy], :comments => [:destroy]}, :require => :member
143 map.permission :manage_news, {:news => [:new, :create, :edit, :update, :destroy], :comments => [:destroy]}, :require => :member
144 map.permission :view_news, {:news => [:index, :show]}, :public => true, :read => true
144 map.permission :view_news, {:news => [:index, :show]}, :public => true, :read => true
145 map.permission :comment_news, {:comments => :create}
145 map.permission :comment_news, {:comments => :create}
146 end
146 end
147
147
148 map.project_module :documents do |map|
148 map.project_module :documents do |map|
149 map.permission :add_documents, {:documents => [:new, :create, :add_attachment]}, :require => :loggedin
149 map.permission :add_documents, {:documents => [:new, :create, :add_attachment]}, :require => :loggedin
150 map.permission :edit_documents, {:documents => [:edit, :update, :add_attachment]}, :require => :loggedin
150 map.permission :edit_documents, {:documents => [:edit, :update, :add_attachment]}, :require => :loggedin
151 map.permission :delete_documents, {:documents => [:destroy]}, :require => :loggedin
151 map.permission :delete_documents, {:documents => [:destroy]}, :require => :loggedin
152 map.permission :view_documents, {:documents => [:index, :show, :download]}, :read => true
152 map.permission :view_documents, {:documents => [:index, :show, :download]}, :read => true
153 end
153 end
154
154
155 map.project_module :files do |map|
155 map.project_module :files do |map|
156 map.permission :manage_files, {:files => [:new, :create]}, :require => :loggedin
156 map.permission :manage_files, {:files => [:new, :create]}, :require => :loggedin
157 map.permission :view_files, {:files => :index, :versions => :download}, :read => true
157 map.permission :view_files, {:files => :index, :versions => :download}, :read => true
158 end
158 end
159
159
160 map.project_module :wiki do |map|
160 map.project_module :wiki do |map|
161 map.permission :manage_wiki, {:wikis => [:edit, :destroy]}, :require => :member
161 map.permission :manage_wiki, {:wikis => [:edit, :destroy]}, :require => :member
162 map.permission :rename_wiki_pages, {:wiki => :rename}, :require => :member
162 map.permission :rename_wiki_pages, {:wiki => :rename}, :require => :member
163 map.permission :delete_wiki_pages, {:wiki => [:destroy, :destroy_version]}, :require => :member
163 map.permission :delete_wiki_pages, {:wiki => [:destroy, :destroy_version]}, :require => :member
164 map.permission :view_wiki_pages, {:wiki => [:index, :show, :special, :date_index]}, :read => true
164 map.permission :view_wiki_pages, {:wiki => [:index, :show, :special, :date_index]}, :read => true
165 map.permission :export_wiki_pages, {:wiki => [:export]}, :read => true
165 map.permission :export_wiki_pages, {:wiki => [:export]}, :read => true
166 map.permission :view_wiki_edits, {:wiki => [:history, :diff, :annotate]}, :read => true
166 map.permission :view_wiki_edits, {:wiki => [:history, :diff, :annotate]}, :read => true
167 map.permission :edit_wiki_pages, :wiki => [:edit, :update, :preview, :add_attachment]
167 map.permission :edit_wiki_pages, :wiki => [:edit, :update, :preview, :add_attachment]
168 map.permission :delete_wiki_pages_attachments, {}
168 map.permission :delete_wiki_pages_attachments, {}
169 map.permission :protect_wiki_pages, {:wiki => :protect}, :require => :member
169 map.permission :protect_wiki_pages, {:wiki => :protect}, :require => :member
170 end
170 end
171
171
172 map.project_module :repository do |map|
172 map.project_module :repository do |map|
173 map.permission :manage_repository, {:repositories => [:new, :create, :edit, :update, :committers, :destroy]}, :require => :member
173 map.permission :manage_repository, {:repositories => [:new, :create, :edit, :update, :committers, :destroy]}, :require => :member
174 map.permission :browse_repository, {:repositories => [:show, :browse, :entry, :raw, :annotate, :changes, :diff, :stats, :graph]}, :read => true
174 map.permission :browse_repository, {:repositories => [:show, :browse, :entry, :raw, :annotate, :changes, :diff, :stats, :graph]}, :read => true
175 map.permission :view_changesets, {:repositories => [:show, :revisions, :revision]}, :read => true
175 map.permission :view_changesets, {:repositories => [:show, :revisions, :revision]}, :read => true
176 map.permission :commit_access, {}
176 map.permission :commit_access, {}
177 map.permission :manage_related_issues, {:repositories => [:add_related_issue, :remove_related_issue]}
177 map.permission :manage_related_issues, {:repositories => [:add_related_issue, :remove_related_issue]}
178 end
178 end
179
179
180 map.project_module :boards do |map|
180 map.project_module :boards do |map|
181 map.permission :manage_boards, {:boards => [:new, :create, :edit, :update, :destroy]}, :require => :member
181 map.permission :manage_boards, {:boards => [:new, :create, :edit, :update, :destroy]}, :require => :member
182 map.permission :view_messages, {:boards => [:index, :show], :messages => [:show]}, :public => true, :read => true
182 map.permission :view_messages, {:boards => [:index, :show], :messages => [:show]}, :public => true, :read => true
183 map.permission :add_messages, {:messages => [:new, :reply, :quote]}
183 map.permission :add_messages, {:messages => [:new, :reply, :quote]}
184 map.permission :edit_messages, {:messages => :edit}, :require => :member
184 map.permission :edit_messages, {:messages => :edit}, :require => :member
185 map.permission :edit_own_messages, {:messages => :edit}, :require => :loggedin
185 map.permission :edit_own_messages, {:messages => :edit}, :require => :loggedin
186 map.permission :delete_messages, {:messages => :destroy}, :require => :member
186 map.permission :delete_messages, {:messages => :destroy}, :require => :member
187 map.permission :delete_own_messages, {:messages => :destroy}, :require => :loggedin
187 map.permission :delete_own_messages, {:messages => :destroy}, :require => :loggedin
188 end
188 end
189
189
190 map.project_module :calendar do |map|
190 map.project_module :calendar do |map|
191 map.permission :view_calendar, {:calendars => [:show, :update]}, :read => true
191 map.permission :view_calendar, {:calendars => [:show, :update]}, :read => true
192 end
192 end
193
193
194 map.project_module :gantt do |map|
194 map.project_module :gantt do |map|
195 map.permission :view_gantt, {:gantts => [:show, :update]}, :read => true
195 map.permission :view_gantt, {:gantts => [:show, :update]}, :read => true
196 end
196 end
197 end
197 end
198
198
199 Redmine::MenuManager.map :top_menu do |menu|
199 Redmine::MenuManager.map :top_menu do |menu|
200 menu.push :home, :home_path
200 menu.push :home, :home_path
201 menu.push :my_page, { :controller => 'my', :action => 'page' }, :if => Proc.new { User.current.logged? }
201 menu.push :my_page, { :controller => 'my', :action => 'page' }, :if => Proc.new { User.current.logged? }
202 menu.push :projects, { :controller => 'projects', :action => 'index' }, :caption => :label_project_plural
202 menu.push :projects, { :controller => 'projects', :action => 'index' }, :caption => :label_project_plural
203 menu.push :administration, { :controller => 'admin', :action => 'index' }, :if => Proc.new { User.current.admin? }, :last => true
203 menu.push :administration, { :controller => 'admin', :action => 'index' }, :if => Proc.new { User.current.admin? }, :last => true
204 menu.push :help, Redmine::Info.help_url, :last => true
204 menu.push :help, Redmine::Info.help_url, :last => true
205 end
205 end
206
206
207 Redmine::MenuManager.map :account_menu do |menu|
207 Redmine::MenuManager.map :account_menu do |menu|
208 menu.push :login, :signin_path, :if => Proc.new { !User.current.logged? }
208 menu.push :login, :signin_path, :if => Proc.new { !User.current.logged? }
209 menu.push :register, :register_path, :if => Proc.new { !User.current.logged? && Setting.self_registration? }
209 menu.push :register, :register_path, :if => Proc.new { !User.current.logged? && Setting.self_registration? }
210 menu.push :my_account, { :controller => 'my', :action => 'account' }, :if => Proc.new { User.current.logged? }
210 menu.push :my_account, { :controller => 'my', :action => 'account' }, :if => Proc.new { User.current.logged? }
211 menu.push :logout, :signout_path, :html => {:method => 'post'}, :if => Proc.new { User.current.logged? }
211 menu.push :logout, :signout_path, :html => {:method => 'post'}, :if => Proc.new { User.current.logged? }
212 end
212 end
213
213
214 Redmine::MenuManager.map :application_menu do |menu|
214 Redmine::MenuManager.map :application_menu do |menu|
215 # Empty
215 # Empty
216 end
216 end
217
217
218 Redmine::MenuManager.map :admin_menu do |menu|
218 Redmine::MenuManager.map :admin_menu do |menu|
219 menu.push :projects, {:controller => 'admin', :action => 'projects'}, :caption => :label_project_plural
219 menu.push :projects, {:controller => 'admin', :action => 'projects'}, :caption => :label_project_plural
220 menu.push :users, {:controller => 'users'}, :caption => :label_user_plural
220 menu.push :users, {:controller => 'users'}, :caption => :label_user_plural
221 menu.push :groups, {:controller => 'groups'}, :caption => :label_group_plural
221 menu.push :groups, {:controller => 'groups'}, :caption => :label_group_plural
222 menu.push :roles, {:controller => 'roles'}, :caption => :label_role_and_permissions
222 menu.push :roles, {:controller => 'roles'}, :caption => :label_role_and_permissions
223 menu.push :trackers, {:controller => 'trackers'}, :caption => :label_tracker_plural
223 menu.push :trackers, {:controller => 'trackers'}, :caption => :label_tracker_plural
224 menu.push :issue_statuses, {:controller => 'issue_statuses'}, :caption => :label_issue_status_plural,
224 menu.push :issue_statuses, {:controller => 'issue_statuses'}, :caption => :label_issue_status_plural,
225 :html => {:class => 'issue_statuses'}
225 :html => {:class => 'issue_statuses'}
226 menu.push :workflows, {:controller => 'workflows', :action => 'edit'}, :caption => :label_workflow
226 menu.push :workflows, {:controller => 'workflows', :action => 'edit'}, :caption => :label_workflow
227 menu.push :custom_fields, {:controller => 'custom_fields'}, :caption => :label_custom_field_plural,
227 menu.push :custom_fields, {:controller => 'custom_fields'}, :caption => :label_custom_field_plural,
228 :html => {:class => 'custom_fields'}
228 :html => {:class => 'custom_fields'}
229 menu.push :enumerations, {:controller => 'enumerations'}
229 menu.push :enumerations, {:controller => 'enumerations'}
230 menu.push :settings, {:controller => 'settings'}
230 menu.push :settings, {:controller => 'settings'}
231 menu.push :ldap_authentication, {:controller => 'auth_sources', :action => 'index'},
231 menu.push :ldap_authentication, {:controller => 'auth_sources', :action => 'index'},
232 :html => {:class => 'server_authentication'}
232 :html => {:class => 'server_authentication'}
233 menu.push :plugins, {:controller => 'admin', :action => 'plugins'}, :last => true
233 menu.push :plugins, {:controller => 'admin', :action => 'plugins'}, :last => true
234 menu.push :info, {:controller => 'admin', :action => 'info'}, :caption => :label_information_plural, :last => true
234 menu.push :info, {:controller => 'admin', :action => 'info'}, :caption => :label_information_plural, :last => true
235 end
235 end
236
236
237 Redmine::MenuManager.map :project_menu do |menu|
237 Redmine::MenuManager.map :project_menu do |menu|
238 menu.push :overview, { :controller => 'projects', :action => 'show' }
238 menu.push :overview, { :controller => 'projects', :action => 'show' }
239 menu.push :activity, { :controller => 'activities', :action => 'index' }
239 menu.push :activity, { :controller => 'activities', :action => 'index' }
240 menu.push :roadmap, { :controller => 'versions', :action => 'index' }, :param => :project_id,
240 menu.push :roadmap, { :controller => 'versions', :action => 'index' }, :param => :project_id,
241 :if => Proc.new { |p| p.shared_versions.any? }
241 :if => Proc.new { |p| p.shared_versions.any? }
242 menu.push :issues, { :controller => 'issues', :action => 'index' }, :param => :project_id, :caption => :label_issue_plural
242 menu.push :issues, { :controller => 'issues', :action => 'index' }, :param => :project_id, :caption => :label_issue_plural
243 menu.push :new_issue, { :controller => 'issues', :action => 'new', :copy_from => nil }, :param => :project_id, :caption => :label_issue_new,
243 menu.push :new_issue, { :controller => 'issues', :action => 'new', :copy_from => nil }, :param => :project_id, :caption => :label_issue_new,
244 :html => { :accesskey => Redmine::AccessKeys.key_for(:new_issue) }
244 :html => { :accesskey => Redmine::AccessKeys.key_for(:new_issue) }
245 menu.push :gantt, { :controller => 'gantts', :action => 'show' }, :param => :project_id, :caption => :label_gantt
245 menu.push :gantt, { :controller => 'gantts', :action => 'show' }, :param => :project_id, :caption => :label_gantt
246 menu.push :calendar, { :controller => 'calendars', :action => 'show' }, :param => :project_id, :caption => :label_calendar
246 menu.push :calendar, { :controller => 'calendars', :action => 'show' }, :param => :project_id, :caption => :label_calendar
247 menu.push :news, { :controller => 'news', :action => 'index' }, :param => :project_id, :caption => :label_news_plural
247 menu.push :news, { :controller => 'news', :action => 'index' }, :param => :project_id, :caption => :label_news_plural
248 menu.push :documents, { :controller => 'documents', :action => 'index' }, :param => :project_id, :caption => :label_document_plural
248 menu.push :documents, { :controller => 'documents', :action => 'index' }, :param => :project_id, :caption => :label_document_plural
249 menu.push :wiki, { :controller => 'wiki', :action => 'show', :id => nil }, :param => :project_id,
249 menu.push :wiki, { :controller => 'wiki', :action => 'show', :id => nil }, :param => :project_id,
250 :if => Proc.new { |p| p.wiki && !p.wiki.new_record? }
250 :if => Proc.new { |p| p.wiki && !p.wiki.new_record? }
251 menu.push :boards, { :controller => 'boards', :action => 'index', :id => nil }, :param => :project_id,
251 menu.push :boards, { :controller => 'boards', :action => 'index', :id => nil }, :param => :project_id,
252 :if => Proc.new { |p| p.boards.any? }, :caption => :label_board_plural
252 :if => Proc.new { |p| p.boards.any? }, :caption => :label_board_plural
253 menu.push :files, { :controller => 'files', :action => 'index' }, :caption => :label_file_plural, :param => :project_id
253 menu.push :files, { :controller => 'files', :action => 'index' }, :caption => :label_file_plural, :param => :project_id
254 menu.push :repository, { :controller => 'repositories', :action => 'show', :repository_id => nil, :path => nil, :rev => nil },
254 menu.push :repository, { :controller => 'repositories', :action => 'show', :repository_id => nil, :path => nil, :rev => nil },
255 :if => Proc.new { |p| p.repository && !p.repository.new_record? }
255 :if => Proc.new { |p| p.repository && !p.repository.new_record? }
256 menu.push :settings, { :controller => 'projects', :action => 'settings' }, :last => true
256 menu.push :settings, { :controller => 'projects', :action => 'settings' }, :last => true
257 end
257 end
258
258
259 Redmine::Activity.map do |activity|
259 Redmine::Activity.map do |activity|
260 activity.register :issues, :class_name => %w(Issue Journal)
260 activity.register :issues, :class_name => %w(Issue Journal)
261 activity.register :changesets
261 activity.register :changesets
262 activity.register :news
262 activity.register :news
263 activity.register :documents, :class_name => %w(Document Attachment)
263 activity.register :documents, :class_name => %w(Document Attachment)
264 activity.register :files, :class_name => 'Attachment'
264 activity.register :files, :class_name => 'Attachment'
265 activity.register :wiki_edits, :class_name => 'WikiContent::Version', :default => false
265 activity.register :wiki_edits, :class_name => 'WikiContent::Version', :default => false
266 activity.register :messages, :default => false
266 activity.register :messages, :default => false
267 activity.register :time_entries, :default => false
267 activity.register :time_entries, :default => false
268 end
268 end
269
269
270 Redmine::Search.map do |search|
270 Redmine::Search.map do |search|
271 search.register :issues
271 search.register :issues
272 search.register :news
272 search.register :news
273 search.register :documents
273 search.register :documents
274 search.register :changesets
274 search.register :changesets
275 search.register :wiki_pages
275 search.register :wiki_pages
276 search.register :messages
276 search.register :messages
277 search.register :projects
277 search.register :projects
278 end
278 end
279
279
280 Redmine::WikiFormatting.map do |format|
280 Redmine::WikiFormatting.map do |format|
281 format.register :textile, Redmine::WikiFormatting::Textile::Formatter, Redmine::WikiFormatting::Textile::Helper
281 format.register :textile, Redmine::WikiFormatting::Textile::Formatter, Redmine::WikiFormatting::Textile::Helper
282 end
282 end
283
283
284 ActionView::Template.register_template_handler :rsb, Redmine::Views::ApiTemplateHandler
284 ActionView::Template.register_template_handler :rsb, Redmine::Views::ApiTemplateHandler
@@ -1,166 +1,169
1 ---
1 ---
2 users_004:
2 users_004:
3 created_on: 2006-07-19 19:34:07 +02:00
3 created_on: 2006-07-19 19:34:07 +02:00
4 status: 1
4 status: 1
5 last_login_on:
5 last_login_on:
6 language: en
6 language: en
7 # password = foo
7 # password = foo
8 salt: 3126f764c3c5ac61cbfc103f25f934cf
8 salt: 3126f764c3c5ac61cbfc103f25f934cf
9 hashed_password: 9e4dd7eeb172c12a0691a6d9d3a269f7e9fe671b
9 hashed_password: 9e4dd7eeb172c12a0691a6d9d3a269f7e9fe671b
10 updated_on: 2006-07-19 19:34:07 +02:00
10 updated_on: 2006-07-19 19:34:07 +02:00
11 admin: false
11 admin: false
12 mail: rhill@somenet.foo
12 mail: rhill@somenet.foo
13 lastname: Hill
13 lastname: Hill
14 firstname: Robert
14 firstname: Robert
15 id: 4
15 id: 4
16 auth_source_id:
16 auth_source_id:
17 mail_notification: all
17 mail_notification: all
18 login: rhill
18 login: rhill
19 type: User
19 type: User
20 users_001:
20 users_001:
21 created_on: 2006-07-19 19:12:21 +02:00
21 created_on: 2006-07-19 19:12:21 +02:00
22 status: 1
22 status: 1
23 last_login_on: 2006-07-19 22:57:52 +02:00
23 last_login_on: 2006-07-19 22:57:52 +02:00
24 language: en
24 language: en
25 # password = admin
25 # password = admin
26 salt: 82090c953c4a0000a7db253b0691a6b4
26 salt: 82090c953c4a0000a7db253b0691a6b4
27 hashed_password: b5b6ff9543bf1387374cdfa27a54c96d236a7150
27 hashed_password: b5b6ff9543bf1387374cdfa27a54c96d236a7150
28 updated_on: 2006-07-19 22:57:52 +02:00
28 updated_on: 2006-07-19 22:57:52 +02:00
29 admin: true
29 admin: true
30 mail: admin@somenet.foo
30 mail: admin@somenet.foo
31 lastname: Admin
31 lastname: Admin
32 firstname: Redmine
32 firstname: Redmine
33 id: 1
33 id: 1
34 auth_source_id:
34 auth_source_id:
35 mail_notification: all
35 mail_notification: all
36 login: admin
36 login: admin
37 type: User
37 type: User
38 users_002:
38 users_002:
39 created_on: 2006-07-19 19:32:09 +02:00
39 created_on: 2006-07-19 19:32:09 +02:00
40 status: 1
40 status: 1
41 last_login_on: 2006-07-19 22:42:15 +02:00
41 last_login_on: 2006-07-19 22:42:15 +02:00
42 language: en
42 language: en
43 # password = jsmith
43 # password = jsmith
44 salt: 67eb4732624d5a7753dcea7ce0bb7d7d
44 salt: 67eb4732624d5a7753dcea7ce0bb7d7d
45 hashed_password: bfbe06043353a677d0215b26a5800d128d5413bc
45 hashed_password: bfbe06043353a677d0215b26a5800d128d5413bc
46 updated_on: 2006-07-19 22:42:15 +02:00
46 updated_on: 2006-07-19 22:42:15 +02:00
47 admin: false
47 admin: false
48 mail: jsmith@somenet.foo
48 mail: jsmith@somenet.foo
49 lastname: Smith
49 lastname: Smith
50 firstname: John
50 firstname: John
51 id: 2
51 id: 2
52 auth_source_id:
52 auth_source_id:
53 mail_notification: all
53 mail_notification: all
54 login: jsmith
54 login: jsmith
55 type: User
55 type: User
56 users_003:
56 users_003:
57 created_on: 2006-07-19 19:33:19 +02:00
57 created_on: 2006-07-19 19:33:19 +02:00
58 status: 1
58 status: 1
59 last_login_on:
59 last_login_on:
60 language: en
60 language: en
61 # password = foo
61 # password = foo
62 salt: 7599f9963ec07b5a3b55b354407120c0
62 salt: 7599f9963ec07b5a3b55b354407120c0
63 hashed_password: 8f659c8d7c072f189374edacfa90d6abbc26d8ed
63 hashed_password: 8f659c8d7c072f189374edacfa90d6abbc26d8ed
64 updated_on: 2006-07-19 19:33:19 +02:00
64 updated_on: 2006-07-19 19:33:19 +02:00
65 admin: false
65 admin: false
66 mail: dlopper@somenet.foo
66 mail: dlopper@somenet.foo
67 lastname: Lopper
67 lastname: Lopper
68 firstname: Dave
68 firstname: Dave
69 id: 3
69 id: 3
70 auth_source_id:
70 auth_source_id:
71 mail_notification: all
71 mail_notification: all
72 login: dlopper
72 login: dlopper
73 type: User
73 type: User
74 users_005:
74 users_005:
75 id: 5
75 id: 5
76 created_on: 2006-07-19 19:33:19 +02:00
76 created_on: 2006-07-19 19:33:19 +02:00
77 # Locked
77 # Locked
78 status: 3
78 status: 3
79 last_login_on:
79 last_login_on:
80 language: en
80 language: en
81 hashed_password: 1
81 hashed_password: 1
82 updated_on: 2006-07-19 19:33:19 +02:00
82 updated_on: 2006-07-19 19:33:19 +02:00
83 admin: false
83 admin: false
84 mail: dlopper2@somenet.foo
84 mail: dlopper2@somenet.foo
85 lastname: Lopper2
85 lastname: Lopper2
86 firstname: Dave2
86 firstname: Dave2
87 auth_source_id:
87 auth_source_id:
88 mail_notification: all
88 mail_notification: all
89 login: dlopper2
89 login: dlopper2
90 type: User
90 type: User
91 users_006:
91 users_006:
92 id: 6
92 id: 6
93 created_on: 2006-07-19 19:33:19 +02:00
93 created_on: 2006-07-19 19:33:19 +02:00
94 status: 0
94 status: 0
95 last_login_on:
95 last_login_on:
96 language: ''
96 language: ''
97 hashed_password: 1
97 hashed_password: 1
98 updated_on: 2006-07-19 19:33:19 +02:00
98 updated_on: 2006-07-19 19:33:19 +02:00
99 admin: false
99 admin: false
100 mail: ''
100 mail: ''
101 lastname: Anonymous
101 lastname: Anonymous
102 firstname: ''
102 firstname: ''
103 auth_source_id:
103 auth_source_id:
104 mail_notification: only_my_events
104 mail_notification: only_my_events
105 login: ''
105 login: ''
106 type: AnonymousUser
106 type: AnonymousUser
107 users_007:
107 users_007:
108 # A user who does not belong to any project
108 id: 7
109 id: 7
109 created_on: 2006-07-19 19:33:19 +02:00
110 created_on: 2006-07-19 19:33:19 +02:00
110 status: 1
111 status: 1
111 last_login_on:
112 last_login_on:
112 language: ''
113 language: 'en'
113 hashed_password: 1
114 # password = foo
115 salt: 7599f9963ec07b5a3b55b354407120c0
116 hashed_password: 8f659c8d7c072f189374edacfa90d6abbc26d8ed
114 updated_on: 2006-07-19 19:33:19 +02:00
117 updated_on: 2006-07-19 19:33:19 +02:00
115 admin: false
118 admin: false
116 mail: someone@foo.bar
119 mail: someone@foo.bar
117 lastname: One
120 lastname: One
118 firstname: Some
121 firstname: Some
119 auth_source_id:
122 auth_source_id:
120 mail_notification: only_my_events
123 mail_notification: only_my_events
121 login: someone
124 login: someone
122 type: User
125 type: User
123 users_008:
126 users_008:
124 id: 8
127 id: 8
125 created_on: 2006-07-19 19:33:19 +02:00
128 created_on: 2006-07-19 19:33:19 +02:00
126 status: 1
129 status: 1
127 last_login_on:
130 last_login_on:
128 language: 'it'
131 language: 'it'
129 # password = foo
132 # password = foo
130 salt: 7599f9963ec07b5a3b55b354407120c0
133 salt: 7599f9963ec07b5a3b55b354407120c0
131 hashed_password: 8f659c8d7c072f189374edacfa90d6abbc26d8ed
134 hashed_password: 8f659c8d7c072f189374edacfa90d6abbc26d8ed
132 updated_on: 2006-07-19 19:33:19 +02:00
135 updated_on: 2006-07-19 19:33:19 +02:00
133 admin: false
136 admin: false
134 mail: miscuser8@foo.bar
137 mail: miscuser8@foo.bar
135 lastname: Misc
138 lastname: Misc
136 firstname: User
139 firstname: User
137 auth_source_id:
140 auth_source_id:
138 mail_notification: only_my_events
141 mail_notification: only_my_events
139 login: miscuser8
142 login: miscuser8
140 type: User
143 type: User
141 users_009:
144 users_009:
142 id: 9
145 id: 9
143 created_on: 2006-07-19 19:33:19 +02:00
146 created_on: 2006-07-19 19:33:19 +02:00
144 status: 1
147 status: 1
145 last_login_on:
148 last_login_on:
146 language: 'it'
149 language: 'it'
147 hashed_password: 1
150 hashed_password: 1
148 updated_on: 2006-07-19 19:33:19 +02:00
151 updated_on: 2006-07-19 19:33:19 +02:00
149 admin: false
152 admin: false
150 mail: miscuser9@foo.bar
153 mail: miscuser9@foo.bar
151 lastname: Misc
154 lastname: Misc
152 firstname: User
155 firstname: User
153 auth_source_id:
156 auth_source_id:
154 mail_notification: only_my_events
157 mail_notification: only_my_events
155 login: miscuser9
158 login: miscuser9
156 type: User
159 type: User
157 groups_010:
160 groups_010:
158 id: 10
161 id: 10
159 lastname: A Team
162 lastname: A Team
160 type: Group
163 type: Group
161 groups_011:
164 groups_011:
162 id: 11
165 id: 11
163 lastname: B Team
166 lastname: B Team
164 type: Group
167 type: Group
165
168
166
169
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,229 +1,220
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2013 Jean-Philippe Lang
2 # Copyright (C) 2006-2013 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19
19
20 class IssuesTest < ActionController::IntegrationTest
20 class IssuesTest < ActionController::IntegrationTest
21 fixtures :projects,
21 fixtures :projects,
22 :users,
22 :users,
23 :roles,
23 :roles,
24 :members,
24 :members,
25 :member_roles,
25 :member_roles,
26 :trackers,
26 :trackers,
27 :projects_trackers,
27 :projects_trackers,
28 :enabled_modules,
28 :enabled_modules,
29 :issue_statuses,
29 :issue_statuses,
30 :issues,
30 :issues,
31 :enumerations,
31 :enumerations,
32 :custom_fields,
32 :custom_fields,
33 :custom_values,
33 :custom_values,
34 :custom_fields_trackers
34 :custom_fields_trackers
35
35
36 # create an issue
36 # create an issue
37 def test_add_issue
37 def test_add_issue
38 log_user('jsmith', 'jsmith')
38 log_user('jsmith', 'jsmith')
39 get 'projects/1/issues/new', :tracker_id => '1'
39 get 'projects/1/issues/new', :tracker_id => '1'
40 assert_response :success
40 assert_response :success
41 assert_template 'issues/new'
41 assert_template 'issues/new'
42
42
43 post 'projects/1/issues', :tracker_id => "1",
43 post 'projects/1/issues', :tracker_id => "1",
44 :issue => { :start_date => "2006-12-26",
44 :issue => { :start_date => "2006-12-26",
45 :priority_id => "4",
45 :priority_id => "4",
46 :subject => "new test issue",
46 :subject => "new test issue",
47 :category_id => "",
47 :category_id => "",
48 :description => "new issue",
48 :description => "new issue",
49 :done_ratio => "0",
49 :done_ratio => "0",
50 :due_date => "",
50 :due_date => "",
51 :assigned_to_id => "" },
51 :assigned_to_id => "" },
52 :custom_fields => {'2' => 'Value for field 2'}
52 :custom_fields => {'2' => 'Value for field 2'}
53 # find created issue
53 # find created issue
54 issue = Issue.find_by_subject("new test issue")
54 issue = Issue.find_by_subject("new test issue")
55 assert_kind_of Issue, issue
55 assert_kind_of Issue, issue
56
56
57 # check redirection
57 # check redirection
58 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
58 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
59 follow_redirect!
59 follow_redirect!
60 assert_equal issue, assigns(:issue)
60 assert_equal issue, assigns(:issue)
61
61
62 # check issue attributes
62 # check issue attributes
63 assert_equal 'jsmith', issue.author.login
63 assert_equal 'jsmith', issue.author.login
64 assert_equal 1, issue.project.id
64 assert_equal 1, issue.project.id
65 assert_equal 1, issue.status.id
65 assert_equal 1, issue.status.id
66 end
66 end
67
67
68 def test_update_issue_form
69 log_user('jsmith', 'jsmith')
70 post 'projects/ecookbook/issues/new', :issue => { :tracker_id => "2"}
71 assert_response :success
72 assert_tag 'select',
73 :attributes => {:name => 'issue[tracker_id]'},
74 :child => {:tag => 'option', :attributes => {:value => '2', :selected => 'selected'}}
75 end
76
77 # add then remove 2 attachments to an issue
68 # add then remove 2 attachments to an issue
78 def test_issue_attachments
69 def test_issue_attachments
79 log_user('jsmith', 'jsmith')
70 log_user('jsmith', 'jsmith')
80 set_tmp_attachments_directory
71 set_tmp_attachments_directory
81
72
82 put 'issues/1',
73 put 'issues/1',
83 :notes => 'Some notes',
74 :notes => 'Some notes',
84 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'This is an attachment'}}
75 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'This is an attachment'}}
85 assert_redirected_to "/issues/1"
76 assert_redirected_to "/issues/1"
86
77
87 # make sure attachment was saved
78 # make sure attachment was saved
88 attachment = Issue.find(1).attachments.find_by_filename("testfile.txt")
79 attachment = Issue.find(1).attachments.find_by_filename("testfile.txt")
89 assert_kind_of Attachment, attachment
80 assert_kind_of Attachment, attachment
90 assert_equal Issue.find(1), attachment.container
81 assert_equal Issue.find(1), attachment.container
91 assert_equal 'This is an attachment', attachment.description
82 assert_equal 'This is an attachment', attachment.description
92 # verify the size of the attachment stored in db
83 # verify the size of the attachment stored in db
93 #assert_equal file_data_1.length, attachment.filesize
84 #assert_equal file_data_1.length, attachment.filesize
94 # verify that the attachment was written to disk
85 # verify that the attachment was written to disk
95 assert File.exist?(attachment.diskfile)
86 assert File.exist?(attachment.diskfile)
96
87
97 # remove the attachments
88 # remove the attachments
98 Issue.find(1).attachments.each(&:destroy)
89 Issue.find(1).attachments.each(&:destroy)
99 assert_equal 0, Issue.find(1).attachments.length
90 assert_equal 0, Issue.find(1).attachments.length
100 end
91 end
101
92
102 def test_other_formats_links_on_index
93 def test_other_formats_links_on_index
103 get '/projects/ecookbook/issues'
94 get '/projects/ecookbook/issues'
104
95
105 %w(Atom PDF CSV).each do |format|
96 %w(Atom PDF CSV).each do |format|
106 assert_tag :a, :content => format,
97 assert_tag :a, :content => format,
107 :attributes => { :href => "/projects/ecookbook/issues.#{format.downcase}",
98 :attributes => { :href => "/projects/ecookbook/issues.#{format.downcase}",
108 :rel => 'nofollow' }
99 :rel => 'nofollow' }
109 end
100 end
110 end
101 end
111
102
112 def test_other_formats_links_on_index_without_project_id_in_url
103 def test_other_formats_links_on_index_without_project_id_in_url
113 get '/issues', :project_id => 'ecookbook'
104 get '/issues', :project_id => 'ecookbook'
114
105
115 %w(Atom PDF CSV).each do |format|
106 %w(Atom PDF CSV).each do |format|
116 assert_tag :a, :content => format,
107 assert_tag :a, :content => format,
117 :attributes => { :href => "/projects/ecookbook/issues.#{format.downcase}",
108 :attributes => { :href => "/projects/ecookbook/issues.#{format.downcase}",
118 :rel => 'nofollow' }
109 :rel => 'nofollow' }
119 end
110 end
120 end
111 end
121
112
122 def test_pagination_links_on_index
113 def test_pagination_links_on_index
123 Setting.per_page_options = '2'
114 Setting.per_page_options = '2'
124 get '/projects/ecookbook/issues'
115 get '/projects/ecookbook/issues'
125
116
126 assert_tag :a, :content => '2',
117 assert_tag :a, :content => '2',
127 :attributes => { :href => '/projects/ecookbook/issues?page=2' }
118 :attributes => { :href => '/projects/ecookbook/issues?page=2' }
128
119
129 end
120 end
130
121
131 def test_pagination_links_on_index_without_project_id_in_url
122 def test_pagination_links_on_index_without_project_id_in_url
132 Setting.per_page_options = '2'
123 Setting.per_page_options = '2'
133 get '/issues', :project_id => 'ecookbook'
124 get '/issues', :project_id => 'ecookbook'
134
125
135 assert_tag :a, :content => '2',
126 assert_tag :a, :content => '2',
136 :attributes => { :href => '/projects/ecookbook/issues?page=2' }
127 :attributes => { :href => '/projects/ecookbook/issues?page=2' }
137
128
138 end
129 end
139
130
140 def test_issue_with_user_custom_field
131 def test_issue_with_user_custom_field
141 @field = IssueCustomField.create!(:name => 'Tester', :field_format => 'user', :is_for_all => true, :trackers => Tracker.all)
132 @field = IssueCustomField.create!(:name => 'Tester', :field_format => 'user', :is_for_all => true, :trackers => Tracker.all)
142 Role.anonymous.add_permission! :add_issues, :edit_issues
133 Role.anonymous.add_permission! :add_issues, :edit_issues
143 users = Project.find(1).users
134 users = Project.find(1).users
144 tester = users.first
135 tester = users.first
145
136
146 # Issue form
137 # Issue form
147 get '/projects/ecookbook/issues/new'
138 get '/projects/ecookbook/issues/new'
148 assert_response :success
139 assert_response :success
149 assert_tag :select,
140 assert_tag :select,
150 :attributes => {:name => "issue[custom_field_values][#{@field.id}]"},
141 :attributes => {:name => "issue[custom_field_values][#{@field.id}]"},
151 :children => {:count => (users.size + 1)}, # +1 for blank value
142 :children => {:count => (users.size + 1)}, # +1 for blank value
152 :child => {
143 :child => {
153 :tag => 'option',
144 :tag => 'option',
154 :attributes => {:value => tester.id.to_s},
145 :attributes => {:value => tester.id.to_s},
155 :content => tester.name
146 :content => tester.name
156 }
147 }
157
148
158 # Create issue
149 # Create issue
159 assert_difference 'Issue.count' do
150 assert_difference 'Issue.count' do
160 post '/projects/ecookbook/issues',
151 post '/projects/ecookbook/issues',
161 :issue => {
152 :issue => {
162 :tracker_id => '1',
153 :tracker_id => '1',
163 :priority_id => '4',
154 :priority_id => '4',
164 :subject => 'Issue with user custom field',
155 :subject => 'Issue with user custom field',
165 :custom_field_values => {@field.id.to_s => users.first.id.to_s}
156 :custom_field_values => {@field.id.to_s => users.first.id.to_s}
166 }
157 }
167 end
158 end
168 issue = Issue.first(:order => 'id DESC')
159 issue = Issue.first(:order => 'id DESC')
169 assert_response 302
160 assert_response 302
170
161
171 # Issue view
162 # Issue view
172 follow_redirect!
163 follow_redirect!
173 assert_tag :th,
164 assert_tag :th,
174 :content => /Tester/,
165 :content => /Tester/,
175 :sibling => {
166 :sibling => {
176 :tag => 'td',
167 :tag => 'td',
177 :content => tester.name
168 :content => tester.name
178 }
169 }
179 assert_tag :select,
170 assert_tag :select,
180 :attributes => {:name => "issue[custom_field_values][#{@field.id}]"},
171 :attributes => {:name => "issue[custom_field_values][#{@field.id}]"},
181 :children => {:count => (users.size + 1)}, # +1 for blank value
172 :children => {:count => (users.size + 1)}, # +1 for blank value
182 :child => {
173 :child => {
183 :tag => 'option',
174 :tag => 'option',
184 :attributes => {:value => tester.id.to_s, :selected => 'selected'},
175 :attributes => {:value => tester.id.to_s, :selected => 'selected'},
185 :content => tester.name
176 :content => tester.name
186 }
177 }
187
178
188 # Update issue
179 # Update issue
189 new_tester = users[1]
180 new_tester = users[1]
190 assert_difference 'Journal.count' do
181 assert_difference 'Journal.count' do
191 put "/issues/#{issue.id}",
182 put "/issues/#{issue.id}",
192 :notes => 'Updating custom field',
183 :notes => 'Updating custom field',
193 :issue => {
184 :issue => {
194 :custom_field_values => {@field.id.to_s => new_tester.id.to_s}
185 :custom_field_values => {@field.id.to_s => new_tester.id.to_s}
195 }
186 }
196 end
187 end
197 assert_response 302
188 assert_response 302
198
189
199 # Issue view
190 # Issue view
200 follow_redirect!
191 follow_redirect!
201 assert_tag :content => 'Tester',
192 assert_tag :content => 'Tester',
202 :ancestor => {:tag => 'ul', :attributes => {:class => /details/}},
193 :ancestor => {:tag => 'ul', :attributes => {:class => /details/}},
203 :sibling => {
194 :sibling => {
204 :content => tester.name,
195 :content => tester.name,
205 :sibling => {
196 :sibling => {
206 :content => new_tester.name
197 :content => new_tester.name
207 }
198 }
208 }
199 }
209 end
200 end
210
201
211 def test_update_using_invalid_http_verbs
202 def test_update_using_invalid_http_verbs
212 subject = 'Updated by an invalid http verb'
203 subject = 'Updated by an invalid http verb'
213
204
214 get '/issues/update/1', {:issue => {:subject => subject}}, credentials('jsmith')
205 get '/issues/update/1', {:issue => {:subject => subject}}, credentials('jsmith')
215 assert_response 404
206 assert_response 404
216 assert_not_equal subject, Issue.find(1).subject
207 assert_not_equal subject, Issue.find(1).subject
217
208
218 post '/issues/1', {:issue => {:subject => subject}}, credentials('jsmith')
209 post '/issues/1', {:issue => {:subject => subject}}, credentials('jsmith')
219 assert_response 404
210 assert_response 404
220 assert_not_equal subject, Issue.find(1).subject
211 assert_not_equal subject, Issue.find(1).subject
221 end
212 end
222
213
223 def test_get_watch_should_be_invalid
214 def test_get_watch_should_be_invalid
224 assert_no_difference 'Watcher.count' do
215 assert_no_difference 'Watcher.count' do
225 get '/watchers/watch?object_type=issue&object_id=1', {}, credentials('jsmith')
216 get '/watchers/watch?object_type=issue&object_id=1', {}, credentials('jsmith')
226 assert_response 404
217 assert_response 404
227 end
218 end
228 end
219 end
229 end
220 end
@@ -1,134 +1,134
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2013 Jean-Philippe Lang
2 # Copyright (C) 2006-2013 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.expand_path('../../../test_helper', __FILE__)
18 require File.expand_path('../../../test_helper', __FILE__)
19
19
20 class RoutingIssuesTest < ActionController::IntegrationTest
20 class RoutingIssuesTest < ActionController::IntegrationTest
21 def test_issues_rest_actions
21 def test_issues_rest_actions
22 assert_routing(
22 assert_routing(
23 { :method => 'get', :path => "/issues" },
23 { :method => 'get', :path => "/issues" },
24 { :controller => 'issues', :action => 'index' }
24 { :controller => 'issues', :action => 'index' }
25 )
25 )
26 assert_routing(
26 assert_routing(
27 { :method => 'get', :path => "/issues.pdf" },
27 { :method => 'get', :path => "/issues.pdf" },
28 { :controller => 'issues', :action => 'index', :format => 'pdf' }
28 { :controller => 'issues', :action => 'index', :format => 'pdf' }
29 )
29 )
30 assert_routing(
30 assert_routing(
31 { :method => 'get', :path => "/issues.atom" },
31 { :method => 'get', :path => "/issues.atom" },
32 { :controller => 'issues', :action => 'index', :format => 'atom' }
32 { :controller => 'issues', :action => 'index', :format => 'atom' }
33 )
33 )
34 assert_routing(
34 assert_routing(
35 { :method => 'get', :path => "/issues.xml" },
35 { :method => 'get', :path => "/issues.xml" },
36 { :controller => 'issues', :action => 'index', :format => 'xml' }
36 { :controller => 'issues', :action => 'index', :format => 'xml' }
37 )
37 )
38 assert_routing(
38 assert_routing(
39 { :method => 'get', :path => "/issues/64" },
39 { :method => 'get', :path => "/issues/64" },
40 { :controller => 'issues', :action => 'show', :id => '64' }
40 { :controller => 'issues', :action => 'show', :id => '64' }
41 )
41 )
42 assert_routing(
42 assert_routing(
43 { :method => 'get', :path => "/issues/64.pdf" },
43 { :method => 'get', :path => "/issues/64.pdf" },
44 { :controller => 'issues', :action => 'show', :id => '64',
44 { :controller => 'issues', :action => 'show', :id => '64',
45 :format => 'pdf' }
45 :format => 'pdf' }
46 )
46 )
47 assert_routing(
47 assert_routing(
48 { :method => 'get', :path => "/issues/64.atom" },
48 { :method => 'get', :path => "/issues/64.atom" },
49 { :controller => 'issues', :action => 'show', :id => '64',
49 { :controller => 'issues', :action => 'show', :id => '64',
50 :format => 'atom' }
50 :format => 'atom' }
51 )
51 )
52 assert_routing(
52 assert_routing(
53 { :method => 'get', :path => "/issues/64.xml" },
53 { :method => 'get', :path => "/issues/64.xml" },
54 { :controller => 'issues', :action => 'show', :id => '64',
54 { :controller => 'issues', :action => 'show', :id => '64',
55 :format => 'xml' }
55 :format => 'xml' }
56 )
56 )
57 assert_routing(
57 assert_routing(
58 { :method => 'post', :path => "/issues.xml" },
58 { :method => 'post', :path => "/issues.xml" },
59 { :controller => 'issues', :action => 'create', :format => 'xml' }
59 { :controller => 'issues', :action => 'create', :format => 'xml' }
60 )
60 )
61 assert_routing(
61 assert_routing(
62 { :method => 'get', :path => "/issues/64/edit" },
62 { :method => 'get', :path => "/issues/64/edit" },
63 { :controller => 'issues', :action => 'edit', :id => '64' }
63 { :controller => 'issues', :action => 'edit', :id => '64' }
64 )
64 )
65 assert_routing(
65 assert_routing(
66 { :method => 'put', :path => "/issues/1.xml" },
66 { :method => 'put', :path => "/issues/1.xml" },
67 { :controller => 'issues', :action => 'update', :id => '1',
67 { :controller => 'issues', :action => 'update', :id => '1',
68 :format => 'xml' }
68 :format => 'xml' }
69 )
69 )
70 assert_routing(
70 assert_routing(
71 { :method => 'delete', :path => "/issues/1.xml" },
71 { :method => 'delete', :path => "/issues/1.xml" },
72 { :controller => 'issues', :action => 'destroy', :id => '1',
72 { :controller => 'issues', :action => 'destroy', :id => '1',
73 :format => 'xml' }
73 :format => 'xml' }
74 )
74 )
75 end
75 end
76
76
77 def test_issues_rest_actions_scoped_under_project
77 def test_issues_rest_actions_scoped_under_project
78 assert_routing(
78 assert_routing(
79 { :method => 'get', :path => "/projects/23/issues" },
79 { :method => 'get', :path => "/projects/23/issues" },
80 { :controller => 'issues', :action => 'index', :project_id => '23' }
80 { :controller => 'issues', :action => 'index', :project_id => '23' }
81 )
81 )
82 assert_routing(
82 assert_routing(
83 { :method => 'get', :path => "/projects/23/issues.pdf" },
83 { :method => 'get', :path => "/projects/23/issues.pdf" },
84 { :controller => 'issues', :action => 'index', :project_id => '23',
84 { :controller => 'issues', :action => 'index', :project_id => '23',
85 :format => 'pdf' }
85 :format => 'pdf' }
86 )
86 )
87 assert_routing(
87 assert_routing(
88 { :method => 'get', :path => "/projects/23/issues.atom" },
88 { :method => 'get', :path => "/projects/23/issues.atom" },
89 { :controller => 'issues', :action => 'index', :project_id => '23',
89 { :controller => 'issues', :action => 'index', :project_id => '23',
90 :format => 'atom' }
90 :format => 'atom' }
91 )
91 )
92 assert_routing(
92 assert_routing(
93 { :method => 'get', :path => "/projects/23/issues.xml" },
93 { :method => 'get', :path => "/projects/23/issues.xml" },
94 { :controller => 'issues', :action => 'index', :project_id => '23',
94 { :controller => 'issues', :action => 'index', :project_id => '23',
95 :format => 'xml' }
95 :format => 'xml' }
96 )
96 )
97 assert_routing(
97 assert_routing(
98 { :method => 'post', :path => "/projects/23/issues" },
98 { :method => 'post', :path => "/projects/23/issues" },
99 { :controller => 'issues', :action => 'create', :project_id => '23' }
99 { :controller => 'issues', :action => 'create', :project_id => '23' }
100 )
100 )
101 assert_routing(
101 assert_routing(
102 { :method => 'get', :path => "/projects/23/issues/new" },
102 { :method => 'get', :path => "/projects/23/issues/new" },
103 { :controller => 'issues', :action => 'new', :project_id => '23' }
103 { :controller => 'issues', :action => 'new', :project_id => '23' }
104 )
104 )
105 end
105 end
106
106
107 def test_issues_form_update
107 def test_issues_form_update
108 ["post", "put"].each do |method|
108 ["post", "put"].each do |method|
109 assert_routing(
109 assert_routing(
110 { :method => method, :path => "/projects/23/issues/new" },
110 { :method => method, :path => "/projects/23/issues/update_form" },
111 { :controller => 'issues', :action => 'new', :project_id => '23' }
111 { :controller => 'issues', :action => 'update_form', :project_id => '23' }
112 )
112 )
113 end
113 end
114 end
114 end
115
115
116 def test_issues_extra_actions
116 def test_issues_extra_actions
117 assert_routing(
117 assert_routing(
118 { :method => 'get', :path => "/projects/23/issues/64/copy" },
118 { :method => 'get', :path => "/projects/23/issues/64/copy" },
119 { :controller => 'issues', :action => 'new', :project_id => '23',
119 { :controller => 'issues', :action => 'new', :project_id => '23',
120 :copy_from => '64' }
120 :copy_from => '64' }
121 )
121 )
122 # For updating the bulk edit form
122 # For updating the bulk edit form
123 ["get", "post"].each do |method|
123 ["get", "post"].each do |method|
124 assert_routing(
124 assert_routing(
125 { :method => method, :path => "/issues/bulk_edit" },
125 { :method => method, :path => "/issues/bulk_edit" },
126 { :controller => 'issues', :action => 'bulk_edit' }
126 { :controller => 'issues', :action => 'bulk_edit' }
127 )
127 )
128 end
128 end
129 assert_routing(
129 assert_routing(
130 { :method => 'post', :path => "/issues/bulk_update" },
130 { :method => 'post', :path => "/issues/bulk_update" },
131 { :controller => 'issues', :action => 'bulk_update' }
131 { :controller => 'issues', :action => 'bulk_update' }
132 )
132 )
133 end
133 end
134 end
134 end
@@ -1,128 +1,186
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2013 Jean-Philippe Lang
2 # Copyright (C) 2006-2013 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.expand_path('../base', __FILE__)
18 require File.expand_path('../base', __FILE__)
19
19
20 class Redmine::UiTest::IssuesTest < Redmine::UiTest::Base
20 class Redmine::UiTest::IssuesTest < Redmine::UiTest::Base
21 fixtures :projects, :users, :roles, :members, :member_roles,
21 fixtures :projects, :users, :roles, :members, :member_roles,
22 :trackers, :projects_trackers, :enabled_modules, :issue_statuses, :issues,
22 :trackers, :projects_trackers, :enabled_modules, :issue_statuses, :issues,
23 :enumerations, :custom_fields, :custom_values, :custom_fields_trackers,
23 :enumerations, :custom_fields, :custom_values, :custom_fields_trackers,
24 :watchers
24 :watchers
25
25
26 def test_create_issue
26 def test_create_issue
27 log_user('jsmith', 'jsmith')
27 log_user('jsmith', 'jsmith')
28 visit '/projects/ecookbook/issues/new'
28 visit '/projects/ecookbook/issues/new'
29 within('form#issue-form') do
29 within('form#issue-form') do
30 select 'Bug', :from => 'Tracker'
30 select 'Bug', :from => 'Tracker'
31 select 'Low', :from => 'Priority'
31 select 'Low', :from => 'Priority'
32 fill_in 'Subject', :with => 'new test issue'
32 fill_in 'Subject', :with => 'new test issue'
33 fill_in 'Description', :with => 'new issue'
33 fill_in 'Description', :with => 'new issue'
34 select '0 %', :from => 'Done'
34 select '0 %', :from => 'Done'
35 fill_in 'Due date', :with => ''
35 fill_in 'Due date', :with => ''
36 select '', :from => 'Assignee'
36 select '', :from => 'Assignee'
37 fill_in 'Searchable field', :with => 'Value for field 2'
37 fill_in 'Searchable field', :with => 'Value for field 2'
38 # click_button 'Create' would match both 'Create' and 'Create and continue' buttons
38 # click_button 'Create' would match both 'Create' and 'Create and continue' buttons
39 find('input[name=commit]').click
39 find('input[name=commit]').click
40 end
40 end
41
41
42 # find created issue
42 # find created issue
43 issue = Issue.find_by_subject("new test issue")
43 issue = Issue.find_by_subject("new test issue")
44 assert_kind_of Issue, issue
44 assert_kind_of Issue, issue
45
45
46 # check redirection
46 # check redirection
47 find 'div#flash_notice', :visible => true, :text => "Issue \##{issue.id} created."
47 find 'div#flash_notice', :visible => true, :text => "Issue \##{issue.id} created."
48 assert_equal issue_path(:id => issue), current_path
48 assert_equal issue_path(:id => issue), current_path
49
49
50 # check issue attributes
50 # check issue attributes
51 assert_equal 'jsmith', issue.author.login
51 assert_equal 'jsmith', issue.author.login
52 assert_equal 1, issue.project.id
52 assert_equal 1, issue.project.id
53 assert_equal IssueStatus.find_by_name('New'), issue.status
53 assert_equal IssueStatus.find_by_name('New'), issue.status
54 assert_equal Tracker.find_by_name('Bug'), issue.tracker
54 assert_equal Tracker.find_by_name('Bug'), issue.tracker
55 assert_equal IssuePriority.find_by_name('Low'), issue.priority
55 assert_equal IssuePriority.find_by_name('Low'), issue.priority
56 assert_equal 'Value for field 2', issue.custom_field_value(CustomField.find_by_name('Searchable field'))
56 assert_equal 'Value for field 2', issue.custom_field_value(CustomField.find_by_name('Searchable field'))
57 end
57 end
58
58
59 def test_create_issue_with_form_update
60 field = IssueCustomField.create!(
61 :field_format => 'string',
62 :name => 'Form update CF',
63 :is_for_all => true,
64 :trackers => Tracker.find_all_by_name('Feature request')
65 )
66
67 Role.non_member.add_permission! :add_issues
68 Role.non_member.remove_permission! :edit_issues, :add_issue_notes
69
70 log_user('someone', 'foo')
71 visit '/projects/ecookbook/issues/new'
72 assert page.has_no_content?('Form update CF')
73
74 fill_in 'Subject', :with => 'new test issue'
75 # the custom field should show up when changing tracker
76 select 'Feature request', :from => 'Tracker'
77 assert page.has_content?('Form update CF')
78
79 fill_in 'Form update', :with => 'CF value'
80 assert_difference 'Issue.count' do
81 find('input[name=commit]').click
82 end
83
84 issue = Issue.order('id desc').first
85 assert_equal 'CF value', issue.custom_field_value(field)
86 end
87
59 def test_create_issue_with_watchers
88 def test_create_issue_with_watchers
60 User.generate!(:firstname => 'Some', :lastname => 'Watcher')
89 User.generate!(:firstname => 'Some', :lastname => 'Watcher')
61
90
62 log_user('jsmith', 'jsmith')
91 log_user('jsmith', 'jsmith')
63 visit '/projects/ecookbook/issues/new'
92 visit '/projects/ecookbook/issues/new'
64 fill_in 'Subject', :with => 'Issue with watchers'
93 fill_in 'Subject', :with => 'Issue with watchers'
65 # Add a project member as watcher
94 # Add a project member as watcher
66 check 'Dave Lopper'
95 check 'Dave Lopper'
67 # Search for another user
96 # Search for another user
68 click_link 'Search for watchers to add'
97 click_link 'Search for watchers to add'
69 within('form#new-watcher-form') do
98 within('form#new-watcher-form') do
70 assert page.has_content?('Some One')
99 assert page.has_content?('Some One')
71 fill_in 'user_search', :with => 'watch'
100 fill_in 'user_search', :with => 'watch'
72 assert page.has_no_content?('Some One')
101 assert page.has_no_content?('Some One')
73 check 'Some Watcher'
102 check 'Some Watcher'
74 click_button 'Add'
103 click_button 'Add'
75 end
104 end
76 assert_difference 'Issue.count' do
105 assert_difference 'Issue.count' do
77 find('input[name=commit]').click
106 find('input[name=commit]').click
78 end
107 end
79
108
80 issue = Issue.order('id desc').first
109 issue = Issue.order('id desc').first
81 assert_equal ['Dave Lopper', 'Some Watcher'], issue.watcher_users.map(&:name).sort
110 assert_equal ['Dave Lopper', 'Some Watcher'], issue.watcher_users.map(&:name).sort
82 end
111 end
83
112
84 def test_preview_issue_description
113 def test_preview_issue_description
85 log_user('jsmith', 'jsmith')
114 log_user('jsmith', 'jsmith')
86 visit '/projects/ecookbook/issues/new'
115 visit '/projects/ecookbook/issues/new'
87 within('form#issue-form') do
116 within('form#issue-form') do
88 fill_in 'Subject', :with => 'new issue subject'
117 fill_in 'Subject', :with => 'new issue subject'
89 fill_in 'Description', :with => 'new issue description'
118 fill_in 'Description', :with => 'new issue description'
90 click_link 'Preview'
119 click_link 'Preview'
91 end
120 end
92 find 'div#preview fieldset', :visible => true, :text => 'new issue description'
121 find 'div#preview fieldset', :visible => true, :text => 'new issue description'
93 assert_difference 'Issue.count' do
122 assert_difference 'Issue.count' do
94 find('input[name=commit]').click
123 find('input[name=commit]').click
95 end
124 end
96
125
97 issue = Issue.order('id desc').first
126 issue = Issue.order('id desc').first
98 assert_equal 'new issue description', issue.description
127 assert_equal 'new issue description', issue.description
99 end
128 end
100
129
130 def test_update_issue_with_form_update
131 field = IssueCustomField.create!(
132 :field_format => 'string',
133 :name => 'Form update CF',
134 :is_for_all => true,
135 :trackers => Tracker.find_all_by_name('Feature request')
136 )
137
138 Role.non_member.add_permission! :edit_issues
139 Role.non_member.remove_permission! :add_issues, :add_issue_notes
140
141 log_user('someone', 'foo')
142 visit '/issues/1'
143 assert page.has_no_content?('Form update CF')
144
145 page.first(:link, 'Update').click
146 # the custom field should show up when changing tracker
147 select 'Feature request', :from => 'Tracker'
148 assert page.has_content?('Form update CF')
149
150 fill_in 'Form update', :with => 'CF value'
151 assert_no_difference 'Issue.count' do
152 page.first(:button, 'Submit').click
153 end
154
155 issue = Issue.find(1)
156 assert_equal 'CF value', issue.custom_field_value(field)
157 end
158
101 def test_watch_issue_via_context_menu
159 def test_watch_issue_via_context_menu
102 log_user('jsmith', 'jsmith')
160 log_user('jsmith', 'jsmith')
103 visit '/issues'
161 visit '/issues'
104 find('tr#issue-1 td.updated_on').click
162 find('tr#issue-1 td.updated_on').click
105 page.execute_script "$('tr#issue-1 td.updated_on').trigger('contextmenu');"
163 page.execute_script "$('tr#issue-1 td.updated_on').trigger('contextmenu');"
106 assert_difference 'Watcher.count' do
164 assert_difference 'Watcher.count' do
107 within('#context-menu') do
165 within('#context-menu') do
108 click_link 'Watch'
166 click_link 'Watch'
109 end
167 end
110 end
168 end
111 assert Issue.find(1).watched_by?(User.find_by_login('jsmith'))
169 assert Issue.find(1).watched_by?(User.find_by_login('jsmith'))
112 end
170 end
113
171
114 def test_bulk_watch_issues_via_context_menu
172 def test_bulk_watch_issues_via_context_menu
115 log_user('jsmith', 'jsmith')
173 log_user('jsmith', 'jsmith')
116 visit '/issues'
174 visit '/issues'
117 find('tr#issue-1 input[type=checkbox]').click
175 find('tr#issue-1 input[type=checkbox]').click
118 find('tr#issue-4 input[type=checkbox]').click
176 find('tr#issue-4 input[type=checkbox]').click
119 page.execute_script "$('tr#issue-1 td.updated_on').trigger('contextmenu');"
177 page.execute_script "$('tr#issue-1 td.updated_on').trigger('contextmenu');"
120 assert_difference 'Watcher.count', 2 do
178 assert_difference 'Watcher.count', 2 do
121 within('#context-menu') do
179 within('#context-menu') do
122 click_link 'Watch'
180 click_link 'Watch'
123 end
181 end
124 end
182 end
125 assert Issue.find(1).watched_by?(User.find_by_login('jsmith'))
183 assert Issue.find(1).watched_by?(User.find_by_login('jsmith'))
126 assert Issue.find(4).watched_by?(User.find_by_login('jsmith'))
184 assert Issue.find(4).watched_by?(User.find_by_login('jsmith'))
127 end
185 end
128 end
186 end
General Comments 0
You need to be logged in to leave comments. Login now