##// END OF EJS Templates
Refactor: Pull up several #find_project methods to ApplicationController...
Eric Davis -
r3256:e5d300af0a4d
parent child
Show More
@@ -1,318 +1,325
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 exempt_from_layout 'builder'
26
26
27 # Remove broken cookie after upgrade from 0.8.x (#4292)
27 # Remove broken cookie after upgrade from 0.8.x (#4292)
28 # See https://rails.lighthouseapp.com/projects/8994/tickets/3360
28 # See https://rails.lighthouseapp.com/projects/8994/tickets/3360
29 # TODO: remove it when Rails is fixed
29 # TODO: remove it when Rails is fixed
30 before_filter :delete_broken_cookies
30 before_filter :delete_broken_cookies
31 def delete_broken_cookies
31 def delete_broken_cookies
32 if cookies['_redmine_session'] && cookies['_redmine_session'] !~ /--/
32 if cookies['_redmine_session'] && cookies['_redmine_session'] !~ /--/
33 cookies.delete '_redmine_session'
33 cookies.delete '_redmine_session'
34 redirect_to home_path
34 redirect_to home_path
35 return false
35 return false
36 end
36 end
37 end
37 end
38
38
39 before_filter :user_setup, :check_if_login_required, :set_localization
39 before_filter :user_setup, :check_if_login_required, :set_localization
40 filter_parameter_logging :password
40 filter_parameter_logging :password
41 protect_from_forgery
41 protect_from_forgery
42
42
43 rescue_from ActionController::InvalidAuthenticityToken, :with => :invalid_authenticity_token
43 rescue_from ActionController::InvalidAuthenticityToken, :with => :invalid_authenticity_token
44
44
45 include Redmine::Search::Controller
45 include Redmine::Search::Controller
46 include Redmine::MenuManager::MenuController
46 include Redmine::MenuManager::MenuController
47 helper Redmine::MenuManager::MenuHelper
47 helper Redmine::MenuManager::MenuHelper
48
48
49 REDMINE_SUPPORTED_SCM.each do |scm|
49 REDMINE_SUPPORTED_SCM.each do |scm|
50 require_dependency "repository/#{scm.underscore}"
50 require_dependency "repository/#{scm.underscore}"
51 end
51 end
52
52
53 def user_setup
53 def user_setup
54 # Check the settings cache for each request
54 # Check the settings cache for each request
55 Setting.check_cache
55 Setting.check_cache
56 # Find the current user
56 # Find the current user
57 User.current = find_current_user
57 User.current = find_current_user
58 end
58 end
59
59
60 # 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
61 # and starts a session if needed
61 # and starts a session if needed
62 def find_current_user
62 def find_current_user
63 if session[:user_id]
63 if session[:user_id]
64 # existing session
64 # existing session
65 (User.active.find(session[:user_id]) rescue nil)
65 (User.active.find(session[:user_id]) rescue nil)
66 elsif cookies[:autologin] && Setting.autologin?
66 elsif cookies[:autologin] && Setting.autologin?
67 # auto-login feature starts a new session
67 # auto-login feature starts a new session
68 user = User.try_to_autologin(cookies[:autologin])
68 user = User.try_to_autologin(cookies[:autologin])
69 session[:user_id] = user.id if user
69 session[:user_id] = user.id if user
70 user
70 user
71 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])
72 # RSS key authentication does not start a session
72 # RSS key authentication does not start a session
73 User.find_by_rss_key(params[:key])
73 User.find_by_rss_key(params[:key])
74 elsif Setting.rest_api_enabled? && ['xml', 'json'].include?(params[:format])
74 elsif Setting.rest_api_enabled? && ['xml', 'json'].include?(params[:format])
75 if params[:key].present? && accept_key_auth_actions.include?(params[:action])
75 if params[:key].present? && accept_key_auth_actions.include?(params[:action])
76 # Use API key
76 # Use API key
77 User.find_by_api_key(params[:key])
77 User.find_by_api_key(params[:key])
78 else
78 else
79 # HTTP Basic, either username/password or API key/random
79 # HTTP Basic, either username/password or API key/random
80 authenticate_with_http_basic do |username, password|
80 authenticate_with_http_basic do |username, password|
81 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)
82 end
82 end
83 end
83 end
84 end
84 end
85 end
85 end
86
86
87 # Sets the logged in user
87 # Sets the logged in user
88 def logged_user=(user)
88 def logged_user=(user)
89 reset_session
89 reset_session
90 if user && user.is_a?(User)
90 if user && user.is_a?(User)
91 User.current = user
91 User.current = user
92 session[:user_id] = user.id
92 session[:user_id] = user.id
93 else
93 else
94 User.current = User.anonymous
94 User.current = User.anonymous
95 end
95 end
96 end
96 end
97
97
98 # check if login is globally required to access the application
98 # check if login is globally required to access the application
99 def check_if_login_required
99 def check_if_login_required
100 # no check needed if user is already logged in
100 # no check needed if user is already logged in
101 return true if User.current.logged?
101 return true if User.current.logged?
102 require_login if Setting.login_required?
102 require_login if Setting.login_required?
103 end
103 end
104
104
105 def set_localization
105 def set_localization
106 lang = nil
106 lang = nil
107 if User.current.logged?
107 if User.current.logged?
108 lang = find_language(User.current.language)
108 lang = find_language(User.current.language)
109 end
109 end
110 if lang.nil? && request.env['HTTP_ACCEPT_LANGUAGE']
110 if lang.nil? && request.env['HTTP_ACCEPT_LANGUAGE']
111 accept_lang = parse_qvalues(request.env['HTTP_ACCEPT_LANGUAGE']).first.downcase
111 accept_lang = parse_qvalues(request.env['HTTP_ACCEPT_LANGUAGE']).first.downcase
112 if !accept_lang.blank?
112 if !accept_lang.blank?
113 lang = find_language(accept_lang) || find_language(accept_lang.split('-').first)
113 lang = find_language(accept_lang) || find_language(accept_lang.split('-').first)
114 end
114 end
115 end
115 end
116 lang ||= Setting.default_language
116 lang ||= Setting.default_language
117 set_language_if_valid(lang)
117 set_language_if_valid(lang)
118 end
118 end
119
119
120 def require_login
120 def require_login
121 if !User.current.logged?
121 if !User.current.logged?
122 # Extract only the basic url parameters on non-GET requests
122 # Extract only the basic url parameters on non-GET requests
123 if request.get?
123 if request.get?
124 url = url_for(params)
124 url = url_for(params)
125 else
125 else
126 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])
127 end
127 end
128 respond_to do |format|
128 respond_to do |format|
129 format.html { redirect_to :controller => "account", :action => "login", :back_url => url }
129 format.html { redirect_to :controller => "account", :action => "login", :back_url => url }
130 format.atom { redirect_to :controller => "account", :action => "login", :back_url => url }
130 format.atom { redirect_to :controller => "account", :action => "login", :back_url => url }
131 format.xml { head :unauthorized }
131 format.xml { head :unauthorized }
132 format.json { head :unauthorized }
132 format.json { head :unauthorized }
133 end
133 end
134 return false
134 return false
135 end
135 end
136 true
136 true
137 end
137 end
138
138
139 def require_admin
139 def require_admin
140 return unless require_login
140 return unless require_login
141 if !User.current.admin?
141 if !User.current.admin?
142 render_403
142 render_403
143 return false
143 return false
144 end
144 end
145 true
145 true
146 end
146 end
147
147
148 def deny_access
148 def deny_access
149 User.current.logged? ? render_403 : require_login
149 User.current.logged? ? render_403 : require_login
150 end
150 end
151
151
152 # Authorize the user for the requested action
152 # Authorize the user for the requested action
153 def authorize(ctrl = params[:controller], action = params[:action], global = false)
153 def authorize(ctrl = params[:controller], action = params[:action], global = false)
154 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)
155 allowed ? true : deny_access
155 allowed ? true : deny_access
156 end
156 end
157
157
158 # Authorize the user for the requested action outside a project
158 # Authorize the user for the requested action outside a project
159 def authorize_global(ctrl = params[:controller], action = params[:action], global = true)
159 def authorize_global(ctrl = params[:controller], action = params[:action], global = true)
160 authorize(ctrl, action, global)
160 authorize(ctrl, action, global)
161 end
161 end
162
163 # Find project of id params[:id]
164 def find_project
165 @project = Project.find(params[:id])
166 rescue ActiveRecord::RecordNotFound
167 render_404
168 end
162
169
163 # make sure that the user is a member of the project (or admin) if project is private
170 # make sure that the user is a member of the project (or admin) if project is private
164 # used as a before_filter for actions that do not require any particular permission on the project
171 # used as a before_filter for actions that do not require any particular permission on the project
165 def check_project_privacy
172 def check_project_privacy
166 if @project && @project.active?
173 if @project && @project.active?
167 if @project.is_public? || User.current.member_of?(@project) || User.current.admin?
174 if @project.is_public? || User.current.member_of?(@project) || User.current.admin?
168 true
175 true
169 else
176 else
170 User.current.logged? ? render_403 : require_login
177 User.current.logged? ? render_403 : require_login
171 end
178 end
172 else
179 else
173 @project = nil
180 @project = nil
174 render_404
181 render_404
175 false
182 false
176 end
183 end
177 end
184 end
178
185
179 def redirect_back_or_default(default)
186 def redirect_back_or_default(default)
180 back_url = CGI.unescape(params[:back_url].to_s)
187 back_url = CGI.unescape(params[:back_url].to_s)
181 if !back_url.blank?
188 if !back_url.blank?
182 begin
189 begin
183 uri = URI.parse(back_url)
190 uri = URI.parse(back_url)
184 # do not redirect user to another host or to the login or register page
191 # do not redirect user to another host or to the login or register page
185 if (uri.relative? || (uri.host == request.host)) && !uri.path.match(%r{/(login|account/register)})
192 if (uri.relative? || (uri.host == request.host)) && !uri.path.match(%r{/(login|account/register)})
186 redirect_to(back_url)
193 redirect_to(back_url)
187 return
194 return
188 end
195 end
189 rescue URI::InvalidURIError
196 rescue URI::InvalidURIError
190 # redirect to default
197 # redirect to default
191 end
198 end
192 end
199 end
193 redirect_to default
200 redirect_to default
194 end
201 end
195
202
196 def render_403
203 def render_403
197 @project = nil
204 @project = nil
198 respond_to do |format|
205 respond_to do |format|
199 format.html { render :template => "common/403", :layout => (request.xhr? ? false : 'base'), :status => 403 }
206 format.html { render :template => "common/403", :layout => (request.xhr? ? false : 'base'), :status => 403 }
200 format.atom { head 403 }
207 format.atom { head 403 }
201 format.xml { head 403 }
208 format.xml { head 403 }
202 format.json { head 403 }
209 format.json { head 403 }
203 end
210 end
204 return false
211 return false
205 end
212 end
206
213
207 def render_404
214 def render_404
208 respond_to do |format|
215 respond_to do |format|
209 format.html { render :template => "common/404", :layout => !request.xhr?, :status => 404 }
216 format.html { render :template => "common/404", :layout => !request.xhr?, :status => 404 }
210 format.atom { head 404 }
217 format.atom { head 404 }
211 format.xml { head 404 }
218 format.xml { head 404 }
212 format.json { head 404 }
219 format.json { head 404 }
213 end
220 end
214 return false
221 return false
215 end
222 end
216
223
217 def render_error(msg)
224 def render_error(msg)
218 respond_to do |format|
225 respond_to do |format|
219 format.html {
226 format.html {
220 flash.now[:error] = msg
227 flash.now[:error] = msg
221 render :text => '', :layout => !request.xhr?, :status => 500
228 render :text => '', :layout => !request.xhr?, :status => 500
222 }
229 }
223 format.atom { head 500 }
230 format.atom { head 500 }
224 format.xml { head 500 }
231 format.xml { head 500 }
225 format.json { head 500 }
232 format.json { head 500 }
226 end
233 end
227 end
234 end
228
235
229 def invalid_authenticity_token
236 def invalid_authenticity_token
230 if api_request?
237 if api_request?
231 logger.error "Form authenticity token is missing or is invalid. API calls must include a proper Content-type header (text/xml or text/json)."
238 logger.error "Form authenticity token is missing or is invalid. API calls must include a proper Content-type header (text/xml or text/json)."
232 end
239 end
233 render_error "Invalid form authenticity token."
240 render_error "Invalid form authenticity token."
234 end
241 end
235
242
236 def render_feed(items, options={})
243 def render_feed(items, options={})
237 @items = items || []
244 @items = items || []
238 @items.sort! {|x,y| y.event_datetime <=> x.event_datetime }
245 @items.sort! {|x,y| y.event_datetime <=> x.event_datetime }
239 @items = @items.slice(0, Setting.feeds_limit.to_i)
246 @items = @items.slice(0, Setting.feeds_limit.to_i)
240 @title = options[:title] || Setting.app_title
247 @title = options[:title] || Setting.app_title
241 render :template => "common/feed.atom.rxml", :layout => false, :content_type => 'application/atom+xml'
248 render :template => "common/feed.atom.rxml", :layout => false, :content_type => 'application/atom+xml'
242 end
249 end
243
250
244 def self.accept_key_auth(*actions)
251 def self.accept_key_auth(*actions)
245 actions = actions.flatten.map(&:to_s)
252 actions = actions.flatten.map(&:to_s)
246 write_inheritable_attribute('accept_key_auth_actions', actions)
253 write_inheritable_attribute('accept_key_auth_actions', actions)
247 end
254 end
248
255
249 def accept_key_auth_actions
256 def accept_key_auth_actions
250 self.class.read_inheritable_attribute('accept_key_auth_actions') || []
257 self.class.read_inheritable_attribute('accept_key_auth_actions') || []
251 end
258 end
252
259
253 # TODO: move to model
260 # TODO: move to model
254 def attach_files(obj, attachments)
261 def attach_files(obj, attachments)
255 attached = []
262 attached = []
256 unsaved = []
263 unsaved = []
257 if attachments && attachments.is_a?(Hash)
264 if attachments && attachments.is_a?(Hash)
258 attachments.each_value do |attachment|
265 attachments.each_value do |attachment|
259 file = attachment['file']
266 file = attachment['file']
260 next unless file && file.size > 0
267 next unless file && file.size > 0
261 a = Attachment.create(:container => obj,
268 a = Attachment.create(:container => obj,
262 :file => file,
269 :file => file,
263 :description => attachment['description'].to_s.strip,
270 :description => attachment['description'].to_s.strip,
264 :author => User.current)
271 :author => User.current)
265 a.new_record? ? (unsaved << a) : (attached << a)
272 a.new_record? ? (unsaved << a) : (attached << a)
266 end
273 end
267 if unsaved.any?
274 if unsaved.any?
268 flash[:warning] = l(:warning_attachments_not_saved, unsaved.size)
275 flash[:warning] = l(:warning_attachments_not_saved, unsaved.size)
269 end
276 end
270 end
277 end
271 attached
278 attached
272 end
279 end
273
280
274 # Returns the number of objects that should be displayed
281 # Returns the number of objects that should be displayed
275 # on the paginated list
282 # on the paginated list
276 def per_page_option
283 def per_page_option
277 per_page = nil
284 per_page = nil
278 if params[:per_page] && Setting.per_page_options_array.include?(params[:per_page].to_s.to_i)
285 if params[:per_page] && Setting.per_page_options_array.include?(params[:per_page].to_s.to_i)
279 per_page = params[:per_page].to_s.to_i
286 per_page = params[:per_page].to_s.to_i
280 session[:per_page] = per_page
287 session[:per_page] = per_page
281 elsif session[:per_page]
288 elsif session[:per_page]
282 per_page = session[:per_page]
289 per_page = session[:per_page]
283 else
290 else
284 per_page = Setting.per_page_options_array.first || 25
291 per_page = Setting.per_page_options_array.first || 25
285 end
292 end
286 per_page
293 per_page
287 end
294 end
288
295
289 # qvalues http header parser
296 # qvalues http header parser
290 # code taken from webrick
297 # code taken from webrick
291 def parse_qvalues(value)
298 def parse_qvalues(value)
292 tmp = []
299 tmp = []
293 if value
300 if value
294 parts = value.split(/,\s*/)
301 parts = value.split(/,\s*/)
295 parts.each {|part|
302 parts.each {|part|
296 if m = %r{^([^\s,]+?)(?:;\s*q=(\d+(?:\.\d+)?))?$}.match(part)
303 if m = %r{^([^\s,]+?)(?:;\s*q=(\d+(?:\.\d+)?))?$}.match(part)
297 val = m[1]
304 val = m[1]
298 q = (m[2] or 1).to_f
305 q = (m[2] or 1).to_f
299 tmp.push([val, q])
306 tmp.push([val, q])
300 end
307 end
301 }
308 }
302 tmp = tmp.sort_by{|val, q| -q}
309 tmp = tmp.sort_by{|val, q| -q}
303 tmp.collect!{|val, q| val}
310 tmp.collect!{|val, q| val}
304 end
311 end
305 return tmp
312 return tmp
306 rescue
313 rescue
307 nil
314 nil
308 end
315 end
309
316
310 # Returns a string that can be used as filename value in Content-Disposition header
317 # Returns a string that can be used as filename value in Content-Disposition header
311 def filename_for_content_disposition(name)
318 def filename_for_content_disposition(name)
312 request.env['HTTP_USER_AGENT'] =~ %r{MSIE} ? ERB::Util.url_encode(name) : name
319 request.env['HTTP_USER_AGENT'] =~ %r{MSIE} ? ERB::Util.url_encode(name) : name
313 end
320 end
314
321
315 def api_request?
322 def api_request?
316 %w(xml json).include? params[:format]
323 %w(xml json).include? params[:format]
317 end
324 end
318 end
325 end
@@ -1,89 +1,83
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006 Jean-Philippe Lang
2 # Copyright (C) 2006 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 MembersController < ApplicationController
18 class MembersController < ApplicationController
19 before_filter :find_member, :except => [:new, :autocomplete_for_member]
19 before_filter :find_member, :except => [:new, :autocomplete_for_member]
20 before_filter :find_project, :only => [:new, :autocomplete_for_member]
20 before_filter :find_project, :only => [:new, :autocomplete_for_member]
21 before_filter :authorize
21 before_filter :authorize
22
22
23 def new
23 def new
24 members = []
24 members = []
25 if params[:member] && request.post?
25 if params[:member] && request.post?
26 attrs = params[:member].dup
26 attrs = params[:member].dup
27 if (user_ids = attrs.delete(:user_ids))
27 if (user_ids = attrs.delete(:user_ids))
28 user_ids.each do |user_id|
28 user_ids.each do |user_id|
29 members << Member.new(attrs.merge(:user_id => user_id))
29 members << Member.new(attrs.merge(:user_id => user_id))
30 end
30 end
31 else
31 else
32 members << Member.new(attrs)
32 members << Member.new(attrs)
33 end
33 end
34 @project.members << members
34 @project.members << members
35 end
35 end
36 respond_to do |format|
36 respond_to do |format|
37 format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project }
37 format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project }
38 format.js {
38 format.js {
39 render(:update) {|page|
39 render(:update) {|page|
40 page.replace_html "tab-content-members", :partial => 'projects/settings/members'
40 page.replace_html "tab-content-members", :partial => 'projects/settings/members'
41 members.each {|member| page.visual_effect(:highlight, "member-#{member.id}") }
41 members.each {|member| page.visual_effect(:highlight, "member-#{member.id}") }
42 }
42 }
43 }
43 }
44 end
44 end
45 end
45 end
46
46
47 def edit
47 def edit
48 if request.post? and @member.update_attributes(params[:member])
48 if request.post? and @member.update_attributes(params[:member])
49 respond_to do |format|
49 respond_to do |format|
50 format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project }
50 format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project }
51 format.js {
51 format.js {
52 render(:update) {|page|
52 render(:update) {|page|
53 page.replace_html "tab-content-members", :partial => 'projects/settings/members'
53 page.replace_html "tab-content-members", :partial => 'projects/settings/members'
54 page.visual_effect(:highlight, "member-#{@member.id}")
54 page.visual_effect(:highlight, "member-#{@member.id}")
55 }
55 }
56 }
56 }
57 end
57 end
58 end
58 end
59 end
59 end
60
60
61 def destroy
61 def destroy
62 if request.post? && @member.deletable?
62 if request.post? && @member.deletable?
63 @member.destroy
63 @member.destroy
64 end
64 end
65 respond_to do |format|
65 respond_to do |format|
66 format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project }
66 format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project }
67 format.js { render(:update) {|page| page.replace_html "tab-content-members", :partial => 'projects/settings/members'} }
67 format.js { render(:update) {|page| page.replace_html "tab-content-members", :partial => 'projects/settings/members'} }
68 end
68 end
69 end
69 end
70
70
71 def autocomplete_for_member
71 def autocomplete_for_member
72 @principals = Principal.active.like(params[:q]).find(:all, :limit => 100) - @project.principals
72 @principals = Principal.active.like(params[:q]).find(:all, :limit => 100) - @project.principals
73 render :layout => false
73 render :layout => false
74 end
74 end
75
75
76 private
76 private
77 def find_project
78 @project = Project.find(params[:id])
79 rescue ActiveRecord::RecordNotFound
80 render_404
81 end
82
83 def find_member
77 def find_member
84 @member = Member.find(params[:id])
78 @member = Member.find(params[:id])
85 @project = @member.project
79 @project = @member.project
86 rescue ActiveRecord::RecordNotFound
80 rescue ActiveRecord::RecordNotFound
87 render_404
81 render_404
88 end
82 end
89 end
83 end
@@ -1,455 +1,446
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 {
56 format.xml {
57 @projects = Project.visible.find(:all, :order => 'lft')
57 @projects = Project.visible.find(:all, :order => 'lft')
58 }
58 }
59 format.atom {
59 format.atom {
60 projects = Project.visible.find(:all, :order => 'created_on DESC',
60 projects = Project.visible.find(:all, :order => 'created_on DESC',
61 :limit => Setting.feeds_limit.to_i)
61 :limit => Setting.feeds_limit.to_i)
62 render_feed(projects, :title => "#{Setting.app_title}: #{l(:label_project_latest)}")
62 render_feed(projects, :title => "#{Setting.app_title}: #{l(:label_project_latest)}")
63 }
63 }
64 end
64 end
65 end
65 end
66
66
67 # Add a new project
67 # Add a new project
68 def add
68 def add
69 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
69 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
70 @trackers = Tracker.all
70 @trackers = Tracker.all
71 @project = Project.new(params[:project])
71 @project = Project.new(params[:project])
72 if request.get?
72 if request.get?
73 @project.identifier = Project.next_identifier if Setting.sequential_project_identifiers?
73 @project.identifier = Project.next_identifier if Setting.sequential_project_identifiers?
74 @project.trackers = Tracker.all
74 @project.trackers = Tracker.all
75 @project.is_public = Setting.default_projects_public?
75 @project.is_public = Setting.default_projects_public?
76 @project.enabled_module_names = Setting.default_projects_modules
76 @project.enabled_module_names = Setting.default_projects_modules
77 else
77 else
78 @project.enabled_module_names = params[:enabled_modules]
78 @project.enabled_module_names = params[:enabled_modules]
79 if validate_parent_id && @project.save
79 if validate_parent_id && @project.save
80 @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')
81 # 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
82 unless User.current.admin?
82 unless User.current.admin?
83 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
84 m = Member.new(:user => User.current, :roles => [r])
84 m = Member.new(:user => User.current, :roles => [r])
85 @project.members << m
85 @project.members << m
86 end
86 end
87 respond_to do |format|
87 respond_to do |format|
88 format.html {
88 format.html {
89 flash[:notice] = l(:notice_successful_create)
89 flash[:notice] = l(:notice_successful_create)
90 redirect_to :controller => 'projects', :action => 'settings', :id => @project
90 redirect_to :controller => 'projects', :action => 'settings', :id => @project
91 }
91 }
92 format.xml { head :created, :location => url_for(:controller => 'projects', :action => 'show', :id => @project.id) }
92 format.xml { head :created, :location => url_for(:controller => 'projects', :action => 'show', :id => @project.id) }
93 end
93 end
94 else
94 else
95 respond_to do |format|
95 respond_to do |format|
96 format.html
96 format.html
97 format.xml { render :xml => @project.errors, :status => :unprocessable_entity }
97 format.xml { render :xml => @project.errors, :status => :unprocessable_entity }
98 end
98 end
99 end
99 end
100 end
100 end
101 end
101 end
102
102
103 def copy
103 def copy
104 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
104 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
105 @trackers = Tracker.all
105 @trackers = Tracker.all
106 @root_projects = Project.find(:all,
106 @root_projects = Project.find(:all,
107 :conditions => "parent_id IS NULL AND status = #{Project::STATUS_ACTIVE}",
107 :conditions => "parent_id IS NULL AND status = #{Project::STATUS_ACTIVE}",
108 :order => 'name')
108 :order => 'name')
109 @source_project = Project.find(params[:id])
109 @source_project = Project.find(params[:id])
110 if request.get?
110 if request.get?
111 @project = Project.copy_from(@source_project)
111 @project = Project.copy_from(@source_project)
112 if @project
112 if @project
113 @project.identifier = Project.next_identifier if Setting.sequential_project_identifiers?
113 @project.identifier = Project.next_identifier if Setting.sequential_project_identifiers?
114 else
114 else
115 redirect_to :controller => 'admin', :action => 'projects'
115 redirect_to :controller => 'admin', :action => 'projects'
116 end
116 end
117 else
117 else
118 @project = Project.new(params[:project])
118 @project = Project.new(params[:project])
119 @project.enabled_module_names = params[:enabled_modules]
119 @project.enabled_module_names = params[:enabled_modules]
120 if validate_parent_id && @project.copy(@source_project, :only => params[:only])
120 if validate_parent_id && @project.copy(@source_project, :only => params[:only])
121 @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')
122 flash[:notice] = l(:notice_successful_create)
122 flash[:notice] = l(:notice_successful_create)
123 redirect_to :controller => 'admin', :action => 'projects'
123 redirect_to :controller => 'admin', :action => 'projects'
124 elsif !@project.new_record?
124 elsif !@project.new_record?
125 # Project was created
125 # Project was created
126 # But some objects were not copied due to validation failures
126 # But some objects were not copied due to validation failures
127 # (eg. issues from disabled trackers)
127 # (eg. issues from disabled trackers)
128 # TODO: inform about that
128 # TODO: inform about that
129 redirect_to :controller => 'admin', :action => 'projects'
129 redirect_to :controller => 'admin', :action => 'projects'
130 end
130 end
131 end
131 end
132 rescue ActiveRecord::RecordNotFound
132 rescue ActiveRecord::RecordNotFound
133 redirect_to :controller => 'admin', :action => 'projects'
133 redirect_to :controller => 'admin', :action => 'projects'
134 end
134 end
135
135
136 # Show @project
136 # Show @project
137 def show
137 def show
138 if params[:jump]
138 if params[:jump]
139 # try to redirect to the requested menu item
139 # try to redirect to the requested menu item
140 redirect_to_project_menu_item(@project, params[:jump]) && return
140 redirect_to_project_menu_item(@project, params[:jump]) && return
141 end
141 end
142
142
143 @users_by_role = @project.users_by_role
143 @users_by_role = @project.users_by_role
144 @subprojects = @project.children.visible
144 @subprojects = @project.children.visible
145 @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")
146 @trackers = @project.rolled_up_trackers
146 @trackers = @project.rolled_up_trackers
147
147
148 cond = @project.project_condition(Setting.display_subprojects_issues?)
148 cond = @project.project_condition(Setting.display_subprojects_issues?)
149
149
150 @open_issues_by_tracker = Issue.visible.count(:group => :tracker,
150 @open_issues_by_tracker = Issue.visible.count(:group => :tracker,
151 :include => [:project, :status, :tracker],
151 :include => [:project, :status, :tracker],
152 :conditions => ["(#{cond}) AND #{IssueStatus.table_name}.is_closed=?", false])
152 :conditions => ["(#{cond}) AND #{IssueStatus.table_name}.is_closed=?", false])
153 @total_issues_by_tracker = Issue.visible.count(:group => :tracker,
153 @total_issues_by_tracker = Issue.visible.count(:group => :tracker,
154 :include => [:project, :status, :tracker],
154 :include => [:project, :status, :tracker],
155 :conditions => cond)
155 :conditions => cond)
156
156
157 TimeEntry.visible_by(User.current) do
157 TimeEntry.visible_by(User.current) do
158 @total_hours = TimeEntry.sum(:hours,
158 @total_hours = TimeEntry.sum(:hours,
159 :include => :project,
159 :include => :project,
160 :conditions => cond).to_f
160 :conditions => cond).to_f
161 end
161 end
162 @key = User.current.rss_key
162 @key = User.current.rss_key
163
163
164 respond_to do |format|
164 respond_to do |format|
165 format.html
165 format.html
166 format.xml
166 format.xml
167 end
167 end
168 end
168 end
169
169
170 def settings
170 def settings
171 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
171 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
172 @issue_category ||= IssueCategory.new
172 @issue_category ||= IssueCategory.new
173 @member ||= @project.members.new
173 @member ||= @project.members.new
174 @trackers = Tracker.all
174 @trackers = Tracker.all
175 @repository ||= @project.repository
175 @repository ||= @project.repository
176 @wiki ||= @project.wiki
176 @wiki ||= @project.wiki
177 end
177 end
178
178
179 # Edit @project
179 # Edit @project
180 def edit
180 def edit
181 if request.get?
181 if request.get?
182 else
182 else
183 @project.attributes = params[:project]
183 @project.attributes = params[:project]
184 if validate_parent_id && @project.save
184 if validate_parent_id && @project.save
185 @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')
186 respond_to do |format|
186 respond_to do |format|
187 format.html {
187 format.html {
188 flash[:notice] = l(:notice_successful_update)
188 flash[:notice] = l(:notice_successful_update)
189 redirect_to :action => 'settings', :id => @project
189 redirect_to :action => 'settings', :id => @project
190 }
190 }
191 format.xml { head :ok }
191 format.xml { head :ok }
192 end
192 end
193 else
193 else
194 respond_to do |format|
194 respond_to do |format|
195 format.html {
195 format.html {
196 settings
196 settings
197 render :action => 'settings'
197 render :action => 'settings'
198 }
198 }
199 format.xml { render :xml => @project.errors, :status => :unprocessable_entity }
199 format.xml { render :xml => @project.errors, :status => :unprocessable_entity }
200 end
200 end
201 end
201 end
202 end
202 end
203 end
203 end
204
204
205 def modules
205 def modules
206 @project.enabled_module_names = params[:enabled_modules]
206 @project.enabled_module_names = params[:enabled_modules]
207 redirect_to :action => 'settings', :id => @project, :tab => 'modules'
207 redirect_to :action => 'settings', :id => @project, :tab => 'modules'
208 end
208 end
209
209
210 def archive
210 def archive
211 if request.post?
211 if request.post?
212 unless @project.archive
212 unless @project.archive
213 flash[:error] = l(:error_can_not_archive_project)
213 flash[:error] = l(:error_can_not_archive_project)
214 end
214 end
215 end
215 end
216 redirect_to(url_for(:controller => 'admin', :action => 'projects', :status => params[:status]))
216 redirect_to(url_for(:controller => 'admin', :action => 'projects', :status => params[:status]))
217 end
217 end
218
218
219 def unarchive
219 def unarchive
220 @project.unarchive if request.post? && !@project.active?
220 @project.unarchive if request.post? && !@project.active?
221 redirect_to(url_for(:controller => 'admin', :action => 'projects', :status => params[:status]))
221 redirect_to(url_for(:controller => 'admin', :action => 'projects', :status => params[:status]))
222 end
222 end
223
223
224 # Delete @project
224 # Delete @project
225 def destroy
225 def destroy
226 @project_to_destroy = @project
226 @project_to_destroy = @project
227 if request.get?
227 if request.get?
228 # display confirmation view
228 # display confirmation view
229 else
229 else
230 if params[:format] == 'xml' || params[:confirm]
230 if params[:format] == 'xml' || params[:confirm]
231 @project_to_destroy.destroy
231 @project_to_destroy.destroy
232 respond_to do |format|
232 respond_to do |format|
233 format.html { redirect_to :controller => 'admin', :action => 'projects' }
233 format.html { redirect_to :controller => 'admin', :action => 'projects' }
234 format.xml { head :ok }
234 format.xml { head :ok }
235 end
235 end
236 end
236 end
237 end
237 end
238 # hide project in layout
238 # hide project in layout
239 @project = nil
239 @project = nil
240 end
240 end
241
241
242 # Add a new issue category to @project
242 # Add a new issue category to @project
243 def add_issue_category
243 def add_issue_category
244 @category = @project.issue_categories.build(params[:category])
244 @category = @project.issue_categories.build(params[:category])
245 if request.post?
245 if request.post?
246 if @category.save
246 if @category.save
247 respond_to do |format|
247 respond_to do |format|
248 format.html do
248 format.html do
249 flash[:notice] = l(:notice_successful_create)
249 flash[:notice] = l(:notice_successful_create)
250 redirect_to :action => 'settings', :tab => 'categories', :id => @project
250 redirect_to :action => 'settings', :tab => 'categories', :id => @project
251 end
251 end
252 format.js do
252 format.js do
253 # 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
254 render(:update) {|page| page.replace "issue_category_id",
254 render(:update) {|page| page.replace "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]')
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]')
256 }
256 }
257 end
257 end
258 end
258 end
259 else
259 else
260 respond_to do |format|
260 respond_to do |format|
261 format.html
261 format.html
262 format.js do
262 format.js do
263 render(:update) {|page| page.alert(@category.errors.full_messages.join('\n')) }
263 render(:update) {|page| page.alert(@category.errors.full_messages.join('\n')) }
264 end
264 end
265 end
265 end
266 end
266 end
267 end
267 end
268 end
268 end
269
269
270 # Add a new version to @project
270 # Add a new version to @project
271 def add_version
271 def add_version
272 @version = @project.versions.build
272 @version = @project.versions.build
273 if params[:version]
273 if params[:version]
274 attributes = params[:version].dup
274 attributes = params[:version].dup
275 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'])
276 @version.attributes = attributes
276 @version.attributes = attributes
277 end
277 end
278 if request.post?
278 if request.post?
279 if @version.save
279 if @version.save
280 respond_to do |format|
280 respond_to do |format|
281 format.html do
281 format.html do
282 flash[:notice] = l(:notice_successful_create)
282 flash[:notice] = l(:notice_successful_create)
283 redirect_to :action => 'settings', :tab => 'versions', :id => @project
283 redirect_to :action => 'settings', :tab => 'versions', :id => @project
284 end
284 end
285 format.js do
285 format.js do
286 # 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
287 render(:update) {|page| page.replace "issue_fixed_version_id",
287 render(:update) {|page| page.replace "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]')
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]')
289 }
289 }
290 end
290 end
291 end
291 end
292 else
292 else
293 respond_to do |format|
293 respond_to do |format|
294 format.html
294 format.html
295 format.js do
295 format.js do
296 render(:update) {|page| page.alert(@version.errors.full_messages.join('\n')) }
296 render(:update) {|page| page.alert(@version.errors.full_messages.join('\n')) }
297 end
297 end
298 end
298 end
299 end
299 end
300 end
300 end
301 end
301 end
302
302
303 def add_file
303 def add_file
304 if request.post?
304 if request.post?
305 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]))
306 attachments = attach_files(container, params[:attachments])
306 attachments = attach_files(container, params[:attachments])
307 if !attachments.empty? && Setting.notified_events.include?('file_added')
307 if !attachments.empty? && Setting.notified_events.include?('file_added')
308 Mailer.deliver_attachments_added(attachments)
308 Mailer.deliver_attachments_added(attachments)
309 end
309 end
310 redirect_to :controller => 'projects', :action => 'list_files', :id => @project
310 redirect_to :controller => 'projects', :action => 'list_files', :id => @project
311 return
311 return
312 end
312 end
313 @versions = @project.versions.sort
313 @versions = @project.versions.sort
314 end
314 end
315
315
316 def save_activities
316 def save_activities
317 if request.post? && params[:enumerations]
317 if request.post? && params[:enumerations]
318 Project.transaction do
318 Project.transaction do
319 params[:enumerations].each do |id, activity|
319 params[:enumerations].each do |id, activity|
320 @project.update_or_create_time_entry_activity(id, activity)
320 @project.update_or_create_time_entry_activity(id, activity)
321 end
321 end
322 end
322 end
323 end
323 end
324
324
325 redirect_to :controller => 'projects', :action => 'settings', :tab => 'activities', :id => @project
325 redirect_to :controller => 'projects', :action => 'settings', :tab => 'activities', :id => @project
326 end
326 end
327
327
328 def reset_activities
328 def reset_activities
329 @project.time_entry_activities.each do |time_entry_activity|
329 @project.time_entry_activities.each do |time_entry_activity|
330 time_entry_activity.destroy(time_entry_activity.parent)
330 time_entry_activity.destroy(time_entry_activity.parent)
331 end
331 end
332 redirect_to :controller => 'projects', :action => 'settings', :tab => 'activities', :id => @project
332 redirect_to :controller => 'projects', :action => 'settings', :tab => 'activities', :id => @project
333 end
333 end
334
334
335 def list_files
335 def list_files
336 sort_init 'filename', 'asc'
336 sort_init 'filename', 'asc'
337 sort_update 'filename' => "#{Attachment.table_name}.filename",
337 sort_update 'filename' => "#{Attachment.table_name}.filename",
338 'created_on' => "#{Attachment.table_name}.created_on",
338 'created_on' => "#{Attachment.table_name}.created_on",
339 'size' => "#{Attachment.table_name}.filesize",
339 'size' => "#{Attachment.table_name}.filesize",
340 'downloads' => "#{Attachment.table_name}.downloads"
340 'downloads' => "#{Attachment.table_name}.downloads"
341
341
342 @containers = [ Project.find(@project.id, :include => :attachments, :order => sort_clause)]
342 @containers = [ Project.find(@project.id, :include => :attachments, :order => sort_clause)]
343 @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
344 render :layout => !request.xhr?
344 render :layout => !request.xhr?
345 end
345 end
346
346
347 def roadmap
347 def roadmap
348 @trackers = @project.trackers.find(:all, :order => 'position')
348 @trackers = @project.trackers.find(:all, :order => 'position')
349 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?})
350 @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')
351 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]
352
352
353 @versions = @project.shared_versions.sort
353 @versions = @project.shared_versions.sort
354 @versions.reject! {|version| version.closed? || version.completed? } unless params[:completed]
354 @versions.reject! {|version| version.closed? || version.completed? } unless params[:completed]
355
355
356 @issues_by_version = {}
356 @issues_by_version = {}
357 unless @selected_tracker_ids.empty?
357 unless @selected_tracker_ids.empty?
358 @versions.each do |version|
358 @versions.each do |version|
359 conditions = {:tracker_id => @selected_tracker_ids}
359 conditions = {:tracker_id => @selected_tracker_ids}
360 if !@project.versions.include?(version)
360 if !@project.versions.include?(version)
361 conditions.merge!(:project_id => project_ids)
361 conditions.merge!(:project_id => project_ids)
362 end
362 end
363 issues = version.fixed_issues.visible.find(:all,
363 issues = version.fixed_issues.visible.find(:all,
364 :include => [:project, :status, :tracker, :priority],
364 :include => [:project, :status, :tracker, :priority],
365 :conditions => conditions,
365 :conditions => conditions,
366 :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")
367 @issues_by_version[version] = issues
367 @issues_by_version[version] = issues
368 end
368 end
369 end
369 end
370 @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?}
371 end
371 end
372
372
373 def activity
373 def activity
374 @days = Setting.activity_days_default.to_i
374 @days = Setting.activity_days_default.to_i
375
375
376 if params[:from]
376 if params[:from]
377 begin; @date_to = params[:from].to_date + 1; rescue; end
377 begin; @date_to = params[:from].to_date + 1; rescue; end
378 end
378 end
379
379
380 @date_to ||= Date.today + 1
380 @date_to ||= Date.today + 1
381 @date_from = @date_to - @days
381 @date_from = @date_to - @days
382 @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')
383 @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]))
384
384
385 @activity = Redmine::Activity::Fetcher.new(User.current, :project => @project,
385 @activity = Redmine::Activity::Fetcher.new(User.current, :project => @project,
386 :with_subprojects => @with_subprojects,
386 :with_subprojects => @with_subprojects,
387 :author => @author)
387 :author => @author)
388 @activity.scope_select {|t| !params["show_#{t}"].nil?}
388 @activity.scope_select {|t| !params["show_#{t}"].nil?}
389 @activity.scope = (@author.nil? ? :default : :all) if @activity.scope.empty?
389 @activity.scope = (@author.nil? ? :default : :all) if @activity.scope.empty?
390
390
391 events = @activity.events(@date_from, @date_to)
391 events = @activity.events(@date_from, @date_to)
392
392
393 if events.empty? || stale?(:etag => [events.first, User.current])
393 if events.empty? || stale?(:etag => [events.first, User.current])
394 respond_to do |format|
394 respond_to do |format|
395 format.html {
395 format.html {
396 @events_by_day = events.group_by(&:event_date)
396 @events_by_day = events.group_by(&:event_date)
397 render :layout => false if request.xhr?
397 render :layout => false if request.xhr?
398 }
398 }
399 format.atom {
399 format.atom {
400 title = l(:label_activity)
400 title = l(:label_activity)
401 if @author
401 if @author
402 title = @author.name
402 title = @author.name
403 elsif @activity.scope.size == 1
403 elsif @activity.scope.size == 1
404 title = l("label_#{@activity.scope.first.singularize}_plural")
404 title = l("label_#{@activity.scope.first.singularize}_plural")
405 end
405 end
406 render_feed(events, :title => "#{@project || Setting.app_title}: #{title}")
406 render_feed(events, :title => "#{@project || Setting.app_title}: #{title}")
407 }
407 }
408 end
408 end
409 end
409 end
410
410
411 rescue ActiveRecord::RecordNotFound
411 rescue ActiveRecord::RecordNotFound
412 render_404
412 render_404
413 end
413 end
414
414
415 private
415 private
416 # Find project of id params[:id]
417 # if not found, redirect to project list
418 # Used as a before_filter
419 def find_project
420 @project = Project.find(params[:id])
421 rescue ActiveRecord::RecordNotFound
422 render_404
423 end
424
425 def find_optional_project
416 def find_optional_project
426 return true unless params[:id]
417 return true unless params[:id]
427 @project = Project.find(params[:id])
418 @project = Project.find(params[:id])
428 authorize
419 authorize
429 rescue ActiveRecord::RecordNotFound
420 rescue ActiveRecord::RecordNotFound
430 render_404
421 render_404
431 end
422 end
432
423
433 def retrieve_selected_tracker_ids(selectable_trackers, default_trackers=nil)
424 def retrieve_selected_tracker_ids(selectable_trackers, default_trackers=nil)
434 if ids = params[:tracker_ids]
425 if ids = params[:tracker_ids]
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 }
426 @selected_tracker_ids = (ids.is_a? Array) ? ids.collect { |id| id.to_i.to_s } : ids.split('/').collect { |id| id.to_i.to_s }
436 else
427 else
437 @selected_tracker_ids = (default_trackers || selectable_trackers).collect {|t| t.id.to_s }
428 @selected_tracker_ids = (default_trackers || selectable_trackers).collect {|t| t.id.to_s }
438 end
429 end
439 end
430 end
440
431
441 # Validates parent_id param according to user's permissions
432 # Validates parent_id param according to user's permissions
442 # TODO: move it to Project model in a validation that depends on User.current
433 # TODO: move it to Project model in a validation that depends on User.current
443 def validate_parent_id
434 def validate_parent_id
444 return true if User.current.admin?
435 return true if User.current.admin?
445 parent_id = params[:project] && params[:project][:parent_id]
436 parent_id = params[:project] && params[:project][:parent_id]
446 if parent_id || @project.new_record?
437 if parent_id || @project.new_record?
447 parent = parent_id.blank? ? nil : Project.find_by_id(parent_id.to_i)
438 parent = parent_id.blank? ? nil : Project.find_by_id(parent_id.to_i)
448 unless @project.allowed_parents.include?(parent)
439 unless @project.allowed_parents.include?(parent)
449 @project.errors.add :parent_id, :invalid
440 @project.errors.add :parent_id, :invalid
450 return false
441 return false
451 end
442 end
452 end
443 end
453 true
444 true
454 end
445 end
455 end
446 end
@@ -1,124 +1,117
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006 Jean-Philippe Lang
2 # Copyright (C) 2006 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 ReportsController < ApplicationController
18 class ReportsController < ApplicationController
19 menu_item :issues
19 menu_item :issues
20 before_filter :find_project, :authorize
20 before_filter :find_project, :authorize
21
21
22 def issue_report
22 def issue_report
23 @statuses = IssueStatus.find(:all, :order => 'position')
23 @statuses = IssueStatus.find(:all, :order => 'position')
24
24
25 case params[:detail]
25 case params[:detail]
26 when "tracker"
26 when "tracker"
27 @field = "tracker_id"
27 @field = "tracker_id"
28 @rows = @project.trackers
28 @rows = @project.trackers
29 @data = issues_by_tracker
29 @data = issues_by_tracker
30 @report_title = l(:field_tracker)
30 @report_title = l(:field_tracker)
31 render :template => "reports/issue_report_details"
31 render :template => "reports/issue_report_details"
32 when "version"
32 when "version"
33 @field = "fixed_version_id"
33 @field = "fixed_version_id"
34 @rows = @project.shared_versions.sort
34 @rows = @project.shared_versions.sort
35 @data = issues_by_version
35 @data = issues_by_version
36 @report_title = l(:field_version)
36 @report_title = l(:field_version)
37 render :template => "reports/issue_report_details"
37 render :template => "reports/issue_report_details"
38 when "priority"
38 when "priority"
39 @field = "priority_id"
39 @field = "priority_id"
40 @rows = IssuePriority.all
40 @rows = IssuePriority.all
41 @data = issues_by_priority
41 @data = issues_by_priority
42 @report_title = l(:field_priority)
42 @report_title = l(:field_priority)
43 render :template => "reports/issue_report_details"
43 render :template => "reports/issue_report_details"
44 when "category"
44 when "category"
45 @field = "category_id"
45 @field = "category_id"
46 @rows = @project.issue_categories
46 @rows = @project.issue_categories
47 @data = issues_by_category
47 @data = issues_by_category
48 @report_title = l(:field_category)
48 @report_title = l(:field_category)
49 render :template => "reports/issue_report_details"
49 render :template => "reports/issue_report_details"
50 when "assigned_to"
50 when "assigned_to"
51 @field = "assigned_to_id"
51 @field = "assigned_to_id"
52 @rows = @project.members.collect { |m| m.user }.sort
52 @rows = @project.members.collect { |m| m.user }.sort
53 @data = issues_by_assigned_to
53 @data = issues_by_assigned_to
54 @report_title = l(:field_assigned_to)
54 @report_title = l(:field_assigned_to)
55 render :template => "reports/issue_report_details"
55 render :template => "reports/issue_report_details"
56 when "author"
56 when "author"
57 @field = "author_id"
57 @field = "author_id"
58 @rows = @project.members.collect { |m| m.user }.sort
58 @rows = @project.members.collect { |m| m.user }.sort
59 @data = issues_by_author
59 @data = issues_by_author
60 @report_title = l(:field_author)
60 @report_title = l(:field_author)
61 render :template => "reports/issue_report_details"
61 render :template => "reports/issue_report_details"
62 when "subproject"
62 when "subproject"
63 @field = "project_id"
63 @field = "project_id"
64 @rows = @project.descendants.active
64 @rows = @project.descendants.active
65 @data = issues_by_subproject
65 @data = issues_by_subproject
66 @report_title = l(:field_subproject)
66 @report_title = l(:field_subproject)
67 render :template => "reports/issue_report_details"
67 render :template => "reports/issue_report_details"
68 else
68 else
69 @trackers = @project.trackers
69 @trackers = @project.trackers
70 @versions = @project.shared_versions.sort
70 @versions = @project.shared_versions.sort
71 @priorities = IssuePriority.all
71 @priorities = IssuePriority.all
72 @categories = @project.issue_categories
72 @categories = @project.issue_categories
73 @assignees = @project.members.collect { |m| m.user }.sort
73 @assignees = @project.members.collect { |m| m.user }.sort
74 @authors = @project.members.collect { |m| m.user }.sort
74 @authors = @project.members.collect { |m| m.user }.sort
75 @subprojects = @project.descendants.active
75 @subprojects = @project.descendants.active
76 issues_by_tracker
76 issues_by_tracker
77 issues_by_version
77 issues_by_version
78 issues_by_priority
78 issues_by_priority
79 issues_by_category
79 issues_by_category
80 issues_by_assigned_to
80 issues_by_assigned_to
81 issues_by_author
81 issues_by_author
82 issues_by_subproject
82 issues_by_subproject
83
83
84 render :template => "reports/issue_report"
84 render :template => "reports/issue_report"
85 end
85 end
86 end
86 end
87
87
88 private
88 private
89 # Find project of id params[:id]
90 def find_project
91 @project = Project.find(params[:id])
92 rescue ActiveRecord::RecordNotFound
93 render_404
94 end
95
96 def issues_by_tracker
89 def issues_by_tracker
97 @issues_by_tracker ||= Issue.by_tracker(@project)
90 @issues_by_tracker ||= Issue.by_tracker(@project)
98 end
91 end
99
92
100 def issues_by_version
93 def issues_by_version
101 @issues_by_version ||= Issue.by_version(@project)
94 @issues_by_version ||= Issue.by_version(@project)
102 end
95 end
103
96
104 def issues_by_priority
97 def issues_by_priority
105 @issues_by_priority ||= Issue.by_priority(@project)
98 @issues_by_priority ||= Issue.by_priority(@project)
106 end
99 end
107
100
108 def issues_by_category
101 def issues_by_category
109 @issues_by_category ||= Issue.by_category(@project)
102 @issues_by_category ||= Issue.by_category(@project)
110 end
103 end
111
104
112 def issues_by_assigned_to
105 def issues_by_assigned_to
113 @issues_by_assigned_to ||= Issue.by_assigned_to(@project)
106 @issues_by_assigned_to ||= Issue.by_assigned_to(@project)
114 end
107 end
115
108
116 def issues_by_author
109 def issues_by_author
117 @issues_by_author ||= Issue.by_author(@project)
110 @issues_by_author ||= Issue.by_author(@project)
118 end
111 end
119
112
120 def issues_by_subproject
113 def issues_by_subproject
121 @issues_by_subproject ||= Issue.by_subproject(@project)
114 @issues_by_subproject ||= Issue.by_subproject(@project)
122 @issues_by_subproject ||= []
115 @issues_by_subproject ||= []
123 end
116 end
124 end
117 end
@@ -1,321 +1,315
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 require 'SVG/Graph/Bar'
18 require 'SVG/Graph/Bar'
19 require 'SVG/Graph/BarHorizontal'
19 require 'SVG/Graph/BarHorizontal'
20 require 'digest/sha1'
20 require 'digest/sha1'
21
21
22 class ChangesetNotFound < Exception; end
22 class ChangesetNotFound < Exception; end
23 class InvalidRevisionParam < Exception; end
23 class InvalidRevisionParam < Exception; end
24
24
25 class RepositoriesController < ApplicationController
25 class RepositoriesController < ApplicationController
26 menu_item :repository
26 menu_item :repository
27 default_search_scope :changesets
27 default_search_scope :changesets
28
28
29 before_filter :find_repository, :except => :edit
29 before_filter :find_repository, :except => :edit
30 before_filter :find_project, :only => :edit
30 before_filter :find_project, :only => :edit
31 before_filter :authorize
31 before_filter :authorize
32 accept_key_auth :revisions
32 accept_key_auth :revisions
33
33
34 rescue_from Redmine::Scm::Adapters::CommandFailed, :with => :show_error_command_failed
34 rescue_from Redmine::Scm::Adapters::CommandFailed, :with => :show_error_command_failed
35
35
36 def edit
36 def edit
37 @repository = @project.repository
37 @repository = @project.repository
38 if !@repository
38 if !@repository
39 @repository = Repository.factory(params[:repository_scm])
39 @repository = Repository.factory(params[:repository_scm])
40 @repository.project = @project if @repository
40 @repository.project = @project if @repository
41 end
41 end
42 if request.post? && @repository
42 if request.post? && @repository
43 @repository.attributes = params[:repository]
43 @repository.attributes = params[:repository]
44 @repository.save
44 @repository.save
45 end
45 end
46 render(:update) {|page| page.replace_html "tab-content-repository", :partial => 'projects/settings/repository'}
46 render(:update) {|page| page.replace_html "tab-content-repository", :partial => 'projects/settings/repository'}
47 end
47 end
48
48
49 def committers
49 def committers
50 @committers = @repository.committers
50 @committers = @repository.committers
51 @users = @project.users
51 @users = @project.users
52 additional_user_ids = @committers.collect(&:last).collect(&:to_i) - @users.collect(&:id)
52 additional_user_ids = @committers.collect(&:last).collect(&:to_i) - @users.collect(&:id)
53 @users += User.find_all_by_id(additional_user_ids) unless additional_user_ids.empty?
53 @users += User.find_all_by_id(additional_user_ids) unless additional_user_ids.empty?
54 @users.compact!
54 @users.compact!
55 @users.sort!
55 @users.sort!
56 if request.post? && params[:committers].is_a?(Hash)
56 if request.post? && params[:committers].is_a?(Hash)
57 # Build a hash with repository usernames as keys and corresponding user ids as values
57 # Build a hash with repository usernames as keys and corresponding user ids as values
58 @repository.committer_ids = params[:committers].values.inject({}) {|h, c| h[c.first] = c.last; h}
58 @repository.committer_ids = params[:committers].values.inject({}) {|h, c| h[c.first] = c.last; h}
59 flash[:notice] = l(:notice_successful_update)
59 flash[:notice] = l(:notice_successful_update)
60 redirect_to :action => 'committers', :id => @project
60 redirect_to :action => 'committers', :id => @project
61 end
61 end
62 end
62 end
63
63
64 def destroy
64 def destroy
65 @repository.destroy
65 @repository.destroy
66 redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'repository'
66 redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'repository'
67 end
67 end
68
68
69 def show
69 def show
70 @repository.fetch_changesets if Setting.autofetch_changesets? && @path.empty?
70 @repository.fetch_changesets if Setting.autofetch_changesets? && @path.empty?
71
71
72 @entries = @repository.entries(@path, @rev)
72 @entries = @repository.entries(@path, @rev)
73 if request.xhr?
73 if request.xhr?
74 @entries ? render(:partial => 'dir_list_content') : render(:nothing => true)
74 @entries ? render(:partial => 'dir_list_content') : render(:nothing => true)
75 else
75 else
76 (show_error_not_found; return) unless @entries
76 (show_error_not_found; return) unless @entries
77 @changesets = @repository.latest_changesets(@path, @rev)
77 @changesets = @repository.latest_changesets(@path, @rev)
78 @properties = @repository.properties(@path, @rev)
78 @properties = @repository.properties(@path, @rev)
79 render :action => 'show'
79 render :action => 'show'
80 end
80 end
81 end
81 end
82
82
83 alias_method :browse, :show
83 alias_method :browse, :show
84
84
85 def changes
85 def changes
86 @entry = @repository.entry(@path, @rev)
86 @entry = @repository.entry(@path, @rev)
87 (show_error_not_found; return) unless @entry
87 (show_error_not_found; return) unless @entry
88 @changesets = @repository.latest_changesets(@path, @rev, Setting.repository_log_display_limit.to_i)
88 @changesets = @repository.latest_changesets(@path, @rev, Setting.repository_log_display_limit.to_i)
89 @properties = @repository.properties(@path, @rev)
89 @properties = @repository.properties(@path, @rev)
90 end
90 end
91
91
92 def revisions
92 def revisions
93 @changeset_count = @repository.changesets.count
93 @changeset_count = @repository.changesets.count
94 @changeset_pages = Paginator.new self, @changeset_count,
94 @changeset_pages = Paginator.new self, @changeset_count,
95 per_page_option,
95 per_page_option,
96 params['page']
96 params['page']
97 @changesets = @repository.changesets.find(:all,
97 @changesets = @repository.changesets.find(:all,
98 :limit => @changeset_pages.items_per_page,
98 :limit => @changeset_pages.items_per_page,
99 :offset => @changeset_pages.current.offset,
99 :offset => @changeset_pages.current.offset,
100 :include => [:user, :repository])
100 :include => [:user, :repository])
101
101
102 respond_to do |format|
102 respond_to do |format|
103 format.html { render :layout => false if request.xhr? }
103 format.html { render :layout => false if request.xhr? }
104 format.atom { render_feed(@changesets, :title => "#{@project.name}: #{l(:label_revision_plural)}") }
104 format.atom { render_feed(@changesets, :title => "#{@project.name}: #{l(:label_revision_plural)}") }
105 end
105 end
106 end
106 end
107
107
108 def entry
108 def entry
109 @entry = @repository.entry(@path, @rev)
109 @entry = @repository.entry(@path, @rev)
110 (show_error_not_found; return) unless @entry
110 (show_error_not_found; return) unless @entry
111
111
112 # If the entry is a dir, show the browser
112 # If the entry is a dir, show the browser
113 (show; return) if @entry.is_dir?
113 (show; return) if @entry.is_dir?
114
114
115 @content = @repository.cat(@path, @rev)
115 @content = @repository.cat(@path, @rev)
116 (show_error_not_found; return) unless @content
116 (show_error_not_found; return) unless @content
117 if 'raw' == params[:format] || @content.is_binary_data? || (@entry.size && @entry.size > Setting.file_max_size_displayed.to_i.kilobyte)
117 if 'raw' == params[:format] || @content.is_binary_data? || (@entry.size && @entry.size > Setting.file_max_size_displayed.to_i.kilobyte)
118 # Force the download
118 # Force the download
119 send_data @content, :filename => @path.split('/').last
119 send_data @content, :filename => @path.split('/').last
120 else
120 else
121 # Prevent empty lines when displaying a file with Windows style eol
121 # Prevent empty lines when displaying a file with Windows style eol
122 @content.gsub!("\r\n", "\n")
122 @content.gsub!("\r\n", "\n")
123 end
123 end
124 end
124 end
125
125
126 def annotate
126 def annotate
127 @entry = @repository.entry(@path, @rev)
127 @entry = @repository.entry(@path, @rev)
128 (show_error_not_found; return) unless @entry
128 (show_error_not_found; return) unless @entry
129
129
130 @annotate = @repository.scm.annotate(@path, @rev)
130 @annotate = @repository.scm.annotate(@path, @rev)
131 (render_error l(:error_scm_annotate); return) if @annotate.nil? || @annotate.empty?
131 (render_error l(:error_scm_annotate); return) if @annotate.nil? || @annotate.empty?
132 end
132 end
133
133
134 def revision
134 def revision
135 @changeset = @repository.find_changeset_by_name(@rev)
135 @changeset = @repository.find_changeset_by_name(@rev)
136 raise ChangesetNotFound unless @changeset
136 raise ChangesetNotFound unless @changeset
137
137
138 respond_to do |format|
138 respond_to do |format|
139 format.html
139 format.html
140 format.js {render :layout => false}
140 format.js {render :layout => false}
141 end
141 end
142 rescue ChangesetNotFound
142 rescue ChangesetNotFound
143 show_error_not_found
143 show_error_not_found
144 end
144 end
145
145
146 def diff
146 def diff
147 if params[:format] == 'diff'
147 if params[:format] == 'diff'
148 @diff = @repository.diff(@path, @rev, @rev_to)
148 @diff = @repository.diff(@path, @rev, @rev_to)
149 (show_error_not_found; return) unless @diff
149 (show_error_not_found; return) unless @diff
150 filename = "changeset_r#{@rev}"
150 filename = "changeset_r#{@rev}"
151 filename << "_r#{@rev_to}" if @rev_to
151 filename << "_r#{@rev_to}" if @rev_to
152 send_data @diff.join, :filename => "#{filename}.diff",
152 send_data @diff.join, :filename => "#{filename}.diff",
153 :type => 'text/x-patch',
153 :type => 'text/x-patch',
154 :disposition => 'attachment'
154 :disposition => 'attachment'
155 else
155 else
156 @diff_type = params[:type] || User.current.pref[:diff_type] || 'inline'
156 @diff_type = params[:type] || User.current.pref[:diff_type] || 'inline'
157 @diff_type = 'inline' unless %w(inline sbs).include?(@diff_type)
157 @diff_type = 'inline' unless %w(inline sbs).include?(@diff_type)
158
158
159 # Save diff type as user preference
159 # Save diff type as user preference
160 if User.current.logged? && @diff_type != User.current.pref[:diff_type]
160 if User.current.logged? && @diff_type != User.current.pref[:diff_type]
161 User.current.pref[:diff_type] = @diff_type
161 User.current.pref[:diff_type] = @diff_type
162 User.current.preference.save
162 User.current.preference.save
163 end
163 end
164
164
165 @cache_key = "repositories/diff/#{@repository.id}/" + Digest::MD5.hexdigest("#{@path}-#{@rev}-#{@rev_to}-#{@diff_type}")
165 @cache_key = "repositories/diff/#{@repository.id}/" + Digest::MD5.hexdigest("#{@path}-#{@rev}-#{@rev_to}-#{@diff_type}")
166 unless read_fragment(@cache_key)
166 unless read_fragment(@cache_key)
167 @diff = @repository.diff(@path, @rev, @rev_to)
167 @diff = @repository.diff(@path, @rev, @rev_to)
168 show_error_not_found unless @diff
168 show_error_not_found unless @diff
169 end
169 end
170 end
170 end
171 end
171 end
172
172
173 def stats
173 def stats
174 end
174 end
175
175
176 def graph
176 def graph
177 data = nil
177 data = nil
178 case params[:graph]
178 case params[:graph]
179 when "commits_per_month"
179 when "commits_per_month"
180 data = graph_commits_per_month(@repository)
180 data = graph_commits_per_month(@repository)
181 when "commits_per_author"
181 when "commits_per_author"
182 data = graph_commits_per_author(@repository)
182 data = graph_commits_per_author(@repository)
183 end
183 end
184 if data
184 if data
185 headers["Content-Type"] = "image/svg+xml"
185 headers["Content-Type"] = "image/svg+xml"
186 send_data(data, :type => "image/svg+xml", :disposition => "inline")
186 send_data(data, :type => "image/svg+xml", :disposition => "inline")
187 else
187 else
188 render_404
188 render_404
189 end
189 end
190 end
190 end
191
191
192 private
192 private
193 def find_project
194 @project = Project.find(params[:id])
195 rescue ActiveRecord::RecordNotFound
196 render_404
197 end
198
199 def find_repository
193 def find_repository
200 @project = Project.find(params[:id])
194 @project = Project.find(params[:id])
201 @repository = @project.repository
195 @repository = @project.repository
202 (render_404; return false) unless @repository
196 (render_404; return false) unless @repository
203 @path = params[:path].join('/') unless params[:path].nil?
197 @path = params[:path].join('/') unless params[:path].nil?
204 @path ||= ''
198 @path ||= ''
205 @rev = params[:rev].blank? ? @repository.default_branch : params[:rev].strip
199 @rev = params[:rev].blank? ? @repository.default_branch : params[:rev].strip
206 @rev_to = params[:rev_to]
200 @rev_to = params[:rev_to]
207 rescue ActiveRecord::RecordNotFound
201 rescue ActiveRecord::RecordNotFound
208 render_404
202 render_404
209 rescue InvalidRevisionParam
203 rescue InvalidRevisionParam
210 show_error_not_found
204 show_error_not_found
211 end
205 end
212
206
213 def show_error_not_found
207 def show_error_not_found
214 render_error l(:error_scm_not_found)
208 render_error l(:error_scm_not_found)
215 end
209 end
216
210
217 # Handler for Redmine::Scm::Adapters::CommandFailed exception
211 # Handler for Redmine::Scm::Adapters::CommandFailed exception
218 def show_error_command_failed(exception)
212 def show_error_command_failed(exception)
219 render_error l(:error_scm_command_failed, exception.message)
213 render_error l(:error_scm_command_failed, exception.message)
220 end
214 end
221
215
222 def graph_commits_per_month(repository)
216 def graph_commits_per_month(repository)
223 @date_to = Date.today
217 @date_to = Date.today
224 @date_from = @date_to << 11
218 @date_from = @date_to << 11
225 @date_from = Date.civil(@date_from.year, @date_from.month, 1)
219 @date_from = Date.civil(@date_from.year, @date_from.month, 1)
226 commits_by_day = repository.changesets.count(:all, :group => :commit_date, :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
220 commits_by_day = repository.changesets.count(:all, :group => :commit_date, :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
227 commits_by_month = [0] * 12
221 commits_by_month = [0] * 12
228 commits_by_day.each {|c| commits_by_month[c.first.to_date.months_ago] += c.last }
222 commits_by_day.each {|c| commits_by_month[c.first.to_date.months_ago] += c.last }
229
223
230 changes_by_day = repository.changes.count(:all, :group => :commit_date, :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
224 changes_by_day = repository.changes.count(:all, :group => :commit_date, :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
231 changes_by_month = [0] * 12
225 changes_by_month = [0] * 12
232 changes_by_day.each {|c| changes_by_month[c.first.to_date.months_ago] += c.last }
226 changes_by_day.each {|c| changes_by_month[c.first.to_date.months_ago] += c.last }
233
227
234 fields = []
228 fields = []
235 12.times {|m| fields << month_name(((Date.today.month - 1 - m) % 12) + 1)}
229 12.times {|m| fields << month_name(((Date.today.month - 1 - m) % 12) + 1)}
236
230
237 graph = SVG::Graph::Bar.new(
231 graph = SVG::Graph::Bar.new(
238 :height => 300,
232 :height => 300,
239 :width => 800,
233 :width => 800,
240 :fields => fields.reverse,
234 :fields => fields.reverse,
241 :stack => :side,
235 :stack => :side,
242 :scale_integers => true,
236 :scale_integers => true,
243 :step_x_labels => 2,
237 :step_x_labels => 2,
244 :show_data_values => false,
238 :show_data_values => false,
245 :graph_title => l(:label_commits_per_month),
239 :graph_title => l(:label_commits_per_month),
246 :show_graph_title => true
240 :show_graph_title => true
247 )
241 )
248
242
249 graph.add_data(
243 graph.add_data(
250 :data => commits_by_month[0..11].reverse,
244 :data => commits_by_month[0..11].reverse,
251 :title => l(:label_revision_plural)
245 :title => l(:label_revision_plural)
252 )
246 )
253
247
254 graph.add_data(
248 graph.add_data(
255 :data => changes_by_month[0..11].reverse,
249 :data => changes_by_month[0..11].reverse,
256 :title => l(:label_change_plural)
250 :title => l(:label_change_plural)
257 )
251 )
258
252
259 graph.burn
253 graph.burn
260 end
254 end
261
255
262 def graph_commits_per_author(repository)
256 def graph_commits_per_author(repository)
263 commits_by_author = repository.changesets.count(:all, :group => :committer)
257 commits_by_author = repository.changesets.count(:all, :group => :committer)
264 commits_by_author.to_a.sort! {|x, y| x.last <=> y.last}
258 commits_by_author.to_a.sort! {|x, y| x.last <=> y.last}
265
259
266 changes_by_author = repository.changes.count(:all, :group => :committer)
260 changes_by_author = repository.changes.count(:all, :group => :committer)
267 h = changes_by_author.inject({}) {|o, i| o[i.first] = i.last; o}
261 h = changes_by_author.inject({}) {|o, i| o[i.first] = i.last; o}
268
262
269 fields = commits_by_author.collect {|r| r.first}
263 fields = commits_by_author.collect {|r| r.first}
270 commits_data = commits_by_author.collect {|r| r.last}
264 commits_data = commits_by_author.collect {|r| r.last}
271 changes_data = commits_by_author.collect {|r| h[r.first] || 0}
265 changes_data = commits_by_author.collect {|r| h[r.first] || 0}
272
266
273 fields = fields + [""]*(10 - fields.length) if fields.length<10
267 fields = fields + [""]*(10 - fields.length) if fields.length<10
274 commits_data = commits_data + [0]*(10 - commits_data.length) if commits_data.length<10
268 commits_data = commits_data + [0]*(10 - commits_data.length) if commits_data.length<10
275 changes_data = changes_data + [0]*(10 - changes_data.length) if changes_data.length<10
269 changes_data = changes_data + [0]*(10 - changes_data.length) if changes_data.length<10
276
270
277 # Remove email adress in usernames
271 # Remove email adress in usernames
278 fields = fields.collect {|c| c.gsub(%r{<.+@.+>}, '') }
272 fields = fields.collect {|c| c.gsub(%r{<.+@.+>}, '') }
279
273
280 graph = SVG::Graph::BarHorizontal.new(
274 graph = SVG::Graph::BarHorizontal.new(
281 :height => 400,
275 :height => 400,
282 :width => 800,
276 :width => 800,
283 :fields => fields,
277 :fields => fields,
284 :stack => :side,
278 :stack => :side,
285 :scale_integers => true,
279 :scale_integers => true,
286 :show_data_values => false,
280 :show_data_values => false,
287 :rotate_y_labels => false,
281 :rotate_y_labels => false,
288 :graph_title => l(:label_commits_per_author),
282 :graph_title => l(:label_commits_per_author),
289 :show_graph_title => true
283 :show_graph_title => true
290 )
284 )
291
285
292 graph.add_data(
286 graph.add_data(
293 :data => commits_data,
287 :data => commits_data,
294 :title => l(:label_revision_plural)
288 :title => l(:label_revision_plural)
295 )
289 )
296
290
297 graph.add_data(
291 graph.add_data(
298 :data => changes_data,
292 :data => changes_data,
299 :title => l(:label_change_plural)
293 :title => l(:label_change_plural)
300 )
294 )
301
295
302 graph.burn
296 graph.burn
303 end
297 end
304
298
305 end
299 end
306
300
307 class Date
301 class Date
308 def months_ago(date = Date.today)
302 def months_ago(date = Date.today)
309 (date.year - self.year)*12 + (date.month - self.month)
303 (date.year - self.year)*12 + (date.month - self.month)
310 end
304 end
311
305
312 def weeks_ago(date = Date.today)
306 def weeks_ago(date = Date.today)
313 (date.year - self.year)*52 + (date.cweek - self.cweek)
307 (date.year - self.year)*52 + (date.cweek - self.cweek)
314 end
308 end
315 end
309 end
316
310
317 class String
311 class String
318 def with_leading_slash
312 def with_leading_slash
319 starts_with?('/') ? self : "/#{self}"
313 starts_with?('/') ? self : "/#{self}"
320 end
314 end
321 end
315 end
@@ -1,44 +1,37
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 class WikisController < ApplicationController
18 class WikisController < ApplicationController
19 menu_item :settings
19 menu_item :settings
20 before_filter :find_project, :authorize
20 before_filter :find_project, :authorize
21
21
22 # Create or update a project's wiki
22 # Create or update a project's wiki
23 def edit
23 def edit
24 @wiki = @project.wiki || Wiki.new(:project => @project)
24 @wiki = @project.wiki || Wiki.new(:project => @project)
25 @wiki.attributes = params[:wiki]
25 @wiki.attributes = params[:wiki]
26 @wiki.save if request.post?
26 @wiki.save if request.post?
27 render(:update) {|page| page.replace_html "tab-content-wiki", :partial => 'projects/settings/wiki'}
27 render(:update) {|page| page.replace_html "tab-content-wiki", :partial => 'projects/settings/wiki'}
28 end
28 end
29
29
30 # Delete a project's wiki
30 # Delete a project's wiki
31 def destroy
31 def destroy
32 if request.post? && params[:confirm] && @project.wiki
32 if request.post? && params[:confirm] && @project.wiki
33 @project.wiki.destroy
33 @project.wiki.destroy
34 redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'wiki'
34 redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'wiki'
35 end
35 end
36 end
36 end
37
38 private
39 def find_project
40 @project = Project.find(params[:id])
41 rescue ActiveRecord::RecordNotFound
42 render_404
43 end
44 end
37 end
General Comments 0
You need to be logged in to leave comments. Login now