diff options
Diffstat (limited to 'app/controllers')
25 files changed, 1204 insertions, 848 deletions
diff --git a/app/controllers/admin_censor_rule_controller.rb b/app/controllers/admin_censor_rule_controller.rb index 5381921bf..6f79b5ba1 100644 --- a/app/controllers/admin_censor_rule_controller.rb +++ b/app/controllers/admin_censor_rule_controller.rb @@ -2,7 +2,7 @@ # For modifying requests. # # Copyright (c) 2008 UK Citizens Online Democracy. All rights reserved. -# Email: francis@mysociety.org; WWW: http://www.mysociety.org/ +# Email: hello@mysociety.org; WWW: http://www.mysociety.org/ class AdminCensorRuleController < AdminController def new @@ -10,7 +10,7 @@ class AdminCensorRuleController < AdminController @info_request = InfoRequest.find(params[:info_request_id]) end if params[:user_id] - @user = User.find(params[:user_id]) + @censor_user = User.find(params[:user_id]) end end @@ -26,9 +26,9 @@ class AdminCensorRuleController < AdminController end flash[:notice] = 'CensorRule was successfully created.' if !@censor_rule.info_request.nil? - redirect_to admin_url('request/show/' + @censor_rule.info_request.id.to_s) + redirect_to admin_request_show_url(@censor_rule.info_request) elsif !@censor_rule.user.nil? - redirect_to admin_url('user/show/' + @censor_rule.user.id.to_s) + redirect_to admin_user_show_url(@censor_rule.user) else raise "internal error" end @@ -53,9 +53,9 @@ class AdminCensorRuleController < AdminController end flash[:notice] = 'CensorRule was successfully updated.' if !@censor_rule.info_request.nil? - redirect_to admin_url('request/show/' + @censor_rule.info_request.id.to_s) + redirect_to admin_request_show_url(@censor_rule.info_request) elsif !@censor_rule.user.nil? - redirect_to admin_url('user/show/' + @censor_rule.user.id.to_s) + redirect_to admin_user_show_url(@censor_rule.user) else raise "internal error" end @@ -79,9 +79,9 @@ class AdminCensorRuleController < AdminController flash[:notice] = "CensorRule was successfully destroyed." if !info_request.nil? - redirect_to admin_url('request/show/' + info_request.id.to_s) + redirect_to admin_request_show_url(info_request) elsif !user.nil? - redirect_to admin_url('user/show/' + user.id.to_s) + redirect_to admin_user_show_url(user) else raise "internal error" end diff --git a/app/controllers/admin_controller.rb b/app/controllers/admin_controller.rb index d93e68dab..8b606ea85 100644 --- a/app/controllers/admin_controller.rb +++ b/app/controllers/admin_controller.rb @@ -2,7 +2,7 @@ # All admin controllers are dervied from this. # # Copyright (c) 2009 UK Citizens Online Democracy. All rights reserved. -# Email: francis@mysociety.org; WWW: http://www.mysociety.org/ +# Email: hello@mysociety.org; WWW: http://www.mysociety.org/ require 'fileutils' @@ -17,7 +17,7 @@ class AdminController < ApplicationController end # Always give full stack trace for admin interface - def local_request? + def show_rails_exceptions? true end @@ -29,8 +29,7 @@ class AdminController < ApplicationController FileUtils.rm_rf(cache_subpath) # Remove any download zips - download_dir = request_download_zip_dir(info_request) - FileUtils.rm_rf(download_dir) + FileUtils.rm_rf(info_request.download_zip_dir) # Remove the database caches of body / attachment text (the attachment text # one is after privacy rules are applied) @@ -51,7 +50,7 @@ class AdminController < ApplicationController # For administration interface, return display name of authenticated user def admin_current_user - if Configuration::skip_admin_auth + if AlaveteliConfiguration::skip_admin_auth admin_http_auth_user else session[:admin_name] @@ -74,12 +73,12 @@ class AdminController < ApplicationController end def authenticate - if Configuration::skip_admin_auth + if AlaveteliConfiguration::skip_admin_auth session[:using_admin] = 1 return else if session[:using_admin].nil? || session[:admin_name].nil? - if params[:emergency].nil? + if params[:emergency].nil? || AlaveteliConfiguration::disable_emergency_user if authenticated?( :web => _("To log into the administrative interface"), :email => _("Then you can log into the administrative interface"), @@ -89,7 +88,6 @@ class AdminController < ApplicationController session[:using_admin] = 1 session[:admin_name] = @user.url_name else - session[:using_admin] = nil session[:user_id] = nil session[:admin_name] = nil @@ -98,7 +96,7 @@ class AdminController < ApplicationController end else authenticate_or_request_with_http_basic do |user_name, password| - if user_name == Configuration::admin_username && password == Configuration::admin_password + if user_name == AlaveteliConfiguration::admin_username && password == AlaveteliConfiguration::admin_password session[:using_admin] = 1 session[:admin_name] = user_name else diff --git a/app/controllers/admin_general_controller.rb b/app/controllers/admin_general_controller.rb index 9f4c398c1..753208c9a 100644 --- a/app/controllers/admin_general_controller.rb +++ b/app/controllers/admin_general_controller.rb @@ -2,9 +2,10 @@ # Controller for main admin pages. # # Copyright (c) 2007 UK Citizens Online Democracy. All rights reserved. -# Email: francis@mysociety.org; WWW: http://www.mysociety.org/ +# Email: hello@mysociety.org; WWW: http://www.mysociety.org/ class AdminGeneralController < AdminController + def index # ensure we have a trailing slash current_uri = request.env['REQUEST_URI'] @@ -26,13 +27,16 @@ class AdminGeneralController < AdminController @comment_count = Comment.count # Tasks to do - @requires_admin_requests = InfoRequest.find(:all, :select => '*, ' + InfoRequest.last_event_time_clause + ' as last_event_time', :conditions => ["described_state = 'requires_admin'"], :order => "last_event_time") - @error_message_requests = InfoRequest.find(:all, :select => '*, ' + InfoRequest.last_event_time_clause + ' as last_event_time', :conditions => ["described_state = 'error_message'"], :order => "last_event_time") - @attention_requests = InfoRequest.find(:all, :select => '*, ' + InfoRequest.last_event_time_clause + ' as last_event_time', :conditions => ["described_state = 'attention_requested'"], :order => "last_event_time") - @blank_contacts = PublicBody.find(:all, :conditions => ["request_email = ''"], :order => "updated_at") + @requires_admin_requests = InfoRequest.find_in_state('requires_admin') + @error_message_requests = InfoRequest.find_in_state('error_message') + @attention_requests = InfoRequest.find_in_state('attention_requested') + @blank_contacts = PublicBody.find(:all, :conditions => ["request_email = ''"], + :order => "updated_at") @old_unclassified = InfoRequest.find_old_unclassified(:limit => 20, - :conditions => ["prominence = 'normal'"]) + :conditions => ["prominence = 'normal'"]) @holding_pen_messages = InfoRequest.holding_pen_request.incoming_messages + @new_body_requests = PublicBodyChangeRequest.new_body_requests.open + @body_update_requests = PublicBodyChangeRequest.body_update_requests.open end def timeline @@ -115,18 +119,30 @@ class AdminGeneralController < AdminController end def stats + # Overview counts of things + @public_body_count = PublicBody.count + + @info_request_count = InfoRequest.count + @outgoing_message_count = OutgoingMessage.count + @incoming_message_count = IncomingMessage.count + + @user_count = User.count + @track_thing_count = TrackThing.count + + @comment_count = Comment.count @request_by_state = InfoRequest.count(:group => 'described_state') @tracks_by_type = TrackThing.count(:group => 'track_type') end def debug @admin_current_user = admin_current_user - @current_commit = `git log -1 --format="%H"` + @current_commit = alaveteli_git_commit @current_branch = `git branch | perl -ne 'print $1 if /^\\* (.*)/'` @current_version = `git describe --always --tags` repo = `git remote show origin -n | perl -ne 'print $1 if m{Fetch URL: .*github\\.com[:/](.*)\\.git}'` @github_origin = "https://github.com/#{repo}/tree/" @request_env = request.env end + end diff --git a/app/controllers/admin_incoming_message_controller.rb b/app/controllers/admin_incoming_message_controller.rb new file mode 100644 index 000000000..6b50d0e36 --- /dev/null +++ b/app/controllers/admin_incoming_message_controller.rb @@ -0,0 +1,80 @@ +class AdminIncomingMessageController < AdminController + + def edit + @incoming_message = IncomingMessage.find(params[:id]) + end + + def update + @incoming_message = IncomingMessage.find(params[:id]) + old_prominence = @incoming_message.prominence + old_prominence_reason = @incoming_message.prominence_reason + @incoming_message.prominence = params[:incoming_message][:prominence] + @incoming_message.prominence_reason = params[:incoming_message][:prominence_reason] + if @incoming_message.save + @incoming_message.info_request.log_event('edit_incoming', + :incoming_message_id => @incoming_message.id, + :editor => admin_current_user(), + :old_prominence => old_prominence, + :prominence => @incoming_message.prominence, + :old_prominence_reason => old_prominence_reason, + :prominence_reason => @incoming_message.prominence_reason) + expire_for_request(@incoming_message.info_request) + flash[:notice] = 'Incoming message successfully updated.' + redirect_to admin_request_show_url(@incoming_message.info_request) + else + render :action => 'edit' + end + end + + def destroy + @incoming_message = IncomingMessage.find(params[:incoming_message_id]) + @info_request = @incoming_message.info_request + incoming_message_id = @incoming_message.id + + @incoming_message.fully_destroy + @incoming_message.info_request.log_event("destroy_incoming", + { :editor => admin_current_user(), :deleted_incoming_message_id => incoming_message_id }) + # expire cached files + expire_for_request(@info_request) + flash[:notice] = 'Incoming message successfully destroyed.' + redirect_to admin_request_show_url(@info_request) + end + + def redeliver + incoming_message = IncomingMessage.find(params[:redeliver_incoming_message_id]) + message_ids = params[:url_title].split(",").each {|x| x.strip} + previous_request = incoming_message.info_request + destination_request = nil + ActiveRecord::Base.transaction do + for m in message_ids + if m.match(/^[0-9]+$/) + destination_request = InfoRequest.find_by_id(m.to_i) + else + destination_request = InfoRequest.find_by_url_title!(m) + end + if destination_request.nil? + flash[:error] = "Failed to find destination request '" + m + "'" + return redirect_to admin_request_show_url(previous_request) + end + + raw_email_data = incoming_message.raw_email.data + mail = MailHandler.mail_from_raw_email(raw_email_data) + destination_request.receive(mail, raw_email_data, true) + + incoming_message_id = incoming_message.id + incoming_message.info_request.log_event("redeliver_incoming", { + :editor => admin_current_user(), + :destination_request => destination_request.id, + :deleted_incoming_message_id => incoming_message_id + }) + + flash[:notice] = "Message has been moved to request(s). Showing the last one:" + end + # expire cached files + expire_for_request(previous_request) + incoming_message.fully_destroy + end + redirect_to admin_request_show_url(destination_request) + end + +end diff --git a/app/controllers/admin_outgoing_message_controller.rb b/app/controllers/admin_outgoing_message_controller.rb new file mode 100644 index 000000000..ec0981677 --- /dev/null +++ b/app/controllers/admin_outgoing_message_controller.rb @@ -0,0 +1,47 @@ +class AdminOutgoingMessageController < AdminController + + def edit + @outgoing_message = OutgoingMessage.find(params[:id]) + end + + def destroy + @outgoing_message = OutgoingMessage.find(params[:outgoing_message_id]) + @info_request = @outgoing_message.info_request + outgoing_message_id = @outgoing_message.id + + @outgoing_message.fully_destroy + @outgoing_message.info_request.log_event("destroy_outgoing", + { :editor => admin_current_user(), :deleted_outgoing_message_id => outgoing_message_id }) + + flash[:notice] = 'Outgoing message successfully destroyed.' + redirect_to admin_request_show_url(@info_request) + end + + def update + @outgoing_message = OutgoingMessage.find(params[:id]) + + old_body = @outgoing_message.body + old_prominence = @outgoing_message.prominence + old_prominence_reason = @outgoing_message.prominence_reason + @outgoing_message.prominence = params[:outgoing_message][:prominence] + @outgoing_message.prominence_reason = params[:outgoing_message][:prominence_reason] + @outgoing_message.body = params[:outgoing_message][:body] + if @outgoing_message.save + @outgoing_message.info_request.log_event("edit_outgoing", + { :outgoing_message_id => @outgoing_message.id, + :editor => admin_current_user(), + :old_body => old_body, + :body => @outgoing_message.body, + :old_prominence => old_prominence, + :old_prominence_reason => old_prominence_reason, + :prominence => @outgoing_message.prominence, + :prominence_reason => @outgoing_message.prominence_reason }) + flash[:notice] = 'Outgoing message successfully updated.' + expire_for_request(@outgoing_message.info_request) + redirect_to admin_request_show_url(@outgoing_message.info_request) + else + render :action => 'edit' + end + end + +end diff --git a/app/controllers/admin_public_body_change_requests_controller.rb b/app/controllers/admin_public_body_change_requests_controller.rb new file mode 100644 index 000000000..d76cdc0e5 --- /dev/null +++ b/app/controllers/admin_public_body_change_requests_controller.rb @@ -0,0 +1,15 @@ +class AdminPublicBodyChangeRequestsController < AdminController + + def edit + @change_request = PublicBodyChangeRequest.find(params[:id]) + end + + def update + @change_request = PublicBodyChangeRequest.find(params[:id]) + @change_request.close! + @change_request.send_response(params[:subject], params[:response]) + flash[:notice] = 'The change request has been closed and the user has been notified' + redirect_to admin_general_index_path + end + +end diff --git a/app/controllers/admin_public_body_controller.rb b/app/controllers/admin_public_body_controller.rb index ac12e97b2..120419a27 100644 --- a/app/controllers/admin_public_body_controller.rb +++ b/app/controllers/admin_public_body_controller.rb @@ -2,7 +2,7 @@ # Controller for editing public bodies from the admin interface. # # Copyright (c) 2007 UK Citizens Online Democracy. All rights reserved. -# Email: francis@mysociety.org; WWW: http://www.mysociety.org/ +# Email: hello@mysociety.org; WWW: http://www.mysociety.org/ require "public_body_categories" @@ -14,7 +14,8 @@ class AdminPublicBodyController < AdminController def _lookup_query_internal @locale = self.locale_from_params() - PublicBody.with_locale(@locale) do + underscore_locale = @locale.gsub '-', '_' + I18n.with_locale(@locale) do @query = params[:query] if @query == "" @query = nil @@ -23,12 +24,10 @@ class AdminPublicBodyController < AdminController if @page == "" @page = nil end - @public_bodies = PublicBody.paginate :order => "public_body_translations.name", :page => @page, :per_page => 100, - :conditions => @query.nil? ? "public_body_translations.locale = '#{@locale}'" : + @public_bodies = PublicBody.joins(:translations).where(@query.nil? ? "public_body_translations.locale = '#{underscore_locale}'" : ["(lower(public_body_translations.name) like lower('%'||?||'%') or lower(public_body_translations.short_name) like lower('%'||?||'%') or - lower(public_body_translations.request_email) like lower('%'||?||'%' )) AND (public_body_translations.locale = '#{@locale}')", @query, @query, @query], - :joins => :translations + lower(public_body_translations.request_email) like lower('%'||?||'%' )) AND (public_body_translations.locale = '#{underscore_locale}')", @query, @query, @query]).paginate :order => "public_body_translations.name", :page => @page, :per_page => 100 end @public_bodies_by_tag = PublicBody.find_by_tag(@query) end @@ -75,24 +74,46 @@ class AdminPublicBodyController < AdminController def show @locale = self.locale_from_params() - PublicBody.with_locale(@locale) do + I18n.with_locale(@locale) do @public_body = PublicBody.find(params[:id]) + @info_requests = @public_body.info_requests.paginate :order => "created_at desc", + :page => params[:page], + :per_page => 100 render end end def new @public_body = PublicBody.new - render + if params[:change_request_id] + @change_request = PublicBodyChangeRequest.find(params[:change_request_id]) + end + if @change_request + @change_request_user_response = render_to_string(:template => "admin_public_body_change_requests/add_accepted", + :formats => [:txt]) + @public_body.name = @change_request.public_body_name + @public_body.request_email = @change_request.public_body_email + @public_body.last_edit_comment = @change_request.comment_for_public_body + end + render :formats => [:html] end def create - PublicBody.with_locale(I18n.default_locale) do + I18n.with_locale(I18n.default_locale) do + if params[:change_request_id] + @change_request = PublicBodyChangeRequest.find(params[:change_request_id]) + end params[:public_body][:last_edit_editor] = admin_current_user() @public_body = PublicBody.new(params[:public_body]) if @public_body.save + if @change_request + response_text = params[:response].gsub(_("[Authority URL will be inserted here]"), + public_body_url(@public_body, :only_path => false)) + @change_request.close! + @change_request.send_response(params[:subject], response_text) + end flash[:notice] = 'PublicBody was successfully created.' - redirect_to admin_url('body/show/' + @public_body.id.to_s) + redirect_to admin_body_show_url(@public_body) else render :action => 'new' end @@ -101,17 +122,34 @@ class AdminPublicBodyController < AdminController def edit @public_body = PublicBody.find(params[:id]) - @public_body.last_edit_comment = "" - render + if params[:change_request_id] + @change_request = PublicBodyChangeRequest.find(params[:change_request_id]) + end + if @change_request + @change_request_user_response = render_to_string(:template => "admin_public_body_change_requests/update_accepted", + :formats => [:txt]) + @public_body.request_email = @change_request.public_body_email + @public_body.last_edit_comment = @change_request.comment_for_public_body + else + @public_body.last_edit_comment = "" + end + render :formats => [:html] end def update - PublicBody.with_locale(I18n.default_locale) do + if params[:change_request_id] + @change_request = PublicBodyChangeRequest.find(params[:change_request_id]) + end + I18n.with_locale(I18n.default_locale) do params[:public_body][:last_edit_editor] = admin_current_user() @public_body = PublicBody.find(params[:id]) if @public_body.update_attributes(params[:public_body]) + if @change_request + @change_request.close! + @change_request.send_response(params[:subject], params[:response]) + end flash[:notice] = 'PublicBody was successfully updated.' - redirect_to admin_url('body/show/' + @public_body.id.to_s) + redirect_to admin_body_show_url(@public_body) else render :action => 'edit' end @@ -120,7 +158,7 @@ class AdminPublicBodyController < AdminController def destroy @locale = self.locale_from_params() - PublicBody.with_locale(@locale) do + I18n.with_locale(@locale) do public_body = PublicBody.find(params[:id]) if public_body.info_requests.size > 0 @@ -141,16 +179,18 @@ class AdminPublicBodyController < AdminController @errors = "" if request.post? dry_run_only = (params['commit'] == 'Upload' ? false : true) + # (FIXME: both of these cases could now be changed to use + # PublicBody.import_csv_from_file.) # Read file from params if params[:csv_file] csv_contents = params[:csv_file].read @original_csv_file = params[:csv_file].original_filename + csv_contents = normalize_string_to_utf8(csv_contents) # or from previous dry-run temporary file elsif params[:temporary_csv_file] && params[:original_csv_file] csv_contents = retrieve_csv_data(params[:temporary_csv_file]) @original_csv_file = params[:original_csv_file] end - if !csv_contents.nil? # Try with dry run first errors, notes = PublicBody.import_csv(csv_contents, diff --git a/app/controllers/admin_request_controller.rb b/app/controllers/admin_request_controller.rb index e39d55c7c..fc291d998 100644 --- a/app/controllers/admin_request_controller.rb +++ b/app/controllers/admin_request_controller.rb @@ -2,7 +2,7 @@ # Controller for viewing FOI requests from the admin interface. # # Copyright (c) 2007 UK Citizens Online Democracy. All rights reserved. -# Email: francis@mysociety.org; WWW: http://www.mysociety.org/ +# Email: hello@mysociety.org; WWW: http://www.mysociety.org/ require 'ostruct' @@ -14,45 +14,32 @@ class AdminRequestController < AdminController def list @query = params[:query] - @info_requests = InfoRequest.paginate :order => "created_at desc", + if @query + info_requests = InfoRequest.where(["lower(title) like lower('%'||?||'%')", @query]) + else + info_requests = InfoRequest + end + @info_requests = info_requests.paginate :order => "created_at desc", :page => params[:page], - :per_page => 100, - :conditions => @query.nil? ? nil : ["lower(title) like lower('%'||?||'%')", @query] - end - - def list_old_unclassified - @info_requests = WillPaginate::Collection.create((params[:page] or 1), 50) do |pager| - info_requests = InfoRequest.find_old_unclassified(:conditions => ["prominence = 'normal'"], - :limit => pager.per_page, - :offset => pager.offset) - # inject the result array into the paginated collection: - pager.replace(info_requests) - - unless pager.total_entries - # the pager didn't manage to guess the total count, do it manually - pager.total_entries = InfoRequest.count_old_unclassified(:conditions => ["prominence = 'normal'"]) - end - end + :per_page => 100 end def show @info_request = InfoRequest.find(params[:id]) - # XXX is this *really* the only way to render a template to a - # variable, rather than to the response? - vars = OpenStruct.new(:name_to => @info_request.user_name, - :name_from => Configuration::contact_name, - :info_request => @info_request, :reason => params[:reason], - :info_request_url => 'http://' + Configuration::domain + request_url(@info_request), - :site_name => site_name) - template = File.read(File.join(File.dirname(__FILE__), "..", "views", "admin_request", "hidden_user_explanation.rhtml")) - @request_hidden_user_explanation = ERB.new(template).result(vars.instance_eval { binding }) + vars_for_explanation = {:reason => params[:reason], + :info_request => @info_request, + :name_to => @info_request.user_name, + :name_from => AlaveteliConfiguration::contact_name, + :info_request_url => request_url(@info_request, :only_path => false)} + @request_hidden_user_explanation = render_to_string(:template => "admin_request/hidden_user_explanation", + :locals => vars_for_explanation) end def resend @outgoing_message = OutgoingMessage.find(params[:outgoing_message_id]) @outgoing_message.resend_message flash[:notice] = "Outgoing message resent" - redirect_to request_admin_url(@outgoing_message.info_request) + redirect_to admin_request_show_url(@outgoing_message.info_request) end def edit @@ -73,9 +60,6 @@ class AdminRequestController < AdminController @info_request.title = params[:info_request][:title] @info_request.prominence = params[:info_request][:prominence] - if @info_request.described_state != params[:info_request][:described_state] - @info_request.set_described_state(params[:info_request][:described_state]) - end @info_request.awaiting_description = params[:info_request][:awaiting_description] == "true" ? true : false @info_request.allow_new_responses_from = params[:info_request][:allow_new_responses_from] @info_request.handle_rejected_responses = params[:info_request][:handle_rejected_responses] @@ -88,17 +72,20 @@ class AdminRequestController < AdminController { :editor => admin_current_user(), :old_title => old_title, :title => @info_request.title, :old_prominence => old_prominence, :prominence => @info_request.prominence, - :old_described_state => old_described_state, :described_state => @info_request.described_state, + :old_described_state => old_described_state, :described_state => params[:info_request][:described_state], :old_awaiting_description => old_awaiting_description, :awaiting_description => @info_request.awaiting_description, :old_allow_new_responses_from => old_allow_new_responses_from, :allow_new_responses_from => @info_request.allow_new_responses_from, :old_handle_rejected_responses => old_handle_rejected_responses, :handle_rejected_responses => @info_request.handle_rejected_responses, :old_tag_string => old_tag_string, :tag_string => @info_request.tag_string, :old_comments_allowed => old_comments_allowed, :comments_allowed => @info_request.comments_allowed }) + if @info_request.described_state != params[:info_request][:described_state] + @info_request.set_described_state(params[:info_request][:described_state]) + end # expire cached files expire_for_request(@info_request) flash[:notice] = 'Request successfully updated.' - redirect_to request_admin_url(@info_request) + redirect_to admin_request_show_url(@info_request) else render :action => 'edit' end @@ -114,40 +101,7 @@ class AdminRequestController < AdminController # expire cached files expire_for_request(@info_request) flash[:notice] = "Request #{url_title} has been completely destroyed. Email of user who made request: " + user.email - redirect_to admin_url('request/list') - end - - def edit_outgoing - @outgoing_message = OutgoingMessage.find(params[:id]) - end - - def destroy_outgoing - @outgoing_message = OutgoingMessage.find(params[:outgoing_message_id]) - @info_request = @outgoing_message.info_request - outgoing_message_id = @outgoing_message.id - - @outgoing_message.fully_destroy - @outgoing_message.info_request.log_event("destroy_outgoing", - { :editor => admin_current_user(), :deleted_outgoing_message_id => outgoing_message_id }) - - flash[:notice] = 'Outgoing message successfully destroyed.' - redirect_to request_admin_url(@info_request) - end - - def update_outgoing - @outgoing_message = OutgoingMessage.find(params[:id]) - - old_body = @outgoing_message.body - - if @outgoing_message.update_attributes(params[:outgoing_message]) - @outgoing_message.info_request.log_event("edit_outgoing", - { :outgoing_message_id => @outgoing_message.id, :editor => admin_current_user(), - :old_body => old_body, :body => @outgoing_message.body }) - flash[:notice] = 'Outgoing message successfully updated.' - redirect_to request_admin_url(@outgoing_message.info_request) - else - render :action => 'edit_outgoing' - end + redirect_to admin_request_list_url end def edit_comment @@ -168,64 +122,12 @@ class AdminRequestController < AdminController :old_visible => old_visible, :visible => @comment.visible, }) flash[:notice] = 'Comment successfully updated.' - redirect_to request_admin_url(@comment.info_request) + redirect_to admin_request_show_url(@comment.info_request) else render :action => 'edit_comment' end end - - def destroy_incoming - @incoming_message = IncomingMessage.find(params[:incoming_message_id]) - @info_request = @incoming_message.info_request - incoming_message_id = @incoming_message.id - - @incoming_message.fully_destroy - @incoming_message.info_request.log_event("destroy_incoming", - { :editor => admin_current_user(), :deleted_incoming_message_id => incoming_message_id }) - # expire cached files - expire_for_request(@info_request) - flash[:notice] = 'Incoming message successfully destroyed.' - redirect_to request_admin_url(@info_request) - end - - def redeliver_incoming - incoming_message = IncomingMessage.find(params[:redeliver_incoming_message_id]) - message_ids = params[:url_title].split(",").each {|x| x.strip} - previous_request = incoming_message.info_request - destination_request = nil - ActiveRecord::Base.transaction do - for m in message_ids - if m.match(/^[0-9]+$/) - destination_request = InfoRequest.find_by_id(m.to_i) - else - destination_request = InfoRequest.find_by_url_title!(m) - end - if destination_request.nil? - flash[:error] = "Failed to find destination request '" + m + "'" - return redirect_to request_admin_url(previous_request) - end - - raw_email_data = incoming_message.raw_email.data - mail = MailHandler.mail_from_raw_email(raw_email_data) - destination_request.receive(mail, raw_email_data, true) - - incoming_message_id = incoming_message.id - incoming_message.info_request.log_event("redeliver_incoming", { - :editor => admin_current_user(), - :destination_request => destination_request.id, - :deleted_incoming_message_id => incoming_message_id - }) - - flash[:notice] = "Message has been moved to request(s). Showing the last one:" - end - # expire cached files - expire_for_request(previous_request) - incoming_message.fully_destroy - end - redirect_to request_admin_url(destination_request) - end - # change user or public body of a request magically def move_request info_request = InfoRequest.find(params[:info_request_id]) @@ -246,7 +148,7 @@ class AdminRequestController < AdminController info_request.reindex_request_events flash[:notice] = "Message has been moved to new user" end - redirect_to request_admin_url(info_request) + redirect_to admin_request_show_url(info_request) elsif params[:commit] == 'Move request to authority' && !params[:public_body_url_name].blank? old_public_body = info_request.public_body destination_public_body = PublicBody.find_by_url_name(params[:public_body_url_name]) @@ -265,10 +167,10 @@ class AdminRequestController < AdminController flash[:notice] = "Request has been moved to new body" end - redirect_to request_admin_url(info_request) + redirect_to admin_request_show_url(info_request) else flash[:error] = "Please enter the user or authority to move the request to" - redirect_to request_admin_url(info_request) + redirect_to admin_request_show_url(info_request) end end @@ -292,20 +194,20 @@ class AdminRequestController < AdminController if !info_request.public_body.is_foi_officer?(user) flash[:notice] = user.email + " is not an email at the domain @" + info_request.public_body.foi_officer_domain_required + ", so won't be able to upload." - redirect_to request_admin_url(info_request) + redirect_to admin_request_show_url(info_request) return end # Bejeeps, look, sometimes a URL is something that belongs in a controller, jesus. - # XXX hammer this square peg into the round MVC hole - should be calling main_url(upload_response_url()) + # XXX hammer this square peg into the round MVC hole post_redirect = PostRedirect.new( - :uri => main_url(upload_response_url(:url_title => info_request.url_title, :only_path => true)), + :uri => upload_response_url(:url_title => info_request.url_title), :user_id => user.id) post_redirect.save! - url = main_url(confirm_url(:email_token => post_redirect.email_token, :only_path => true)) + url = confirm_url(:email_token => post_redirect.email_token) - flash[:notice] = 'Send "' + name + '" <<a href="mailto:' + email + '">' + email + '</a>> this URL: <a href="' + url + '">' + url + "</a> - it will log them in and let them upload a response to this request.".html_safe - redirect_to request_admin_url(info_request) + flash[:notice] = ("Send \"#{name}\" <<a href=\"mailto:#{email}\">#{email}</a>> this URL: <a href=\"#{url}\">#{url}</a> - it will log them in and let them upload a response to this request.").html_safe + redirect_to admin_request_show_url(info_request) end def show_raw_email @@ -355,7 +257,7 @@ class AdminRequestController < AdminController info_request_event.save! flash[:notice] = "Old response marked as having been a clarification" - redirect_to request_admin_url(info_request_event.info_request) + redirect_to admin_request_show_url(info_request_event.info_request) end def hide_request @@ -376,18 +278,19 @@ class AdminRequestController < AdminController info_request.save! if ! info_request.is_external? - ContactMailer.deliver_from_admin_message( - info_request.user, + ContactMailer.from_admin_message( + info_request.user.name, + info_request.user.email, subject, - params[:explanation] - ) + params[:explanation].strip.html_safe + ).deliver flash[:notice] = _("Your message to {{recipient_user_name}} has been sent",:recipient_user_name=>CGI.escapeHTML(info_request.user.name)) else flash[:notice] = _("This external request has been hidden") end # expire cached files expire_for_request(info_request) - redirect_to request_admin_url(info_request) + redirect_to admin_request_show_url(info_request) end end diff --git a/app/controllers/admin_track_controller.rb b/app/controllers/admin_track_controller.rb index 03217da45..085c9c6cc 100644 --- a/app/controllers/admin_track_controller.rb +++ b/app/controllers/admin_track_controller.rb @@ -2,13 +2,18 @@ # Show email alerts / RSS feeds from admin interface. # # Copyright (c) 2008 UK Citizens Online Democracy. All rights reserved. -# Email: francis@mysociety.org; WWW: http://www.mysociety.org/ +# Email: hello@mysociety.org; WWW: http://www.mysociety.org/ class AdminTrackController < AdminController def list @query = params[:query] - @admin_tracks = TrackThing.paginate :order => "created_at desc", :page => params[:page], :per_page => 100, - :conditions => @query.nil? ? nil : ["lower(track_query) like lower('%'||?||'%')", @query ] + if @query + track_things = TrackThing.where(["lower(track_query) like lower('%'||?||'%')", @query]) + else + track_things = TrackThing + end + @admin_tracks = track_things.paginate :order => "created_at desc", :page => params[:page], :per_page => 100 + @popular = ActiveRecord::Base.connection.select_all("select count(*) as count, title, info_request_id from track_things join info_requests on info_request_id = info_requests.id where info_request_id is not null group by info_request_id, title order by count desc limit 10;") end private diff --git a/app/controllers/admin_user_controller.rb b/app/controllers/admin_user_controller.rb index ed20ddcf4..940a5fe8f 100644 --- a/app/controllers/admin_user_controller.rb +++ b/app/controllers/admin_user_controller.rb @@ -2,7 +2,7 @@ # Controller for viewing user accounts from the admin interface. # # Copyright (c) 2008 UK Citizens Online Democracy. All rights reserved. -# Email: francis@mysociety.org; WWW: http://www.mysociety.org/ +# Email: hello@mysociety.org; WWW: http://www.mysociety.org/ class AdminUserController < AdminController def index @@ -12,9 +12,13 @@ class AdminUserController < AdminController def list @query = params[:query] - @admin_users = User.paginate :order => "name", :page => params[:page], :per_page => 100, - :conditions => @query.nil? ? nil : ["lower(name) like lower('%'||?||'%') or - lower(email) like lower('%'||?||'%')", @query, @query] + if @query + users = User.where(["lower(name) like lower('%'||?||'%') or + lower(email) like lower('%'||?||'%')", @query, @query]) + else + users = User + end + @admin_users = users.paginate :order => "name", :page => params[:page], :per_page => 100 end def list_banned @@ -44,11 +48,12 @@ class AdminUserController < AdminController @admin_user.ban_text = params[:admin_user][:ban_text] @admin_user.about_me = params[:admin_user][:about_me] @admin_user.no_limit = params[:admin_user][:no_limit] + @admin_user.can_make_batch_requests = params[:admin_user][:can_make_batch_requests] if @admin_user.valid? @admin_user.save! flash[:notice] = 'User successfully updated.' - redirect_to user_admin_url(@admin_user) + redirect_to admin_user_show_url(@admin_user) else render :action => 'edit' end @@ -58,7 +63,7 @@ class AdminUserController < AdminController track_thing = TrackThing.find(params[:track_id].to_i) track_thing.destroy flash[:notice] = 'Track destroyed' - redirect_to user_admin_url(track_thing.tracking_user) + redirect_to admin_user_show_url(track_thing.tracking_user) end def clear_bounce @@ -66,15 +71,15 @@ class AdminUserController < AdminController user.email_bounced_at = nil user.email_bounce_message = "" user.save! - redirect_to user_admin_url(user) + redirect_to admin_user_show_url(user) end def login_as @admin_user = User.find(params[:id]) # check user does exist - post_redirect = PostRedirect.new( :uri => main_url(user_url(@admin_user)), :user_id => @admin_user.id, :circumstance => "login_as" ) + post_redirect = PostRedirect.new( :uri => user_url(@admin_user), :user_id => @admin_user.id, :circumstance => "login_as" ) post_redirect.save! - url = main_url(confirm_url(:email_token => post_redirect.email_token, :only_path => true)) + url = confirm_url(:email_token => post_redirect.email_token) redirect_to url end @@ -91,7 +96,7 @@ class AdminUserController < AdminController end flash[:notice] = "Profile photo cleared" - redirect_to user_admin_url(@admin_user) + redirect_to admin_user_show_url(@admin_user) end private diff --git a/app/controllers/api_controller.rb b/app/controllers/api_controller.rb index 15fb4f5f9..e6b0c121a 100644 --- a/app/controllers/api_controller.rb +++ b/app/controllers/api_controller.rb @@ -16,11 +16,14 @@ class ApiController < ApplicationController :status => @request.calculate_status, :public_body_url => make_url("body", @request.public_body.url_name), - :requestor_url => make_url("user", @request.user.url_name), + :request_email => @request.incoming_email, :request_text => @request.last_event_forming_initial_request.outgoing_message.body, } + if @request.user + @request_data[:requestor_url] = make_url("user", @request.user.url_name) + end render :json => @request_data end @@ -63,6 +66,8 @@ class ApiController < ApplicationController :smtp_message_id => nil ) + request.set_described_state('waiting_response') + # Return the URL and ID number. render :json => { 'url' => make_url("request", request.url_title), @@ -83,7 +88,7 @@ class ApiController < ApplicationController direction = json["direction"] body = json["body"] - sent_at_str = json["sent_at"] + sent_at = json["sent_at"] errors = [] @@ -107,12 +112,6 @@ class ApiController < ApplicationController errors << "The 'body' is empty" end - begin - sent_at = Time.iso8601(sent_at_str) - rescue ArgumentError - errors << "Failed to parse 'sent_at' field as ISO8601 time: #{sent_at_str}" - end - if direction == "request" && !attachments.nil? errors << "You cannot attach files to messages in the 'request' direction" end @@ -155,7 +154,8 @@ class ApiController < ApplicationController ) end - mail = RequestMailer.create_external_response(request, body, sent_at, attachment_hashes) + mail = RequestMailer.external_response(request, body, sent_at, attachment_hashes) + request.receive(mail, mail.encoded, true) end render :json => { @@ -203,7 +203,7 @@ class ApiController < ApplicationController ]) end if feed_type == "atom" - render :template => "api/request_events.atom", :layout => false + render :template => "api/request_events", :formats => ['atom'], :layout => false elsif feed_type == "json" # For the JSON feed, we take a "since" parameter that allows the client # to restrict to events more recent than a certain other event @@ -220,7 +220,7 @@ class ApiController < ApplicationController :event_id => event.id, :created_at => event.created_at.iso8601, :event_type => event.event_type, - :request_url => main_url(request_url(request)), + :request_url => request_url(request), :request_email => request.incoming_email, :title => request.title, :body => event.outgoing_message.body, @@ -228,7 +228,7 @@ class ApiController < ApplicationController :user_name => request.user_name, } if request.user - this_event[:user_url] = main_url(user_url(request.user)) + this_event[:user_url] = user_url(request.user) end @event_data.push(this_event) @@ -248,6 +248,6 @@ class ApiController < ApplicationController private def make_url(*args) - "http://" + Configuration::domain + "/" + args.join("/") + "http://" + AlaveteliConfiguration::domain + "/" + args.join("/") end end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index ed1523f75..370e8e15c 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -5,30 +5,28 @@ # will be available for all controllers. # # Copyright (c) 2007 UK Citizens Online Democracy. All rights reserved. -# Email: francis@mysociety.org; WWW: http://www.mysociety.org/ +# Email: hello@mysociety.org; WWW: http://www.mysociety.org/ require 'open-uri' class ApplicationController < ActionController::Base class PermissionDenied < StandardError end + class RouteNotFound < StandardError + end + # assign our own handler method for non-local exceptions + rescue_from Exception, :with => :render_exception + # Standard headers, footers and navigation for whole site layout "default" include FastGettext::Translation # make functions like _, n_, N_ etc available) - # Send notification email on exceptions - include ExceptionNotification::Notifiable - # Note: a filter stops the chain if it redirects or renders something before_filter :authentication_check before_filter :set_gettext_locale before_filter :check_in_post_redirect before_filter :session_remember_me before_filter :set_vary_header - before_filter :set_popup_banner - - # scrub sensitive parameters from the logs - filter_parameter_logging :password def set_vary_header response.headers['Vary'] = 'Cookie' @@ -53,19 +51,22 @@ class ApplicationController < ActionController::Base anonymous_cache(24.hours) end + # This is an override of the method provided by gettext_i18n_rails - note the explicit + # setting of I18n.locale, required due to the I18nProxy used in Rails 3 to trigger the + # lookup_context and expire the template cache def set_gettext_locale - if Configuration::include_default_locale_in_urls == false + if AlaveteliConfiguration::include_default_locale_in_urls == false params_locale = params[:locale] ? params[:locale] : I18n.default_locale else params_locale = params[:locale] end - if Configuration::use_default_browser_language + if AlaveteliConfiguration::use_default_browser_language requested_locale = params_locale || session[:locale] || cookies[:locale] || request.env['HTTP_ACCEPT_LANGUAGE'] || I18n.default_locale else requested_locale = params_locale || session[:locale] || cookies[:locale] || I18n.default_locale end requested_locale = FastGettext.best_locale_in(requested_locale) - session[:locale] = FastGettext.set_locale(requested_locale) + session[:locale] = I18n.locale = FastGettext.set_locale(requested_locale) if !@user.nil? if @user.locale != requested_locale @user.locale = session[:locale] @@ -74,9 +75,6 @@ class ApplicationController < ActionController::Base end end - # scrub sensitive parameters from the logs - filter_parameter_logging :password - helper_method :locale_from_params # Help work out which request causes RAM spike. @@ -92,7 +90,7 @@ class ApplicationController < ActionController::Base # egrep "CONSUME MEMORY: [0-9]{7} KB" production.log around_filter :record_memory def record_memory - record_memory = Configuration::debug_record_memory + record_memory = AlaveteliConfiguration::debug_record_memory if record_memory logger.info "Processing request for #{request.url} with Rails process #{Process.pid}" File.read("/proc/#{Process.pid}/status").match(/VmRSS:\s+(\d+)/) @@ -120,61 +118,45 @@ class ApplicationController < ActionController::Base end end - # Override default error handler, for production sites. - def rescue_action_in_public(exception) - # Looks for before_filters called something like `set_view_paths_{themename}`. These - # are set by the themes. - # Normally, this is called by the theme itself in a - # :before_filter, but when there's an error, this doesn't - # happen. By calling it here, we can ensure error pages are - # still styled according to the theme. - ActionController::Base.before_filters.select{|f| f.to_s =~ /set_view_paths/}.each do |f| - self.send(f) + def render_exception(exception) + # In development or the admin interface let Rails handle the exception + # with its stack trace templates + if Rails.application.config.consider_all_requests_local || show_rails_exceptions? + raise exception end - # Make sure expiry time for session is set (before_filters are - # otherwise missed by this override) - session_remember_me - - # Make sure the locale is set correctly too - set_gettext_locale + @exception_backtrace = exception.backtrace.join("\n") + @exception_class = exception.class.to_s + @exception_message = exception.message case exception - when ActiveRecord::RecordNotFound, ActionController::UnknownAction, ActionController::RoutingError + when ActiveRecord::RecordNotFound, RouteNotFound @status = 404 when PermissionDenied @status = 403 else + message = "\n#{@exception_class} (#{@exception_message}):\n" + backtrace = Rails.backtrace_cleaner.clean(exception.backtrace, :silent) + message << " " << backtrace.join("\n ") + Rails.logger.fatal("#{message}\n\n") + if !AlaveteliConfiguration.exception_notifications_from.blank? && !AlaveteliConfiguration.exception_notifications_to.blank? + ExceptionNotifier::Notifier.exception_notification(request.env, exception).deliver + end @status = 500 - notify_about_exception exception end - # Display user appropriate error message - @exception_backtrace = exception.backtrace.join("\n") - @exception_class = exception.class.to_s - @exception_message = exception.message - render :template => "general/exception_caught.rhtml", :status => @status - end - - # For development sites. - alias original_rescue_action_locally rescue_action_locally - def rescue_action_locally(exception) - # Make sure expiry time for session is set (before_filters are - # otherwise missed by this override) - session_remember_me - - # Make sure the locale is set correctly too - set_gettext_locale - - # Display default, detailed error for developers - original_rescue_action_locally(exception) + respond_to do |format| + format.html{ render :template => "general/exception_caught", :status => @status } + format.any{ render :nothing => true, :status => @status } + end end - def local_request? + def show_rails_exceptions? false end # Called from test code, is a mimic of UserController.confirm, for use in following email # links when in controller tests (though we also have full integration tests that # can work over multiple controllers) + # TODO: Move this to the tests. It shouldn't be here def test_code_redirect_by_email_token(token, controller_example_group) post_redirect = PostRedirect.find_by_email_token(token) if post_redirect.nil? @@ -182,7 +164,7 @@ class ApplicationController < ActionController::Base end session[:user_id] = post_redirect.user.id session[:user_circumstance] = post_redirect.circumstance - params = controller_example_group.params_from(:get, post_redirect.local_part_uri) + params = Rails.application.routes.recognize_path(post_redirect.local_part_uri) params.merge(post_redirect.post_params) controller_example_group.get params[:action], params end @@ -231,19 +213,6 @@ class ApplicationController < ActionController::Base end end - def request_dirs(info_request) - first_three_digits = info_request.id.to_s()[0..2] - File.join(first_three_digits.to_s, info_request.id.to_s) - end - - def request_download_zip_dir(info_request) - File.join(download_zip_dir, "download", request_dirs(info_request)) - end - - def download_zip_dir() - File.join(Rails.root, '/cache/zips/') - end - # get the local locale def locale_from_params(*args) if params[:show_locale] @@ -258,7 +227,7 @@ class ApplicationController < ActionController::Base # Check the user is logged in def authenticated?(reason_params) unless session[:user_id] - post_redirect = PostRedirect.new(:uri => request.request_uri, :post_params => params, + post_redirect = PostRedirect.new(:uri => request.fullpath, :post_params => params, :reason_params => reason_params) post_redirect.save! # 'modal' controls whether the sign-in form will be displayed in the typical full-blown @@ -346,10 +315,10 @@ class ApplicationController < ActionController::Base # def check_read_only - if !Configuration::read_only.empty? + if !AlaveteliConfiguration::read_only.empty? flash[:notice] = _("<p>{{site_name}} is currently in maintenance. You can only view existing requests. You cannot make new ones, add followups or annotations, or otherwise change the database.</p> <p>{{read_only}}</p>", :site_name => site_name, - :read_only => Configuration::read_only) + :read_only => AlaveteliConfiguration::read_only) redirect_to frontpage_url end @@ -380,12 +349,15 @@ class ApplicationController < ActionController::Base # Peform the search @per_page = per_page - if this_page.nil? - @page = get_search_page_from_params - else - @page = this_page - end - result = InfoRequest.full_search(models, @query, order, ascending, collapse, @per_page, @page) + @page = this_page || get_search_page_from_params + + result = ActsAsXapian::Search.new(models, @query, + :offset => (@page - 1) * @per_page, + :limit => @per_page, + :sort_by_prefix => order, + :sort_by_ascending => ascending, + :collapse_by_prefix => collapse + ) result.results # Touch the results to load them, otherwise accessing them from the view # might fail later if the database has subsequently been reopened. return result @@ -397,9 +369,9 @@ class ApplicationController < ActionController::Base return page end - def perform_search_typeahead(query, model) + def perform_search_typeahead(query, model, per_page=25) @page = get_search_page_from_params - @per_page = 10 + @per_page = per_page query_words = query.split(/ +(?![-+]+)/) if query_words.last.nil? || query_words.last.strip.length < 3 xapian_requests = nil @@ -456,116 +428,19 @@ class ApplicationController < ActionController::Base end end - def param_exists(item) - return params[item] && !params[item].empty? - end - - def get_request_variety_from_params - query = "" - sortby = "newest" - varieties = [] - if params[:request_variety] && !(query =~ /variety:/) - if params[:request_variety].include? "sent" - varieties -= ['variety:sent', 'variety:followup_sent', 'variety:response', 'variety:comment'] - varieties << ['variety:sent', 'variety:followup_sent'] - end - if params[:request_variety].include? "response" - varieties << ['variety:response'] - end - if params[:request_variety].include? "comment" - varieties << ['variety:comment'] - end - end - if !varieties.empty? - query = " (#{varieties.join(' OR ')})" - end - return query - end - - def get_status_from_params - query = "" - if params[:latest_status] - statuses = [] - if params[:latest_status].class == String - params[:latest_status] = [params[:latest_status]] - end - if params[:latest_status].include?("recent") || params[:latest_status].include?("all") - query += " (variety:sent OR variety:followup_sent OR variety:response OR variety:comment)" - end - if params[:latest_status].include? "successful" - statuses << ['latest_status:successful', 'latest_status:partially_successful'] - end - if params[:latest_status].include? "unsuccessful" - statuses << ['latest_status:rejected', 'latest_status:not_held'] - end - if params[:latest_status].include? "awaiting" - statuses << ['latest_status:waiting_response', 'latest_status:waiting_clarification', 'waiting_classification:true', 'latest_status:internal_review','latest_status:gone_postal', 'latest_status:error_message', 'latest_status:requires_admin'] - end - if params[:latest_status].include? "internal_review" - statuses << ['status:internal_review'] - end - if params[:latest_status].include? "other" - statuses << ['latest_status:gone_postal', 'latest_status:error_message', 'latest_status:requires_admin', 'latest_status:user_withdrawn'] - end - if params[:latest_status].include? "gone_postal" - statuses << ['latest_status:gone_postal'] - end - if !statuses.empty? - query = " (#{statuses.join(' OR ')})" - end - end - return query - end - - def get_date_range_from_params - query = "" - if param_exists(:request_date_after) && !param_exists(:request_date_before) - params[:request_date_before] = Time.now.strftime("%d/%m/%Y") - query += " #{params[:request_date_after]}..#{params[:request_date_before]}" - elsif !param_exists(:request_date_after) && param_exists(:request_date_before) - params[:request_date_after] = "01/01/2001" - end - if param_exists(:request_date_after) - query = " #{params[:request_date_after]}..#{params[:request_date_before]}" - end - return query - end - - def get_tags_from_params - query = "" - tags = [] - if param_exists(:tags) - params[:tags].split().each do |tag| - tags << "tag:#{tag}" - end - end - if !tags.empty? - query = " (#{tags.join(' OR ')})" - end - return query - end - - def make_query_from_params - query = params[:query] || "" if query.nil? - query += get_date_range_from_params - query += get_request_variety_from_params - query += get_status_from_params - query += get_tags_from_params - return query - end - def country_from_ip country = "" - if !Configuration::gaze_url.empty? - country = quietly_try_to_open("#{Configuration::gaze_url}/gaze-rest?f=get_country_from_ip;ip=#{request.remote_ip}") + if !AlaveteliConfiguration::gaze_url.empty? + country = quietly_try_to_open("#{AlaveteliConfiguration::gaze_url}/gaze-rest?f=get_country_from_ip;ip=#{request.remote_ip}") end - country = Configuration::iso_country_code if country.empty? + country = AlaveteliConfiguration::iso_country_code if country.empty? return country end - def set_popup_banner - @popup_banner = render_to_string(:partial => "general/popup_banner").strip.html_safe + def alaveteli_git_commit + `git log -1 --format="%H"`.strip end + # URL generating functions are needed by all controllers (for redirects), # views (for links) and mailers (for use in emails), so include them into # all of all. diff --git a/app/controllers/comment_controller.rb b/app/controllers/comment_controller.rb index ed249d6cc..cda56a211 100644 --- a/app/controllers/comment_controller.rb +++ b/app/controllers/comment_controller.rb @@ -2,7 +2,7 @@ # Show annotations upon a request or other object. # # Copyright (c) 2008 UK Citizens Online Democracy. All rights reserved. -# Email: francis@mysociety.org; WWW: http://www.mysociety.org/ +# Email: hello@mysociety.org; WWW: http://www.mysociety.org/ class CommentController < ApplicationController before_filter :check_read_only, :only => [ :new ] @@ -38,7 +38,7 @@ class CommentController < ApplicationController if params[:comment] # XXX this check should theoretically be a validation rule in the model - @existing_comment = Comment.find_by_existing_comment(@info_request.id, params[:comment][:body]) + @existing_comment = Comment.find_existing(@info_request.id, params[:comment][:body]) else # Default to subscribing to request when first viewing form params[:subscribe_to_request] = true @@ -68,7 +68,7 @@ class CommentController < ApplicationController if params[:subscribe_to_request] @track_thing = TrackThing.create_track_for_request(@info_request) - @existing_track = TrackThing.find_by_existing_track(@user, @track_thing) + @existing_track = TrackThing.find_existing(@user, @track_thing) if @user && @info_request.user == @user # don't subscribe to own request! elsif !@existing_track diff --git a/app/controllers/general_controller.rb b/app/controllers/general_controller.rb index 875e39494..6f0d29889 100644 --- a/app/controllers/general_controller.rb +++ b/app/controllers/general_controller.rb @@ -3,14 +3,7 @@ # particular model. # # Copyright (c) 2008 UK Citizens Online Democracy. All rights reserved. -# Email: francis@mysociety.org; WWW: http://www.mysociety.org/ - -begin - require 'xmlsimple' -rescue LoadError - # Debian maintainers put their xmlsimple in a different location :( - require 'lib/xmlsimple' -end +# Email: hello@mysociety.org; WWW: http://www.mysociety.org/ require 'open-uri' @@ -19,59 +12,18 @@ class GeneralController < ApplicationController # New, improved front page! def frontpage medium_cache - # get some example searches and public bodies to display - # either from config, or based on a (slow!) query if not set - body_short_names = Configuration::frontpage_publicbody_examples.split(/\s*;\s*/).map{|s| "'%s'" % s.gsub(/'/, "''") }.join(", ") @locale = self.locale_from_params() - locale_condition = 'public_body_translations.locale = ?' - conditions = [locale_condition, @locale] - PublicBody.with_locale(@locale) do - if body_short_names.empty? - # This is too slow - @popular_bodies = PublicBody.visible.find(:all, - :order => "info_requests_count desc", - :limit => 32, - :conditions => conditions, - :joins => :translations - ) - else - conditions[0] += " and public_bodies.url_name in (" + body_short_names + ")" - @popular_bodies = PublicBody.find(:all, - :conditions => conditions, - :joins => :translations) - end - end - # Get some successful requests - begin - query = 'variety:response (status:successful OR status:partially_successful)' - sortby = "newest" - max_count = 5 - xapian_object = perform_search([InfoRequestEvent], query, sortby, 'request_title_collapse', max_count) - @request_events = xapian_object.results.map { |r| r[:model] } - - # If there are not yet enough successful requests, fill out the list with - # other requests - if @request_events.count < max_count - @request_events_all_successful = false - query = 'variety:sent' - xapian_object = perform_search([InfoRequestEvent], query, sortby, 'request_title_collapse', max_count-@request_events.count) - more_events = xapian_object.results.map { |r| r[:model] } - @request_events += more_events - # Overall we still want the list sorted with the newest first - @request_events.sort!{|e1,e2| e2.created_at <=> e1.created_at} - else - @request_events_all_successful = true - end - rescue - @request_events = [] - end end # Display blog entries def blog + if AlaveteliConfiguration::blog_feed.empty? + raise ActiveRecord::RecordNotFound.new("Page not enabled") + end + medium_cache @feed_autodetect = [] - @feed_url = Configuration::blog_feed + @feed_url = AlaveteliConfiguration::blog_feed separator = @feed_url.include?('?') ? '&' : '?' @feed_url = "#{@feed_url}#{separator}lang=#{self.locale_from_params()}" @blog_items = [] @@ -84,7 +36,7 @@ class GeneralController < ApplicationController @feed_autodetect = [{:url => @feed_url, :title => "#{site_name} blog"}] end end - @twitter_user = Configuration::twitter_username + @twitter_user = AlaveteliConfiguration::twitter_username end # Just does a redirect from ?query= search to /query @@ -109,7 +61,7 @@ class GeneralController < ApplicationController def search # XXX Why is this so complicated with arrays and stuff? Look at the route # in config/routes.rb for comments. - combined = params[:combined] + combined = params[:combined].split("/") @sortby = nil @bodies = @requests = @users = true if combined.size > 0 && (['advanced'].include?(combined[-1])) @@ -151,16 +103,15 @@ class GeneralController < ApplicationController params[:query] = @query end if @variety_postfix != "all" && @requests - @query, _ = make_query_from_params + @query = InfoRequestEvent.make_query_from_params(params) end @inputted_sortby = @sortby - @common_query = get_tags_from_params if @sortby.nil? # Parse query, so can work out if it has prefix terms only - if so then it is a # structured query which should show newest first, rather than a free text search # where we want most relevant as default. begin - dummy_query = ::ActsAsXapian::Search.new([InfoRequestEvent], @query, :limit => 1) + dummy_query = ActsAsXapian::Search.new([InfoRequestEvent], @query, :limit => 1) rescue => e flash[:error] = "Your query was not quite right. " + CGI.escapeHTML(e.to_str) redirect_to search_url("") @@ -176,10 +127,8 @@ class GeneralController < ApplicationController # Query each type separately for separate display (XXX we are calling # perform_search multiple times and it clobbers per_page for each one, # so set as separate var) - requests_per_page = 25 - if params[:requests_per_page] - requests_per_page = params[:requests_per_page].to_i - end + requests_per_page = params[:requests_per_page] ? params[:requests_per_page].to_i : 25 + @this_page_hits = @total_hits = @xapian_requests_hits = @xapian_bodies_hits = @xapian_users_hits = 0 if @requests @xapian_requests = perform_search([InfoRequestEvent], @query, @sortby, 'request_collapse', requests_per_page) @@ -188,6 +137,7 @@ class GeneralController < ApplicationController @xapian_requests_hits = @xapian_requests.results.size @xapian_requests_total_hits = @xapian_requests.matches_estimated @total_hits += @xapian_requests.matches_estimated + @request_for_spelling = @xapian_requests end if @bodies @xapian_bodies = perform_search([PublicBody], @query, @sortby, nil, 5) @@ -196,6 +146,7 @@ class GeneralController < ApplicationController @xapian_bodies_hits = @xapian_bodies.results.size @xapian_bodies_total_hits = @xapian_bodies.matches_estimated @total_hits += @xapian_bodies.matches_estimated + @request_for_spelling = @xapian_bodies end if @users @xapian_users = perform_search([User], @query, @sortby, nil, 5) @@ -204,32 +155,32 @@ class GeneralController < ApplicationController @xapian_users_hits = @xapian_users.results.size @xapian_users_total_hits = @xapian_users.matches_estimated @total_hits += @xapian_users.matches_estimated + @request_for_spelling = @xapian_users end # Spelling and highight words are same for all three queries - if !@xapian_requests.nil? - @highlight_words = @xapian_requests.words_to_highlight - if !(@xapian_requests.spelling_correction =~ /[a-z]+:/) - @spelling_correction = @xapian_requests.spelling_correction - end + @highlight_words = @request_for_spelling.words_to_highlight + if !(@request_for_spelling.spelling_correction =~ /[a-z]+:/) + @spelling_correction = @request_for_spelling.spelling_correction end @track_thing = TrackThing.create_track_for_search_query(@query, @variety_postfix) @feed_autodetect = [ { :url => do_track_url(@track_thing, 'feed'), :title => @track_thing.params[:title_in_rss], :has_json => true } ] end - # Jump to a random request - def random_request - info_request = InfoRequest.random - redirect_to request_url(info_request) + # Handle requests for non-existent URLs - will be handled by ApplicationController::render_exception + def not_found + raise RouteNotFound end - def custom_css - long_cache - @locale = self.locale_from_params() - render(:layout => false, :content_type => 'text/css') + def version + respond_to do |format| + format.json { render :json => { + :alaveteli_git_commit => alaveteli_git_commit, + :alaveteli_version => ALAVETELI_VERSION, + :ruby_version => RUBY_VERSION + }} + end end - - end diff --git a/app/controllers/help_controller.rb b/app/controllers/help_controller.rb index 573abac63..9959df6d8 100644 --- a/app/controllers/help_controller.rb +++ b/app/controllers/help_controller.rb @@ -2,7 +2,7 @@ # Show information about one particular request. # # Copyright (c) 2008 UK Citizens Online Democracy. All rights reserved. -# Email: francis@mysociety.org; WWW: http://www.mysociety.org/ +# Email: hello@mysociety.org; WWW: http://www.mysociety.org/ class HelpController < ApplicationController @@ -18,7 +18,7 @@ class HelpController < ApplicationController end def contact - @contact_email = Configuration::contact_email + @contact_email = AlaveteliConfiguration::contact_email # if they clicked remove for link to request/body, remove it if params[:remove] @@ -49,14 +49,14 @@ class HelpController < ApplicationController end @contact = ContactValidator.new(params[:contact]) if @contact.valid? && !params[:remove] - ContactMailer.deliver_to_admin_message( + ContactMailer.to_admin_message( params[:contact][:name], params[:contact][:email], params[:contact][:subject], params[:contact][:message], @user, @last_request, @last_body - ) + ).deliver flash[:notice] = _("Your message has been sent. Thank you for getting in touch! We'll get back to you soon.") redirect_to frontpage_url return diff --git a/app/controllers/holiday_controller.rb b/app/controllers/holiday_controller.rb index 3101c07e3..efc20701d 100644 --- a/app/controllers/holiday_controller.rb +++ b/app/controllers/holiday_controller.rb @@ -2,7 +2,7 @@ # Calculate dates # # Copyright (c) 2008 UK Citizens Online Democracy. All rights reserved. -# Email: francis@mysociety.org; WWW: http://www.mysociety.org/ +# Email: hello@mysociety.org; WWW: http://www.mysociety.org/ class HolidayController < ApplicationController @@ -12,7 +12,7 @@ class HolidayController < ApplicationController def due_date if params[:holiday] @request_date = Date.strptime(params[:holiday]) or raise "Invalid date" - @due_date = Holiday.due_date_from(@request_date, Configuration::reply_late_after_days, Configuration::working_or_calendar_days) + @due_date = Holiday.due_date_from(@request_date, AlaveteliConfiguration::reply_late_after_days, AlaveteliConfiguration::working_or_calendar_days) @skipped = Holiday.all( :conditions => [ 'day >= ? AND day <= ?', @request_date.strftime("%F"), @due_date.strftime("%F") diff --git a/app/controllers/info_request_batch_controller.rb b/app/controllers/info_request_batch_controller.rb new file mode 100644 index 000000000..b66658757 --- /dev/null +++ b/app/controllers/info_request_batch_controller.rb @@ -0,0 +1,16 @@ +class InfoRequestBatchController < ApplicationController + + def show + @info_request_batch = InfoRequestBatch.find(params[:id]) + @per_page = 25 + @page = get_search_page_from_params + if @info_request_batch.sent_at + @info_requests = @info_request_batch.info_requests.visible.all(:offset => (@page - 1) * @per_page, + :limit => @per_page) + else + @public_bodies = @info_request_batch.public_bodies.all(:offset => (@page - 1) * @per_page, + :limit => @per_page) + end + end + +end diff --git a/app/controllers/public_body_change_requests_controller.rb b/app/controllers/public_body_change_requests_controller.rb new file mode 100644 index 000000000..4a6c5f5cb --- /dev/null +++ b/app/controllers/public_body_change_requests_controller.rb @@ -0,0 +1,28 @@ +class PublicBodyChangeRequestsController < ApplicationController + + def create + @change_request = PublicBodyChangeRequest.from_params(params[:public_body_change_request], @user) + if @change_request.save + @change_request.send_message + flash[:notice] = @change_request.thanks_notice + redirect_to frontpage_url + return + else + render :action => 'new' + end + end + + def new + @change_request = PublicBodyChangeRequest.new + if params[:body] + @change_request.public_body = PublicBody.find_by_url_name_with_historic(params[:body]) + end + if @change_request.public_body + @title = _('Ask us to update the email address for {{public_body_name}}', + :public_body_name => @change_request.public_body.name) + else + @title = _('Ask us to add an authority') + end + + end +end diff --git a/app/controllers/public_body_controller.rb b/app/controllers/public_body_controller.rb index 8a4a65820..862f4b318 100644 --- a/app/controllers/public_body_controller.rb +++ b/app/controllers/public_body_controller.rb @@ -3,9 +3,11 @@ # Show information about a public body. # # Copyright (c) 2007 UK Citizens Online Democracy. All rights reserved. -# Email: francis@mysociety.org; WWW: http://www.mysociety.org/ +# Email: hello@mysociety.org; WWW: http://www.mysociety.org/ require 'fastercsv' +require 'confidence_intervals' +require 'tempfile' class PublicBodyController < ApplicationController # XXX tidy this up with better error messages, and a more standard infrastructure for the redirect to canonical URL @@ -16,7 +18,7 @@ class PublicBodyController < ApplicationController return end @locale = self.locale_from_params() - PublicBody.with_locale(@locale) do + I18n.with_locale(@locale) do @public_body = PublicBody.find_by_url_name_with_historic(params[:url_name]) raise ActiveRecord::RecordNotFound.new("None found") if @public_body.nil? if @public_body.url_name.nil? @@ -25,22 +27,20 @@ class PublicBodyController < ApplicationController end # If found by historic name, or alternate locale name, redirect to new name if @public_body.url_name != params[:url_name] - redirect_to show_public_body_url(:url_name => @public_body.url_name) + redirect_to :url_name => @public_body.url_name return end set_last_body(@public_body) - top_url = main_url("/") + top_url = frontpage_url @searched_to_send_request = false referrer = request.env['HTTP_REFERER'] if !referrer.nil? && referrer.match(%r{^#{top_url}search/.*/bodies$}) @searched_to_send_request = true end @view = params[:view] - params[:latest_status] = @view - - query = make_query_from_params + query = InfoRequestEvent.make_query_from_params(params.merge(:latest_status => @view)) query += " requested_from:#{@public_body.url_name}" # Use search query for this so can collapse and paginate easily # XXX really should just use SQL query here rather than Xapian. @@ -71,7 +71,7 @@ class PublicBodyController < ApplicationController @public_body = PublicBody.find_by_url_name_with_historic(params[:url_name]) raise ActiveRecord::RecordNotFound.new("None found") if @public_body.nil? - PublicBody.with_locale(self.locale_from_params()) do + I18n.with_locale(self.locale_from_params()) do if params[:submitted_view_email] if verify_recaptcha flash.discard(:error) @@ -87,34 +87,45 @@ class PublicBodyController < ApplicationController def list long_cache # XXX move some of these tag SQL queries into has_tag_string.rb - @query = "%#{params[:public_body_query].nil? ? "" : params[:public_body_query]}%" + + like_query = params[:public_body_query] + like_query = "" if like_query.nil? + like_query = "%#{like_query}%" + @tag = params[:tag] - @locale = self.locale_from_params() - default_locale = I18n.default_locale.to_s - locale_condition = "(upper(public_body_translations.name) LIKE upper(?) - OR upper(public_body_translations.notes) LIKE upper (?)) - AND public_body_translations.locale = ? - AND public_bodies.id <> #{PublicBody.internal_admin_body.id}" + + @locale = self.locale_from_params + underscore_locale = @locale.gsub '-', '_' + underscore_default_locale = I18n.default_locale.to_s.gsub '-', '_' + + where_condition = "public_bodies.id <> #{PublicBody.internal_admin_body.id}" + where_parameters = [] + + first_letter = false + + base_tag_condition = " AND (SELECT count(*) FROM has_tag_string_tags" \ + " WHERE has_tag_string_tags.model_id = public_bodies.id" \ + " AND has_tag_string_tags.model = 'PublicBody'" + + # Restrict the public bodies shown according to the tag + # parameter supplied in the URL: if @tag.nil? or @tag == "all" @tag = "all" - conditions = [locale_condition, @query, @query, default_locale] elsif @tag == 'other' category_list = PublicBodyCategories::get().tags().map{|c| "'"+c+"'"}.join(",") - conditions = [locale_condition + ' AND (select count(*) from has_tag_string_tags where has_tag_string_tags.model_id = public_bodies.id - and has_tag_string_tags.model = \'PublicBody\' - and has_tag_string_tags.name in (' + category_list + ')) = 0', @query, @query, default_locale] - elsif @tag.size == 1 - @tag.upcase! - conditions = [locale_condition + ' AND public_body_translations.first_letter = ?', @query, @query, default_locale, @tag] + where_condition += base_tag_condition + " AND has_tag_string_tags.name in (#{category_list})) = 0" + elsif @tag.scan(/./mu).size == 1 + @tag = Unicode.upcase @tag + # The first letter queries have to be done on + # translations, so just indicate to add that later: + first_letter = true elsif @tag.include?(":") name, value = HasTagString::HasTagStringTag.split_tag_into_name_value(@tag) - conditions = [locale_condition + ' AND (select count(*) from has_tag_string_tags where has_tag_string_tags.model_id = public_bodies.id - and has_tag_string_tags.model = \'PublicBody\' - and has_tag_string_tags.name = ? and has_tag_string_tags.value = ?) > 0', @query, @query, default_locale, name, value] + where_condition += base_tag_condition + " AND has_tag_string_tags.name = ? AND has_tag_string_tags.value = ?) > 0" + where_parameters.concat [name, value] else - conditions = [locale_condition + ' AND (select count(*) from has_tag_string_tags where has_tag_string_tags.model_id = public_bodies.id - and has_tag_string_tags.model = \'PublicBody\' - and has_tag_string_tags.name = ?) > 0', @query, @query, default_locale, @tag] + where_condition += base_tag_condition + " AND has_tag_string_tags.name = ?) > 0" + where_parameters.concat [@tag] end if @tag == "all" @@ -129,13 +140,48 @@ class PublicBodyController < ApplicationController @description = _("in the category ‘{{category_name}}’", :category_name=>category_name) end end - PublicBody.with_locale(@locale) do - @public_bodies = PublicBody.paginate( - :order => "public_body_translations.name", :page => params[:page], :per_page => 100, - :conditions => conditions, - :joins => :translations - ) - render :template => "public_body/list" + + I18n.with_locale(@locale) do + + if AlaveteliConfiguration::public_body_list_fallback_to_default_locale + # Unfortunately, when we might fall back to the + # default locale, this is a rather complex query: + query = %Q{ + SELECT public_bodies.*, COALESCE(current_locale.name, default_locale.name) AS display_name + FROM public_bodies + LEFT OUTER JOIN public_body_translations as current_locale + ON (public_bodies.id = current_locale.public_body_id + AND current_locale.locale = ? AND #{get_public_body_list_translated_condition 'current_locale', first_letter}) + LEFT OUTER JOIN public_body_translations as default_locale + ON (public_bodies.id = default_locale.public_body_id + AND default_locale.locale = ? AND #{get_public_body_list_translated_condition 'default_locale', first_letter}) + WHERE #{where_condition} AND COALESCE(current_locale.name, default_locale.name) IS NOT NULL + ORDER BY display_name} + sql = [query, underscore_locale, like_query, like_query] + sql.push @tag if first_letter + sql += [underscore_default_locale, like_query, like_query] + sql.push @tag if first_letter + sql += where_parameters + @public_bodies = PublicBody.paginate_by_sql( + sql, + :page => params[:page], + :per_page => 100) + else + # The simpler case where we're just searching in the current locale: + where_condition = get_public_body_list_translated_condition('public_body_translations', first_letter, true) + + ' AND ' + where_condition + where_sql = [where_condition, like_query, like_query] + where_sql.push @tag if first_letter + where_sql += [underscore_locale] + where_parameters + @public_bodies = PublicBody.where(where_sql) \ + .joins(:translations) \ + .order("public_body_translations.name") \ + .paginate(:page => params[:page], :per_page => 100) + end + + respond_to do |format| + format.html { render :template => "public_body/list" } + end end end @@ -146,9 +192,146 @@ class PublicBodyController < ApplicationController end def list_all_csv - send_data(PublicBody.export_csv, :type=> 'text/csv; charset=utf-8; header=present', + # FIXME: this is just using the download directory for zip + # archives, since we know that is allowed for X-Sendfile and + # the filename can't clash with the numeric subdirectory names + # used for the zips. However, really there should be a + # generically named downloads directory that contains all + # kinds of downloadable assets. + download_directory = File.join(InfoRequest.download_zip_dir(), + 'download') + FileUtils.mkdir_p download_directory + output_leafname = 'all-authorities.csv' + output_filename = File.join download_directory, output_leafname + # Create a temporary file in the same directory, so we can + # rename it atomically to the intended filename: + tmp = Tempfile.new output_leafname, download_directory + tmp.close + # Export all the public bodies to that temporary path and make + # it readable: + PublicBody.export_csv tmp.path + FileUtils.chmod 0644, tmp.path + # Rename into place and send the file: + File.rename tmp.path, output_filename + send_file(output_filename, + :type => 'text/csv; charset=utf-8; header=present', :filename => 'all-authorities.csv', - :disposition =>'attachment', :encoding => 'utf8') + :disposition =>'attachment', + :encoding => 'utf8') + end + + + # This is a helper method to take data returned by the PublicBody + # model's statistics-generating methods, and converting them to + # simpler data structure that can be rendered by a Javascript + # graph library. (This could be a class method except that we need + # access to the URL helper public_body_path.) + def simplify_stats_for_graphs(data, + column, + percentages, + graph_properties) + # Copy the data, only taking known-to-be-safe keys: + result = Hash.new { |h, k| h[k] = [] } + result.update Hash[data.select do |key, value| + ['y_values', + 'y_max', + 'totals', + 'cis_below', + 'cis_above'].include? key + end] + + # Extract data about the public bodies for the x-axis, + # tooltips, and so on: + data['public_bodies'].each_with_index do |pb, i| + result['x_values'] << i + result['x_ticks'] << [i, pb.name] + result['tooltips'] << "#{pb.name} (#{result['totals'][i]})" + result['public_bodies'] << { + 'name' => pb.name, + 'url' => public_body_path(pb) + } + end + + # Set graph metadata properties, like the title, axis labels, etc. + graph_id = "#{column}-" + graph_id += graph_properties[:highest] ? 'highest' : 'lowest' + result.update({ + 'id' => graph_id, + 'x_axis' => _('Public Bodies'), + 'y_axis' => graph_properties[:y_axis], + 'errorbars' => percentages, + 'title' => graph_properties[:title] + }) + end + + def statistics + unless AlaveteliConfiguration::public_body_statistics_page + raise ActiveRecord::RecordNotFound.new("Page not enabled") + end + + per_graph = 10 + minimum_requests = AlaveteliConfiguration::minimum_requests_for_statistics + # Make sure minimum_requests is > 0 to avoid division-by-zero + minimum_requests = [minimum_requests, 1].max + total_column = 'info_requests_count' + + @graph_list = [] + + [[total_column, + [{ + :title => _('Public bodies with the most requests'), + :y_axis => _('Number of requests'), + :highest => true}]], + ['info_requests_successful_count', + [{ + :title => _('Public bodies with the most successful requests'), + :y_axis => _('Percentage of total requests'), + :highest => true}, + { + :title => _('Public bodies with the fewest successful requests'), + :y_axis => _('Percentage of total requests'), + :highest => false}]], + ['info_requests_overdue_count', + [{ + :title => _('Public bodies with most overdue requests'), + :y_axis => _('Percentage of requests that are overdue'), + :highest => true}]], + ['info_requests_not_held_count', + [{ + :title => _('Public bodies that most frequently replied with "Not Held"'), + :y_axis => _('Percentage of total requests'), + :highest => true}]]].each do |column, graphs_properties| + + graphs_properties.each do |graph_properties| + + percentages = (column != total_column) + highest = graph_properties[:highest] + + data = nil + if percentages + data = PublicBody.get_request_percentages(column, + per_graph, + highest, + minimum_requests) + else + data = PublicBody.get_request_totals(per_graph, + highest, + minimum_requests) + end + + if data + @graph_list.push simplify_stats_for_graphs(data, + column, + percentages, + graph_properties) + end + end + end + + respond_to do |format| + format.html { render :template => "public_body/statistics" } + format.json { render :json => @graph_list } + end end # Type ahead search @@ -159,5 +342,18 @@ class PublicBodyController < ApplicationController @xapian_requests = perform_search_typeahead(query, PublicBody) render :partial => "public_body/search_ahead" end -end + private + def get_public_body_list_translated_condition(table, first_letter=false, locale=nil) + result = "(upper(#{table}.name) LIKE upper(?)" \ + " OR upper(#{table}.notes) LIKE upper (?))" + if first_letter + result += " AND #{table}.first_letter = ?" + end + if locale + result += " AND #{table}.locale = ?" + end + result + end + +end diff --git a/app/controllers/reports_controller.rb b/app/controllers/reports_controller.rb new file mode 100644 index 000000000..a1dd53125 --- /dev/null +++ b/app/controllers/reports_controller.rb @@ -0,0 +1,31 @@ +class ReportsController < ApplicationController + def create + @info_request = InfoRequest.find_by_url_title!(params[:request_id]) + @reason = params[:reason] + @message = params[:message] + if @reason.empty? + flash[:error] = _("Please choose a reason") + render "new" + return + end + + if !authenticated_user + flash[:notice] = _("You need to be logged in to report a request for administrator attention") + elsif @info_request.attention_requested + flash[:notice] = _("This request has already been reported for administrator attention") + else + @info_request.report!(@reason, @message, @user) + flash[:notice] = _("This request has been reported for administrator attention") + end + redirect_to request_url(@info_request) + end + + def new + @info_request = InfoRequest.find_by_url_title!(params[:request_id]) + if authenticated?( + :web => _("To report this request"), + :email => _("Then you can report the request '{{title}}'", :title => @info_request.title), + :email_subject => _("Report an offensive or unsuitable request")) + end + end +end diff --git a/app/controllers/request_controller.rb b/app/controllers/request_controller.rb index dfa3a4834..a94461758 100644 --- a/app/controllers/request_controller.rb +++ b/app/controllers/request_controller.rb @@ -1,23 +1,23 @@ +# encoding: UTF-8 # app/controllers/request_controller.rb: # Show information about one particular request. # # Copyright (c) 2007 UK Citizens Online Democracy. All rights reserved. -# Email: francis@mysociety.org; WWW: http://www.mysociety.org/ +# Email: hello@mysociety.org; WWW: http://www.mysociety.org/ -require 'alaveteli_file_types' require 'zip/zip' require 'open-uri' class RequestController < ApplicationController before_filter :check_read_only, :only => [ :new, :show_response, :describe_state, :upload_response ] protect_from_forgery :only => [ :new, :show_response, :describe_state, :upload_response ] # See ActionController::RequestForgeryProtection for details - + before_filter :check_batch_requests_and_user_allowed, :only => [ :select_authorities, :new_batch ] MAX_RESULTS = 500 PER_PAGE = 25 @@custom_states_loaded = false begin - if ENV["RAILS_ENV"] != "test" + if !Rails.env.test? require 'customstates' include RequestControllerCustomStates @@custom_states_loaded = true @@ -28,7 +28,7 @@ class RequestController < ApplicationController def select_authority # Check whether we force the user to sign in right at the start, or we allow her # to start filling the request anonymously - if Configuration::force_registration_on_new_request && !authenticated?( + if AlaveteliConfiguration::force_registration_on_new_request && !authenticated?( :web => _("To send your FOI request"), :email => _("Then you'll be allowed to send FOI requests."), :email_subject => _("Confirm your email address") @@ -43,8 +43,34 @@ class RequestController < ApplicationController medium_cache end + def select_authorities + if !params[:public_body_query].nil? + @search_bodies = perform_search_typeahead(params[:public_body_query], PublicBody, 1000) + end + respond_to do |format| + format.html do + if !params[:public_body_ids].nil? + if !params[:remove_public_body_ids].nil? + body_ids = params[:public_body_ids] - params[:remove_public_body_ids] + else + body_ids = params[:public_body_ids] + end + @public_bodies = PublicBody.where({:id => body_ids}).all + end + end + format.json do + if @search_bodies + render :json => @search_bodies.results.map{ |result| {:name => result[:model].name, + :id => result[:model].id } } + else + render :json => [] + end + end + end + end + def show - if !Configuration::varnish_host.blank? + if !AlaveteliConfiguration::varnish_host.blank? # If varnish is set up to accept PURGEs, then cache for a # long time long_cache @@ -52,7 +78,7 @@ class RequestController < ApplicationController medium_cache end @locale = self.locale_from_params() - PublicBody.with_locale(@locale) do + I18n.with_locale(@locale) do # Look up by old style numeric identifiers if params[:url_title].match(/^[0-9]+$/) @@ -63,27 +89,24 @@ class RequestController < ApplicationController # Look up by new style text names @info_request = InfoRequest.find_by_url_title!(params[:url_title]) - set_last_request(@info_request) # Test for whole request being hidden if !@info_request.user_can_view?(authenticated_user) - render :template => 'request/hidden', :status => 410 # gone - return + return render_hidden end - # Other parameters - @info_request_events = @info_request.info_request_events - @status = @info_request.calculate_status - @collapse_quotes = params[:unfold] ? false : true + set_last_request(@info_request) + # assign variables from request parameters + @collapse_quotes = params[:unfold] ? false : true # Don't allow status update on external requests, otherwise accept param if @info_request.is_external? @update_status = false else @update_status = params[:update_status] ? true : false end - @old_unclassified = @info_request.is_old_unclassified? && !authenticated_user.nil? - @is_owning_user = @info_request.is_owning_user?(authenticated_user) + + assign_variables_for_show_template(@info_request) if @update_status return if !@is_owning_user && !authenticated_as_user?(@info_request.user, @@ -93,27 +116,15 @@ class RequestController < ApplicationController ) end - - @last_info_request_event_id = @info_request.last_event_id_needing_description - @new_responses_count = @info_request.events_needing_description.select {|i| i.event_type == 'response'}.size - # Sidebar stuff - # ... requests that have similar imporant terms - begin - limit = 10 - @xapian_similar = ::ActsAsXapian::Similar.new([InfoRequestEvent], @info_request.info_request_events, - :limit => limit, :collapse_by_prefix => 'request_collapse') - @xapian_similar_more = (@xapian_similar.matches_estimated > limit) - rescue - @xapian_similar = nil - end + @sidebar = true + @similar_cache_key = cache_key_for_similar_requests(@info_request, @locale) # Track corresponding to this page @track_thing = TrackThing.create_track_for_request(@info_request) @feed_autodetect = [ { :url => do_track_url(@track_thing, 'feed'), :title => @track_thing.params[:title_in_rss], :has_json => true } ] - # For send followup link at bottom - @last_response = @info_request.get_last_response + respond_to do |format| format.html { @has_json = true; render :template => 'request/show'} format.json { render :json => @info_request.json_for_api(true) } @@ -126,8 +137,7 @@ class RequestController < ApplicationController long_cache @info_request = InfoRequest.find_by_url_title!(params[:url_title]) if !@info_request.user_can_view?(authenticated_user) - render :template => 'request/hidden', :status => 410 # gone - return + return render_hidden end @columns = ['id', 'event_type', 'created_at', 'described_state', 'last_described_at', 'calculated_state' ] end @@ -146,27 +156,23 @@ class RequestController < ApplicationController raise ActiveRecord::RecordNotFound.new("Request not found") if @info_request.nil? if !@info_request.user_can_view?(authenticated_user) - render :template => 'request/hidden', :status => 410 # gone - return + return render_hidden end - @xapian_object = ::ActsAsXapian::Similar.new([InfoRequestEvent], @info_request.info_request_events, + @xapian_object = ActsAsXapian::Similar.new([InfoRequestEvent], @info_request.info_request_events, :offset => (@page - 1) * @per_page, :limit => @per_page, :collapse_by_prefix => 'request_collapse') @matches_estimated = @xapian_object.matches_estimated @show_no_more_than = (@matches_estimated > MAX_RESULTS) ? MAX_RESULTS : @matches_estimated - - if (@page > 1) - @page_desc = " (page " + @page.to_s + ")" - else - @page_desc = "" - end end def list medium_cache @view = params[:view] + @locale = self.locale_from_params() @page = get_search_page_from_params if !@page # used in cache case, as perform_search sets @page as side effect + @per_page = PER_PAGE + @max_results = MAX_RESULTS if @view == "recent" - return redirect_to request_list_all_path(:action => "list", :view => "all", :page => @page), :status => :moved_permanently + return redirect_to request_list_all_url(:action => "list", :view => "all", :page => @page), :status => :moved_permanently end # Later pages are very expensive to load @@ -174,17 +180,11 @@ class RequestController < ApplicationController raise ActiveRecord::RecordNotFound.new("Sorry. No pages after #{MAX_RESULTS / PER_PAGE}.") end - params[:latest_status] = @view - query = make_query_from_params + @filters = params.merge(:latest_status => @view) @title = _("View and search requests") - sortby = "newest" - xapian_object = perform_search([InfoRequestEvent], query, sortby, 'request_collapse') - @list_results = xapian_object.results.map { |r| r[:model] } - @matches_estimated = xapian_object.matches_estimated - @show_no_more_than = (@matches_estimated > MAX_RESULTS) ? MAX_RESULTS : @matches_estimated @title = @title + " (page " + @page.to_s + ")" if (@page > 1) - @track_thing = TrackThing.create_track_for_search_query(query) + @track_thing = TrackThing.create_track_for_search_query(InfoRequestEvent.make_query_from_params(@filters)) @feed_autodetect = [ { :url => do_track_url(@track_thing, 'feed'), :title => @track_thing.params[:title_in_rss], :has_json => true } ] # Don't let robots go more than 20 pages in @@ -193,6 +193,69 @@ class RequestController < ApplicationController end end + def new_batch + if params[:public_body_ids].blank? + redirect_to select_authorities_path and return + end + + # TODO: Decide if we make batch requesters describe their undescribed requests + # before being able to make a new batch request + + if !authenticated_user.can_file_requests? + @details = authenticated_user.can_fail_html + render :template => 'user/banned' and return + end + + @batch = true + + I18n.with_locale(@locale) do + @public_bodies = PublicBody.where({:id => params[:public_body_ids]}). + includes(:translations). + order('public_body_translations.name').all + end + if params[:submitted_new_request].nil? || params[:reedit] + return render_new_compose(batch=true) + end + + # Check for double submission of batch + @existing_batch = InfoRequestBatch.find_existing(authenticated_user, + params[:info_request][:title], + params[:outgoing_message][:body], + params[:public_body_ids]) + + @info_request = InfoRequest.create_from_attributes(params[:info_request], + params[:outgoing_message], + authenticated_user) + @outgoing_message = @info_request.outgoing_messages.first + @info_request.is_batch_request_template = true + if !@existing_batch.nil? || !@info_request.valid? + # We don't want the error "Outgoing messages is invalid", as in this + # case the list of errors will also contain a more specific error + # describing the reason it is invalid. + @info_request.errors.delete(:outgoing_messages) + render :action => 'new' + return + end + + # Show preview page, if it is a preview + if params[:preview].to_i == 1 + return render_new_preview + end + + @info_request_batch = InfoRequestBatch.create!(:title => params[:info_request][:title], + :body => params[:outgoing_message][:body], + :public_bodies => @public_bodies, + :user => authenticated_user) + flash[:notice] = _("<p>Your {{law_used_full}} requests will be <strong>sent</strong> shortly!</p> + <p><strong>We will email you</strong> when they have been sent. + We will also email you when there is a response to any of them, or after {{late_number_of_days}} working days if the authorities still haven't + replied by then.</p> + <p>If you write about these requests (for example in a forum or a blog) please link to this page.</p>", + :law_used_full=>@info_request.law_used_full, + :late_number_of_days => AlaveteliConfiguration::reply_late_after_days) + redirect_to info_request_batch_path(@info_request_batch) + end + # Page new form posts to def new # All new requests are of normal_sort @@ -237,68 +300,19 @@ class RequestController < ApplicationController render :template => 'user/rate_limited' return end - - params[:info_request] = { } if !params[:info_request] - - # Read parameters in - first the public body (by URL name or id) - if params[:url_name] - if params[:url_name].match(/^[0-9]+$/) - params[:info_request][:public_body_id] = params[:url_name] - else - public_body = PublicBody.find_by_url_name_with_historic(params[:url_name]) - raise ActiveRecord::RecordNotFound.new("None found") if public_body.nil? # XXX proper 404 - params[:info_request][:public_body_id] = public_body.id - end - elsif params[:public_body_id] - params[:info_request][:public_body_id] = params[:public_body_id] - end - if !params[:info_request][:public_body_id] - # compulsory to have a body by here, or go to front page which is start of process - redirect_to frontpage_url - return - end - - # ... next any tags or other things - params[:info_request][:title] = params[:title] if params[:title] - params[:info_request][:tag_string] = params[:tags] if params[:tags] - - @info_request = InfoRequest.new(params[:info_request]) - params[:info_request_id] = @info_request.id - params[:outgoing_message] = {} if !params[:outgoing_message] - params[:outgoing_message][:body] = params[:body] if params[:body] - params[:outgoing_message][:default_letter] = params[:default_letter] if params[:default_letter] - params[:outgoing_message][:info_request] = @info_request - @outgoing_message = OutgoingMessage.new(params[:outgoing_message]) - @outgoing_message.set_signature_name(@user.name) if !@user.nil? - - if @info_request.public_body.is_requestable? - render :action => 'new' - else - if @info_request.public_body.not_requestable_reason == 'bad_contact' - render :action => 'new_bad_contact' - else - # if not requestable because defunct or not_apply, redirect to main page - # (which doesn't link to the /new/ URL) - redirect_to public_body_url(@info_request.public_body) - end - end - return + return render_new_compose(batch=false) end # See if the exact same request has already been submitted # XXX this check should theoretically be a validation rule in the # model, except we really want to pass @existing_request to the view so # it can link to it. - @existing_request = InfoRequest.find_by_existing_request(params[:info_request][:title], params[:info_request][:public_body_id], params[:outgoing_message][:body]) + @existing_request = InfoRequest.find_existing(params[:info_request][:title], params[:info_request][:public_body_id], params[:outgoing_message][:body]) # Create both FOI request and the first request message - @info_request = InfoRequest.new(params[:info_request]) - @outgoing_message = OutgoingMessage.new(params[:outgoing_message].merge({ - :status => 'ready', - :message_type => 'initial_request' - })) - @info_request.outgoing_messages << @outgoing_message - @outgoing_message.info_request = @info_request + @info_request = InfoRequest.create_from_attributes(params[:info_request], + params[:outgoing_message]) + @outgoing_message = @info_request.outgoing_messages.first # Maybe we lost the address while they're writing it if !@info_request.public_body.is_requestable? @@ -311,7 +325,7 @@ class RequestController < ApplicationController # We don't want the error "Outgoing messages is invalid", as in this # case the list of errors will also contain a more specific error # describing the reason it is invalid. - @info_request.errors.delete("outgoing_messages") + @info_request.errors.delete(:outgoing_messages) render :action => 'new' return @@ -319,24 +333,7 @@ class RequestController < ApplicationController # Show preview page, if it is a preview if params[:preview].to_i == 1 - message = "" - if @outgoing_message.contains_email? - if @user.nil? - message += (_("<p>You do not need to include your email in the request in order to get a reply, as we will ask for it on the next screen (<a href=\"%s\">details</a>).</p>") % [help_privacy_path+"#email_address"]).html_safe; - else - message += (_("<p>You do not need to include your email in the request in order to get a reply (<a href=\"%s\">details</a>).</p>") % [help_privacy_path+"#email_address"]).html_safe; - end - message += _("<p>We recommend that you edit your request and remove the email address. - If you leave it, the email address will be sent to the authority, but will not be displayed on the site.</p>") - end - if @outgoing_message.contains_postcode? - message += _("<p>Your request contains a <strong>postcode</strong>. Unless it directly relates to the subject of your request, please remove any address as it will <strong>appear publicly on the Internet</strong>.</p>"); - end - if not message.empty? - flash.now[:error] = message.html_safe - end - render :action => 'preview' - return + return render_new_preview end if user_exceeded_limit @@ -345,9 +342,9 @@ class RequestController < ApplicationController end if !authenticated?( - :web => _("To send your FOI request"), + :web => _("To send your FOI request").to_str, :email => _("Then your FOI request to {{public_body_name}} will be sent.",:public_body_name=>@info_request.public_body.name), - :email_subject => _("Confirm your FOI request to ") + @info_request.public_body.name + :email_subject => _("Confirm your FOI request to {{public_body_name}}",:public_body_name=>@info_request.public_body.name) ) # do nothing - as "authenticated?" has done the redirect to signin page for us return @@ -369,99 +366,94 @@ class RequestController < ApplicationController replied by then.</p> <p>If you write about this request (for example in a forum or a blog) please link to this page, and add an annotation below telling people about your writing.</p>",:law_used_full=>@info_request.law_used_full, - :late_number_of_days => Configuration::reply_late_after_days) + :late_number_of_days => AlaveteliConfiguration::reply_late_after_days) redirect_to show_new_request_path(:url_title => @info_request.url_title) end # Submitted to the describing state of messages form def describe_state - @info_request = InfoRequest.find(params[:id].to_i) - set_last_request(@info_request) - - # If this isn't a form submit, go to the request page - if params[:submitted_describe_state].nil? - redirect_to request_url(@info_request) - return - end + info_request = InfoRequest.find(params[:id].to_i) + set_last_request(info_request) # If this is an external request, go to the request page - we don't allow # state change from the front end interface. - if @info_request.is_external? - redirect_to request_url(@info_request) + if info_request.is_external? + redirect_to request_url(info_request) return end - @is_owning_user = @info_request.is_owning_user?(authenticated_user) - @last_info_request_event_id = @info_request.last_event_id_needing_description - @old_unclassified = @info_request.is_old_unclassified? && !authenticated_user.nil? - - # Check authenticated, and parameters set. We check is_owning_user - # to get admin overrides (see is_owning_user? above) - if !@old_unclassified && !@is_owning_user && !authenticated_as_user?(@info_request.user, + # Check authenticated, and parameters set. + unless Ability::can_update_request_state?(authenticated_user, info_request) + authenticated_as_user?(info_request.user, :web => _("To classify the response to this FOI request"), - :email => _("Then you can classify the FOI response you have got from ") + @info_request.public_body.name + ".", - :email_subject => _("Classify an FOI response from ") + @info_request.public_body.name - ) + :email => _("Then you can classify the FOI response you have got from ") + info_request.public_body.name + ".", + :email_subject => _("Classify an FOI response from ") + info_request.public_body.name) # do nothing - as "authenticated?" has done the redirect to signin page for us return end if !params[:incoming_message] flash[:error] = _("Please choose whether or not you got some of the information that you wanted.") - redirect_to request_url(@info_request) + redirect_to request_url(info_request) return end - if params[:last_info_request_event_id].to_i != @last_info_request_event_id + if params[:last_info_request_event_id].to_i != info_request.last_event_id_needing_description flash[:error] = _("The request has been updated since you originally loaded this page. Please check for any new incoming messages below, and try again.") - redirect_to request_url(@info_request) + redirect_to request_url(info_request) + return + end + + described_state = params[:incoming_message][:described_state] + message = params[:incoming_message][:message] + # For requires_admin and error_message states we ask for an extra message to send to + # the administrators. + # If this message hasn't been included then ask for it + if ["error_message", "requires_admin"].include?(described_state) && message.nil? + redirect_to describe_state_message_url(:url_title => info_request.url_title, :described_state => described_state) return end # Make the state change - old_described_state = @info_request.described_state - @info_request.set_described_state(params[:incoming_message][:described_state]) + event = info_request.log_event("status_update", + { :user_id => authenticated_user.id, + :old_described_state => info_request.described_state, + :described_state => described_state, + }) + + info_request.set_described_state(described_state, authenticated_user, message) # If you're not the *actual* requester. e.g. you are playing the # classification game, or you're doing this just because you are an # admin user (not because you also own the request). - if !@info_request.is_actual_owning_user?(authenticated_user) - # Log the status change by someone other than the requester - event = @info_request.log_event("status_update", - { :user_id => authenticated_user.id, - :old_described_state => old_described_state, - :described_state => @info_request.described_state, - }) + if !info_request.is_actual_owning_user?(authenticated_user) # Create a classification event for league tables RequestClassification.create!(:user_id => authenticated_user.id, :info_request_event_id => event.id) # Don't give advice on what to do next, as it isn't their request - RequestMailer.deliver_old_unclassified_updated(@info_request) if !@info_request.is_external? if session[:request_game] - flash[:notice] = _('Thank you for updating the status of the request \'<a href="{{url}}">{{info_request_title}}</a>\'. There are some more requests below for you to classify.',:info_request_title=>CGI.escapeHTML(@info_request.title), :url=>CGI.escapeHTML(request_url(@info_request))) - redirect_to play_url + flash[:notice] = _('Thank you for updating the status of the request \'<a href="{{url}}">{{info_request_title}}</a>\'. There are some more requests below for you to classify.',:info_request_title=>CGI.escapeHTML(info_request.title), :url=>CGI.escapeHTML(request_path(info_request))) + redirect_to categorise_play_url else flash[:notice] = _('Thank you for updating this request!') - redirect_to request_url(@info_request) + redirect_to request_url(info_request) end return end - calculated_status = @info_request.calculate_status + calculated_status = info_request.calculate_status # Display advice for requester on what to do next, as appropriate - if calculated_status == 'waiting_response' - flash[:notice] = _("<p>Thank you! Hopefully your wait isn't too long.</p> <p>By law, you should get a response promptly, and normally before the end of <strong> -{{date_response_required_by}}</strong>.</p>",:date_response_required_by=>simple_date(@info_request.date_response_required_by)) - redirect_to request_url(@info_request) - elsif calculated_status == 'waiting_response_overdue' - flash[:notice] = _("<p>Thank you! Hope you don't have to wait much longer.</p> <p>By law, you should have got a response promptly, and normally before the end of <strong>{{date_response_required_by}}</strong>.</p>",:date_response_required_by=>simple_date(@info_request.date_response_required_by)) - redirect_to request_url(@info_request) - elsif calculated_status == 'waiting_response_very_overdue' - flash[:notice] = _("<p>Thank you! Your request is long overdue, by more than {{very_late_number_of_days}} working days. Most requests should be answered within {{late_number_of_days}} working days. You might like to complain about this, see below.</p>", :very_late_number_of_days => Configuration::reply_very_late_after_days, :late_number_of_days => Configuration::reply_late_after_days) - redirect_to unhappy_url(@info_request) - elsif calculated_status == 'not_held' - flash[:notice] = _("<p>Thank you! Here are some ideas on what to do next:</p> + flash[:notice] = case info_request.calculate_status + when 'waiting_response' + _("<p>Thank you! Hopefully your wait isn't too long.</p> <p>By law, you should get a response promptly, and normally before the end of <strong> +{{date_response_required_by}}</strong>.</p>",:date_response_required_by=>simple_date(info_request.date_response_required_by)) + when 'waiting_response_overdue' + _("<p>Thank you! Hope you don't have to wait much longer.</p> <p>By law, you should have got a response promptly, and normally before the end of <strong>{{date_response_required_by}}</strong>.</p>",:date_response_required_by=>simple_date(info_request.date_response_required_by)) + when 'waiting_response_very_overdue' + _("<p>Thank you! Your request is long overdue, by more than {{very_late_number_of_days}} working days. Most requests should be answered within {{late_number_of_days}} working days. You might like to complain about this, see below.</p>", :very_late_number_of_days => AlaveteliConfiguration::reply_very_late_after_days, :late_number_of_days => AlaveteliConfiguration::reply_late_after_days) + when 'not_held' + _("<p>Thank you! Here are some ideas on what to do next:</p> <ul> <li>To send your request to another authority, first copy the text of your request below, then <a href=\"{{find_authority_url}}\">find the other authority</a>.</li> <li>If you would like to contest the authority's claim that they do not hold the information, here is @@ -472,44 +464,70 @@ class RequestController < ApplicationController </li> </ul>", :find_authority_url => "/new", - :complain_url => CGI.escapeHTML(unhappy_url(@info_request)), - :other_means_url => CGI.escapeHTML(unhappy_url(@info_request)) + "#other_means") - redirect_to request_url(@info_request) - elsif calculated_status == 'rejected' - flash[:notice] = _("Oh no! Sorry to hear that your request was refused. Here is what to do now.") - redirect_to unhappy_url(@info_request) - elsif calculated_status == 'successful' - flash[:notice] = _("<p>We're glad you got all the information that you wanted. If you write about or make use of the information, please come back and add an annotation below saying what you did.</p><p>If you found {{site_name}} useful, <a href=\"{{donation_url}}\">make a donation</a> to the charity which runs it.</p>", :site_name=>site_name, :donation_url => "http://www.mysociety.org/donate/") - redirect_to request_url(@info_request) - elsif calculated_status == 'partially_successful' - flash[:notice] = _("<p>We're glad you got some of the information that you wanted. If you found {{site_name}} useful, <a href=\"{{donation_url}}\">make a donation</a> to the charity which runs it.</p><p>If you want to try and get the rest of the information, here's what to do now.</p>", :site_name=>site_name, :donation_url=>"http://www.mysociety.org/donate/") - redirect_to unhappy_url(@info_request) - elsif calculated_status == 'waiting_clarification' - flash[:notice] = _("Please write your follow up message containing the necessary clarifications below.") - redirect_to respond_to_last_url(@info_request) - elsif calculated_status == 'gone_postal' - redirect_to respond_to_last_url(@info_request) + "?gone_postal=1" - elsif calculated_status == 'internal_review' - flash[:notice] = _("<p>Thank you! Hopefully your wait isn't too long.</p><p>You should get a response within {{late_number_of_days}} days, or be told if it will take longer (<a href=\"{{review_url}}\">details</a>).</p>",:late_number_of_days => Configuration.reply_late_after_days, :review_url => unhappy_url(@info_request) + "#internal_review") - redirect_to request_url(@info_request) - elsif calculated_status == 'error_message' - flash[:notice] = _("<p>Thank you! We'll look into what happened and try and fix it up.</p><p>If the error was a delivery failure, and you can find an up to date FOI email address for the authority, please tell us using the form below.</p>") - redirect_to help_general_url(:action => 'contact') - elsif calculated_status == 'requires_admin' - flash[:notice] = _("Please use the form below to tell us more.") - redirect_to help_general_url(:action => 'contact') - elsif calculated_status == 'user_withdrawn' - flash[:notice] = _("If you have not done so already, please write a message below telling the authority that you have withdrawn your request. Otherwise they will not know it has been withdrawn.") - redirect_to respond_to_last_url(@info_request) + :complain_url => CGI.escapeHTML(unhappy_url(info_request)), + :other_means_url => CGI.escapeHTML(unhappy_url(info_request)) + "#other_means") + when 'rejected' + _("Oh no! Sorry to hear that your request was refused. Here is what to do now.") + when 'successful' + if AlaveteliConfiguration::donation_url.blank? + _("<p>We're glad you got all the information that you wanted. If you write about or make use of the information, please come back and add an annotation below saying what you did.</p>") + else + _("<p>We're glad you got all the information that you wanted. If you write about or make use of the information, please come back and add an annotation below saying what you did.</p><p>If you found {{site_name}} useful, <a href=\"{{donation_url}}\">make a donation</a> to the charity which runs it.</p>", + :site_name => site_name, :donation_url => AlaveteliConfiguration::donation_url) + end + when 'partially_successful' + if AlaveteliConfiguration::donation_url.blank? + _("<p>We're glad you got some of the information that you wanted.</p><p>If you want to try and get the rest of the information, here's what to do now.</p>") + else + _("<p>We're glad you got some of the information that you wanted. If you found {{site_name}} useful, <a href=\"{{donation_url}}\">make a donation</a> to the charity which runs it.</p><p>If you want to try and get the rest of the information, here's what to do now.</p>", + :site_name => site_name, :donation_url => AlaveteliConfiguration::donation_url) + end + when 'waiting_clarification' + _("Please write your follow up message containing the necessary clarifications below.") + when 'gone_postal' + nil + when 'internal_review' + _("<p>Thank you! Hopefully your wait isn't too long.</p><p>You should get a response within {{late_number_of_days}} days, or be told if it will take longer (<a href=\"{{review_url}}\">details</a>).</p>",:late_number_of_days => AlaveteliConfiguration.reply_late_after_days, :review_url => unhappy_url(info_request) + "#internal_review") + when 'error_message', 'requires_admin' + _("Thank you! We'll look into what happened and try and fix it up.") + when 'user_withdrawn' + _("If you have not done so already, please write a message below telling the authority that you have withdrawn your request. Otherwise they will not know it has been withdrawn.") + end + + case info_request.calculate_status + when 'waiting_response', 'waiting_response_overdue', 'not_held', 'successful', + 'internal_review', 'error_message', 'requires_admin' + redirect_to request_url(info_request) + when 'waiting_response_very_overdue', 'rejected', 'partially_successful' + redirect_to unhappy_url(info_request) + when 'waiting_clarification', 'user_withdrawn' + redirect_to respond_to_last_url(info_request) + when 'gone_postal' + redirect_to respond_to_last_url(info_request) + "?gone_postal=1" else if @@custom_states_loaded - return self.theme_describe_state(@info_request) + return self.theme_describe_state(info_request) else - raise "unknown calculate_status " + calculated_status + raise "unknown calculate_status #{info_request.calculate_status}" end end end + # Collect a message to include with the change of state + def describe_state_message + @info_request = InfoRequest.find_by_url_title!(params[:url_title]) + @described_state = params[:described_state] + @last_info_request_event_id = @info_request.last_event_id_needing_description + @title = case @described_state + when "error_message" + _("I've received an error message") + when "requires_admin" + _("This request requires administrator attention") + else + raise "Unsupported state" + end + end + # Used for links from polymorphic URLs e.g. in Atom feeds - just redirect to # proper URL for the message the event refers to def show_request_event @@ -561,10 +579,7 @@ class RequestController < ApplicationController end - params_outgoing_message = params[:outgoing_message] - if params_outgoing_message.nil? - params_outgoing_message = {} - end + params_outgoing_message = params[:outgoing_message] ? params[:outgoing_message].clone : {} params_outgoing_message.merge!({ :status => 'ready', :message_type => 'followup', @@ -582,13 +597,12 @@ class RequestController < ApplicationController @outgoing_message.set_signature_name(@user.name) if !@user.nil? if (not @incoming_message.nil?) and @info_request != @incoming_message.info_request - raise sprintf("Incoming message %d does not belong to request %d", @incoming_message.info_request_id, @info_request.id) + raise ActiveRecord::RecordNotFound.new("Incoming message #{@incoming_message.id} does not belong to request #{@info_request.id}") end # Test for hidden requests if !authenticated_user.nil? && !@info_request.user_can_view?(authenticated_user) - render :template => 'request/hidden', :status => 410 # gone - return + return render_hidden end # Check address is good @@ -625,7 +639,7 @@ class RequestController < ApplicationController if !params[:submitted_followup].nil? && !params[:reedit] if @info_request.allow_new_responses_from == 'nobody' - flash[:error] = (_('Your follow up has not been sent because this request has been stopped to prevent spam. Please <a href="%s">contact us</a> if you really want to send a follow up message.') % [help_contact_path]).html_safe + flash[:error] = _('Your follow up has not been sent because this request has been stopped to prevent spam. Please <a href="{{url}}">contact us</a> if you really want to send a follow up message.', :url => help_contact_path.html_safe) else if @info_request.find_existing_outgoing_message(params[:outgoing_message][:body]) flash[:error] = _('You previously submitted that exact follow up message for this request.') @@ -671,34 +685,19 @@ class RequestController < ApplicationController raise ActiveRecord::RecordNotFound.new("Message not found") if incoming_message.nil? if !incoming_message.info_request.user_can_view?(authenticated_user) @info_request = incoming_message.info_request # used by view - render :template => 'request/hidden', :status => 410 # gone + return render_hidden + end + if !incoming_message.user_can_view?(authenticated_user) + @incoming_message = incoming_message # used by view + return render_hidden('request/hidden_correspondence') end # Is this a completely public request that we can cache attachments for # to be served up without authentication? - if incoming_message.info_request.all_can_view? + if incoming_message.info_request.all_can_view? && incoming_message.all_can_view? @files_can_be_cached = true end end - def report_request - info_request = InfoRequest.find_by_url_title!(params[:url_title]) - return if !authenticated?( - :web => _("To report this FOI request"), - :email => _("Then you can report the request '{{title}}'", :title => info_request.title), - :email_subject => _("Report an offensive or unsuitable request") - ) - - if !info_request.attention_requested - info_request.set_described_state('attention_requested', @user) - info_request.attention_requested = true # tells us if attention has ever been requested - info_request.save! - flash[:notice] = _("This request has been reported for administrator attention") - else - flash[:notice] = _("This request has already been reported for administrator attention") - end - redirect_to request_url(info_request) - end - # special caching code so mime types are handled right around_filter :cache_attachments, :only => [ :get_attachment, :get_attachment_as_html ] def cache_attachments @@ -709,16 +708,19 @@ class RequestController < ApplicationController key_path = foi_fragment_cache_path(key) if foi_fragment_cache_exists?(key_path) logger.info("Reading cache for #{key_path}") - raise PermissionDenied.new("Directory listing not allowed") if File.directory?(key_path) - cached = foi_fragment_cache_read(key_path) - response.content_type = AlaveteliFileTypes.filename_to_mimetype(params[:file_name].join("/")) || 'application/octet-stream' - render_for_text(cached) + + if File.directory?(key_path) + render :text => "Directory listing not allowed", :status => 403 + else + render :text => foi_fragment_cache_read(key_path), + :content_type => (AlaveteliFileTypes.filename_to_mimetype(params[:file_name]) || 'application/octet-stream') + end return end yield - if params[:skip_cache].nil? + if params[:skip_cache].nil? && response.status == 200 # write it to the fileystem ourselves, so is just a plain file. (The # various fragment cache functions using Ruby Marshall to write the file # which adds a header, so isnt compatible with images that have been @@ -733,13 +735,14 @@ class RequestController < ApplicationController def get_attachment get_attachment_internal(false) + return unless @attachment # Prevent spam to magic request address. Note that the binary # subsitution method used depends on the content type @incoming_message.binary_mask_stuff!(@attachment.body, @attachment.content_type) # we don't use @attachment.content_type here, as we want same mime type when cached in cache_attachments above - response.content_type = AlaveteliFileTypes.filename_to_mimetype(params[:file_name].join("/")) || 'application/octet-stream' + response.content_type = AlaveteliFileTypes.filename_to_mimetype(params[:file_name]) || 'application/octet-stream' render :text => @attachment.body end @@ -752,6 +755,7 @@ class RequestController < ApplicationController raise ActiveRecord::RecordNotFound.new("Attachment HTML not found.") end get_attachment_internal(true) + return unless @attachment # images made during conversion (e.g. images in PDF files) are put in the cache directory, so # the same cache code in cache_attachments above will display them. @@ -789,7 +793,7 @@ class RequestController < ApplicationController raise ActiveRecord::RecordNotFound.new(message) end @part_number = params[:part].to_i - @filename = params[:file_name].join("/") + @filename = params[:file_name] if html_conversion @original_filename = @filename.gsub(/\.html$/, "") else @@ -798,8 +802,11 @@ class RequestController < ApplicationController # check permissions raise "internal error, pre-auth filter should have caught this" if !@info_request.user_can_view?(authenticated_user) - @attachment = IncomingMessage.get_attachment_by_url_part_number(@incoming_message.get_attachments_for_display, @part_number) - raise ActiveRecord::RecordNotFound.new("attachment not found part number " + @part_number.to_s + " incoming_message " + @incoming_message.id.to_s) if @attachment.nil? + @attachment = IncomingMessage.get_attachment_by_url_part_number_and_filename(@incoming_message.get_attachments_for_display, @part_number, @original_filename) + # If we can't find the right attachment, redirect to the incoming message: + unless @attachment + return redirect_to incoming_message_url(@incoming_message), :status => 303 + end # check filename in URL matches that in database (use a censor rule if you want to change a filename) raise ActiveRecord::RecordNotFound.new("please use same filename as original file has, display: '" + @attachment.display_filename + "' old_display: '" + @attachment.old_display_filename + "' original: '" + @original_filename + "'") if @attachment.display_filename != @original_filename && @attachment.old_display_filename != @original_filename @@ -812,7 +819,7 @@ class RequestController < ApplicationController # FOI officers can upload a response def upload_response @locale = self.locale_from_params() - PublicBody.with_locale(@locale) do + I18n.with_locale(@locale) do @info_request = InfoRequest.find_by_url_title!(params[:url_title]) @reason_params = { @@ -850,7 +857,8 @@ class RequestController < ApplicationController return end - mail = RequestMailer.create_fake_response(@info_request, @user, body, file_name, file_content) + mail = RequestMailer.fake_response(@info_request, @user, body, file_name, file_content) + @info_request.receive(mail, mail.encoded, true) flash[:notice] = _("Thank you for responding to this FOI request! Your response has been published below, and a link to your response has been emailed to ") + CGI.escapeHTML(@info_request.user.name) + "." redirect_to request_url(@info_request) @@ -864,18 +872,13 @@ class RequestController < ApplicationController # by making the last work a wildcard, which is quite the same query = params[:q] @xapian_requests = perform_search_typeahead(query, InfoRequestEvent) - render :partial => "request/search_ahead.rhtml" + render :partial => "request/search_ahead" end def download_entire_request @locale = self.locale_from_params() - PublicBody.with_locale(@locale) do + I18n.with_locale(@locale) do @info_request = InfoRequest.find_by_url_title!(params[:url_title]) - # Test for whole request being hidden or requester-only - if !@info_request.all_can_view? - render :template => 'request/hidden', :status => 410 # gone - return - end if authenticated?( :web => _("To download the zip file"), :email => _("Then you can download a zip file of {{info_request_title}}.", @@ -883,56 +886,195 @@ class RequestController < ApplicationController :email_subject => _("Log in to download a zip file of {{info_request_title}}", :info_request_title=>@info_request.title) ) - updated = Digest::SHA1.hexdigest(@info_request.get_last_event.created_at.to_i.to_s + @info_request.updated_at.to_i.to_s) - @url_path = File.join("/download", - request_dirs(@info_request), - updated, - "#{params[:url_title]}.zip") - file_path = File.expand_path(File.join(download_zip_dir(), @url_path)) - if !File.exists?(file_path) - FileUtils.mkdir_p(File.dirname(file_path)) - Zip::ZipFile.open(file_path, Zip::ZipFile::CREATE) { |zipfile| - convert_command = Configuration::html_to_pdf_command - done = false - if !convert_command.blank? && File.exists?(convert_command) - url = "http://#{Configuration::domain}#{request_url(@info_request)}?print_stylesheet=1" - tempfile = Tempfile.new('foihtml2pdf') - output = AlaveteliExternalCommand.run(convert_command, url, tempfile.path) - if !output.nil? - zipfile.get_output_stream("correspondence.pdf") { |f| - f.puts(File.open(tempfile.path).read) - } - done = true - else - logger.error("Could not convert info request #{@info_request.id} to PDF with command '#{convert_command} #{url} #{tempfile.path}'") - end - tempfile.close - else - logger.warn("No HTML -> PDF converter found at #{convert_command}") - end - if !done - @info_request_events = @info_request.info_request_events - template = File.read(File.join(File.dirname(__FILE__), "..", "views", "request", "simple_correspondence.rhtml")) - output = ERB.new(template).result(binding) - zipfile.get_output_stream("correspondence.txt") { |f| - f.puts(output) - } - end - for message in @info_request.incoming_messages - attachments = message.get_attachments_for_display - for attachment in attachments - filename = "#{attachment.url_part_number}_#{attachment.display_filename}" - zipfile.get_output_stream(filename) { |f| - f.puts(attachment.body) - } - end - end - } - File.chmod(0644, file_path) + # Test for whole request being hidden or requester-only + if !@info_request.user_can_view?(@user) + return render_hidden + end + cache_file_path = @info_request.make_zip_cache_path(@user) + if !File.exists?(cache_file_path) + FileUtils.mkdir_p(File.dirname(cache_file_path)) + make_request_zip(@info_request, cache_file_path) + File.chmod(0644, cache_file_path) + end + send_file(cache_file_path, :filename => "#{@info_request.url_title}.zip") + end + end + end + + private + + def render_hidden(template='request/hidden') + respond_to do |format| + response_code = 403 # forbidden + format.html{ render :template => template, :status => response_code } + format.any{ render :nothing => true, :status => response_code } + end + false + end + + def assign_variables_for_show_template(info_request) + @info_request = info_request + @info_request_events = info_request.info_request_events + @status = info_request.calculate_status + @old_unclassified = info_request.is_old_unclassified? && !authenticated_user.nil? + @is_owning_user = info_request.is_owning_user?(authenticated_user) + @last_info_request_event_id = info_request.last_event_id_needing_description + @new_responses_count = info_request.events_needing_description.select {|i| i.event_type == 'response'}.size + # For send followup link at bottom + @last_response = info_request.get_last_public_response + end + + def make_request_zip(info_request, file_path) + Zip::ZipFile.open(file_path, Zip::ZipFile::CREATE) do |zipfile| + file_info = make_request_summary_file(info_request) + zipfile.get_output_stream(file_info[:filename]) { |f| f.puts(file_info[:data]) } + message_index = 0 + info_request.incoming_messages.each do |message| + next unless message.user_can_view?(authenticated_user) + message_index += 1 + message.get_attachments_for_display.each do |attachment| + filename = "#{message_index}_#{attachment.url_part_number}_#{attachment.display_filename}" + zipfile.get_output_stream(filename) { |f| f.puts(attachment.body) } end - redirect_to @url_path end end end + + def make_request_summary_file(info_request) + done = false + convert_command = AlaveteliConfiguration::html_to_pdf_command + assign_variables_for_show_template(info_request) + if !convert_command.blank? && File.exists?(convert_command) + @render_to_file = true + html_output = render_to_string(:template => 'request/show') + tmp_input = Tempfile.new(['foihtml2pdf-input', '.html']) + tmp_input.write(html_output) + tmp_input.close + tmp_output = Tempfile.new('foihtml2pdf-output') + output = AlaveteliExternalCommand.run(convert_command, tmp_input.path, tmp_output.path) + if !output.nil? + file_info = { :filename => 'correspondence.pdf', + :data => File.open(tmp_output.path).read } + done = true + else + logger.error("Could not convert info request #{info_request.id} to PDF with command '#{convert_command} #{tmp_input.path} #{tmp_output.path}'") + end + tmp_output.close + tmp_input.delete + tmp_output.delete + else + logger.warn("No HTML -> PDF converter found at #{convert_command}") + end + if !done + file_info = { :filename => 'correspondence.txt', + :data => render_to_string(:template => 'request/show', + :layout => false, + :formats => [:text]) } + end + file_info + end + + def cache_key_for_similar_requests(info_request, locale) + "request/similar/#{info_request.id}/#{locale}" + end + + def check_batch_requests_and_user_allowed + if !AlaveteliConfiguration::allow_batch_requests + raise RouteNotFound.new("Page not enabled") + end + if !authenticated?( + :web => _("To make a batch request"), + :email => _("Then you can make a batch request"), + :email_subject => _("Make a batch request"), + :user_name => "a user who has been authorised to make batch requests") + # do nothing - as "authenticated?" has done the redirect to signin page for us + return + end + if !@user.can_make_batch_requests? + return render_hidden('request/batch_not_allowed') + end + end + + def render_new_compose(batch) + + params[:info_request] = { } if !params[:info_request] + + # Read parameters in + unless batch + # first the public body (by URL name or id) + if params[:url_name] + if params[:url_name].match(/^[0-9]+$/) + params[:info_request][:public_body] = PublicBody.find(params[:url_name]) + else + public_body = PublicBody.find_by_url_name_with_historic(params[:url_name]) + raise ActiveRecord::RecordNotFound.new("None found") if public_body.nil? # XXX proper 404 + params[:info_request][:public_body] = public_body + end + elsif params[:public_body_id] + params[:info_request][:public_body] = PublicBody.find(params[:public_body_id]) + # Explicitly load the association as this isn't done automatically in newer Rails versions + elsif params[:info_request][:public_body_id] + params[:info_request][:public_body] = PublicBody.find(params[:info_request][:public_body_id]) + end + if !params[:info_request][:public_body] + # compulsory to have a body by here, or go to front page which is start of process + redirect_to frontpage_url + return + end + end + + # ... next any tags or other things + params[:info_request][:title] = params[:title] if params[:title] + params[:info_request][:tag_string] = params[:tags] if params[:tags] + + @info_request = InfoRequest.new(params[:info_request]) + if batch + @info_request.is_batch_request_template = true + end + params[:info_request_id] = @info_request.id + params[:outgoing_message] = {} if !params[:outgoing_message] + params[:outgoing_message][:body] = params[:body] if params[:body] + params[:outgoing_message][:default_letter] = params[:default_letter] if params[:default_letter] + params[:outgoing_message][:info_request] = @info_request + @outgoing_message = OutgoingMessage.new(params[:outgoing_message]) + @outgoing_message.set_signature_name(@user.name) if !@user.nil? + + if batch + render :action => 'new' + else + if @info_request.public_body.is_requestable? + render :action => 'new' + else + if @info_request.public_body.not_requestable_reason == 'bad_contact' + render :action => 'new_bad_contact' + else + # if not requestable because defunct or not_apply, redirect to main page + # (which doesn't link to the /new/ URL) + redirect_to public_body_url(@info_request.public_body) + end + end + end + return + end + + def render_new_preview + message = "" + if @outgoing_message.contains_email? + if @user.nil? + message += _("<p>You do not need to include your email in the request in order to get a reply, as we will ask for it on the next screen (<a href=\"{{url}}\">details</a>).</p>", :url => (help_privacy_path+"#email_address").html_safe); + else + message += _("<p>You do not need to include your email in the request in order to get a reply (<a href=\"{{url}}\">details</a>).</p>", :url => (help_privacy_path+"#email_address").html_safe); + end + message += _("<p>We recommend that you edit your request and remove the email address. + If you leave it, the email address will be sent to the authority, but will not be displayed on the site.</p>") + end + if @outgoing_message.contains_postcode? + message += _("<p>Your request contains a <strong>postcode</strong>. Unless it directly relates to the subject of your request, please remove any address as it will <strong>appear publicly on the Internet</strong>.</p>"); + end + if not message.empty? + flash.now[:error] = message.html_safe + end + render :action => 'preview' + end end diff --git a/app/controllers/request_game_controller.rb b/app/controllers/request_game_controller.rb index 4b6f02970..298818bc7 100644 --- a/app/controllers/request_game_controller.rb +++ b/app/controllers/request_game_controller.rb @@ -2,7 +2,7 @@ # The 'categorise old requests' game # # Copyright (c) 2009 UK Citizens Online Democracy. All rights reserved. -# Email: francis@mysociety.org; WWW: http://www.mysociety.org/ +# Email: hello@mysociety.org; WWW: http://www.mysociety.org/ class RequestGameController < ApplicationController @@ -13,8 +13,7 @@ class RequestGameController < ApplicationController @total = InfoRequest.count @done = @total - @missing @percentage = (@done.to_f / @total.to_f * 10000).round / 100.0 - - @requests = InfoRequest.get_random_old_unclassified(3) + @requests = InfoRequest.get_random_old_unclassified(3, :conditions => ["prominence = 'normal'"]) if @missing == 0 flash[:notice] = _('<p>All done! Thank you very much for your help.</p><p>There are <a href="{{helpus_url}}">more things you can do</a> to help {{site_name}}.</p>', diff --git a/app/controllers/services_controller.rb b/app/controllers/services_controller.rb index 1db5348c7..78c494dba 100644 --- a/app/controllers/services_controller.rb +++ b/app/controllers/services_controller.rb @@ -6,7 +6,7 @@ class ServicesController < ApplicationController def other_country_message text = "" - iso_country_code = Configuration::iso_country_code.downcase + iso_country_code = AlaveteliConfiguration::iso_country_code.downcase if country_from_ip.downcase != iso_country_code found_country = WorldFOIWebsites.by_code(country_from_ip) @@ -15,7 +15,7 @@ class ServicesController < ApplicationController FastGettext.locale = FastGettext.best_locale_in(request.env['HTTP_ACCEPT_LANGUAGE']) if found_country && found_country[:country_name] && found_country[:url] && found_country[:name] text = _("Hello! You can make Freedom of Information requests within {{country_name}} at {{link_to_website}}", - :country_name => found_country[:country_name], :link_to_website => "<a href=\"#{found_country[:url]}\">#{found_country[:name]}</a>") + :country_name => found_country[:country_name], :link_to_website => "<a href=\"#{found_country[:url]}\">#{found_country[:name]}</a>".html_safe) else current_country = WorldFOIWebsites.by_code(iso_country_code)[:country_name] text = _("Hello! We have an <a href=\"/help/alaveteli?country_name=#{CGI.escape(current_country)}\">important message</a> for visitors outside {{country_name}}", :country_name => current_country) @@ -24,9 +24,6 @@ class ServicesController < ApplicationController FastGettext.locale = old_fgt_locale end end - if !text.empty? - text += ' <span class="close-button">X</span>'.html_safe - end render :text => text, :content_type => "text/plain" # XXX workaround the HTML validation in test suite end @@ -36,9 +33,9 @@ class ServicesController < ApplicationController :content_type => "text/plain", :layout => false, :locals => {:name_to => info_request.user_name, - :name_from => Configuration::contact_name, + :name_from => AlaveteliConfiguration::contact_name, :info_request => info_request, :reason => params[:reason], - :info_request_url => 'http://' + Configuration::domain + request_url(info_request), + :info_request_url => 'http://' + AlaveteliConfiguration::domain + request_path(info_request), :site_name => site_name} end diff --git a/app/controllers/track_controller.rb b/app/controllers/track_controller.rb index 15da7f327..83e05ebbc 100644 --- a/app/controllers/track_controller.rb +++ b/app/controllers/track_controller.rb @@ -3,7 +3,7 @@ # social bookmarking. # # Copyright (c) 2008 UK Citizens Online Democracy. All rights reserved. -# Email: francis@mysociety.org; WWW: http://www.mysociety.org/ +# Email: hello@mysociety.org; WWW: http://www.mysociety.org/ class TrackController < ApplicationController @@ -80,10 +80,7 @@ class TrackController < ApplicationController # Track a search term def track_search_query - # XXX should be better thing in rails routes than having to do this - # join just to get / and . to work in a query. - query_array = params[:query_array] - @query = query_array.join("/") + @query = params[:query_array] # XXX more hackery to make alternate formats still work with query_array if /^(.*)\.json$/.match(@query) @@ -119,7 +116,7 @@ class TrackController < ApplicationController # Generic request tracker - set @track_thing before calling def track_set if @user - @existing_track = TrackThing.find_by_existing_track(@user, @track_thing) + @existing_track = TrackThing.find_existing(@user, @track_thing) if @existing_track flash[:notice] = _("You are already following updates about {{track_description}}", :track_description => @track_thing.params[:list_description]) return true @@ -156,11 +153,17 @@ class TrackController < ApplicationController def atom_feed_internal @xapian_object = perform_search([InfoRequestEvent], @track_thing.track_query, @track_thing.params[:feed_sortby], nil, 25, 1) + # We're assuming that a request to a feed url with no format suffix wants atom/xml + # so set that as the default, regardless of content negotiation + request.format = 'xml' unless params[:format] respond_to do |format| - format.atom { render :template => 'track/atom_feed', :content_type => "application/atom+xml" } format.json { render :json => @xapian_object.results.map { |r| r[:model].json_for_api(true, - lambda { |t| @template.highlight_and_excerpt(t, @xapian_object.words_to_highlight, 150) } + lambda { |t| view_context.highlight_and_excerpt(t, @xapian_object.words_to_highlight, 150) } ) } } + format.any { render :template => 'track/atom_feed', + :formats => ['atom'], + :layout => false, + :content_type => 'application/atom+xml' } end end @@ -181,7 +184,8 @@ class TrackController < ApplicationController if new_medium == 'delete' track_thing.destroy flash[:notice] = _("You are no longer following {{track_description}}.", :track_description => track_thing.params[:list_description]) - redirect_to params[:r] + redirect_to URI.parse(params[:r]).path + # Reuse code like this if we let medium change again. #elsif new_medium == 'email_daily' # track_thing.track_medium = new_medium diff --git a/app/controllers/user_controller.rb b/app/controllers/user_controller.rb index 4ee527bae..8d6522923 100644 --- a/app/controllers/user_controller.rb +++ b/app/controllers/user_controller.rb @@ -2,7 +2,7 @@ # Show information about a user. # # Copyright (c) 2007 UK Citizens Online Democracy. All rights reserved. -# Email: francis@mysociety.org; WWW: http://www.mysociety.org/ +# Email: hello@mysociety.org; WWW: http://www.mysociety.org/ require 'set' @@ -19,19 +19,22 @@ class UserController < ApplicationController # Show page about a user def show long_cache - if MySociety::Format.simplify_url_part(params[:url_name], 'user', 32) != params[:url_name] - redirect_to :url_name => MySociety::Format.simplify_url_part(params[:url_name], 'user', 32), :status => :moved_permanently + if MySociety::Format.simplify_url_part(params[:url_name], 'user') != params[:url_name] + redirect_to :url_name => MySociety::Format.simplify_url_part(params[:url_name], 'user'), :status => :moved_permanently return end if params[:view].nil? @show_requests = true @show_profile = true + @show_batches = false elsif params[:view] == 'profile' @show_profile = true @show_requests = false + @show_batches = false elsif params[:view] == 'requests' @show_profile = false @show_requests = true + @show_batches = true end @display_user = User.find(:first, :conditions => [ "url_name = ? and email_confirmed = ?", params[:url_name], true ]) @@ -119,7 +122,11 @@ class UserController < ApplicationController @track_things = TrackThing.find(:all, :conditions => ["tracking_user_id = ? and track_medium = ?", @display_user.id, 'email_daily'], :order => 'created_at desc') for track_thing in @track_things # XXX factor out of track_mailer.rb - xapian_object = InfoRequest.full_search([InfoRequestEvent], track_thing.track_query, 'described_at', true, nil, 20, 1) + xapian_object = ActsAsXapian::Search.new([InfoRequestEvent], track_thing.track_query, + :sort_by_prefix => 'described_at', + :sort_by_ascending => true, + :collapse_by_prefix => nil, + :limit => 20) feed_results += xapian_object.results.map {|x| x[:model]} end end @@ -136,7 +143,7 @@ class UserController < ApplicationController # Login form def signin work_out_post_redirect - @request_from_foreign_country = country_from_ip != Configuration::iso_country_code + @request_from_foreign_country = country_from_ip != AlaveteliConfiguration::iso_country_code # make sure we have cookies if session.instance_variable_get(:@dbman) if not session.instance_variable_get(:@dbman).instance_variable_get(:@original) @@ -190,7 +197,7 @@ class UserController < ApplicationController # Create new account form def signup work_out_post_redirect - @request_from_foreign_country = country_from_ip != Configuration::iso_country_code + @request_from_foreign_country = country_from_ip != AlaveteliConfiguration::iso_country_code # Make the user and try to save it @user_signup = User.new(params[:user_signup]) error = false @@ -222,7 +229,7 @@ class UserController < ApplicationController post_redirect = PostRedirect.find_by_email_token(params[:email_token]) if post_redirect.nil? - render :template => 'user/bad_token.rhtml' + render :template => 'user/bad_token' return end @@ -288,7 +295,7 @@ class UserController < ApplicationController post_redirect.user = user_signchangepassword post_redirect.save! url = confirm_url(:email_token => post_redirect.email_token) - UserMailer.deliver_confirm_login(user_signchangepassword, post_redirect.reason_params, url) + UserMailer.confirm_login(user_signchangepassword, post_redirect.reason_params, url).deliver else # User not found, but still show confirm page to not leak fact user exists end @@ -352,7 +359,7 @@ class UserController < ApplicationController # if new email already in use, send email there saying what happened user_alreadyexists = User.find_user_by_email(@signchangeemail.new_email) if user_alreadyexists - UserMailer.deliver_changeemail_already_used(@user.email, @signchangeemail.new_email) + UserMailer.changeemail_already_used(@user.email, @signchangeemail.new_email).deliver # it is important this screen looks the same as the one below, so # you can't change to someone's email in order to tell if they are # registered with that email on the site @@ -373,7 +380,7 @@ class UserController < ApplicationController post_redirect.save! url = confirm_url(:email_token => post_redirect.email_token) - UserMailer.deliver_changeemail_confirm(@user, @signchangeemail.new_email, url) + UserMailer.changeemail_confirm(@user, @signchangeemail.new_email, url).deliver # it is important this screen looks the same as the one above, so # you can't change to someone's email in order to tell if they are # registered with that email on the site @@ -419,13 +426,13 @@ class UserController < ApplicationController params[:contact][:email] = @user.email @contact = ContactValidator.new(params[:contact]) if @contact.valid? - ContactMailer.deliver_user_message( + ContactMailer.user_message( @user, @recipient_user, - main_url(user_url(@user)), + user_url(@user), params[:contact][:subject], params[:contact][:message] - ) + ).deliver flash[:notice] = _("Your message to {{recipient_user_name}} has been sent!",:recipient_user_name=>CGI.escapeHTML(@recipient_user.name)) redirect_to user_url(@recipient_user) return @@ -465,7 +472,7 @@ class UserController < ApplicationController @draft_profile_photo = ProfilePhoto.new(:data => file_content, :draft => true) if !@draft_profile_photo.valid? # error page (uses @profile_photo's error fields in view to show errors) - render :template => 'user/set_draft_profile_photo.rhtml' + render :template => 'user/set_draft_profile_photo' return end @draft_profile_photo.save @@ -480,7 +487,7 @@ class UserController < ApplicationController return end - render :template => 'user/set_crop_profile_photo.rhtml' + render :template => 'user/set_crop_profile_photo' return elsif !params[:submitted_crop_profile_photo].nil? # crop the draft photo according to jquery parameters and set it as the users photo @@ -499,7 +506,7 @@ class UserController < ApplicationController redirect_to set_profile_about_me_url() end else - render :template => 'user/set_draft_profile_photo.rhtml' + render :template => 'user/set_draft_profile_photo' end end @@ -527,11 +534,12 @@ class UserController < ApplicationController def get_draft_profile_photo profile_photo = ProfilePhoto.find(params[:id]) response.content_type = "image/png" - render_for_text(profile_photo.data) + render :text => profile_photo.data end # actual profile photo of a user def get_profile_photo + long_cache @display_user = User.find(:first, :conditions => [ "url_name = ? and email_confirmed = ?", params[:url_name], true ]) if !@display_user raise ActiveRecord::RecordNotFound.new("user not found, url_name=" + params[:url_name]) @@ -542,7 +550,7 @@ class UserController < ApplicationController end response.content_type = "image/png" - render_for_text(@display_user.profile_photo.data) + render :text => @display_user.profile_photo.data end # Change about me text on your profile page @@ -631,7 +639,7 @@ class UserController < ApplicationController post_redirect.save! url = confirm_url(:email_token => post_redirect.email_token) - UserMailer.deliver_confirm_login(user, post_redirect.reason_params, url) + UserMailer.confirm_login(user, post_redirect.reason_params, url).deliver render :action => 'confirm' end @@ -642,7 +650,7 @@ class UserController < ApplicationController post_redirect.save! url = confirm_url(:email_token => post_redirect.email_token) - UserMailer.deliver_already_registered(user, post_redirect.reason_params, url) + UserMailer.already_registered(user, post_redirect.reason_params, url).deliver render :action => 'confirm' # must be same as for send_confirmation_mail above to avoid leak of presence of email in db end |