##// END OF EJS Templates
Removed the "New issue" menu item (#6204)....
Jean-Philippe Lang -
r14962:dcc569fa34f7
parent child
Show More

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

@@ -1,539 +1,538
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class IssuesController < ApplicationController
18 class IssuesController < ApplicationController
19 menu_item :new_issue, :only => [:new, :create]
20 default_search_scope :issues
19 default_search_scope :issues
21
20
22 before_filter :find_issue, :only => [:show, :edit, :update]
21 before_filter :find_issue, :only => [:show, :edit, :update]
23 before_filter :find_issues, :only => [:bulk_edit, :bulk_update, :destroy]
22 before_filter :find_issues, :only => [:bulk_edit, :bulk_update, :destroy]
24 before_filter :authorize, :except => [:index, :new, :create]
23 before_filter :authorize, :except => [:index, :new, :create]
25 before_filter :find_optional_project, :only => [:index, :new, :create]
24 before_filter :find_optional_project, :only => [:index, :new, :create]
26 before_filter :build_new_issue_from_params, :only => [:new, :create]
25 before_filter :build_new_issue_from_params, :only => [:new, :create]
27 accept_rss_auth :index, :show
26 accept_rss_auth :index, :show
28 accept_api_auth :index, :show, :create, :update, :destroy
27 accept_api_auth :index, :show, :create, :update, :destroy
29
28
30 rescue_from Query::StatementInvalid, :with => :query_statement_invalid
29 rescue_from Query::StatementInvalid, :with => :query_statement_invalid
31
30
32 helper :journals
31 helper :journals
33 helper :projects
32 helper :projects
34 helper :custom_fields
33 helper :custom_fields
35 helper :issue_relations
34 helper :issue_relations
36 helper :watchers
35 helper :watchers
37 helper :attachments
36 helper :attachments
38 helper :queries
37 helper :queries
39 include QueriesHelper
38 include QueriesHelper
40 helper :repositories
39 helper :repositories
41 helper :sort
40 helper :sort
42 include SortHelper
41 include SortHelper
43 helper :timelog
42 helper :timelog
44
43
45 def index
44 def index
46 retrieve_query
45 retrieve_query
47 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
46 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
48 sort_update(@query.sortable_columns)
47 sort_update(@query.sortable_columns)
49 @query.sort_criteria = sort_criteria.to_a
48 @query.sort_criteria = sort_criteria.to_a
50
49
51 if @query.valid?
50 if @query.valid?
52 case params[:format]
51 case params[:format]
53 when 'csv', 'pdf'
52 when 'csv', 'pdf'
54 @limit = Setting.issues_export_limit.to_i
53 @limit = Setting.issues_export_limit.to_i
55 if params[:columns] == 'all'
54 if params[:columns] == 'all'
56 @query.column_names = @query.available_inline_columns.map(&:name)
55 @query.column_names = @query.available_inline_columns.map(&:name)
57 end
56 end
58 when 'atom'
57 when 'atom'
59 @limit = Setting.feeds_limit.to_i
58 @limit = Setting.feeds_limit.to_i
60 when 'xml', 'json'
59 when 'xml', 'json'
61 @offset, @limit = api_offset_and_limit
60 @offset, @limit = api_offset_and_limit
62 @query.column_names = %w(author)
61 @query.column_names = %w(author)
63 else
62 else
64 @limit = per_page_option
63 @limit = per_page_option
65 end
64 end
66
65
67 @issue_count = @query.issue_count
66 @issue_count = @query.issue_count
68 @issue_pages = Paginator.new @issue_count, @limit, params['page']
67 @issue_pages = Paginator.new @issue_count, @limit, params['page']
69 @offset ||= @issue_pages.offset
68 @offset ||= @issue_pages.offset
70 @issues = @query.issues(:include => [:assigned_to, :tracker, :priority, :category, :fixed_version],
69 @issues = @query.issues(:include => [:assigned_to, :tracker, :priority, :category, :fixed_version],
71 :order => sort_clause,
70 :order => sort_clause,
72 :offset => @offset,
71 :offset => @offset,
73 :limit => @limit)
72 :limit => @limit)
74 @issue_count_by_group = @query.issue_count_by_group
73 @issue_count_by_group = @query.issue_count_by_group
75
74
76 respond_to do |format|
75 respond_to do |format|
77 format.html { render :template => 'issues/index', :layout => !request.xhr? }
76 format.html { render :template => 'issues/index', :layout => !request.xhr? }
78 format.api {
77 format.api {
79 Issue.load_visible_relations(@issues) if include_in_api_response?('relations')
78 Issue.load_visible_relations(@issues) if include_in_api_response?('relations')
80 }
79 }
81 format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
80 format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
82 format.csv { send_data(query_to_csv(@issues, @query, params[:csv]), :type => 'text/csv; header=present', :filename => 'issues.csv') }
81 format.csv { send_data(query_to_csv(@issues, @query, params[:csv]), :type => 'text/csv; header=present', :filename => 'issues.csv') }
83 format.pdf { send_file_headers! :type => 'application/pdf', :filename => 'issues.pdf' }
82 format.pdf { send_file_headers! :type => 'application/pdf', :filename => 'issues.pdf' }
84 end
83 end
85 else
84 else
86 respond_to do |format|
85 respond_to do |format|
87 format.html { render(:template => 'issues/index', :layout => !request.xhr?) }
86 format.html { render(:template => 'issues/index', :layout => !request.xhr?) }
88 format.any(:atom, :csv, :pdf) { render(:nothing => true) }
87 format.any(:atom, :csv, :pdf) { render(:nothing => true) }
89 format.api { render_validation_errors(@query) }
88 format.api { render_validation_errors(@query) }
90 end
89 end
91 end
90 end
92 rescue ActiveRecord::RecordNotFound
91 rescue ActiveRecord::RecordNotFound
93 render_404
92 render_404
94 end
93 end
95
94
96 def show
95 def show
97 @journals = @issue.journals.includes(:user, :details).
96 @journals = @issue.journals.includes(:user, :details).
98 references(:user, :details).
97 references(:user, :details).
99 reorder(:created_on, :id).to_a
98 reorder(:created_on, :id).to_a
100 @journals.each_with_index {|j,i| j.indice = i+1}
99 @journals.each_with_index {|j,i| j.indice = i+1}
101 @journals.reject!(&:private_notes?) unless User.current.allowed_to?(:view_private_notes, @issue.project)
100 @journals.reject!(&:private_notes?) unless User.current.allowed_to?(:view_private_notes, @issue.project)
102 Journal.preload_journals_details_custom_fields(@journals)
101 Journal.preload_journals_details_custom_fields(@journals)
103 @journals.select! {|journal| journal.notes? || journal.visible_details.any?}
102 @journals.select! {|journal| journal.notes? || journal.visible_details.any?}
104 @journals.reverse! if User.current.wants_comments_in_reverse_order?
103 @journals.reverse! if User.current.wants_comments_in_reverse_order?
105
104
106 @changesets = @issue.changesets.visible.preload(:repository, :user).to_a
105 @changesets = @issue.changesets.visible.preload(:repository, :user).to_a
107 @changesets.reverse! if User.current.wants_comments_in_reverse_order?
106 @changesets.reverse! if User.current.wants_comments_in_reverse_order?
108
107
109 @relations = @issue.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? }
108 @relations = @issue.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? }
110 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
109 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
111 @priorities = IssuePriority.active
110 @priorities = IssuePriority.active
112 @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
111 @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
113 @relation = IssueRelation.new
112 @relation = IssueRelation.new
114
113
115 respond_to do |format|
114 respond_to do |format|
116 format.html {
115 format.html {
117 retrieve_previous_and_next_issue_ids
116 retrieve_previous_and_next_issue_ids
118 render :template => 'issues/show'
117 render :template => 'issues/show'
119 }
118 }
120 format.api
119 format.api
121 format.atom { render :template => 'journals/index', :layout => false, :content_type => 'application/atom+xml' }
120 format.atom { render :template => 'journals/index', :layout => false, :content_type => 'application/atom+xml' }
122 format.pdf {
121 format.pdf {
123 send_file_headers! :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf"
122 send_file_headers! :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf"
124 }
123 }
125 end
124 end
126 end
125 end
127
126
128 def new
127 def new
129 respond_to do |format|
128 respond_to do |format|
130 format.html { render :action => 'new', :layout => !request.xhr? }
129 format.html { render :action => 'new', :layout => !request.xhr? }
131 format.js
130 format.js
132 end
131 end
133 end
132 end
134
133
135 def create
134 def create
136 unless User.current.allowed_to?(:add_issues, @issue.project, :global => true)
135 unless User.current.allowed_to?(:add_issues, @issue.project, :global => true)
137 raise ::Unauthorized
136 raise ::Unauthorized
138 end
137 end
139 call_hook(:controller_issues_new_before_save, { :params => params, :issue => @issue })
138 call_hook(:controller_issues_new_before_save, { :params => params, :issue => @issue })
140 @issue.save_attachments(params[:attachments] || (params[:issue] && params[:issue][:uploads]))
139 @issue.save_attachments(params[:attachments] || (params[:issue] && params[:issue][:uploads]))
141 if @issue.save
140 if @issue.save
142 call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue})
141 call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue})
143 respond_to do |format|
142 respond_to do |format|
144 format.html {
143 format.html {
145 render_attachment_warning_if_needed(@issue)
144 render_attachment_warning_if_needed(@issue)
146 flash[:notice] = l(:notice_issue_successful_create, :id => view_context.link_to("##{@issue.id}", issue_path(@issue), :title => @issue.subject))
145 flash[:notice] = l(:notice_issue_successful_create, :id => view_context.link_to("##{@issue.id}", issue_path(@issue), :title => @issue.subject))
147 redirect_after_create
146 redirect_after_create
148 }
147 }
149 format.api { render :action => 'show', :status => :created, :location => issue_url(@issue) }
148 format.api { render :action => 'show', :status => :created, :location => issue_url(@issue) }
150 end
149 end
151 return
150 return
152 else
151 else
153 respond_to do |format|
152 respond_to do |format|
154 format.html {
153 format.html {
155 if @issue.project.nil?
154 if @issue.project.nil?
156 render_error :status => 422
155 render_error :status => 422
157 else
156 else
158 render :action => 'new'
157 render :action => 'new'
159 end
158 end
160 }
159 }
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.js
170 format.js
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 = save_issue_with_child_records
179 saved = save_issue_with_child_records
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]).to_a
183 @conflict_journals = @issue.journals_after(params[:last_journal_id]).to_a
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, previous_and_next_issue_ids_params) }
193 format.html { redirect_back_or_default issue_path(@issue, previous_and_next_issue_ids_params) }
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
205 # Bulk edit/copy a set of issues
204 # Bulk edit/copy a set of issues
206 def bulk_edit
205 def bulk_edit
207 @issues.sort!
206 @issues.sort!
208 @copy = params[:copy].present?
207 @copy = params[:copy].present?
209 @notes = params[:notes]
208 @notes = params[:notes]
210
209
211 if @copy
210 if @copy
212 unless User.current.allowed_to?(:copy_issues, @projects)
211 unless User.current.allowed_to?(:copy_issues, @projects)
213 raise ::Unauthorized
212 raise ::Unauthorized
214 end
213 end
215 end
214 end
216
215
217 @allowed_projects = Issue.allowed_target_projects
216 @allowed_projects = Issue.allowed_target_projects
218 if params[:issue]
217 if params[:issue]
219 @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}
220 if @target_project
219 if @target_project
221 target_projects = [@target_project]
220 target_projects = [@target_project]
222 end
221 end
223 end
222 end
224 target_projects ||= @projects
223 target_projects ||= @projects
225
224
226 if @copy
225 if @copy
227 # Copied issues will get their default statuses
226 # Copied issues will get their default statuses
228 @available_statuses = []
227 @available_statuses = []
229 else
228 else
230 @available_statuses = @issues.map(&:new_statuses_allowed_to).reduce(:&)
229 @available_statuses = @issues.map(&:new_statuses_allowed_to).reduce(:&)
231 end
230 end
232 @custom_fields = @issues.map{|i|i.editable_custom_fields}.reduce(:&)
231 @custom_fields = @issues.map{|i|i.editable_custom_fields}.reduce(:&)
233 @assignables = target_projects.map(&:assignable_users).reduce(:&)
232 @assignables = target_projects.map(&:assignable_users).reduce(:&)
234 @trackers = target_projects.map(&:trackers).reduce(:&)
233 @trackers = target_projects.map(&:trackers).reduce(:&)
235 @versions = target_projects.map {|p| p.shared_versions.open}.reduce(:&)
234 @versions = target_projects.map {|p| p.shared_versions.open}.reduce(:&)
236 @categories = target_projects.map {|p| p.issue_categories}.reduce(:&)
235 @categories = target_projects.map {|p| p.issue_categories}.reduce(:&)
237 if @copy
236 if @copy
238 @attachments_present = @issues.detect {|i| i.attachments.any?}.present?
237 @attachments_present = @issues.detect {|i| i.attachments.any?}.present?
239 @subtasks_present = @issues.detect {|i| !i.leaf?}.present?
238 @subtasks_present = @issues.detect {|i| !i.leaf?}.present?
240 end
239 end
241
240
242 @safe_attributes = @issues.map(&:safe_attribute_names).reduce(:&)
241 @safe_attributes = @issues.map(&:safe_attribute_names).reduce(:&)
243
242
244 @issue_params = params[:issue] || {}
243 @issue_params = params[:issue] || {}
245 @issue_params[:custom_field_values] ||= {}
244 @issue_params[:custom_field_values] ||= {}
246 end
245 end
247
246
248 def bulk_update
247 def bulk_update
249 @issues.sort!
248 @issues.sort!
250 @copy = params[:copy].present?
249 @copy = params[:copy].present?
251
250
252 attributes = parse_params_for_bulk_issue_attributes(params)
251 attributes = parse_params_for_bulk_issue_attributes(params)
253 copy_subtasks = (params[:copy_subtasks] == '1')
252 copy_subtasks = (params[:copy_subtasks] == '1')
254 copy_attachments = (params[:copy_attachments] == '1')
253 copy_attachments = (params[:copy_attachments] == '1')
255
254
256 if @copy
255 if @copy
257 unless User.current.allowed_to?(:copy_issues, @projects)
256 unless User.current.allowed_to?(:copy_issues, @projects)
258 raise ::Unauthorized
257 raise ::Unauthorized
259 end
258 end
260 target_projects = @projects
259 target_projects = @projects
261 if attributes['project_id'].present?
260 if attributes['project_id'].present?
262 target_projects = Project.where(:id => attributes['project_id']).to_a
261 target_projects = Project.where(:id => attributes['project_id']).to_a
263 end
262 end
264 unless User.current.allowed_to?(:add_issues, target_projects)
263 unless User.current.allowed_to?(:add_issues, target_projects)
265 raise ::Unauthorized
264 raise ::Unauthorized
266 end
265 end
267 end
266 end
268
267
269 unsaved_issues = []
268 unsaved_issues = []
270 saved_issues = []
269 saved_issues = []
271
270
272 if @copy && copy_subtasks
271 if @copy && copy_subtasks
273 # Descendant issues will be copied with the parent task
272 # Descendant issues will be copied with the parent task
274 # Don't copy them twice
273 # Don't copy them twice
275 @issues.reject! {|issue| @issues.detect {|other| issue.is_descendant_of?(other)}}
274 @issues.reject! {|issue| @issues.detect {|other| issue.is_descendant_of?(other)}}
276 end
275 end
277
276
278 @issues.each do |orig_issue|
277 @issues.each do |orig_issue|
279 orig_issue.reload
278 orig_issue.reload
280 if @copy
279 if @copy
281 issue = orig_issue.copy({},
280 issue = orig_issue.copy({},
282 :attachments => copy_attachments,
281 :attachments => copy_attachments,
283 :subtasks => copy_subtasks,
282 :subtasks => copy_subtasks,
284 :link => link_copy?(params[:link_copy])
283 :link => link_copy?(params[:link_copy])
285 )
284 )
286 else
285 else
287 issue = orig_issue
286 issue = orig_issue
288 end
287 end
289 journal = issue.init_journal(User.current, params[:notes])
288 journal = issue.init_journal(User.current, params[:notes])
290 issue.safe_attributes = attributes
289 issue.safe_attributes = attributes
291 call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
290 call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
292 if issue.save
291 if issue.save
293 saved_issues << issue
292 saved_issues << issue
294 else
293 else
295 unsaved_issues << orig_issue
294 unsaved_issues << orig_issue
296 end
295 end
297 end
296 end
298
297
299 if unsaved_issues.empty?
298 if unsaved_issues.empty?
300 flash[:notice] = l(:notice_successful_update) unless saved_issues.empty?
299 flash[:notice] = l(:notice_successful_update) unless saved_issues.empty?
301 if params[:follow]
300 if params[:follow]
302 if @issues.size == 1 && saved_issues.size == 1
301 if @issues.size == 1 && saved_issues.size == 1
303 redirect_to issue_path(saved_issues.first)
302 redirect_to issue_path(saved_issues.first)
304 elsif saved_issues.map(&:project).uniq.size == 1
303 elsif saved_issues.map(&:project).uniq.size == 1
305 redirect_to project_issues_path(saved_issues.map(&:project).first)
304 redirect_to project_issues_path(saved_issues.map(&:project).first)
306 end
305 end
307 else
306 else
308 redirect_back_or_default _project_issues_path(@project)
307 redirect_back_or_default _project_issues_path(@project)
309 end
308 end
310 else
309 else
311 @saved_issues = @issues
310 @saved_issues = @issues
312 @unsaved_issues = unsaved_issues
311 @unsaved_issues = unsaved_issues
313 @issues = Issue.visible.where(:id => @unsaved_issues.map(&:id)).to_a
312 @issues = Issue.visible.where(:id => @unsaved_issues.map(&:id)).to_a
314 bulk_edit
313 bulk_edit
315 render :action => 'bulk_edit'
314 render :action => 'bulk_edit'
316 end
315 end
317 end
316 end
318
317
319 def destroy
318 def destroy
320 @hours = TimeEntry.where(:issue_id => @issues.map(&:id)).sum(:hours).to_f
319 @hours = TimeEntry.where(:issue_id => @issues.map(&:id)).sum(:hours).to_f
321 if @hours > 0
320 if @hours > 0
322 case params[:todo]
321 case params[:todo]
323 when 'destroy'
322 when 'destroy'
324 # nothing to do
323 # nothing to do
325 when 'nullify'
324 when 'nullify'
326 TimeEntry.where(['issue_id IN (?)', @issues]).update_all('issue_id = NULL')
325 TimeEntry.where(['issue_id IN (?)', @issues]).update_all('issue_id = NULL')
327 when 'reassign'
326 when 'reassign'
328 reassign_to = @project.issues.find_by_id(params[:reassign_to_id])
327 reassign_to = @project.issues.find_by_id(params[:reassign_to_id])
329 if reassign_to.nil?
328 if reassign_to.nil?
330 flash.now[:error] = l(:error_issue_not_found_in_project)
329 flash.now[:error] = l(:error_issue_not_found_in_project)
331 return
330 return
332 else
331 else
333 TimeEntry.where(['issue_id IN (?)', @issues]).
332 TimeEntry.where(['issue_id IN (?)', @issues]).
334 update_all("issue_id = #{reassign_to.id}")
333 update_all("issue_id = #{reassign_to.id}")
335 end
334 end
336 else
335 else
337 # display the destroy form if it's a user request
336 # display the destroy form if it's a user request
338 return unless api_request?
337 return unless api_request?
339 end
338 end
340 end
339 end
341 @issues.each do |issue|
340 @issues.each do |issue|
342 begin
341 begin
343 issue.reload.destroy
342 issue.reload.destroy
344 rescue ::ActiveRecord::RecordNotFound # raised by #reload if issue no longer exists
343 rescue ::ActiveRecord::RecordNotFound # raised by #reload if issue no longer exists
345 # nothing to do, issue was already deleted (eg. by a parent)
344 # nothing to do, issue was already deleted (eg. by a parent)
346 end
345 end
347 end
346 end
348 respond_to do |format|
347 respond_to do |format|
349 format.html { redirect_back_or_default _project_issues_path(@project) }
348 format.html { redirect_back_or_default _project_issues_path(@project) }
350 format.api { render_api_ok }
349 format.api { render_api_ok }
351 end
350 end
352 end
351 end
353
352
354 private
353 private
355
354
356 def retrieve_previous_and_next_issue_ids
355 def retrieve_previous_and_next_issue_ids
357 if params[:prev_issue_id].present? || params[:next_issue_id].present?
356 if params[:prev_issue_id].present? || params[:next_issue_id].present?
358 @prev_issue_id = params[:prev_issue_id].presence.try(:to_i)
357 @prev_issue_id = params[:prev_issue_id].presence.try(:to_i)
359 @next_issue_id = params[:next_issue_id].presence.try(:to_i)
358 @next_issue_id = params[:next_issue_id].presence.try(:to_i)
360 @issue_position = params[:issue_position].presence.try(:to_i)
359 @issue_position = params[:issue_position].presence.try(:to_i)
361 @issue_count = params[:issue_count].presence.try(:to_i)
360 @issue_count = params[:issue_count].presence.try(:to_i)
362 else
361 else
363 retrieve_query_from_session
362 retrieve_query_from_session
364 if @query
363 if @query
365 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
364 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
366 sort_update(@query.sortable_columns, 'issues_index_sort')
365 sort_update(@query.sortable_columns, 'issues_index_sort')
367 limit = 500
366 limit = 500
368 issue_ids = @query.issue_ids(:order => sort_clause, :limit => (limit + 1), :include => [:assigned_to, :tracker, :priority, :category, :fixed_version])
367 issue_ids = @query.issue_ids(:order => sort_clause, :limit => (limit + 1), :include => [:assigned_to, :tracker, :priority, :category, :fixed_version])
369 if (idx = issue_ids.index(@issue.id)) && idx < limit
368 if (idx = issue_ids.index(@issue.id)) && idx < limit
370 if issue_ids.size < 500
369 if issue_ids.size < 500
371 @issue_position = idx + 1
370 @issue_position = idx + 1
372 @issue_count = issue_ids.size
371 @issue_count = issue_ids.size
373 end
372 end
374 @prev_issue_id = issue_ids[idx - 1] if idx > 0
373 @prev_issue_id = issue_ids[idx - 1] if idx > 0
375 @next_issue_id = issue_ids[idx + 1] if idx < (issue_ids.size - 1)
374 @next_issue_id = issue_ids[idx + 1] if idx < (issue_ids.size - 1)
376 end
375 end
377 end
376 end
378 end
377 end
379 end
378 end
380
379
381 def previous_and_next_issue_ids_params
380 def previous_and_next_issue_ids_params
382 {
381 {
383 :prev_issue_id => params[:prev_issue_id],
382 :prev_issue_id => params[:prev_issue_id],
384 :next_issue_id => params[:next_issue_id],
383 :next_issue_id => params[:next_issue_id],
385 :issue_position => params[:issue_position],
384 :issue_position => params[:issue_position],
386 :issue_count => params[:issue_count]
385 :issue_count => params[:issue_count]
387 }.reject {|k,v| k.blank?}
386 }.reject {|k,v| k.blank?}
388 end
387 end
389
388
390 # Used by #edit and #update to set some common instance variables
389 # Used by #edit and #update to set some common instance variables
391 # from the params
390 # from the params
392 def update_issue_from_params
391 def update_issue_from_params
393 @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
392 @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
394 if params[:time_entry]
393 if params[:time_entry]
395 @time_entry.safe_attributes = params[:time_entry]
394 @time_entry.safe_attributes = params[:time_entry]
396 end
395 end
397
396
398 @issue.init_journal(User.current)
397 @issue.init_journal(User.current)
399
398
400 issue_attributes = params[:issue]
399 issue_attributes = params[:issue]
401 if issue_attributes && params[:conflict_resolution]
400 if issue_attributes && params[:conflict_resolution]
402 case params[:conflict_resolution]
401 case params[:conflict_resolution]
403 when 'overwrite'
402 when 'overwrite'
404 issue_attributes = issue_attributes.dup
403 issue_attributes = issue_attributes.dup
405 issue_attributes.delete(:lock_version)
404 issue_attributes.delete(:lock_version)
406 when 'add_notes'
405 when 'add_notes'
407 issue_attributes = issue_attributes.slice(:notes, :private_notes)
406 issue_attributes = issue_attributes.slice(:notes, :private_notes)
408 when 'cancel'
407 when 'cancel'
409 redirect_to issue_path(@issue)
408 redirect_to issue_path(@issue)
410 return false
409 return false
411 end
410 end
412 end
411 end
413 @issue.safe_attributes = issue_attributes
412 @issue.safe_attributes = issue_attributes
414 @priorities = IssuePriority.active
413 @priorities = IssuePriority.active
415 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
414 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
416 true
415 true
417 end
416 end
418
417
419 # Used by #new and #create to build a new issue from the params
418 # Used by #new and #create to build a new issue from the params
420 # The new issue will be copied from an existing one if copy_from parameter is given
419 # The new issue will be copied from an existing one if copy_from parameter is given
421 def build_new_issue_from_params
420 def build_new_issue_from_params
422 @issue = Issue.new
421 @issue = Issue.new
423 if params[:copy_from]
422 if params[:copy_from]
424 begin
423 begin
425 @issue.init_journal(User.current)
424 @issue.init_journal(User.current)
426 @copy_from = Issue.visible.find(params[:copy_from])
425 @copy_from = Issue.visible.find(params[:copy_from])
427 unless User.current.allowed_to?(:copy_issues, @copy_from.project)
426 unless User.current.allowed_to?(:copy_issues, @copy_from.project)
428 raise ::Unauthorized
427 raise ::Unauthorized
429 end
428 end
430 @link_copy = link_copy?(params[:link_copy]) || request.get?
429 @link_copy = link_copy?(params[:link_copy]) || request.get?
431 @copy_attachments = params[:copy_attachments].present? || request.get?
430 @copy_attachments = params[:copy_attachments].present? || request.get?
432 @copy_subtasks = params[:copy_subtasks].present? || request.get?
431 @copy_subtasks = params[:copy_subtasks].present? || request.get?
433 @issue.copy_from(@copy_from, :attachments => @copy_attachments, :subtasks => @copy_subtasks, :link => @link_copy)
432 @issue.copy_from(@copy_from, :attachments => @copy_attachments, :subtasks => @copy_subtasks, :link => @link_copy)
434 @issue.parent_issue_id = @copy_from.parent_id
433 @issue.parent_issue_id = @copy_from.parent_id
435 rescue ActiveRecord::RecordNotFound
434 rescue ActiveRecord::RecordNotFound
436 render_404
435 render_404
437 return
436 return
438 end
437 end
439 end
438 end
440 @issue.project = @project
439 @issue.project = @project
441 if request.get?
440 if request.get?
442 @issue.project ||= @issue.allowed_target_projects.first
441 @issue.project ||= @issue.allowed_target_projects.first
443 end
442 end
444 @issue.author ||= User.current
443 @issue.author ||= User.current
445 @issue.start_date ||= Date.today if Setting.default_issue_start_date_to_creation_date?
444 @issue.start_date ||= Date.today if Setting.default_issue_start_date_to_creation_date?
446
445
447 attrs = (params[:issue] || {}).deep_dup
446 attrs = (params[:issue] || {}).deep_dup
448 if action_name == 'new' && params[:was_default_status] == attrs[:status_id]
447 if action_name == 'new' && params[:was_default_status] == attrs[:status_id]
449 attrs.delete(:status_id)
448 attrs.delete(:status_id)
450 end
449 end
451 if action_name == 'new' && params[:form_update_triggered_by] == 'issue_project_id'
450 if action_name == 'new' && params[:form_update_triggered_by] == 'issue_project_id'
452 # Discard submitted version when changing the project on the issue form
451 # Discard submitted version when changing the project on the issue form
453 # so we can use the default version for the new project
452 # so we can use the default version for the new project
454 attrs.delete(:fixed_version_id)
453 attrs.delete(:fixed_version_id)
455 end
454 end
456 @issue.safe_attributes = attrs
455 @issue.safe_attributes = attrs
457
456
458 if @issue.project
457 if @issue.project
459 @issue.tracker ||= @issue.project.trackers.first
458 @issue.tracker ||= @issue.project.trackers.first
460 if @issue.tracker.nil?
459 if @issue.tracker.nil?
461 render_error l(:error_no_tracker_in_project)
460 render_error l(:error_no_tracker_in_project)
462 return false
461 return false
463 end
462 end
464 if @issue.status.nil?
463 if @issue.status.nil?
465 render_error l(:error_no_default_issue_status)
464 render_error l(:error_no_default_issue_status)
466 return false
465 return false
467 end
466 end
468 end
467 end
469
468
470 @priorities = IssuePriority.active
469 @priorities = IssuePriority.active
471 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
470 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
472 end
471 end
473
472
474 def parse_params_for_bulk_issue_attributes(params)
473 def parse_params_for_bulk_issue_attributes(params)
475 attributes = (params[:issue] || {}).reject {|k,v| v.blank?}
474 attributes = (params[:issue] || {}).reject {|k,v| v.blank?}
476 attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
475 attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
477 if custom = attributes[:custom_field_values]
476 if custom = attributes[:custom_field_values]
478 custom.reject! {|k,v| v.blank?}
477 custom.reject! {|k,v| v.blank?}
479 custom.keys.each do |k|
478 custom.keys.each do |k|
480 if custom[k].is_a?(Array)
479 if custom[k].is_a?(Array)
481 custom[k] << '' if custom[k].delete('__none__')
480 custom[k] << '' if custom[k].delete('__none__')
482 else
481 else
483 custom[k] = '' if custom[k] == '__none__'
482 custom[k] = '' if custom[k] == '__none__'
484 end
483 end
485 end
484 end
486 end
485 end
487 attributes
486 attributes
488 end
487 end
489
488
490 # Saves @issue and a time_entry from the parameters
489 # Saves @issue and a time_entry from the parameters
491 def save_issue_with_child_records
490 def save_issue_with_child_records
492 Issue.transaction do
491 Issue.transaction do
493 if params[:time_entry] && (params[:time_entry][:hours].present? || params[:time_entry][:comments].present?) && User.current.allowed_to?(:log_time, @issue.project)
492 if params[:time_entry] && (params[:time_entry][:hours].present? || params[:time_entry][:comments].present?) && User.current.allowed_to?(:log_time, @issue.project)
494 time_entry = @time_entry || TimeEntry.new
493 time_entry = @time_entry || TimeEntry.new
495 time_entry.project = @issue.project
494 time_entry.project = @issue.project
496 time_entry.issue = @issue
495 time_entry.issue = @issue
497 time_entry.user = User.current
496 time_entry.user = User.current
498 time_entry.spent_on = User.current.today
497 time_entry.spent_on = User.current.today
499 time_entry.attributes = params[:time_entry]
498 time_entry.attributes = params[:time_entry]
500 @issue.time_entries << time_entry
499 @issue.time_entries << time_entry
501 end
500 end
502
501
503 call_hook(:controller_issues_edit_before_save, { :params => params, :issue => @issue, :time_entry => time_entry, :journal => @issue.current_journal})
502 call_hook(:controller_issues_edit_before_save, { :params => params, :issue => @issue, :time_entry => time_entry, :journal => @issue.current_journal})
504 if @issue.save
503 if @issue.save
505 call_hook(:controller_issues_edit_after_save, { :params => params, :issue => @issue, :time_entry => time_entry, :journal => @issue.current_journal})
504 call_hook(:controller_issues_edit_after_save, { :params => params, :issue => @issue, :time_entry => time_entry, :journal => @issue.current_journal})
506 else
505 else
507 raise ActiveRecord::Rollback
506 raise ActiveRecord::Rollback
508 end
507 end
509 end
508 end
510 end
509 end
511
510
512 # Returns true if the issue copy should be linked
511 # Returns true if the issue copy should be linked
513 # to the original issue
512 # to the original issue
514 def link_copy?(param)
513 def link_copy?(param)
515 case Setting.link_copied_issue
514 case Setting.link_copied_issue
516 when 'yes'
515 when 'yes'
517 true
516 true
518 when 'no'
517 when 'no'
519 false
518 false
520 when 'ask'
519 when 'ask'
521 param == '1'
520 param == '1'
522 end
521 end
523 end
522 end
524
523
525 # Redirects user after a successful issue creation
524 # Redirects user after a successful issue creation
526 def redirect_after_create
525 def redirect_after_create
527 if params[:continue]
526 if params[:continue]
528 attrs = {:tracker_id => @issue.tracker, :parent_issue_id => @issue.parent_issue_id}.reject {|k,v| v.nil?}
527 attrs = {:tracker_id => @issue.tracker, :parent_issue_id => @issue.parent_issue_id}.reject {|k,v| v.nil?}
529 if params[:project_id]
528 if params[:project_id]
530 redirect_to new_project_issue_path(@issue.project, :issue => attrs)
529 redirect_to new_project_issue_path(@issue.project, :issue => attrs)
531 else
530 else
532 attrs.merge! :project_id => @issue.project_id
531 attrs.merge! :project_id => @issue.project_id
533 redirect_to new_issue_path(:issue => attrs)
532 redirect_to new_issue_path(:issue => attrs)
534 end
533 end
535 else
534 else
536 redirect_to issue_path(@issue)
535 redirect_to issue_path(@issue)
537 end
536 end
538 end
537 end
539 end
538 end
@@ -1,121 +1,127
1 <div class="contextual">
1 <div class="contextual">
2 <% if !@query.new_record? && @query.editable_by?(User.current) %>
2 <% if @project && User.current.allowed_to?(:add_issues, @project) && @project.trackers.any? %>
3 <%= link_to l(:button_edit), edit_query_path(@query), :class => 'icon icon-edit' %>
3 <%= link_to l(:label_issue_new), new_project_issue_path(@project), :class => 'icon icon-add new-issue' %>
4 <%= delete_link query_path(@query) %>
5 <% end %>
4 <% end %>
6 </div>
5 </div>
7
6
8 <h2><%= @query.new_record? ? l(:label_issue_plural) : @query.name %></h2>
7 <h2><%= @query.new_record? ? l(:label_issue_plural) : @query.name %></h2>
9 <% html_title(@query.new_record? ? l(:label_issue_plural) : @query.name) %>
8 <% html_title(@query.new_record? ? l(:label_issue_plural) : @query.name) %>
10
9
11 <%= form_tag({ :controller => 'issues', :action => 'index', :project_id => @project },
10 <%= form_tag({ :controller => 'issues', :action => 'index', :project_id => @project },
12 :method => :get, :id => 'query_form') do %>
11 :method => :get, :id => 'query_form') do %>
13 <div id="query_form_with_buttons" class="hide-when-print">
12 <div id="query_form_with_buttons" class="hide-when-print">
14 <%= hidden_field_tag 'set_filter', '1' %>
13 <%= hidden_field_tag 'set_filter', '1' %>
15 <div id="query_form_content">
14 <div id="query_form_content">
16 <fieldset id="filters" class="collapsible <%= @query.new_record? ? "" : "collapsed" %>">
15 <fieldset id="filters" class="collapsible <%= @query.new_record? ? "" : "collapsed" %>">
17 <legend onclick="toggleFieldset(this);"><%= l(:label_filter_plural) %></legend>
16 <legend onclick="toggleFieldset(this);"><%= l(:label_filter_plural) %></legend>
18 <div style="<%= @query.new_record? ? "" : "display: none;" %>">
17 <div style="<%= @query.new_record? ? "" : "display: none;" %>">
19 <%= render :partial => 'queries/filters', :locals => {:query => @query} %>
18 <%= render :partial => 'queries/filters', :locals => {:query => @query} %>
20 </div>
19 </div>
21 </fieldset>
20 </fieldset>
22 <fieldset id="options" class="collapsible collapsed">
21 <fieldset id="options" class="collapsible collapsed">
23 <legend onclick="toggleFieldset(this);"><%= l(:label_options) %></legend>
22 <legend onclick="toggleFieldset(this);"><%= l(:label_options) %></legend>
24 <div style="display: none;">
23 <div style="display: none;">
25 <table>
24 <table>
26 <tr>
25 <tr>
27 <td class="field"><%= l(:field_column_names) %></td>
26 <td class="field"><%= l(:field_column_names) %></td>
28 <td><%= render_query_columns_selection(@query) %></td>
27 <td><%= render_query_columns_selection(@query) %></td>
29 </tr>
28 </tr>
30 <tr>
29 <tr>
31 <td class="field"><label for='group_by'><%= l(:field_group_by) %></label></td>
30 <td class="field"><label for='group_by'><%= l(:field_group_by) %></label></td>
32 <td><%= select_tag('group_by',
31 <td><%= select_tag('group_by',
33 options_for_select(
32 options_for_select(
34 [[]] + @query.groupable_columns.collect {|c| [c.caption, c.name.to_s]},
33 [[]] + @query.groupable_columns.collect {|c| [c.caption, c.name.to_s]},
35 @query.group_by)
34 @query.group_by)
36 ) %></td>
35 ) %></td>
37 </tr>
36 </tr>
38 <tr>
37 <tr>
39 <td class="field"><%= l(:button_show) %></td>
38 <td class="field"><%= l(:button_show) %></td>
40 <td><%= available_block_columns_tags(@query) %></td>
39 <td><%= available_block_columns_tags(@query) %></td>
41 </tr>
40 </tr>
42 <tr>
41 <tr>
43 <td><%= l(:label_total_plural) %></td>
42 <td><%= l(:label_total_plural) %></td>
44 <td><%= available_totalable_columns_tags(@query) %></td>
43 <td><%= available_totalable_columns_tags(@query) %></td>
45 </tr>
44 </tr>
46 </table>
45 </table>
47 </div>
46 </div>
48 </fieldset>
47 </fieldset>
49 </div>
48 </div>
50 <p class="buttons">
49 <p class="buttons">
51 <%= link_to_function l(:button_apply), '$("#query_form").submit()', :class => 'icon icon-checked' %>
50 <%= link_to_function l(:button_apply), '$("#query_form").submit()', :class => 'icon icon-checked' %>
52 <%= link_to l(:button_clear), { :set_filter => 1, :project_id => @project }, :class => 'icon icon-reload' %>
51 <%= link_to l(:button_clear), { :set_filter => 1, :project_id => @project }, :class => 'icon icon-reload' %>
53 <% if @query.new_record? && User.current.allowed_to?(:save_queries, @project, :global => true) %>
52 <% if @query.new_record? %>
53 <% if User.current.allowed_to?(:save_queries, @project, :global => true) %>
54 <%= link_to_function l(:button_save),
54 <%= link_to_function l(:button_save),
55 "$('#query_form').attr('action', '#{ @project ? new_project_query_path(@project) : new_query_path }').submit()",
55 "$('#query_form').attr('action', '#{ @project ? new_project_query_path(@project) : new_query_path }').submit()",
56 :class => 'icon icon-save' %>
56 :class => 'icon icon-save' %>
57 <% end %>
57 <% end %>
58 <% else %>
59 <% if @query.editable_by?(User.current) %>
60 <%= link_to l(:button_edit), edit_query_path(@query), :class => 'icon icon-edit' %>
61 <%= delete_link query_path(@query) %>
62 <% end %>
63 <% end %>
58 </p>
64 </p>
59 </div>
65 </div>
60 <% end %>
66 <% end %>
61
67
62 <%= error_messages_for 'query' %>
68 <%= error_messages_for 'query' %>
63 <% if @query.valid? %>
69 <% if @query.valid? %>
64 <% if @issues.empty? %>
70 <% if @issues.empty? %>
65 <p class="nodata"><%= l(:label_no_data) %></p>
71 <p class="nodata"><%= l(:label_no_data) %></p>
66 <% else %>
72 <% else %>
67 <%= render_query_totals(@query) %>
73 <%= render_query_totals(@query) %>
68 <%= render :partial => 'issues/list', :locals => {:issues => @issues, :query => @query} %>
74 <%= render :partial => 'issues/list', :locals => {:issues => @issues, :query => @query} %>
69 <span class="pagination"><%= pagination_links_full @issue_pages, @issue_count %></span>
75 <span class="pagination"><%= pagination_links_full @issue_pages, @issue_count %></span>
70 <% end %>
76 <% end %>
71
77
72 <% other_formats_links do |f| %>
78 <% other_formats_links do |f| %>
73 <%= f.link_to 'Atom', :url => params.merge(:key => User.current.rss_key) %>
79 <%= f.link_to 'Atom', :url => params.merge(:key => User.current.rss_key) %>
74 <%= f.link_to 'CSV', :url => params, :onclick => "showModal('csv-export-options', '350px'); return false;" %>
80 <%= f.link_to 'CSV', :url => params, :onclick => "showModal('csv-export-options', '350px'); return false;" %>
75 <%= f.link_to 'PDF', :url => params %>
81 <%= f.link_to 'PDF', :url => params %>
76 <% end %>
82 <% end %>
77
83
78 <div id="csv-export-options" style="display:none;">
84 <div id="csv-export-options" style="display:none;">
79 <h3 class="title"><%= l(:label_export_options, :export_format => 'CSV') %></h3>
85 <h3 class="title"><%= l(:label_export_options, :export_format => 'CSV') %></h3>
80 <%= form_tag(_project_issues_path(@project, :format => 'csv'), :method => :get, :id => 'csv-export-form') do %>
86 <%= form_tag(_project_issues_path(@project, :format => 'csv'), :method => :get, :id => 'csv-export-form') do %>
81 <%= query_as_hidden_field_tags(@query) %>
87 <%= query_as_hidden_field_tags(@query) %>
82 <%= hidden_field_tag 'sort', @sort_criteria.to_param, :id => nil %>
88 <%= hidden_field_tag 'sort', @sort_criteria.to_param, :id => nil %>
83 <p>
89 <p>
84 <label><%= radio_button_tag 'csv[columns]', '', true %> <%= l(:description_selected_columns) %></label><br />
90 <label><%= radio_button_tag 'csv[columns]', '', true %> <%= l(:description_selected_columns) %></label><br />
85 <label><%= radio_button_tag 'csv[columns]', 'all' %> <%= l(:description_all_columns) %></label>
91 <label><%= radio_button_tag 'csv[columns]', 'all' %> <%= l(:description_all_columns) %></label>
86 </p>
92 </p>
87 <p>
93 <p>
88 <label><%= check_box_tag 'csv[description]', '1', @query.has_column?(:description) %> <%= l(:field_description) %></label>
94 <label><%= check_box_tag 'csv[description]', '1', @query.has_column?(:description) %> <%= l(:field_description) %></label>
89 </p>
95 </p>
90 <% if @issue_count > Setting.issues_export_limit.to_i %>
96 <% if @issue_count > Setting.issues_export_limit.to_i %>
91 <p class="icon icon-warning">
97 <p class="icon icon-warning">
92 <%= l(:setting_issues_export_limit) %>: <%= Setting.issues_export_limit.to_i %>
98 <%= l(:setting_issues_export_limit) %>: <%= Setting.issues_export_limit.to_i %>
93 </p>
99 </p>
94 <% end %>
100 <% end %>
95 <p class="buttons">
101 <p class="buttons">
96 <%= submit_tag l(:button_export), :name => nil, :onclick => "hideModal(this);" %>
102 <%= submit_tag l(:button_export), :name => nil, :onclick => "hideModal(this);" %>
97 <%= submit_tag l(:button_cancel), :name => nil, :onclick => "hideModal(this);", :type => 'button' %>
103 <%= submit_tag l(:button_cancel), :name => nil, :onclick => "hideModal(this);", :type => 'button' %>
98 </p>
104 </p>
99 <% end %>
105 <% end %>
100 </div>
106 </div>
101
107
102 <% end %>
108 <% end %>
103 <%= call_hook(:view_issues_index_bottom, { :issues => @issues, :project => @project, :query => @query }) %>
109 <%= call_hook(:view_issues_index_bottom, { :issues => @issues, :project => @project, :query => @query }) %>
104
110
105 <% content_for :sidebar do %>
111 <% content_for :sidebar do %>
106 <%= render :partial => 'issues/sidebar' %>
112 <%= render :partial => 'issues/sidebar' %>
107 <% end %>
113 <% end %>
108
114
109 <% content_for :header_tags do %>
115 <% content_for :header_tags do %>
110 <%= auto_discovery_link_tag(:atom,
116 <%= auto_discovery_link_tag(:atom,
111 {:query_id => @query, :format => 'atom',
117 {:query_id => @query, :format => 'atom',
112 :page => nil, :key => User.current.rss_key},
118 :page => nil, :key => User.current.rss_key},
113 :title => l(:label_issue_plural)) %>
119 :title => l(:label_issue_plural)) %>
114 <%= auto_discovery_link_tag(:atom,
120 <%= auto_discovery_link_tag(:atom,
115 {:controller => 'journals', :action => 'index',
121 {:controller => 'journals', :action => 'index',
116 :query_id => @query, :format => 'atom',
122 :query_id => @query, :format => 'atom',
117 :page => nil, :key => User.current.rss_key},
123 :page => nil, :key => User.current.rss_key},
118 :title => l(:label_changes_details)) %>
124 :title => l(:label_changes_details)) %>
119 <% end %>
125 <% end %>
120
126
121 <%= context_menu issues_context_menu_path %>
127 <%= context_menu issues_context_menu_path %>
@@ -1,278 +1,274
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 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 begin
25 begin
26 require 'redcarpet' unless Object.const_defined?(:Redcarpet)
26 require 'redcarpet' unless Object.const_defined?(:Redcarpet)
27 rescue LoadError
27 rescue LoadError
28 # Redcarpet is not available
28 # Redcarpet is not available
29 end
29 end
30
30
31 require 'redmine/acts/positioned'
31 require 'redmine/acts/positioned'
32
32
33 require 'redmine/scm/base'
33 require 'redmine/scm/base'
34 require 'redmine/access_control'
34 require 'redmine/access_control'
35 require 'redmine/access_keys'
35 require 'redmine/access_keys'
36 require 'redmine/activity'
36 require 'redmine/activity'
37 require 'redmine/activity/fetcher'
37 require 'redmine/activity/fetcher'
38 require 'redmine/ciphering'
38 require 'redmine/ciphering'
39 require 'redmine/codeset_util'
39 require 'redmine/codeset_util'
40 require 'redmine/field_format'
40 require 'redmine/field_format'
41 require 'redmine/menu_manager'
41 require 'redmine/menu_manager'
42 require 'redmine/notifiable'
42 require 'redmine/notifiable'
43 require 'redmine/platform'
43 require 'redmine/platform'
44 require 'redmine/mime_type'
44 require 'redmine/mime_type'
45 require 'redmine/notifiable'
45 require 'redmine/notifiable'
46 require 'redmine/search'
46 require 'redmine/search'
47 require 'redmine/syntax_highlighting'
47 require 'redmine/syntax_highlighting'
48 require 'redmine/thumbnail'
48 require 'redmine/thumbnail'
49 require 'redmine/unified_diff'
49 require 'redmine/unified_diff'
50 require 'redmine/utils'
50 require 'redmine/utils'
51 require 'redmine/version'
51 require 'redmine/version'
52 require 'redmine/wiki_formatting'
52 require 'redmine/wiki_formatting'
53
53
54 require 'redmine/default_data/loader'
54 require 'redmine/default_data/loader'
55 require 'redmine/helpers/calendar'
55 require 'redmine/helpers/calendar'
56 require 'redmine/helpers/diff'
56 require 'redmine/helpers/diff'
57 require 'redmine/helpers/gantt'
57 require 'redmine/helpers/gantt'
58 require 'redmine/helpers/time_report'
58 require 'redmine/helpers/time_report'
59 require 'redmine/views/other_formats_builder'
59 require 'redmine/views/other_formats_builder'
60 require 'redmine/views/labelled_form_builder'
60 require 'redmine/views/labelled_form_builder'
61 require 'redmine/views/builders'
61 require 'redmine/views/builders'
62
62
63 require 'redmine/themes'
63 require 'redmine/themes'
64 require 'redmine/hook'
64 require 'redmine/hook'
65 require 'redmine/hook/listener'
65 require 'redmine/hook/listener'
66 require 'redmine/hook/view_listener'
66 require 'redmine/hook/view_listener'
67 require 'redmine/plugin'
67 require 'redmine/plugin'
68
68
69 Redmine::Scm::Base.add "Subversion"
69 Redmine::Scm::Base.add "Subversion"
70 Redmine::Scm::Base.add "Darcs"
70 Redmine::Scm::Base.add "Darcs"
71 Redmine::Scm::Base.add "Mercurial"
71 Redmine::Scm::Base.add "Mercurial"
72 Redmine::Scm::Base.add "Cvs"
72 Redmine::Scm::Base.add "Cvs"
73 Redmine::Scm::Base.add "Bazaar"
73 Redmine::Scm::Base.add "Bazaar"
74 Redmine::Scm::Base.add "Git"
74 Redmine::Scm::Base.add "Git"
75 Redmine::Scm::Base.add "Filesystem"
75 Redmine::Scm::Base.add "Filesystem"
76
76
77 # Permissions
77 # Permissions
78 Redmine::AccessControl.map do |map|
78 Redmine::AccessControl.map do |map|
79 map.permission :view_project, {:projects => [:show], :activities => [:index]}, :public => true, :read => true
79 map.permission :view_project, {:projects => [:show], :activities => [:index]}, :public => true, :read => true
80 map.permission :search_project, {:search => :index}, :public => true, :read => true
80 map.permission :search_project, {:search => :index}, :public => true, :read => true
81 map.permission :add_project, {:projects => [:new, :create]}, :require => :loggedin
81 map.permission :add_project, {:projects => [:new, :create]}, :require => :loggedin
82 map.permission :edit_project, {:projects => [:settings, :edit, :update]}, :require => :member
82 map.permission :edit_project, {:projects => [:settings, :edit, :update]}, :require => :member
83 map.permission :close_project, {:projects => [:close, :reopen]}, :require => :member, :read => true
83 map.permission :close_project, {:projects => [:close, :reopen]}, :require => :member, :read => true
84 map.permission :select_project_modules, {:projects => :modules}, :require => :member
84 map.permission :select_project_modules, {:projects => :modules}, :require => :member
85 map.permission :view_members, {:members => [:index, :show]}, :public => true, :read => true
85 map.permission :view_members, {:members => [:index, :show]}, :public => true, :read => true
86 map.permission :manage_members, {:projects => :settings, :members => [:index, :show, :new, :create, :update, :destroy, :autocomplete]}, :require => :member
86 map.permission :manage_members, {:projects => :settings, :members => [:index, :show, :new, :create, :update, :destroy, :autocomplete]}, :require => :member
87 map.permission :manage_versions, {:projects => :settings, :versions => [:new, :create, :edit, :update, :close_completed, :destroy]}, :require => :member
87 map.permission :manage_versions, {:projects => :settings, :versions => [:new, :create, :edit, :update, :close_completed, :destroy]}, :require => :member
88 map.permission :add_subprojects, {:projects => [:new, :create]}, :require => :member
88 map.permission :add_subprojects, {:projects => [:new, :create]}, :require => :member
89
89
90 map.project_module :issue_tracking do |map|
90 map.project_module :issue_tracking do |map|
91 # Issue categories
91 # Issue categories
92 map.permission :manage_categories, {:projects => :settings, :issue_categories => [:index, :show, :new, :create, :edit, :update, :destroy]}, :require => :member
92 map.permission :manage_categories, {:projects => :settings, :issue_categories => [:index, :show, :new, :create, :edit, :update, :destroy]}, :require => :member
93 # Issues
93 # Issues
94 map.permission :view_issues, {:issues => [:index, :show],
94 map.permission :view_issues, {:issues => [:index, :show],
95 :auto_complete => [:issues],
95 :auto_complete => [:issues],
96 :context_menus => [:issues],
96 :context_menus => [:issues],
97 :versions => [:index, :show, :status_by],
97 :versions => [:index, :show, :status_by],
98 :journals => [:index, :diff],
98 :journals => [:index, :diff],
99 :queries => :index,
99 :queries => :index,
100 :reports => [:issue_report, :issue_report_details]},
100 :reports => [:issue_report, :issue_report_details]},
101 :read => true
101 :read => true
102 map.permission :add_issues, {:issues => [:new, :create], :attachments => :upload}
102 map.permission :add_issues, {:issues => [:new, :create], :attachments => :upload}
103 map.permission :edit_issues, {:issues => [:edit, :update, :bulk_edit, :bulk_update], :journals => [:new], :attachments => :upload}
103 map.permission :edit_issues, {:issues => [:edit, :update, :bulk_edit, :bulk_update], :journals => [:new], :attachments => :upload}
104 map.permission :copy_issues, {:issues => [:new, :create, :bulk_edit, :bulk_update], :attachments => :upload}
104 map.permission :copy_issues, {:issues => [:new, :create, :bulk_edit, :bulk_update], :attachments => :upload}
105 map.permission :manage_issue_relations, {:issue_relations => [:index, :show, :create, :destroy]}
105 map.permission :manage_issue_relations, {:issue_relations => [:index, :show, :create, :destroy]}
106 map.permission :manage_subtasks, {}
106 map.permission :manage_subtasks, {}
107 map.permission :set_issues_private, {}
107 map.permission :set_issues_private, {}
108 map.permission :set_own_issues_private, {}, :require => :loggedin
108 map.permission :set_own_issues_private, {}, :require => :loggedin
109 map.permission :add_issue_notes, {:issues => [:edit, :update], :journals => [:new], :attachments => :upload}
109 map.permission :add_issue_notes, {:issues => [:edit, :update], :journals => [:new], :attachments => :upload}
110 map.permission :edit_issue_notes, {:journals => [:edit, :update]}, :require => :loggedin
110 map.permission :edit_issue_notes, {:journals => [:edit, :update]}, :require => :loggedin
111 map.permission :edit_own_issue_notes, {:journals => [:edit, :update]}, :require => :loggedin
111 map.permission :edit_own_issue_notes, {:journals => [:edit, :update]}, :require => :loggedin
112 map.permission :view_private_notes, {}, :read => true, :require => :member
112 map.permission :view_private_notes, {}, :read => true, :require => :member
113 map.permission :set_notes_private, {}, :require => :member
113 map.permission :set_notes_private, {}, :require => :member
114 map.permission :delete_issues, {:issues => :destroy}, :require => :member
114 map.permission :delete_issues, {:issues => :destroy}, :require => :member
115 # Queries
115 # Queries
116 map.permission :manage_public_queries, {:queries => [:new, :create, :edit, :update, :destroy]}, :require => :member
116 map.permission :manage_public_queries, {:queries => [:new, :create, :edit, :update, :destroy]}, :require => :member
117 map.permission :save_queries, {:queries => [:new, :create, :edit, :update, :destroy]}, :require => :loggedin
117 map.permission :save_queries, {:queries => [:new, :create, :edit, :update, :destroy]}, :require => :loggedin
118 # Watchers
118 # Watchers
119 map.permission :view_issue_watchers, {}, :read => true
119 map.permission :view_issue_watchers, {}, :read => true
120 map.permission :add_issue_watchers, {:watchers => [:new, :create, :append, :autocomplete_for_user]}
120 map.permission :add_issue_watchers, {:watchers => [:new, :create, :append, :autocomplete_for_user]}
121 map.permission :delete_issue_watchers, {:watchers => :destroy}
121 map.permission :delete_issue_watchers, {:watchers => :destroy}
122 map.permission :import_issues, {:imports => [:new, :create, :settings, :mapping, :run, :show]}
122 map.permission :import_issues, {:imports => [:new, :create, :settings, :mapping, :run, :show]}
123 end
123 end
124
124
125 map.project_module :time_tracking do |map|
125 map.project_module :time_tracking do |map|
126 map.permission :log_time, {:timelog => [:new, :create]}, :require => :loggedin
126 map.permission :log_time, {:timelog => [:new, :create]}, :require => :loggedin
127 map.permission :view_time_entries, {:timelog => [:index, :report, :show]}, :read => true
127 map.permission :view_time_entries, {:timelog => [:index, :report, :show]}, :read => true
128 map.permission :edit_time_entries, {:timelog => [:edit, :update, :destroy, :bulk_edit, :bulk_update]}, :require => :member
128 map.permission :edit_time_entries, {:timelog => [:edit, :update, :destroy, :bulk_edit, :bulk_update]}, :require => :member
129 map.permission :edit_own_time_entries, {:timelog => [:edit, :update, :destroy,:bulk_edit, :bulk_update]}, :require => :loggedin
129 map.permission :edit_own_time_entries, {:timelog => [:edit, :update, :destroy,:bulk_edit, :bulk_update]}, :require => :loggedin
130 map.permission :manage_project_activities, {:project_enumerations => [:update, :destroy]}, :require => :member
130 map.permission :manage_project_activities, {:project_enumerations => [:update, :destroy]}, :require => :member
131 end
131 end
132
132
133 map.project_module :news do |map|
133 map.project_module :news do |map|
134 map.permission :manage_news, {:news => [:new, :create, :edit, :update, :destroy], :comments => [:destroy], :attachments => :upload}, :require => :member
134 map.permission :manage_news, {:news => [:new, :create, :edit, :update, :destroy], :comments => [:destroy], :attachments => :upload}, :require => :member
135 map.permission :view_news, {:news => [:index, :show]}, :public => true, :read => true
135 map.permission :view_news, {:news => [:index, :show]}, :public => true, :read => true
136 map.permission :comment_news, {:comments => :create}
136 map.permission :comment_news, {:comments => :create}
137 end
137 end
138
138
139 map.project_module :documents do |map|
139 map.project_module :documents do |map|
140 map.permission :add_documents, {:documents => [:new, :create, :add_attachment], :attachments => :upload}, :require => :loggedin
140 map.permission :add_documents, {:documents => [:new, :create, :add_attachment], :attachments => :upload}, :require => :loggedin
141 map.permission :edit_documents, {:documents => [:edit, :update, :add_attachment], :attachments => :upload}, :require => :loggedin
141 map.permission :edit_documents, {:documents => [:edit, :update, :add_attachment], :attachments => :upload}, :require => :loggedin
142 map.permission :delete_documents, {:documents => [:destroy]}, :require => :loggedin
142 map.permission :delete_documents, {:documents => [:destroy]}, :require => :loggedin
143 map.permission :view_documents, {:documents => [:index, :show, :download]}, :read => true
143 map.permission :view_documents, {:documents => [:index, :show, :download]}, :read => true
144 end
144 end
145
145
146 map.project_module :files do |map|
146 map.project_module :files do |map|
147 map.permission :manage_files, {:files => [:new, :create], :attachments => :upload}, :require => :loggedin
147 map.permission :manage_files, {:files => [:new, :create], :attachments => :upload}, :require => :loggedin
148 map.permission :view_files, {:files => :index, :versions => :download}, :read => true
148 map.permission :view_files, {:files => :index, :versions => :download}, :read => true
149 end
149 end
150
150
151 map.project_module :wiki do |map|
151 map.project_module :wiki do |map|
152 map.permission :manage_wiki, {:wikis => [:edit, :destroy]}, :require => :member
152 map.permission :manage_wiki, {:wikis => [:edit, :destroy]}, :require => :member
153 map.permission :rename_wiki_pages, {:wiki => :rename}, :require => :member
153 map.permission :rename_wiki_pages, {:wiki => :rename}, :require => :member
154 map.permission :delete_wiki_pages, {:wiki => [:destroy, :destroy_version]}, :require => :member
154 map.permission :delete_wiki_pages, {:wiki => [:destroy, :destroy_version]}, :require => :member
155 map.permission :view_wiki_pages, {:wiki => [:index, :show, :special, :date_index]}, :read => true
155 map.permission :view_wiki_pages, {:wiki => [:index, :show, :special, :date_index]}, :read => true
156 map.permission :export_wiki_pages, {:wiki => [:export]}, :read => true
156 map.permission :export_wiki_pages, {:wiki => [:export]}, :read => true
157 map.permission :view_wiki_edits, {:wiki => [:history, :diff, :annotate]}, :read => true
157 map.permission :view_wiki_edits, {:wiki => [:history, :diff, :annotate]}, :read => true
158 map.permission :edit_wiki_pages, :wiki => [:edit, :update, :preview, :add_attachment], :attachments => :upload
158 map.permission :edit_wiki_pages, :wiki => [:edit, :update, :preview, :add_attachment], :attachments => :upload
159 map.permission :delete_wiki_pages_attachments, {}
159 map.permission :delete_wiki_pages_attachments, {}
160 map.permission :protect_wiki_pages, {:wiki => :protect}, :require => :member
160 map.permission :protect_wiki_pages, {:wiki => :protect}, :require => :member
161 end
161 end
162
162
163 map.project_module :repository do |map|
163 map.project_module :repository do |map|
164 map.permission :manage_repository, {:repositories => [:new, :create, :edit, :update, :committers, :destroy]}, :require => :member
164 map.permission :manage_repository, {:repositories => [:new, :create, :edit, :update, :committers, :destroy]}, :require => :member
165 map.permission :browse_repository, {:repositories => [:show, :browse, :entry, :raw, :annotate, :changes, :diff, :stats, :graph]}, :read => true
165 map.permission :browse_repository, {:repositories => [:show, :browse, :entry, :raw, :annotate, :changes, :diff, :stats, :graph]}, :read => true
166 map.permission :view_changesets, {:repositories => [:show, :revisions, :revision]}, :read => true
166 map.permission :view_changesets, {:repositories => [:show, :revisions, :revision]}, :read => true
167 map.permission :commit_access, {}
167 map.permission :commit_access, {}
168 map.permission :manage_related_issues, {:repositories => [:add_related_issue, :remove_related_issue]}
168 map.permission :manage_related_issues, {:repositories => [:add_related_issue, :remove_related_issue]}
169 end
169 end
170
170
171 map.project_module :boards do |map|
171 map.project_module :boards do |map|
172 map.permission :manage_boards, {:boards => [:new, :create, :edit, :update, :destroy]}, :require => :member
172 map.permission :manage_boards, {:boards => [:new, :create, :edit, :update, :destroy]}, :require => :member
173 map.permission :view_messages, {:boards => [:index, :show], :messages => [:show]}, :public => true, :read => true
173 map.permission :view_messages, {:boards => [:index, :show], :messages => [:show]}, :public => true, :read => true
174 map.permission :add_messages, {:messages => [:new, :reply, :quote], :attachments => :upload}
174 map.permission :add_messages, {:messages => [:new, :reply, :quote], :attachments => :upload}
175 map.permission :edit_messages, {:messages => :edit, :attachments => :upload}, :require => :member
175 map.permission :edit_messages, {:messages => :edit, :attachments => :upload}, :require => :member
176 map.permission :edit_own_messages, {:messages => :edit, :attachments => :upload}, :require => :loggedin
176 map.permission :edit_own_messages, {:messages => :edit, :attachments => :upload}, :require => :loggedin
177 map.permission :delete_messages, {:messages => :destroy}, :require => :member
177 map.permission :delete_messages, {:messages => :destroy}, :require => :member
178 map.permission :delete_own_messages, {:messages => :destroy}, :require => :loggedin
178 map.permission :delete_own_messages, {:messages => :destroy}, :require => :loggedin
179 end
179 end
180
180
181 map.project_module :calendar do |map|
181 map.project_module :calendar do |map|
182 map.permission :view_calendar, {:calendars => [:show, :update]}, :read => true
182 map.permission :view_calendar, {:calendars => [:show, :update]}, :read => true
183 end
183 end
184
184
185 map.project_module :gantt do |map|
185 map.project_module :gantt do |map|
186 map.permission :view_gantt, {:gantts => [:show, :update]}, :read => true
186 map.permission :view_gantt, {:gantts => [:show, :update]}, :read => true
187 end
187 end
188 end
188 end
189
189
190 Redmine::MenuManager.map :top_menu do |menu|
190 Redmine::MenuManager.map :top_menu do |menu|
191 menu.push :home, :home_path
191 menu.push :home, :home_path
192 menu.push :my_page, { :controller => 'my', :action => 'page' }, :if => Proc.new { User.current.logged? }
192 menu.push :my_page, { :controller => 'my', :action => 'page' }, :if => Proc.new { User.current.logged? }
193 menu.push :projects, { :controller => 'projects', :action => 'index' }, :caption => :label_project_plural
193 menu.push :projects, { :controller => 'projects', :action => 'index' }, :caption => :label_project_plural
194 menu.push :administration, { :controller => 'admin', :action => 'index' }, :if => Proc.new { User.current.admin? }, :last => true
194 menu.push :administration, { :controller => 'admin', :action => 'index' }, :if => Proc.new { User.current.admin? }, :last => true
195 menu.push :help, Redmine::Info.help_url, :last => true
195 menu.push :help, Redmine::Info.help_url, :last => true
196 end
196 end
197
197
198 Redmine::MenuManager.map :account_menu do |menu|
198 Redmine::MenuManager.map :account_menu do |menu|
199 menu.push :login, :signin_path, :if => Proc.new { !User.current.logged? }
199 menu.push :login, :signin_path, :if => Proc.new { !User.current.logged? }
200 menu.push :register, :register_path, :if => Proc.new { !User.current.logged? && Setting.self_registration? }
200 menu.push :register, :register_path, :if => Proc.new { !User.current.logged? && Setting.self_registration? }
201 menu.push :my_account, { :controller => 'my', :action => 'account' }, :if => Proc.new { User.current.logged? }
201 menu.push :my_account, { :controller => 'my', :action => 'account' }, :if => Proc.new { User.current.logged? }
202 menu.push :logout, :signout_path, :html => {:method => 'post'}, :if => Proc.new { User.current.logged? }
202 menu.push :logout, :signout_path, :html => {:method => 'post'}, :if => Proc.new { User.current.logged? }
203 end
203 end
204
204
205 Redmine::MenuManager.map :application_menu do |menu|
205 Redmine::MenuManager.map :application_menu do |menu|
206 # Empty
206 # Empty
207 end
207 end
208
208
209 Redmine::MenuManager.map :admin_menu do |menu|
209 Redmine::MenuManager.map :admin_menu do |menu|
210 menu.push :projects, {:controller => 'admin', :action => 'projects'}, :caption => :label_project_plural
210 menu.push :projects, {:controller => 'admin', :action => 'projects'}, :caption => :label_project_plural
211 menu.push :users, {:controller => 'users'}, :caption => :label_user_plural
211 menu.push :users, {:controller => 'users'}, :caption => :label_user_plural
212 menu.push :groups, {:controller => 'groups'}, :caption => :label_group_plural
212 menu.push :groups, {:controller => 'groups'}, :caption => :label_group_plural
213 menu.push :roles, {:controller => 'roles'}, :caption => :label_role_and_permissions
213 menu.push :roles, {:controller => 'roles'}, :caption => :label_role_and_permissions
214 menu.push :trackers, {:controller => 'trackers'}, :caption => :label_tracker_plural
214 menu.push :trackers, {:controller => 'trackers'}, :caption => :label_tracker_plural
215 menu.push :issue_statuses, {:controller => 'issue_statuses'}, :caption => :label_issue_status_plural,
215 menu.push :issue_statuses, {:controller => 'issue_statuses'}, :caption => :label_issue_status_plural,
216 :html => {:class => 'issue_statuses'}
216 :html => {:class => 'issue_statuses'}
217 menu.push :workflows, {:controller => 'workflows', :action => 'edit'}, :caption => :label_workflow
217 menu.push :workflows, {:controller => 'workflows', :action => 'edit'}, :caption => :label_workflow
218 menu.push :custom_fields, {:controller => 'custom_fields'}, :caption => :label_custom_field_plural,
218 menu.push :custom_fields, {:controller => 'custom_fields'}, :caption => :label_custom_field_plural,
219 :html => {:class => 'custom_fields'}
219 :html => {:class => 'custom_fields'}
220 menu.push :enumerations, {:controller => 'enumerations'}
220 menu.push :enumerations, {:controller => 'enumerations'}
221 menu.push :settings, {:controller => 'settings'}
221 menu.push :settings, {:controller => 'settings'}
222 menu.push :ldap_authentication, {:controller => 'auth_sources', :action => 'index'},
222 menu.push :ldap_authentication, {:controller => 'auth_sources', :action => 'index'},
223 :html => {:class => 'server_authentication'}
223 :html => {:class => 'server_authentication'}
224 menu.push :plugins, {:controller => 'admin', :action => 'plugins'}, :last => true
224 menu.push :plugins, {:controller => 'admin', :action => 'plugins'}, :last => true
225 menu.push :info, {:controller => 'admin', :action => 'info'}, :caption => :label_information_plural, :last => true
225 menu.push :info, {:controller => 'admin', :action => 'info'}, :caption => :label_information_plural, :last => true
226 end
226 end
227
227
228 Redmine::MenuManager.map :project_menu do |menu|
228 Redmine::MenuManager.map :project_menu do |menu|
229 menu.push :overview, { :controller => 'projects', :action => 'show' }
229 menu.push :overview, { :controller => 'projects', :action => 'show' }
230 menu.push :activity, { :controller => 'activities', :action => 'index' }
230 menu.push :activity, { :controller => 'activities', :action => 'index' }
231 menu.push :roadmap, { :controller => 'versions', :action => 'index' }, :param => :project_id,
231 menu.push :roadmap, { :controller => 'versions', :action => 'index' }, :param => :project_id,
232 :if => Proc.new { |p| p.shared_versions.any? }
232 :if => Proc.new { |p| p.shared_versions.any? }
233 menu.push :issues, { :controller => 'issues', :action => 'index' }, :param => :project_id, :caption => :label_issue_plural
233 menu.push :issues, { :controller => 'issues', :action => 'index' }, :param => :project_id, :caption => :label_issue_plural
234 menu.push :new_issue, { :controller => 'issues', :action => 'new', :copy_from => nil }, :param => :project_id, :caption => :label_issue_new,
235 :html => { :accesskey => Redmine::AccessKeys.key_for(:new_issue) },
236 :if => Proc.new { |p| p.trackers.any? },
237 :permission => :add_issues
238 menu.push :gantt, { :controller => 'gantts', :action => 'show' }, :param => :project_id, :caption => :label_gantt
234 menu.push :gantt, { :controller => 'gantts', :action => 'show' }, :param => :project_id, :caption => :label_gantt
239 menu.push :calendar, { :controller => 'calendars', :action => 'show' }, :param => :project_id, :caption => :label_calendar
235 menu.push :calendar, { :controller => 'calendars', :action => 'show' }, :param => :project_id, :caption => :label_calendar
240 menu.push :news, { :controller => 'news', :action => 'index' }, :param => :project_id, :caption => :label_news_plural
236 menu.push :news, { :controller => 'news', :action => 'index' }, :param => :project_id, :caption => :label_news_plural
241 menu.push :documents, { :controller => 'documents', :action => 'index' }, :param => :project_id, :caption => :label_document_plural
237 menu.push :documents, { :controller => 'documents', :action => 'index' }, :param => :project_id, :caption => :label_document_plural
242 menu.push :wiki, { :controller => 'wiki', :action => 'show', :id => nil }, :param => :project_id,
238 menu.push :wiki, { :controller => 'wiki', :action => 'show', :id => nil }, :param => :project_id,
243 :if => Proc.new { |p| p.wiki && !p.wiki.new_record? }
239 :if => Proc.new { |p| p.wiki && !p.wiki.new_record? }
244 menu.push :boards, { :controller => 'boards', :action => 'index', :id => nil }, :param => :project_id,
240 menu.push :boards, { :controller => 'boards', :action => 'index', :id => nil }, :param => :project_id,
245 :if => Proc.new { |p| p.boards.any? }, :caption => :label_board_plural
241 :if => Proc.new { |p| p.boards.any? }, :caption => :label_board_plural
246 menu.push :files, { :controller => 'files', :action => 'index' }, :caption => :label_file_plural, :param => :project_id
242 menu.push :files, { :controller => 'files', :action => 'index' }, :caption => :label_file_plural, :param => :project_id
247 menu.push :repository, { :controller => 'repositories', :action => 'show', :repository_id => nil, :path => nil, :rev => nil },
243 menu.push :repository, { :controller => 'repositories', :action => 'show', :repository_id => nil, :path => nil, :rev => nil },
248 :if => Proc.new { |p| p.repository && !p.repository.new_record? }
244 :if => Proc.new { |p| p.repository && !p.repository.new_record? }
249 menu.push :settings, { :controller => 'projects', :action => 'settings' }, :last => true
245 menu.push :settings, { :controller => 'projects', :action => 'settings' }, :last => true
250 end
246 end
251
247
252 Redmine::Activity.map do |activity|
248 Redmine::Activity.map do |activity|
253 activity.register :issues, :class_name => %w(Issue Journal)
249 activity.register :issues, :class_name => %w(Issue Journal)
254 activity.register :changesets
250 activity.register :changesets
255 activity.register :news
251 activity.register :news
256 activity.register :documents, :class_name => %w(Document Attachment)
252 activity.register :documents, :class_name => %w(Document Attachment)
257 activity.register :files, :class_name => 'Attachment'
253 activity.register :files, :class_name => 'Attachment'
258 activity.register :wiki_edits, :class_name => 'WikiContent::Version', :default => false
254 activity.register :wiki_edits, :class_name => 'WikiContent::Version', :default => false
259 activity.register :messages, :default => false
255 activity.register :messages, :default => false
260 activity.register :time_entries, :default => false
256 activity.register :time_entries, :default => false
261 end
257 end
262
258
263 Redmine::Search.map do |search|
259 Redmine::Search.map do |search|
264 search.register :issues
260 search.register :issues
265 search.register :news
261 search.register :news
266 search.register :documents
262 search.register :documents
267 search.register :changesets
263 search.register :changesets
268 search.register :wiki_pages
264 search.register :wiki_pages
269 search.register :messages
265 search.register :messages
270 search.register :projects
266 search.register :projects
271 end
267 end
272
268
273 Redmine::WikiFormatting.map do |format|
269 Redmine::WikiFormatting.map do |format|
274 format.register :textile
270 format.register :textile
275 format.register :markdown if Object.const_defined?(:Redcarpet)
271 format.register :markdown if Object.const_defined?(:Redcarpet)
276 end
272 end
277
273
278 ActionView::Template.register_template_handler :rsb, Redmine::Views::ApiTemplateHandler
274 ActionView::Template.register_template_handler :rsb, Redmine::Views::ApiTemplateHandler
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,718 +1,694
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19
19
20 class ProjectsControllerTest < ActionController::TestCase
20 class ProjectsControllerTest < ActionController::TestCase
21 fixtures :projects, :versions, :users, :email_addresses, :roles, :members,
21 fixtures :projects, :versions, :users, :email_addresses, :roles, :members,
22 :member_roles, :issues, :journals, :journal_details,
22 :member_roles, :issues, :journals, :journal_details,
23 :trackers, :projects_trackers, :issue_statuses,
23 :trackers, :projects_trackers, :issue_statuses,
24 :enabled_modules, :enumerations, :boards, :messages,
24 :enabled_modules, :enumerations, :boards, :messages,
25 :attachments, :custom_fields, :custom_values, :time_entries,
25 :attachments, :custom_fields, :custom_values, :time_entries,
26 :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions
26 :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions
27
27
28 def setup
28 def setup
29 @request.session[:user_id] = nil
29 @request.session[:user_id] = nil
30 Setting.default_language = 'en'
30 Setting.default_language = 'en'
31 end
31 end
32
32
33 def test_index_by_anonymous_should_not_show_private_projects
33 def test_index_by_anonymous_should_not_show_private_projects
34 get :index
34 get :index
35 assert_response :success
35 assert_response :success
36 assert_template 'index'
36 assert_template 'index'
37 projects = assigns(:projects)
37 projects = assigns(:projects)
38 assert_not_nil projects
38 assert_not_nil projects
39 assert projects.all?(&:is_public?)
39 assert projects.all?(&:is_public?)
40
40
41 assert_select 'ul' do
41 assert_select 'ul' do
42 assert_select 'li' do
42 assert_select 'li' do
43 assert_select 'a', :text => 'eCookbook'
43 assert_select 'a', :text => 'eCookbook'
44 assert_select 'ul' do
44 assert_select 'ul' do
45 assert_select 'a', :text => 'Child of private child'
45 assert_select 'a', :text => 'Child of private child'
46 end
46 end
47 end
47 end
48 end
48 end
49 assert_select 'a', :text => /Private child of eCookbook/, :count => 0
49 assert_select 'a', :text => /Private child of eCookbook/, :count => 0
50 end
50 end
51
51
52 def test_index_atom
52 def test_index_atom
53 get :index, :format => 'atom'
53 get :index, :format => 'atom'
54 assert_response :success
54 assert_response :success
55 assert_template 'common/feed'
55 assert_template 'common/feed'
56 assert_select 'feed>title', :text => 'Redmine: Latest projects'
56 assert_select 'feed>title', :text => 'Redmine: Latest projects'
57 assert_select 'feed>entry', :count => Project.visible(User.current).count
57 assert_select 'feed>entry', :count => Project.visible(User.current).count
58 end
58 end
59
59
60 test "#index by non-admin user with view_time_entries permission should show overall spent time link" do
60 test "#index by non-admin user with view_time_entries permission should show overall spent time link" do
61 @request.session[:user_id] = 3
61 @request.session[:user_id] = 3
62 get :index
62 get :index
63 assert_template 'index'
63 assert_template 'index'
64 assert_select 'a[href=?]', '/time_entries'
64 assert_select 'a[href=?]', '/time_entries'
65 end
65 end
66
66
67 test "#index by non-admin user without view_time_entries permission should not show overall spent time link" do
67 test "#index by non-admin user without view_time_entries permission should not show overall spent time link" do
68 Role.find(2).remove_permission! :view_time_entries
68 Role.find(2).remove_permission! :view_time_entries
69 Role.non_member.remove_permission! :view_time_entries
69 Role.non_member.remove_permission! :view_time_entries
70 Role.anonymous.remove_permission! :view_time_entries
70 Role.anonymous.remove_permission! :view_time_entries
71 @request.session[:user_id] = 3
71 @request.session[:user_id] = 3
72
72
73 get :index
73 get :index
74 assert_template 'index'
74 assert_template 'index'
75 assert_select 'a[href=?]', '/time_entries', 0
75 assert_select 'a[href=?]', '/time_entries', 0
76 end
76 end
77
77
78 test "#index by non-admin user with permission should show add project link" do
78 test "#index by non-admin user with permission should show add project link" do
79 Role.find(1).add_permission! :add_project
79 Role.find(1).add_permission! :add_project
80 @request.session[:user_id] = 2
80 @request.session[:user_id] = 2
81 get :index
81 get :index
82 assert_template 'index'
82 assert_template 'index'
83 assert_select 'a[href=?]', '/projects/new'
83 assert_select 'a[href=?]', '/projects/new'
84 end
84 end
85
85
86 test "#new by admin user should accept get" do
86 test "#new by admin user should accept get" do
87 @request.session[:user_id] = 1
87 @request.session[:user_id] = 1
88
88
89 get :new
89 get :new
90 assert_response :success
90 assert_response :success
91 assert_template 'new'
91 assert_template 'new'
92 end
92 end
93
93
94 test "#new by non-admin user with add_project permission should accept get" do
94 test "#new by non-admin user with add_project permission should accept get" do
95 Role.non_member.add_permission! :add_project
95 Role.non_member.add_permission! :add_project
96 @request.session[:user_id] = 9
96 @request.session[:user_id] = 9
97
97
98 get :new
98 get :new
99 assert_response :success
99 assert_response :success
100 assert_template 'new'
100 assert_template 'new'
101 assert_select 'select[name=?]', 'project[parent_id]', 0
101 assert_select 'select[name=?]', 'project[parent_id]', 0
102 end
102 end
103
103
104 test "#new by non-admin user with add_subprojects permission should accept get" do
104 test "#new by non-admin user with add_subprojects permission should accept get" do
105 Role.find(1).remove_permission! :add_project
105 Role.find(1).remove_permission! :add_project
106 Role.find(1).add_permission! :add_subprojects
106 Role.find(1).add_permission! :add_subprojects
107 @request.session[:user_id] = 2
107 @request.session[:user_id] = 2
108
108
109 get :new, :parent_id => 'ecookbook'
109 get :new, :parent_id => 'ecookbook'
110 assert_response :success
110 assert_response :success
111 assert_template 'new'
111 assert_template 'new'
112
112
113 assert_select 'select[name=?]', 'project[parent_id]' do
113 assert_select 'select[name=?]', 'project[parent_id]' do
114 # parent project selected
114 # parent project selected
115 assert_select 'option[value="1"][selected=selected]'
115 assert_select 'option[value="1"][selected=selected]'
116 # no empty value
116 # no empty value
117 assert_select 'option[value=""]', 0
117 assert_select 'option[value=""]', 0
118 end
118 end
119 end
119 end
120
120
121 def test_new_should_not_display_invalid_search_link
121 def test_new_should_not_display_invalid_search_link
122 @request.session[:user_id] = 1
122 @request.session[:user_id] = 1
123
123
124 get :new
124 get :new
125 assert_response :success
125 assert_response :success
126 assert_select '#quick-search form[action=?]', '/search'
126 assert_select '#quick-search form[action=?]', '/search'
127 assert_select '#quick-search a[href=?]', '/search'
127 assert_select '#quick-search a[href=?]', '/search'
128 end
128 end
129
129
130 test "#create by admin user should create a new project" do
130 test "#create by admin user should create a new project" do
131 @request.session[:user_id] = 1
131 @request.session[:user_id] = 1
132
132
133 post :create,
133 post :create,
134 :project => {
134 :project => {
135 :name => "blog",
135 :name => "blog",
136 :description => "weblog",
136 :description => "weblog",
137 :homepage => 'http://weblog',
137 :homepage => 'http://weblog',
138 :identifier => "blog",
138 :identifier => "blog",
139 :is_public => 1,
139 :is_public => 1,
140 :custom_field_values => { '3' => 'Beta' },
140 :custom_field_values => { '3' => 'Beta' },
141 :tracker_ids => ['1', '3'],
141 :tracker_ids => ['1', '3'],
142 # an issue custom field that is not for all project
142 # an issue custom field that is not for all project
143 :issue_custom_field_ids => ['9'],
143 :issue_custom_field_ids => ['9'],
144 :enabled_module_names => ['issue_tracking', 'news', 'repository']
144 :enabled_module_names => ['issue_tracking', 'news', 'repository']
145 }
145 }
146 assert_redirected_to '/projects/blog/settings'
146 assert_redirected_to '/projects/blog/settings'
147
147
148 project = Project.find_by_name('blog')
148 project = Project.find_by_name('blog')
149 assert_kind_of Project, project
149 assert_kind_of Project, project
150 assert project.active?
150 assert project.active?
151 assert_equal 'weblog', project.description
151 assert_equal 'weblog', project.description
152 assert_equal 'http://weblog', project.homepage
152 assert_equal 'http://weblog', project.homepage
153 assert_equal true, project.is_public?
153 assert_equal true, project.is_public?
154 assert_nil project.parent
154 assert_nil project.parent
155 assert_equal 'Beta', project.custom_value_for(3).value
155 assert_equal 'Beta', project.custom_value_for(3).value
156 assert_equal [1, 3], project.trackers.map(&:id).sort
156 assert_equal [1, 3], project.trackers.map(&:id).sort
157 assert_equal ['issue_tracking', 'news', 'repository'], project.enabled_module_names.sort
157 assert_equal ['issue_tracking', 'news', 'repository'], project.enabled_module_names.sort
158 assert project.issue_custom_fields.include?(IssueCustomField.find(9))
158 assert project.issue_custom_fields.include?(IssueCustomField.find(9))
159 end
159 end
160
160
161 test "#create by admin user should create a new subproject" do
161 test "#create by admin user should create a new subproject" do
162 @request.session[:user_id] = 1
162 @request.session[:user_id] = 1
163
163
164 assert_difference 'Project.count' do
164 assert_difference 'Project.count' do
165 post :create, :project => { :name => "blog",
165 post :create, :project => { :name => "blog",
166 :description => "weblog",
166 :description => "weblog",
167 :identifier => "blog",
167 :identifier => "blog",
168 :is_public => 1,
168 :is_public => 1,
169 :custom_field_values => { '3' => 'Beta' },
169 :custom_field_values => { '3' => 'Beta' },
170 :parent_id => 1
170 :parent_id => 1
171 }
171 }
172 assert_redirected_to '/projects/blog/settings'
172 assert_redirected_to '/projects/blog/settings'
173 end
173 end
174
174
175 project = Project.find_by_name('blog')
175 project = Project.find_by_name('blog')
176 assert_kind_of Project, project
176 assert_kind_of Project, project
177 assert_equal Project.find(1), project.parent
177 assert_equal Project.find(1), project.parent
178 end
178 end
179
179
180 test "#create by admin user should continue" do
180 test "#create by admin user should continue" do
181 @request.session[:user_id] = 1
181 @request.session[:user_id] = 1
182
182
183 assert_difference 'Project.count' do
183 assert_difference 'Project.count' do
184 post :create, :project => {:name => "blog", :identifier => "blog"}, :continue => 'Create and continue'
184 post :create, :project => {:name => "blog", :identifier => "blog"}, :continue => 'Create and continue'
185 end
185 end
186 assert_redirected_to '/projects/new'
186 assert_redirected_to '/projects/new'
187 end
187 end
188
188
189 test "#create by non-admin user with add_project permission should create a new project" do
189 test "#create by non-admin user with add_project permission should create a new project" do
190 Role.non_member.add_permission! :add_project
190 Role.non_member.add_permission! :add_project
191 @request.session[:user_id] = 9
191 @request.session[:user_id] = 9
192
192
193 post :create, :project => { :name => "blog",
193 post :create, :project => { :name => "blog",
194 :description => "weblog",
194 :description => "weblog",
195 :identifier => "blog",
195 :identifier => "blog",
196 :is_public => 1,
196 :is_public => 1,
197 :custom_field_values => { '3' => 'Beta' },
197 :custom_field_values => { '3' => 'Beta' },
198 :tracker_ids => ['1', '3'],
198 :tracker_ids => ['1', '3'],
199 :enabled_module_names => ['issue_tracking', 'news', 'repository']
199 :enabled_module_names => ['issue_tracking', 'news', 'repository']
200 }
200 }
201
201
202 assert_redirected_to '/projects/blog/settings'
202 assert_redirected_to '/projects/blog/settings'
203
203
204 project = Project.find_by_name('blog')
204 project = Project.find_by_name('blog')
205 assert_kind_of Project, project
205 assert_kind_of Project, project
206 assert_equal 'weblog', project.description
206 assert_equal 'weblog', project.description
207 assert_equal true, project.is_public?
207 assert_equal true, project.is_public?
208 assert_equal [1, 3], project.trackers.map(&:id).sort
208 assert_equal [1, 3], project.trackers.map(&:id).sort
209 assert_equal ['issue_tracking', 'news', 'repository'], project.enabled_module_names.sort
209 assert_equal ['issue_tracking', 'news', 'repository'], project.enabled_module_names.sort
210
210
211 # User should be added as a project member
211 # User should be added as a project member
212 assert User.find(9).member_of?(project)
212 assert User.find(9).member_of?(project)
213 assert_equal 1, project.members.size
213 assert_equal 1, project.members.size
214 end
214 end
215
215
216 test "#create by non-admin user with add_project permission should fail with parent_id" do
216 test "#create by non-admin user with add_project permission should fail with parent_id" do
217 Role.non_member.add_permission! :add_project
217 Role.non_member.add_permission! :add_project
218 @request.session[:user_id] = 9
218 @request.session[:user_id] = 9
219
219
220 assert_no_difference 'Project.count' do
220 assert_no_difference 'Project.count' do
221 post :create, :project => { :name => "blog",
221 post :create, :project => { :name => "blog",
222 :description => "weblog",
222 :description => "weblog",
223 :identifier => "blog",
223 :identifier => "blog",
224 :is_public => 1,
224 :is_public => 1,
225 :custom_field_values => { '3' => 'Beta' },
225 :custom_field_values => { '3' => 'Beta' },
226 :parent_id => 1
226 :parent_id => 1
227 }
227 }
228 end
228 end
229 assert_response :success
229 assert_response :success
230 project = assigns(:project)
230 project = assigns(:project)
231 assert_kind_of Project, project
231 assert_kind_of Project, project
232 assert_not_equal [], project.errors[:parent_id]
232 assert_not_equal [], project.errors[:parent_id]
233 end
233 end
234
234
235 test "#create by non-admin user with add_subprojects permission should create a project with a parent_id" do
235 test "#create by non-admin user with add_subprojects permission should create a project with a parent_id" do
236 Role.find(1).remove_permission! :add_project
236 Role.find(1).remove_permission! :add_project
237 Role.find(1).add_permission! :add_subprojects
237 Role.find(1).add_permission! :add_subprojects
238 @request.session[:user_id] = 2
238 @request.session[:user_id] = 2
239
239
240 post :create, :project => { :name => "blog",
240 post :create, :project => { :name => "blog",
241 :description => "weblog",
241 :description => "weblog",
242 :identifier => "blog",
242 :identifier => "blog",
243 :is_public => 1,
243 :is_public => 1,
244 :custom_field_values => { '3' => 'Beta' },
244 :custom_field_values => { '3' => 'Beta' },
245 :parent_id => 1
245 :parent_id => 1
246 }
246 }
247 assert_redirected_to '/projects/blog/settings'
247 assert_redirected_to '/projects/blog/settings'
248 project = Project.find_by_name('blog')
248 project = Project.find_by_name('blog')
249 end
249 end
250
250
251 test "#create by non-admin user with add_subprojects permission should fail without parent_id" do
251 test "#create by non-admin user with add_subprojects permission should fail without parent_id" do
252 Role.find(1).remove_permission! :add_project
252 Role.find(1).remove_permission! :add_project
253 Role.find(1).add_permission! :add_subprojects
253 Role.find(1).add_permission! :add_subprojects
254 @request.session[:user_id] = 2
254 @request.session[:user_id] = 2
255
255
256 assert_no_difference 'Project.count' do
256 assert_no_difference 'Project.count' do
257 post :create, :project => { :name => "blog",
257 post :create, :project => { :name => "blog",
258 :description => "weblog",
258 :description => "weblog",
259 :identifier => "blog",
259 :identifier => "blog",
260 :is_public => 1,
260 :is_public => 1,
261 :custom_field_values => { '3' => 'Beta' }
261 :custom_field_values => { '3' => 'Beta' }
262 }
262 }
263 end
263 end
264 assert_response :success
264 assert_response :success
265 project = assigns(:project)
265 project = assigns(:project)
266 assert_kind_of Project, project
266 assert_kind_of Project, project
267 assert_not_equal [], project.errors[:parent_id]
267 assert_not_equal [], project.errors[:parent_id]
268 end
268 end
269
269
270 test "#create by non-admin user with add_subprojects permission should fail with unauthorized parent_id" do
270 test "#create by non-admin user with add_subprojects permission should fail with unauthorized parent_id" do
271 Role.find(1).remove_permission! :add_project
271 Role.find(1).remove_permission! :add_project
272 Role.find(1).add_permission! :add_subprojects
272 Role.find(1).add_permission! :add_subprojects
273 @request.session[:user_id] = 2
273 @request.session[:user_id] = 2
274
274
275 assert !User.find(2).member_of?(Project.find(6))
275 assert !User.find(2).member_of?(Project.find(6))
276 assert_no_difference 'Project.count' do
276 assert_no_difference 'Project.count' do
277 post :create, :project => { :name => "blog",
277 post :create, :project => { :name => "blog",
278 :description => "weblog",
278 :description => "weblog",
279 :identifier => "blog",
279 :identifier => "blog",
280 :is_public => 1,
280 :is_public => 1,
281 :custom_field_values => { '3' => 'Beta' },
281 :custom_field_values => { '3' => 'Beta' },
282 :parent_id => 6
282 :parent_id => 6
283 }
283 }
284 end
284 end
285 assert_response :success
285 assert_response :success
286 project = assigns(:project)
286 project = assigns(:project)
287 assert_kind_of Project, project
287 assert_kind_of Project, project
288 assert_not_equal [], project.errors[:parent_id]
288 assert_not_equal [], project.errors[:parent_id]
289 end
289 end
290
290
291 def test_create_subproject_with_inherit_members_should_inherit_members
291 def test_create_subproject_with_inherit_members_should_inherit_members
292 Role.find_by_name('Manager').add_permission! :add_subprojects
292 Role.find_by_name('Manager').add_permission! :add_subprojects
293 parent = Project.find(1)
293 parent = Project.find(1)
294 @request.session[:user_id] = 2
294 @request.session[:user_id] = 2
295
295
296 assert_difference 'Project.count' do
296 assert_difference 'Project.count' do
297 post :create, :project => {
297 post :create, :project => {
298 :name => 'inherited', :identifier => 'inherited', :parent_id => parent.id, :inherit_members => '1'
298 :name => 'inherited', :identifier => 'inherited', :parent_id => parent.id, :inherit_members => '1'
299 }
299 }
300 assert_response 302
300 assert_response 302
301 end
301 end
302
302
303 project = Project.order('id desc').first
303 project = Project.order('id desc').first
304 assert_equal 'inherited', project.name
304 assert_equal 'inherited', project.name
305 assert_equal parent, project.parent
305 assert_equal parent, project.parent
306 assert project.memberships.count > 0
306 assert project.memberships.count > 0
307 assert_equal parent.memberships.count, project.memberships.count
307 assert_equal parent.memberships.count, project.memberships.count
308 end
308 end
309
309
310 def test_create_should_preserve_modules_on_validation_failure
310 def test_create_should_preserve_modules_on_validation_failure
311 with_settings :default_projects_modules => ['issue_tracking', 'repository'] do
311 with_settings :default_projects_modules => ['issue_tracking', 'repository'] do
312 @request.session[:user_id] = 1
312 @request.session[:user_id] = 1
313 assert_no_difference 'Project.count' do
313 assert_no_difference 'Project.count' do
314 post :create, :project => {
314 post :create, :project => {
315 :name => "blog",
315 :name => "blog",
316 :identifier => "",
316 :identifier => "",
317 :enabled_module_names => %w(issue_tracking news)
317 :enabled_module_names => %w(issue_tracking news)
318 }
318 }
319 end
319 end
320 assert_response :success
320 assert_response :success
321 project = assigns(:project)
321 project = assigns(:project)
322 assert_equal %w(issue_tracking news), project.enabled_module_names.sort
322 assert_equal %w(issue_tracking news), project.enabled_module_names.sort
323 end
323 end
324 end
324 end
325
325
326 def test_show_by_id
326 def test_show_by_id
327 get :show, :id => 1
327 get :show, :id => 1
328 assert_response :success
328 assert_response :success
329 assert_template 'show'
329 assert_template 'show'
330 assert_not_nil assigns(:project)
330 assert_not_nil assigns(:project)
331 end
331 end
332
332
333 def test_show_by_identifier
333 def test_show_by_identifier
334 get :show, :id => 'ecookbook'
334 get :show, :id => 'ecookbook'
335 assert_response :success
335 assert_response :success
336 assert_template 'show'
336 assert_template 'show'
337 assert_not_nil assigns(:project)
337 assert_not_nil assigns(:project)
338 assert_equal Project.find_by_identifier('ecookbook'), assigns(:project)
338 assert_equal Project.find_by_identifier('ecookbook'), assigns(:project)
339
339
340 assert_select 'li', :text => /Development status/
340 assert_select 'li', :text => /Development status/
341 end
341 end
342
342
343 def test_show_should_not_display_empty_sidebar
343 def test_show_should_not_display_empty_sidebar
344 p = Project.find(1)
344 p = Project.find(1)
345 p.enabled_module_names = []
345 p.enabled_module_names = []
346 p.save!
346 p.save!
347
347
348 get :show, :id => 'ecookbook'
348 get :show, :id => 'ecookbook'
349 assert_response :success
349 assert_response :success
350 assert_select '#main.nosidebar'
350 assert_select '#main.nosidebar'
351 end
351 end
352
352
353 def test_show_should_not_display_hidden_custom_fields
353 def test_show_should_not_display_hidden_custom_fields
354 ProjectCustomField.find_by_name('Development status').update_attribute :visible, false
354 ProjectCustomField.find_by_name('Development status').update_attribute :visible, false
355 get :show, :id => 'ecookbook'
355 get :show, :id => 'ecookbook'
356 assert_response :success
356 assert_response :success
357 assert_template 'show'
357 assert_template 'show'
358 assert_not_nil assigns(:project)
358 assert_not_nil assigns(:project)
359
359
360 assert_select 'li', :text => /Development status/, :count => 0
360 assert_select 'li', :text => /Development status/, :count => 0
361 end
361 end
362
362
363 def test_show_should_not_display_blank_custom_fields_with_multiple_values
363 def test_show_should_not_display_blank_custom_fields_with_multiple_values
364 f1 = ProjectCustomField.generate! :field_format => 'list', :possible_values => %w(Foo Bar), :multiple => true
364 f1 = ProjectCustomField.generate! :field_format => 'list', :possible_values => %w(Foo Bar), :multiple => true
365 f2 = ProjectCustomField.generate! :field_format => 'list', :possible_values => %w(Baz Qux), :multiple => true
365 f2 = ProjectCustomField.generate! :field_format => 'list', :possible_values => %w(Baz Qux), :multiple => true
366 project = Project.generate!(:custom_field_values => {f2.id.to_s => %w(Qux)})
366 project = Project.generate!(:custom_field_values => {f2.id.to_s => %w(Qux)})
367
367
368 get :show, :id => project.id
368 get :show, :id => project.id
369 assert_response :success
369 assert_response :success
370
370
371 assert_select 'li', :text => /#{f1.name}/, :count => 0
371 assert_select 'li', :text => /#{f1.name}/, :count => 0
372 assert_select 'li', :text => /#{f2.name}/
372 assert_select 'li', :text => /#{f2.name}/
373 end
373 end
374
374
375 def test_show_should_not_display_blank_text_custom_fields
375 def test_show_should_not_display_blank_text_custom_fields
376 f1 = ProjectCustomField.generate! :field_format => 'text'
376 f1 = ProjectCustomField.generate! :field_format => 'text'
377
377
378 get :show, :id => 1
378 get :show, :id => 1
379 assert_response :success
379 assert_response :success
380
380
381 assert_select 'li', :text => /#{f1.name}/, :count => 0
381 assert_select 'li', :text => /#{f1.name}/, :count => 0
382 end
382 end
383
383
384 def test_show_should_not_fail_when_custom_values_are_nil
384 def test_show_should_not_fail_when_custom_values_are_nil
385 project = Project.find_by_identifier('ecookbook')
385 project = Project.find_by_identifier('ecookbook')
386 project.custom_values.first.update_attribute(:value, nil)
386 project.custom_values.first.update_attribute(:value, nil)
387 get :show, :id => 'ecookbook'
387 get :show, :id => 'ecookbook'
388 assert_response :success
388 assert_response :success
389 assert_template 'show'
389 assert_template 'show'
390 assert_not_nil assigns(:project)
390 assert_not_nil assigns(:project)
391 assert_equal Project.find_by_identifier('ecookbook'), assigns(:project)
391 assert_equal Project.find_by_identifier('ecookbook'), assigns(:project)
392 end
392 end
393
393
394 def show_archived_project_should_be_denied
394 def show_archived_project_should_be_denied
395 project = Project.find_by_identifier('ecookbook')
395 project = Project.find_by_identifier('ecookbook')
396 project.archive!
396 project.archive!
397
397
398 get :show, :id => 'ecookbook'
398 get :show, :id => 'ecookbook'
399 assert_response 403
399 assert_response 403
400 assert_nil assigns(:project)
400 assert_nil assigns(:project)
401 assert_select 'p', :text => /archived/
401 assert_select 'p', :text => /archived/
402 end
402 end
403
403
404 def test_show_should_not_show_private_subprojects_that_are_not_visible
404 def test_show_should_not_show_private_subprojects_that_are_not_visible
405 get :show, :id => 'ecookbook'
405 get :show, :id => 'ecookbook'
406 assert_response :success
406 assert_response :success
407 assert_template 'show'
407 assert_template 'show'
408 assert_select 'a', :text => /Private child/, :count => 0
408 assert_select 'a', :text => /Private child/, :count => 0
409 end
409 end
410
410
411 def test_show_should_show_private_subprojects_that_are_visible
411 def test_show_should_show_private_subprojects_that_are_visible
412 @request.session[:user_id] = 2 # manager who is a member of the private subproject
412 @request.session[:user_id] = 2 # manager who is a member of the private subproject
413 get :show, :id => 'ecookbook'
413 get :show, :id => 'ecookbook'
414 assert_response :success
414 assert_response :success
415 assert_template 'show'
415 assert_template 'show'
416 assert_select 'a', :text => /Private child/
416 assert_select 'a', :text => /Private child/
417 end
417 end
418
418
419 def test_settings
419 def test_settings
420 @request.session[:user_id] = 2 # manager
420 @request.session[:user_id] = 2 # manager
421 get :settings, :id => 1
421 get :settings, :id => 1
422 assert_response :success
422 assert_response :success
423 assert_template 'settings'
423 assert_template 'settings'
424 end
424 end
425
425
426 def test_settings_of_subproject
426 def test_settings_of_subproject
427 @request.session[:user_id] = 2
427 @request.session[:user_id] = 2
428 get :settings, :id => 'private-child'
428 get :settings, :id => 'private-child'
429 assert_response :success
429 assert_response :success
430 assert_template 'settings'
430 assert_template 'settings'
431
431
432 assert_select 'input[type=checkbox][name=?]', 'project[inherit_members]'
432 assert_select 'input[type=checkbox][name=?]', 'project[inherit_members]'
433 end
433 end
434
434
435 def test_settings_should_be_denied_for_member_on_closed_project
435 def test_settings_should_be_denied_for_member_on_closed_project
436 Project.find(1).close
436 Project.find(1).close
437 @request.session[:user_id] = 2 # manager
437 @request.session[:user_id] = 2 # manager
438
438
439 get :settings, :id => 1
439 get :settings, :id => 1
440 assert_response 403
440 assert_response 403
441 end
441 end
442
442
443 def test_settings_should_be_denied_for_anonymous_on_closed_project
443 def test_settings_should_be_denied_for_anonymous_on_closed_project
444 Project.find(1).close
444 Project.find(1).close
445
445
446 get :settings, :id => 1
446 get :settings, :id => 1
447 assert_response 302
447 assert_response 302
448 end
448 end
449
449
450 def test_setting_with_wiki_module_and_no_wiki
450 def test_setting_with_wiki_module_and_no_wiki
451 Project.find(1).wiki.destroy
451 Project.find(1).wiki.destroy
452 Role.find(1).add_permission! :manage_wiki
452 Role.find(1).add_permission! :manage_wiki
453 @request.session[:user_id] = 2
453 @request.session[:user_id] = 2
454
454
455 get :settings, :id => 1
455 get :settings, :id => 1
456 assert_response :success
456 assert_response :success
457 assert_template 'settings'
457 assert_template 'settings'
458
458
459 assert_select 'form[action=?]', '/projects/ecookbook/wiki' do
459 assert_select 'form[action=?]', '/projects/ecookbook/wiki' do
460 assert_select 'input[name=?]', 'wiki[start_page]'
460 assert_select 'input[name=?]', 'wiki[start_page]'
461 end
461 end
462 end
462 end
463
463
464 def test_update
464 def test_update
465 @request.session[:user_id] = 2 # manager
465 @request.session[:user_id] = 2 # manager
466 post :update, :id => 1, :project => {:name => 'Test changed name',
466 post :update, :id => 1, :project => {:name => 'Test changed name',
467 :issue_custom_field_ids => ['']}
467 :issue_custom_field_ids => ['']}
468 assert_redirected_to '/projects/ecookbook/settings'
468 assert_redirected_to '/projects/ecookbook/settings'
469 project = Project.find(1)
469 project = Project.find(1)
470 assert_equal 'Test changed name', project.name
470 assert_equal 'Test changed name', project.name
471 end
471 end
472
472
473 def test_update_with_failure
473 def test_update_with_failure
474 @request.session[:user_id] = 2 # manager
474 @request.session[:user_id] = 2 # manager
475 post :update, :id => 1, :project => {:name => ''}
475 post :update, :id => 1, :project => {:name => ''}
476 assert_response :success
476 assert_response :success
477 assert_template 'settings'
477 assert_template 'settings'
478 assert_select_error /name cannot be blank/i
478 assert_select_error /name cannot be blank/i
479 end
479 end
480
480
481 def test_update_should_be_denied_for_member_on_closed_project
481 def test_update_should_be_denied_for_member_on_closed_project
482 Project.find(1).close
482 Project.find(1).close
483 @request.session[:user_id] = 2 # manager
483 @request.session[:user_id] = 2 # manager
484
484
485 post :update, :id => 1, :project => {:name => 'Closed'}
485 post :update, :id => 1, :project => {:name => 'Closed'}
486 assert_response 403
486 assert_response 403
487 assert_equal 'eCookbook', Project.find(1).name
487 assert_equal 'eCookbook', Project.find(1).name
488 end
488 end
489
489
490 def test_update_should_be_denied_for_anonymous_on_closed_project
490 def test_update_should_be_denied_for_anonymous_on_closed_project
491 Project.find(1).close
491 Project.find(1).close
492
492
493 post :update, :id => 1, :project => {:name => 'Closed'}
493 post :update, :id => 1, :project => {:name => 'Closed'}
494 assert_response 302
494 assert_response 302
495 assert_equal 'eCookbook', Project.find(1).name
495 assert_equal 'eCookbook', Project.find(1).name
496 end
496 end
497
497
498 def test_update_child_project_without_parent_permission_should_not_show_validation_error
498 def test_update_child_project_without_parent_permission_should_not_show_validation_error
499 child = Project.generate_with_parent!
499 child = Project.generate_with_parent!
500 user = User.generate!
500 user = User.generate!
501 User.add_to_project(user, child, Role.generate!(:permissions => [:edit_project]))
501 User.add_to_project(user, child, Role.generate!(:permissions => [:edit_project]))
502 @request.session[:user_id] = user.id
502 @request.session[:user_id] = user.id
503
503
504 post :update, :id => child.id, :project => {:name => 'Updated'}
504 post :update, :id => child.id, :project => {:name => 'Updated'}
505 assert_response 302
505 assert_response 302
506 assert_match /Successful update/, flash[:notice]
506 assert_match /Successful update/, flash[:notice]
507 end
507 end
508
508
509 def test_modules
509 def test_modules
510 @request.session[:user_id] = 2
510 @request.session[:user_id] = 2
511 Project.find(1).enabled_module_names = ['issue_tracking', 'news']
511 Project.find(1).enabled_module_names = ['issue_tracking', 'news']
512
512
513 post :modules, :id => 1, :enabled_module_names => ['issue_tracking', 'repository', 'documents']
513 post :modules, :id => 1, :enabled_module_names => ['issue_tracking', 'repository', 'documents']
514 assert_redirected_to '/projects/ecookbook/settings/modules'
514 assert_redirected_to '/projects/ecookbook/settings/modules'
515 assert_equal ['documents', 'issue_tracking', 'repository'], Project.find(1).enabled_module_names.sort
515 assert_equal ['documents', 'issue_tracking', 'repository'], Project.find(1).enabled_module_names.sort
516 end
516 end
517
517
518 def test_destroy_leaf_project_without_confirmation_should_show_confirmation
518 def test_destroy_leaf_project_without_confirmation_should_show_confirmation
519 @request.session[:user_id] = 1 # admin
519 @request.session[:user_id] = 1 # admin
520
520
521 assert_no_difference 'Project.count' do
521 assert_no_difference 'Project.count' do
522 delete :destroy, :id => 2
522 delete :destroy, :id => 2
523 assert_response :success
523 assert_response :success
524 assert_template 'destroy'
524 assert_template 'destroy'
525 end
525 end
526 end
526 end
527
527
528 def test_destroy_without_confirmation_should_show_confirmation_with_subprojects
528 def test_destroy_without_confirmation_should_show_confirmation_with_subprojects
529 @request.session[:user_id] = 1 # admin
529 @request.session[:user_id] = 1 # admin
530
530
531 assert_no_difference 'Project.count' do
531 assert_no_difference 'Project.count' do
532 delete :destroy, :id => 1
532 delete :destroy, :id => 1
533 assert_response :success
533 assert_response :success
534 assert_template 'destroy'
534 assert_template 'destroy'
535 end
535 end
536 assert_select 'strong',
536 assert_select 'strong',
537 :text => ['Private child of eCookbook',
537 :text => ['Private child of eCookbook',
538 'Child of private child, eCookbook Subproject 1',
538 'Child of private child, eCookbook Subproject 1',
539 'eCookbook Subproject 2'].join(', ')
539 'eCookbook Subproject 2'].join(', ')
540 end
540 end
541
541
542 def test_destroy_with_confirmation_should_destroy_the_project_and_subprojects
542 def test_destroy_with_confirmation_should_destroy_the_project_and_subprojects
543 @request.session[:user_id] = 1 # admin
543 @request.session[:user_id] = 1 # admin
544
544
545 assert_difference 'Project.count', -5 do
545 assert_difference 'Project.count', -5 do
546 delete :destroy, :id => 1, :confirm => 1
546 delete :destroy, :id => 1, :confirm => 1
547 assert_redirected_to '/admin/projects'
547 assert_redirected_to '/admin/projects'
548 end
548 end
549 assert_nil Project.find_by_id(1)
549 assert_nil Project.find_by_id(1)
550 end
550 end
551
551
552 def test_archive
552 def test_archive
553 @request.session[:user_id] = 1 # admin
553 @request.session[:user_id] = 1 # admin
554 post :archive, :id => 1
554 post :archive, :id => 1
555 assert_redirected_to '/admin/projects'
555 assert_redirected_to '/admin/projects'
556 assert !Project.find(1).active?
556 assert !Project.find(1).active?
557 end
557 end
558
558
559 def test_archive_with_failure
559 def test_archive_with_failure
560 @request.session[:user_id] = 1
560 @request.session[:user_id] = 1
561 Project.any_instance.stubs(:archive).returns(false)
561 Project.any_instance.stubs(:archive).returns(false)
562 post :archive, :id => 1
562 post :archive, :id => 1
563 assert_redirected_to '/admin/projects'
563 assert_redirected_to '/admin/projects'
564 assert_match /project cannot be archived/i, flash[:error]
564 assert_match /project cannot be archived/i, flash[:error]
565 end
565 end
566
566
567 def test_unarchive
567 def test_unarchive
568 @request.session[:user_id] = 1 # admin
568 @request.session[:user_id] = 1 # admin
569 Project.find(1).archive
569 Project.find(1).archive
570 post :unarchive, :id => 1
570 post :unarchive, :id => 1
571 assert_redirected_to '/admin/projects'
571 assert_redirected_to '/admin/projects'
572 assert Project.find(1).active?
572 assert Project.find(1).active?
573 end
573 end
574
574
575 def test_close
575 def test_close
576 @request.session[:user_id] = 2
576 @request.session[:user_id] = 2
577 post :close, :id => 1
577 post :close, :id => 1
578 assert_redirected_to '/projects/ecookbook'
578 assert_redirected_to '/projects/ecookbook'
579 assert_equal Project::STATUS_CLOSED, Project.find(1).status
579 assert_equal Project::STATUS_CLOSED, Project.find(1).status
580 end
580 end
581
581
582 def test_reopen
582 def test_reopen
583 Project.find(1).close
583 Project.find(1).close
584 @request.session[:user_id] = 2
584 @request.session[:user_id] = 2
585 post :reopen, :id => 1
585 post :reopen, :id => 1
586 assert_redirected_to '/projects/ecookbook'
586 assert_redirected_to '/projects/ecookbook'
587 assert Project.find(1).active?
587 assert Project.find(1).active?
588 end
588 end
589
589
590 def test_project_breadcrumbs_should_be_limited_to_3_ancestors
590 def test_project_breadcrumbs_should_be_limited_to_3_ancestors
591 CustomField.delete_all
591 CustomField.delete_all
592 parent = nil
592 parent = nil
593 6.times do |i|
593 6.times do |i|
594 p = Project.generate_with_parent!(parent)
594 p = Project.generate_with_parent!(parent)
595 get :show, :id => p
595 get :show, :id => p
596 assert_select '#header h1' do
596 assert_select '#header h1' do
597 assert_select 'a', :count => [i, 3].min
597 assert_select 'a', :count => [i, 3].min
598 end
598 end
599
599
600 parent = p
600 parent = p
601 end
601 end
602 end
602 end
603
603
604 def test_get_copy
604 def test_get_copy
605 @request.session[:user_id] = 1 # admin
605 @request.session[:user_id] = 1 # admin
606 get :copy, :id => 1
606 get :copy, :id => 1
607 assert_response :success
607 assert_response :success
608 assert_template 'copy'
608 assert_template 'copy'
609 assert assigns(:project)
609 assert assigns(:project)
610 assert_equal Project.find(1).description, assigns(:project).description
610 assert_equal Project.find(1).description, assigns(:project).description
611 assert_nil assigns(:project).id
611 assert_nil assigns(:project).id
612
612
613 assert_select 'input[name=?][value=?]', 'project[enabled_module_names][]', 'issue_tracking', 1
613 assert_select 'input[name=?][value=?]', 'project[enabled_module_names][]', 'issue_tracking', 1
614 end
614 end
615
615
616 def test_get_copy_with_invalid_source_should_respond_with_404
616 def test_get_copy_with_invalid_source_should_respond_with_404
617 @request.session[:user_id] = 1
617 @request.session[:user_id] = 1
618 get :copy, :id => 99
618 get :copy, :id => 99
619 assert_response 404
619 assert_response 404
620 end
620 end
621
621
622 def test_get_copy_should_preselect_custom_fields
622 def test_get_copy_should_preselect_custom_fields
623 field1 = IssueCustomField.generate!(:is_for_all => false)
623 field1 = IssueCustomField.generate!(:is_for_all => false)
624 field2 = IssueCustomField.generate!(:is_for_all => false)
624 field2 = IssueCustomField.generate!(:is_for_all => false)
625 source = Project.generate!(:issue_custom_fields => [field1])
625 source = Project.generate!(:issue_custom_fields => [field1])
626 @request.session[:user_id] = 1
626 @request.session[:user_id] = 1
627
627
628 get :copy, :id => source.id
628 get :copy, :id => source.id
629 assert_response :success
629 assert_response :success
630 assert_select 'fieldset#project_issue_custom_fields' do
630 assert_select 'fieldset#project_issue_custom_fields' do
631 assert_select 'input[type=checkbox][value=?][checked=checked]', field1.id.to_s
631 assert_select 'input[type=checkbox][value=?][checked=checked]', field1.id.to_s
632 assert_select 'input[type=checkbox][value=?]:not([checked])', field2.id.to_s
632 assert_select 'input[type=checkbox][value=?]:not([checked])', field2.id.to_s
633 end
633 end
634 end
634 end
635
635
636 def test_post_copy_should_copy_requested_items
636 def test_post_copy_should_copy_requested_items
637 @request.session[:user_id] = 1 # admin
637 @request.session[:user_id] = 1 # admin
638 CustomField.delete_all
638 CustomField.delete_all
639
639
640 assert_difference 'Project.count' do
640 assert_difference 'Project.count' do
641 post :copy, :id => 1,
641 post :copy, :id => 1,
642 :project => {
642 :project => {
643 :name => 'Copy',
643 :name => 'Copy',
644 :identifier => 'unique-copy',
644 :identifier => 'unique-copy',
645 :tracker_ids => ['1', '2', '3', ''],
645 :tracker_ids => ['1', '2', '3', ''],
646 :enabled_module_names => %w(issue_tracking time_tracking)
646 :enabled_module_names => %w(issue_tracking time_tracking)
647 },
647 },
648 :only => %w(issues versions)
648 :only => %w(issues versions)
649 end
649 end
650 project = Project.find('unique-copy')
650 project = Project.find('unique-copy')
651 source = Project.find(1)
651 source = Project.find(1)
652 assert_equal %w(issue_tracking time_tracking), project.enabled_module_names.sort
652 assert_equal %w(issue_tracking time_tracking), project.enabled_module_names.sort
653
653
654 assert_equal source.versions.count, project.versions.count, "All versions were not copied"
654 assert_equal source.versions.count, project.versions.count, "All versions were not copied"
655 assert_equal source.issues.count, project.issues.count, "All issues were not copied"
655 assert_equal source.issues.count, project.issues.count, "All issues were not copied"
656 assert_equal 0, project.members.count
656 assert_equal 0, project.members.count
657 end
657 end
658
658
659 def test_post_copy_should_redirect_to_settings_when_successful
659 def test_post_copy_should_redirect_to_settings_when_successful
660 @request.session[:user_id] = 1 # admin
660 @request.session[:user_id] = 1 # admin
661 post :copy, :id => 1, :project => {:name => 'Copy', :identifier => 'unique-copy'}
661 post :copy, :id => 1, :project => {:name => 'Copy', :identifier => 'unique-copy'}
662 assert_response :redirect
662 assert_response :redirect
663 assert_redirected_to :controller => 'projects', :action => 'settings', :id => 'unique-copy'
663 assert_redirected_to :controller => 'projects', :action => 'settings', :id => 'unique-copy'
664 end
664 end
665
665
666 def test_post_copy_with_failure
666 def test_post_copy_with_failure
667 @request.session[:user_id] = 1
667 @request.session[:user_id] = 1
668 post :copy, :id => 1, :project => {:name => 'Copy', :identifier => ''}
668 post :copy, :id => 1, :project => {:name => 'Copy', :identifier => ''}
669 assert_response :success
669 assert_response :success
670 assert_template 'copy'
670 assert_template 'copy'
671 end
671 end
672
672
673 def test_jump_should_redirect_to_active_tab
673 def test_jump_should_redirect_to_active_tab
674 get :show, :id => 1, :jump => 'issues'
674 get :show, :id => 1, :jump => 'issues'
675 assert_redirected_to '/projects/ecookbook/issues'
675 assert_redirected_to '/projects/ecookbook/issues'
676 end
676 end
677
677
678 def test_jump_should_not_redirect_to_inactive_tab
678 def test_jump_should_not_redirect_to_inactive_tab
679 get :show, :id => 3, :jump => 'documents'
679 get :show, :id => 3, :jump => 'documents'
680 assert_response :success
680 assert_response :success
681 assert_template 'show'
681 assert_template 'show'
682 end
682 end
683
683
684 def test_jump_should_not_redirect_to_unknown_tab
684 def test_jump_should_not_redirect_to_unknown_tab
685 get :show, :id => 3, :jump => 'foobar'
685 get :show, :id => 3, :jump => 'foobar'
686 assert_response :success
686 assert_response :success
687 assert_template 'show'
687 assert_template 'show'
688 end
688 end
689
689
690 def test_body_should_have_project_css_class
690 def test_body_should_have_project_css_class
691 get :show, :id => 1
691 get :show, :id => 1
692 assert_select 'body.project-ecookbook'
692 assert_select 'body.project-ecookbook'
693 end
693 end
694
695 def test_project_menu_should_include_new_issue_link
696 @request.session[:user_id] = 2
697 get :show, :id => 1
698 assert_select '#main-menu a.new-issue[href="/projects/ecookbook/issues/new"]', :text => 'New issue'
699 end
700
701 def test_project_menu_should_not_include_new_issue_link_for_project_without_trackers
702 Project.find(1).trackers.clear
703
704 @request.session[:user_id] = 2
705 get :show, :id => 1
706 assert_select '#main-menu a.new-issue', 0
707 end
708
709 def test_project_menu_should_not_include_new_issue_link_for_users_with_copy_issues_permission_only
710 role = Role.find(1)
711 role.remove_permission! :add_issues
712 role.add_permission! :copy_issues
713
714 @request.session[:user_id] = 2
715 get :show, :id => 1
716 assert_select '#main-menu a.new-issue', 0
717 end
718 end
694 end
@@ -1,86 +1,80
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.expand_path('../../../test_helper', __FILE__)
18 require File.expand_path('../../../test_helper', __FILE__)
19
19
20 module RedmineMenuTestHelper
20 module RedmineMenuTestHelper
21 # Assertions
21 # Assertions
22 def assert_number_of_items_in_menu(menu_name, count)
22 def assert_number_of_items_in_menu(menu_name, count)
23 assert Redmine::MenuManager.items(menu_name).size >= count, "Menu has less than #{count} items"
23 assert Redmine::MenuManager.items(menu_name).size >= count, "Menu has less than #{count} items"
24 end
24 end
25
25
26 def assert_menu_contains_item_named(menu_name, item_name)
26 def assert_menu_contains_item_named(menu_name, item_name)
27 assert Redmine::MenuManager.items(menu_name).collect(&:name).include?(item_name.to_sym), "Menu did not have an item named #{item_name}"
27 assert Redmine::MenuManager.items(menu_name).collect(&:name).include?(item_name.to_sym), "Menu did not have an item named #{item_name}"
28 end
28 end
29
29
30 # Helpers
30 # Helpers
31 def get_menu_item(menu_name, item_name)
31 def get_menu_item(menu_name, item_name)
32 Redmine::MenuManager.items(menu_name).find {|item| item.name == item_name.to_sym}
32 Redmine::MenuManager.items(menu_name).find {|item| item.name == item_name.to_sym}
33 end
33 end
34 end
34 end
35
35
36 class RedmineTest < ActiveSupport::TestCase
36 class RedmineTest < ActiveSupport::TestCase
37 include RedmineMenuTestHelper
37 include RedmineMenuTestHelper
38
38
39 def test_top_menu
39 def test_top_menu
40 assert_number_of_items_in_menu :top_menu, 5
40 assert_number_of_items_in_menu :top_menu, 5
41 assert_menu_contains_item_named :top_menu, :home
41 assert_menu_contains_item_named :top_menu, :home
42 assert_menu_contains_item_named :top_menu, :my_page
42 assert_menu_contains_item_named :top_menu, :my_page
43 assert_menu_contains_item_named :top_menu, :projects
43 assert_menu_contains_item_named :top_menu, :projects
44 assert_menu_contains_item_named :top_menu, :administration
44 assert_menu_contains_item_named :top_menu, :administration
45 assert_menu_contains_item_named :top_menu, :help
45 assert_menu_contains_item_named :top_menu, :help
46 end
46 end
47
47
48 def test_account_menu
48 def test_account_menu
49 assert_number_of_items_in_menu :account_menu, 4
49 assert_number_of_items_in_menu :account_menu, 4
50 assert_menu_contains_item_named :account_menu, :login
50 assert_menu_contains_item_named :account_menu, :login
51 assert_menu_contains_item_named :account_menu, :register
51 assert_menu_contains_item_named :account_menu, :register
52 assert_menu_contains_item_named :account_menu, :my_account
52 assert_menu_contains_item_named :account_menu, :my_account
53 assert_menu_contains_item_named :account_menu, :logout
53 assert_menu_contains_item_named :account_menu, :logout
54 end
54 end
55
55
56 def test_application_menu
56 def test_application_menu
57 assert_number_of_items_in_menu :application_menu, 0
57 assert_number_of_items_in_menu :application_menu, 0
58 end
58 end
59
59
60 def test_admin_menu
60 def test_admin_menu
61 assert_number_of_items_in_menu :admin_menu, 0
61 assert_number_of_items_in_menu :admin_menu, 0
62 end
62 end
63
63
64 def test_project_menu
64 def test_project_menu
65 assert_number_of_items_in_menu :project_menu, 14
65 assert_number_of_items_in_menu :project_menu, 13
66 assert_menu_contains_item_named :project_menu, :overview
66 assert_menu_contains_item_named :project_menu, :overview
67 assert_menu_contains_item_named :project_menu, :activity
67 assert_menu_contains_item_named :project_menu, :activity
68 assert_menu_contains_item_named :project_menu, :roadmap
68 assert_menu_contains_item_named :project_menu, :roadmap
69 assert_menu_contains_item_named :project_menu, :issues
69 assert_menu_contains_item_named :project_menu, :issues
70 assert_menu_contains_item_named :project_menu, :new_issue
71 assert_menu_contains_item_named :project_menu, :calendar
70 assert_menu_contains_item_named :project_menu, :calendar
72 assert_menu_contains_item_named :project_menu, :gantt
71 assert_menu_contains_item_named :project_menu, :gantt
73 assert_menu_contains_item_named :project_menu, :news
72 assert_menu_contains_item_named :project_menu, :news
74 assert_menu_contains_item_named :project_menu, :documents
73 assert_menu_contains_item_named :project_menu, :documents
75 assert_menu_contains_item_named :project_menu, :wiki
74 assert_menu_contains_item_named :project_menu, :wiki
76 assert_menu_contains_item_named :project_menu, :boards
75 assert_menu_contains_item_named :project_menu, :boards
77 assert_menu_contains_item_named :project_menu, :files
76 assert_menu_contains_item_named :project_menu, :files
78 assert_menu_contains_item_named :project_menu, :repository
77 assert_menu_contains_item_named :project_menu, :repository
79 assert_menu_contains_item_named :project_menu, :settings
78 assert_menu_contains_item_named :project_menu, :settings
80 end
79 end
81
82 def test_new_issue_should_have_root_as_a_parent
83 new_issue = get_menu_item(:project_menu, :new_issue)
84 assert_equal :root, new_issue.parent.name
85 end
86 end
80 end
General Comments 0
You need to be logged in to leave comments. Login now