Merge commit 'refs/merge-requests/2206' of gitorious.org:gitorious/mainline into...
[gitorious:taladars-gitorious-saltation.git] / app / controllers / application_controller.rb
1 # encoding: utf-8
2 #--
3 #   Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies)
4 #   Copyright (C) 2007, 2008 Johan Sørensen <johan@johansorensen.com>
5 #
6 #   This program is free software: you can redistribute it and/or modify
7 #   it under the terms of the GNU Affero General Public License as published by
8 #   the Free Software Foundation, either version 3 of the License, or
9 #   (at your option) any later version.
10 #
11 #   This program is distributed in the hope that it will be useful,
12 #   but WITHOUT ANY WARRANTY; without even the implied warranty of
13 #   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 #   GNU Affero General Public License for more details.
15 #
16 #   You should have received a copy of the GNU Affero General Public License
17 #   along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 #++
19
20 # Filters added to this controller apply to all controllers in the application.
21 # Likewise, all the methods added will be available for all controllers.
22
23 class ApplicationController < ActionController::Base
24   include AuthenticatedSystem
25   include ExceptionNotifiable
26   
27   before_filter :public_and_logged_in
28   before_filter :require_current_eula
29   
30   include SslRequirement # Need to be included after the above
31
32   after_filter :mark_flash_status
33
34   filter_parameter_logging :password, :password_confirmation
35
36   layout :pick_layout_based_on_site
37   
38   rescue_from ActiveRecord::RecordNotFound, :with => :render_not_found
39   rescue_from ActionController::UnknownController, :with => :render_not_found
40   rescue_from ActionController::UnknownAction, :with => :render_not_found
41   rescue_from Grit::GitRuby::Repository::NoSuchPath, :with => :render_not_found
42   rescue_from Grit::Git::GitTimeout, :with => :render_git_timeout
43   rescue_from RecordThrottling::LimitReachedError, :with => :render_throttled_record
44   
45   def rescue_action(exception)
46     return super if RAILS_ENV != "production"
47     
48     case exception
49       # Can't catch RoutingError with rescue_from it seems, 
50       # so do it the old-fashioned way
51     when ActionController::RoutingError
52       render_not_found
53     else
54       super
55     end
56   end
57   
58   def current_site
59     @current_site || Site.default
60   end
61   
62   protected
63     # Sets the before_filters needed to be able to render in a Site specific
64     # context. +options+ is the options for the before_filters
65     def self.renders_in_site_specific_context(options = {})
66       before_filter :find_current_site, options
67       before_filter :redirect_to_current_site_subdomain, options
68     end
69     
70     # Sets the before_filters needed to make sure the requests are rendered
71     # in the "global" (eg without any Site specific layouts + subdomains).
72     # +options+ is the options for the before_filter
73     def self.renders_in_global_context(options = {})
74       before_filter :require_global_site_context, options
75     end
76   
77     # return the url with the +repo+.owner prefixed if it's a mainline repo,
78     # otherwise return the +path_spec+
79     # if +path_spec+ is an array (and no +args+ given) it'll use that as the 
80     # polymorphic-url-style (eg [@project, @repo, @foo])
81     def repo_owner_path(repo, path_spec, *args)
82       if repo.team_repo?
83         if path_spec.is_a?(Symbol)
84           return send("group_#{path_spec}", *args.unshift(repo.owner))
85         else
86           return *unshifted_polymorphic_path(repo, path_spec)
87         end
88       elsif repo.user_repo?
89         if path_spec.is_a?(Symbol)
90           return send("user_#{path_spec}", *args.unshift(repo.owner))
91         else
92           return *unshifted_polymorphic_path(repo, path_spec)
93         end
94       else
95         if path_spec.is_a?(Symbol)
96           return send(path_spec, *args)
97         else
98           return *path_spec
99         end
100       end
101     end
102     helper_method :repo_owner_path
103   
104     def require_user_has_ssh_keys
105       unless current_user.ssh_keys.count > 0
106         flash[:error] = I18n.t "application.require_ssh_keys_error"
107         redirect_to new_user_key_path(current_user)
108         return 
109       end
110     end
111
112     def require_current_user
113       unless @user == current_user
114         flash[:error] = I18n.t "application.require_current_user", :title => current_user.title
115         redirect_to user_path(current_user)
116         return
117       end
118     end
119     
120     def require_not_logged_in
121       redirect_to root_path if logged_in?
122     end
123     
124     def require_current_eula
125       if logged_in?
126         unless current_user.terms_accepted?
127           store_location
128           flash[:error] = I18n.t "views.license.terms_not_accepted"
129           redirect_to user_license_path(current_user)
130           return
131         end
132       end
133       return true
134     end
135     
136     def find_repository_owner
137       if params[:user_id]
138         @owner = User.find_by_login!(params[:user_id])
139         @containing_project = Project.find_by_slug!(params[:project_id]) if params[:project_id]
140       elsif params[:group_id]
141         @owner = Group.find_by_name!(params[:group_id])
142         @containing_project = Project.find_by_slug!(params[:project_id]) if params[:project_id]
143       elsif params[:project_id]
144         @owner = Project.find_by_slug!(params[:project_id])
145         @project = @owner
146       else
147         raise ActiveRecord::RecordNotFound
148       end
149     end
150     
151     def find_repository_owner_and_repository
152       find_repository_owner
153       @owner.repositories.find_by_name!(params[:id])
154     end
155     
156     def find_project
157       @project = Project.find_by_slug!(params[:project_id])
158     end
159     
160     def find_project_and_repository
161       @project = Project.find_by_slug!(params[:project_id])
162       # We want to look in all repositories that's somehow within this project
163       # realm, not just @project.repositories
164       @repository = Repository.find_by_name_and_project_id!(params[:repository_id], @project.id)
165     end
166     
167     def check_repository_for_commits
168       unless @repository.has_commits?
169         flash[:notice] = I18n.t "application.no_commits_notice"
170         redirect_to project_repository_path(@project, @repository) and return
171       end
172     end
173     
174     def render_not_found
175       render :template => "#{RAILS_ROOT}/public/404.html", :status => 404, :layout => "application"
176     end
177     
178     def render_git_timeout
179       render :partial => "/shared/git_timeout", :layout => "application" and return
180     end
181     
182     def render_throttled_record
183       render :partial => "/shared/throttled_record",
184         :layout => "application", :status => 412 # precondition failed
185       return false
186     end
187     
188     def public_and_logged_in
189       login_required unless GitoriousConfig['public_mode']
190     end
191     
192     def mark_flash_status
193       unless flash.empty?
194         headers['X-Has-Flash'] = "true"
195       end
196     end
197     
198     # turns ["foo", "bar"] route globbing parameters into "foo/bar"
199     # Note that while the path components will be uri unescaped, any
200     # '+' will be preserved
201     def desplat_path(*paths)
202       # we temporarily swap the + out with a magic byte, so
203       # filenames/branches with +'s won't get unescaped to a space
204       paths.flatten.compact.map do |p|
205         CGI.unescape(p.gsub("+", "\001")).gsub("\001", '+')
206       end.join("/")
207     end
208     helper_method :desplat_path
209     
210     # turns "foo/bar" into ["foo", "bar"] for route globbing
211     def ensplat_path(path)
212       path.split("/").select{|p| !p.blank? }
213     end
214     helper_method :ensplat_path
215     
216     # Returns an array like [branch_ref, *tree_path]
217     def branch_with_tree(branch_ref, tree_path)
218       tree_path = tree_path.is_a?(Array) ? tree_path : ensplat_path(tree_path)
219       ensplat_path(branch_ref) + tree_path
220     end
221     helper_method :branch_with_tree
222     
223     def branch_and_path(branch_and_path, git)
224       branch_and_path = desplat_path(branch_and_path)
225       branch_ref = path = nil
226       heads = Array(git.heads).map{|h| h.name }.sort{|a,b| b.length <=> a.length }
227       heads.each do |head|
228         if branch_and_path.starts_with?(head)
229           branch_ref = head
230           path = ensplat_path(branch_and_path.sub(head, "")) || []
231           break
232         end
233       end
234       unless path # fallback
235         path = ensplat_path(branch_and_path)[1..-1]
236         branch_ref = ensplat_path(branch_and_path)[0]
237       end
238       [branch_ref, path]
239     end
240     
241     def find_current_site
242       @current_site ||= begin
243         if @project
244           @project.site
245         else
246           if !subdomain_without_common.blank?
247             Site.find_by_subdomain(subdomain_without_common)
248           end
249         end
250       end
251     end
252     
253     def pick_layout_based_on_site
254       if current_site && current_site.subdomain
255         current_site.subdomain
256       else
257         "application"
258       end
259     end
260     
261     def subdomain_without_common
262       tld_length = GitoriousConfig["gitorious_host"].split(".").length - 1
263       request.subdomains(tld_length).select{|s| s !~ /^(ww.|secure)$/}.first
264     end
265     
266     def redirect_to_current_site_subdomain
267       return unless request.get?
268       if !current_site.subdomain.blank?
269         if subdomain_without_common != current_site.subdomain
270           url_parameters = {:only_path => false, :host => "#{current_site.subdomain}.#{GitoriousConfig["gitorious_host"]}#{request.port_string}"}.merge(params)
271           redirect_to url_parameters
272         end
273       elsif !subdomain_without_common.blank?
274         redirect_to_top_domain
275       end
276     end
277     
278     def require_global_site_context
279       unless subdomain_without_common.blank?
280         redirect_to_top_domain
281       end
282     end
283     
284     def redirect_to_top_domain
285       host_without_subdomain = {
286         :only_path => false, 
287         :host => GitoriousConfig["gitorious_host"]
288       }
289       if ![80, 443].include?(request.port)
290         host_without_subdomain[:host] << ":#{request.port}"
291       end
292       redirect_to host_without_subdomain
293     end
294     
295     # A wrapper around ActionPack's #stale?, that always returns true
296     # if there's data in the flash hash
297     def stale_conditional?(etag, last_modified)
298       return true unless flash.empty?
299       stale?(:etag => [etag, current_user], :last_modified => last_modified)
300     end
301     
302   private  
303     def unshifted_polymorphic_path(repo, path_spec)
304       if path_spec[0].is_a?(Symbol)
305         path_spec.insert(1, repo.owner)
306       else
307         path_spec.unshift(repo.owner)
308       end
309     end
310 end