Guard against non-numeric lat and lons in nodes and notes
[rails.git] / app / controllers / trace_controller.rb
1 class TraceController < ApplicationController
2   layout 'site'
3
4   skip_before_filter :verify_authenticity_token, :only => [:api_create, :api_read, :api_update, :api_delete, :api_data]
5   before_filter :authorize_web
6   before_filter :set_locale
7   before_filter :require_user, :only => [:mine, :create, :edit, :delete]
8   before_filter :authorize, :only => [:api_create, :api_read, :api_update, :api_delete, :api_data]
9   before_filter :check_database_readable, :except => [:api_read, :api_data]
10   before_filter :check_database_writable, :only => [:create, :edit, :delete, :api_create, :api_update, :api_delete]
11   before_filter :check_api_readable, :only => [:api_read, :api_data]
12   before_filter :check_api_writable, :only => [:api_create, :api_update, :api_delete]
13   before_filter :require_allow_read_gpx, :only => [:api_read, :api_data]
14   before_filter :require_allow_write_gpx, :only => [:api_create, :api_update, :api_delete]
15   before_filter :offline_warning, :only => [:mine, :view]
16   before_filter :offline_redirect, :only => [:create, :edit, :delete, :data, :api_create, :api_delete, :api_data]
17   around_filter :api_call_handle_error, :only => [:api_create, :api_read, :api_update, :api_delete, :api_data]
18
19 #  caches_action :list, :unless => :logged_in?, :layout => false
20 #  caches_action :view, :layout => false
21   caches_action :georss, :layout => true
22   cache_sweeper :trace_sweeper, :only => [:create, :edit, :delete, :api_create, :api_update, :api_delete]
23   cache_sweeper :tracetag_sweeper, :only => [:create, :edit, :delete, :api_create, :api_update, :api_delete]
24
25   # Counts and selects pages of GPX traces for various criteria (by user, tags, public etc.).
26   #  target_user - if set, specifies the user to fetch traces for.  if not set will fetch all traces
27   def list
28     # from display name, pick up user id if one user's traces only
29     display_name = params[:display_name]
30     if !display_name.blank?
31       target_user = User.active.where(:display_name => display_name).first
32       if target_user.nil?
33         render_unknown_user display_name
34         return
35       end
36     end
37
38     # set title
39     if target_user.nil?
40       @title = t 'trace.list.public_traces'
41     elsif @user and @user == target_user
42       @title = t 'trace.list.your_traces'
43     else
44       @title = t 'trace.list.public_traces_from', :user => target_user.display_name
45     end
46
47     @title += t 'trace.list.tagged_with', :tags => params[:tag] if params[:tag]
48
49     # four main cases:
50     # 1 - all traces, logged in = all public traces + all user's (i.e + all mine)
51     # 2 - all traces, not logged in = all public traces
52     # 3 - user's traces, logged in as same user = all user's traces
53     # 4 - user's traces, not logged in as that user = all user's public traces
54     if target_user.nil? # all traces
55       if @user
56         @traces = Trace.visible_to(@user) #1
57       else
58         @traces = Trace.public #2
59       end
60     else
61       if @user and @user == target_user
62         @traces = @user.traces #3 (check vs user id, so no join + can't pick up non-public traces by changing name)
63       else
64         @traces = target_user.traces.public #4
65       end
66     end
67
68     if params[:tag]
69       @traces = @traces.tagged(params[:tag])
70     end
71
72     @page = (params[:page] || 1).to_i
73     @page_size = 20
74
75     @traces = @traces.visible
76     @traces = @traces.order("timestamp DESC")
77     @traces = @traces.offset((@page - 1) * @page_size)
78     @traces = @traces.limit(@page_size)
79     @traces = @traces.includes(:user, :tags)
80
81     # put together SET of tags across traces, for related links
82     tagset = Hash.new
83     @traces.each do |trace|
84       trace.tags.reload if params[:tag] # if searched by tag, ActiveRecord won't bring back other tags, so do explicitly here
85       trace.tags.each do |tag|
86         tagset[tag.tag] = tag.tag
87       end
88     end
89
90     # final helper vars for view
91     @target_user = target_user
92     @display_name = target_user.display_name if target_user
93     @all_tags = tagset.values
94   end
95
96   def mine
97     redirect_to :action => :list, :display_name => @user.display_name
98   end
99
100   def view
101     @trace = Trace.find(params[:id])
102
103     if @trace and @trace.visible? and
104        (@trace.public? or @trace.user == @user)
105       @title = t 'trace.view.title', :name => @trace.name
106     else
107       flash[:error] = t 'trace.view.trace_not_found'
108       redirect_to :controller => 'trace', :action => 'list'
109     end
110   rescue ActiveRecord::RecordNotFound
111     flash[:error] = t 'trace.view.trace_not_found'
112     redirect_to :controller => 'trace', :action => 'list'
113   end
114
115   def create
116     if params[:trace]
117       logger.info(params[:trace][:gpx_file].class.name)
118
119       if params[:trace][:gpx_file].respond_to?(:read)
120         begin
121           do_create(params[:trace][:gpx_file], params[:trace][:tagstring],
122                     params[:trace][:description], params[:trace][:visibility])
123         rescue => ex
124           logger.debug ex
125         end
126
127         if @trace.id
128           logger.info("id is #{@trace.id}")
129           flash[:notice] = t 'trace.create.trace_uploaded'
130
131           if @user.traces.count(:conditions => { :inserted => false }) > 4
132             flash[:warning] = t 'trace.trace_header.traces_waiting', :count => @user.traces.count(:conditions => { :inserted => false })
133           end
134
135           redirect_to :action => :list, :display_name => @user.display_name
136         end
137       else
138         @trace = Trace.new({:name => "Dummy",
139                             :tagstring => params[:trace][:tagstring],
140                             :description => params[:trace][:description],
141                             :visibility => params[:trace][:visibility],
142                             :inserted => false, :user => @user,
143                             :timestamp => Time.now.getutc})
144         @trace.valid?
145         @trace.errors.add(:gpx_file, "can't be blank")
146       end
147     else
148       @trace = Trace.new({:visibility => default_visibility}, :without_protection => true)
149     end
150
151     @title = t 'trace.create.upload_trace'
152   end
153
154   def data
155     trace = Trace.find(params[:id])
156
157     if trace.visible? and (trace.public? or (@user and @user == trace.user))
158       if Acl.no_trace_download(request.remote_ip)
159         render :nothing => true, :status => :forbidden
160       elsif request.format == Mime::XML
161         send_file(trace.xml_file, :filename => "#{trace.id}.xml", :type => Mime::XML.to_s, :disposition => 'attachment')
162       else
163         send_file(trace.trace_name, :filename => "#{trace.id}#{trace.extension_name}", :type => trace.mime_type, :disposition => 'attachment')
164       end
165     else
166       render :nothing => true, :status => :not_found
167     end
168   rescue ActiveRecord::RecordNotFound
169     render :nothing => true, :status => :not_found
170   end
171
172   def edit
173     @trace = Trace.find(params[:id])
174
175     if @user and @trace.user == @user
176       @title = t 'trace.edit.title', :name => @trace.name
177       if params[:trace]
178         @trace.description = params[:trace][:description]
179         @trace.tagstring = params[:trace][:tagstring]
180         @trace.visibility = params[:trace][:visibility]
181         if @trace.save
182           redirect_to :action => 'view', :display_name => @user.display_name
183         end
184       end
185     else
186       render :nothing => true, :status => :forbidden
187     end
188   rescue ActiveRecord::RecordNotFound
189     render :nothing => true, :status => :not_found
190   end
191
192   def delete
193     trace = Trace.find(params[:id])
194
195     if @user and trace.user == @user
196       if trace.visible?
197         trace.visible = false
198         trace.save
199         flash[:notice] = t 'trace.delete.scheduled_for_deletion'
200         redirect_to :action => :list, :display_name => @user.display_name
201       else
202         render :nothing => true, :status => :not_found
203       end
204     else
205       render :nothing => true, :status => :forbidden
206     end
207   rescue ActiveRecord::RecordNotFound
208     render :nothing => true, :status => :not_found
209   end
210
211   def georss
212     traces = Trace.public.visible
213
214     if params[:display_name]
215       traces = traces.joins(:user).where(:users => {:display_name => params[:display_name]})
216     end
217
218     if params[:tag]
219       traces = traces.tagged(params[:tag])
220     end
221
222     traces = traces.order("timestamp DESC")
223     traces = traces.limit(20)
224     traces = traces.includes(:user)
225
226     rss = OSM::GeoRSS.new
227
228     traces.each do |trace|
229       rss.add(trace.latitude, trace.longitude, trace.name, trace.user.display_name, url_for({:controller => 'trace', :action => 'view', :id => trace.id, :display_name => trace.user.display_name}), "<img src='#{url_for({:controller => 'trace', :action => 'icon', :id => trace.id, :display_name => trace.user.display_name})}'> GPX file with #{trace.size} points from #{trace.user.display_name}", trace.timestamp)
230     end
231
232     render :text => rss.to_s, :content_type => "application/rss+xml"
233   end
234
235   def picture
236     trace = Trace.find(params[:id])
237
238     if trace.inserted?
239       if trace.public? or (@user and @user == trace.user)
240         expires_in 7.days, :private => !trace.public?, :public => trace.public?
241         send_file(trace.large_picture_name, :filename => "#{trace.id}.gif", :type => 'image/gif', :disposition => 'inline')
242       else
243         render :nothing => true, :status => :forbidden
244       end
245     else
246       render :nothing => true, :status => :not_found
247     end
248   rescue ActiveRecord::RecordNotFound
249     render :nothing => true, :status => :not_found
250   end
251
252   def icon
253     trace = Trace.find(params[:id])
254
255     if trace.inserted?
256       if trace.public? or (@user and @user == trace.user)
257         expires_in 7.days, :private => !trace.public?, :public => trace.public?
258         send_file(trace.icon_picture_name, :filename => "#{trace.id}_icon.gif", :type => 'image/gif', :disposition => 'inline')
259       else
260         render :nothing => true, :status => :forbidden
261       end
262     else
263       render :nothing => true, :status => :not_found
264     end
265   rescue ActiveRecord::RecordNotFound
266     render :nothing => true, :status => :not_found
267   end
268
269   def api_read
270     trace = Trace.visible.find(params[:id])
271
272     if trace.public? or trace.user == @user
273       render :text => trace.to_xml.to_s, :content_type => "text/xml"
274     else
275       render :nothing => true, :status => :forbidden
276     end
277   end
278
279   def api_update
280     trace = Trace.visible.find(params[:id])
281
282     if trace.user == @user
283       new_trace = Trace.from_xml(request.raw_post)
284
285       unless new_trace and new_trace.id == trace.id
286         raise OSM::APIBadUserInput.new("The id in the url (#{trace.id}) is not the same as provided in the xml (#{new_trace.id})")
287       end
288
289       trace.description = new_trace.description
290       trace.tags = new_trace.tags
291       trace.visibility = new_trace.visibility
292       trace.save!
293
294       render :nothing => true, :status => :ok
295     else
296       render :nothing => true, :status => :forbidden
297     end
298   end
299
300   def api_delete
301     trace = Trace.visible.find(params[:id])
302
303     if trace.user == @user
304       trace.visible = false
305       trace.save!
306
307       render :nothing => true, :status => :ok
308     else
309       render :nothing => true, :status => :forbidden
310     end
311   end
312
313   def api_data
314     trace = Trace.find(params[:id])
315
316     if trace.public? or trace.user == @user
317       if request.format == Mime::XML
318         send_file(trace.xml_file, :filename => "#{trace.id}.xml", :type => Mime::XML.to_s, :disposition => 'attachment')
319       else
320         send_file(trace.trace_name, :filename => "#{trace.id}#{trace.extension_name}", :type => trace.mime_type, :disposition => 'attachment')
321       end
322     else
323       render :nothing => true, :status => :forbidden
324     end
325   end
326
327   def api_create
328     tags = params[:tags] || ""
329     description = params[:description] || ""
330     visibility = params[:visibility]
331
332     if visibility.nil?
333       if params[:public] && params[:public].to_i.nonzero?
334         visibility = "public"
335       else
336         visibility = "private"
337       end
338     end
339
340     if params[:file].respond_to?(:read)
341       do_create(params[:file], tags, description, visibility)
342
343       if @trace.id
344         render :text => @trace.id.to_s, :content_type => "text/plain"
345       elsif @trace.valid?
346         render :nothing => true, :status => :internal_server_error
347       else
348         render :nothing => true, :status => :bad_request
349       end
350     else
351       render :nothing => true, :status => :bad_request
352     end
353   end
354
355 private
356
357   def do_create(file, tags, description, visibility)
358     # Sanitise the user's filename
359     name = file.original_filename.gsub(/[^a-zA-Z0-9.]/, '_')
360
361     # Get a temporary filename...
362     filename = "/tmp/#{rand}"
363
364     # ...and save the uploaded file to that location
365     File.open(filename, "wb") { |f| f.write(file.read) }
366
367     # Create the trace object, falsely marked as already
368     # inserted to stop the import daemon trying to load it
369     @trace = Trace.new({
370       :name => name,
371       :tagstring => tags,
372       :description => description,
373       :visibility => visibility,
374       :inserted => true,
375       :user => @user,
376       :timestamp => Time.now.getutc
377     }, :without_protection => true)
378
379     Trace.transaction do
380       begin
381         # Save the trace object
382         @trace.save!
383
384         # Rename the temporary file to the final name
385         FileUtils.mv(filename, @trace.trace_name)
386       rescue Exception => ex
387         # Remove the file as we have failed to update the database
388         FileUtils.rm_f(filename)
389
390         # Pass the exception on
391         raise
392       end
393
394       begin
395         # Clear the inserted flag to make the import daemon load the trace
396         @trace.inserted = false
397         @trace.save!
398       rescue Exception => ex
399         # Remove the file as we have failed to update the database
400         FileUtils.rm_f(@trace.trace_name)
401
402         # Pass the exception on
403         raise
404       end
405     end
406
407     # Finally save the user's preferred privacy level
408     if pref = @user.preferences.where(:k => "gps.trace.visibility").first
409       pref.v = visibility
410       pref.save
411     else
412       @user.preferences.create(:k => "gps.trace.visibility", :v => visibility)
413     end
414
415   end
416
417   def offline_warning
418     flash.now[:warning] = t 'trace.offline_warning.message' if STATUS == :gpx_offline
419   end
420
421   def offline_redirect
422     redirect_to :action => :offline if STATUS == :gpx_offline
423   end
424
425   def default_visibility
426     visibility = @user.preferences.where(:k => "gps.trace.visibility").first
427
428     if visibility
429       visibility.v
430     elsif @user.preferences.where(:k => "gps.trace.public", :v => "default").first.nil?
431       "private"
432     else
433       "public"
434     end
435   end
436
437 end