diff options
Diffstat (limited to 'app')
74 files changed, 1206 insertions, 671 deletions
diff --git a/app/controllers/admin_censor_rule_controller.rb b/app/controllers/admin_censor_rule_controller.rb index 2c0c7ca4e..52df8dfc1 100644 --- a/app/controllers/admin_censor_rule_controller.rb +++ b/app/controllers/admin_censor_rule_controller.rb @@ -65,7 +65,7 @@ class AdminCensorRuleController < AdminController render :action => 'edit' end end - + def destroy censor_rule = CensorRule.find(params[:censor_rule_id]) info_request = censor_rule.info_request diff --git a/app/controllers/admin_controller.rb b/app/controllers/admin_controller.rb index adb506b91..884d7e540 100644 --- a/app/controllers/admin_controller.rb +++ b/app/controllers/admin_controller.rb @@ -36,6 +36,8 @@ class AdminController < ApplicationController # also force a search reindexing (so changed text reflected in search) info_request.reindex_request_events + # and remove from varnsi + info_request.purge_in_cache end # Expire cached attachment files for a user @@ -44,23 +46,40 @@ class AdminController < ApplicationController expire_for_request(info_request) end end - private - def authenticate - config_username = MySociety::Config.get('ADMIN_USERNAME', '') - config_password = MySociety::Config.get('ADMIN_PASSWORD', '') - if !config_username.empty? && !config_password.empty? - authenticate_or_request_with_http_basic do |user_name, password| - if user_name == config_username && password == config_password - session[:using_admin] = 1 - request.env['REMOTE_USER'] = user_name - else - request_http_basic_authentication + private + + def authenticate + if MySociety::Config.get('SKIP_ADMIN_AUTH', false) + session[:using_admin] = 1 + return + else + if session[:using_admin].nil? + if params[:emergency].nil? + if authenticated?( + :web => _("To log into the administrative interface"), + :email => _("Then you can log into the administrative interface"), + :email_subject => _("Log into the admin interface"), + :user_name => "a superuser") + if !@user.nil? && @user.admin_level == "super" + session[:using_admin] = 1 + request.env['REMOTE_USER'] = @user.url_name + end + end + else + config_username = MySociety::Config.get('ADMIN_USERNAME', '') + config_password = MySociety::Config.get('ADMIN_PASSWORD', '') + authenticate_or_request_with_http_basic do |user_name, password| + if user_name == config_username && password == config_password + session[:using_admin] = 1 + request.env['REMOTE_USER'] = user_name + else + request_http_basic_authentication + end end end - else - session[:using_admin] = 1 end - end + end + end end diff --git a/app/controllers/admin_general_controller.rb b/app/controllers/admin_general_controller.rb index 0b7e9bec0..2c961dfc5 100644 --- a/app/controllers/admin_general_controller.rb +++ b/app/controllers/admin_general_controller.rb @@ -30,8 +30,9 @@ class AdminGeneralController < AdminController # 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") - @old_unclassified = InfoRequest.find_old_unclassified(:limit => 20, + @old_unclassified = InfoRequest.find_old_unclassified(:limit => 20, :conditions => ["prominence = 'normal'"]) @holding_pen_messages = InfoRequest.holding_pen_request.incoming_messages end @@ -78,11 +79,13 @@ class AdminGeneralController < AdminController end def debug + @http_auth_user = admin_http_auth_user @current_commit = `git log -1 --format="%H"` - @current_branch = `git branch | grep "\*" | awk '{print $2}'` - repo = `git remote show origin -n | grep Fetch | awk '{print $3}' | sed -re 's/.*:(.*).git/\\1/'` - @github_origin = "https://github.com/#{repo.strip}/tree/" - @request_env = request.env + @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_public_body_controller.rb b/app/controllers/admin_public_body_controller.rb index bf7c07905..be733ab7d 100644 --- a/app/controllers/admin_public_body_controller.rb +++ b/app/controllers/admin_public_body_controller.rb @@ -16,7 +16,7 @@ class AdminPublicBodyController < AdminController def _lookup_query_internal @locale = self.locale_from_params() - PublicBody.with_locale(@locale) do + PublicBody.with_locale(@locale) do @query = params[:query] if @query == "" @query = nil @@ -26,13 +26,13 @@ class AdminPublicBodyController < AdminController @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}'" : - ["(lower(public_body_translations.name) like lower('%'||?||'%') or - lower(public_body_translations.short_name) like lower('%'||?||'%') or + :conditions => @query.nil? ? "public_body_translations.locale = '#{@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 end - @public_bodies_by_tag = PublicBody.find_by_tag(@query) + @public_bodies_by_tag = PublicBody.find_by_tag(@query) end def list @@ -62,11 +62,11 @@ class AdminPublicBodyController < AdminController def missing_scheme # There might be a way to do this in ActiveRecord, but I can't find it @public_bodies = PublicBody.find_by_sql(" - SELECT a.id, a.name, a.url_name, COUNT(*) AS howmany - FROM public_bodies a JOIN info_requests r ON a.id = r.public_body_id - WHERE a.publication_scheme = '' - GROUP BY a.id, a.name, a.url_name - ORDER BY howmany DESC + SELECT a.id, a.name, a.url_name, COUNT(*) AS howmany + FROM public_bodies a JOIN info_requests r ON a.id = r.public_body_id + WHERE a.publication_scheme = '' + GROUP BY a.id, a.name, a.url_name + ORDER BY howmany DESC LIMIT 20 ") @stats = { @@ -77,7 +77,7 @@ class AdminPublicBodyController < AdminController def show @locale = self.locale_from_params() - PublicBody.with_locale(@locale) do + PublicBody.with_locale(@locale) do @public_body = PublicBody.find(params[:id]) render end @@ -87,7 +87,7 @@ class AdminPublicBodyController < AdminController @public_body = PublicBody.new render end - + def create PublicBody.with_locale(I18n.default_locale) do params[:public_body][:last_edit_editor] = admin_http_auth_user() @@ -103,7 +103,7 @@ class AdminPublicBodyController < AdminController def edit @public_body = PublicBody.find(params[:id]) - @public_body.last_edit_comment = "" + @public_body.last_edit_comment = "" render end @@ -122,7 +122,7 @@ class AdminPublicBodyController < AdminController def destroy @locale = self.locale_from_params() - PublicBody.with_locale(@locale) do + PublicBody.with_locale(@locale) do public_body = PublicBody.find(params[:id]) if public_body.info_requests.size > 0 @@ -147,7 +147,7 @@ class AdminPublicBodyController < AdminController else raise "internal error, unknown button label" end - + # Try with dry run first csv_contents = params[:csv_file].read en = PublicBody.import_csv(csv_contents, params[:tag], params[:tag_behaviour], true, admin_http_auth_user(), I18n.available_locales) @@ -174,7 +174,7 @@ class AdminPublicBodyController < AdminController @errors = "" @notes = "" end - + end private diff --git a/app/controllers/admin_request_controller.rb b/app/controllers/admin_request_controller.rb index e5de4f8b7..fd1405319 100644 --- a/app/controllers/admin_request_controller.rb +++ b/app/controllers/admin_request_controller.rb @@ -6,6 +6,8 @@ # # $Id: admin_request_controller.rb,v 1.42 2009-10-03 01:28:33 francis Exp $ +require 'ostruct' + class AdminRequestController < AdminController def index list @@ -24,6 +26,15 @@ class AdminRequestController < AdminController 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 => MySociety::Config.get("CONTACT_NAME", 'Alaveteli'), + :info_request => @info_request, :reason => params[:reason], + :info_request_url => 'http://' + MySociety::Config.get('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 }) end def resend @@ -60,10 +71,10 @@ class AdminRequestController < AdminController if @info_request.valid? @info_request.save! - @info_request.log_event("edit", - { :editor => admin_http_auth_user(), - :old_title => old_title, :title => @info_request.title, - :old_prominence => old_prominence, :prominence => @info_request.prominence, + @info_request.log_event("edit", + { :editor => admin_http_auth_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_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, @@ -75,7 +86,7 @@ class AdminRequestController < AdminController else render :action => 'edit' end - end + end def fully_destroy @info_request = InfoRequest.find(params[:id]) @@ -99,28 +110,28 @@ class AdminRequestController < AdminController outgoing_message_id = @outgoing_message.id @outgoing_message.fully_destroy - @outgoing_message.info_request.log_event("destroy_outgoing", + @outgoing_message.info_request.log_event("destroy_outgoing", { :editor => admin_http_auth_user(), :deleted_outgoing_message_id => outgoing_message_id }) flash[:notice] = 'Outgoing message successfully destroyed.' redirect_to request_admin_url(@info_request) - end + 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_http_auth_user(), + if @outgoing_message.update_attributes(params[:outgoing_message]) + @outgoing_message.info_request.log_event("edit_outgoing", + { :outgoing_message_id => @outgoing_message.id, :editor => admin_http_auth_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 - end + end def edit_comment @comment = Comment.find(params[:id]) @@ -133,9 +144,9 @@ class AdminRequestController < AdminController old_visible = @comment.visible @comment.visible = params[:comment][:visible] == "true" ? true : false - if @comment.update_attributes(params[:comment]) - @comment.info_request.log_event("edit_comment", - { :comment_id => @comment.id, :editor => admin_http_auth_user(), + if @comment.update_attributes(params[:comment]) + @comment.info_request.log_event("edit_comment", + { :comment_id => @comment.id, :editor => admin_http_auth_user(), :old_body => old_body, :body => @comment.body, :old_visible => old_visible, :visible => @comment.visible, }) @@ -144,7 +155,7 @@ class AdminRequestController < AdminController else render :action => 'edit_comment' end - end + end def destroy_incoming @@ -153,41 +164,45 @@ class AdminRequestController < AdminController incoming_message_id = @incoming_message.id @incoming_message.fully_destroy - @incoming_message.info_request.log_event("destroy_incoming", + @incoming_message.info_request.log_event("destroy_incoming", { :editor => admin_http_auth_user(), :deleted_incoming_message_id => incoming_message_id }) flash[:notice] = 'Incoming message successfully destroyed.' redirect_to request_admin_url(@info_request) - end + end def redeliver_incoming incoming_message = IncomingMessage.find(params[:redeliver_incoming_message_id]) - - if params[:url_title].match(/^[0-9]+$/) - destination_request = InfoRequest.find(params[:url_title].to_i) - else - destination_request = InfoRequest.find_by_url_title(params[:url_title]) - end - - if destination_request.nil? - flash[:error] = "Failed to find destination request '" + params[:url_title] + "'" - redirect_to request_admin_url(incoming_message.info_request) + message_ids = params[:url_title].split(",").each {|x| x.strip} + 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(incoming_message.info_request) + end + + raw_email_data = incoming_message.raw_email.data + mail = TMail::Mail.parse(raw_email_data) + mail.base64_decode + destination_request.receive(mail, raw_email_data, true) + + incoming_message_id = incoming_message.id + incoming_message.info_request.log_event("redeliver_incoming", { + :editor => admin_http_auth_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 + incoming_message.fully_destroy end - - raw_email_data = incoming_message.raw_email.data - mail = TMail::Mail.parse(raw_email_data) - mail.base64_decode - destination_request.receive(mail, raw_email_data, true) - - incoming_message_id = incoming_message.id - incoming_message.fully_destroy - incoming_message.info_request.log_event("redeliver_incoming", { - :editor => admin_http_auth_user(), - :destination_request => destination_request.id, - :deleted_incoming_message_id => incoming_message_id - }) - - flash[:notice] = "Message has been moved to this request" redirect_to request_admin_url(destination_request) end @@ -202,10 +217,10 @@ class AdminRequestController < AdminController else info_request.user = destination_user info_request.save! - info_request.log_event("move_request", { - :editor => admin_http_auth_user(), - :old_user_url_name => old_user.url_name, - :user_url_name => destination_user.url_name + info_request.log_event("move_request", { + :editor => admin_http_auth_user(), + :old_user_url_name => old_user.url_name, + :user_url_name => destination_user.url_name }) info_request.reindex_request_events @@ -220,10 +235,10 @@ class AdminRequestController < AdminController else info_request.public_body = destination_public_body info_request.save! - info_request.log_event("move_request", { - :editor => admin_http_auth_user(), - :old_public_body_url_name => old_public_body.url_name, - :public_body_url_name => destination_public_body.url_name + info_request.log_event("move_request", { + :editor => admin_http_auth_user(), + :old_public_body_url_name => old_public_body.url_name, + :public_body_url_name => destination_public_body.url_name }) info_request.reindex_request_events @@ -288,16 +303,16 @@ class AdminRequestController < AdminController if domain.nil? @public_bodies = [] else - @public_bodies = PublicBody.find(:all, :order => "name", + @public_bodies = PublicBody.find(:all, :order => "name", :conditions => [ "lower(request_email) like lower('%'||?||'%')", domain ]) end - + # 2. Match the email address in the message without matching the hash @info_requests = InfoRequest.guess_by_incoming_email(@raw_email.incoming_message) # 3. Give a reason why it's in the holding pen last_event = InfoRequestEvent.find_by_incoming_message_id(@raw_email.incoming_message.id) - @rejected_reason = last_event.params[:rejected_reason] + @rejected_reason = last_event.params[:rejected_reason] || "unknown reason" end end @@ -323,6 +338,33 @@ class AdminRequestController < AdminController redirect_to request_admin_url(info_request_event.info_request) end + def hide_request + ActiveRecord::Base.transaction do + subject = params[:subject] + explanation = params[:explanation] + info_request = InfoRequest.find(params[:id]) + info_request.prominence = "requester_only" + + info_request.log_event("hide", { + :editor => admin_http_auth_user(), + :reason => params[:reason], + :subject => subject, + :explanation => explanation + }) + + info_request.set_described_state(params[:reason]) + info_request.save! + + ContactMailer.deliver_from_admin_message( + info_request.user, + subject, + params[:explanation] + ) + flash[:notice] = _("Your message to {{recipient_user_name}} has been sent",:recipient_user_name=>CGI.escapeHTML(info_request.user.name)) + redirect_to request_admin_url(info_request) + end + end + private end diff --git a/app/controllers/admin_user_controller.rb b/app/controllers/admin_user_controller.rb index b2c084739..4059ac0bb 100644 --- a/app/controllers/admin_user_controller.rb +++ b/app/controllers/admin_user_controller.rb @@ -15,7 +15,7 @@ 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 + :conditions => @query.nil? ? nil : ["lower(name) like lower('%'||?||'%') or lower(email) like lower('%'||?||'%')", @query, @query] end @@ -28,7 +28,7 @@ class AdminUserController < AdminController # Don't use @user as that is any logged in user @admin_user = User.find(params[:id]) end - + def show_bounce_message @admin_user = User.find(params[:id]) end @@ -54,7 +54,7 @@ class AdminUserController < AdminController else render :action => 'edit' end - end + end def destroy_track track_thing = TrackThing.find(params[:track_id].to_i) @@ -62,7 +62,7 @@ class AdminUserController < AdminController flash[:notice] = 'Track destroyed' redirect_to user_admin_url(track_thing.tracking_user) end - + def clear_bounce user = User.find(params[:id]) user.email_bounced_at = nil @@ -74,10 +74,9 @@ class AdminUserController < AdminController 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) + post_redirect = PostRedirect.new( :uri => main_url(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)) - session[:user_id] = nil # Log out current (usually admin) user, so we get logged in as the other user redirect_to url end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index b681f455d..41adf1848 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # controllers/application.rb: # Parent class of all controllers in FOI site. Filters added to this controller # apply to all controllers in the application. Likewise, all the methods added @@ -19,7 +20,7 @@ class ApplicationController < ActionController::Base # 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 @@ -33,7 +34,7 @@ class ApplicationController < ActionController::Base def set_vary_header response.headers['Vary'] = 'Cookie' end - + helper_method :anonymous_cache, :short_cache, :medium_cache, :long_cache def anonymous_cache(time) if session[:user_id].nil? @@ -117,8 +118,20 @@ class ApplicationController < ActionController::Base # Override default error handler, for production sites. def rescue_action_in_public(exception) + # Call `set_view_paths` from the theme, if it exists. + # 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. + begin + set_view_paths + rescue NameError => e + if !(e.message =~ /undefined local variable or method `set_view_paths'/) + raise + end + end # Make sure expiry time for session is set (before_filters are - # otherwise missed by this override) + # otherwise missed by this override) session_remember_me case exception when ActiveRecord::RecordNotFound, ActionController::UnknownAction, ActionController::RoutingError @@ -140,19 +153,19 @@ class ApplicationController < ActionController::Base 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) + # otherwise missed by this override) session_remember_me # Display default, detailed error for developers original_rescue_action_locally(exception) end - + def local_request? false end - # Called from test code, is a mimic of User.confirm, for use in following email - # links when in controller tests (since we don't have full integration tests that + # 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) def test_code_redirect_by_email_token(token, controller_example_group) post_redirect = PostRedirect.find_by_email_token(token) @@ -178,7 +191,7 @@ class ApplicationController < ActionController::Base end def foi_fragment_cache_path(param) - path = File.join(RAILS_ROOT, 'cache', 'views', foi_fragment_cache_part_path(param)) + path = File.join(Rails.root, 'cache', 'views', foi_fragment_cache_part_path(param)) max_file_length = 255 - 35 # we subtract 35 because tempfile # adds on a variable number of # characters @@ -189,7 +202,7 @@ class ApplicationController < ActionController::Base # return stub path so admin can expire it first_three_digits = info_request.id.to_s()[0..2] path = "views/request/#{first_three_digits}/#{info_request.id}" - foi_cache_path = File.join(File.dirname(__FILE__), '../../cache') + foi_cache_path = File.expand_path(File.join(File.dirname(__FILE__), '../../cache')) return File.join(foi_cache_path, path) end def foi_fragment_cache_exists?(key_path) @@ -207,7 +220,7 @@ class ApplicationController < ActionController::Base end end - # get the local locale + # get the local locale def locale_from_params(*args) if params[:show_locale] params[:show_locale] @@ -224,15 +237,15 @@ class ApplicationController < ActionController::Base post_redirect = PostRedirect.new(:uri => request.request_uri, :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 - # page or on its own, useful for pop-ups + # 'modal' controls whether the sign-in form will be displayed in the typical full-blown + # page or on its own, useful for pop-ups redirect_to signin_url(:token => post_redirect.token, :modal => params[:modal]) return false end return true end - def authenticated_as_user?(user, reason_params) + def authenticated_as_user?(user, reason_params) reason_params[:user_name] = user.name reason_params[:user_url] = show_user_url(:url_name => user.url_name) if session[:user_id] @@ -274,6 +287,8 @@ class ApplicationController < ActionController::Base # XXX what is the built in Ruby URI munging function that can do this # choice of & vs. ? more elegantly than this dumb if statement? if uri.include?("?") + # XXX This looks odd. What would a fragment identifier be doing server-side? + # But it also looks harmless, so I’ll leave it just in case. if uri.include?("#") uri.sub!("#", "&post_redirect=1#") else @@ -294,6 +309,7 @@ class ApplicationController < ActionController::Base if params[:post_redirect] and session[:post_redirect_token] post_redirect = PostRedirect.find_by_token(session[:post_redirect_token]) params.update(post_redirect.post_params) + params[:post_redirect_user] = post_redirect.user end end @@ -304,7 +320,7 @@ class ApplicationController < ActionController::Base end end - # + # def check_read_only read_only = MySociety::Config.get('READ_ONLY', '') if !read_only.empty? @@ -329,11 +345,8 @@ class ApplicationController < ActionController::Base return "*unknown*"; end end - def assign_http_auth_user - @http_auth_user = admin_http_auth_user - end - # Convert URL name for sort by order, to Xapian query + # Convert URL name for sort by order, to Xapian query def order_to_sort_by(sortby) if sortby.nil? return [nil, nil] @@ -349,7 +362,7 @@ class ApplicationController < ActionController::Base end # Function for search - def perform_search(models, query, sortby, collapse, per_page = 25, this_page = nil) + def perform_search(models, query, sortby, collapse, per_page = 25, this_page = nil) @query = query @sortby = sortby @@ -385,7 +398,7 @@ class ApplicationController < ActionController::Base collapse = 'request_collapse' end options = { - :offset => (@page - 1) * @per_page, + :offset => (@page - 1) * @per_page, :limit => @per_page, :sort_by_prefix => nil, :sort_by_ascending => true, @@ -404,7 +417,7 @@ class ApplicationController < ActionController::Base if e.message =~ /^QueryParserError: Wildcard/ # Wildcard expands to too many terms logger.info "Wildcard query '#{query.strip + '*'}' caused: #{e.message}" - + user_query = ActsAsXapian.query_parser.parse_query( query, Xapian::QueryParser::FLAG_LOVEHATE | @@ -433,8 +446,8 @@ class ApplicationController < ActionController::Base def param_exists(item) return params[item] && !params[item].empty? - end - + end + def get_request_variety_from_params query = "" sortby = "newest" @@ -459,7 +472,7 @@ class ApplicationController < ActionController::Base def get_status_from_params query = "" - if params[:latest_status] + if params[:latest_status] statuses = [] if params[:latest_status].class == String params[:latest_status] = [params[:latest_status]] @@ -510,7 +523,7 @@ class ApplicationController < ActionController::Base query = "" tags = [] if param_exists(:tags) - params[:tags].split().each do |tag| + params[:tags].split().each do |tag| tags << "tag:#{tag}" end end @@ -519,7 +532,7 @@ class ApplicationController < ActionController::Base end return query end - + def make_query_from_params query = params[:query] || "" if query.nil? query += get_date_range_from_params @@ -540,16 +553,6 @@ class ApplicationController < ActionController::Base return country end - def quietly_try_to_open(url) - begin - result = open(url).read.strip - rescue OpenURI::HTTPError, SocketError, Errno::ETIMEDOUT, Errno::ECONNREFUSED, Errno::EHOSTUNREACH - logger.warn("Unable to open third-party URL #{url}") - result = "" - end - return result - 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 86d5b0a06..d9cd002dd 100644 --- a/app/controllers/comment_controller.rb +++ b/app/controllers/comment_controller.rb @@ -9,14 +9,14 @@ class CommentController < ApplicationController before_filter :check_read_only, :only => [ :new ] protect_from_forgery :only => [ :new ] - + def new if params[:type] == 'request' @info_request = InfoRequest.find_by_url_title(params[:url_title]) @track_thing = TrackThing.create_track_for_request(@info_request) if params[:comment] @comment = Comment.new(params[:comment].merge({ - :comment_type => 'request', + :comment_type => 'request', :user => @user })) end @@ -38,7 +38,7 @@ class CommentController < ApplicationController # Default to subscribing to request when first viewing form params[:subscribe_to_request] = true end - + # See if values were valid or not if !params[:comment] || !@existing_comment.nil? || !@comment.valid? || params[:reedit] render :action => 'new' diff --git a/app/controllers/general_controller.rb b/app/controllers/general_controller.rb index 82b1b8629..6e89a2832 100644 --- a/app/controllers/general_controller.rb +++ b/app/controllers/general_controller.rb @@ -28,19 +28,19 @@ class GeneralController < ApplicationController @locale = self.locale_from_params() locale_condition = 'public_body_translations.locale = ?' conditions = [locale_condition, @locale] - PublicBody.with_locale(@locale) do + PublicBody.with_locale(@locale) do if body_short_names.empty? # This is too slow - @popular_bodies = PublicBody.find(:all, - :select => "public_bodies.*, (select count(*) from info_requests where info_requests.public_body_id = public_bodies.id) as c", - :order => "c desc", + @popular_bodies = PublicBody.find(:all, + :select => "public_bodies.*, (select count(*) from info_requests where info_requests.public_body_id = public_bodies.id) as c", + :order => "c desc", :limit => 32, :conditions => conditions, :joins => :translations ) else conditions[0] += " and public_bodies.url_name in (" + body_short_names + ")" - @popular_bodies = PublicBody.find(:all, + @popular_bodies = PublicBody.find(:all, :conditions => conditions, :joins => :translations) end @@ -52,7 +52,7 @@ class GeneralController < ApplicationController 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 @@ -97,7 +97,7 @@ class GeneralController < ApplicationController query_parts = @query.split("/") if !['bodies', 'requests', 'users', 'all'].include?(query_parts[-1]) redirect_to search_url([@query, "all"], params) - else + else redirect_to search_url(@query, params) end end @@ -236,4 +236,4 @@ class GeneralController < ApplicationController end - + diff --git a/app/controllers/help_controller.rb b/app/controllers/help_controller.rb index b08438b52..e3b77271e 100644 --- a/app/controllers/help_controller.rb +++ b/app/controllers/help_controller.rb @@ -9,7 +9,7 @@ class HelpController < ApplicationController # we don't even have a control subroutine for most help pages, just see their templates - + before_filter :long_cache def unhappy @@ -61,7 +61,7 @@ class HelpController < ApplicationController @last_request, @last_body ) flash[:notice] = _("Your message has been sent. Thank you for getting in touch! We'll get back to you soon.") - redirect_to frontpage_url + redirect_to frontpage_url return end @@ -69,7 +69,7 @@ class HelpController < ApplicationController @contact.errors.clear end end - + end end diff --git a/app/controllers/holiday_controller.rb b/app/controllers/holiday_controller.rb index 916ff54c8..7f62aa26d 100644 --- a/app/controllers/holiday_controller.rb +++ b/app/controllers/holiday_controller.rb @@ -1,5 +1,5 @@ # app/controllers/holiday_controller.rb: -# Calculate dates +# Calculate dates # # Copyright (c) 2008 UK Citizens Online Democracy. All rights reserved. # Email: francis@mysociety.org; WWW: http://www.mysociety.org/ @@ -16,7 +16,7 @@ class HolidayController < ApplicationController @request_date = Date.strptime(params[:holiday]) or raise "Invalid date" @due_date = Holiday.due_date_from(@request_date, 20) @skipped = Holiday.all( - :conditions => [ 'day >= ? AND day <= ?', + :conditions => [ 'day >= ? AND day <= ?', @request_date.strftime("%F"), @due_date.strftime("%F") ] ).collect { |h| h.day }.sort diff --git a/app/controllers/public_body_controller.rb b/app/controllers/public_body_controller.rb index 00d1cc1e0..95d936e54 100644 --- a/app/controllers/public_body_controller.rb +++ b/app/controllers/public_body_controller.rb @@ -14,23 +14,23 @@ class PublicBodyController < ApplicationController def show long_cache if MySociety::Format.simplify_url_part(params[:url_name], 'body') != params[:url_name] - redirect_to :url_name => MySociety::Format.simplify_url_part(params[:url_name], 'body'), :status => :moved_permanently + redirect_to :url_name => MySociety::Format.simplify_url_part(params[:url_name], 'body'), :status => :moved_permanently return end @locale = self.locale_from_params() - PublicBody.with_locale(@locale) do + PublicBody.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? redirect_to :back return - end + 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 show_public_body_url(:url_name => @public_body.url_name) return end - + set_last_body(@public_body) top_url = main_url("/") @@ -50,8 +50,8 @@ class PublicBodyController < ApplicationController begin @xapian_requests = perform_search([InfoRequestEvent], query, sortby, 'request_collapse') if (@page > 1) - @page_desc = " (page " + @page.to_s + ")" - else + @page_desc = " (page " + @page.to_s + ")" + else @page_desc = "" end rescue @@ -65,7 +65,7 @@ class PublicBodyController < ApplicationController format.html { @has_json = true; render :template => "public_body/show"} format.json { render :json => @public_body.json_for_api } end - + end end @@ -93,8 +93,8 @@ class PublicBodyController < ApplicationController @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 (?)) + 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}" if @tag.nil? or @tag == "all" @@ -152,10 +152,10 @@ class PublicBodyController < ApplicationController report = StringIO.new CSV::Writer.generate(report, ',') do |title| title << [ - 'Name', + 'Name', 'Short name', # deliberately not including 'Request email' - 'URL name', + 'URL name', 'Tags', 'Home page', 'Publication scheme', @@ -164,12 +164,12 @@ class PublicBodyController < ApplicationController 'Version', ] public_bodies.each do |public_body| - title << [ - public_body.name, - public_body.short_name, + title << [ + public_body.name, + public_body.short_name, # DO NOT include request_email (we don't want to make it # easy to spam all authorities with requests) - public_body.url_name, + public_body.url_name, public_body.tag_string, public_body.calculated_home_page, public_body.publication_scheme, @@ -181,7 +181,7 @@ class PublicBodyController < ApplicationController end report.rewind send_data(report.read, :type=> 'text/csv; charset=utf-8; header=present', - :filename => 'all-authorities.csv', + :filename => 'all-authorities.csv', :disposition =>'attachment', :encoding => 'utf8') end diff --git a/app/controllers/request_controller.rb b/app/controllers/request_controller.rb index 313a57d7d..2f5b4d643 100644 --- a/app/controllers/request_controller.rb +++ b/app/controllers/request_controller.rb @@ -16,7 +16,7 @@ class RequestController < ApplicationController MAX_RESULTS = 500 PER_PAGE = 25 - + @@custom_states_loaded = false begin if ENV["RAILS_ENV"] != "test" @@ -44,11 +44,17 @@ class RequestController < ApplicationController end medium_cache end - + def show - medium_cache + if !MySociety::Config.get('VARNISH_HOST').nil? + # If varnish is set up to accept PURGEs, then cache for a + # long time + long_cache + else + medium_cache + end @locale = self.locale_from_params() - PublicBody.with_locale(@locale) do + PublicBody.with_locale(@locale) do # Look up by old style numeric identifiers if params[:url_title].match(/^[0-9]+$/) @@ -57,7 +63,7 @@ class RequestController < ApplicationController return end - # Look up by new style text names + # Look up by new style text names @info_request = InfoRequest.find_by_url_title(params[:url_title]) if @info_request.nil? raise ActiveRecord::RecordNotFound.new("Request not found") @@ -69,7 +75,7 @@ class RequestController < ApplicationController render :template => 'request/hidden', :status => 410 # gone return end - + # Other parameters @info_request_events = @info_request.info_request_events @status = @info_request.calculate_status @@ -77,7 +83,7 @@ class RequestController < ApplicationController @update_status = params[:update_status] ? true : false @old_unclassified = @info_request.is_old_unclassified? && !authenticated_user.nil? @is_owning_user = @info_request.is_owning_user?(authenticated_user) - + if @update_status return if !@is_owning_user && !authenticated_as_user?(@info_request.user, :web => _("To update the status of this FOI request"), @@ -85,7 +91,7 @@ class RequestController < ApplicationController :email_subject => _("Update the status of your request to ") + @info_request.public_body.name ) 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 @@ -95,14 +101,14 @@ class RequestController < ApplicationController behavior_cache :tag => ['similar', @info_request.id] do begin limit = 10 - @xapian_similar = ::ActsAsXapian::Similar.new([InfoRequestEvent], @info_request.info_request_events, + @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 end - + # 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 } ] @@ -122,7 +128,7 @@ class RequestController < ApplicationController @info_request = InfoRequest.find_by_url_title(params[:url_title]) if @info_request.nil? raise ActiveRecord::RecordNotFound.new("Request not found") - else + else if !@info_request.user_can_view?(authenticated_user) render :template => 'request/hidden', :status => 410 # gone return @@ -137,16 +143,18 @@ class RequestController < ApplicationController @per_page = 25 @page = (params[:page] || "1").to_i @info_request = InfoRequest.find_by_url_title(params[:url_title]) + 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 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') - + if (@page > 1) - @page_desc = " (page " + @page.to_s + ")" - else + @page_desc = " (page " + @page.to_s + ")" + else @page_desc = "" end end @@ -158,7 +166,7 @@ class RequestController < ApplicationController if @view == "recent" return redirect_to request_list_all_path(:action => "list", :view => "all", :page => @page), :status => :moved_permanently end - + # Later pages are very expensive to load if @page > MAX_RESULTS / PER_PAGE raise ActiveRecord::RecordNotFound.new("Sorry. No pages after #{MAX_RESULTS / PER_PAGE}.") @@ -168,14 +176,14 @@ class RequestController < ApplicationController query = make_query_from_params @title = _("View and search requests") sortby = "newest" - @cache_tag = Digest::MD5.hexdigest(query + @page.to_s) + @cache_tag = Digest::MD5.hexdigest(query + @page.to_s + I18n.locale.to_s) behavior_cache :tag => [@cache_tag] do 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 end - + @title = @title + " (page " + @page.to_s + ")" if (@page > 1) @track_thing = TrackThing.create_track_for_search_query(query) @feed_autodetect = [ { :url => do_track_url(@track_thing, 'feed'), :title => @track_thing.params[:title_in_rss], :has_json => true } ] @@ -199,7 +207,7 @@ class RequestController < ApplicationController # get_undescribed_requests also allows one day since the response # arrived. if !@user.nil? && params[:submitted_new_request].nil? && !@user.can_leave_requests_undescribed? - @undescribed_requests = @user.get_undescribed_requests + @undescribed_requests = @user.get_undescribed_requests if @undescribed_requests.size > 1 render :action => 'new_please_describe' return @@ -245,7 +253,7 @@ class RequestController < ApplicationController elsif params[:public_body_id] params[:info_request][:public_body_id] = params[:public_body_id] end - if !params[:info_request][:public_body_id] + 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 @@ -263,7 +271,7 @@ class RequestController < ApplicationController 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 @@ -286,8 +294,8 @@ class RequestController < ApplicationController # 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', + @outgoing_message = OutgoingMessage.new(params[:outgoing_message].merge({ + :status => 'ready', :message_type => 'initial_request' })) @info_request.outgoing_messages << @outgoing_message @@ -312,7 +320,7 @@ class RequestController < ApplicationController if params[:preview].to_i == 1 message = "" if @outgoing_message.contains_email? - if @user.nil? + 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"]; 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"]; @@ -344,7 +352,13 @@ class RequestController < ApplicationController return end - @info_request.user = authenticated_user + if params[:post_redirect_user] + # If an admin has clicked the confirmation link on a users behalf, + # we don’t want to reassign the request to the administrator. + @info_request.user = params[:post_redirect_user] + else + @info_request.user = authenticated_user + end # This automatically saves dependent objects, such as @outgoing_message, in the same transaction @info_request.save! # XXX send_message needs the database id, so we send after saving, which isn't ideal if the request broke here. @@ -352,7 +366,7 @@ class RequestController < ApplicationController flash[:notice] = _("<p>Your {{law_used_full}} request has been <strong>sent on its way</strong>!</p> <p><strong>We will email you</strong> when there is a response, or after {{late_number_of_days}} working days if the authority still hasn't 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 + <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 => MySociety::Config.get('REPLY_LATE_AFTER_DAYS', 20)) redirect_to show_new_request_path(:url_title => @info_request.url_title) @@ -369,10 +383,10 @@ class RequestController < ApplicationController return end - @is_owning_user = @info_request.is_owning_user?(authenticated_user) + @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, @@ -399,7 +413,7 @@ class RequestController < ApplicationController # Make the state change old_described_state = @info_request.described_state @info_request.set_described_state(params[:incoming_message][:described_state]) - + # If you're not the *actual* requester owner. 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). @@ -408,24 +422,24 @@ class RequestController < ApplicationController # don't log if you were the requester XXX This is presumably so you # don't score for classifying your own requests. Could instead # always log and filter at display time. - @info_request.log_event("status_update", - { :user_id => authenticated_user.id, - :old_described_state => old_described_state, + @info_request.log_event("status_update", + { :user_id => authenticated_user.id, + :old_described_state => old_described_state, :described_state => @info_request.described_state, }) - + # Don't give advice on what to do next, as it isn't their request RequestMailer.deliver_old_unclassified_updated(@info_request) - if session[:request_game] + 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 + redirect_to play_url else flash[:notice] = _('Thank you for updating this request!') redirect_to request_url(@info_request) end return end - + # Display advice for requester on what to do next, as appropriate if @info_request.calculate_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> @@ -441,14 +455,14 @@ class RequestController < ApplicationController flash[:notice] = _("<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 + <li>If you would like to contest the authority's claim that they do not hold the information, here is <a href=\"{{complain_url}}\">how to complain</a>. </li> <li>We have <a href=\"{{other_means_url}}\">suggestions</a> on other means to answer your question. </li> - </ul>", - :find_authority_url => "/new", + </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) @@ -487,7 +501,7 @@ class RequestController < ApplicationController end end - # Used for links from polymorphic URLs e.g. in Atom feeds - just redirect to + # 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 @info_request_event = InfoRequestEvent.find(params[:info_request_event_id]) @@ -497,8 +511,8 @@ class RequestController < ApplicationController redirect_to outgoing_message_url(@info_request_event.outgoing_message), :status => :moved_permanently else # XXX maybe there are better URLs for some events than this - redirect_to request_url(@info_request_event.info_request), :status => :moved_permanently - end + redirect_to request_url(@info_request_event.info_request), :status => :moved_permanently + end end # Show an individual incoming message, and allow followup @@ -542,8 +556,8 @@ class RequestController < ApplicationController if params_outgoing_message.nil? params_outgoing_message = {} end - params_outgoing_message.merge!({ - :status => 'ready', + params_outgoing_message.merge!({ + :status => 'ready', :message_type => 'followup', :incoming_message_followup => @incoming_message, :info_request_id => @info_request.id @@ -567,7 +581,7 @@ class RequestController < ApplicationController render :template => 'request/hidden', :status => 410 # gone return end - + # Check address is good if !OutgoingMailer.is_followupable?(@info_request, @incoming_message) raise "unexpected followupable inconsistency" if @info_request.public_body.is_requestable? @@ -580,7 +594,7 @@ class RequestController < ApplicationController # to make sure they're the right user first, before they start writing a # message and wasting their time if they are not the requester. if !authenticated_as_user?(@info_request.user, - :web => @incoming_message.nil? ? + :web => @incoming_message.nil? ? _("To send a follow up message to ") + @info_request.public_body.name : _("To reply to ") + @info_request.public_body.name, :email => @incoming_message.nil? ? @@ -645,6 +659,25 @@ class RequestController < ApplicationController 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 @@ -678,7 +711,7 @@ class RequestController < ApplicationController # 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) + @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' @@ -706,7 +739,7 @@ class RequestController < ApplicationController html.sub!("<prefix-here>", view_html_prefix) html.sub!("<attachment-url-here>", CGI.escape(@attachment_url)) - @incoming_message.html_mask_stuff!(html) + @incoming_message.html_mask_stuff!(html) response.content_type = 'text/html' render :text => html end @@ -731,7 +764,7 @@ class RequestController < ApplicationController else @original_filename = @filename end - + # 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) @@ -748,7 +781,7 @@ class RequestController < ApplicationController # FOI officers can upload a response def upload_response @locale = self.locale_from_params() - PublicBody.with_locale(@locale) do + PublicBody.with_locale(@locale) do @info_request = InfoRequest.find_by_url_title(params[:url_title]) @reason_params = { @@ -817,8 +850,8 @@ class RequestController < ApplicationController ) 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 = "/download/#{updated[0..1]}/#{updated}/#{params[:url_title]}.zip" - file_path = File.join(File.dirname(__FILE__), '../../cache/zips', @url_path) - if !File.exists?(file_path) + file_path = File.expand_path(File.join(File.dirname(__FILE__), '../../cache/zips', @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 = MySociety::Config.get("HTML_TO_PDF_COMMAND") @@ -837,7 +870,7 @@ class RequestController < ApplicationController logger.error("Could not convert info request #{info_request.id} to PDF with command '#{convert_command} #{url} #{tempfile.path}'") end tempfile.close - else + else logger.warn("No HTML -> PDF converter found at #{convert_command}") end if !done @@ -849,7 +882,7 @@ class RequestController < ApplicationController f.puts(output) } end - for message in info_request.incoming_messages + 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}" diff --git a/app/controllers/request_game_controller.rb b/app/controllers/request_game_controller.rb index 8a84575bb..904c44759 100644 --- a/app/controllers/request_game_controller.rb +++ b/app/controllers/request_game_controller.rb @@ -7,7 +7,7 @@ # $Id: request_game_controller.rb,v 1.9 2009-10-19 22:06:54 francis Exp $ class RequestGameController < ApplicationController - + def play session[:request_game] = Time.now @@ -20,7 +20,7 @@ class RequestGameController < ApplicationController @requests = old.sort_by{ rand }.slice(0..2) 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>', + 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>', :helpus_url => help_credits_path+"#helpus", :site_name => site_name) end @@ -38,7 +38,7 @@ class RequestGameController < ApplicationController url_title = params[:url_title] if !authenticated?( :web => _("To play the request categorisation game"), - :email => _("Then you can play the request categorisation game."), + :email => _("Then you can play the request categorisation game."), :email_subject => _("Play the request categorisation game") ) # do nothing - as "authenticated?" has done the redirect to signin page for us diff --git a/app/controllers/services_controller.rb b/app/controllers/services_controller.rb index 225790d71..00c0e61bd 100644 --- a/app/controllers/services_controller.rb +++ b/app/controllers/services_controller.rb @@ -9,16 +9,30 @@ class ServicesController < ApplicationController if country_from_ip.downcase != iso_country_code found_country = WorldFOIWebsites.by_code(country_from_ip) found_country_name = !found_country.nil? && found_country[:country_name] + old_locale = FastGettext.locale + FastGettext.locale = FastGettext.best_locale_in(request.env['HTTP_ACCEPT_LANGUAGE']) if found_country_name text = _("Hello! You can make Freedom of Information requests within {{country_name}} at {{link_to_website}}", :country_name => found_country_name, :link_to_website => "<a href=\"#{found_country[:url]}\">#{found_country[:name]}</a>") 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) end + FastGettext.locale = old_locale end if !text.empty? text += ' <span class="close-button">X</span>' end render :text => text, :content_type => "text/plain" # XXX workaround the HTML validation in test suite end + def hidden_user_explanation + info_request = InfoRequest.find(params[:info_request_id]) + render :template => "admin_request/hidden_user_explanation", + :content_type => "text/plain", + :layout => false, + :locals => {:name_to => info_request.user.name, + :name_from => MySociety::Config.get("CONTACT_NAME", 'Alaveteli'), + :info_request => info_request, :reason => params[:reason], + :info_request_url => 'http://' + MySociety::Config.get('DOMAIN') + request_url(info_request), + :site_name => site_name} + end end diff --git a/app/controllers/track_controller.rb b/app/controllers/track_controller.rb index d858ab233..07e807451 100644 --- a/app/controllers/track_controller.rb +++ b/app/controllers/track_controller.rb @@ -50,11 +50,15 @@ class TrackController < ApplicationController raise ActiveRecord::RecordNotFound.new("None found") if @public_body.nil? # If found by historic name, or alternate locale name, redirect to new name if @public_body.url_name != params[:url_name] - redirect_to track_public_body_url(:url_name => @public_body.url_name, :feed => params[:feed]) + redirect_to track_public_body_url(:url_name => @public_body.url_name, :feed => params[:feed], :event_type => params[:event_type]) return end - @track_thing = TrackThing.create_track_for_public_body(@public_body) + if params[:event_type] + @track_thing = TrackThing.create_track_for_public_body(@public_body, params[:event_type]) + else + @track_thing = TrackThing.create_track_for_public_body(@public_body) + end return atom_feed_internal if params[:feed] == 'feed' @@ -94,7 +98,23 @@ class TrackController < ApplicationController return atom_feed_internal if params[:feed] == 'feed' if self.track_set - redirect_to search_url(@query) + if @query.scan("variety").length == 1 + # we're making a track for a simple filter, for which + # there's an expression in the UI (rather than relying + # on index:value strings in the query) + if @query =~ /variety:user/ + postfix = "users" + @query.sub!("variety:user", "") + elsif @query =~ /variety:authority/ + postfix = "bodies" + @query.sub!("variety:authority", "") + elsif @query =~ /variety:sent/ + postfix = "requests" + @query.sub!("variety:sent", "") + end + @query.strip! + end + redirect_to search_url([@query, postfix]) end end @@ -103,7 +123,7 @@ class TrackController < ApplicationController if @user @existing_track = TrackThing.find_by_existing_track(@user, @track_thing) if @existing_track - flash[:notice] = _("You are already being emailed updates about ") + @track_thing.params[:list_description] + flash[:notice] = _("You are already following updates about {{track_description}}", :track_description => @track_thing.params[:list_description]) return true end end @@ -115,8 +135,11 @@ class TrackController < ApplicationController @track_thing.track_medium = 'email_daily' @track_thing.tracking_user_id = @user.id @track_thing.save! - - flash[:notice] = _("You will now be emailed updates about ") + @track_thing.params[:list_description] + if @user.receive_email_alerts + flash[:notice] = _('You will now be emailed updates about {{track_description}}. <a href="{{change_email_alerts_url}}">Prefer not to receive emails?</a>', :track_description => @track_thing.params[:list_description], :change_email_alerts_url => url_for(:controller => "user", :action => "wall", :url_name => @user.url_name)) + else + flash[:notice] = _('You are now <a href="{{wall_url_user}}">following</a> updates about {{track_description}}', :track_description => @track_thing.params[:list_description], :wall_url_user => url_for(:controller => "user", :action => "wall", :url_name => @user.url_name)) + end return true end @@ -159,7 +182,7 @@ class TrackController < ApplicationController new_medium = params[:track_medium] if new_medium == 'delete' track_thing.destroy - flash[:notice] = _("You will no longer be emailed updates about ") + track_thing.params[:list_description] + flash[:notice] = _("You are no longer following {{track_description}}", :track_description => track_thing.params[:list_description]) redirect_to params[:r] # Reuse code like this if we let medium change again. #elsif new_medium == 'email_daily' diff --git a/app/controllers/user_controller.rb b/app/controllers/user_controller.rb index 403cb9684..e56c4dd33 100644 --- a/app/controllers/user_controller.rb +++ b/app/controllers/user_controller.rb @@ -6,10 +6,12 @@ # # $Id: user_controller.rb,v 1.71 2009-09-17 07:51:47 francis Exp $ +require 'set' + class UserController < ApplicationController layout :select_layout - + protect_from_forgery :only => [ :contact, :set_profile_photo, :signchangeemail, @@ -33,7 +35,7 @@ class UserController < ApplicationController @show_profile = false @show_requests = true end - + @display_user = User.find(:first, :conditions => [ "url_name = ? and email_confirmed = ?", params[:url_name], true ]) if not @display_user raise ActiveRecord::RecordNotFound.new("user not found, url_name=" + params[:url_name]) @@ -55,7 +57,7 @@ class UserController < ApplicationController end @xapian_requests = perform_search([InfoRequestEvent], requests_query, 'newest', 'request_collapse') @xapian_comments = perform_search([InfoRequestEvent], comments_query, 'newest', nil) - + if (@page > 1) @page_desc = " (page " + @page.to_s + ")" else @@ -89,6 +91,50 @@ class UserController < ApplicationController end + # Show the user's wall + def wall + long_cache + @display_user = User.find(:first, :conditions => [ "url_name = ? and email_confirmed = ?", params[:url_name], true ]) + if not @display_user + raise ActiveRecord::RecordNotFound.new("user not found, url_name=" + params[:url_name]) + end + @is_you = !@user.nil? && @user.id == @display_user.id + feed_results = Set.new + # Use search query for this so can collapse and paginate easily + # XXX really should just use SQL query here rather than Xapian. + begin + requests_query = 'requested_by:' + @display_user.url_name + comments_query = 'commented_by:' + @display_user.url_name + # XXX combine these as OR query + @xapian_requests = perform_search([InfoRequestEvent], requests_query, 'newest', 'request_collapse') + @xapian_comments = perform_search([InfoRequestEvent], comments_query, 'newest', nil) + rescue + @xapian_requests = nil + @xapian_comments = nil + end + + feed_results += @xapian_requests.results.map {|x| x[:model]} if !@xapian_requests.nil? + feed_results += @xapian_comments.results.map {|x| x[:model]} if !@xapian_comments.nil? + + # All tracks for the user + if @is_you + @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) + feed_results += xapian_object.results.map {|x| x[:model]} + end + end + + @feed_results = Array(feed_results).sort {|x,y| y.created_at <=> x.created_at}.first(20) + + respond_to do |format| + format.html { @has_json = true } + format.json { render :json => @display_user.json_for_api } + end + + end + # Login form def signin work_out_post_redirect @@ -129,7 +175,7 @@ class UserController < ApplicationController session[:user_id] = @user_signin.id session[:user_circumstance] = nil session[:remember_me] = params[:remember_me] ? true : false - + if is_modal_dialog render :action => 'signin_successful' else @@ -182,7 +228,7 @@ class UserController < ApplicationController return end - if !User.stay_logged_in_on_redirect?(@user) + if !User.stay_logged_in_on_redirect?(@user) || post_redirect.circumstance == "login_as" @user = post_redirect.user @user.email_confirmed = true @user.save! @@ -319,7 +365,7 @@ class UserController < ApplicationController if (not session[:user_circumstance]) or (session[:user_circumstance] != "change_email") # don't store the password in the db params[:signchangeemail].delete(:password) - post_redirect = PostRedirect.new(:uri => signchangeemail_url(), + post_redirect = PostRedirect.new(:uri => signchangeemail_url(), :post_params => params, :circumstance => "change_email" # special login that lets you change your email ) @@ -533,17 +579,29 @@ class UserController < ApplicationController end end + # Change about me text on your profile page + def set_receive_email_alerts + if authenticated_user.nil? + flash[:error] = _("You need to be logged in to edit your profile.") + redirect_to frontpage_url + return + end + @user.receive_email_alerts = params[:receive_email_alerts] + @user.save! + redirect_to params[:came_from] + end + private def is_modal_dialog (params[:modal].to_i != 0) end - + # when logging in through a modal iframe, don't display chrome around the content def select_layout is_modal_dialog ? 'no_chrome' : 'default' end - + # Decide where we are going to redirect back to after signin/signup, and record that def work_out_post_redirect # Redirect to front page later if nothing else specified diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index a0f16dfaf..cb6615199 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -17,6 +17,9 @@ module ApplicationHelper # Site-wide access to configuration settings include ConfigHelper + # Useful for sending emails + include MailerHelper + # Copied from error_messages_for in active_record_helper.rb def foi_error_messages_for(*params) options = params.last.is_a?(Hash) ? params.pop.symbolize_keys : {} @@ -32,14 +35,14 @@ module ApplicationHelper html[key] = 'errorExplanation' end end - + error_messages = [] for object in objects object.errors.each do |attr, message| error_messages << content_tag(:li, message) end end - + content_tag(:div, content_tag(:ul, error_messages), html @@ -48,7 +51,7 @@ module ApplicationHelper '' end end - + # Highlight words, also escapes HTML (other than spans that we add) def highlight_words(t, words, html = true) if html @@ -70,10 +73,10 @@ module ApplicationHelper t = highlight_words(t, words, html) return t end - + def locale_name(locale) return LanguageNames::get_language_name(locale) - end + end # Use our own algorithm for finding path of cache def foi_cache(name = {}, options = nil, &block) @@ -100,11 +103,11 @@ module ApplicationHelper def sanitized_object_name(object_name) object_name.gsub(/\]\[|[^-a-zA-Z0-9:.]/,"_").sub(/_$/,"") end - + def sanitized_method_name(method_name) method_name.sub(/\?$/, "") end - + def form_tag_id(object_name, method_name, locale=nil) if locale.nil? return "#{sanitized_object_name(object_name.to_s)}_#{sanitized_method_name(method_name.to_s)}" @@ -113,5 +116,21 @@ module ApplicationHelper end end + def admin_value(v) + if v.nil? + nil + elsif v.instance_of?(Time) + admin_date(v) + else + h(v) + end + end + + def admin_date(date) + ago_text = _('{{length_of_time}} ago', :length_of_time => time_ago_in_words(date)) + exact_date = I18n.l(date, :format => "%e %B %Y %H:%M:%S") + return "#{exact_date} (#{ago_text})" + end + end diff --git a/app/helpers/config_helper.rb b/app/helpers/config_helper.rb index b0381a2f5..543b60256 100644 --- a/app/helpers/config_helper.rb +++ b/app/helpers/config_helper.rb @@ -2,7 +2,7 @@ module ConfigHelper def site_name MySociety::Config.get('SITE_NAME', 'Alaveteli') end - + def force_registration_on_new_request MySociety::Config.get('FORCE_REGISTRATION_ON_NEW_REQUEST', false) end diff --git a/app/helpers/link_to_helper.rb b/app/helpers/link_to_helper.rb index 56c33e512..f621721b6 100755 --- a/app/helpers/link_to_helper.rb +++ b/app/helpers/link_to_helper.rb @@ -1,6 +1,6 @@ # app/helpers/link_to_helper.rb: # This module is included into all controllers via controllers/application.rb -# - +# - # # Copyright (c) 2007 UK Citizens Online Democracy. All rights reserved. # Email: francis@mysociety.org; WWW: http://www.mysociety.org/ @@ -10,25 +10,29 @@ module LinkToHelper # Links to various models - + # Requests def request_url(info_request, extra_params={}) params = {:url_title => info_request.url_title, :only_path => true} return show_request_url(params.merge(extra_params)) end - - def request_link(info_request) - link_to h(info_request.title), request_url(info_request) + + def request_link(info_request, cls=nil ) + link_to h(info_request.title), request_url(info_request), :class => cls end - + def request_admin_url(info_request) return admin_url('request/show/' + info_request.id.to_s) end - + + def request_admin_link(info_request, name="admin", cls=nil) + link_to name, request_admin_url(info_request), :class => cls + end + def request_both_links(info_request) link_to(h(info_request.title), main_url(request_url(info_request))) + " (" + link_to("admin", request_admin_url(info_request)) + ")" end - + def request_similar_url(info_request) return similar_request_url(:url_title => info_request.url_title, :only_path => true) end @@ -58,7 +62,7 @@ module LinkToHelper end return respond_url end - + # Public bodies def public_body_url(public_body) public_body.url_name.nil? ? '' : show_public_body_url(:url_name => public_body.url_name, :only_path => true) @@ -66,8 +70,8 @@ module LinkToHelper def public_body_link_short(public_body) link_to h(public_body.short_or_long_name), public_body_url(public_body) end - def public_body_link(public_body) - link_to h(public_body.name), public_body_url(public_body) + def public_body_link(public_body, cls=nil) + link_to h(public_body.name), public_body_url(public_body), :class => cls end def public_body_link_absolute(public_body) # e.g. for in RSS link_to h(public_body.name), main_url(public_body_url(public_body)) @@ -79,15 +83,15 @@ module LinkToHelper link_to(h(public_body.name), main_url(public_body_url(public_body))) + " (" + link_to("admin", public_body_admin_url(public_body)) + ")" end def list_public_bodies_default - list_public_bodies_url(:tag => 'all') + list_public_bodies_url(:tag => 'all') end # Users def user_url(user) return show_user_url(:url_name => user.url_name, :only_path => true) end - def user_link(user) - link_to h(user.name), user_url(user) + def user_link(user, cls=nil) + link_to h(user.name), user_url(user), :class => cls end def user_link_absolute(user) link_to h(user.name), main_url(user_url(user)) @@ -112,6 +116,9 @@ module LinkToHelper def user_admin_url(user) return admin_url('user/show/' + user.id.to_s) end + def user_admin_link(user, name="admin", cls=nil) + link_to name, user_admin_url(user), :class => cls + end def user_both_links(user) link_to(h(user.name), main_url(user_url(user))) + " (" + link_to("admin", user_admin_url(user)) + ")" end @@ -120,15 +127,15 @@ module LinkToHelper def do_track_url(track_thing, feed = 'track') if track_thing.track_type == 'request_updates' track_request_url(:url_title => track_thing.info_request.url_title, :feed => feed) - elsif track_thing.track_type == 'all_new_requests' + elsif track_thing.track_type == 'all_new_requests' track_list_url(:view => 'recent', :feed => feed) - elsif track_thing.track_type == 'all_successful_requests' + elsif track_thing.track_type == 'all_successful_requests' track_list_url(:view => 'successful', :feed => feed) - elsif track_thing.track_type == 'public_body_updates' + elsif track_thing.track_type == 'public_body_updates' track_public_body_url(:url_name => track_thing.public_body.url_name, :feed => feed) - elsif track_thing.track_type == 'user_updates' + elsif track_thing.track_type == 'user_updates' track_user_url(:url_name => track_thing.tracked_user.url_name, :feed => feed) - elsif track_thing.track_type == 'search_query' + elsif track_thing.track_type == 'search_query' track_search_url(:query_array => track_thing.track_query, :feed => feed) else raise "unknown tracking type " + track_thing.track_type @@ -141,7 +148,7 @@ module LinkToHelper query = query - ["", nil] query = query.join("/") end - routing_info = {:controller => 'general', + routing_info = {:controller => 'general', :action => 'search', :combined => query, :view => nil} @@ -204,7 +211,9 @@ module LinkToHelper # Basic date format def simple_date(date) - return I18n.l(date, :format => "%e %B %Y") + date_format = _("simple_date_format") + date_format = :long if date_format == "simple_date_format" + return I18n.l(date.to_date, :format => date_format) end def simple_time(date) diff --git a/app/helpers/mailer_helper.rb b/app/helpers/mailer_helper.rb new file mode 100644 index 000000000..c0a950d47 --- /dev/null +++ b/app/helpers/mailer_helper.rb @@ -0,0 +1,7 @@ +module MailerHelper + def contact_from_name_and_email + contact_name = MySociety::Config.get("CONTACT_NAME", 'Alaveteli') + contact_email = MySociety::Config.get("CONTACT_EMAIL", 'contact@localhost') + return "#{contact_name} <#{contact_email}>" + end +end diff --git a/app/models/about_me_validator.rb b/app/models/about_me_validator.rb index e24c5512c..67b81bc9c 100644 --- a/app/models/about_me_validator.rb +++ b/app/models/about_me_validator.rb @@ -1,5 +1,5 @@ # == Schema Information -# Schema version: 95 +# Schema version: 114 # # Table name: about_me_validators # diff --git a/app/models/application_mailer.rb b/app/models/application_mailer.rb index e9f82a2c3..044006f7c 100644 --- a/app/models/application_mailer.rb +++ b/app/models/application_mailer.rb @@ -9,17 +9,12 @@ class ApplicationMailer < ActionMailer::Base # Include all the functions views get, as emails call similar things. helper :application + include MailerHelper # This really should be the default - otherwise you lose any information # about the errors, and have to do error checking on return codes. self.raise_delivery_errors = true - def contact_from_name_and_email - contact_name = MySociety::Config.get("CONTACT_NAME", 'Alaveteli') - contact_email = MySociety::Config.get("CONTACT_EMAIL", 'contact@localhost') - return "#{contact_name} <#{contact_email}>" - end - def blackhole_email MySociety::Config.get("BLACKHOLE_PREFIX", 'do-not-reply-to-this-address')+"@"+MySociety::Config.get("INCOMING_EMAIL_DOMAIN", "localhost") end @@ -28,7 +23,7 @@ class ApplicationMailer < ActionMailer::Base # views (for links) and mailers (for use in emails), so include them into # all of all. include LinkToHelper - + # Site-wide access to configuration settings include ConfigHelper end diff --git a/app/models/censor_rule.rb b/app/models/censor_rule.rb index 201e60746..a477d2568 100644 --- a/app/models/censor_rule.rb +++ b/app/models/censor_rule.rb @@ -1,5 +1,5 @@ # == Schema Information -# Schema version: 108 +# Schema version: 114 # # Table name: censor_rules # @@ -51,7 +51,10 @@ class CensorRule < ActiveRecord::Base errors.add("Censor must apply to an info request a user or a body; ") end end -end - - + def for_admin_column + self.class.content_columns.each do |column| + yield(column.human_name, self.send(column.name), column.type.to_s, column.name) + end + end +end diff --git a/app/models/change_email_validator.rb b/app/models/change_email_validator.rb index e3f8fa892..0395ab6d5 100644 --- a/app/models/change_email_validator.rb +++ b/app/models/change_email_validator.rb @@ -1,5 +1,5 @@ # == Schema Information -# Schema version: 108 +# Schema version: 114 # # Table name: change_email_validators # @@ -30,7 +30,7 @@ class ChangeEmailValidator < ActiveRecord::BaseWithoutTable validates_presence_of :old_email, :message => N_("Please enter your old email address") validates_presence_of :new_email, :message => N_("Please enter your new email address") validates_presence_of :password, :message => N_("Please enter your password"), :unless => :changing_email - + def changing_email() self.user_circumstance == 'change_email' end diff --git a/app/models/comment.rb b/app/models/comment.rb index 44a1079cd..6edfaa24f 100644 --- a/app/models/comment.rb +++ b/app/models/comment.rb @@ -1,5 +1,5 @@ # == Schema Information -# Schema version: 108 +# Schema version: 114 # # Table name: comments # @@ -84,7 +84,9 @@ class Comment < ActiveRecord::Base return Comment.find(:first, :conditions => [ "info_request_id = ? and body = ?", info_request_id, body ]) end end - + def for_admin_column + self.class.content_columns.each do |column| + yield(column.human_name, self.send(column.name), column.type.to_s, column.name) + end + end end - - diff --git a/app/models/contact_mailer.rb b/app/models/contact_mailer.rb index 0390fc347..6e781d48c 100644 --- a/app/models/contact_mailer.rb +++ b/app/models/contact_mailer.rb @@ -25,7 +25,7 @@ class ContactMailer < ApplicationMailer # they shouldn't, and this might help. (Have had mysterious cases of a # reply coming in duplicate from a public body to both From and envelope # from) - + # Send message to another user def user_message(from_user, recipient_user, from_user_url, subject, message) @from = from_user.name_and_email @@ -34,7 +34,7 @@ class ContactMailer < ApplicationMailer headers 'Return-Path' => blackhole_email, 'Reply-To' => @from @recipients = recipient_user.name_and_email @subject = subject - @body = { + @body = { :message => message, :from_user => from_user, :recipient_user => recipient_user, @@ -42,4 +42,17 @@ class ContactMailer < ApplicationMailer } end + # Send message to a user from the administrator + def from_admin_message(recipient_user, subject, message) + @from = contact_from_name_and_email + @recipients = recipient_user.name_and_email + @subject = subject + @body = { + :message => message, + :from_user => @from, + :recipient_user => recipient_user, + } + bcc MySociety::Config::get("CONTACT_EMAIL") + end + end diff --git a/app/models/contact_validator.rb b/app/models/contact_validator.rb index 0bc562835..a9748a739 100644 --- a/app/models/contact_validator.rb +++ b/app/models/contact_validator.rb @@ -1,5 +1,5 @@ # == Schema Information -# Schema version: 95 +# Schema version: 114 # # Table name: contact_validators # diff --git a/app/models/exim_log.rb b/app/models/exim_log.rb index 77e5e2d21..60faa7f0b 100644 --- a/app/models/exim_log.rb +++ b/app/models/exim_log.rb @@ -1,5 +1,5 @@ # == Schema Information -# Schema version: 108 +# Schema version: 114 # # Table name: exim_logs # @@ -40,7 +40,7 @@ class EximLog < ActiveRecord::Base ActiveRecord::Base.transaction do # see if we already have it - done = EximLogDone.find_by_filename(file_name_db) + done = EximLogDone.find_by_filename(file_name_db) if !done.nil? if modified.utc == done.last_stat.utc # already have that, nothing to do @@ -124,7 +124,7 @@ class EximLog < ActiveRecord::Base return ok end - + end diff --git a/app/models/exim_log_done.rb b/app/models/exim_log_done.rb index b8a39033a..3cedc1379 100644 --- a/app/models/exim_log_done.rb +++ b/app/models/exim_log_done.rb @@ -1,5 +1,5 @@ # == Schema Information -# Schema version: 95 +# Schema version: 114 # # Table name: exim_log_dones # diff --git a/app/models/foi_attachment.rb b/app/models/foi_attachment.rb index da92d1c2d..9bbf0988f 100644 --- a/app/models/foi_attachment.rb +++ b/app/models/foi_attachment.rb @@ -1,5 +1,5 @@ # == Schema Information -# Schema version: 108 +# Schema version: 114 # # Table name: foi_attachments # @@ -42,7 +42,7 @@ class FoiAttachment < ActiveRecord::Base if rails_env.nil? || rails_env.empty? raise "$RAILS_ENV is not set" end - base_dir = File.join(File.dirname(__FILE__), "../../cache", "attachments_#{rails_env}") + base_dir = File.expand_path(File.join(File.dirname(__FILE__), "../../cache", "attachments_#{rails_env}")) return File.join(base_dir, self.hexdigest[0..2]) end @@ -177,7 +177,7 @@ class FoiAttachment < ActiveRecord::Base filename = filename.gsub(/\//, "-") return filename - end + end # XXX changing this will break existing URLs, so have a care - maybe # make another old_display_filename see above @@ -248,16 +248,16 @@ class FoiAttachment < ActiveRecord::Base return !! { "application/pdf" => true, # .pdf "image/tiff" => true, # .tiff - + "application/vnd.ms-word" => true, # .doc "application/vnd.openxmlformats-officedocument.wordprocessingml.document" => true, # .docx - + "application/vnd.ms-powerpoint" => true, # .ppt "application/vnd.openxmlformats-officedocument.presentationml.presentation" => true, # .pptx - + "application/vnd.ms-excel" => true, # .xls "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" => true, # .xlsx - + } [self.content_type] end @@ -277,16 +277,16 @@ class FoiAttachment < ActiveRecord::Base return { "text/plain" => "Text file", 'application/rtf' => "RTF file", - + 'application/pdf' => "PDF file", 'image/tiff' => "TIFF image", - + 'application/vnd.ms-word' => "Word document", 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => "Word document", - + 'application/vnd.ms-powerpoint' => "PowerPoint presentation", 'application/vnd.openxmlformats-officedocument.presentationml.presentation' => "PowerPoint presentation", - + 'application/vnd.ms-excel' => "Excel spreadsheet", 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => "Excel spreadsheet", }[self.content_type] @@ -345,7 +345,7 @@ class FoiAttachment < ActiveRecord::Base if self.has_google_docs_viewer? wrapper_id = "wrapper_google_embed" ret = ret + "<iframe src='http://docs.google.com/viewer?url=<attachment-url-here>&embedded=true' width='100%' height='100%' style='border: none;'></iframe>"; - else + else ret = ret + "<p>Sorry, we were unable to convert this file to HTML. Please use the download link at the top right.</p>" end ret = ret + "</body></html>" diff --git a/app/models/holiday.rb b/app/models/holiday.rb index 60b5ff443..debd88dec 100644 --- a/app/models/holiday.rb +++ b/app/models/holiday.rb @@ -1,5 +1,5 @@ # == Schema Information -# Schema version: 108 +# Schema version: 114 # # Table name: holidays # @@ -10,7 +10,7 @@ # models/holiday.rb: # -# Store details on, and perform calculations with, public holidays on which +# Store details on, and perform calculations with, public holidays on which # the clock for answering FOI requests does not run: # # ... "working day" means any day other than a Saturday, a Sunday, Christmas @@ -37,7 +37,7 @@ class Holiday < ActiveRecord::Base # Count forward (20) working days. We start with today as "day zero". The # first of the twenty full working days is the next day. We return the # date of the last of the twenty. - + # This response for example of a public authority complains that we had # it wrong. We didn't (even thought I changed the code for a while, # it's changed back now). A day is a day, our lawyer tells us. diff --git a/app/models/incoming_message.rb b/app/models/incoming_message.rb index cbbcf5aa6..3419956d6 100644 --- a/app/models/incoming_message.rb +++ b/app/models/incoming_message.rb @@ -1,5 +1,5 @@ # == Schema Information -# Schema version: 108 +# Schema version: 114 # # Table name: incoming_messages # @@ -11,12 +11,12 @@ # cached_attachment_text_clipped :text # cached_main_body_text_folded :text # cached_main_body_text_unfolded :text -# sent_at :time # subject :text # mail_from_domain :text # valid_to_reply_to :boolean # last_parsed :datetime # mail_from :text +# sent_at :datetime # # encoding: UTF-8 @@ -81,7 +81,7 @@ class IncomingMessage < ActiveRecord::Base # http://www.whatdotheyknow.com/request/reviews_of_unduly_lenient_senten#incoming-4830 # Report of TMail bug: # http://rubyforge.org/tracker/index.php?func=detail&aid=21810&group_id=4512&atid=17370 - copy_of_raw_data = self.raw_email.data.gsub(/; boundary=\s+"/ims,'; boundary="') + copy_of_raw_data = self.raw_email.data.gsub(/; boundary=\s+"/ims,'; boundary="') @mail = TMail::Mail.parse(copy_of_raw_data) @mail.base64_decode @@ -92,7 +92,7 @@ class IncomingMessage < ActiveRecord::Base # Returns the name of the person the incoming message is from, or nil if # there isn't one or if there is only an email address. XXX can probably # remove from_name_if_present (which is a monkey patch) by just calling - # .from_addrs[0].name here instead? + # .from_addrs[0].name here instead? # Return false if for some reason this is a message that we shouldn't let them reply to def _calculate_valid_to_reply_to @@ -178,7 +178,7 @@ class IncomingMessage < ActiveRecord::Base def safe_mail_from if !self.mail_from.nil? mail_from = self.mail_from.dup - self.info_request.apply_censor_rules_to_text!(mail_from) + self.info_request.apply_censor_rules_to_text!(mail_from) return mail_from end end @@ -191,7 +191,7 @@ class IncomingMessage < ActiveRecord::Base # XXX This fills in part.rfc822_attachment and part.url_part_number within # all the parts of the email (see TMail monkeypatch above for how these # attributes are added). ensure_parts_counted must be called before using - # the attributes. + # the attributes. def ensure_parts_counted @count_parts_count = 0 _count_parts_recursive(self.mail) @@ -215,7 +215,7 @@ class IncomingMessage < ActiveRecord::Base # e.g. http://www.whatdotheyknow.com/request/chinese_names_for_british_politi msg = Mapi::Msg.open(StringIO.new(part.body)) part.rfc822_attachment = TMail::Mail.parse(msg.to_mime.to_s) - elsif part.content_type == 'application/ms-tnef' + elsif part.content_type == 'application/ms-tnef' # A set of attachments in a TNEF file part.rfc822_attachment = TNEF.as_tmail(part.body) end @@ -250,10 +250,10 @@ class IncomingMessage < ActiveRecord::Base # if they are public anyway. For now just be precautionary and only # put in descriptions of them in square brackets. if self.info_request.public_body.is_followupable? - text.gsub!(self.info_request.public_body.request_email, "[" + self.info_request.public_body.short_or_long_name + " request email]") + text.gsub!(self.info_request.public_body.request_email, _("[{{public_body}} request email]", :public_body => self.info_request.public_body.short_or_long_name)) end - text.gsub!(self.info_request.incoming_email, "[FOI #" + self.info_request.id.to_s + " email]") - text.gsub!(MySociety::Config.get("CONTACT_EMAIL", 'contact@localhost'), "[#{MySociety::Config.get('SITE_NAME', 'Alaveteli')} contact email]") + text.gsub!(self.info_request.incoming_email, _('[FOI #{{request}} email]', :request => self.info_request.id.to_s) ) + text.gsub!(MySociety::Config.get("CONTACT_EMAIL", 'contact@localhost'), _("[{{site_name}} contact email]", :site_name => MySociety::Config.get('SITE_NAME', 'Alaveteli')) ) end # Replaces all email addresses in (possibly binary data) with equal length alternative ones. @@ -289,7 +289,7 @@ class IncomingMessage < ActiveRecord::Base # buggy versions of pdftk sometimes fail on # compression, I don't see it's a disaster in # these cases to save an uncompressed version? - recompressed_text = censored_uncompressed_text + recompressed_text = censored_uncompressed_text logger.warn "Unable to compress PDF; problem with your pdftk version?" end if !recompressed_text.nil? && !recompressed_text.empty? @@ -297,10 +297,10 @@ class IncomingMessage < ActiveRecord::Base end end end - return + return end - self._binary_mask_stuff_internal!(text) + self._binary_mask_stuff_internal!(text) end # Used by binary_mask_stuff - replace text in place @@ -309,7 +309,7 @@ class IncomingMessage < ActiveRecord::Base orig_size = text.size # Replace ASCII email addresses... - text.gsub!(MySociety::Validate.email_find_regexp) do |email| + text.gsub!(MySociety::Validate.email_find_regexp) do |email| email.gsub(/[^@.]/, 'x') end @@ -320,7 +320,7 @@ class IncomingMessage < ActiveRecord::Base emails = ascii_chars.scan(MySociety::Validate.email_find_regexp) # Convert back to UCS-2, making a mask at the same time emails.map! {|email| [ - Iconv.conv('ucs-2le', 'ascii', email[0]), + Iconv.conv('ucs-2le', 'ascii', email[0]), Iconv.conv('ucs-2le', 'ascii', email[0].gsub(/[^@.]/, 'x')) ] } # Now search and replace the UCS-2 email with the UCS-2 mask @@ -416,7 +416,7 @@ class IncomingMessage < ActiveRecord::Base # http://www.whatdotheyknow.com/request/secured_convictions_aided_by_cct multiline_original_message = '(' + '''>>>.* \d\d/\d\d/\d\d\d\d\s+\d\d:\d\d(?::\d\d)?\s*>>>''' + ')' text.gsub!(/^(#{multiline_original_message}\n.*)$/ms, replacement) - + # Single line sections text.gsub!(/^(>.*\n)/, replacement) text.gsub!(/^(On .+ (wrote|said):\n)/, replacement) @@ -453,8 +453,8 @@ class IncomingMessage < ActiveRecord::Base # http://www.whatdotheyknow.com/request/123/response/192 # http://www.whatdotheyknow.com/request/235/response/513 # http://www.whatdotheyknow.com/request/445/response/743 - original_message = - '(' + '''----* This is a copy of the message, including all the headers. ----*''' + + original_message = + '(' + '''----* This is a copy of the message, including all the headers. ----*''' + '|' + '''----*\s*Original Message\s*----*''' + '|' + '''----*\s*Forwarded message.+----*''' + '|' + '''----*\s*Forwarded by.+----*''' + @@ -482,7 +482,7 @@ class IncomingMessage < ActiveRecord::Base return part_file_name end - # (This risks losing info if the unchosen alternative is the only one to contain + # (This risks losing info if the unchosen alternative is the only one to contain # useful info, but let's worry about that another time) def get_attachment_leaves force = true @@ -538,7 +538,7 @@ class IncomingMessage < ActiveRecord::Base if calc_mime curr_mail.content_type = calc_mime end - end + end # Use standard content types for Word documents etc. curr_mail.content_type = normalise_content_type(curr_mail.content_type) @@ -660,11 +660,11 @@ class IncomingMessage < ActiveRecord::Base # Test if it's good UTF-8 text = Iconv.conv('utf-8', 'utf-8', text) rescue Iconv::IllegalSequence - # Text looks like unlabelled nonsense, + # Text looks like unlabelled nonsense, # strip out anything that isn't UTF-8 begin - text = Iconv.conv('utf-8//IGNORE', source_charset, text) + - _("\n\n[ {{site_name}} note: The above text was badly encoded, and has had strange characters removed. ]", + text = Iconv.conv('utf-8//IGNORE', source_charset, text) + + _("\n\n[ {{site_name}} note: The above text was badly encoded, and has had strange characters removed. ]", :site_name => MySociety::Config.get('SITE_NAME', 'Alaveteli')) rescue Iconv::InvalidEncoding, Iconv::IllegalSequence if source_charset != "utf-8" @@ -673,7 +673,6 @@ class IncomingMessage < ActiveRecord::Base end end end - # Fix DOS style linefeeds to Unix style ones (or other later regexps won't work) # Needed for e.g. http://www.whatdotheyknow.com/request/60/response/98 @@ -693,7 +692,7 @@ class IncomingMessage < ActiveRecord::Base # Find first part which is text/plain or text/html # (We have to include HTML, as increasingly there are mail clients that # include no text alternative for the main part, and we don't want to - # instead use the first text attachment + # instead use the first text attachment # e.g. http://www.whatdotheyknow.com/request/list_of_public_authorties) leaves.each do |p| if p.content_type == 'text/plain' or p.content_type == 'text/html' @@ -707,8 +706,8 @@ class IncomingMessage < ActiveRecord::Base return p end end - - # ... or if none, consider first part + + # ... or if none, consider first part p = leaves[0] # if it is a known type then don't use it, return no body (nil) if !p.nil? && AlaveteliFileTypes.mimetype_to_extension(p.content_type) @@ -752,7 +751,7 @@ class IncomingMessage < ActiveRecord::Base :display_size => "0K") attachment.save! attachments << attachment - end + end return attachments end @@ -802,7 +801,7 @@ class IncomingMessage < ActiveRecord::Base # XXX call _convert_part_body_to_text here, but need to get charset somehow # e.g. http://www.whatdotheyknow.com/request/1593/response/3088/attach/4/Freedom%20of%20Information%20request%20-%20car%20oval%20sticker:%20Article%2020,%20Convention%20on%20Road%20Traffic%201949.txt body = headers + "\n" + body - + # This is quick way of getting all headers, but instead we only add some a) to # make it more usable, b) as at least one authority accidentally leaked security # information into a header. @@ -816,7 +815,6 @@ class IncomingMessage < ActiveRecord::Base :filename => _get_part_file_name(leaf), :charset => leaf.charset, :within_rfc822_subject => within_rfc822_subject, - :display_size => "0K", :body => body) attachment.save! attachments << attachment.id @@ -837,7 +835,7 @@ class IncomingMessage < ActiveRecord::Base end # now get rid of any attachments we no longer have - FoiAttachment.destroy_all("id NOT IN (#{attachments.join(',')}) AND incoming_message_id = #{self.id}") + FoiAttachment.destroy_all("id NOT IN (#{attachments.join(',')}) AND incoming_message_id = #{self.id}") end # Returns body text as HTML with quotes flattened, and emails removed. @@ -866,10 +864,10 @@ class IncomingMessage < ActiveRecord::Base text = "[Subject only] " + CGI.escapeHTML(self.subject) + text end # and display link for quoted stuff - text = text.gsub(/FOLDED_QUOTED_SECTION/, "\n\n" + '<span class="unfold_link"><a href="?unfold=1#incoming-'+self.id.to_s+'">show quoted sections</a></span>' + "\n\n") + text = text.gsub(/FOLDED_QUOTED_SECTION/, "\n\n" + '<span class="unfold_link"><a href="?unfold=1#incoming-'+self.id.to_s+'">'+_("show quoted sections")+'</a></span>' + "\n\n") else if folded_quoted_text.include?('FOLDED_QUOTED_SECTION') - text = text + "\n\n" + '<span class="unfold_link"><a href="?#incoming-'+self.id.to_s+'">hide quoted sections</a></span>' + text = text + "\n\n" + '<span class="unfold_link"><a href="?#incoming-'+self.id.to_s+'">'+_("hide quoted sections")+'</a></span>' end end text.strip! @@ -899,13 +897,13 @@ class IncomingMessage < ActiveRecord::Base self.remove_privacy_sensitive_things!(text) # This can be useful for memory debugging #STDOUT.puts 'xxx '+ MySociety::DebugHelpers::allocated_string_size_around_gc - + # Save clipped version for snippets if self.cached_attachment_text_clipped.nil? self.cached_attachment_text_clipped = text[0..MAX_ATTACHMENT_TEXT_CLIPPED] self.save! end - + return text end # Returns a version reduced to a sensible maximum size - this @@ -988,7 +986,7 @@ class IncomingMessage < ActiveRecord::Base for entry in zip_file if entry.file? filename = entry.to_s - begin + begin body = entry.get_input_stream.read rescue # move to next attachment silently if there were problems @@ -1002,7 +1000,7 @@ class IncomingMessage < ActiveRecord::Base else content_type = 'application/octet-stream' end - + text += _get_attachment_text_internal_one_file(content_type, body) end end @@ -1030,8 +1028,6 @@ class IncomingMessage < ActiveRecord::Base return get_body_for_quoting + "\n\n" + get_attachment_text_clipped end - - # Has message arrived "recently"? def recently_arrived (Time.now - self.created_at) <= 3.days @@ -1052,7 +1048,7 @@ class IncomingMessage < ActiveRecord::Base end end - # Search all info requests for + # Search all info requests for def IncomingMessage.find_all_unknown_mime_types for incoming_message in IncomingMessage.find(:all) for attachment in incoming_message.get_attachments_for_display @@ -1118,7 +1114,7 @@ class IncomingMessage < ActiveRecord::Base content_type = 'application/vnd.ms-excel' end if content_type == 'application/mspowerpoint' or content_type == 'application/x-ms-powerpoint' - content_type = 'application/vnd.ms-powerpoint' + content_type = 'application/vnd.ms-powerpoint' end if content_type == 'application/msword' or content_type == 'application/x-ms-word' content_type = 'application/vnd.ms-word' @@ -1134,8 +1130,16 @@ class IncomingMessage < ActiveRecord::Base return content_type end - private :normalise_content_type + + def for_admin_column + self.class.content_columns.each do |column| + yield(column.human_name, self.send(column.name), column.type.to_s, column.name) + end + end + + private :normalise_content_type end + diff --git a/app/models/info_request.rb b/app/models/info_request.rb index b5a1cd833..45819bfe7 100644 --- a/app/models/info_request.rb +++ b/app/models/info_request.rb @@ -1,5 +1,5 @@ # == Schema Information -# Schema version: 108 +# Schema version: 114 # # Table name: info_requests # @@ -17,12 +17,16 @@ # allow_new_responses_from :string(255) default("anybody"), not null # handle_rejected_responses :string(255) default("bounce"), not null # idhash :string(255) not null +# attention_requested :boolean default(FALSE) # require 'digest/sha1' class InfoRequest < ActiveRecord::Base + include ActionView::Helpers::UrlHelper + include ActionController::UrlWriter + strip_attributes! validates_presence_of :title, :message => N_("Please enter a summary of your request") @@ -48,14 +52,14 @@ class InfoRequest < ActiveRecord::Base # user described state (also update in info_request_event, admin_request/edit.rhtml) validate :must_be_valid_state - validates_inclusion_of :prominence, :in => [ - 'normal', + validates_inclusion_of :prominence, :in => [ + 'normal', 'backpage', 'hidden', 'requester_only' ] - validates_inclusion_of :law_used, :in => [ + validates_inclusion_of :law_used, :in => [ 'foi', # Freedom of Information Act 'eir', # Environmental Information Regulations ] @@ -74,18 +78,21 @@ class InfoRequest < ActiveRecord::Base ] def self.enumerate_states - states = [ + states = [ 'waiting_response', - 'waiting_clarification', + 'waiting_clarification', 'gone_postal', 'not_held', 'rejected', # this is called 'refused' in UK FOI law and the user interface, but 'rejected' internally for historic reasons - 'successful', + 'successful', 'partially_successful', 'internal_review', 'error_message', 'requires_admin', - 'user_withdrawn' + 'user_withdrawn', + 'attention_requested', + 'vexatious', + 'not_foi' ] if @@custom_states_loaded states += InfoRequest.theme_extra_states @@ -94,7 +101,7 @@ class InfoRequest < ActiveRecord::Base end def must_be_valid_state - errors.add(:described_state, "is not a valid state") if + errors.add(:described_state, "is not a valid state") if !InfoRequest.enumerate_states.include? described_state end @@ -120,7 +127,7 @@ class InfoRequest < ActiveRecord::Base errors.add(:title, _('Please describe more what the request is about in the subject. There is no need to say it is an FOI request, we add that on anyway.')) end end - + OLD_AGE_IN_DAYS = 21.days def after_initialize @@ -166,7 +173,7 @@ class InfoRequest < ActiveRecord::Base end end # Force reindex when tag string changes - alias_method :orig_tag_string=, :tag_string= + alias_method :orig_tag_string=, :tag_string= def tag_string=(tag_string) ret = self.orig_tag_string=(tag_string) reindex_request_events @@ -219,7 +226,7 @@ public end # Email which public body should use to respond to request. This is in - # the format PREFIXrequest-ID-HASH@DOMAIN. Here ID is the id of the + # the format PREFIXrequest-ID-HASH@DOMAIN. Here ID is the id of the # FOI request, and HASH is a signature for that id. def incoming_email return self.magic_email("request-") @@ -251,7 +258,7 @@ public end end - # Two sorts of laws for requests, FOI or EIR + # Two sorts of laws for requests, FOI or EIR def law_used_full if self.law_used == 'foi' return _("Freedom of Information") @@ -306,7 +313,7 @@ public guesses = [] # 1. Try to guess based on the email address(es) addresses = - (incoming_message.mail.to || []) + + (incoming_message.mail.to || []) + (incoming_message.mail.cc || []) + (incoming_message.mail.envelope_to || []) addresses.uniq! @@ -453,7 +460,6 @@ public # An annotation (comment) is made def add_comment(body, user) comment = Comment.new - ActiveRecord::Base.transaction do comment.body = body comment.user = user @@ -501,7 +507,7 @@ public # states which require administrator action (hence email administrators # when they are entered, and offer state change dialog to them) def InfoRequest.requires_admin_states - return ['requires_admin', 'error_message'] + return ['requires_admin', 'error_message', 'attention_requested'] end def requires_admin? @@ -509,8 +515,11 @@ public return false end + def can_have_attention_requested? + end + # change status, including for last event for later historical purposes - def set_described_state(new_state) + def set_described_state(new_state, set_by = nil) ActiveRecord::Base.transaction do self.awaiting_description = false last_event = self.get_last_event @@ -523,7 +532,7 @@ public self.calculate_event_states if self.requires_admin? - RequestMailer.deliver_requires_admin(self) + RequestMailer.deliver_requires_admin(self, set_by) end end @@ -539,7 +548,7 @@ public self.base_calculate_status end end - + def base_calculate_status return 'waiting_classification' if self.awaiting_description return described_state unless self.described_state == "waiting_response" @@ -559,13 +568,13 @@ public curr_state = nil for event in self.info_request_events.reverse event.xapian_mark_needs_index # we need to reindex all events in order to update their latest_* terms - if curr_state.nil? + if curr_state.nil? if !event.described_state.nil? curr_state = event.described_state end end - if !curr_state.nil? && event.event_type == 'response' + if !curr_state.nil? && event.event_type == 'response' if event.calculated_state != curr_state event.calculated_state = curr_state event.last_described_at = Time.now() @@ -579,7 +588,7 @@ public elsif !curr_state.nil? && (event.event_type == 'followup_sent' || event.event_type == 'sent') && !event.described_state.nil? && (event.described_state == 'waiting_response' || event.described_state == 'internal_review') # Followups can set the status to waiting response / internal # review. Initial requests ('sent') set the status to waiting response. - + # We want to store that in calculated_state state so it gets # indexed. if event.calculated_state != event.described_state @@ -665,7 +674,11 @@ public return self.public_body.is_followupable? end def recipient_name_and_email - return TMail::Address.address_from_name_and_email(self.law_used_short + " requests at " + self.public_body.short_or_long_name, self.recipient_email).to_s + return TMail::Address.address_from_name_and_email( + _("{{law_used}} requests at {{public_body}}", + :law_used => self.law_used_short, + :public_body => self.public_body.short_or_long_name), + self.recipient_email).to_s end # History of some things that have happened @@ -723,8 +736,8 @@ public def index_of_last_described_event events = self.info_request_events events.each_index do |i| - revi = events.size - 1 - i - m = events[revi] + revi = events.size - 1 - i + m = events[revi] if not m.described_state.nil? return revi end @@ -735,7 +748,7 @@ public def last_event_id_needing_description last_event = events_needing_description[-1] last_event.nil? ? 0 : last_event.id - end + end # Returns all the events which the user hasn't described yet - an empty array if all described. def events_needing_description @@ -801,8 +814,14 @@ public _("Delivery error") elsif status == 'requires_admin' _("Unusual response.") + elsif status == 'attention_requested' + _("Reported for administrator attention.") elsif status == 'user_withdrawn' _("Withdrawn by the requester.") + elsif status == 'vexatious' + _("Considered by administrators as vexatious and hidden from site.") + elsif status == 'not_foi' + _("Considered by administrators as not an FOI request and hidden from site.") else begin return self.theme_display_status(status) @@ -823,11 +842,11 @@ public track_thing.destroy end self.user_info_request_sent_alerts.each { |a| a.destroy } - self.info_request_events.each do |info_request_event| + self.info_request_events.each do |info_request_event| info_request_event.track_things_sent_emails.each { |a| a.destroy } info_request_event.destroy end - self.exim_logs.each do |exim_log| + self.exim_logs.each do |exim_log| exim_log.destroy end self.outgoing_messages.each { |a| a.destroy } @@ -842,8 +861,8 @@ public return InfoRequest.magic_email_for_id(prefix_part, self.id) end - def InfoRequest.magic_email_for_id(prefix_part, id) - magic_email = MySociety::Config.get("INCOMING_EMAIL_PREFIX", "") + def InfoRequest.magic_email_for_id(prefix_part, id) + magic_email = MySociety::Config.get("INCOMING_EMAIL_PREFIX", "") magic_email += prefix_part + id.to_s magic_email += "-" + InfoRequest.hash_from_id(id) magic_email += "@" + MySociety::Config.get("INCOMING_EMAIL_DOMAIN", "localhost") @@ -888,14 +907,14 @@ public def InfoRequest.find_old_unclassified(extra_params={}) last_response_created_at = last_event_time_clause('response') age = extra_params[:age_in_days] ? extra_params[:age_in_days].days : OLD_AGE_IN_DAYS - params = {:select => "*, #{last_response_created_at} as last_response_time", - :conditions => ["awaiting_description = ? and #{last_response_created_at} < ? and url_title != 'holding_pen'", - true, Time.now() - age], + params = {:select => "*, #{last_response_created_at} as last_response_time", + :conditions => ["awaiting_description = ? and #{last_response_created_at} < ? and url_title != 'holding_pen'", + true, Time.now() - age], :order => "last_response_time"} params[:limit] = extra_params[:limit] if extra_params[:limit] params[:include] = extra_params[:include] if extra_params[:include] if extra_params[:order] - params[:order] = extra_params[:order] + params[:order] = extra_params[:order] params.delete(:select) end if extra_params[:conditions] @@ -905,7 +924,7 @@ public end find(:all, params) end - + def is_old_unclassified? return false if !awaiting_description return false if url_title == 'holding_pen' @@ -924,7 +943,7 @@ public next end incoming_message.safe_mail_from - + email = OutgoingMailer.email_for_followup(self, incoming_message) name = OutgoingMailer.name_for_followup(self, incoming_message) @@ -953,7 +972,7 @@ public end end end - + def apply_censor_rules_to_binary!(binary) for censor_rule in self.censor_rules censor_rule.apply_to_binary!(binary) @@ -964,7 +983,7 @@ public end end end - + def is_owning_user?(user) !user.nil? && (user.id == user_id || user.owns_every_request?) end @@ -973,10 +992,10 @@ public end def user_can_view?(user) - if self.prominence == 'hidden' + if self.prominence == 'hidden' return User.view_hidden_requests?(user) end - if self.prominence == 'requester_only' + if self.prominence == 'requester_only' return self.is_owning_user?(user) end return true @@ -1017,7 +1036,7 @@ public end def json_for_api(deep) - ret = { + ret = { :id => self.id, :url_title => self.url_title, :title => self.title, @@ -1042,6 +1061,26 @@ public end return ret end -end + before_save :purge_in_cache + def purge_in_cache + if !MySociety::Config.get('VARNISH_HOST').nil? && !self.id.nil? + # we only do this for existing info_requests (new ones have a nil id) + path = url_for(:controller => 'request', :action => 'show', :url_title => self.url_title, :only_path => true, :locale => :none) + req = PurgeRequest.find_by_url(path) + if req.nil? + req = PurgeRequest.new(:url => path, + :model => self.class.base_class.to_s, + :model_id => self.id) + end + req.save() + end + end + + def for_admin_column + self.class.content_columns.map{|c| c unless %w(title url_title).include?(c.name) }.compact.each do |column| + yield(column.human_name, self.send(column.name), column.type.to_s, column.name) + end + end +end diff --git a/app/models/info_request_event.rb b/app/models/info_request_event.rb index 99f34cf9e..9a4f6d9fe 100644 --- a/app/models/info_request_event.rb +++ b/app/models/info_request_event.rb @@ -1,5 +1,5 @@ # == Schema Information -# Schema version: 108 +# Schema version: 114 # # Table name: info_request_events # @@ -36,51 +36,57 @@ class InfoRequestEvent < ActiveRecord::Base has_many :track_things_sent_emails validates_presence_of :event_type - validates_inclusion_of :event_type, :in => [ - 'sent', - 'resent', - 'followup_sent', - 'followup_resent', - - 'edit', # title etc. edited (in admin interface) - 'edit_outgoing', # outgoing message edited (in admin interface) - 'edit_comment', # comment edited (in admin interface) - 'destroy_incoming', # deleted an incoming message (in admin interface) - 'destroy_outgoing', # deleted an outgoing message (in admin interface) - 'redeliver_incoming', # redelivered an incoming message elsewhere (in admin interface) - 'move_request', # changed user or public body (in admin interface) - 'manual', # you did something in the db by hand - - 'response', - 'comment', - 'status_update' - ] + + def self.enumerate_event_types + [ + 'sent', + 'resent', + 'followup_sent', + 'followup_resent', + + 'edit', # title etc. edited (in admin interface) + 'edit_outgoing', # outgoing message edited (in admin interface) + 'edit_comment', # comment edited (in admin interface) + 'destroy_incoming', # deleted an incoming message (in admin interface) + 'destroy_outgoing', # deleted an outgoing message (in admin interface) + 'redeliver_incoming', # redelivered an incoming message elsewhere (in admin interface) + 'move_request', # changed user or public body (in admin interface) + 'hide', # hid a request (in admin interface) + 'manual', # you did something in the db by hand + + 'response', + 'comment', + 'status_update' + ] + end + + validates_inclusion_of :event_type, :in => enumerate_event_types # user described state (also update in info_request) validate :must_be_valid_state # whether event is publicly visible - validates_inclusion_of :prominence, :in => [ - 'normal', + validates_inclusion_of :prominence, :in => [ + 'normal', 'hidden', 'requester_only' ] def must_be_valid_state if !described_state.nil? and !InfoRequest.enumerate_states.include?(described_state) - errors.add(described_state, "is not a valid state") + errors.add(described_state, "is not a valid state") end end - + def user_can_view?(user) if !self.info_request.user_can_view?(user) raise "internal error, called user_can_view? on event when there is not permission to view entire request" end - if self.prominence == 'hidden' + if self.prominence == 'hidden' return User.view_hidden_requests?(user) end - if self.prominence == 'requester_only' + if self.prominence == 'requester_only' return self.info_request.is_owning_user?(user) end return true @@ -89,7 +95,7 @@ class InfoRequestEvent < ActiveRecord::Base # Full text search indexing acts_as_xapian :texts => [ :search_text_main, :title ], - :values => [ + :values => [ [ :created_at, 0, "range_search", :date ], # for QueryParser range searches e.g. 01/01/2008..14/01/2008 [ :created_at_numeric, 1, "created_at", :number ], # for sorting [ :described_at_numeric, 2, "described_at", :number ], # XXX using :number for lack of :datetime support in Xapian values @@ -106,7 +112,7 @@ class InfoRequestEvent < ActiveRecord::Base [ :latest_status, 'L', "latest_status" ], [ :waiting_classification, 'W', "waiting_classification" ], [ :filetype, 'T', "filetype" ], - [ :tags, 'U', "tag" ] + [ :tags, 'U', "tag" ] ], :if => :indexed_by_search?, :eager_load => [ :outgoing_message, :comment, { :info_request => [ :user, :public_body, :censor_rules ] } ] @@ -115,7 +121,7 @@ class InfoRequestEvent < ActiveRecord::Base self.info_request.user.url_name end def requested_from - # acts_as_xapian will detect translated fields via Globalize and add all the + # acts_as_xapian will detect translated fields via Globalize and add all the # available locales to the index. But 'requested_from' is not translated directly, # although it relies on a translated field in PublicBody. Hence, we need to # manually add all the localized values to the index (Xapian can handle a list @@ -170,15 +176,15 @@ class InfoRequestEvent < ActiveRecord::Base end def described_at_numeric # format it here as no datetime support in Xapian's value ranges - return self.described_at.strftime("%Y%m%d%H%M%S") + return self.described_at.strftime("%Y%m%d%H%M%S") end def created_at_numeric # format it here as no datetime support in Xapian's value ranges - return self.created_at.strftime("%Y%m%d%H%M%S") + return self.created_at.strftime("%Y%m%d%H%M%S") end - + def incoming_message_selective_columns(fields) - message = IncomingMessage.find(:all, + message = IncomingMessage.find(:all, :select => fields + ", incoming_messages.info_request_id", :joins => "INNER JOIN info_request_events ON incoming_messages.id = incoming_message_id ", :conditions => "info_request_events.id = #{self.id}" @@ -214,7 +220,7 @@ class InfoRequestEvent < ActiveRecord::Base # performance reasons. Xapian will take the full text. def search_text_main(clipped = false) text = '' - if self.event_type == 'sent' + if self.event_type == 'sent' text = text + self.outgoing_message.get_text_for_indexing + "\n\n" elsif self.event_type == 'followup_sent' text = text + self.outgoing_message.get_text_for_indexing + "\n\n" @@ -232,7 +238,7 @@ class InfoRequestEvent < ActiveRecord::Base return text end def title - if self.event_type == 'sent' + if self.event_type == 'sent' return self.info_request.title end return '' @@ -313,26 +319,26 @@ class InfoRequestEvent < ActiveRecord::Base old_value = old_params[key].to_s new_value = new_params[key].to_s if old_value != new_value - ret = ret + "<em>" + CGI.escapeHTML(key) + ":</em> " - ret = ret + - CGI.escapeHTML(MySociety::Format.wrap_email_body_by_lines(old_value).strip).gsub(/\n/, '<br>') + - " => " + + ret = ret + "<em>" + CGI.escapeHTML(key) + ":</em> " + ret = ret + + CGI.escapeHTML(MySociety::Format.wrap_email_body_by_lines(old_value).strip).gsub(/\n/, '<br>') + + " => " + CGI.escapeHTML(MySociety::Format.wrap_email_body_by_lines(new_value).strip).gsub(/\n/, '<br>') ret = ret + "<br>" end end for key, value in other_params - ret = ret + "<em>" + CGI.escapeHTML(key.to_s) + ":</em> " - ret = ret + CGI.escapeHTML(value.to_s.strip) + ret = ret + "<em>" + CGI.escapeHTML(key.to_s) + ":</em> " + ret = ret + CGI.escapeHTML(value.to_s.strip) ret = ret + "<br>" end return ret end - - def is_incoming_message?() not self.incoming_message_selective_columns("incoming_messages.id").nil? end - def is_outgoing_message?() not self.outgoing_message.nil? end - def is_comment?() not self.comment.nil? end + + def is_incoming_message?() not self.incoming_message_selective_columns("incoming_messages.id").nil? end + def is_outgoing_message?() not self.outgoing_message.nil? end + def is_comment?() not self.comment.nil? end # Display version of status def display_status @@ -402,7 +408,7 @@ class InfoRequestEvent < ActiveRecord::Base end def json_for_api(deep, snippet_highlight_proc = nil) - ret = { + ret = { :id => self.id, :event_type => self.event_type, # params_yaml has possibly sensitive data in it, don't include it @@ -427,7 +433,7 @@ class InfoRequestEvent < ActiveRecord::Base ret[:snippet] = snippet_highlight_proc.call(self.search_text_main(true)) end - if deep + if deep ret[:info_request] = self.info_request.json_for_api(false) ret[:public_body] = self.info_request.public_body.json_for_api ret[:user] = self.info_request.user.json_for_api @@ -436,7 +442,9 @@ class InfoRequestEvent < ActiveRecord::Base return ret end - + def for_admin_column + self.class.content_columns.each do |column| + yield(column.human_name, self.send(column.name), column.type.to_s, column.name) + end + end end - - diff --git a/app/models/outgoing_mailer.rb b/app/models/outgoing_mailer.rb index bf81bb89f..8562c5b68 100644 --- a/app/models/outgoing_mailer.rb +++ b/app/models/outgoing_mailer.rb @@ -10,12 +10,12 @@ # separated) paragraphs, as is the convention for all the other mailers. This # turned out to fit better with user exepectations when formatting messages. # -# XXX The other mail templates are written to use blank line separated +# XXX The other mail templates are written to use blank line separated # paragraphs. They could be rewritten, and the wrapping method made uniform # throughout the application. class OutgoingMailer < ApplicationMailer - + # Email to public body requesting info def initial_request(info_request, outgoing_message) @wrap_lines_as_paragraphs = true @@ -96,4 +96,4 @@ class OutgoingMailer < ApplicationMailer end end - + diff --git a/app/models/outgoing_message.rb b/app/models/outgoing_message.rb index cc561b21d..0ce1ee11c 100644 --- a/app/models/outgoing_message.rb +++ b/app/models/outgoing_message.rb @@ -1,5 +1,5 @@ # == Schema Information -# Schema version: 108 +# Schema version: 114 # # Table name: outgoing_messages # @@ -37,7 +37,7 @@ class OutgoingMessage < ActiveRecord::Base # can have many events, for items which were resent by site admin e.g. if # contact address changed - has_many :info_request_events + has_many :info_request_events # To override the default letter attr_accessor :default_letter @@ -45,7 +45,7 @@ class OutgoingMessage < ActiveRecord::Base # reindex if body text is edited (e.g. by admin interface) after_update :xapian_reindex_after_update def xapian_reindex_after_update - if self.changes.include?('body') + if self.changes.include?('body') for info_request_event in self.info_request_events info_request_event.xapian_mark_needs_index end @@ -83,11 +83,11 @@ class OutgoingMessage < ActiveRecord::Base "\n\n" + "I am writing to request an internal review of " + self.info_request.public_body.name + - "'s handling of my FOI request " + - "'" + self.info_request.title + "'." + + "'s handling of my FOI request " + + "'" + self.info_request.title + "'." + "\n\n\n\n [ " + self.get_internal_review_insert_here_note + " ] \n\n\n\n" + "A full history of my FOI request and all correspondence is available on the Internet at this address:\n" + - "http://" + MySociety::Config.get("DOMAIN", '127.0.0.1:3000') + "/request/" + self.info_request.url_title + "http://" + MySociety::Config.get("DOMAIN", '127.0.0.1:3000') + "/request/" + self.info_request.url_title else "" end @@ -98,7 +98,7 @@ class OutgoingMessage < ActiveRecord::Base def set_signature_name(name) # XXX We use raw_body here to get unstripped one if self.raw_body == self.get_default_message - self.body = self.raw_body + name + self.body = self.raw_body + name end end @@ -130,7 +130,7 @@ class OutgoingMessage < ActiveRecord::Base def contains_postcode? MySociety::Validate.contains_postcode?(self.body) end - + # Set default letter def after_initialize if self.body.nil? @@ -267,7 +267,16 @@ class OutgoingMessage < ActiveRecord::Base end end + after_save(:purge_in_cache) + def purge_in_cache + self.info_request.purge_in_cache + end + def for_admin_column + self.class.content_columns.each do |column| + yield(column.human_name, self.send(column.name), column.type.to_s, column.name) + end + end end diff --git a/app/models/post_redirect.rb b/app/models/post_redirect.rb index 59cc86799..f613fc58d 100644 --- a/app/models/post_redirect.rb +++ b/app/models/post_redirect.rb @@ -1,5 +1,5 @@ # == Schema Information -# Schema version: 108 +# Schema version: 114 # # Table name: post_redirects # @@ -39,7 +39,7 @@ class PostRedirect < ActiveRecord::Base self.post_params_yaml = params.to_yaml end def post_params - if self.post_params_yaml.nil? + if self.post_params_yaml.nil? return {} end YAML.load(self.post_params_yaml) @@ -64,7 +64,7 @@ class PostRedirect < ActiveRecord::Base MySociety::Util.generate_token end - # Make the token + # Make the token def after_initialize # The token is used to return you to what you are doing after the login form. if not self.token diff --git a/app/models/profile_photo.rb b/app/models/profile_photo.rb index 43dbbbf0a..72bfe954f 100644 --- a/app/models/profile_photo.rb +++ b/app/models/profile_photo.rb @@ -1,5 +1,5 @@ # == Schema Information -# Schema version: 108 +# Schema version: 114 # # Table name: profile_photos # @@ -17,8 +17,6 @@ # # $Id: profile_photo.rb,v 1.2 2009-09-17 21:10:05 francis Exp $ # -require 'mahoro' -require 'RMagick' class ProfilePhoto < ActiveRecord::Base WIDTH = 96 @@ -29,9 +27,9 @@ class ProfilePhoto < ActiveRecord::Base belongs_to :user # deliberately don't strip_attributes, so keeps raw photo properly - + attr_accessor :x, :y, :w, :h - + # convert binary data blob into ImageMagick image when assigned attr_accessor :image def after_initialize @@ -47,7 +45,7 @@ class ProfilePhoto < ActiveRecord::Base self.image = nil return end - + self.image = image_list[0] # XXX perhaps take largest image or somesuch if there were multiple in the file? self.convert_image end @@ -70,7 +68,7 @@ class ProfilePhoto < ActiveRecord::Base # draft images are before the user has cropped them if !self.draft && (image.columns != WIDTH || image.rows != HEIGHT) # do any exact cropping (taken from Jcrop interface) - if self.w && self.h + if self.w && self.h image.crop!(self.x.to_i, self.y.to_i, self.w.to_i, self.h.to_i) end # do any further cropping @@ -100,7 +98,7 @@ class ProfilePhoto < ActiveRecord::Base if self.image.format != 'PNG' errors.add(:data, N_("Failed to convert image to a PNG")) end - + if !self.draft && (self.image.columns != WIDTH || self.image.rows != HEIGHT) errors.add(:data, N_("Failed to convert image to the correct size: at %{cols}x%{rows}, need %{width}x%{height}" % { :cols => self.image.columns, :rows => self.image.rows, :width => WIDTH, :height => HEIGHT })) end diff --git a/app/models/public_body.rb b/app/models/public_body.rb index 90e9395ae..267b5d60c 100644 --- a/app/models/public_body.rb +++ b/app/models/public_body.rb @@ -1,5 +1,5 @@ # == Schema Information -# Schema version: 95 +# Schema version: 114 # # Table name: public_bodies # @@ -58,11 +58,11 @@ class PublicBody < ActiveRecord::Base short_long_name = t.short_name if t.short_name and !t.short_name.empty? t.url_name = MySociety::Format.simplify_url_part(short_long_name, 'body') end - + def translated_versions translations end - + def translated_versions=(translation_attrs) def skip?(attrs) valueless = attrs.inject({}) { |h, (k, v)| h[k] = v if v != '' and k != 'locale'; h } # because we want to fall back to alternative translations where there are empty values @@ -86,7 +86,7 @@ class PublicBody < ActiveRecord::Base end end end - + # Make sure publication_scheme gets the correct default value. # (This would work automatically, were publication_scheme not a translated attribute) def after_initialize @@ -96,7 +96,7 @@ class PublicBody < ActiveRecord::Base # like find_by_url_name but also search historic url_name if none found def self.find_by_url_name_with_historic(name) locale = self.locale || I18n.locale - PublicBody.with_locale(locale) do + PublicBody.with_locale(locale) do found = PublicBody.find(:all, :conditions => ["public_body_translations.url_name='#{name}'"], :joins => :translations, @@ -189,18 +189,37 @@ class PublicBody < ActiveRecord::Base text = text.gsub(/\n/, '<br>') return text end + + def compare(previous = nil) + if previous.nil? + yield([]) + else + v = self + changes = self.class.content_columns.inject([]) {|memo, c| + unless %w(version last_edit_editor last_edit_comment updated_at).include?(c.name) + from = previous.send(c.name) + to = self.send(c.name) + memo << { :name => c.human_name, :from => from, :to => to } if from != to + end + memo + } + changes.each do |change| + yield(change) + end + end + end end acts_as_xapian :texts => [ :name, :short_name, :notes ], - :values => [ + :values => [ [ :created_at_numeric, 1, "created_at", :number ] # for sorting ], :terms => [ [ :variety, 'V', "variety" ], - [ :tag_array_for_search, 'U', "tag" ] + [ :tag_array_for_search, 'U', "tag" ] ] def created_at_numeric # format it here as no datetime support in Xapian's value ranges - return self.created_at.strftime("%Y%m%d%H%M%S") + return self.created_at.strftime("%Y%m%d%H%M%S") end def variety return "authority" @@ -236,7 +255,7 @@ class PublicBody < ActiveRecord::Base def update_url_name self.url_name = MySociety::Format.simplify_url_part(self.short_or_long_name, 'body') end - + # Return the short name if present, or else long name def short_or_long_name if self.short_name.nil? || self.short_name.empty? # 'nil' can happen during construction @@ -253,7 +272,7 @@ class PublicBody < ActiveRecord::Base first = true for tag in self.tags if PublicBodyCategories::get().by_tag().include?(tag.name) - desc = PublicBodyCategories::get().singular_by_tag()[tag.name] + desc = PublicBodyCategories::get().singular_by_tag()[tag.name] if first # terrible that Ruby/Rails doesn't have an equivalent of ucfirst # (capitalize shockingly converts later characters to lowercase) @@ -270,7 +289,7 @@ class PublicBody < ActiveRecord::Base if types.size > 0 ret = types[0, types.size - 1].join(", ") if types.size > 1 - ret = ret + " and " + ret = ret + " and " end ret = ret + types[-1] return ret @@ -282,19 +301,11 @@ class PublicBody < ActiveRecord::Base # Guess home page from the request email, or use explicit override, or nil # if not known. def calculated_home_page - # manual override for ones we calculate wrongly - if self.home_page != '' - return self.home_page - end - - # extract the domain name from the FOI request email - url = self.request_email_domain - if url.nil? - return nil + if home_page && !home_page.empty? + home_page[URI::regexp(%w(http https))] ? home_page : "http://#{home_page}" + elsif request_email_domain + "http://www.#{request_email_domain}" end - - # add standard URL prefix - return "http://www." + url end # Are all requests to this body under the Environmental Information Regulations? @@ -359,12 +370,12 @@ class PublicBody < ActiveRecord::Base for existing_body in bodies # Hide InternalAdminBody from import notes next if existing_body.id == PublicBody.internal_admin_body.id - + bodies_by_name[existing_body.name] = existing_body set_of_existing.add(existing_body.name) end end - + set_of_importing = Set.new() field_names = { 'name'=>1, 'request_email'=>2 } # Default values in case no field list is given line = 0 @@ -380,7 +391,7 @@ class PublicBody < ActiveRecord::Base fields = {} field_names.each{|name, i| fields[name] = row[i]} - + name = row[field_names['name']] email = row[field_names['request_email']] next if name.nil? @@ -392,7 +403,7 @@ class PublicBody < ActiveRecord::Base errors.push "error: line #{line.to_s}: invalid email '#{email}' for authority '#{name}'" next end - + field_list = ['name', 'short_name', 'request_email', 'notes', 'publication_scheme', 'home_page', 'tag_string'] if public_body = bodies_by_name[name] # Existing public body @@ -402,7 +413,7 @@ class PublicBody < ActiveRecord::Base field_list.each do |field_name| localized_field_name = (locale.to_s == I18n.default_locale.to_s) ? field_name : "#{field_name}.#{locale}" localized_value = field_names[localized_field_name] && row[field_names[localized_field_name]] - + # Tags are a special case, as we support adding to the field, not just setting a new value if localized_field_name == 'tag_string' if localized_value.nil? @@ -410,11 +421,11 @@ class PublicBody < ActiveRecord::Base else if tag_behaviour == 'add' localized_value = "#{localized_value} #{tag}" unless tag.empty? - localized_value = "#{localized_value} #{public_body.tag_string}" + localized_value = "#{localized_value} #{public_body.tag_string}" end end end - + if !localized_value.nil? and public_body.send(field_name) != localized_value changed[field_name] = "#{public_body.send(field_name)}: #{localized_value}" public_body.send("#{field_name}=", localized_value) @@ -424,14 +435,14 @@ class PublicBody < ActiveRecord::Base unless changed.empty? notes.push "line #{line.to_s}: updating authority '#{name}' (locale: #{locale}):\n\t#{changed.to_json}" public_body.last_edit_editor = editor - public_body.last_edit_comment = 'Updated from spreadsheet' + public_body.last_edit_comment = 'Updated from spreadsheet' public_body.save! end end end else # New public body public_body = PublicBody.new(:name=>"", :short_name=>"", :request_email=>"") - available_locales.each do |locale| + available_locales.each do |locale| PublicBody.with_locale(locale) do changed = ActiveSupport::OrderedHash.new field_list.each do |field_name| @@ -441,7 +452,7 @@ class PublicBody < ActiveRecord::Base if localized_field_name == 'tag_string' and tag_behaviour == 'add' localized_value = "#{localized_value} #{tag}" unless tag.empty? end - + if !localized_value.nil? and public_body.send(field_name) != localized_value changed[field_name] = localized_value public_body.send("#{field_name}=", localized_value) @@ -452,7 +463,7 @@ class PublicBody < ActiveRecord::Base notes.push "line #{line.to_s}: creating new authority '#{name}' (locale: #{locale}):\n\t#{changed.to_json}" public_body.publication_scheme = public_body.publication_scheme || "" public_body.last_edit_editor = editor - public_body.last_edit_comment = 'Created from spreadsheet' + public_body.last_edit_comment = 'Created from spreadsheet' public_body.save! end end @@ -462,7 +473,7 @@ class PublicBody < ActiveRecord::Base set_of_importing.add(name) end - # Give an error listing ones that are to be deleted + # Give an error listing ones that are to be deleted deleted_ones = set_of_existing - set_of_importing if deleted_ones.size > 0 notes.push "Notes: Some " + tag + " bodies are in database, but not in CSV file:\n " + Array(deleted_ones).sort.join("\n ") + "\nYou may want to delete them manually.\n" @@ -527,7 +538,7 @@ class PublicBody < ActiveRecord::Base end def has_notes? - return self.notes != "" + return !self.notes.nil? && self.notes != "" end def notes_as_html self.notes @@ -538,7 +549,7 @@ class PublicBody < ActiveRecord::Base end def json_for_api - return { + return { :id => self.id, :url_name => self.url_name, :name => self.name, @@ -546,7 +557,7 @@ class PublicBody < ActiveRecord::Base # :request_email # we hide this behind a captcha, to stop people doing bulk requests easily :created_at => self.created_at, :updated_at => self.updated_at, - # don't add the history as some edit comments contain sensitive information + # don't add the history as some edit comments contain sensitive information # :version, :last_edit_editor, :last_edit_comment :home_page => self.calculated_home_page, :notes => self.notes, @@ -555,6 +566,17 @@ class PublicBody < ActiveRecord::Base } end + after_save(:purge_in_cache) + def purge_in_cache + self.info_requests.each {|x| x.purge_in_cache} + end + + def for_admin_column + self.class.content_columns.map{|c| c unless %w(name last_edit_comment).include?(c.name)}.compact.each do |column| + yield(column.human_name, self.send(column.name), column.type.to_s, column.name) + end + end + end diff --git a/app/models/purge_request.rb b/app/models/purge_request.rb new file mode 100644 index 000000000..48a16f9e6 --- /dev/null +++ b/app/models/purge_request.rb @@ -0,0 +1,52 @@ +# == Schema Information +# Schema version: 114 +# +# Table name: purge_requests +# +# id :integer not null, primary key +# url :string(255) +# created_at :datetime not null +# model :string(255) not null +# model_id :integer not null +# + +# models/purge_request.rb: +# A queue of URLs to purge +# +# Copyright (c) 2008 UK Citizens Online Democracy. All rights reserved. +# Email: francis@mysociety.org; WWW: http://www.mysociety.org/ +# + +class PurgeRequest < ActiveRecord::Base + def self.purge_all + done_something = false + for item in PurgeRequest.all() + item.purge + done_something = true + end + return done_something + end + + def self.purge_all_loop + # Run purge_all in an endless loop, sleeping when there is nothing to do + while true + sleep_seconds = 1 + while !purge_all + sleep sleep_seconds + sleep_seconds *= 2 + sleep_seconds = 30 if sleep_seconds > 30 + end + end + end + + def purge + config = MySociety::Config.load_default() + varnish_url = config['VARNISH_HOST'] + result = quietly_try_to_purge(varnish_url, self.url) + self.delete() + end +end + + + + diff --git a/app/models/raw_email.rb b/app/models/raw_email.rb index 3e12a6feb..1466e5d9c 100644 --- a/app/models/raw_email.rb +++ b/app/models/raw_email.rb @@ -1,5 +1,5 @@ # == Schema Information -# Schema version: 108 +# Schema version: 114 # # Table name: raw_emails # @@ -16,7 +16,7 @@ class RawEmail < ActiveRecord::Base # deliberately don't strip_attributes, so keeps raw email properly - + has_one :incoming_message # We keep the old data_text field (which is of type text) for backwards @@ -27,10 +27,10 @@ class RawEmail < ActiveRecord::Base def directory request_id = self.incoming_message.info_request.id.to_s if ENV["RAILS_ENV"] == "test" - return File.join(RAILS_ROOT, 'files/raw_email_test') + return File.join(Rails.root, 'files/raw_email_test') else return File.join(MySociety::Config.get('RAW_EMAILS_LOCATION', - 'files/raw_emails'), + 'files/raw_emails'), request_id[0..2], request_id) end end diff --git a/app/models/request_mailer.rb b/app/models/request_mailer.rb index 83cce9045..1b0bb48b9 100644 --- a/app/models/request_mailer.rb +++ b/app/models/request_mailer.rb @@ -9,7 +9,7 @@ require 'alaveteli_file_types' class RequestMailer < ApplicationMailer - + # Used when an FOI officer uploads a response from their web browser - this is # the "fake" email used to store in the same format in the database as if they @@ -38,20 +38,25 @@ class RequestMailer < ApplicationMailer @subject = "Your response to an FOI request was not delivered" attachment :content_type => 'message/rfc822', :body => raw_email_data, :filename => "original.eml", :transfer_encoding => '7bit', :content_disposition => 'inline' - @body = { + @body = { :info_request => info_request, - :contact_email => MySociety::Config.get("CONTACT_EMAIL", 'contact@localhost') + :contact_email => MySociety::Config.get("CONTACT_EMAIL", 'contact@localhost') } end # An FOI response is outside the scope of the system, and needs admin attention - def requires_admin(info_request) - @from = info_request.user.name_and_email + def requires_admin(info_request, set_by = nil) + if !set_by.nil? + user = set_by + else + user = info_request.user + end + @from = user.name_and_email @recipients = contact_from_name_and_email - @subject = _("FOI response requires admin - ") + info_request.title + @subject = _("FOI response requires admin ({{reason}}) - {{title}}", :reason => info_request.described_state, :title => info_request.title) url = main_url(request_url(info_request)) admin_url = request_admin_url(info_request) - @body = {:info_request => info_request, :url => url, :admin_url => admin_url } + @body = {:reported_by => user, :info_request => info_request, :url => url, :admin_url => admin_url } end # Tell the requester that a new response has arrived @@ -142,7 +147,7 @@ class RequestMailer < ApplicationMailer # Tell the requester that they need to clarify their request def not_clarified_alert(info_request, incoming_message) respond_url = show_response_url(:id => info_request.id, :incoming_message_id => incoming_message.id) - respond_url = respond_url + "#followup" + respond_url = respond_url + "#followup" post_redirect = PostRedirect.new( :uri => respond_url, @@ -155,7 +160,7 @@ class RequestMailer < ApplicationMailer 'Auto-Submitted' => 'auto-generated', # http://tools.ietf.org/html/rfc3834 'X-Auto-Response-Suppress' => 'OOF' @recipients = info_request.user.name_and_email - @subject = "Clarify your FOI request - " + info_request.title + @subject = _("Clarify your FOI request - ") + info_request.title @body = { :incoming_message => incoming_message, :info_request => info_request, :url => url } end @@ -182,7 +187,7 @@ class RequestMailer < ApplicationMailer # Class function, called by script/mailin with all incoming responses. # [ This is a copy (Monkeypatch!) of function from action_mailer/base.rb, # but which additionally passes the raw_email to the member function, as we - # want to record it. + # want to record it. # # That is because we want to be sure we properly record the actual message # received in its raw form - so any information won't be lost in a round @@ -215,7 +220,7 @@ class RequestMailer < ApplicationMailer # Find which info requests the email is for reply_info_requests = self.requests_matching_email(email) # Nothing found, so save in holding pen - if reply_info_requests.size == 0 + if reply_info_requests.size == 0 reason = _("Could not identify the request from the email address") request = InfoRequest.holding_pen_request request.receive(email, raw_email, false, reason) @@ -225,7 +230,7 @@ class RequestMailer < ApplicationMailer # Send the message to each request, to be archived with it for reply_info_request in reply_info_requests # If environment variable STOP_DUPLICATES is set, don't send message with same id again - if ENV['STOP_DUPLICATES'] + if ENV['STOP_DUPLICATES'] if reply_info_request.already_received?(email, raw_email) raise "message " + email.message_id + " already received by request" end @@ -275,7 +280,7 @@ class RequestMailer < ApplicationMailer end end - # Send email alerts for new responses which haven't been classified. By default, + # Send email alerts for new responses which haven't been classified. By default, # it goes out 3 days after last update of event, then after 10, then after 24. def self.alert_new_response_reminders MySociety::Config.get("NEW_RESPONSE_REMINDER_AFTER_DAYS", [3, 10, 24]).each_with_index do |days, i| @@ -283,10 +288,10 @@ class RequestMailer < ApplicationMailer end end def self.alert_new_response_reminders_internal(days_since, type_code) - info_requests = InfoRequest.find_old_unclassified(:order => 'info_requests.id', - :include => [:user], + info_requests = InfoRequest.find_old_unclassified(:order => 'info_requests.id', + :include => [:user], :age_in_days => days_since) - + for info_request in info_requests alert_event_id = info_request.get_last_response_event_id last_response_message = info_request.get_last_response @@ -302,7 +307,7 @@ class RequestMailer < ApplicationMailer store_sent.user = info_request.user store_sent.alert_type = type_code store_sent.info_request_event_id = alert_event_id - # XXX uses same template for reminder 1 and reminder 2 right now. + # XXX uses same template for reminder 1 and reminder 2 right now. RequestMailer.deliver_new_response_reminder_alert(info_request, last_response_message) store_sent.save! end @@ -340,11 +345,11 @@ class RequestMailer < ApplicationMailer # Send email alert to request submitter for new comments on the request. def self.alert_comment_on_request() - + # We only check comments made in the last month - this means if the # cron jobs broke for more than a month events would be lost, but no # matter. I suspect the performance gain will be needed (with an index on updated_at) - + # XXX the :order part info_request_events.created_at is a work around # for a very old Rails bug which means eager loading does not respect # association orders. @@ -352,7 +357,7 @@ class RequestMailer < ApplicationMailer # http://lists.rubyonrails.org/pipermail/rails-core/2006-July/001798.html # That that patch has not been applied, despite bribes of beer, is # typical of the lack of quality of Rails. - + info_requests = InfoRequest.find(:all, :conditions => [ "info_requests.id in ( diff --git a/app/models/track_mailer.rb b/app/models/track_mailer.rb index 0c053c4ad..92da7c376 100644 --- a/app/models/track_mailer.rb +++ b/app/models/track_mailer.rb @@ -47,8 +47,8 @@ class TrackMailer < ApplicationMailer return done_something end for user in users - next if !user.should_be_emailed? - + next if !user.should_be_emailed? || !user.receive_email_alerts + email_about_things = [] track_things = TrackThing.find(:all, :conditions => [ "tracking_user_id = ? and track_medium = ?", user.id, 'email_daily' ]) for track_thing in track_things @@ -56,7 +56,7 @@ class TrackMailer < ApplicationMailer # # We only use track_things_sent_emails records which are less than 14 days old. # In the search query loop below, we also only use items described in last 7 days. - # An item described that recently definitely can't appear in track_things_sent_emails + # An item described that recently definitely can't appear in track_things_sent_emails # earlier, so this is safe (with a week long margin of error). If the alerts break # for a whole week, then they will miss some items. Tough. done_info_request_events = {} @@ -70,7 +70,7 @@ class TrackMailer < ApplicationMailer # Query for things in this track. We use described_at for the # ordering, so we catch anything new (before described), or # anything whose new status has been described. - xapian_object = InfoRequest.full_search([InfoRequestEvent], track_thing.track_query, 'described_at', true, nil, 100, 1) + xapian_object = InfoRequest.full_search([InfoRequestEvent], track_thing.track_query, 'described_at', true, nil, 100, 1) # Go through looking for unalerted things alert_results = [] for result in xapian_object.results @@ -86,7 +86,7 @@ class TrackMailer < ApplicationMailer alert_results.push(result) end # If there were more alerts for this track, then store them - if alert_results.size > 0 + if alert_results.size > 0 email_about_things.push([track_thing, alert_results, xapian_object]) end end diff --git a/app/models/track_thing.rb b/app/models/track_thing.rb index 58d70ed86..d0fc62e12 100644 --- a/app/models/track_thing.rb +++ b/app/models/track_thing.rb @@ -1,5 +1,5 @@ # == Schema Information -# Schema version: 108 +# Schema version: 114 # # Table name: track_things # @@ -35,17 +35,17 @@ class TrackThing < ActiveRecord::Base has_many :track_things_sent_emails - validates_inclusion_of :track_type, :in => [ - 'request_updates', + validates_inclusion_of :track_type, :in => [ + 'request_updates', 'all_new_requests', 'all_successful_requests', - 'public_body_updates', + 'public_body_updates', 'user_updates', 'search_query' ] - validates_inclusion_of :track_medium, :in => [ - 'email_daily', + validates_inclusion_of :track_medium, :in => [ + 'email_daily', 'feed' ] @@ -69,7 +69,7 @@ class TrackThing < ActiveRecord::Base end def track_query_description - # XXX this is very brittle... we should probably ask users + # XXX this is very brittle... we should probably ask users # simply to name their tracks when they make them? original_text = parsed_text = self.track_query.gsub(/([()]|OR)/, "") filters = parsed_text.scan /\b\S+:\S+\b/ @@ -101,14 +101,14 @@ class TrackThing < ActiveRecord::Base end if filter =~ /waiting/ statuses << _("awaiting a response") - end + end end if filters.empty? parsed_text = original_text end descriptions = [] if varieties.include? _("requests") - descriptions << _("requests which are {{list_of_statuses}}", :list_of_statuses => Array(statuses).join(_(' or '))) + descriptions << _("requests which are {{list_of_statuses}}", :list_of_statuses => Array(statuses).sort.join(_(' or '))) varieties -= [_("requests")] end if descriptions.empty? and varieties.empty? @@ -116,7 +116,7 @@ class TrackThing < ActiveRecord::Base end descriptions += Array(varieties) parsed_text = parsed_text.strip - descriptions = descriptions.join(_(" or ")) + descriptions = descriptions.sort.join(_(" or ")) if !parsed_text.empty? descriptions += _("{{list_of_things}} matching text '{{search_query}}'", :list_of_things => "", :search_query => parsed_text) end @@ -146,11 +146,15 @@ class TrackThing < ActiveRecord::Base return track_thing end - def TrackThing.create_track_for_public_body(public_body) + def TrackThing.create_track_for_public_body(public_body, event_type = nil) track_thing = TrackThing.new track_thing.track_type = 'public_body_updates' track_thing.public_body = public_body - track_thing.track_query = "requested_from:" + public_body.url_name + query = "requested_from:" + public_body.url_name + if InfoRequestEvent.enumerate_event_types.include?(event_type) + query += " variety:" + event_type + end + track_thing.track_query = query return track_thing end @@ -171,10 +175,10 @@ class TrackThing < ActiveRecord::Base query += " variety:sent" when "users" query += " variety:user" - when "authorities" - query += " variety:authority" + when "bodies" + query += " variety:authority" end - end + end track_thing.track_query = query # XXX should extract requested_by:, request:, requested_from: # and stick their values into the respective relations. @@ -192,15 +196,15 @@ class TrackThing < ActiveRecord::Base @params = { # Website :list_description => _("'{{link_to_request}}', a request", :link_to_request => "<a href=\"/request/" + CGI.escapeHTML(self.info_request.url_title) + "\">" + CGI.escapeHTML(self.info_request.title) + "</a>"), # XXX yeuch, sometimes I just want to call view helpers from the model, sorry! can't work out how - :verb_on_page => _("Track this request by email"), - :verb_on_page_already => _("You are already tracking this request by email"), + :verb_on_page => _("Follow this request"), + :verb_on_page_already => _("You are already following this request"), # Email :title_in_email => _("New updates for the request '{{request_title}}'", :request_title => self.info_request.title), :title_in_rss => _("New updates for the request '{{request_title}}'", :request_title => self.info_request.title), # Authentication - :web => _("To follow updates to the request '{{request_title}}'", :request_title => CGI.escapeHTML(self.info_request.title)), - :email => _("Then you will be emailed whenever the request '{{request_title}}' is updated.", :request_title => CGI.escapeHTML(self.info_request.title)), - :email_subject => _("Confirm you want to follow updates to the request '{{request_title}}'", :request_title => self.info_request.title), + :web => _("To follow the request '{{request_title}}'", :request_title => CGI.escapeHTML(self.info_request.title)), + :email => _("Then you will be updated whenever the request '{{request_title}}' is updated.", :request_title => CGI.escapeHTML(self.info_request.title)), + :email_subject => _("Confirm you want to follow the request '{{request_title}}'", :request_title => self.info_request.title), # RSS sorting :feed_sortby => 'newest' } @@ -208,15 +212,15 @@ class TrackThing < ActiveRecord::Base @params = { # Website :list_description => _("any <a href=\"/list\">new requests</a>"), - :verb_on_page => _("Email me when there are new requests"), - :verb_on_page_already => _("You are being emailed when there are new requests"), + :verb_on_page => _("Follow all new requests"), + :verb_on_page_already => _("You are already following new requests"), # Email :title_in_email => _("New Freedom of Information requests"), :title_in_rss => _("New Freedom of Information requests"), # Authentication - :web => _("To be emailed about any new requests"), - :email => _("Then you will be emailed whenever anyone makes a new FOI request."), - :email_subject => _("Confirm you want to be emailed about new requests"), + :web => _("To follow new requests"), + :email => _("Then you will be following all new FOI requests."), + :email_subject => _("Confirm you want to follow new requests"), # RSS sorting :feed_sortby => 'newest' } @@ -224,15 +228,15 @@ class TrackThing < ActiveRecord::Base @params = { # Website :list_description => _("any <a href=\"/list/successful\">successful requests</a>"), - :verb_on_page => _("Email me new successful responses "), - :verb_on_page_already => _("You are being emailed about any new successful responses"), + :verb_on_page => _("Follow new successful responses"), + :verb_on_page_already => _("You are following all new successful responses"), # Email :title_in_email => _("Successful Freedom of Information requests"), :title_in_rss => _("Successful Freedom of Information requests"), # Authentication - :web => _("To be emailed about any successful requests"), - :email => _("Then you will be emailed whenever an FOI request succeeds."), - :email_subject => _("Confirm you want to be emailed when an FOI request succeeds"), + :web => _("To follow all successful requests"), + :email => _("Then you will be notified whenever an FOI request succeeds."), + :email_subject => _("Confirm you want to follow all successful FOI requests"), # RSS sorting - used described date, as newest would give a # date for responses possibly days before description, so # wouldn't appear at top of list when description (known @@ -243,15 +247,15 @@ class TrackThing < ActiveRecord::Base @params = { # Website :list_description => _("'{{link_to_authority}}', a public authority", :link_to_authority => "<a href=\"/body/" + CGI.escapeHTML(self.public_body.url_name) + "\">" + CGI.escapeHTML(self.public_body.name) + "</a>"), # XXX yeuch, sometimes I just want to call view helpers from the model, sorry! can't work out how - :verb_on_page => _("Track requests to {{public_body_name}} by email",:public_body_name=>CGI.escapeHTML(self.public_body.name)), - :verb_on_page_already => _("You are already tracking requests to {{public_body_name}} by email", :public_body_name=>CGI.escapeHTML(self.public_body.name)), + :verb_on_page => _("Follow requests to {{public_body_name}}",:public_body_name=>CGI.escapeHTML(self.public_body.name)), + :verb_on_page_already => _("You are already following requests to {{public_body_name}}", :public_body_name=>CGI.escapeHTML(self.public_body.name)), # Email :title_in_email => self.public_body.law_only_short + " requests to '" + self.public_body.name + "'", :title_in_rss => self.public_body.law_only_short + " requests to '" + self.public_body.name + "'", # Authentication - :web => _("To be emailed about requests made using {{site_name}} to the public authority '{{public_body_name}}'", :site_name=>MySociety::Config.get('SITE_NAME', 'Alaveteli'), :public_body_name=>CGI.escapeHTML(self.public_body.name)), - :email => _("Then you will be emailed whenever someone requests something or gets a response from '{{public_body_name}}'.", :public_body_name=>CGI.escapeHTML(self.public_body.name)), - :email_subject => _("Confirm you want to be emailed about requests to '{{public_body_name}}'", :public_body_name=>self.public_body.name), + :web => _("To follow requests made using {{site_name}} to the public authority '{{public_body_name}}'", :site_name=>MySociety::Config.get('SITE_NAME', 'Alaveteli'), :public_body_name=>CGI.escapeHTML(self.public_body.name)), + :email => _("Then you will be notified whenever someone requests something or gets a response from '{{public_body_name}}'.", :public_body_name=>CGI.escapeHTML(self.public_body.name)), + :email_subject => _("Confirm you want to follow requests to '{{public_body_name}}'", :public_body_name=>self.public_body.name), # RSS sorting :feed_sortby => 'newest' } @@ -259,15 +263,15 @@ class TrackThing < ActiveRecord::Base @params = { # Website :list_description => _("'{{link_to_user}}', a person", :link_to_user => "<a href=\"/user/" + CGI.escapeHTML(self.tracked_user.url_name) + "\">" + CGI.escapeHTML(self.tracked_user.name) + "</a>"), # XXX yeuch, sometimes I just want to call view helpers from the model, sorry! can't work out how - :verb_on_page => _("Track this person by email"), - :verb_on_page_already => _("You are already tracking this person by email"), + :verb_on_page => _("Follow this person"), + :verb_on_page_already => _("You are already following this person"), # Email :title_in_email => _("FOI requests by '{{user_name}}'", :user_name=>self.tracked_user.name), :title_in_rss => _("FOI requests by '{{user_name}}'", :user_name=>self.tracked_user.name), # Authentication - :web => _("To be emailed about requests by '{{user_name}}'", :user_name=>CGI.escapeHTML(self.tracked_user.name)), - :email => _("Then you will be emailed whenever '{{user_name}}' requests something or gets a response.", :user_name=>CGI.escapeHTML(self.tracked_user.name)), - :email_subject => _("Confirm you want to be emailed about requests by '{{user_name}}'", :user_name=>self.tracked_user.name), + :web => _("To follow requests by '{{user_name}}'", :user_name=>CGI.escapeHTML(self.tracked_user.name)), + :email => _("Then you will be notified whenever '{{user_name}}' requests something or gets a response.", :user_name=>CGI.escapeHTML(self.tracked_user.name)), + :email_subject => _("Confirm you want to follow requests by '{{user_name}}'", :user_name=>self.tracked_user.name), # RSS sorting :feed_sortby => 'newest' } @@ -275,15 +279,15 @@ class TrackThing < ActiveRecord::Base @params = { # Website :list_description => "<a href=\"/search/" + CGI.escapeHTML(self.track_query) + "/newest/advanced\">" + self.track_query_description + "</a>", # XXX yeuch, sometimes I just want to call view helpers from the model, sorry! can't work out how - :verb_on_page => _("Track things matching this search by email"), - :verb_on_page_already => _("You are already tracking things matching this search by email"), + :verb_on_page => _("Follow things matching this search"), + :verb_on_page_already => _("You are already following things matching this search"), # Email :title_in_email => _("Requests or responses matching your saved search"), :title_in_rss => _("Requests or responses matching your saved search"), # Authentication :web => _("To follow requests and responses matching your search"), - :email => _("Then you will be emailed whenever a new request or response matches your search."), - :email_subject => _("Confirm you want to be emailed about new requests or responses matching your search"), + :email => _("Then you will be notified whenever a new request or response matches your search."), + :email_subject => _("Confirm you want to follow new requests or responses matching your search"), # RSS sorting - XXX hmmm, we don't really know which to use # here for sorting. Might be a query term (e.g. 'cctv'), in # which case newest is good, or might be something like diff --git a/app/models/track_things_sent_email.rb b/app/models/track_things_sent_email.rb index 777339d75..24297f57b 100644 --- a/app/models/track_things_sent_email.rb +++ b/app/models/track_things_sent_email.rb @@ -1,5 +1,5 @@ # == Schema Information -# Schema version: 108 +# Schema version: 114 # # Table name: track_things_sent_emails # diff --git a/app/models/user.rb b/app/models/user.rb index 59a84b7aa..a21676f68 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,5 +1,5 @@ # == Schema Information -# Schema version: 108 +# Schema version: 114 # # Table name: users # @@ -20,6 +20,8 @@ # email_bounced_at :datetime # email_bounce_message :text default(""), not null # no_limit :boolean default(FALSE), not null +# receive_email_alerts :boolean default(TRUE), not null +# user_similarity_id :integer # # models/user.rb: @@ -52,22 +54,22 @@ class User < ActiveRecord::Base attr_accessor :password_confirmation, :no_xapian_reindex validates_confirmation_of :password, :message => _("Please enter the same password twice") - validates_inclusion_of :admin_level, :in => [ + validates_inclusion_of :admin_level, :in => [ 'none', - 'super', + 'super', ], :message => N_('Admin level is not included in list') acts_as_xapian :texts => [ :name, :about_me ], - :values => [ + :values => [ [ :created_at_numeric, 1, "created_at", :number ] # for sorting ], :terms => [ [ :variety, 'V', "variety" ] ], :if => :indexed_by_search? def created_at_numeric # format it here as no datetime support in Xapian's value ranges - return self.created_at.strftime("%Y%m%d%H%M%S") + return self.created_at.strftime("%Y%m%d%H%M%S") end - + def variety "user" end @@ -79,7 +81,7 @@ class User < ActiveRecord::Base if self.new_record? # make alert emails go out at a random time for each new user, so # overall they are spread out throughout the day. - self.last_daily_track_email = User.random_time_in_last_day + self.last_daily_track_email = User.random_time_in_last_day end end @@ -101,7 +103,7 @@ class User < ActiveRecord::Base end end end - + def get_locale if !self.locale.nil? locale = self.locale @@ -117,10 +119,10 @@ class User < ActiveRecord::Base def validate if self.email != "" && !MySociety::Validate.is_valid_email(self.email) - errors.add(:email, _("Please enter a valid email address")) + errors.add(:email, _("Please enter a valid email address")) end if MySociety::Validate.is_valid_email(self.name) - errors.add(:name, _("Please enter your name, not your email address, in the name field.")) + errors.add(:name, _("Please enter your name, not your email address, in the name field.")) end end @@ -139,7 +141,7 @@ class User < ActiveRecord::Base end # Return user given login email, password and other form parameters (e.g. name) - # + # # The specific_user_login parameter says that login as a particular user is # expected, so no parallel registration form is being displayed. def User.authenticate_from_form(params, specific_user_login = false) @@ -235,10 +237,10 @@ class User < ActiveRecord::Base # changed more than a day ago) def get_undescribed_requests self.info_requests.find( - :all, - :conditions => [ 'awaiting_description = ? and ' + InfoRequest.last_event_time_clause + ' < ?', - true, Time.now() - 1.day - ] + :all, + :conditions => [ 'awaiting_description = ? and ' + InfoRequest.last_event_time_clause + ' < ?', + true, Time.now() - 1.day + ] ) end @@ -256,7 +258,7 @@ class User < ActiveRecord::Base def owns_every_request? self.admin_level == 'super' end - + def User.owns_every_request?(user) !user.nil? && user.owns_every_request? end @@ -271,7 +273,7 @@ class User < ActiveRecord::Base def User.stay_logged_in_on_redirect?(user) !user.nil? && user.admin_level == 'super' end - + # Does the user get "(admin)" links on each page on the main site? def admin_page_links? self.admin_level == 'super' @@ -287,21 +289,21 @@ class User < ActiveRecord::Base def exceeded_limit? # Some users have no limit return false if self.no_limit - + # Has the user issued as many as MAX_REQUESTS_PER_USER_PER_DAY requests in the past 24 hours? daily_limit = MySociety::Config.get("MAX_REQUESTS_PER_USER_PER_DAY") return false if daily_limit.nil? recent_requests = InfoRequest.count(:conditions => ["user_id = ? and created_at > now() - '1 day'::interval", self.id]) - + return (recent_requests >= daily_limit) end def next_request_permitted_at return nil if self.no_limit - + daily_limit = MySociety::Config.get("MAX_REQUESTS_PER_USER_PER_DAY") n_most_recent_requests = InfoRequest.all(:conditions => ["user_id = ? and created_at > now() - '1 day'::interval", self.id], :order => "created_at DESC", :limit => daily_limit) return nil if n_most_recent_requests.size < daily_limit - + nth_most_recent_request = n_most_recent_requests[-1] return nth_most_recent_request.created_at + 1.day end @@ -375,7 +377,7 @@ class User < ActiveRecord::Base end def json_for_api - return { + return { :id => self.id, :url_name => self.url_name, :name => self.name, @@ -391,36 +393,55 @@ class User < ActiveRecord::Base self.email_bounce_message = message self.save! end - + def should_be_emailed? return (self.email_confirmed && self.email_bounced_at.nil?) end - + def indexed_by_search? return self.email_confirmed end + def for_admin_column(complete = false) + if complete + columns = self.class.content_columns + else + columns = self.class.content_columns.map{|c| c if %w(created_at updated_at admin_level email_confirmed).include?(c.name) }.compact + end + columns.each do |column| + yield(column.human_name, self.send(column.name), column.type.to_s, column.name) + end + end + ## Private instance methods private def create_new_salt self.salt = self.object_id.to_s + rand.to_s end - + ## Class methods def User.encrypted_password(password, salt) string_to_hash = password + salt # XXX need to add a secret here too? Digest::SHA1.hexdigest(string_to_hash) end - + def User.record_bounce_for_email(email, message) user = User.find_user_by_email(email) return false if user.nil? - + if user.email_bounced_at.nil? user.record_bounce(message) end return true end + + after_save(:purge_in_cache) + def purge_in_cache + if self.name_changed? + self.info_requests.each {|x| x.purge_in_cache} + end + end + end diff --git a/app/models/user_info_request_sent_alert.rb b/app/models/user_info_request_sent_alert.rb index 5f23355bf..a97fd5d44 100644 --- a/app/models/user_info_request_sent_alert.rb +++ b/app/models/user_info_request_sent_alert.rb @@ -1,5 +1,5 @@ # == Schema Information -# Schema version: 108 +# Schema version: 114 # # Table name: user_info_request_sent_alerts # @@ -23,7 +23,7 @@ class UserInfoRequestSentAlert < ActiveRecord::Base belongs_to :user belongs_to :info_request - validates_inclusion_of :alert_type, :in => [ + validates_inclusion_of :alert_type, :in => [ 'overdue_1', # tell user that info request has become overdue 'very_overdue_1', # tell user that info request has become very overdue 'new_response_reminder_1', # reminder user to classify the recent response diff --git a/app/views/admin_general/debug.rhtml b/app/views/admin_general/debug.rhtml index 40fe33616..d7bf1c6da 100644 --- a/app/views/admin_general/debug.rhtml +++ b/app/views/admin_general/debug.rhtml @@ -7,6 +7,8 @@ <h2>Version numbers</h2> <p> +Alaveteli version: <%= link_to @current_version, @github_origin + @current_version %> +<br> Alaveteli branch: <%= link_to @current_branch, @github_origin + @current_branch %> <br> Alaveteli commit: <%= link_to @current_commit, @github_origin + @current_commit %> diff --git a/app/views/admin_general/index.rhtml b/app/views/admin_general/index.rhtml index 1a4b8ba96..48bd7f694 100644 --- a/app/views/admin_general/index.rhtml +++ b/app/views/admin_general/index.rhtml @@ -46,6 +46,20 @@ </ul> <% end %> +<% if @attention_requests.size > 0 %> + <h3>Review requests which have been marked as requiring your attention by users (<%=@error_message_requests.size%> total)</h3> + + <ul> + <% for @request in @attention_requests %> + <li> + <%= request_both_links(@request)%> + – <%=simple_date(@request.get_last_event.created_at)%> + </li> + <% end %> + </ul> +<% end %> + + <% if @requires_admin_requests.size > 0 %> <h3>These require administrator attention (<%=@requires_admin_requests.size%> total)</h3> diff --git a/app/views/admin_general/timeline.rhtml b/app/views/admin_general/timeline.rhtml index 39a4b3e36..eecab4823 100644 --- a/app/views/admin_general/timeline.rhtml +++ b/app/views/admin_general/timeline.rhtml @@ -65,6 +65,8 @@ had incoming message deleted by administrator <strong><%=h event.params[:editor] %></strong>. <% elsif event.event_type == 'destroy_outgoing' %> had outgoing message deleted by administrator <strong><%=h event.params[:editor] %></strong>. + <% elsif event.event_type == 'hide' %> + was hidden by administrator <strong><%=h event.params[:editor] %></strong>. <% elsif event.event_type == 'redeliver_outgoing' %> had incoming message redelivered to another request by administrator <strong><%=h event.params[:editor] %></strong>. <% elsif event.event_type == 'response' %> diff --git a/app/views/admin_request/_incoming_message_actions.rhtml b/app/views/admin_request/_incoming_message_actions.rhtml index c23b4060a..569132861 100644 --- a/app/views/admin_request/_incoming_message_actions.rhtml +++ b/app/views/admin_request/_incoming_message_actions.rhtml @@ -1,6 +1,6 @@ <% form_tag '../redeliver_incoming' do %> <div> - id or url_title of request: + id or url_title of request (or a list of requests, comma-separated): <% if @info_requests && @info_requests.size == 1 %> <%= text_field_tag 'url_title', @info_requests[0].url_title, { :size => 20 } %> <% else %> diff --git a/app/views/admin_request/edit.rhtml b/app/views/admin_request/edit.rhtml index b659c676d..4026ee712 100644 --- a/app/views/admin_request/edit.rhtml +++ b/app/views/admin_request/edit.rhtml @@ -22,20 +22,7 @@ </p> <p><label for="info_request_described_state"><strong>Described state</strong></label> - <%= select( 'info_request', "described_state", - [ - 'waiting_response', - 'waiting_clarification', - 'gone_postal', - 'not_held', - 'rejected', - 'successful', - 'partially_successful', - 'internal_review', - 'error_message', - 'requires_admin', - 'user_withdrawn' - ]) %>; + <%= select( 'info_request', "described_state", InfoRequest.enumerate_states ) %>; <label for="info_request_awaiting_description"><strong>Awaiting description</strong></label> <%= select('info_request', "awaiting_description", [["Yes - needs state updating",true],["No - state is up to date",false]]) %> <br/>(don't forget to change 'awaiting description' when you set described state)<br/> @@ -60,7 +47,7 @@ <hr> -<% form_tag '../fully_destroy/' + @info_request.id.to_s do %> +<% form_tag '../destroy/' + @info_request.id.to_s do %> <p> <strong>This is permanent and irreversible!</strong> <%= submit_tag 'Destory request entirely' %> <br>Use it mainly if someone posts private information, e.g. made a Data Protection request. It diff --git a/app/views/admin_request/hidden_user_explanation.rhtml b/app/views/admin_request/hidden_user_explanation.rhtml new file mode 100644 index 000000000..64387ffee --- /dev/null +++ b/app/views/admin_request/hidden_user_explanation.rhtml @@ -0,0 +1,9 @@ +Dear <%= name_to %>, + +Your request '<%= info_request.title %>' at <%= info_request_url %> has been reviewed by moderators. + +We consider it <% if reason == 'not_foi' %>is not a valid FOI request<% else %>to be vexatious<% end%>, and have therefore hidden it from other users. You will still be able to view it while logged in to the site. Please reply to this email if you would like to discuss this decision further. + +Yours, + +The <%= site_name %> team. diff --git a/app/views/contact_mailer/from_admin_message.rhtml b/app/views/contact_mailer/from_admin_message.rhtml new file mode 100644 index 000000000..bdb48d580 --- /dev/null +++ b/app/views/contact_mailer/from_admin_message.rhtml @@ -0,0 +1,2 @@ +<%= @message.strip %> + diff --git a/app/views/general/_locale_switcher.rhtml b/app/views/general/_locale_switcher.rhtml index 27e492e84..2521b5eb5 100644 --- a/app/views/general/_locale_switcher.rhtml +++ b/app/views/general/_locale_switcher.rhtml @@ -1,11 +1,13 @@ - <% if FastGettext.default_available_locales.length > 1 && !params.empty? %> - <div id="user_locale_switcher"> - <% for possible_locale in FastGettext.default_available_locales %> - <% if possible_locale == I18n.locale.to_s %> - <span class="active"><%= locale_name(possible_locale) %></span> - <% else %> - <a href="<%= locale_switcher(possible_locale, params) %>"><%= locale_name(possible_locale) %></a> - <% end %> - <% end %> + <% if FastGettext.default_available_locales.length > 1 && !params.empty? %> + <div id="user_locale_switcher"> + <div class="btn-group"> + <% for possible_locale in FastGettext.default_available_locales %> + <% if possible_locale == I18n.locale.to_s %> + <a href="#" class="btn disabled"><%= locale_name(possible_locale) %></a> + <% else %> + <a href="<%= locale_switcher(possible_locale, params) %>" class="btn"><%= locale_name(possible_locale) %></a> + <% end %> + <% end %> </div> - <% end %> + </div> + <% end %> diff --git a/app/views/general/_orglink.rhtml b/app/views/general/_orglink.rhtml index 7d74dbaac..fbe688d85 100644 --- a/app/views/general/_orglink.rhtml +++ b/app/views/general/_orglink.rhtml @@ -1,2 +1,2 @@ -<%-# Put the link to your organisation here, or leave blank -%> -<%= link_to image_tag('logo.png'), frontpage_url, :id=>'logo' %> +<%# Put the link to your organisation here, or leave blank %> +<%= link_to image_tag('logo.png'), frontpage_url, :id=>'logo' %> diff --git a/app/views/general/search.rhtml b/app/views/general/search.rhtml index 90ace809e..a1f8c8f04 100644 --- a/app/views/general/search.rhtml +++ b/app/views/general/search.rhtml @@ -131,8 +131,7 @@ </p> <% end %> </div> <!-- header left --> - -<% if @track_thing && (@xapian_bodies_hits > 0 || @xapian_users_hits > 0 || @total_hits == 0)%> +<% if !@track_thing.nil? %> <div id="header_right"> <h2><%= _('Track this search')%></h2> <%= render :partial => 'track/tracking_links', :locals => { :track_thing => @track_thing, :own_request => false, :location => 'main' } %> diff --git a/app/views/layouts/admin.rhtml b/app/views/layouts/admin.rhtml index 42ca5dbbb..d85eecbf2 100644 --- a/app/views/layouts/admin.rhtml +++ b/app/views/layouts/admin.rhtml @@ -9,19 +9,19 @@ <%= stylesheet_link_tag 'admin-theme/jquery-ui-1.8.15.custom.css', :rel => 'stylesheet'%> <%= stylesheet_link_tag 'admin', :title => "Main", :rel => "stylesheet" %> </head> - <body> + <body class="admin"> <p> <strong><%= link_to 'Alaveteli', main_url('/') %> admin:</strong> - <%= link_to 'Summary', admin_general_index_path %> - | <%= link_to 'Timeline', admin_timeline_path %> - | <%= link_to 'Stats', admin_stats_path %> - | <%= link_to 'Debug', admin_debug_path %> + <%= link_to 'Summary', admin_url("") %> + | <%= link_to 'Timeline', admin_url("timeline") %> + | <%= link_to 'Stats', admin_url("stats") %> + | <%= link_to 'Debug', admin_url("debug") %> <strong>View:</strong> - <%= link_to 'Authorities', admin_body_list_path %> - | <%= link_to 'Requests', admin_request_list_path %> - | <%= link_to 'Users', admin_user_list_path %> - | <%= link_to 'Tracks', admin_track_list_path %> + <%= link_to 'Authorities', admin_url("body/list") %> + | <%= link_to 'Requests', admin_url("request/list") %> + | <%= link_to 'Users', admin_url("user/list") %> + | <%= link_to 'Tracks', admin_url("track/list") %> </p> <%= render :partial => 'general/locale_switcher' %> diff --git a/app/views/layouts/default.rhtml b/app/views/layouts/default.rhtml index f439b27d2..ed0a52e85 100644 --- a/app/views/layouts/default.rhtml +++ b/app/views/layouts/default.rhtml @@ -58,7 +58,7 @@ <%= render :partial => 'general/before_head_end' %> </head> - <body <%= "class='front'" if params[:action] == 'frontpage' %>> + <body class="<%= 'admin' if !session[:using_admin].nil?%> <%= 'front' if params[:action] == 'frontpage' %>"> <!-- XXX: move to a separate file --> <% if force_registration_on_new_request && !@user %> @@ -104,6 +104,7 @@ <% if @user %> <%=link_to _("My requests"), show_user_requests_path(:url_name => @user.url_name) %> <%=link_to _("My profile"), show_user_profile_path(:url_name => @user.url_name) %> + <%=link_to _("My wall"), show_user_wall_path(:url_name => @user.url_name) %> <% end %> @@ -146,7 +147,6 @@ <%= render :partial => 'general/footer' %> - <%= render :partial => 'general/before_body_end' %> </div> <div id="other-country-notice"></div> <div id="link_box"><span class="close-button">X</span> @@ -154,6 +154,22 @@ <br /> <input type="text"> </div> + <% + ga_code = MySociety::Config.get('GA_CODE', '') + + unless ga_code.empty? %> + <script> + var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www."); + document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E")); + </script> + <script> + var pageTracker = _gat._getTracker("<%=ga_code%>"); + pageTracker._trackPageview(); + </script> + + <% end %> + + <%= render :partial => 'general/before_body_end' %> </body> </html> diff --git a/app/views/public_body/show.rhtml b/app/views/public_body/show.rhtml index 5f20a9717..63bd5f7fc 100644 --- a/app/views/public_body/show.rhtml +++ b/app/views/public_body/show.rhtml @@ -4,7 +4,7 @@ <h2><%= _('Follow this authority')%></h2> <% follower_count = TrackThing.count(:all, :conditions => ["public_body_id = ?", @public_body.id]) %> - <p><%= n_("There is %d person following this authority", "There are %d people following this authority", follower_count) % follower_count %></p> + <p><%= n_("<span id='follow_count'>%d</span> person is following this authority", "<span id='follow_count'>%d</span> people are following this authority", follower_count) % follower_count %></p> <%= render :partial => 'track/tracking_links', :locals => { :track_thing => @track_thing, :own_request => false, :location => 'sidebar' } %> <h2><%= _('More about this authority')%></h2> diff --git a/app/views/request/_bubble.rhtml b/app/views/request/_bubble.rhtml index 87079e9ea..331c2163e 100644 --- a/app/views/request/_bubble.rhtml +++ b/app/views/request/_bubble.rhtml @@ -13,7 +13,7 @@ :file_name => a.display_filename + '.html') %> <% img_filename = "icon_" + a.content_type.sub('/', '_') + "_large.png" - full_filename = File.join(File.dirname(__FILE__), "../../../public/images", img_filename) + full_filename = File.expand_path(File.join(File.dirname(__FILE__), "../../../public/images", img_filename)) if File.exist?(full_filename) %> <a href="<%=attachment_url%>"><img class="attachment_image" alt="Attachment" src="/images/<%=img_filename%>"></a> <% else %> diff --git a/app/views/request/_sidebar.rhtml b/app/views/request/_sidebar.rhtml index 758387b09..d6d5e8f12 100644 --- a/app/views/request/_sidebar.rhtml +++ b/app/views/request/_sidebar.rhtml @@ -1,10 +1,33 @@ <div id="right_column"> + <div id="follow_box"> <h2><%= _('Follow this request') %></h2> <% follower_count = TrackThing.count(:all, :conditions => ["info_request_id = ?", @info_request.id]) + 1 %> <p><%= n_("There is %d person following this request", "There are %d people following this request", follower_count) % follower_count %></p> <%= render :partial => 'track/tracking_links', :locals => { :track_thing => @track_thing, :own_request => @info_request.user == @user, :location => 'sidebar' } %> - + </div> + <% if @info_request.described_state != "attention_requested" %> + <h2><%= _('Offensive? Unsuitable?') %></h2> + <% if @info_request.attention_requested %> + <% if @info_request.prominence == 'hidden' %> + <%# The eccentric formatting of the following string is in order that it be identical + to the corresponding string in request/show.rhtml %> + <p><%= _('This request has prominence \'hidden\'. You can only see it because you are logged + in as a super user.') %></p> + <% elsif @info_request.prominence == 'requester_only' %> + <%# The eccentric formatting of the following string is in order that it be identical + to the corresponding string in request/show.rhtml %> + <p><%= _('This request is hidden, so that only you the requester can see it. Please + <a href="%s">contact us</a> if you are not sure why.') % [help_requesting_path] %></p> + <% else %> + <p><%= _('This request has been marked for review by the site administrators, who have not hidden it at this time. If you believe it should be hidden, please <a href="%s">contact us</a>.') % [help_requesting_path] %></p> + <% end %> + <% else %> + <p><%= _('Requests for personal information and vexatious requests are not considered valid for FOI purposes (<a href="/help/about">read more</a>).') %> + <p><%= ('If you believe this request is not suitable, you can report it for attention by the site administrators') %></p> + <%= link_to _("Report this request"), report_path, :class => "link_button_green", :method => "POST" %> + <% end %> + <% end %> <h2><%= _("Act on what you've learnt") %></h2> <div class="act_link"> diff --git a/app/views/request/_wall_listing.rhtml b/app/views/request/_wall_listing.rhtml new file mode 100644 index 000000000..9cde234c0 --- /dev/null +++ b/app/views/request/_wall_listing.rhtml @@ -0,0 +1,30 @@ +<% if @highlight_words.nil? + @highlight_words = [] +end %> + +<div class="request_listing"> + <div class="request_left"> + <div class="requester"> + <% if event.event_type == 'sent' %> + <%= _('A new request, <em><a href="{{request_url}}">{{request_title}}</a></em>, was sent to {{public_body_name}} by {{info_request_user}} on {{date}}.',:public_body_name=>public_body_link_absolute(info_request.public_body),:info_request_user=>user_link_absolute(info_request.user),:date=>simple_date(event.created_at),:request_url=>request_url(info_request),:request_title=>info_request.title) %> + <% elsif event.event_type == 'followup_sent' %> + <%= _('A <a href="{{request_url}}">follow up</a> to <em>{{request_title}}</em> was sent to {{public_body_name}} by {{info_request_user}} on {{date}}.',:public_body_name=>public_body_link_absolute(info_request.public_body),:info_request_user=>user_link_absolute(info_request.user),:date=>simple_date(event.created_at),:request_url=>outgoing_message_url(event.outgoing_message),:request_title=>info_request.title) %> + <% elsif event.event_type == 'response' %> + <%= _('A <a href="{{request_url}}">response</a> to <em>{{request_title}}</em> was sent by {{public_body_name}} to {{info_request_user}} on {{date}}. The request status is: {{request_status}}',:public_body_name=>public_body_link_absolute(info_request.public_body),:info_request_user=>user_link_absolute(info_request.user),:date=>simple_date(event.created_at),:request_url=>incoming_message_url(event.incoming_message_selective_columns("incoming_messages.id")),:request_title=>info_request.title,:request_status=>info_request.display_status) %> + <% elsif event.event_type == 'comment' %> + <%= _('An <a href="{{request_url}}">annotation</a> to <em>{{request_title}}</em> was made by {{event_comment_user}} on {{date}}',:public_body_name=>public_body_link_absolute(info_request.public_body),:info_request_user=>user_link_absolute(info_request.user),:event_comment_user=>user_link_absolute(event.comment.user),:date=>simple_date(event.created_at),:request_url=>comment_url(event.comment),:request_title=>info_request.title) %> + <% else %> + <%# Events of other types will not be indexed: see InfoRequestEvent#indexed_by_search? + However, it can happen that we see other types of event transiently here in the period + between a change being made and the update-xapian-index job being run. %> + <!-- Event of type '<%= event.event_type %>', id=<%= event.id %> --> + <% end %> + </div> + </div> + <div class="request_right"> + <span class="desc"> + <%= highlight_and_excerpt(event.search_text_main(true), @highlight_words, 150) %> + </span> + </div> +</div> + diff --git a/app/views/request/show.rhtml b/app/views/request/show.rhtml index c5d040fb7..27ad0700e 100644 --- a/app/views/request/show.rhtml +++ b/app/views/request/show.rhtml @@ -1,4 +1,6 @@ -<% @title = "#{h(@info_request.title)} - a Freedom of Information request to #{h(@info_request.public_body.name)}" %> +<% @title = _("{{title}} - a Freedom of Information request to {{public_body}}", + :title => h(@info_request.title), + :public_body => (@info_request.public_body.name)) %> <% if @info_request.prominence == 'hidden' %> <p id="hidden_request"> @@ -72,7 +74,7 @@ <%= _('normally') %> <% end %> <%= _('no later than') %> <strong><%= simple_date(@info_request.date_response_required_by) %></strong> - (<%= link_to "details", "/help/requesting#quickly_response" %>). + (<%= link_to _("details"), "/help/requesting#quickly_response" %>). <% elsif @status == 'waiting_response_overdue' %> <%= _('Response to this request is <strong>delayed</strong>.') %> <%= _('By law, {{public_body_link}} should normally have responded <strong>promptly</strong> and',:public_body_link=>public_body_link(@info_request.public_body)) %> @@ -116,6 +118,12 @@ <% elsif @status == 'user_withdrawn' %> <%= _('This request has been <strong>withdrawn</strong> by the person who made it. There may be an explanation in the correspondence below.') %> + <% elsif @status == 'attention_requested' %> + <%= _('This request has been <strong>reported</strong> as needing administrator attention (perhaps because it is vexatious, or a request for personal information)') %> + <% elsif @status == 'vexatious' %> + <%= _('This request has been <strong>hidden</strong> from the site, because an administrator considers it vexatious') %> + <% elsif @status == 'not_foi' %> + <%= _('This request has been <strong>hidden</strong> from the site, because an administrator considers it not to be an FOI request') %> <% else %> <%= render :partial => 'general/custom_state_descriptions', :locals => { :status => @status } %> <% end %> diff --git a/app/views/request_mailer/not_clarified_alert.rhtml b/app/views/request_mailer/not_clarified_alert.rhtml index 82d15ba76..2408452b3 100644 --- a/app/views/request_mailer/not_clarified_alert.rhtml +++ b/app/views/request_mailer/not_clarified_alert.rhtml @@ -1,5 +1,7 @@ -<%=@info_request.public_body.name%> <%=('has asked you to explain part of your')%> <%=@info_request.law_used_short%> <%= _('request.')%> -<%= _('To do this, first click on the link below.')%> +<%= _('{{public_body}} has asked you to explain part of your {{law_used}} request.', + :public_body => @info_request.public_body.name, + :law_used => @info_request.law_used_short ) %> +<%= _('To do this, first click on the link below.') %> <%=@url%> diff --git a/app/views/request_mailer/requires_admin.rhtml b/app/views/request_mailer/requires_admin.rhtml index acd37f405..06a798792 100644 --- a/app/views/request_mailer/requires_admin.rhtml +++ b/app/views/request_mailer/requires_admin.rhtml @@ -1,5 +1,5 @@ --------------------------------------------------------------------- -<%=@info_request.user.name%> <%= _('has reported an')%> <%=@info_request.law_used_short%> +<%=@reported_by.name%> <%= _('has reported an')%> <%=@info_request.law_used_short%> <%= _('response as needing administrator attention. Take a look, and reply to this email to let them know what you are going to do about it.')%> diff --git a/app/views/track/_tracking_links.rhtml b/app/views/track/_tracking_links.rhtml index f50a8bbbf..39f346eff 100644 --- a/app/views/track/_tracking_links.rhtml +++ b/app/views/track/_tracking_links.rhtml @@ -7,18 +7,17 @@ <% if own_request %> <p><%= _('This is your own request, so you will be automatically emailed when new responses arrive.')%></p> <% elsif existing_track %> - <% form_tag({:controller => 'track', :action => 'update', :track_id => existing_track.id}, :class => "feed_form feed_form_" + location) do %> - <p> - <%= track_thing.params[:verb_on_page_already] %> - <%= hidden_field_tag 'track_medium', "delete" %> - <%= hidden_field_tag 'r', request.request_uri %> - <%= submit_tag "unsubscribe" %> - </p> - <% end %> + <p><%= track_thing.params[:verb_on_page_already] %></p> + <div class="feed_link feed_link_<%=location%>"> + <%= link_to "Unsubscribe", {:controller => 'track', :action => 'update', :track_id => existing_track.id, :track_medium => "delete", :r => request.request_uri}, :class => "link_button_green" %> + </div> <% elsif track_thing %> <div class="feed_link feed_link_<%=location%>"> - <%= link_to '<img src="/images/email-16.png" alt="">', do_track_url(track_thing) %> - <%= link_to _("Follow by email"), do_track_url(track_thing) %> + <% if defined? follower_count && follower_count > 0 %> + <%= link_to _("I like this request"), do_track_url(track_thing), :class => "link_button_green" %> + <% else %> + <%= link_to _("Follow"), do_track_url(track_thing), :class => "link_button_green" %> + <% end %> </div> <div class="feed_link feed_link_<%=location%>"> diff --git a/app/views/user/_change_receive_email.rhtml b/app/views/user/_change_receive_email.rhtml new file mode 100644 index 000000000..83e5d8601 --- /dev/null +++ b/app/views/user/_change_receive_email.rhtml @@ -0,0 +1,16 @@ +<% form_tag(:controller=>"user", :action=>"set_receive_email_alerts") do %> + <div> + <% if @user.receive_email_alerts %> + <%= _('You are currently receiving notification of new activity on your wall by email.', :wall_url => show_user_wall_path) %><br><br> + <%= hidden_field_tag 'receive_email_alerts', 'false' %> + <%= hidden_field_tag 'came_from', request.url %> + <%= submit_tag _("Turn off email alerts") %> + <% else %> + <%= _('Items matching the following conditions are currently displayed on your wall.') %><br><br> + <%= hidden_field_tag 'receive_email_alerts', 'true' %> + <%= hidden_field_tag 'came_from', request.url %> + <%= submit_tag _("Also send me alerts by email") %> + <% end %> + </div> +<% end %> + diff --git a/app/views/user/confirm.rhtml b/app/views/user/confirm.rhtml index bdaf5c8e9..bc70a1f36 100644 --- a/app/views/user/confirm.rhtml +++ b/app/views/user/confirm.rhtml @@ -1,6 +1,6 @@ -<% @title = h("Now check your email!") %> +<% @title = _("Now check your email!") %> -<h1 class="confirmation_heading">Now check your email!</h1> +<h1 class="confirmation_heading"><%= _("Now check your email!") %></h1> <p class="confirmation_message"> <%= _('We\'ve sent you an email, and you\'ll need to click the link in it before you can diff --git a/app/views/user/rate_limited.rhtml b/app/views/user/rate_limited.rhtml index 2a770d62e..d5accf114 100644 --- a/app/views/user/rate_limited.rhtml +++ b/app/views/user/rate_limited.rhtml @@ -1,4 +1,4 @@ -<% @title = "Too many requests" %> +<% @title = _("Too many requests") %> <h1><%=@title%></h1> diff --git a/app/views/user/show.rhtml b/app/views/user/show.rhtml index 8f1803442..d723196d3 100644 --- a/app/views/user/show.rhtml +++ b/app/views/user/show.rhtml @@ -1,7 +1,7 @@ <% if @show_requests %> - <% @title = h(@display_user.name) + " - Freedom of Information requests" %> + <% @title = h(@display_user.name) + _(" - Freedom of Information requests") %> <% else %> - <% @title = h(@display_user.name) + " - user profile" %> + <% @title = h(@display_user.name) + _(" - user profile") %> <% end %> <% if (@same_name_users.size >= 1) %> @@ -139,7 +139,7 @@ <% if !@xapian_requests.nil? %> <% if @xapian_requests.results.empty? %> <% if @page == 1 %> - <h2 class="foi_results" id="foi_requests"><%= @is_you ? 'Freedom of Information requests made by you' : 'Freedom of Information requests made by this person' %> <%= @match_phrase %> + <h2 class="foi_results" id="foi_requests"><%= @is_you ? _('Freedom of Information requests made by you') : _('Freedom of Information requests made by this person') %> <%= @match_phrase %> </h2> <p><%= @is_you ? _('You have made no Freedom of Information requests using this site.') : _('This person has made no Freedom of Information requests using this site.') %> <%= @page_desc %> @@ -192,11 +192,12 @@ </div> <% end %> <% if @show_profile && @is_you %> + <h2 id="email_subscriptions"> <%= _("Things you're following")%></h2> + <%= render :partial => 'change_receive_email' %> + <br> <% if @track_things.empty? %> - <h2 id="email_subscriptions"> <%= _('Your email subscriptions')%></h2> - <p><%= _('None made.')%></p> + <p><%= _("You're not following anything.")%></p> <% else %> - <h2 id="email_subscriptions"> Your <%=pluralize(@track_things.size, _('email subscription')) %> </h2> <% if @track_things_grouped.size == 1 %> <% form_tag({:controller => 'track', :action => 'delete_all_type'}, :class => "feed_form") do %> <h3> diff --git a/app/views/user/sign.rhtml b/app/views/user/sign.rhtml index bfd0fa63e..4704ea95a 100644 --- a/app/views/user/sign.rhtml +++ b/app/views/user/sign.rhtml @@ -10,7 +10,10 @@ <%= @post_redirect.reason_params[:web] %>, <%= _('please sign in as ')%><%= link_to h(@post_redirect.reason_params[:user_name]), @post_redirect.reason_params[:user_url] %>. <% end %> - </p> + </p> + <% if @post_redirect.post_params["controller"] == "admin_general" %> + <p id="superuser_message">Don't have a superuser account yet? <%= link_to "Sign in as the emergency user", @post_redirect.uri + "?emergency=1" %></p> + <% end %> <%= render :partial => 'signin', :locals => { :sign_in_as_existing_user => true } %> diff --git a/app/views/user/wall.rhtml b/app/views/user/wall.rhtml new file mode 100644 index 000000000..190cc0a6d --- /dev/null +++ b/app/views/user/wall.rhtml @@ -0,0 +1,16 @@ +<% @title = h(@display_user.name) + _(" - wall") %> +<% if @is_you %> +<div class="medium_column"> + <p><%= _('You can change the requests and users you are following on <a href="{{profile_url}}">your profile page</a>.', :profile_url => show_user_profile_path) %> + <%= render :partial => 'change_receive_email' %> +</div> +<% end %> +<div id="user_profile_search"> + <% if !@feed_results.nil? %> + <% for result in @feed_results %> + <%= render :partial => 'request/wall_listing', :locals => { :event => result, :info_request => result.info_request } %> + <% end %> + <% end %> + + +</div> |