##// END OF EJS Templates
XML REST API for Projects (#296)....
Jean-Philippe Lang -
r3199:68a4cd38f543
parent child
Show More
@@ -0,0 +1,18
1 xml.instruct!
2 xml.projects :type => 'array' do
3 @projects.each do |project|
4 xml.project :id => project.id do
5 xml.name project.name
6 xml.identifier project.identifier
7 xml.description project.description
8 xml.parent(:id => project.parent_id, :name => project.parent.name) unless project.parent.nil?
9 xml.custom_fields do
10 project.custom_field_values.each do |custom_value|
11 xml.custom_field custom_value.value, :id => custom_value.custom_field_id, :name => custom_value.custom_field.name
12 end
13 end unless project.custom_field_values.empty?
14 xml.created_on project.created_on
15 xml.updated_on project.updated_on
16 end
17 end
18 end
@@ -0,0 +1,16
1 xml.instruct!
2 xml.project :id => @project.id do
3 xml.name @project.name
4 xml.identifier @project.identifier
5 xml.description @project.description
6 xml.homepage @project.homepage
7
8 xml.custom_fields do
9 @project.custom_field_values.each do |custom_value|
10 xml.custom_field custom_value.value, :id => custom_value.custom_field_id, :name => custom_value.custom_field.name
11 end
12 end unless @project.custom_field_values.empty?
13
14 xml.created_on @project.created_on
15 xml.updated_on @project.updated_on
16 end
@@ -0,0 +1,134
1 # Redmine - project management software
2 # Copyright (C) 2006-2010 Jean-Philippe Lang
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 require "#{File.dirname(__FILE__)}/../test_helper"
19
20 class ProjectsApiTest < ActionController::IntegrationTest
21 fixtures :projects, :versions, :users, :roles, :members, :member_roles, :issues, :journals, :journal_details,
22 :trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations, :boards, :messages,
23 :attachments, :custom_fields, :custom_values, :time_entries
24
25 def setup
26 Setting.rest_api_enabled = '1'
27 end
28
29 def test_index_routing
30 assert_routing(
31 {:method => :get, :path => '/projects.xml'},
32 :controller => 'projects', :action => 'index', :format => 'xml'
33 )
34 end
35
36 def test_index
37 get '/projects.xml'
38 assert_response :success
39 assert_equal 'application/xml', @response.content_type
40 end
41
42 def test_show_routing
43 assert_routing(
44 {:method => :get, :path => '/projects/1.xml'},
45 :controller => 'projects', :action => 'show', :id => '1', :format => 'xml'
46 )
47 end
48
49 def test_show
50 get '/projects/1.xml'
51 assert_response :success
52 assert_equal 'application/xml', @response.content_type
53 end
54
55 def test_create_routing
56 assert_routing(
57 {:method => :post, :path => '/projects.xml'},
58 :controller => 'projects', :action => 'add', :format => 'xml'
59 )
60 end
61
62 def test_create
63 attributes = {:name => 'API test', :identifier => 'api-test'}
64 assert_difference 'Project.count' do
65 post '/projects.xml', {:project => attributes}, :authorization => credentials('admin')
66 end
67 assert_response :created
68 assert_equal 'application/xml', @response.content_type
69 project = Project.first(:order => 'id DESC')
70 attributes.each do |attribute, value|
71 assert_equal value, project.send(attribute)
72 end
73 end
74
75 def test_create_failure
76 attributes = {:name => 'API test'}
77 assert_no_difference 'Project.count' do
78 post '/projects.xml', {:project => attributes}, :authorization => credentials('admin')
79 end
80 assert_response :unprocessable_entity
81 assert_equal 'application/xml', @response.content_type
82 assert_tag :errors, :child => {:tag => 'error', :content => "Identifier can't be blank"}
83 end
84
85 def test_update_routing
86 assert_routing(
87 {:method => :put, :path => '/projects/1.xml'},
88 :controller => 'projects', :action => 'edit', :id => '1', :format => 'xml'
89 )
90 end
91
92 def test_update
93 attributes = {:name => 'API update'}
94 assert_no_difference 'Project.count' do
95 put '/projects/1.xml', {:project => attributes}, :authorization => credentials('jsmith')
96 end
97 assert_response :ok
98 assert_equal 'application/xml', @response.content_type
99 project = Project.find(1)
100 attributes.each do |attribute, value|
101 assert_equal value, project.send(attribute)
102 end
103 end
104
105 def test_update_failure
106 attributes = {:name => ''}
107 assert_no_difference 'Project.count' do
108 put '/projects/1.xml', {:project => attributes}, :authorization => credentials('jsmith')
109 end
110 assert_response :unprocessable_entity
111 assert_equal 'application/xml', @response.content_type
112 assert_tag :errors, :child => {:tag => 'error', :content => "Name can't be blank"}
113 end
114
115 def test_destroy_routing
116 assert_routing(
117 {:method => :delete, :path => '/projects/1.xml'},
118 :controller => 'projects', :action => 'destroy', :id => '1', :format => 'xml'
119 )
120 end
121
122 def test_destroy
123 assert_difference 'Project.count', -1 do
124 delete '/projects/2.xml', {}, :authorization => credentials('admin')
125 end
126 assert_response :ok
127 assert_equal 'application/xml', @response.content_type
128 assert_nil Project.find_by_id(2)
129 end
130
131 def credentials(user, password=nil)
132 ActionController::HttpAuthentication::Basic.encode_credentials(user, password || user)
133 end
134 end
@@ -1,310 +1,311
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 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 'uri'
18 require 'uri'
19 require 'cgi'
19 require 'cgi'
20
20
21 class ApplicationController < ActionController::Base
21 class ApplicationController < ActionController::Base
22 include Redmine::I18n
22 include Redmine::I18n
23
23
24 layout 'base'
24 layout 'base'
25 exempt_from_layout 'builder'
25
26
26 # Remove broken cookie after upgrade from 0.8.x (#4292)
27 # Remove broken cookie after upgrade from 0.8.x (#4292)
27 # See https://rails.lighthouseapp.com/projects/8994/tickets/3360
28 # See https://rails.lighthouseapp.com/projects/8994/tickets/3360
28 # TODO: remove it when Rails is fixed
29 # TODO: remove it when Rails is fixed
29 before_filter :delete_broken_cookies
30 before_filter :delete_broken_cookies
30 def delete_broken_cookies
31 def delete_broken_cookies
31 if cookies['_redmine_session'] && cookies['_redmine_session'] !~ /--/
32 if cookies['_redmine_session'] && cookies['_redmine_session'] !~ /--/
32 cookies.delete '_redmine_session'
33 cookies.delete '_redmine_session'
33 redirect_to home_path
34 redirect_to home_path
34 return false
35 return false
35 end
36 end
36 end
37 end
37
38
38 before_filter :user_setup, :check_if_login_required, :set_localization
39 before_filter :user_setup, :check_if_login_required, :set_localization
39 filter_parameter_logging :password
40 filter_parameter_logging :password
40 protect_from_forgery
41 protect_from_forgery
41
42
42 rescue_from ActionController::InvalidAuthenticityToken, :with => :invalid_authenticity_token
43 rescue_from ActionController::InvalidAuthenticityToken, :with => :invalid_authenticity_token
43
44
44 include Redmine::Search::Controller
45 include Redmine::Search::Controller
45 include Redmine::MenuManager::MenuController
46 include Redmine::MenuManager::MenuController
46 helper Redmine::MenuManager::MenuHelper
47 helper Redmine::MenuManager::MenuHelper
47
48
48 REDMINE_SUPPORTED_SCM.each do |scm|
49 REDMINE_SUPPORTED_SCM.each do |scm|
49 require_dependency "repository/#{scm.underscore}"
50 require_dependency "repository/#{scm.underscore}"
50 end
51 end
51
52
52 def user_setup
53 def user_setup
53 # Check the settings cache for each request
54 # Check the settings cache for each request
54 Setting.check_cache
55 Setting.check_cache
55 # Find the current user
56 # Find the current user
56 User.current = find_current_user
57 User.current = find_current_user
57 end
58 end
58
59
59 # Returns the current user or nil if no user is logged in
60 # Returns the current user or nil if no user is logged in
60 # and starts a session if needed
61 # and starts a session if needed
61 def find_current_user
62 def find_current_user
62 if session[:user_id]
63 if session[:user_id]
63 # existing session
64 # existing session
64 (User.active.find(session[:user_id]) rescue nil)
65 (User.active.find(session[:user_id]) rescue nil)
65 elsif cookies[:autologin] && Setting.autologin?
66 elsif cookies[:autologin] && Setting.autologin?
66 # auto-login feature starts a new session
67 # auto-login feature starts a new session
67 user = User.try_to_autologin(cookies[:autologin])
68 user = User.try_to_autologin(cookies[:autologin])
68 session[:user_id] = user.id if user
69 session[:user_id] = user.id if user
69 user
70 user
70 elsif params[:format] == 'atom' && params[:key] && accept_key_auth_actions.include?(params[:action])
71 elsif params[:format] == 'atom' && params[:key] && accept_key_auth_actions.include?(params[:action])
71 # RSS key authentication does not start a session
72 # RSS key authentication does not start a session
72 User.find_by_rss_key(params[:key])
73 User.find_by_rss_key(params[:key])
73 elsif Setting.rest_api_enabled? && ['xml', 'json'].include?(params[:format])
74 elsif Setting.rest_api_enabled? && ['xml', 'json'].include?(params[:format])
74 if params[:key].present? && accept_key_auth_actions.include?(params[:action])
75 if params[:key].present? && accept_key_auth_actions.include?(params[:action])
75 # Use API key
76 # Use API key
76 User.find_by_api_key(params[:key])
77 User.find_by_api_key(params[:key])
77 else
78 else
78 # HTTP Basic, either username/password or API key/random
79 # HTTP Basic, either username/password or API key/random
79 authenticate_with_http_basic do |username, password|
80 authenticate_with_http_basic do |username, password|
80 User.try_to_login(username, password) || User.find_by_api_key(username)
81 User.try_to_login(username, password) || User.find_by_api_key(username)
81 end
82 end
82 end
83 end
83 end
84 end
84 end
85 end
85
86
86 # Sets the logged in user
87 # Sets the logged in user
87 def logged_user=(user)
88 def logged_user=(user)
88 reset_session
89 reset_session
89 if user && user.is_a?(User)
90 if user && user.is_a?(User)
90 User.current = user
91 User.current = user
91 session[:user_id] = user.id
92 session[:user_id] = user.id
92 else
93 else
93 User.current = User.anonymous
94 User.current = User.anonymous
94 end
95 end
95 end
96 end
96
97
97 # check if login is globally required to access the application
98 # check if login is globally required to access the application
98 def check_if_login_required
99 def check_if_login_required
99 # no check needed if user is already logged in
100 # no check needed if user is already logged in
100 return true if User.current.logged?
101 return true if User.current.logged?
101 require_login if Setting.login_required?
102 require_login if Setting.login_required?
102 end
103 end
103
104
104 def set_localization
105 def set_localization
105 lang = nil
106 lang = nil
106 if User.current.logged?
107 if User.current.logged?
107 lang = find_language(User.current.language)
108 lang = find_language(User.current.language)
108 end
109 end
109 if lang.nil? && request.env['HTTP_ACCEPT_LANGUAGE']
110 if lang.nil? && request.env['HTTP_ACCEPT_LANGUAGE']
110 accept_lang = parse_qvalues(request.env['HTTP_ACCEPT_LANGUAGE']).first.downcase
111 accept_lang = parse_qvalues(request.env['HTTP_ACCEPT_LANGUAGE']).first.downcase
111 if !accept_lang.blank?
112 if !accept_lang.blank?
112 lang = find_language(accept_lang) || find_language(accept_lang.split('-').first)
113 lang = find_language(accept_lang) || find_language(accept_lang.split('-').first)
113 end
114 end
114 end
115 end
115 lang ||= Setting.default_language
116 lang ||= Setting.default_language
116 set_language_if_valid(lang)
117 set_language_if_valid(lang)
117 end
118 end
118
119
119 def require_login
120 def require_login
120 if !User.current.logged?
121 if !User.current.logged?
121 # Extract only the basic url parameters on non-GET requests
122 # Extract only the basic url parameters on non-GET requests
122 if request.get?
123 if request.get?
123 url = url_for(params)
124 url = url_for(params)
124 else
125 else
125 url = url_for(:controller => params[:controller], :action => params[:action], :id => params[:id], :project_id => params[:project_id])
126 url = url_for(:controller => params[:controller], :action => params[:action], :id => params[:id], :project_id => params[:project_id])
126 end
127 end
127 respond_to do |format|
128 respond_to do |format|
128 format.html { redirect_to :controller => "account", :action => "login", :back_url => url }
129 format.html { redirect_to :controller => "account", :action => "login", :back_url => url }
129 format.atom { redirect_to :controller => "account", :action => "login", :back_url => url }
130 format.atom { redirect_to :controller => "account", :action => "login", :back_url => url }
130 format.xml { head :unauthorized }
131 format.xml { head :unauthorized }
131 format.json { head :unauthorized }
132 format.json { head :unauthorized }
132 end
133 end
133 return false
134 return false
134 end
135 end
135 true
136 true
136 end
137 end
137
138
138 def require_admin
139 def require_admin
139 return unless require_login
140 return unless require_login
140 if !User.current.admin?
141 if !User.current.admin?
141 render_403
142 render_403
142 return false
143 return false
143 end
144 end
144 true
145 true
145 end
146 end
146
147
147 def deny_access
148 def deny_access
148 User.current.logged? ? render_403 : require_login
149 User.current.logged? ? render_403 : require_login
149 end
150 end
150
151
151 # Authorize the user for the requested action
152 # Authorize the user for the requested action
152 def authorize(ctrl = params[:controller], action = params[:action], global = false)
153 def authorize(ctrl = params[:controller], action = params[:action], global = false)
153 allowed = User.current.allowed_to?({:controller => ctrl, :action => action}, @project, :global => global)
154 allowed = User.current.allowed_to?({:controller => ctrl, :action => action}, @project, :global => global)
154 allowed ? true : deny_access
155 allowed ? true : deny_access
155 end
156 end
156
157
157 # Authorize the user for the requested action outside a project
158 # Authorize the user for the requested action outside a project
158 def authorize_global(ctrl = params[:controller], action = params[:action], global = true)
159 def authorize_global(ctrl = params[:controller], action = params[:action], global = true)
159 authorize(ctrl, action, global)
160 authorize(ctrl, action, global)
160 end
161 end
161
162
162 # make sure that the user is a member of the project (or admin) if project is private
163 # make sure that the user is a member of the project (or admin) if project is private
163 # used as a before_filter for actions that do not require any particular permission on the project
164 # used as a before_filter for actions that do not require any particular permission on the project
164 def check_project_privacy
165 def check_project_privacy
165 if @project && @project.active?
166 if @project && @project.active?
166 if @project.is_public? || User.current.member_of?(@project) || User.current.admin?
167 if @project.is_public? || User.current.member_of?(@project) || User.current.admin?
167 true
168 true
168 else
169 else
169 User.current.logged? ? render_403 : require_login
170 User.current.logged? ? render_403 : require_login
170 end
171 end
171 else
172 else
172 @project = nil
173 @project = nil
173 render_404
174 render_404
174 false
175 false
175 end
176 end
176 end
177 end
177
178
178 def redirect_back_or_default(default)
179 def redirect_back_or_default(default)
179 back_url = CGI.unescape(params[:back_url].to_s)
180 back_url = CGI.unescape(params[:back_url].to_s)
180 if !back_url.blank?
181 if !back_url.blank?
181 begin
182 begin
182 uri = URI.parse(back_url)
183 uri = URI.parse(back_url)
183 # do not redirect user to another host or to the login or register page
184 # do not redirect user to another host or to the login or register page
184 if (uri.relative? || (uri.host == request.host)) && !uri.path.match(%r{/(login|account/register)})
185 if (uri.relative? || (uri.host == request.host)) && !uri.path.match(%r{/(login|account/register)})
185 redirect_to(back_url)
186 redirect_to(back_url)
186 return
187 return
187 end
188 end
188 rescue URI::InvalidURIError
189 rescue URI::InvalidURIError
189 # redirect to default
190 # redirect to default
190 end
191 end
191 end
192 end
192 redirect_to default
193 redirect_to default
193 end
194 end
194
195
195 def render_403
196 def render_403
196 @project = nil
197 @project = nil
197 respond_to do |format|
198 respond_to do |format|
198 format.html { render :template => "common/403", :layout => (request.xhr? ? false : 'base'), :status => 403 }
199 format.html { render :template => "common/403", :layout => (request.xhr? ? false : 'base'), :status => 403 }
199 format.atom { head 403 }
200 format.atom { head 403 }
200 format.xml { head 403 }
201 format.xml { head 403 }
201 format.json { head 403 }
202 format.json { head 403 }
202 end
203 end
203 return false
204 return false
204 end
205 end
205
206
206 def render_404
207 def render_404
207 respond_to do |format|
208 respond_to do |format|
208 format.html { render :template => "common/404", :layout => !request.xhr?, :status => 404 }
209 format.html { render :template => "common/404", :layout => !request.xhr?, :status => 404 }
209 format.atom { head 404 }
210 format.atom { head 404 }
210 format.xml { head 404 }
211 format.xml { head 404 }
211 format.json { head 404 }
212 format.json { head 404 }
212 end
213 end
213 return false
214 return false
214 end
215 end
215
216
216 def render_error(msg)
217 def render_error(msg)
217 respond_to do |format|
218 respond_to do |format|
218 format.html {
219 format.html {
219 flash.now[:error] = msg
220 flash.now[:error] = msg
220 render :text => '', :layout => !request.xhr?, :status => 500
221 render :text => '', :layout => !request.xhr?, :status => 500
221 }
222 }
222 format.atom { head 500 }
223 format.atom { head 500 }
223 format.xml { head 500 }
224 format.xml { head 500 }
224 format.json { head 500 }
225 format.json { head 500 }
225 end
226 end
226 end
227 end
227
228
228 def invalid_authenticity_token
229 def invalid_authenticity_token
229 render_error "Invalid form authenticity token."
230 render_error "Invalid form authenticity token."
230 end
231 end
231
232
232 def render_feed(items, options={})
233 def render_feed(items, options={})
233 @items = items || []
234 @items = items || []
234 @items.sort! {|x,y| y.event_datetime <=> x.event_datetime }
235 @items.sort! {|x,y| y.event_datetime <=> x.event_datetime }
235 @items = @items.slice(0, Setting.feeds_limit.to_i)
236 @items = @items.slice(0, Setting.feeds_limit.to_i)
236 @title = options[:title] || Setting.app_title
237 @title = options[:title] || Setting.app_title
237 render :template => "common/feed.atom.rxml", :layout => false, :content_type => 'application/atom+xml'
238 render :template => "common/feed.atom.rxml", :layout => false, :content_type => 'application/atom+xml'
238 end
239 end
239
240
240 def self.accept_key_auth(*actions)
241 def self.accept_key_auth(*actions)
241 actions = actions.flatten.map(&:to_s)
242 actions = actions.flatten.map(&:to_s)
242 write_inheritable_attribute('accept_key_auth_actions', actions)
243 write_inheritable_attribute('accept_key_auth_actions', actions)
243 end
244 end
244
245
245 def accept_key_auth_actions
246 def accept_key_auth_actions
246 self.class.read_inheritable_attribute('accept_key_auth_actions') || []
247 self.class.read_inheritable_attribute('accept_key_auth_actions') || []
247 end
248 end
248
249
249 # TODO: move to model
250 # TODO: move to model
250 def attach_files(obj, attachments)
251 def attach_files(obj, attachments)
251 attached = []
252 attached = []
252 unsaved = []
253 unsaved = []
253 if attachments && attachments.is_a?(Hash)
254 if attachments && attachments.is_a?(Hash)
254 attachments.each_value do |attachment|
255 attachments.each_value do |attachment|
255 file = attachment['file']
256 file = attachment['file']
256 next unless file && file.size > 0
257 next unless file && file.size > 0
257 a = Attachment.create(:container => obj,
258 a = Attachment.create(:container => obj,
258 :file => file,
259 :file => file,
259 :description => attachment['description'].to_s.strip,
260 :description => attachment['description'].to_s.strip,
260 :author => User.current)
261 :author => User.current)
261 a.new_record? ? (unsaved << a) : (attached << a)
262 a.new_record? ? (unsaved << a) : (attached << a)
262 end
263 end
263 if unsaved.any?
264 if unsaved.any?
264 flash[:warning] = l(:warning_attachments_not_saved, unsaved.size)
265 flash[:warning] = l(:warning_attachments_not_saved, unsaved.size)
265 end
266 end
266 end
267 end
267 attached
268 attached
268 end
269 end
269
270
270 # Returns the number of objects that should be displayed
271 # Returns the number of objects that should be displayed
271 # on the paginated list
272 # on the paginated list
272 def per_page_option
273 def per_page_option
273 per_page = nil
274 per_page = nil
274 if params[:per_page] && Setting.per_page_options_array.include?(params[:per_page].to_s.to_i)
275 if params[:per_page] && Setting.per_page_options_array.include?(params[:per_page].to_s.to_i)
275 per_page = params[:per_page].to_s.to_i
276 per_page = params[:per_page].to_s.to_i
276 session[:per_page] = per_page
277 session[:per_page] = per_page
277 elsif session[:per_page]
278 elsif session[:per_page]
278 per_page = session[:per_page]
279 per_page = session[:per_page]
279 else
280 else
280 per_page = Setting.per_page_options_array.first || 25
281 per_page = Setting.per_page_options_array.first || 25
281 end
282 end
282 per_page
283 per_page
283 end
284 end
284
285
285 # qvalues http header parser
286 # qvalues http header parser
286 # code taken from webrick
287 # code taken from webrick
287 def parse_qvalues(value)
288 def parse_qvalues(value)
288 tmp = []
289 tmp = []
289 if value
290 if value
290 parts = value.split(/,\s*/)
291 parts = value.split(/,\s*/)
291 parts.each {|part|
292 parts.each {|part|
292 if m = %r{^([^\s,]+?)(?:;\s*q=(\d+(?:\.\d+)?))?$}.match(part)
293 if m = %r{^([^\s,]+?)(?:;\s*q=(\d+(?:\.\d+)?))?$}.match(part)
293 val = m[1]
294 val = m[1]
294 q = (m[2] or 1).to_f
295 q = (m[2] or 1).to_f
295 tmp.push([val, q])
296 tmp.push([val, q])
296 end
297 end
297 }
298 }
298 tmp = tmp.sort_by{|val, q| -q}
299 tmp = tmp.sort_by{|val, q| -q}
299 tmp.collect!{|val, q| val}
300 tmp.collect!{|val, q| val}
300 end
301 end
301 return tmp
302 return tmp
302 rescue
303 rescue
303 nil
304 nil
304 end
305 end
305
306
306 # Returns a string that can be used as filename value in Content-Disposition header
307 # Returns a string that can be used as filename value in Content-Disposition header
307 def filename_for_content_disposition(name)
308 def filename_for_content_disposition(name)
308 request.env['HTTP_USER_AGENT'] =~ %r{MSIE} ? ERB::Util.url_encode(name) : name
309 request.env['HTTP_USER_AGENT'] =~ %r{MSIE} ? ERB::Util.url_encode(name) : name
309 end
310 end
310 end
311 end
@@ -1,419 +1,455
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 :activity, :only => :activity
20 menu_item :activity, :only => :activity
21 menu_item :roadmap, :only => :roadmap
21 menu_item :roadmap, :only => :roadmap
22 menu_item :files, :only => [:list_files, :add_file]
22 menu_item :files, :only => [:list_files, :add_file]
23 menu_item :settings, :only => :settings
23 menu_item :settings, :only => :settings
24
24
25 before_filter :find_project, :except => [ :index, :list, :add, :copy, :activity ]
25 before_filter :find_project, :except => [ :index, :list, :add, :copy, :activity ]
26 before_filter :find_optional_project, :only => :activity
26 before_filter :find_optional_project, :only => :activity
27 before_filter :authorize, :except => [ :index, :list, :add, :copy, :archive, :unarchive, :destroy, :activity ]
27 before_filter :authorize, :except => [ :index, :list, :add, :copy, :archive, :unarchive, :destroy, :activity ]
28 before_filter :authorize_global, :only => :add
28 before_filter :authorize_global, :only => :add
29 before_filter :require_admin, :only => [ :copy, :archive, :unarchive, :destroy ]
29 before_filter :require_admin, :only => [ :copy, :archive, :unarchive, :destroy ]
30 accept_key_auth :activity
30 accept_key_auth :activity
31
31
32 after_filter :only => [:add, :edit, :archive, :unarchive, :destroy] do |controller|
32 after_filter :only => [:add, :edit, :archive, :unarchive, :destroy] do |controller|
33 if controller.request.post?
33 if controller.request.post?
34 controller.send :expire_action, :controller => 'welcome', :action => 'robots.txt'
34 controller.send :expire_action, :controller => 'welcome', :action => 'robots.txt'
35 end
35 end
36 end
36 end
37
37
38 helper :sort
38 helper :sort
39 include SortHelper
39 include SortHelper
40 helper :custom_fields
40 helper :custom_fields
41 include CustomFieldsHelper
41 include CustomFieldsHelper
42 helper :issues
42 helper :issues
43 helper IssuesHelper
43 helper IssuesHelper
44 helper :queries
44 helper :queries
45 include QueriesHelper
45 include QueriesHelper
46 helper :repositories
46 helper :repositories
47 include RepositoriesHelper
47 include RepositoriesHelper
48 include ProjectsHelper
48 include ProjectsHelper
49
49
50 # Lists visible projects
50 # Lists visible projects
51 def index
51 def index
52 respond_to do |format|
52 respond_to do |format|
53 format.html {
53 format.html {
54 @projects = Project.visible.find(:all, :order => 'lft')
54 @projects = Project.visible.find(:all, :order => 'lft')
55 }
55 }
56 format.xml {
57 @projects = Project.visible.find(:all, :order => 'lft')
58 }
56 format.atom {
59 format.atom {
57 projects = Project.visible.find(:all, :order => 'created_on DESC',
60 projects = Project.visible.find(:all, :order => 'created_on DESC',
58 :limit => Setting.feeds_limit.to_i)
61 :limit => Setting.feeds_limit.to_i)
59 render_feed(projects, :title => "#{Setting.app_title}: #{l(:label_project_latest)}")
62 render_feed(projects, :title => "#{Setting.app_title}: #{l(:label_project_latest)}")
60 }
63 }
61 end
64 end
62 end
65 end
63
66
64 # Add a new project
67 # Add a new project
65 def add
68 def add
66 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
69 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
67 @trackers = Tracker.all
70 @trackers = Tracker.all
68 @project = Project.new(params[:project])
71 @project = Project.new(params[:project])
69 if request.get?
72 if request.get?
70 @project.identifier = Project.next_identifier if Setting.sequential_project_identifiers?
73 @project.identifier = Project.next_identifier if Setting.sequential_project_identifiers?
71 @project.trackers = Tracker.all
74 @project.trackers = Tracker.all
72 @project.is_public = Setting.default_projects_public?
75 @project.is_public = Setting.default_projects_public?
73 @project.enabled_module_names = Setting.default_projects_modules
76 @project.enabled_module_names = Setting.default_projects_modules
74 else
77 else
75 @project.enabled_module_names = params[:enabled_modules]
78 @project.enabled_module_names = params[:enabled_modules]
76 if validate_parent_id && @project.save
79 if validate_parent_id && @project.save
77 @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
80 @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
78 # Add current user as a project member if he is not admin
81 # Add current user as a project member if he is not admin
79 unless User.current.admin?
82 unless User.current.admin?
80 r = Role.givable.find_by_id(Setting.new_project_user_role_id.to_i) || Role.givable.first
83 r = Role.givable.find_by_id(Setting.new_project_user_role_id.to_i) || Role.givable.first
81 m = Member.new(:user => User.current, :roles => [r])
84 m = Member.new(:user => User.current, :roles => [r])
82 @project.members << m
85 @project.members << m
83 end
86 end
84 flash[:notice] = l(:notice_successful_create)
87 respond_to do |format|
85 redirect_to :controller => 'projects', :action => 'settings', :id => @project
88 format.html {
89 flash[:notice] = l(:notice_successful_create)
90 redirect_to :controller => 'projects', :action => 'settings', :id => @project
91 }
92 format.xml { head :created, :location => url_for(:controller => 'projects', :action => 'show', :id => @project.id) }
93 end
94 else
95 respond_to do |format|
96 format.html
97 format.xml { render :xml => @project.errors, :status => :unprocessable_entity }
98 end
86 end
99 end
87 end
100 end
88 end
101 end
89
102
90 def copy
103 def copy
91 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
104 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
92 @trackers = Tracker.all
105 @trackers = Tracker.all
93 @root_projects = Project.find(:all,
106 @root_projects = Project.find(:all,
94 :conditions => "parent_id IS NULL AND status = #{Project::STATUS_ACTIVE}",
107 :conditions => "parent_id IS NULL AND status = #{Project::STATUS_ACTIVE}",
95 :order => 'name')
108 :order => 'name')
96 @source_project = Project.find(params[:id])
109 @source_project = Project.find(params[:id])
97 if request.get?
110 if request.get?
98 @project = Project.copy_from(@source_project)
111 @project = Project.copy_from(@source_project)
99 if @project
112 if @project
100 @project.identifier = Project.next_identifier if Setting.sequential_project_identifiers?
113 @project.identifier = Project.next_identifier if Setting.sequential_project_identifiers?
101 else
114 else
102 redirect_to :controller => 'admin', :action => 'projects'
115 redirect_to :controller => 'admin', :action => 'projects'
103 end
116 end
104 else
117 else
105 @project = Project.new(params[:project])
118 @project = Project.new(params[:project])
106 @project.enabled_module_names = params[:enabled_modules]
119 @project.enabled_module_names = params[:enabled_modules]
107 if validate_parent_id && @project.copy(@source_project, :only => params[:only])
120 if validate_parent_id && @project.copy(@source_project, :only => params[:only])
108 @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
121 @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
109 flash[:notice] = l(:notice_successful_create)
122 flash[:notice] = l(:notice_successful_create)
110 redirect_to :controller => 'admin', :action => 'projects'
123 redirect_to :controller => 'admin', :action => 'projects'
111 elsif !@project.new_record?
124 elsif !@project.new_record?
112 # Project was created
125 # Project was created
113 # But some objects were not copied due to validation failures
126 # But some objects were not copied due to validation failures
114 # (eg. issues from disabled trackers)
127 # (eg. issues from disabled trackers)
115 # TODO: inform about that
128 # TODO: inform about that
116 redirect_to :controller => 'admin', :action => 'projects'
129 redirect_to :controller => 'admin', :action => 'projects'
117 end
130 end
118 end
131 end
119 rescue ActiveRecord::RecordNotFound
132 rescue ActiveRecord::RecordNotFound
120 redirect_to :controller => 'admin', :action => 'projects'
133 redirect_to :controller => 'admin', :action => 'projects'
121 end
134 end
122
135
123 # Show @project
136 # Show @project
124 def show
137 def show
125 if params[:jump]
138 if params[:jump]
126 # try to redirect to the requested menu item
139 # try to redirect to the requested menu item
127 redirect_to_project_menu_item(@project, params[:jump]) && return
140 redirect_to_project_menu_item(@project, params[:jump]) && return
128 end
141 end
129
142
130 @users_by_role = @project.users_by_role
143 @users_by_role = @project.users_by_role
131 @subprojects = @project.children.visible
144 @subprojects = @project.children.visible
132 @news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
145 @news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
133 @trackers = @project.rolled_up_trackers
146 @trackers = @project.rolled_up_trackers
134
147
135 cond = @project.project_condition(Setting.display_subprojects_issues?)
148 cond = @project.project_condition(Setting.display_subprojects_issues?)
136
149
137 @open_issues_by_tracker = Issue.visible.count(:group => :tracker,
150 @open_issues_by_tracker = Issue.visible.count(:group => :tracker,
138 :include => [:project, :status, :tracker],
151 :include => [:project, :status, :tracker],
139 :conditions => ["(#{cond}) AND #{IssueStatus.table_name}.is_closed=?", false])
152 :conditions => ["(#{cond}) AND #{IssueStatus.table_name}.is_closed=?", false])
140 @total_issues_by_tracker = Issue.visible.count(:group => :tracker,
153 @total_issues_by_tracker = Issue.visible.count(:group => :tracker,
141 :include => [:project, :status, :tracker],
154 :include => [:project, :status, :tracker],
142 :conditions => cond)
155 :conditions => cond)
143
156
144 TimeEntry.visible_by(User.current) do
157 TimeEntry.visible_by(User.current) do
145 @total_hours = TimeEntry.sum(:hours,
158 @total_hours = TimeEntry.sum(:hours,
146 :include => :project,
159 :include => :project,
147 :conditions => cond).to_f
160 :conditions => cond).to_f
148 end
161 end
149 @key = User.current.rss_key
162 @key = User.current.rss_key
163
164 respond_to do |format|
165 format.html
166 format.xml
167 end
150 end
168 end
151
169
152 def settings
170 def settings
153 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
171 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
154 @issue_category ||= IssueCategory.new
172 @issue_category ||= IssueCategory.new
155 @member ||= @project.members.new
173 @member ||= @project.members.new
156 @trackers = Tracker.all
174 @trackers = Tracker.all
157 @repository ||= @project.repository
175 @repository ||= @project.repository
158 @wiki ||= @project.wiki
176 @wiki ||= @project.wiki
159 end
177 end
160
178
161 # Edit @project
179 # Edit @project
162 def edit
180 def edit
163 if request.post?
181 if request.get?
182 else
164 @project.attributes = params[:project]
183 @project.attributes = params[:project]
165 if validate_parent_id && @project.save
184 if validate_parent_id && @project.save
166 @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
185 @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
167 flash[:notice] = l(:notice_successful_update)
186 respond_to do |format|
168 redirect_to :action => 'settings', :id => @project
187 format.html {
188 flash[:notice] = l(:notice_successful_update)
189 redirect_to :action => 'settings', :id => @project
190 }
191 format.xml { head :ok }
192 end
169 else
193 else
170 settings
194 respond_to do |format|
171 render :action => 'settings'
195 format.html {
196 settings
197 render :action => 'settings'
198 }
199 format.xml { render :xml => @project.errors, :status => :unprocessable_entity }
200 end
172 end
201 end
173 end
202 end
174 end
203 end
175
204
176 def modules
205 def modules
177 @project.enabled_module_names = params[:enabled_modules]
206 @project.enabled_module_names = params[:enabled_modules]
178 redirect_to :action => 'settings', :id => @project, :tab => 'modules'
207 redirect_to :action => 'settings', :id => @project, :tab => 'modules'
179 end
208 end
180
209
181 def archive
210 def archive
182 if request.post?
211 if request.post?
183 unless @project.archive
212 unless @project.archive
184 flash[:error] = l(:error_can_not_archive_project)
213 flash[:error] = l(:error_can_not_archive_project)
185 end
214 end
186 end
215 end
187 redirect_to(url_for(:controller => 'admin', :action => 'projects', :status => params[:status]))
216 redirect_to(url_for(:controller => 'admin', :action => 'projects', :status => params[:status]))
188 end
217 end
189
218
190 def unarchive
219 def unarchive
191 @project.unarchive if request.post? && !@project.active?
220 @project.unarchive if request.post? && !@project.active?
192 redirect_to(url_for(:controller => 'admin', :action => 'projects', :status => params[:status]))
221 redirect_to(url_for(:controller => 'admin', :action => 'projects', :status => params[:status]))
193 end
222 end
194
223
195 # Delete @project
224 # Delete @project
196 def destroy
225 def destroy
197 @project_to_destroy = @project
226 @project_to_destroy = @project
198 if request.post? and params[:confirm]
227 if request.get?
199 @project_to_destroy.destroy
228 # display confirmation view
200 redirect_to :controller => 'admin', :action => 'projects'
229 else
230 if params[:format] == 'xml' || params[:confirm]
231 @project_to_destroy.destroy
232 respond_to do |format|
233 format.html { redirect_to :controller => 'admin', :action => 'projects' }
234 format.xml { head :ok }
235 end
236 end
201 end
237 end
202 # hide project in layout
238 # hide project in layout
203 @project = nil
239 @project = nil
204 end
240 end
205
241
206 # Add a new issue category to @project
242 # Add a new issue category to @project
207 def add_issue_category
243 def add_issue_category
208 @category = @project.issue_categories.build(params[:category])
244 @category = @project.issue_categories.build(params[:category])
209 if request.post?
245 if request.post?
210 if @category.save
246 if @category.save
211 respond_to do |format|
247 respond_to do |format|
212 format.html do
248 format.html do
213 flash[:notice] = l(:notice_successful_create)
249 flash[:notice] = l(:notice_successful_create)
214 redirect_to :action => 'settings', :tab => 'categories', :id => @project
250 redirect_to :action => 'settings', :tab => 'categories', :id => @project
215 end
251 end
216 format.js do
252 format.js do
217 # IE doesn't support the replace_html rjs method for select box options
253 # IE doesn't support the replace_html rjs method for select box options
218 render(:update) {|page| page.replace "issue_category_id",
254 render(:update) {|page| page.replace "issue_category_id",
219 content_tag('select', '<option></option>' + options_from_collection_for_select(@project.issue_categories, 'id', 'name', @category.id), :id => 'issue_category_id', :name => 'issue[category_id]')
255 content_tag('select', '<option></option>' + options_from_collection_for_select(@project.issue_categories, 'id', 'name', @category.id), :id => 'issue_category_id', :name => 'issue[category_id]')
220 }
256 }
221 end
257 end
222 end
258 end
223 else
259 else
224 respond_to do |format|
260 respond_to do |format|
225 format.html
261 format.html
226 format.js do
262 format.js do
227 render(:update) {|page| page.alert(@category.errors.full_messages.join('\n')) }
263 render(:update) {|page| page.alert(@category.errors.full_messages.join('\n')) }
228 end
264 end
229 end
265 end
230 end
266 end
231 end
267 end
232 end
268 end
233
269
234 # Add a new version to @project
270 # Add a new version to @project
235 def add_version
271 def add_version
236 @version = @project.versions.build
272 @version = @project.versions.build
237 if params[:version]
273 if params[:version]
238 attributes = params[:version].dup
274 attributes = params[:version].dup
239 attributes.delete('sharing') unless attributes.nil? || @version.allowed_sharings.include?(attributes['sharing'])
275 attributes.delete('sharing') unless attributes.nil? || @version.allowed_sharings.include?(attributes['sharing'])
240 @version.attributes = attributes
276 @version.attributes = attributes
241 end
277 end
242 if request.post?
278 if request.post?
243 if @version.save
279 if @version.save
244 respond_to do |format|
280 respond_to do |format|
245 format.html do
281 format.html do
246 flash[:notice] = l(:notice_successful_create)
282 flash[:notice] = l(:notice_successful_create)
247 redirect_to :action => 'settings', :tab => 'versions', :id => @project
283 redirect_to :action => 'settings', :tab => 'versions', :id => @project
248 end
284 end
249 format.js do
285 format.js do
250 # IE doesn't support the replace_html rjs method for select box options
286 # IE doesn't support the replace_html rjs method for select box options
251 render(:update) {|page| page.replace "issue_fixed_version_id",
287 render(:update) {|page| page.replace "issue_fixed_version_id",
252 content_tag('select', '<option></option>' + version_options_for_select(@project.shared_versions.open, @version), :id => 'issue_fixed_version_id', :name => 'issue[fixed_version_id]')
288 content_tag('select', '<option></option>' + version_options_for_select(@project.shared_versions.open, @version), :id => 'issue_fixed_version_id', :name => 'issue[fixed_version_id]')
253 }
289 }
254 end
290 end
255 end
291 end
256 else
292 else
257 respond_to do |format|
293 respond_to do |format|
258 format.html
294 format.html
259 format.js do
295 format.js do
260 render(:update) {|page| page.alert(@version.errors.full_messages.join('\n')) }
296 render(:update) {|page| page.alert(@version.errors.full_messages.join('\n')) }
261 end
297 end
262 end
298 end
263 end
299 end
264 end
300 end
265 end
301 end
266
302
267 def add_file
303 def add_file
268 if request.post?
304 if request.post?
269 container = (params[:version_id].blank? ? @project : @project.versions.find_by_id(params[:version_id]))
305 container = (params[:version_id].blank? ? @project : @project.versions.find_by_id(params[:version_id]))
270 attachments = attach_files(container, params[:attachments])
306 attachments = attach_files(container, params[:attachments])
271 if !attachments.empty? && Setting.notified_events.include?('file_added')
307 if !attachments.empty? && Setting.notified_events.include?('file_added')
272 Mailer.deliver_attachments_added(attachments)
308 Mailer.deliver_attachments_added(attachments)
273 end
309 end
274 redirect_to :controller => 'projects', :action => 'list_files', :id => @project
310 redirect_to :controller => 'projects', :action => 'list_files', :id => @project
275 return
311 return
276 end
312 end
277 @versions = @project.versions.sort
313 @versions = @project.versions.sort
278 end
314 end
279
315
280 def save_activities
316 def save_activities
281 if request.post? && params[:enumerations]
317 if request.post? && params[:enumerations]
282 Project.transaction do
318 Project.transaction do
283 params[:enumerations].each do |id, activity|
319 params[:enumerations].each do |id, activity|
284 @project.update_or_create_time_entry_activity(id, activity)
320 @project.update_or_create_time_entry_activity(id, activity)
285 end
321 end
286 end
322 end
287 end
323 end
288
324
289 redirect_to :controller => 'projects', :action => 'settings', :tab => 'activities', :id => @project
325 redirect_to :controller => 'projects', :action => 'settings', :tab => 'activities', :id => @project
290 end
326 end
291
327
292 def reset_activities
328 def reset_activities
293 @project.time_entry_activities.each do |time_entry_activity|
329 @project.time_entry_activities.each do |time_entry_activity|
294 time_entry_activity.destroy(time_entry_activity.parent)
330 time_entry_activity.destroy(time_entry_activity.parent)
295 end
331 end
296 redirect_to :controller => 'projects', :action => 'settings', :tab => 'activities', :id => @project
332 redirect_to :controller => 'projects', :action => 'settings', :tab => 'activities', :id => @project
297 end
333 end
298
334
299 def list_files
335 def list_files
300 sort_init 'filename', 'asc'
336 sort_init 'filename', 'asc'
301 sort_update 'filename' => "#{Attachment.table_name}.filename",
337 sort_update 'filename' => "#{Attachment.table_name}.filename",
302 'created_on' => "#{Attachment.table_name}.created_on",
338 'created_on' => "#{Attachment.table_name}.created_on",
303 'size' => "#{Attachment.table_name}.filesize",
339 'size' => "#{Attachment.table_name}.filesize",
304 'downloads' => "#{Attachment.table_name}.downloads"
340 'downloads' => "#{Attachment.table_name}.downloads"
305
341
306 @containers = [ Project.find(@project.id, :include => :attachments, :order => sort_clause)]
342 @containers = [ Project.find(@project.id, :include => :attachments, :order => sort_clause)]
307 @containers += @project.versions.find(:all, :include => :attachments, :order => sort_clause).sort.reverse
343 @containers += @project.versions.find(:all, :include => :attachments, :order => sort_clause).sort.reverse
308 render :layout => !request.xhr?
344 render :layout => !request.xhr?
309 end
345 end
310
346
311 def roadmap
347 def roadmap
312 @trackers = @project.trackers.find(:all, :order => 'position')
348 @trackers = @project.trackers.find(:all, :order => 'position')
313 retrieve_selected_tracker_ids(@trackers, @trackers.select {|t| t.is_in_roadmap?})
349 retrieve_selected_tracker_ids(@trackers, @trackers.select {|t| t.is_in_roadmap?})
314 @with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1')
350 @with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1')
315 project_ids = @with_subprojects ? @project.self_and_descendants.collect(&:id) : [@project.id]
351 project_ids = @with_subprojects ? @project.self_and_descendants.collect(&:id) : [@project.id]
316
352
317 @versions = @project.shared_versions.sort
353 @versions = @project.shared_versions.sort
318 @versions.reject! {|version| version.closed? || version.completed? } unless params[:completed]
354 @versions.reject! {|version| version.closed? || version.completed? } unless params[:completed]
319
355
320 @issues_by_version = {}
356 @issues_by_version = {}
321 unless @selected_tracker_ids.empty?
357 unless @selected_tracker_ids.empty?
322 @versions.each do |version|
358 @versions.each do |version|
323 conditions = {:tracker_id => @selected_tracker_ids}
359 conditions = {:tracker_id => @selected_tracker_ids}
324 if !@project.versions.include?(version)
360 if !@project.versions.include?(version)
325 conditions.merge!(:project_id => project_ids)
361 conditions.merge!(:project_id => project_ids)
326 end
362 end
327 issues = version.fixed_issues.visible.find(:all,
363 issues = version.fixed_issues.visible.find(:all,
328 :include => [:project, :status, :tracker, :priority],
364 :include => [:project, :status, :tracker, :priority],
329 :conditions => conditions,
365 :conditions => conditions,
330 :order => "#{Project.table_name}.lft, #{Tracker.table_name}.position, #{Issue.table_name}.id")
366 :order => "#{Project.table_name}.lft, #{Tracker.table_name}.position, #{Issue.table_name}.id")
331 @issues_by_version[version] = issues
367 @issues_by_version[version] = issues
332 end
368 end
333 end
369 end
334 @versions.reject! {|version| !project_ids.include?(version.project_id) && @issues_by_version[version].empty?}
370 @versions.reject! {|version| !project_ids.include?(version.project_id) && @issues_by_version[version].empty?}
335 end
371 end
336
372
337 def activity
373 def activity
338 @days = Setting.activity_days_default.to_i
374 @days = Setting.activity_days_default.to_i
339
375
340 if params[:from]
376 if params[:from]
341 begin; @date_to = params[:from].to_date + 1; rescue; end
377 begin; @date_to = params[:from].to_date + 1; rescue; end
342 end
378 end
343
379
344 @date_to ||= Date.today + 1
380 @date_to ||= Date.today + 1
345 @date_from = @date_to - @days
381 @date_from = @date_to - @days
346 @with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1')
382 @with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1')
347 @author = (params[:user_id].blank? ? nil : User.active.find(params[:user_id]))
383 @author = (params[:user_id].blank? ? nil : User.active.find(params[:user_id]))
348
384
349 @activity = Redmine::Activity::Fetcher.new(User.current, :project => @project,
385 @activity = Redmine::Activity::Fetcher.new(User.current, :project => @project,
350 :with_subprojects => @with_subprojects,
386 :with_subprojects => @with_subprojects,
351 :author => @author)
387 :author => @author)
352 @activity.scope_select {|t| !params["show_#{t}"].nil?}
388 @activity.scope_select {|t| !params["show_#{t}"].nil?}
353 @activity.scope = (@author.nil? ? :default : :all) if @activity.scope.empty?
389 @activity.scope = (@author.nil? ? :default : :all) if @activity.scope.empty?
354
390
355 events = @activity.events(@date_from, @date_to)
391 events = @activity.events(@date_from, @date_to)
356
392
357 if events.empty? || stale?(:etag => [events.first, User.current])
393 if events.empty? || stale?(:etag => [events.first, User.current])
358 respond_to do |format|
394 respond_to do |format|
359 format.html {
395 format.html {
360 @events_by_day = events.group_by(&:event_date)
396 @events_by_day = events.group_by(&:event_date)
361 render :layout => false if request.xhr?
397 render :layout => false if request.xhr?
362 }
398 }
363 format.atom {
399 format.atom {
364 title = l(:label_activity)
400 title = l(:label_activity)
365 if @author
401 if @author
366 title = @author.name
402 title = @author.name
367 elsif @activity.scope.size == 1
403 elsif @activity.scope.size == 1
368 title = l("label_#{@activity.scope.first.singularize}_plural")
404 title = l("label_#{@activity.scope.first.singularize}_plural")
369 end
405 end
370 render_feed(events, :title => "#{@project || Setting.app_title}: #{title}")
406 render_feed(events, :title => "#{@project || Setting.app_title}: #{title}")
371 }
407 }
372 end
408 end
373 end
409 end
374
410
375 rescue ActiveRecord::RecordNotFound
411 rescue ActiveRecord::RecordNotFound
376 render_404
412 render_404
377 end
413 end
378
414
379 private
415 private
380 # Find project of id params[:id]
416 # Find project of id params[:id]
381 # if not found, redirect to project list
417 # if not found, redirect to project list
382 # Used as a before_filter
418 # Used as a before_filter
383 def find_project
419 def find_project
384 @project = Project.find(params[:id])
420 @project = Project.find(params[:id])
385 rescue ActiveRecord::RecordNotFound
421 rescue ActiveRecord::RecordNotFound
386 render_404
422 render_404
387 end
423 end
388
424
389 def find_optional_project
425 def find_optional_project
390 return true unless params[:id]
426 return true unless params[:id]
391 @project = Project.find(params[:id])
427 @project = Project.find(params[:id])
392 authorize
428 authorize
393 rescue ActiveRecord::RecordNotFound
429 rescue ActiveRecord::RecordNotFound
394 render_404
430 render_404
395 end
431 end
396
432
397 def retrieve_selected_tracker_ids(selectable_trackers, default_trackers=nil)
433 def retrieve_selected_tracker_ids(selectable_trackers, default_trackers=nil)
398 if ids = params[:tracker_ids]
434 if ids = params[:tracker_ids]
399 @selected_tracker_ids = (ids.is_a? Array) ? ids.collect { |id| id.to_i.to_s } : ids.split('/').collect { |id| id.to_i.to_s }
435 @selected_tracker_ids = (ids.is_a? Array) ? ids.collect { |id| id.to_i.to_s } : ids.split('/').collect { |id| id.to_i.to_s }
400 else
436 else
401 @selected_tracker_ids = (default_trackers || selectable_trackers).collect {|t| t.id.to_s }
437 @selected_tracker_ids = (default_trackers || selectable_trackers).collect {|t| t.id.to_s }
402 end
438 end
403 end
439 end
404
440
405 # Validates parent_id param according to user's permissions
441 # Validates parent_id param according to user's permissions
406 # TODO: move it to Project model in a validation that depends on User.current
442 # TODO: move it to Project model in a validation that depends on User.current
407 def validate_parent_id
443 def validate_parent_id
408 return true if User.current.admin?
444 return true if User.current.admin?
409 parent_id = params[:project] && params[:project][:parent_id]
445 parent_id = params[:project] && params[:project][:parent_id]
410 if parent_id || @project.new_record?
446 if parent_id || @project.new_record?
411 parent = parent_id.blank? ? nil : Project.find_by_id(parent_id.to_i)
447 parent = parent_id.blank? ? nil : Project.find_by_id(parent_id.to_i)
412 unless @project.allowed_parents.include?(parent)
448 unless @project.allowed_parents.include?(parent)
413 @project.errors.add :parent_id, :invalid
449 @project.errors.add :parent_id, :invalid
414 return false
450 return false
415 end
451 end
416 end
452 end
417 true
453 true
418 end
454 end
419 end
455 end
@@ -1,280 +1,287
1 ActionController::Routing::Routes.draw do |map|
1 ActionController::Routing::Routes.draw do |map|
2 # Add your own custom routes here.
2 # Add your own custom routes here.
3 # The priority is based upon order of creation: first created -> highest priority.
3 # The priority is based upon order of creation: first created -> highest priority.
4
4
5 # Here's a sample route:
5 # Here's a sample route:
6 # map.connect 'products/:id', :controller => 'catalog', :action => 'view'
6 # map.connect 'products/:id', :controller => 'catalog', :action => 'view'
7 # Keep in mind you can assign values other than :controller and :action
7 # Keep in mind you can assign values other than :controller and :action
8
8
9 map.home '', :controller => 'welcome'
9 map.home '', :controller => 'welcome'
10
10
11 map.signin 'login', :controller => 'account', :action => 'login'
11 map.signin 'login', :controller => 'account', :action => 'login'
12 map.signout 'logout', :controller => 'account', :action => 'logout'
12 map.signout 'logout', :controller => 'account', :action => 'logout'
13
13
14 map.connect 'roles/workflow/:id/:role_id/:tracker_id', :controller => 'roles', :action => 'workflow'
14 map.connect 'roles/workflow/:id/:role_id/:tracker_id', :controller => 'roles', :action => 'workflow'
15 map.connect 'help/:ctrl/:page', :controller => 'help'
15 map.connect 'help/:ctrl/:page', :controller => 'help'
16
16
17 map.connect 'time_entries/:id/edit', :action => 'edit', :controller => 'timelog'
17 map.connect 'time_entries/:id/edit', :action => 'edit', :controller => 'timelog'
18 map.connect 'projects/:project_id/time_entries/new', :action => 'edit', :controller => 'timelog'
18 map.connect 'projects/:project_id/time_entries/new', :action => 'edit', :controller => 'timelog'
19 map.connect 'projects/:project_id/issues/:issue_id/time_entries/new', :action => 'edit', :controller => 'timelog'
19 map.connect 'projects/:project_id/issues/:issue_id/time_entries/new', :action => 'edit', :controller => 'timelog'
20
20
21 map.with_options :controller => 'timelog' do |timelog|
21 map.with_options :controller => 'timelog' do |timelog|
22 timelog.connect 'projects/:project_id/time_entries', :action => 'details'
22 timelog.connect 'projects/:project_id/time_entries', :action => 'details'
23
23
24 timelog.with_options :action => 'details', :conditions => {:method => :get} do |time_details|
24 timelog.with_options :action => 'details', :conditions => {:method => :get} do |time_details|
25 time_details.connect 'time_entries'
25 time_details.connect 'time_entries'
26 time_details.connect 'time_entries.:format'
26 time_details.connect 'time_entries.:format'
27 time_details.connect 'issues/:issue_id/time_entries'
27 time_details.connect 'issues/:issue_id/time_entries'
28 time_details.connect 'issues/:issue_id/time_entries.:format'
28 time_details.connect 'issues/:issue_id/time_entries.:format'
29 time_details.connect 'projects/:project_id/time_entries.:format'
29 time_details.connect 'projects/:project_id/time_entries.:format'
30 time_details.connect 'projects/:project_id/issues/:issue_id/time_entries'
30 time_details.connect 'projects/:project_id/issues/:issue_id/time_entries'
31 time_details.connect 'projects/:project_id/issues/:issue_id/time_entries.:format'
31 time_details.connect 'projects/:project_id/issues/:issue_id/time_entries.:format'
32 end
32 end
33 timelog.connect 'projects/:project_id/time_entries/report', :action => 'report'
33 timelog.connect 'projects/:project_id/time_entries/report', :action => 'report'
34 timelog.with_options :action => 'report',:conditions => {:method => :get} do |time_report|
34 timelog.with_options :action => 'report',:conditions => {:method => :get} do |time_report|
35 time_report.connect 'time_entries/report'
35 time_report.connect 'time_entries/report'
36 time_report.connect 'time_entries/report.:format'
36 time_report.connect 'time_entries/report.:format'
37 time_report.connect 'projects/:project_id/time_entries/report.:format'
37 time_report.connect 'projects/:project_id/time_entries/report.:format'
38 end
38 end
39
39
40 timelog.with_options :action => 'edit', :conditions => {:method => :get} do |time_edit|
40 timelog.with_options :action => 'edit', :conditions => {:method => :get} do |time_edit|
41 time_edit.connect 'issues/:issue_id/time_entries/new'
41 time_edit.connect 'issues/:issue_id/time_entries/new'
42 end
42 end
43
43
44 timelog.connect 'time_entries/:id/destroy', :action => 'destroy', :conditions => {:method => :post}
44 timelog.connect 'time_entries/:id/destroy', :action => 'destroy', :conditions => {:method => :post}
45 end
45 end
46
46
47 map.connect 'projects/:id/wiki', :controller => 'wikis', :action => 'edit', :conditions => {:method => :post}
47 map.connect 'projects/:id/wiki', :controller => 'wikis', :action => 'edit', :conditions => {:method => :post}
48 map.connect 'projects/:id/wiki/destroy', :controller => 'wikis', :action => 'destroy', :conditions => {:method => :get}
48 map.connect 'projects/:id/wiki/destroy', :controller => 'wikis', :action => 'destroy', :conditions => {:method => :get}
49 map.connect 'projects/:id/wiki/destroy', :controller => 'wikis', :action => 'destroy', :conditions => {:method => :post}
49 map.connect 'projects/:id/wiki/destroy', :controller => 'wikis', :action => 'destroy', :conditions => {:method => :post}
50 map.with_options :controller => 'wiki' do |wiki_routes|
50 map.with_options :controller => 'wiki' do |wiki_routes|
51 wiki_routes.with_options :conditions => {:method => :get} do |wiki_views|
51 wiki_routes.with_options :conditions => {:method => :get} do |wiki_views|
52 wiki_views.connect 'projects/:id/wiki/:page', :action => 'special', :page => /page_index|date_index|export/i
52 wiki_views.connect 'projects/:id/wiki/:page', :action => 'special', :page => /page_index|date_index|export/i
53 wiki_views.connect 'projects/:id/wiki/:page', :action => 'index', :page => nil
53 wiki_views.connect 'projects/:id/wiki/:page', :action => 'index', :page => nil
54 wiki_views.connect 'projects/:id/wiki/:page/edit', :action => 'edit'
54 wiki_views.connect 'projects/:id/wiki/:page/edit', :action => 'edit'
55 wiki_views.connect 'projects/:id/wiki/:page/rename', :action => 'rename'
55 wiki_views.connect 'projects/:id/wiki/:page/rename', :action => 'rename'
56 wiki_views.connect 'projects/:id/wiki/:page/history', :action => 'history'
56 wiki_views.connect 'projects/:id/wiki/:page/history', :action => 'history'
57 wiki_views.connect 'projects/:id/wiki/:page/diff/:version/vs/:version_from', :action => 'diff'
57 wiki_views.connect 'projects/:id/wiki/:page/diff/:version/vs/:version_from', :action => 'diff'
58 wiki_views.connect 'projects/:id/wiki/:page/annotate/:version', :action => 'annotate'
58 wiki_views.connect 'projects/:id/wiki/:page/annotate/:version', :action => 'annotate'
59 end
59 end
60
60
61 wiki_routes.connect 'projects/:id/wiki/:page/:action',
61 wiki_routes.connect 'projects/:id/wiki/:page/:action',
62 :action => /edit|rename|destroy|preview|protect/,
62 :action => /edit|rename|destroy|preview|protect/,
63 :conditions => {:method => :post}
63 :conditions => {:method => :post}
64 end
64 end
65
65
66 map.with_options :controller => 'messages' do |messages_routes|
66 map.with_options :controller => 'messages' do |messages_routes|
67 messages_routes.with_options :conditions => {:method => :get} do |messages_views|
67 messages_routes.with_options :conditions => {:method => :get} do |messages_views|
68 messages_views.connect 'boards/:board_id/topics/new', :action => 'new'
68 messages_views.connect 'boards/:board_id/topics/new', :action => 'new'
69 messages_views.connect 'boards/:board_id/topics/:id', :action => 'show'
69 messages_views.connect 'boards/:board_id/topics/:id', :action => 'show'
70 messages_views.connect 'boards/:board_id/topics/:id/edit', :action => 'edit'
70 messages_views.connect 'boards/:board_id/topics/:id/edit', :action => 'edit'
71 end
71 end
72 messages_routes.with_options :conditions => {:method => :post} do |messages_actions|
72 messages_routes.with_options :conditions => {:method => :post} do |messages_actions|
73 messages_actions.connect 'boards/:board_id/topics/new', :action => 'new'
73 messages_actions.connect 'boards/:board_id/topics/new', :action => 'new'
74 messages_actions.connect 'boards/:board_id/topics/:id/replies', :action => 'reply'
74 messages_actions.connect 'boards/:board_id/topics/:id/replies', :action => 'reply'
75 messages_actions.connect 'boards/:board_id/topics/:id/:action', :action => /edit|destroy/
75 messages_actions.connect 'boards/:board_id/topics/:id/:action', :action => /edit|destroy/
76 end
76 end
77 end
77 end
78
78
79 map.with_options :controller => 'boards' do |board_routes|
79 map.with_options :controller => 'boards' do |board_routes|
80 board_routes.with_options :conditions => {:method => :get} do |board_views|
80 board_routes.with_options :conditions => {:method => :get} do |board_views|
81 board_views.connect 'projects/:project_id/boards', :action => 'index'
81 board_views.connect 'projects/:project_id/boards', :action => 'index'
82 board_views.connect 'projects/:project_id/boards/new', :action => 'new'
82 board_views.connect 'projects/:project_id/boards/new', :action => 'new'
83 board_views.connect 'projects/:project_id/boards/:id', :action => 'show'
83 board_views.connect 'projects/:project_id/boards/:id', :action => 'show'
84 board_views.connect 'projects/:project_id/boards/:id.:format', :action => 'show'
84 board_views.connect 'projects/:project_id/boards/:id.:format', :action => 'show'
85 board_views.connect 'projects/:project_id/boards/:id/edit', :action => 'edit'
85 board_views.connect 'projects/:project_id/boards/:id/edit', :action => 'edit'
86 end
86 end
87 board_routes.with_options :conditions => {:method => :post} do |board_actions|
87 board_routes.with_options :conditions => {:method => :post} do |board_actions|
88 board_actions.connect 'projects/:project_id/boards', :action => 'new'
88 board_actions.connect 'projects/:project_id/boards', :action => 'new'
89 board_actions.connect 'projects/:project_id/boards/:id/:action', :action => /edit|destroy/
89 board_actions.connect 'projects/:project_id/boards/:id/:action', :action => /edit|destroy/
90 end
90 end
91 end
91 end
92
92
93 map.with_options :controller => 'documents' do |document_routes|
93 map.with_options :controller => 'documents' do |document_routes|
94 document_routes.with_options :conditions => {:method => :get} do |document_views|
94 document_routes.with_options :conditions => {:method => :get} do |document_views|
95 document_views.connect 'projects/:project_id/documents', :action => 'index'
95 document_views.connect 'projects/:project_id/documents', :action => 'index'
96 document_views.connect 'projects/:project_id/documents/new', :action => 'new'
96 document_views.connect 'projects/:project_id/documents/new', :action => 'new'
97 document_views.connect 'documents/:id', :action => 'show'
97 document_views.connect 'documents/:id', :action => 'show'
98 document_views.connect 'documents/:id/edit', :action => 'edit'
98 document_views.connect 'documents/:id/edit', :action => 'edit'
99 end
99 end
100 document_routes.with_options :conditions => {:method => :post} do |document_actions|
100 document_routes.with_options :conditions => {:method => :post} do |document_actions|
101 document_actions.connect 'projects/:project_id/documents', :action => 'new'
101 document_actions.connect 'projects/:project_id/documents', :action => 'new'
102 document_actions.connect 'documents/:id/:action', :action => /destroy|edit/
102 document_actions.connect 'documents/:id/:action', :action => /destroy|edit/
103 end
103 end
104 end
104 end
105
105
106 map.with_options :controller => 'issues' do |issues_routes|
106 map.with_options :controller => 'issues' do |issues_routes|
107 issues_routes.with_options :conditions => {:method => :get} do |issues_views|
107 issues_routes.with_options :conditions => {:method => :get} do |issues_views|
108 issues_views.connect 'issues', :action => 'index'
108 issues_views.connect 'issues', :action => 'index'
109 issues_views.connect 'issues.:format', :action => 'index'
109 issues_views.connect 'issues.:format', :action => 'index'
110 issues_views.connect 'projects/:project_id/issues', :action => 'index'
110 issues_views.connect 'projects/:project_id/issues', :action => 'index'
111 issues_views.connect 'projects/:project_id/issues.:format', :action => 'index'
111 issues_views.connect 'projects/:project_id/issues.:format', :action => 'index'
112 issues_views.connect 'projects/:project_id/issues/new', :action => 'new'
112 issues_views.connect 'projects/:project_id/issues/new', :action => 'new'
113 issues_views.connect 'projects/:project_id/issues/gantt', :action => 'gantt'
113 issues_views.connect 'projects/:project_id/issues/gantt', :action => 'gantt'
114 issues_views.connect 'projects/:project_id/issues/calendar', :action => 'calendar'
114 issues_views.connect 'projects/:project_id/issues/calendar', :action => 'calendar'
115 issues_views.connect 'projects/:project_id/issues/:copy_from/copy', :action => 'new'
115 issues_views.connect 'projects/:project_id/issues/:copy_from/copy', :action => 'new'
116 issues_views.connect 'issues/:id', :action => 'show', :id => /\d+/
116 issues_views.connect 'issues/:id', :action => 'show', :id => /\d+/
117 issues_views.connect 'issues/:id.:format', :action => 'show', :id => /\d+/
117 issues_views.connect 'issues/:id.:format', :action => 'show', :id => /\d+/
118 issues_views.connect 'issues/:id/edit', :action => 'edit', :id => /\d+/
118 issues_views.connect 'issues/:id/edit', :action => 'edit', :id => /\d+/
119 issues_views.connect 'issues/:id/move', :action => 'move', :id => /\d+/
119 issues_views.connect 'issues/:id/move', :action => 'move', :id => /\d+/
120 end
120 end
121 issues_routes.with_options :conditions => {:method => :post} do |issues_actions|
121 issues_routes.with_options :conditions => {:method => :post} do |issues_actions|
122 issues_actions.connect 'issues', :action => 'index'
122 issues_actions.connect 'issues', :action => 'index'
123 issues_actions.connect 'projects/:project_id/issues', :action => 'new'
123 issues_actions.connect 'projects/:project_id/issues', :action => 'new'
124 issues_actions.connect 'issues/:id/quoted', :action => 'reply', :id => /\d+/
124 issues_actions.connect 'issues/:id/quoted', :action => 'reply', :id => /\d+/
125 issues_actions.connect 'issues/:id/:action', :action => /edit|move|destroy/, :id => /\d+/
125 issues_actions.connect 'issues/:id/:action', :action => /edit|move|destroy/, :id => /\d+/
126 issues_actions.connect 'issues.:format', :action => 'new', :format => /xml/
126 issues_actions.connect 'issues.:format', :action => 'new', :format => /xml/
127 end
127 end
128 issues_routes.with_options :conditions => {:method => :put} do |issues_actions|
128 issues_routes.with_options :conditions => {:method => :put} do |issues_actions|
129 issues_actions.connect 'issues/:id.:format', :action => 'edit', :id => /\d+/, :format => /xml/
129 issues_actions.connect 'issues/:id.:format', :action => 'edit', :id => /\d+/, :format => /xml/
130 end
130 end
131 issues_routes.with_options :conditions => {:method => :delete} do |issues_actions|
131 issues_routes.with_options :conditions => {:method => :delete} do |issues_actions|
132 issues_actions.connect 'issues/:id.:format', :action => 'destroy', :id => /\d+/, :format => /xml/
132 issues_actions.connect 'issues/:id.:format', :action => 'destroy', :id => /\d+/, :format => /xml/
133 end
133 end
134 issues_routes.connect 'issues/:action'
134 issues_routes.connect 'issues/:action'
135 end
135 end
136
136
137 map.with_options :controller => 'issue_relations', :conditions => {:method => :post} do |relations|
137 map.with_options :controller => 'issue_relations', :conditions => {:method => :post} do |relations|
138 relations.connect 'issues/:issue_id/relations/:id', :action => 'new'
138 relations.connect 'issues/:issue_id/relations/:id', :action => 'new'
139 relations.connect 'issues/:issue_id/relations/:id/destroy', :action => 'destroy'
139 relations.connect 'issues/:issue_id/relations/:id/destroy', :action => 'destroy'
140 end
140 end
141
141
142 map.with_options :controller => 'reports', :action => 'issue_report', :conditions => {:method => :get} do |reports|
142 map.with_options :controller => 'reports', :action => 'issue_report', :conditions => {:method => :get} do |reports|
143 reports.connect 'projects/:id/issues/report'
143 reports.connect 'projects/:id/issues/report'
144 reports.connect 'projects/:id/issues/report/:detail'
144 reports.connect 'projects/:id/issues/report/:detail'
145 end
145 end
146
146
147 map.with_options :controller => 'news' do |news_routes|
147 map.with_options :controller => 'news' do |news_routes|
148 news_routes.with_options :conditions => {:method => :get} do |news_views|
148 news_routes.with_options :conditions => {:method => :get} do |news_views|
149 news_views.connect 'news', :action => 'index'
149 news_views.connect 'news', :action => 'index'
150 news_views.connect 'projects/:project_id/news', :action => 'index'
150 news_views.connect 'projects/:project_id/news', :action => 'index'
151 news_views.connect 'projects/:project_id/news.:format', :action => 'index'
151 news_views.connect 'projects/:project_id/news.:format', :action => 'index'
152 news_views.connect 'news.:format', :action => 'index'
152 news_views.connect 'news.:format', :action => 'index'
153 news_views.connect 'projects/:project_id/news/new', :action => 'new'
153 news_views.connect 'projects/:project_id/news/new', :action => 'new'
154 news_views.connect 'news/:id', :action => 'show'
154 news_views.connect 'news/:id', :action => 'show'
155 news_views.connect 'news/:id/edit', :action => 'edit'
155 news_views.connect 'news/:id/edit', :action => 'edit'
156 end
156 end
157 news_routes.with_options do |news_actions|
157 news_routes.with_options do |news_actions|
158 news_actions.connect 'projects/:project_id/news', :action => 'new'
158 news_actions.connect 'projects/:project_id/news', :action => 'new'
159 news_actions.connect 'news/:id/edit', :action => 'edit'
159 news_actions.connect 'news/:id/edit', :action => 'edit'
160 news_actions.connect 'news/:id/destroy', :action => 'destroy'
160 news_actions.connect 'news/:id/destroy', :action => 'destroy'
161 end
161 end
162 end
162 end
163
163
164 map.connect 'projects/:id/members/new', :controller => 'members', :action => 'new'
164 map.connect 'projects/:id/members/new', :controller => 'members', :action => 'new'
165
165
166 map.with_options :controller => 'users' do |users|
166 map.with_options :controller => 'users' do |users|
167 users.with_options :conditions => {:method => :get} do |user_views|
167 users.with_options :conditions => {:method => :get} do |user_views|
168 user_views.connect 'users', :action => 'index'
168 user_views.connect 'users', :action => 'index'
169 user_views.connect 'users/:id', :action => 'show', :id => /\d+/
169 user_views.connect 'users/:id', :action => 'show', :id => /\d+/
170 user_views.connect 'users/new', :action => 'add'
170 user_views.connect 'users/new', :action => 'add'
171 user_views.connect 'users/:id/edit/:tab', :action => 'edit', :tab => nil
171 user_views.connect 'users/:id/edit/:tab', :action => 'edit', :tab => nil
172 end
172 end
173 users.with_options :conditions => {:method => :post} do |user_actions|
173 users.with_options :conditions => {:method => :post} do |user_actions|
174 user_actions.connect 'users', :action => 'add'
174 user_actions.connect 'users', :action => 'add'
175 user_actions.connect 'users/new', :action => 'add'
175 user_actions.connect 'users/new', :action => 'add'
176 user_actions.connect 'users/:id/edit', :action => 'edit'
176 user_actions.connect 'users/:id/edit', :action => 'edit'
177 user_actions.connect 'users/:id/memberships', :action => 'edit_membership'
177 user_actions.connect 'users/:id/memberships', :action => 'edit_membership'
178 user_actions.connect 'users/:id/memberships/:membership_id', :action => 'edit_membership'
178 user_actions.connect 'users/:id/memberships/:membership_id', :action => 'edit_membership'
179 user_actions.connect 'users/:id/memberships/:membership_id/destroy', :action => 'destroy_membership'
179 user_actions.connect 'users/:id/memberships/:membership_id/destroy', :action => 'destroy_membership'
180 end
180 end
181 end
181 end
182
182
183 map.with_options :controller => 'projects' do |projects|
183 map.with_options :controller => 'projects' do |projects|
184 projects.with_options :conditions => {:method => :get} do |project_views|
184 projects.with_options :conditions => {:method => :get} do |project_views|
185 project_views.connect 'projects', :action => 'index'
185 project_views.connect 'projects', :action => 'index'
186 project_views.connect 'projects.:format', :action => 'index'
186 project_views.connect 'projects.:format', :action => 'index'
187 project_views.connect 'projects/new', :action => 'add'
187 project_views.connect 'projects/new', :action => 'add'
188 project_views.connect 'projects/:id', :action => 'show'
188 project_views.connect 'projects/:id', :action => 'show'
189 project_views.connect 'projects/:id.:format', :action => 'show'
189 project_views.connect 'projects/:id/:action', :action => /roadmap|destroy|settings/
190 project_views.connect 'projects/:id/:action', :action => /roadmap|destroy|settings/
190 project_views.connect 'projects/:id/files', :action => 'list_files'
191 project_views.connect 'projects/:id/files', :action => 'list_files'
191 project_views.connect 'projects/:id/files/new', :action => 'add_file'
192 project_views.connect 'projects/:id/files/new', :action => 'add_file'
192 project_views.connect 'projects/:id/versions/new', :action => 'add_version'
193 project_views.connect 'projects/:id/versions/new', :action => 'add_version'
193 project_views.connect 'projects/:id/categories/new', :action => 'add_issue_category'
194 project_views.connect 'projects/:id/categories/new', :action => 'add_issue_category'
194 project_views.connect 'projects/:id/settings/:tab', :action => 'settings'
195 project_views.connect 'projects/:id/settings/:tab', :action => 'settings'
195 end
196 end
196
197
197 projects.with_options :action => 'activity', :conditions => {:method => :get} do |activity|
198 projects.with_options :action => 'activity', :conditions => {:method => :get} do |activity|
198 activity.connect 'projects/:id/activity'
199 activity.connect 'projects/:id/activity'
199 activity.connect 'projects/:id/activity.:format'
200 activity.connect 'projects/:id/activity.:format'
200 activity.connect 'activity', :id => nil
201 activity.connect 'activity', :id => nil
201 activity.connect 'activity.:format', :id => nil
202 activity.connect 'activity.:format', :id => nil
202 end
203 end
203
204
204 projects.with_options :conditions => {:method => :post} do |project_actions|
205 projects.with_options :conditions => {:method => :post} do |project_actions|
205 project_actions.connect 'projects/new', :action => 'add'
206 project_actions.connect 'projects/new', :action => 'add'
206 project_actions.connect 'projects', :action => 'add'
207 project_actions.connect 'projects', :action => 'add'
208 project_actions.connect 'projects.:format', :action => 'add', :format => /xml/
207 project_actions.connect 'projects/:id/:action', :action => /destroy|archive|unarchive/
209 project_actions.connect 'projects/:id/:action', :action => /destroy|archive|unarchive/
208 project_actions.connect 'projects/:id/files/new', :action => 'add_file'
210 project_actions.connect 'projects/:id/files/new', :action => 'add_file'
209 project_actions.connect 'projects/:id/versions/new', :action => 'add_version'
211 project_actions.connect 'projects/:id/versions/new', :action => 'add_version'
210 project_actions.connect 'projects/:id/categories/new', :action => 'add_issue_category'
212 project_actions.connect 'projects/:id/categories/new', :action => 'add_issue_category'
211 project_actions.connect 'projects/:id/activities/save', :action => 'save_activities'
213 project_actions.connect 'projects/:id/activities/save', :action => 'save_activities'
212 end
214 end
213
215
216 projects.with_options :conditions => {:method => :put} do |project_actions|
217 project_actions.conditions 'projects/:id.:format', :action => 'edit', :format => /xml/
218 end
219
214 projects.with_options :conditions => {:method => :delete} do |project_actions|
220 projects.with_options :conditions => {:method => :delete} do |project_actions|
221 project_actions.conditions 'projects/:id.:format', :action => 'destroy', :format => /xml/
215 project_actions.conditions 'projects/:id/reset_activities', :action => 'reset_activities'
222 project_actions.conditions 'projects/:id/reset_activities', :action => 'reset_activities'
216 end
223 end
217 end
224 end
218
225
219 map.with_options :controller => 'versions' do |versions|
226 map.with_options :controller => 'versions' do |versions|
220 versions.with_options :conditions => {:method => :post} do |version_actions|
227 versions.with_options :conditions => {:method => :post} do |version_actions|
221 version_actions.connect 'projects/:project_id/versions/close_completed', :action => 'close_completed'
228 version_actions.connect 'projects/:project_id/versions/close_completed', :action => 'close_completed'
222 end
229 end
223 end
230 end
224
231
225 map.with_options :controller => 'repositories' do |repositories|
232 map.with_options :controller => 'repositories' do |repositories|
226 repositories.with_options :conditions => {:method => :get} do |repository_views|
233 repositories.with_options :conditions => {:method => :get} do |repository_views|
227 repository_views.connect 'projects/:id/repository', :action => 'show'
234 repository_views.connect 'projects/:id/repository', :action => 'show'
228 repository_views.connect 'projects/:id/repository/edit', :action => 'edit'
235 repository_views.connect 'projects/:id/repository/edit', :action => 'edit'
229 repository_views.connect 'projects/:id/repository/statistics', :action => 'stats'
236 repository_views.connect 'projects/:id/repository/statistics', :action => 'stats'
230 repository_views.connect 'projects/:id/repository/revisions', :action => 'revisions'
237 repository_views.connect 'projects/:id/repository/revisions', :action => 'revisions'
231 repository_views.connect 'projects/:id/repository/revisions.:format', :action => 'revisions'
238 repository_views.connect 'projects/:id/repository/revisions.:format', :action => 'revisions'
232 repository_views.connect 'projects/:id/repository/revisions/:rev', :action => 'revision'
239 repository_views.connect 'projects/:id/repository/revisions/:rev', :action => 'revision'
233 repository_views.connect 'projects/:id/repository/revisions/:rev/diff', :action => 'diff'
240 repository_views.connect 'projects/:id/repository/revisions/:rev/diff', :action => 'diff'
234 repository_views.connect 'projects/:id/repository/revisions/:rev/diff.:format', :action => 'diff'
241 repository_views.connect 'projects/:id/repository/revisions/:rev/diff.:format', :action => 'diff'
235 repository_views.connect 'projects/:id/repository/revisions/:rev/raw/*path', :action => 'entry', :format => 'raw', :requirements => { :rev => /[a-z0-9\.\-_]+/ }
242 repository_views.connect 'projects/:id/repository/revisions/:rev/raw/*path', :action => 'entry', :format => 'raw', :requirements => { :rev => /[a-z0-9\.\-_]+/ }
236 repository_views.connect 'projects/:id/repository/revisions/:rev/:action/*path', :requirements => { :rev => /[a-z0-9\.\-_]+/ }
243 repository_views.connect 'projects/:id/repository/revisions/:rev/:action/*path', :requirements => { :rev => /[a-z0-9\.\-_]+/ }
237 repository_views.connect 'projects/:id/repository/raw/*path', :action => 'entry', :format => 'raw'
244 repository_views.connect 'projects/:id/repository/raw/*path', :action => 'entry', :format => 'raw'
238 # TODO: why the following route is required?
245 # TODO: why the following route is required?
239 repository_views.connect 'projects/:id/repository/entry/*path', :action => 'entry'
246 repository_views.connect 'projects/:id/repository/entry/*path', :action => 'entry'
240 repository_views.connect 'projects/:id/repository/:action/*path'
247 repository_views.connect 'projects/:id/repository/:action/*path'
241 end
248 end
242
249
243 repositories.connect 'projects/:id/repository/:action', :conditions => {:method => :post}
250 repositories.connect 'projects/:id/repository/:action', :conditions => {:method => :post}
244 end
251 end
245
252
246 map.connect 'attachments/:id', :controller => 'attachments', :action => 'show', :id => /\d+/
253 map.connect 'attachments/:id', :controller => 'attachments', :action => 'show', :id => /\d+/
247 map.connect 'attachments/:id/:filename', :controller => 'attachments', :action => 'show', :id => /\d+/, :filename => /.*/
254 map.connect 'attachments/:id/:filename', :controller => 'attachments', :action => 'show', :id => /\d+/, :filename => /.*/
248 map.connect 'attachments/download/:id/:filename', :controller => 'attachments', :action => 'download', :id => /\d+/, :filename => /.*/
255 map.connect 'attachments/download/:id/:filename', :controller => 'attachments', :action => 'download', :id => /\d+/, :filename => /.*/
249
256
250 map.resources :groups
257 map.resources :groups
251
258
252 #left old routes at the bottom for backwards compat
259 #left old routes at the bottom for backwards compat
253 map.connect 'projects/:project_id/issues/:action', :controller => 'issues'
260 map.connect 'projects/:project_id/issues/:action', :controller => 'issues'
254 map.connect 'projects/:project_id/documents/:action', :controller => 'documents'
261 map.connect 'projects/:project_id/documents/:action', :controller => 'documents'
255 map.connect 'projects/:project_id/boards/:action/:id', :controller => 'boards'
262 map.connect 'projects/:project_id/boards/:action/:id', :controller => 'boards'
256 map.connect 'boards/:board_id/topics/:action/:id', :controller => 'messages'
263 map.connect 'boards/:board_id/topics/:action/:id', :controller => 'messages'
257 map.connect 'wiki/:id/:page/:action', :page => nil, :controller => 'wiki'
264 map.connect 'wiki/:id/:page/:action', :page => nil, :controller => 'wiki'
258 map.connect 'issues/:issue_id/relations/:action/:id', :controller => 'issue_relations'
265 map.connect 'issues/:issue_id/relations/:action/:id', :controller => 'issue_relations'
259 map.connect 'projects/:project_id/news/:action', :controller => 'news'
266 map.connect 'projects/:project_id/news/:action', :controller => 'news'
260 map.connect 'projects/:project_id/timelog/:action/:id', :controller => 'timelog', :project_id => /.+/
267 map.connect 'projects/:project_id/timelog/:action/:id', :controller => 'timelog', :project_id => /.+/
261 map.with_options :controller => 'repositories' do |omap|
268 map.with_options :controller => 'repositories' do |omap|
262 omap.repositories_show 'repositories/browse/:id/*path', :action => 'browse'
269 omap.repositories_show 'repositories/browse/:id/*path', :action => 'browse'
263 omap.repositories_changes 'repositories/changes/:id/*path', :action => 'changes'
270 omap.repositories_changes 'repositories/changes/:id/*path', :action => 'changes'
264 omap.repositories_diff 'repositories/diff/:id/*path', :action => 'diff'
271 omap.repositories_diff 'repositories/diff/:id/*path', :action => 'diff'
265 omap.repositories_entry 'repositories/entry/:id/*path', :action => 'entry'
272 omap.repositories_entry 'repositories/entry/:id/*path', :action => 'entry'
266 omap.repositories_entry 'repositories/annotate/:id/*path', :action => 'annotate'
273 omap.repositories_entry 'repositories/annotate/:id/*path', :action => 'annotate'
267 omap.connect 'repositories/revision/:id/:rev', :action => 'revision'
274 omap.connect 'repositories/revision/:id/:rev', :action => 'revision'
268 end
275 end
269
276
270 map.with_options :controller => 'sys' do |sys|
277 map.with_options :controller => 'sys' do |sys|
271 sys.connect 'sys/projects.:format', :action => 'projects', :conditions => {:method => :get}
278 sys.connect 'sys/projects.:format', :action => 'projects', :conditions => {:method => :get}
272 sys.connect 'sys/projects/:id/repository.:format', :action => 'create_project_repository', :conditions => {:method => :post}
279 sys.connect 'sys/projects/:id/repository.:format', :action => 'create_project_repository', :conditions => {:method => :post}
273 end
280 end
274
281
275 # Install the default route as the lowest priority.
282 # Install the default route as the lowest priority.
276 map.connect ':controller/:action/:id'
283 map.connect ':controller/:action/:id'
277 map.connect 'robots.txt', :controller => 'welcome', :action => 'robots'
284 map.connect 'robots.txt', :controller => 'welcome', :action => 'robots'
278 # Used for OpenID
285 # Used for OpenID
279 map.root :controller => 'account', :action => 'login'
286 map.root :controller => 'account', :action => 'login'
280 end
287 end
@@ -1,131 +1,131
1 ---
1 ---
2 custom_fields_001:
2 custom_fields_001:
3 name: Database
3 name: Database
4 min_length: 0
4 min_length: 0
5 regexp: ""
5 regexp: ""
6 is_for_all: true
6 is_for_all: true
7 is_filter: true
7 is_filter: true
8 type: IssueCustomField
8 type: IssueCustomField
9 max_length: 0
9 max_length: 0
10 possible_values:
10 possible_values:
11 - MySQL
11 - MySQL
12 - PostgreSQL
12 - PostgreSQL
13 - Oracle
13 - Oracle
14 id: 1
14 id: 1
15 is_required: false
15 is_required: false
16 field_format: list
16 field_format: list
17 default_value: ""
17 default_value: ""
18 editable: true
18 editable: true
19 custom_fields_002:
19 custom_fields_002:
20 name: Searchable field
20 name: Searchable field
21 min_length: 1
21 min_length: 1
22 regexp: ""
22 regexp: ""
23 is_for_all: true
23 is_for_all: true
24 type: IssueCustomField
24 type: IssueCustomField
25 max_length: 100
25 max_length: 100
26 possible_values: ""
26 possible_values: ""
27 id: 2
27 id: 2
28 is_required: false
28 is_required: false
29 field_format: string
29 field_format: string
30 searchable: true
30 searchable: true
31 default_value: "Default string"
31 default_value: "Default string"
32 editable: true
32 editable: true
33 custom_fields_003:
33 custom_fields_003:
34 name: Development status
34 name: Development status
35 min_length: 0
35 min_length: 0
36 regexp: ""
36 regexp: ""
37 is_for_all: false
37 is_for_all: false
38 is_filter: true
38 is_filter: true
39 type: ProjectCustomField
39 type: ProjectCustomField
40 max_length: 0
40 max_length: 0
41 possible_values:
41 possible_values:
42 - Stable
42 - Stable
43 - Beta
43 - Beta
44 - Alpha
44 - Alpha
45 - Planning
45 - Planning
46 id: 3
46 id: 3
47 is_required: true
47 is_required: false
48 field_format: list
48 field_format: list
49 default_value: ""
49 default_value: ""
50 editable: true
50 editable: true
51 custom_fields_004:
51 custom_fields_004:
52 name: Phone number
52 name: Phone number
53 min_length: 0
53 min_length: 0
54 regexp: ""
54 regexp: ""
55 is_for_all: false
55 is_for_all: false
56 type: UserCustomField
56 type: UserCustomField
57 max_length: 0
57 max_length: 0
58 possible_values: ""
58 possible_values: ""
59 id: 4
59 id: 4
60 is_required: false
60 is_required: false
61 field_format: string
61 field_format: string
62 default_value: ""
62 default_value: ""
63 editable: true
63 editable: true
64 custom_fields_005:
64 custom_fields_005:
65 name: Money
65 name: Money
66 min_length: 0
66 min_length: 0
67 regexp: ""
67 regexp: ""
68 is_for_all: false
68 is_for_all: false
69 type: UserCustomField
69 type: UserCustomField
70 max_length: 0
70 max_length: 0
71 possible_values: ""
71 possible_values: ""
72 id: 5
72 id: 5
73 is_required: false
73 is_required: false
74 field_format: float
74 field_format: float
75 default_value: ""
75 default_value: ""
76 editable: true
76 editable: true
77 custom_fields_006:
77 custom_fields_006:
78 name: Float field
78 name: Float field
79 min_length: 0
79 min_length: 0
80 regexp: ""
80 regexp: ""
81 is_for_all: true
81 is_for_all: true
82 type: IssueCustomField
82 type: IssueCustomField
83 max_length: 0
83 max_length: 0
84 possible_values: ""
84 possible_values: ""
85 id: 6
85 id: 6
86 is_required: false
86 is_required: false
87 field_format: float
87 field_format: float
88 default_value: ""
88 default_value: ""
89 editable: true
89 editable: true
90 custom_fields_007:
90 custom_fields_007:
91 name: Billable
91 name: Billable
92 min_length: 0
92 min_length: 0
93 regexp: ""
93 regexp: ""
94 is_for_all: false
94 is_for_all: false
95 is_filter: true
95 is_filter: true
96 type: TimeEntryActivityCustomField
96 type: TimeEntryActivityCustomField
97 max_length: 0
97 max_length: 0
98 possible_values: ""
98 possible_values: ""
99 id: 7
99 id: 7
100 is_required: false
100 is_required: false
101 field_format: bool
101 field_format: bool
102 default_value: ""
102 default_value: ""
103 editable: true
103 editable: true
104 custom_fields_008:
104 custom_fields_008:
105 name: Custom date
105 name: Custom date
106 min_length: 0
106 min_length: 0
107 regexp: ""
107 regexp: ""
108 is_for_all: true
108 is_for_all: true
109 is_filter: false
109 is_filter: false
110 type: IssueCustomField
110 type: IssueCustomField
111 max_length: 0
111 max_length: 0
112 possible_values: ""
112 possible_values: ""
113 id: 8
113 id: 8
114 is_required: false
114 is_required: false
115 field_format: date
115 field_format: date
116 default_value: ""
116 default_value: ""
117 editable: true
117 editable: true
118 custom_fields_009:
118 custom_fields_009:
119 name: Project 1 cf
119 name: Project 1 cf
120 min_length: 0
120 min_length: 0
121 regexp: ""
121 regexp: ""
122 is_for_all: false
122 is_for_all: false
123 is_filter: true
123 is_filter: true
124 type: IssueCustomField
124 type: IssueCustomField
125 max_length: 0
125 max_length: 0
126 possible_values: ""
126 possible_values: ""
127 id: 9
127 id: 9
128 is_required: false
128 is_required: false
129 field_format: date
129 field_format: date
130 default_value: ""
130 default_value: ""
131 editable: true
131 editable: true
General Comments 0
You need to be logged in to leave comments. Login now