##// END OF EJS Templates
Verify HTTP method on ProjectsController#create....
Jean-Philippe Lang -
r4526:072c4ad14cf0
parent child
Show More
@@ -1,269 +1,269
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2009 Jean-Philippe Lang
2 # Copyright (C) 2006-2009 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 ProjectsController < ApplicationController
18 class ProjectsController < ApplicationController
19 menu_item :overview
19 menu_item :overview
20 menu_item :roadmap, :only => :roadmap
20 menu_item :roadmap, :only => :roadmap
21 menu_item :settings, :only => :settings
21 menu_item :settings, :only => :settings
22
22
23 before_filter :find_project, :except => [ :index, :list, :new, :create, :copy ]
23 before_filter :find_project, :except => [ :index, :list, :new, :create, :copy ]
24 before_filter :authorize, :except => [ :index, :list, :new, :create, :copy, :archive, :unarchive, :destroy]
24 before_filter :authorize, :except => [ :index, :list, :new, :create, :copy, :archive, :unarchive, :destroy]
25 before_filter :authorize_global, :only => [:new, :create]
25 before_filter :authorize_global, :only => [:new, :create]
26 before_filter :require_admin, :only => [ :copy, :archive, :unarchive, :destroy ]
26 before_filter :require_admin, :only => [ :copy, :archive, :unarchive, :destroy ]
27 accept_key_auth :index, :show, :create, :update, :destroy
27 accept_key_auth :index, :show, :create, :update, :destroy
28
28
29 after_filter :only => [:create, :edit, :update, :archive, :unarchive, :destroy] do |controller|
29 after_filter :only => [:create, :edit, :update, :archive, :unarchive, :destroy] do |controller|
30 if controller.request.post?
30 if controller.request.post?
31 controller.send :expire_action, :controller => 'welcome', :action => 'robots.txt'
31 controller.send :expire_action, :controller => 'welcome', :action => 'robots.txt'
32 end
32 end
33 end
33 end
34
34
35 # TODO: convert to PUT only
36 verify :method => [:post, :put], :only => :update, :render => {:nothing => true, :status => :method_not_allowed }
37
38 helper :sort
35 helper :sort
39 include SortHelper
36 include SortHelper
40 helper :custom_fields
37 helper :custom_fields
41 include CustomFieldsHelper
38 include CustomFieldsHelper
42 helper :issues
39 helper :issues
43 helper :queries
40 helper :queries
44 include QueriesHelper
41 include QueriesHelper
45 helper :repositories
42 helper :repositories
46 include RepositoriesHelper
43 include RepositoriesHelper
47 include ProjectsHelper
44 include ProjectsHelper
48
45
49 # Lists visible projects
46 # Lists visible projects
50 def index
47 def index
51 respond_to do |format|
48 respond_to do |format|
52 format.html {
49 format.html {
53 @projects = Project.visible.find(:all, :order => 'lft')
50 @projects = Project.visible.find(:all, :order => 'lft')
54 }
51 }
55 format.api {
52 format.api {
56 @offset, @limit = api_offset_and_limit
53 @offset, @limit = api_offset_and_limit
57 @project_count = Project.visible.count
54 @project_count = Project.visible.count
58 @projects = Project.visible.all(:offset => @offset, :limit => @limit, :order => 'lft')
55 @projects = Project.visible.all(:offset => @offset, :limit => @limit, :order => 'lft')
59 }
56 }
60 format.atom {
57 format.atom {
61 projects = Project.visible.find(:all, :order => 'created_on DESC',
58 projects = Project.visible.find(:all, :order => 'created_on DESC',
62 :limit => Setting.feeds_limit.to_i)
59 :limit => Setting.feeds_limit.to_i)
63 render_feed(projects, :title => "#{Setting.app_title}: #{l(:label_project_latest)}")
60 render_feed(projects, :title => "#{Setting.app_title}: #{l(:label_project_latest)}")
64 }
61 }
65 end
62 end
66 end
63 end
67
64
68 def new
65 def new
69 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
66 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
70 @trackers = Tracker.all
67 @trackers = Tracker.all
71 @project = Project.new(params[:project])
68 @project = Project.new(params[:project])
72 end
69 end
73
70
71 verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
74 def create
72 def create
75 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
73 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
76 @trackers = Tracker.all
74 @trackers = Tracker.all
77 @project = Project.new
75 @project = Project.new
78 @project.safe_attributes = params[:project]
76 @project.safe_attributes = params[:project]
79
77
80 if validate_parent_id && @project.save
78 if validate_parent_id && @project.save
81 @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
79 @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
82 # Add current user as a project member if he is not admin
80 # Add current user as a project member if he is not admin
83 unless User.current.admin?
81 unless User.current.admin?
84 r = Role.givable.find_by_id(Setting.new_project_user_role_id.to_i) || Role.givable.first
82 r = Role.givable.find_by_id(Setting.new_project_user_role_id.to_i) || Role.givable.first
85 m = Member.new(:user => User.current, :roles => [r])
83 m = Member.new(:user => User.current, :roles => [r])
86 @project.members << m
84 @project.members << m
87 end
85 end
88 respond_to do |format|
86 respond_to do |format|
89 format.html {
87 format.html {
90 flash[:notice] = l(:notice_successful_create)
88 flash[:notice] = l(:notice_successful_create)
91 redirect_to :controller => 'projects', :action => 'settings', :id => @project
89 redirect_to :controller => 'projects', :action => 'settings', :id => @project
92 }
90 }
93 format.api { render :action => 'show', :status => :created, :location => url_for(:controller => 'projects', :action => 'show', :id => @project.id) }
91 format.api { render :action => 'show', :status => :created, :location => url_for(:controller => 'projects', :action => 'show', :id => @project.id) }
94 end
92 end
95 else
93 else
96 respond_to do |format|
94 respond_to do |format|
97 format.html { render :action => 'new' }
95 format.html { render :action => 'new' }
98 format.api { render_validation_errors(@project) }
96 format.api { render_validation_errors(@project) }
99 end
97 end
100 end
98 end
101
99
102 end
100 end
103
101
104 def copy
102 def copy
105 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
103 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
106 @trackers = Tracker.all
104 @trackers = Tracker.all
107 @root_projects = Project.find(:all,
105 @root_projects = Project.find(:all,
108 :conditions => "parent_id IS NULL AND status = #{Project::STATUS_ACTIVE}",
106 :conditions => "parent_id IS NULL AND status = #{Project::STATUS_ACTIVE}",
109 :order => 'name')
107 :order => 'name')
110 @source_project = Project.find(params[:id])
108 @source_project = Project.find(params[:id])
111 if request.get?
109 if request.get?
112 @project = Project.copy_from(@source_project)
110 @project = Project.copy_from(@source_project)
113 if @project
111 if @project
114 @project.identifier = Project.next_identifier if Setting.sequential_project_identifiers?
112 @project.identifier = Project.next_identifier if Setting.sequential_project_identifiers?
115 else
113 else
116 redirect_to :controller => 'admin', :action => 'projects'
114 redirect_to :controller => 'admin', :action => 'projects'
117 end
115 end
118 else
116 else
119 Mailer.with_deliveries(params[:notifications] == '1') do
117 Mailer.with_deliveries(params[:notifications] == '1') do
120 @project = Project.new
118 @project = Project.new
121 @project.safe_attributes = params[:project]
119 @project.safe_attributes = params[:project]
122 @project.enabled_module_names = params[:enabled_modules]
120 @project.enabled_module_names = params[:enabled_modules]
123 if validate_parent_id && @project.copy(@source_project, :only => params[:only])
121 if validate_parent_id && @project.copy(@source_project, :only => params[:only])
124 @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
122 @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
125 flash[:notice] = l(:notice_successful_create)
123 flash[:notice] = l(:notice_successful_create)
126 redirect_to :controller => 'projects', :action => 'settings', :id => @project
124 redirect_to :controller => 'projects', :action => 'settings', :id => @project
127 elsif !@project.new_record?
125 elsif !@project.new_record?
128 # Project was created
126 # Project was created
129 # But some objects were not copied due to validation failures
127 # But some objects were not copied due to validation failures
130 # (eg. issues from disabled trackers)
128 # (eg. issues from disabled trackers)
131 # TODO: inform about that
129 # TODO: inform about that
132 redirect_to :controller => 'projects', :action => 'settings', :id => @project
130 redirect_to :controller => 'projects', :action => 'settings', :id => @project
133 end
131 end
134 end
132 end
135 end
133 end
136 rescue ActiveRecord::RecordNotFound
134 rescue ActiveRecord::RecordNotFound
137 redirect_to :controller => 'admin', :action => 'projects'
135 redirect_to :controller => 'admin', :action => 'projects'
138 end
136 end
139
137
140 # Show @project
138 # Show @project
141 def show
139 def show
142 if params[:jump]
140 if params[:jump]
143 # try to redirect to the requested menu item
141 # try to redirect to the requested menu item
144 redirect_to_project_menu_item(@project, params[:jump]) && return
142 redirect_to_project_menu_item(@project, params[:jump]) && return
145 end
143 end
146
144
147 @users_by_role = @project.users_by_role
145 @users_by_role = @project.users_by_role
148 @subprojects = @project.children.visible
146 @subprojects = @project.children.visible
149 @news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
147 @news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
150 @trackers = @project.rolled_up_trackers
148 @trackers = @project.rolled_up_trackers
151
149
152 cond = @project.project_condition(Setting.display_subprojects_issues?)
150 cond = @project.project_condition(Setting.display_subprojects_issues?)
153
151
154 @open_issues_by_tracker = Issue.visible.count(:group => :tracker,
152 @open_issues_by_tracker = Issue.visible.count(:group => :tracker,
155 :include => [:project, :status, :tracker],
153 :include => [:project, :status, :tracker],
156 :conditions => ["(#{cond}) AND #{IssueStatus.table_name}.is_closed=?", false])
154 :conditions => ["(#{cond}) AND #{IssueStatus.table_name}.is_closed=?", false])
157 @total_issues_by_tracker = Issue.visible.count(:group => :tracker,
155 @total_issues_by_tracker = Issue.visible.count(:group => :tracker,
158 :include => [:project, :status, :tracker],
156 :include => [:project, :status, :tracker],
159 :conditions => cond)
157 :conditions => cond)
160
158
161 TimeEntry.visible_by(User.current) do
159 TimeEntry.visible_by(User.current) do
162 @total_hours = TimeEntry.sum(:hours,
160 @total_hours = TimeEntry.sum(:hours,
163 :include => :project,
161 :include => :project,
164 :conditions => cond).to_f
162 :conditions => cond).to_f
165 end
163 end
166 @key = User.current.rss_key
164 @key = User.current.rss_key
167
165
168 respond_to do |format|
166 respond_to do |format|
169 format.html
167 format.html
170 format.api
168 format.api
171 end
169 end
172 end
170 end
173
171
174 def settings
172 def settings
175 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
173 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
176 @issue_category ||= IssueCategory.new
174 @issue_category ||= IssueCategory.new
177 @member ||= @project.members.new
175 @member ||= @project.members.new
178 @trackers = Tracker.all
176 @trackers = Tracker.all
179 @repository ||= @project.repository
177 @repository ||= @project.repository
180 @wiki ||= @project.wiki
178 @wiki ||= @project.wiki
181 end
179 end
182
180
183 def edit
181 def edit
184 end
182 end
185
183
184 # TODO: convert to PUT only
185 verify :method => [:post, :put], :only => :update, :render => {:nothing => true, :status => :method_not_allowed }
186 def update
186 def update
187 @project.safe_attributes = params[:project]
187 @project.safe_attributes = params[:project]
188 if validate_parent_id && @project.save
188 if validate_parent_id && @project.save
189 @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
189 @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
190 respond_to do |format|
190 respond_to do |format|
191 format.html {
191 format.html {
192 flash[:notice] = l(:notice_successful_update)
192 flash[:notice] = l(:notice_successful_update)
193 redirect_to :action => 'settings', :id => @project
193 redirect_to :action => 'settings', :id => @project
194 }
194 }
195 format.api { head :ok }
195 format.api { head :ok }
196 end
196 end
197 else
197 else
198 respond_to do |format|
198 respond_to do |format|
199 format.html {
199 format.html {
200 settings
200 settings
201 render :action => 'settings'
201 render :action => 'settings'
202 }
202 }
203 format.api { render_validation_errors(@project) }
203 format.api { render_validation_errors(@project) }
204 end
204 end
205 end
205 end
206 end
206 end
207
207
208 def modules
208 def modules
209 @project.enabled_module_names = params[:enabled_modules]
209 @project.enabled_module_names = params[:enabled_modules]
210 flash[:notice] = l(:notice_successful_update)
210 flash[:notice] = l(:notice_successful_update)
211 redirect_to :action => 'settings', :id => @project, :tab => 'modules'
211 redirect_to :action => 'settings', :id => @project, :tab => 'modules'
212 end
212 end
213
213
214 def archive
214 def archive
215 if request.post?
215 if request.post?
216 unless @project.archive
216 unless @project.archive
217 flash[:error] = l(:error_can_not_archive_project)
217 flash[:error] = l(:error_can_not_archive_project)
218 end
218 end
219 end
219 end
220 redirect_to(url_for(:controller => 'admin', :action => 'projects', :status => params[:status]))
220 redirect_to(url_for(:controller => 'admin', :action => 'projects', :status => params[:status]))
221 end
221 end
222
222
223 def unarchive
223 def unarchive
224 @project.unarchive if request.post? && !@project.active?
224 @project.unarchive if request.post? && !@project.active?
225 redirect_to(url_for(:controller => 'admin', :action => 'projects', :status => params[:status]))
225 redirect_to(url_for(:controller => 'admin', :action => 'projects', :status => params[:status]))
226 end
226 end
227
227
228 # Delete @project
228 # Delete @project
229 def destroy
229 def destroy
230 @project_to_destroy = @project
230 @project_to_destroy = @project
231 if request.get?
231 if request.get?
232 # display confirmation view
232 # display confirmation view
233 else
233 else
234 if api_request? || params[:confirm]
234 if api_request? || params[:confirm]
235 @project_to_destroy.destroy
235 @project_to_destroy.destroy
236 respond_to do |format|
236 respond_to do |format|
237 format.html { redirect_to :controller => 'admin', :action => 'projects' }
237 format.html { redirect_to :controller => 'admin', :action => 'projects' }
238 format.api { head :ok }
238 format.api { head :ok }
239 end
239 end
240 end
240 end
241 end
241 end
242 # hide project in layout
242 # hide project in layout
243 @project = nil
243 @project = nil
244 end
244 end
245
245
246 private
246 private
247 def find_optional_project
247 def find_optional_project
248 return true unless params[:id]
248 return true unless params[:id]
249 @project = Project.find(params[:id])
249 @project = Project.find(params[:id])
250 authorize
250 authorize
251 rescue ActiveRecord::RecordNotFound
251 rescue ActiveRecord::RecordNotFound
252 render_404
252 render_404
253 end
253 end
254
254
255 # Validates parent_id param according to user's permissions
255 # Validates parent_id param according to user's permissions
256 # TODO: move it to Project model in a validation that depends on User.current
256 # TODO: move it to Project model in a validation that depends on User.current
257 def validate_parent_id
257 def validate_parent_id
258 return true if User.current.admin?
258 return true if User.current.admin?
259 parent_id = params[:project] && params[:project][:parent_id]
259 parent_id = params[:project] && params[:project][:parent_id]
260 if parent_id || @project.new_record?
260 if parent_id || @project.new_record?
261 parent = parent_id.blank? ? nil : Project.find_by_id(parent_id.to_i)
261 parent = parent_id.blank? ? nil : Project.find_by_id(parent_id.to_i)
262 unless @project.allowed_parents.include?(parent)
262 unless @project.allowed_parents.include?(parent)
263 @project.errors.add :parent_id, :invalid
263 @project.errors.add :parent_id, :invalid
264 return false
264 return false
265 end
265 end
266 end
266 end
267 true
267 true
268 end
268 end
269 end
269 end
@@ -1,477 +1,488
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19 require 'projects_controller'
19 require 'projects_controller'
20
20
21 # Re-raise errors caught by the controller.
21 # Re-raise errors caught by the controller.
22 class ProjectsController; def rescue_action(e) raise e end; end
22 class ProjectsController; def rescue_action(e) raise e end; end
23
23
24 class ProjectsControllerTest < ActionController::TestCase
24 class ProjectsControllerTest < ActionController::TestCase
25 fixtures :projects, :versions, :users, :roles, :members, :member_roles, :issues, :journals, :journal_details,
25 fixtures :projects, :versions, :users, :roles, :members, :member_roles, :issues, :journals, :journal_details,
26 :trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations, :boards, :messages,
26 :trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations, :boards, :messages,
27 :attachments, :custom_fields, :custom_values, :time_entries
27 :attachments, :custom_fields, :custom_values, :time_entries
28
28
29 def setup
29 def setup
30 @controller = ProjectsController.new
30 @controller = ProjectsController.new
31 @request = ActionController::TestRequest.new
31 @request = ActionController::TestRequest.new
32 @response = ActionController::TestResponse.new
32 @response = ActionController::TestResponse.new
33 @request.session[:user_id] = nil
33 @request.session[:user_id] = nil
34 Setting.default_language = 'en'
34 Setting.default_language = 'en'
35 end
35 end
36
36
37 def test_index
37 def test_index
38 get :index
38 get :index
39 assert_response :success
39 assert_response :success
40 assert_template 'index'
40 assert_template 'index'
41 assert_not_nil assigns(:projects)
41 assert_not_nil assigns(:projects)
42
42
43 assert_tag :ul, :child => {:tag => 'li',
43 assert_tag :ul, :child => {:tag => 'li',
44 :descendant => {:tag => 'a', :content => 'eCookbook'},
44 :descendant => {:tag => 'a', :content => 'eCookbook'},
45 :child => { :tag => 'ul',
45 :child => { :tag => 'ul',
46 :descendant => { :tag => 'a',
46 :descendant => { :tag => 'a',
47 :content => 'Child of private child'
47 :content => 'Child of private child'
48 }
48 }
49 }
49 }
50 }
50 }
51
51
52 assert_no_tag :a, :content => /Private child of eCookbook/
52 assert_no_tag :a, :content => /Private child of eCookbook/
53 end
53 end
54
54
55 def test_index_atom
55 def test_index_atom
56 get :index, :format => 'atom'
56 get :index, :format => 'atom'
57 assert_response :success
57 assert_response :success
58 assert_template 'common/feed.atom.rxml'
58 assert_template 'common/feed.atom.rxml'
59 assert_select 'feed>title', :text => 'Redmine: Latest projects'
59 assert_select 'feed>title', :text => 'Redmine: Latest projects'
60 assert_select 'feed>entry', :count => Project.count(:conditions => Project.visible_by(User.current))
60 assert_select 'feed>entry', :count => Project.count(:conditions => Project.visible_by(User.current))
61 end
61 end
62
62
63 context "#index" do
63 context "#index" do
64 context "by non-admin user with view_time_entries permission" do
64 context "by non-admin user with view_time_entries permission" do
65 setup do
65 setup do
66 @request.session[:user_id] = 3
66 @request.session[:user_id] = 3
67 end
67 end
68 should "show overall spent time link" do
68 should "show overall spent time link" do
69 get :index
69 get :index
70 assert_template 'index'
70 assert_template 'index'
71 assert_tag :a, :attributes => {:href => '/time_entries'}
71 assert_tag :a, :attributes => {:href => '/time_entries'}
72 end
72 end
73 end
73 end
74
74
75 context "by non-admin user without view_time_entries permission" do
75 context "by non-admin user without view_time_entries permission" do
76 setup do
76 setup do
77 Role.find(2).remove_permission! :view_time_entries
77 Role.find(2).remove_permission! :view_time_entries
78 Role.non_member.remove_permission! :view_time_entries
78 Role.non_member.remove_permission! :view_time_entries
79 Role.anonymous.remove_permission! :view_time_entries
79 Role.anonymous.remove_permission! :view_time_entries
80 @request.session[:user_id] = 3
80 @request.session[:user_id] = 3
81 end
81 end
82 should "not show overall spent time link" do
82 should "not show overall spent time link" do
83 get :index
83 get :index
84 assert_template 'index'
84 assert_template 'index'
85 assert_no_tag :a, :attributes => {:href => '/time_entries'}
85 assert_no_tag :a, :attributes => {:href => '/time_entries'}
86 end
86 end
87 end
87 end
88 end
88 end
89
89
90 context "#new" do
90 context "#new" do
91 context "by admin user" do
91 context "by admin user" do
92 setup do
92 setup do
93 @request.session[:user_id] = 1
93 @request.session[:user_id] = 1
94 end
94 end
95
95
96 should "accept get" do
96 should "accept get" do
97 get :new
97 get :new
98 assert_response :success
98 assert_response :success
99 assert_template 'new'
99 assert_template 'new'
100 end
100 end
101
101
102 end
102 end
103
103
104 context "by non-admin user with add_project permission" do
104 context "by non-admin user with add_project permission" do
105 setup do
105 setup do
106 Role.non_member.add_permission! :add_project
106 Role.non_member.add_permission! :add_project
107 @request.session[:user_id] = 9
107 @request.session[:user_id] = 9
108 end
108 end
109
109
110 should "accept get" do
110 should "accept get" do
111 get :new
111 get :new
112 assert_response :success
112 assert_response :success
113 assert_template 'new'
113 assert_template 'new'
114 assert_no_tag :select, :attributes => {:name => 'project[parent_id]'}
114 assert_no_tag :select, :attributes => {:name => 'project[parent_id]'}
115 end
115 end
116 end
116 end
117
117
118 context "by non-admin user with add_subprojects permission" do
118 context "by non-admin user with add_subprojects permission" do
119 setup do
119 setup do
120 Role.find(1).remove_permission! :add_project
120 Role.find(1).remove_permission! :add_project
121 Role.find(1).add_permission! :add_subprojects
121 Role.find(1).add_permission! :add_subprojects
122 @request.session[:user_id] = 2
122 @request.session[:user_id] = 2
123 end
123 end
124
124
125 should "accept get" do
125 should "accept get" do
126 get :new, :parent_id => 'ecookbook'
126 get :new, :parent_id => 'ecookbook'
127 assert_response :success
127 assert_response :success
128 assert_template 'new'
128 assert_template 'new'
129 # parent project selected
129 # parent project selected
130 assert_tag :select, :attributes => {:name => 'project[parent_id]'},
130 assert_tag :select, :attributes => {:name => 'project[parent_id]'},
131 :child => {:tag => 'option', :attributes => {:value => '1', :selected => 'selected'}}
131 :child => {:tag => 'option', :attributes => {:value => '1', :selected => 'selected'}}
132 # no empty value
132 # no empty value
133 assert_no_tag :select, :attributes => {:name => 'project[parent_id]'},
133 assert_no_tag :select, :attributes => {:name => 'project[parent_id]'},
134 :child => {:tag => 'option', :attributes => {:value => ''}}
134 :child => {:tag => 'option', :attributes => {:value => ''}}
135 end
135 end
136 end
136 end
137
137
138 end
138 end
139
139
140 context "POST :create" do
140 context "POST :create" do
141 context "by admin user" do
141 context "by admin user" do
142 setup do
142 setup do
143 @request.session[:user_id] = 1
143 @request.session[:user_id] = 1
144 end
144 end
145
145
146 should "create a new project" do
146 should "create a new project" do
147 post :create,
147 post :create,
148 :project => {
148 :project => {
149 :name => "blog",
149 :name => "blog",
150 :description => "weblog",
150 :description => "weblog",
151 :homepage => 'http://weblog',
151 :homepage => 'http://weblog',
152 :identifier => "blog",
152 :identifier => "blog",
153 :is_public => 1,
153 :is_public => 1,
154 :custom_field_values => { '3' => 'Beta' },
154 :custom_field_values => { '3' => 'Beta' },
155 :tracker_ids => ['1', '3'],
155 :tracker_ids => ['1', '3'],
156 # an issue custom field that is not for all project
156 # an issue custom field that is not for all project
157 :issue_custom_field_ids => ['9'],
157 :issue_custom_field_ids => ['9'],
158 :enabled_module_names => ['issue_tracking', 'news', 'repository']
158 :enabled_module_names => ['issue_tracking', 'news', 'repository']
159 }
159 }
160 assert_redirected_to '/projects/blog/settings'
160 assert_redirected_to '/projects/blog/settings'
161
161
162 project = Project.find_by_name('blog')
162 project = Project.find_by_name('blog')
163 assert_kind_of Project, project
163 assert_kind_of Project, project
164 assert project.active?
164 assert project.active?
165 assert_equal 'weblog', project.description
165 assert_equal 'weblog', project.description
166 assert_equal 'http://weblog', project.homepage
166 assert_equal 'http://weblog', project.homepage
167 assert_equal true, project.is_public?
167 assert_equal true, project.is_public?
168 assert_nil project.parent
168 assert_nil project.parent
169 assert_equal 'Beta', project.custom_value_for(3).value
169 assert_equal 'Beta', project.custom_value_for(3).value
170 assert_equal [1, 3], project.trackers.map(&:id).sort
170 assert_equal [1, 3], project.trackers.map(&:id).sort
171 assert_equal ['issue_tracking', 'news', 'repository'], project.enabled_module_names.sort
171 assert_equal ['issue_tracking', 'news', 'repository'], project.enabled_module_names.sort
172 assert project.issue_custom_fields.include?(IssueCustomField.find(9))
172 assert project.issue_custom_fields.include?(IssueCustomField.find(9))
173 end
173 end
174
174
175 should "create a new subproject" do
175 should "create a new subproject" do
176 post :create, :project => { :name => "blog",
176 post :create, :project => { :name => "blog",
177 :description => "weblog",
177 :description => "weblog",
178 :identifier => "blog",
178 :identifier => "blog",
179 :is_public => 1,
179 :is_public => 1,
180 :custom_field_values => { '3' => 'Beta' },
180 :custom_field_values => { '3' => 'Beta' },
181 :parent_id => 1
181 :parent_id => 1
182 }
182 }
183 assert_redirected_to '/projects/blog/settings'
183 assert_redirected_to '/projects/blog/settings'
184
184
185 project = Project.find_by_name('blog')
185 project = Project.find_by_name('blog')
186 assert_kind_of Project, project
186 assert_kind_of Project, project
187 assert_equal Project.find(1), project.parent
187 assert_equal Project.find(1), project.parent
188 end
188 end
189 end
189 end
190
190
191 context "by non-admin user with add_project permission" do
191 context "by non-admin user with add_project permission" do
192 setup do
192 setup do
193 Role.non_member.add_permission! :add_project
193 Role.non_member.add_permission! :add_project
194 @request.session[:user_id] = 9
194 @request.session[:user_id] = 9
195 end
195 end
196
196
197 should "accept create a Project" do
197 should "accept create a Project" do
198 post :create, :project => { :name => "blog",
198 post :create, :project => { :name => "blog",
199 :description => "weblog",
199 :description => "weblog",
200 :identifier => "blog",
200 :identifier => "blog",
201 :is_public => 1,
201 :is_public => 1,
202 :custom_field_values => { '3' => 'Beta' },
202 :custom_field_values => { '3' => 'Beta' },
203 :tracker_ids => ['1', '3'],
203 :tracker_ids => ['1', '3'],
204 :enabled_module_names => ['issue_tracking', 'news', 'repository']
204 :enabled_module_names => ['issue_tracking', 'news', 'repository']
205 }
205 }
206
206
207 assert_redirected_to '/projects/blog/settings'
207 assert_redirected_to '/projects/blog/settings'
208
208
209 project = Project.find_by_name('blog')
209 project = Project.find_by_name('blog')
210 assert_kind_of Project, project
210 assert_kind_of Project, project
211 assert_equal 'weblog', project.description
211 assert_equal 'weblog', project.description
212 assert_equal true, project.is_public?
212 assert_equal true, project.is_public?
213 assert_equal [1, 3], project.trackers.map(&:id).sort
213 assert_equal [1, 3], project.trackers.map(&:id).sort
214 assert_equal ['issue_tracking', 'news', 'repository'], project.enabled_module_names.sort
214 assert_equal ['issue_tracking', 'news', 'repository'], project.enabled_module_names.sort
215
215
216 # User should be added as a project member
216 # User should be added as a project member
217 assert User.find(9).member_of?(project)
217 assert User.find(9).member_of?(project)
218 assert_equal 1, project.members.size
218 assert_equal 1, project.members.size
219 end
219 end
220
220
221 should "fail with parent_id" do
221 should "fail with parent_id" do
222 assert_no_difference 'Project.count' do
222 assert_no_difference 'Project.count' do
223 post :create, :project => { :name => "blog",
223 post :create, :project => { :name => "blog",
224 :description => "weblog",
224 :description => "weblog",
225 :identifier => "blog",
225 :identifier => "blog",
226 :is_public => 1,
226 :is_public => 1,
227 :custom_field_values => { '3' => 'Beta' },
227 :custom_field_values => { '3' => 'Beta' },
228 :parent_id => 1
228 :parent_id => 1
229 }
229 }
230 end
230 end
231 assert_response :success
231 assert_response :success
232 project = assigns(:project)
232 project = assigns(:project)
233 assert_kind_of Project, project
233 assert_kind_of Project, project
234 assert_not_nil project.errors.on(:parent_id)
234 assert_not_nil project.errors.on(:parent_id)
235 end
235 end
236 end
236 end
237
237
238 context "by non-admin user with add_subprojects permission" do
238 context "by non-admin user with add_subprojects permission" do
239 setup do
239 setup do
240 Role.find(1).remove_permission! :add_project
240 Role.find(1).remove_permission! :add_project
241 Role.find(1).add_permission! :add_subprojects
241 Role.find(1).add_permission! :add_subprojects
242 @request.session[:user_id] = 2
242 @request.session[:user_id] = 2
243 end
243 end
244
244
245 should "create a project with a parent_id" do
245 should "create a project with a parent_id" do
246 post :create, :project => { :name => "blog",
246 post :create, :project => { :name => "blog",
247 :description => "weblog",
247 :description => "weblog",
248 :identifier => "blog",
248 :identifier => "blog",
249 :is_public => 1,
249 :is_public => 1,
250 :custom_field_values => { '3' => 'Beta' },
250 :custom_field_values => { '3' => 'Beta' },
251 :parent_id => 1
251 :parent_id => 1
252 }
252 }
253 assert_redirected_to '/projects/blog/settings'
253 assert_redirected_to '/projects/blog/settings'
254 project = Project.find_by_name('blog')
254 project = Project.find_by_name('blog')
255 end
255 end
256
256
257 should "fail without parent_id" do
257 should "fail without parent_id" do
258 assert_no_difference 'Project.count' do
258 assert_no_difference 'Project.count' do
259 post :create, :project => { :name => "blog",
259 post :create, :project => { :name => "blog",
260 :description => "weblog",
260 :description => "weblog",
261 :identifier => "blog",
261 :identifier => "blog",
262 :is_public => 1,
262 :is_public => 1,
263 :custom_field_values => { '3' => 'Beta' }
263 :custom_field_values => { '3' => 'Beta' }
264 }
264 }
265 end
265 end
266 assert_response :success
266 assert_response :success
267 project = assigns(:project)
267 project = assigns(:project)
268 assert_kind_of Project, project
268 assert_kind_of Project, project
269 assert_not_nil project.errors.on(:parent_id)
269 assert_not_nil project.errors.on(:parent_id)
270 end
270 end
271
271
272 should "fail with unauthorized parent_id" do
272 should "fail with unauthorized parent_id" do
273 assert !User.find(2).member_of?(Project.find(6))
273 assert !User.find(2).member_of?(Project.find(6))
274 assert_no_difference 'Project.count' do
274 assert_no_difference 'Project.count' do
275 post :create, :project => { :name => "blog",
275 post :create, :project => { :name => "blog",
276 :description => "weblog",
276 :description => "weblog",
277 :identifier => "blog",
277 :identifier => "blog",
278 :is_public => 1,
278 :is_public => 1,
279 :custom_field_values => { '3' => 'Beta' },
279 :custom_field_values => { '3' => 'Beta' },
280 :parent_id => 6
280 :parent_id => 6
281 }
281 }
282 end
282 end
283 assert_response :success
283 assert_response :success
284 project = assigns(:project)
284 project = assigns(:project)
285 assert_kind_of Project, project
285 assert_kind_of Project, project
286 assert_not_nil project.errors.on(:parent_id)
286 assert_not_nil project.errors.on(:parent_id)
287 end
287 end
288 end
288 end
289 end
289 end
290
290
291 context "GET :create" do
292 setup do
293 @request.session[:user_id] = 1
294 end
295
296 should "not be allowed" do
297 get :create
298 assert_response :method_not_allowed
299 end
300 end
301
291 def test_show_by_id
302 def test_show_by_id
292 get :show, :id => 1
303 get :show, :id => 1
293 assert_response :success
304 assert_response :success
294 assert_template 'show'
305 assert_template 'show'
295 assert_not_nil assigns(:project)
306 assert_not_nil assigns(:project)
296 end
307 end
297
308
298 def test_show_by_identifier
309 def test_show_by_identifier
299 get :show, :id => 'ecookbook'
310 get :show, :id => 'ecookbook'
300 assert_response :success
311 assert_response :success
301 assert_template 'show'
312 assert_template 'show'
302 assert_not_nil assigns(:project)
313 assert_not_nil assigns(:project)
303 assert_equal Project.find_by_identifier('ecookbook'), assigns(:project)
314 assert_equal Project.find_by_identifier('ecookbook'), assigns(:project)
304
315
305 assert_tag 'li', :content => /Development status/
316 assert_tag 'li', :content => /Development status/
306 end
317 end
307
318
308 def test_show_should_not_display_hidden_custom_fields
319 def test_show_should_not_display_hidden_custom_fields
309 ProjectCustomField.find_by_name('Development status').update_attribute :visible, false
320 ProjectCustomField.find_by_name('Development status').update_attribute :visible, false
310 get :show, :id => 'ecookbook'
321 get :show, :id => 'ecookbook'
311 assert_response :success
322 assert_response :success
312 assert_template 'show'
323 assert_template 'show'
313 assert_not_nil assigns(:project)
324 assert_not_nil assigns(:project)
314
325
315 assert_no_tag 'li', :content => /Development status/
326 assert_no_tag 'li', :content => /Development status/
316 end
327 end
317
328
318 def test_show_should_not_fail_when_custom_values_are_nil
329 def test_show_should_not_fail_when_custom_values_are_nil
319 project = Project.find_by_identifier('ecookbook')
330 project = Project.find_by_identifier('ecookbook')
320 project.custom_values.first.update_attribute(:value, nil)
331 project.custom_values.first.update_attribute(:value, nil)
321 get :show, :id => 'ecookbook'
332 get :show, :id => 'ecookbook'
322 assert_response :success
333 assert_response :success
323 assert_template 'show'
334 assert_template 'show'
324 assert_not_nil assigns(:project)
335 assert_not_nil assigns(:project)
325 assert_equal Project.find_by_identifier('ecookbook'), assigns(:project)
336 assert_equal Project.find_by_identifier('ecookbook'), assigns(:project)
326 end
337 end
327
338
328 def show_archived_project_should_be_denied
339 def show_archived_project_should_be_denied
329 project = Project.find_by_identifier('ecookbook')
340 project = Project.find_by_identifier('ecookbook')
330 project.archive!
341 project.archive!
331
342
332 get :show, :id => 'ecookbook'
343 get :show, :id => 'ecookbook'
333 assert_response 403
344 assert_response 403
334 assert_nil assigns(:project)
345 assert_nil assigns(:project)
335 assert_tag :tag => 'p', :content => /archived/
346 assert_tag :tag => 'p', :content => /archived/
336 end
347 end
337
348
338 def test_private_subprojects_hidden
349 def test_private_subprojects_hidden
339 get :show, :id => 'ecookbook'
350 get :show, :id => 'ecookbook'
340 assert_response :success
351 assert_response :success
341 assert_template 'show'
352 assert_template 'show'
342 assert_no_tag :tag => 'a', :content => /Private child/
353 assert_no_tag :tag => 'a', :content => /Private child/
343 end
354 end
344
355
345 def test_private_subprojects_visible
356 def test_private_subprojects_visible
346 @request.session[:user_id] = 2 # manager who is a member of the private subproject
357 @request.session[:user_id] = 2 # manager who is a member of the private subproject
347 get :show, :id => 'ecookbook'
358 get :show, :id => 'ecookbook'
348 assert_response :success
359 assert_response :success
349 assert_template 'show'
360 assert_template 'show'
350 assert_tag :tag => 'a', :content => /Private child/
361 assert_tag :tag => 'a', :content => /Private child/
351 end
362 end
352
363
353 def test_settings
364 def test_settings
354 @request.session[:user_id] = 2 # manager
365 @request.session[:user_id] = 2 # manager
355 get :settings, :id => 1
366 get :settings, :id => 1
356 assert_response :success
367 assert_response :success
357 assert_template 'settings'
368 assert_template 'settings'
358 end
369 end
359
370
360 def test_update
371 def test_update
361 @request.session[:user_id] = 2 # manager
372 @request.session[:user_id] = 2 # manager
362 post :update, :id => 1, :project => {:name => 'Test changed name',
373 post :update, :id => 1, :project => {:name => 'Test changed name',
363 :issue_custom_field_ids => ['']}
374 :issue_custom_field_ids => ['']}
364 assert_redirected_to '/projects/ecookbook/settings'
375 assert_redirected_to '/projects/ecookbook/settings'
365 project = Project.find(1)
376 project = Project.find(1)
366 assert_equal 'Test changed name', project.name
377 assert_equal 'Test changed name', project.name
367 end
378 end
368
379
369 def test_get_destroy
380 def test_get_destroy
370 @request.session[:user_id] = 1 # admin
381 @request.session[:user_id] = 1 # admin
371 get :destroy, :id => 1
382 get :destroy, :id => 1
372 assert_response :success
383 assert_response :success
373 assert_template 'destroy'
384 assert_template 'destroy'
374 assert_not_nil Project.find_by_id(1)
385 assert_not_nil Project.find_by_id(1)
375 end
386 end
376
387
377 def test_post_destroy
388 def test_post_destroy
378 @request.session[:user_id] = 1 # admin
389 @request.session[:user_id] = 1 # admin
379 post :destroy, :id => 1, :confirm => 1
390 post :destroy, :id => 1, :confirm => 1
380 assert_redirected_to '/admin/projects'
391 assert_redirected_to '/admin/projects'
381 assert_nil Project.find_by_id(1)
392 assert_nil Project.find_by_id(1)
382 end
393 end
383
394
384 def test_archive
395 def test_archive
385 @request.session[:user_id] = 1 # admin
396 @request.session[:user_id] = 1 # admin
386 post :archive, :id => 1
397 post :archive, :id => 1
387 assert_redirected_to '/admin/projects'
398 assert_redirected_to '/admin/projects'
388 assert !Project.find(1).active?
399 assert !Project.find(1).active?
389 end
400 end
390
401
391 def test_unarchive
402 def test_unarchive
392 @request.session[:user_id] = 1 # admin
403 @request.session[:user_id] = 1 # admin
393 Project.find(1).archive
404 Project.find(1).archive
394 post :unarchive, :id => 1
405 post :unarchive, :id => 1
395 assert_redirected_to '/admin/projects'
406 assert_redirected_to '/admin/projects'
396 assert Project.find(1).active?
407 assert Project.find(1).active?
397 end
408 end
398
409
399 def test_project_breadcrumbs_should_be_limited_to_3_ancestors
410 def test_project_breadcrumbs_should_be_limited_to_3_ancestors
400 CustomField.delete_all
411 CustomField.delete_all
401 parent = nil
412 parent = nil
402 6.times do |i|
413 6.times do |i|
403 p = Project.create!(:name => "Breadcrumbs #{i}", :identifier => "breadcrumbs-#{i}")
414 p = Project.create!(:name => "Breadcrumbs #{i}", :identifier => "breadcrumbs-#{i}")
404 p.set_parent!(parent)
415 p.set_parent!(parent)
405 get :show, :id => p
416 get :show, :id => p
406 assert_tag :h1, :parent => { :attributes => {:id => 'header'}},
417 assert_tag :h1, :parent => { :attributes => {:id => 'header'}},
407 :children => { :count => [i, 3].min,
418 :children => { :count => [i, 3].min,
408 :only => { :tag => 'a' } }
419 :only => { :tag => 'a' } }
409
420
410 parent = p
421 parent = p
411 end
422 end
412 end
423 end
413
424
414 def test_copy_with_project
425 def test_copy_with_project
415 @request.session[:user_id] = 1 # admin
426 @request.session[:user_id] = 1 # admin
416 get :copy, :id => 1
427 get :copy, :id => 1
417 assert_response :success
428 assert_response :success
418 assert_template 'copy'
429 assert_template 'copy'
419 assert assigns(:project)
430 assert assigns(:project)
420 assert_equal Project.find(1).description, assigns(:project).description
431 assert_equal Project.find(1).description, assigns(:project).description
421 assert_nil assigns(:project).id
432 assert_nil assigns(:project).id
422 end
433 end
423
434
424 def test_copy_without_project
435 def test_copy_without_project
425 @request.session[:user_id] = 1 # admin
436 @request.session[:user_id] = 1 # admin
426 get :copy
437 get :copy
427 assert_response :redirect
438 assert_response :redirect
428 assert_redirected_to :controller => 'admin', :action => 'projects'
439 assert_redirected_to :controller => 'admin', :action => 'projects'
429 end
440 end
430
441
431 context "POST :copy" do
442 context "POST :copy" do
432 should "TODO: test the rest of the method"
443 should "TODO: test the rest of the method"
433
444
434 should "redirect to the project settings when successful" do
445 should "redirect to the project settings when successful" do
435 @request.session[:user_id] = 1 # admin
446 @request.session[:user_id] = 1 # admin
436 post :copy, :id => 1, :project => {:name => 'Copy', :identifier => 'unique-copy'}
447 post :copy, :id => 1, :project => {:name => 'Copy', :identifier => 'unique-copy'}
437 assert_response :redirect
448 assert_response :redirect
438 assert_redirected_to :controller => 'projects', :action => 'settings', :id => 'unique-copy'
449 assert_redirected_to :controller => 'projects', :action => 'settings', :id => 'unique-copy'
439 end
450 end
440 end
451 end
441
452
442 def test_jump_should_redirect_to_active_tab
453 def test_jump_should_redirect_to_active_tab
443 get :show, :id => 1, :jump => 'issues'
454 get :show, :id => 1, :jump => 'issues'
444 assert_redirected_to '/projects/ecookbook/issues'
455 assert_redirected_to '/projects/ecookbook/issues'
445 end
456 end
446
457
447 def test_jump_should_not_redirect_to_inactive_tab
458 def test_jump_should_not_redirect_to_inactive_tab
448 get :show, :id => 3, :jump => 'documents'
459 get :show, :id => 3, :jump => 'documents'
449 assert_response :success
460 assert_response :success
450 assert_template 'show'
461 assert_template 'show'
451 end
462 end
452
463
453 def test_jump_should_not_redirect_to_unknown_tab
464 def test_jump_should_not_redirect_to_unknown_tab
454 get :show, :id => 3, :jump => 'foobar'
465 get :show, :id => 3, :jump => 'foobar'
455 assert_response :success
466 assert_response :success
456 assert_template 'show'
467 assert_template 'show'
457 end
468 end
458
469
459 # A hook that is manually registered later
470 # A hook that is manually registered later
460 class ProjectBasedTemplate < Redmine::Hook::ViewListener
471 class ProjectBasedTemplate < Redmine::Hook::ViewListener
461 def view_layouts_base_html_head(context)
472 def view_layouts_base_html_head(context)
462 # Adds a project stylesheet
473 # Adds a project stylesheet
463 stylesheet_link_tag(context[:project].identifier) if context[:project]
474 stylesheet_link_tag(context[:project].identifier) if context[:project]
464 end
475 end
465 end
476 end
466 # Don't use this hook now
477 # Don't use this hook now
467 Redmine::Hook.clear_listeners
478 Redmine::Hook.clear_listeners
468
479
469 def test_hook_response
480 def test_hook_response
470 Redmine::Hook.add_listener(ProjectBasedTemplate)
481 Redmine::Hook.add_listener(ProjectBasedTemplate)
471 get :show, :id => 1
482 get :show, :id => 1
472 assert_tag :tag => 'link', :attributes => {:href => '/stylesheets/ecookbook.css'},
483 assert_tag :tag => 'link', :attributes => {:href => '/stylesheets/ecookbook.css'},
473 :parent => {:tag => 'head'}
484 :parent => {:tag => 'head'}
474
485
475 Redmine::Hook.clear_listeners
486 Redmine::Hook.clear_listeners
476 end
487 end
477 end
488 end
General Comments 0
You need to be logged in to leave comments. Login now