aboutsummaryrefslogtreecommitdiffstats
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/controllers/admin_controller.rb16
-rw-r--r--app/controllers/admin_public_body_controller.rb28
-rw-r--r--app/controllers/admin_request_controller.rb4
-rw-r--r--app/controllers/application_controller.rb52
-rw-r--r--app/controllers/comment_controller.rb1
-rw-r--r--app/controllers/general_controller.rb2
-rw-r--r--app/controllers/public_body_controller.rb48
-rw-r--r--app/controllers/request_controller.rb87
-rw-r--r--app/controllers/user_controller.rb99
-rw-r--r--app/helpers/application_helper.rb6
-rw-r--r--app/helpers/link_to_helper.rb4
-rw-r--r--app/models/change_email_validator.rb42
-rw-r--r--app/models/comment.rb2
-rw-r--r--app/models/incoming_message.rb177
-rw-r--r--app/models/info_request.rb61
-rw-r--r--app/models/info_request_event.rb2
-rw-r--r--app/models/outgoing_mailer.rb2
-rw-r--r--app/models/outgoing_message.rb25
-rw-r--r--app/models/post_redirect.rb8
-rw-r--r--app/models/request_mailer.rb42
-rw-r--r--app/models/track_mailer.rb30
-rw-r--r--app/models/track_thing.rb2
-rw-r--r--app/models/track_things_sent_email.rb6
-rw-r--r--app/models/user.rb10
-rw-r--r--app/models/user_info_request_sent_alert.rb1
-rw-r--r--app/models/user_mailer.rb21
-rw-r--r--app/views/admin_public_body/import_csv.rhtml31
-rw-r--r--app/views/admin_request/show.rhtml10
-rw-r--r--app/views/admin_track/_some_tracks.rhtml2
-rw-r--r--app/views/admin_track/list.rhtml2
-rw-r--r--app/views/comment/new.rhtml2
-rw-r--r--app/views/general/frontpage.rhtml2
-rw-r--r--app/views/general/search.rhtml2
-rw-r--r--app/views/help/about.rhtml259
-rw-r--r--app/views/help/unhappy.rhtml4
-rw-r--r--app/views/layouts/default.rhtml24
-rw-r--r--app/views/public_body/list.rhtml53
-rw-r--r--app/views/public_body/show.rhtml18
-rw-r--r--app/views/request/_after_actions.rhtml2
-rw-r--r--app/views/request/_correspondence.rhtml2
-rw-r--r--app/views/request/_describe_state.rhtml2
-rw-r--r--app/views/request/_followup.rhtml55
-rw-r--r--app/views/request/_hidden_correspondence.rhtml37
-rw-r--r--app/views/request/_other_describe_state.rhtml2
-rw-r--r--app/views/request/_sidebar.rhtml3
-rw-r--r--app/views/request/details.rhtml54
-rw-r--r--app/views/request/new.rhtml4
-rw-r--r--app/views/request/show.rhtml46
-rw-r--r--app/views/request/show_response.rhtml2
-rw-r--r--app/views/request_mailer/overdue_alert.rhtml6
-rw-r--r--app/views/request_mailer/very_overdue_alert.rhtml14
-rw-r--r--app/views/user/_signin.rhtml2
-rw-r--r--app/views/user/show.rhtml3
-rw-r--r--app/views/user/signchange_send_confirm.rhtml30
-rw-r--r--app/views/user/signchangeemail.rhtml41
-rw-r--r--app/views/user/signchangeemail_confirm.rhtml14
-rw-r--r--app/views/user/signchangepassword.rhtml (renamed from app/views/user/signchange.rhtml)10
-rw-r--r--app/views/user/signchangepassword_confirm.rhtml (renamed from app/views/user/signchange_confirm.rhtml)0
-rw-r--r--app/views/user/signchangepassword_send_confirm.rhtml30
-rw-r--r--app/views/user_mailer/changeemail_already_used.rhtml9
-rw-r--r--app/views/user_mailer/changeemail_confirm.rhtml12
61 files changed, 1156 insertions, 411 deletions
diff --git a/app/controllers/admin_controller.rb b/app/controllers/admin_controller.rb
index 7f8cfbd67..ca5538e03 100644
--- a/app/controllers/admin_controller.rb
+++ b/app/controllers/admin_controller.rb
@@ -19,19 +19,9 @@ class AdminController < ApplicationController
# Expire cached attachment files for a request
def expire_for_request(info_request)
- # Clear out cached entries - use low level disk removal, even though we
- # are clearing results from caches_action, for several reasons:
- # * We can't use expire_action here, as it doesn't seem to be
- # compatible with the :only_path we used in the caches_action
- # call.
- # * Removing everything is simpler than having to get all the
- # parameters right for the path, and calling for HTML version vs. raw
- # attachment version.
- # * We cope properly with filenames changed by censor rules, which
- # change the URL.
- # * We could use expire_fragment with a Regexp, but it walks the whole
- # cache which is insanely slow
- cache_subpath = File.join(self.cache_store.cache_path, "views/request/#{info_request.id}")
+ # Clear out cached entries, by removing files from disk (the built in
+ # Rails fragment cache made doing this and other things too hard)
+ cache_subpath = foi_fragment_cache_all_for_request(info_request)
FileUtils.rm_rf(cache_subpath)
# Remove the database caches of body / attachment text (the attachment text
diff --git a/app/controllers/admin_public_body_controller.rb b/app/controllers/admin_public_body_controller.rb
index 74a3a86c6..bce04ff98 100644
--- a/app/controllers/admin_public_body_controller.rb
+++ b/app/controllers/admin_public_body_controller.rb
@@ -120,7 +120,15 @@ class AdminPublicBodyController < AdminController
def import_csv
if params[:csv_file]
- if not params[:tag].empty?
+ if !params[:tag].empty?
+ if params['commit'] == 'Dry run'
+ dry_run_only = true
+ elsif params['commit'] == 'Upload'
+ dry_run_only = false
+ 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], true, admin_http_auth_user())
@@ -128,14 +136,18 @@ class AdminPublicBodyController < AdminController
notes = en[1]
if errors.size == 0
- # And if OK, with real run
- en = PublicBody.import_csv(csv_contents, params[:tag], false, admin_http_auth_user())
- errors = en[0]
- notes = en[1]
- if errors.size != 0
- raise "dry run mismatched real run"
+ if dry_run_only
+ notes.push("Dry run was successful, real run would do as above.")
+ else
+ # And if OK, with real run
+ en = PublicBody.import_csv(csv_contents, params[:tag], false, admin_http_auth_user())
+ errors = en[0]
+ notes = en[1]
+ if errors.size != 0
+ raise "dry run mismatched real run"
+ end
+ notes.push("Import was successful.")
end
- notes.push("Import was successful.")
end
@errors = errors.join("\n")
@notes = notes.join("\n")
diff --git a/app/controllers/admin_request_controller.rb b/app/controllers/admin_request_controller.rb
index ff2772b0e..f077691ff 100644
--- a/app/controllers/admin_request_controller.rb
+++ b/app/controllers/admin_request_controller.rb
@@ -215,10 +215,10 @@ class AdminRequestController < AdminController
# Bejeeps, look, sometimes a URL is something that belongs in a controller, jesus.
# XXX hammer this square peg into the round MVC hole - should be calling main_url(upload_response_url())
post_redirect = PostRedirect.new(
- :uri => upload_response_url(:url_title => info_request.url_title),
+ :uri => main_url(upload_response_url(:url_title => info_request.url_title, :only_path => true)),
:user_id => user.id)
post_redirect.save!
- url = confirm_url(:email_token => post_redirect.email_token)
+ url = main_url(confirm_url(:email_token => post_redirect.email_token, :only_path => true))
flash[:notice] = 'Send "' + name + '" &lt;<a href="mailto:' + email + '">' + email + '</a>&gt; this URL: <a href="' + url + '">' + url + "</a> - it will log them in and let them upload a response to this request."
redirect_to request_admin_url(info_request)
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 5055519ec..9ee1c250b 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -40,21 +40,12 @@ class ApplicationController < ActionController::Base
before_filter :session_remember_me
def session_remember_me
# Reset the "sliding window" session expiry time.
- if session[:remember_me]
- expire_time = 1.month.from_now
- # "Why is session[:force_new_cookie] set to Time.now? In order for the “sliding window”
- # concept to work, a fresh cookie must be sent with every response. Rails only
- # sends a cookie when the session data has changed so using a value like Time.now
- # ensures that it changes every time. What I have actually found is that some
- # internal voodoo causes the session data to change slightly anyway but it’s best
- # to be sure!"
- session[:force_new_cookie] = Time.now
- else
- expire_time = nil
- end
- # if statement here is so test code runs
- if session.instance_variable_get(:@dbman)
- session.instance_variable_get(:@dbman).instance_variable_get(:@cookie_options)['expires'] = expire_time
+ if request.env['rack.session.options']
+ if session[:remember_me]
+ request.env['rack.session.options'][:expire_after] = 1.month
+ else
+ request.env['rack.session.options'][:expire_after] = nil
+ end
end
end
@@ -100,6 +91,27 @@ class ApplicationController < ActionController::Base
controller_example_group.get params[:action], params
end
+ # Used to work out where to cache fragments. We add an extra path to the
+ # URL using the first three digits of the info request id, because we can't
+ # have more than 32,000 entries in one directory on an ext3 filesystem.
+ def foi_fragment_cache_part_path(param)
+ path = url_for(param)
+ id = param['id'] || param[:id]
+ first_three_digits = id.to_s()[0..2]
+ path = path.sub("/request/", "/request/" + first_three_digits + "/")
+ return path
+ end
+ def foi_fragment_cache_path(param)
+ path = foi_fragment_cache_part_path(param)
+ path = "/views" + path
+ return File.join(self.cache_store.cache_path, path)
+ end
+ def foi_fragment_cache_all_for_request(info_request)
+ first_three_digits = info_request.id.to_s()[0..2]
+ path = "views/request/#{first_three_digits}/#{info_request.id}"
+ return File.join(self.cache_store.cache_path, path)
+ end
+
private
# Check the user is logged in
@@ -184,6 +196,16 @@ class ApplicationController < ActionController::Base
end
end
+ #
+ def check_read_only
+ read_only = MySociety::Config.get('READ_ONLY')
+ if !read_only.empty?
+ flash[:notice] = "<p>WhatDoTheyKnow is currently in maintenance. You can only view existing requests. You cannot make new ones, add followups or annotations, or otherwise change the database.</p> <p>" + read_only + "</p>"
+ redirect_to frontpage_url
+ end
+
+ end
+
# For administration interface, return display name of authenticated user
def admin_http_auth_user
# This needs special magic in mongrel: http://www.ruby-forum.com/topic/83067
diff --git a/app/controllers/comment_controller.rb b/app/controllers/comment_controller.rb
index dfa31f9ef..d5f8f89fb 100644
--- a/app/controllers/comment_controller.rb
+++ b/app/controllers/comment_controller.rb
@@ -7,6 +7,7 @@
# $Id: comment_controller.rb,v 1.9 2009-03-09 01:17:04 francis Exp $
class CommentController < ApplicationController
+ before_filter :check_read_only, :only => [ :new ]
def new
if params[:type] == 'request'
diff --git a/app/controllers/general_controller.rb b/app/controllers/general_controller.rb
index 5c4103616..efab26fad 100644
--- a/app/controllers/general_controller.rb
+++ b/app/controllers/general_controller.rb
@@ -7,7 +7,7 @@
#
# $Id: general_controller.rb,v 1.57 2009-10-03 10:23:43 francis Exp $
-require 'xmlsimple'
+require 'lib/xmlsimple'
require 'open-uri'
class GeneralController < ApplicationController
diff --git a/app/controllers/public_body_controller.rb b/app/controllers/public_body_controller.rb
index 1ca82e463..591081fe9 100644
--- a/app/controllers/public_body_controller.rb
+++ b/app/controllers/public_body_controller.rb
@@ -6,6 +6,8 @@
#
# $Id: public_body_controller.rb,v 1.8 2009-09-14 13:27:00 francis Exp $
+require 'csv'
+
class PublicBodyController < ApplicationController
# XXX tidy this up with better error messages, and a more standard infrastructure for the redirect to canonical URL
def show
@@ -93,5 +95,51 @@ class PublicBodyController < ApplicationController
cache_in_squid
end
+
+ # Used so URLs like /local/islington work, for use e.g. writing to a local paper.
+ def list_redirect
+ @tag = params[:tag]
+ redirect_to list_public_bodies_url(:tag => @tag)
+ end
+
+ def list_all_csv
+ public_bodies = PublicBody.find(:all, :order => 'url_name')
+ report = StringIO.new
+ CSV::Writer.generate(report, ',') do |title|
+ title << [
+ 'Name',
+ 'Short name',
+ # deliberately not including 'Request email'
+ 'URL name',
+ 'Tags',
+ 'Home page',
+ 'Publication scheme',
+ 'Charity number',
+ 'Created at',
+ 'Updated at',
+ 'Version',
+ ]
+ public_bodies.each do |public_body|
+ 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.tag_string,
+ public_body.calculated_home_page,
+ public_body.publication_scheme,
+ public_body.charity_number,
+ public_body.created_at,
+ public_body.updated_at,
+ public_body.version,
+ ]
+ end
+ end
+ report.rewind
+ send_data(report.read, :type=> 'text/csv; charset=utf-8; header=present',
+ :filename => 'all-authorities.csv',
+ :disposition =>'attachment', :encoding => 'utf8')
+ end
end
diff --git a/app/controllers/request_controller.rb b/app/controllers/request_controller.rb
index 2606a9609..0664093c3 100644
--- a/app/controllers/request_controller.rb
+++ b/app/controllers/request_controller.rb
@@ -7,6 +7,7 @@
# $Id: request_controller.rb,v 1.192 2009-10-19 19:26:40 francis Exp $
class RequestController < ApplicationController
+ before_filter :check_read_only, :only => [ :new, :show_response, :describe_state ]
def show
# Look up by old style numeric identifiers
@@ -64,11 +65,26 @@ class RequestController < ApplicationController
@last_response = @info_request.get_last_response
end
+ # Extra info about a request, such as event history
+ def details
+ @info_request = InfoRequest.find_by_url_title(params[:url_title])
+ if !@info_request.user_can_view?(authenticated_user)
+ render :template => 'request/hidden', :status => 410 # gone
+ return
+ end
+
+ @columns = ['id', 'event_type', 'created_at', 'described_state', 'last_described_at', 'calculated_state' ]
+ end
+
# Requests similar to this one
def similar
@per_page = 25
@page = (params[:page] || "1").to_i
@info_request = InfoRequest.find_by_url_title(params[:url_title])
+ if !@info_request.user_can_view?(authenticated_user)
+ render :template => 'request/hidden', :status => 410 # gone
+ return
+ end
@xapian_object = ::ActsAsXapian::Similar.new([InfoRequestEvent], @info_request.info_request_events,
:offset => (@page - 1) * @per_page, :limit => @per_page, :collapse_by_prefix => 'request_collapse')
@@ -148,27 +164,27 @@ class RequestController < ApplicationController
# First time we get to the page, just display it
if params[:submitted_new_request].nil? || params[:reedit]
# Read parameters in - public body must be passed in
- if params[:public_body_id]
- params[:info_request] = { :public_body_id => params[:public_body_id] }
+ params[:info_request] = { :public_body_id => params[:public_body_id] } if !params[:info_request]
+ if !params[:info_request][:public_body_id]
+ redirect_to frontpage_url
+ return
end
@info_request = InfoRequest.new(params[:info_request])
params[:info_request_id] = @info_request.id
+ params[:outgoing_message] = {} if !params[:outgoing_message]
+ 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.nil?
- redirect_to frontpage_url
- else
- if @info_request.public_body.is_requestable?
- render :action => 'new'
+ if @info_request.public_body.is_requestable?
+ render :action => 'new'
+ else
+ if @info_request.public_body.not_requestable_reason == 'bad_contact'
+ render :action => 'new_bad_contact'
else
- if @info_request.public_body.not_requestable_reason == 'bad_contact'
- render :action => 'new_bad_contact'
- else
- # if not requestable because defunct or not_apply, redirect to main page
- # (which doesn't link to the /new/ URL)
- redirect_to public_body_url(@info_request.public_body)
- end
+ # if not requestable because defunct or not_apply, redirect to main page
+ # (which doesn't link to the /new/ URL)
+ redirect_to public_body_url(@info_request.public_body)
end
end
return
@@ -318,11 +334,14 @@ class RequestController < ApplicationController
# 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>" + simple_date(@info_request.date_response_required_by) + "</strong>.</p>"
+ flash[:notice] = "<p>Thank you! Hopefully your wait isn't too long.</p> <p>By law, you should get a response promptly, and " + (@info_request.public_body.is_school? ? "in term time" : "") + " normally before the end of <strong>" + simple_date(@info_request.date_response_required_by) + "</strong>.</p>"
redirect_to request_url(@info_request)
elsif @info_request.calculate_status == 'waiting_response_overdue'
- flash[:notice] = "<p>Thank you! Hope you don't have to wait much longer.</p> <p>By law, you should have got a response promptly, and normally before the end of <strong>" + simple_date(@info_request.date_response_required_by) + "</strong>.</p>"
+ flash[:notice] = "<p>Thank you! Hope you don't have to wait much longer.</p> <p>By law, you should have got a response promptly, and " + (@info_request.public_body.is_school? ? "in term time" : "") + " normally before the end of <strong>" + simple_date(@info_request.date_response_required_by) + "</strong>.</p>"
redirect_to request_url(@info_request)
+ elsif @info_request.calculate_status == 'waiting_response_very_overdue'
+ flash[:notice] = "<p>Thank you! Your request is long overdue, by more than 40 working days. Most requests should be answered within 20 working days. You might like to complain about this, see below.</p>"
+ redirect_to unhappy_url(@info_request)
elsif @info_request.calculate_status == 'not_held'
flash[:notice] = "<p>Thank you! Here are some ideas on what to do next:</p>
<ul>
@@ -337,7 +356,7 @@ class RequestController < ApplicationController
"
redirect_to request_url(@info_request)
elsif @info_request.calculate_status == 'rejected'
- flash[:notice] = "Oh no! Sorry to hear that your request was rejected. Here is what to do now."
+ flash[:notice] = "Oh no! Sorry to hear that your request was refused. Here is what to do now."
redirect_to unhappy_url(@info_request)
elsif @info_request.calculate_status == 'successful'
flash[:notice] = "<p>We're glad you got all the information that you wanted. If you write about or make use of the information, please come back and add an annotation below saying what you did.</p><p>If you found WhatDoTheyKnow useful, <a href=\"http://www.mysociety.org/donate/\">make a donation</a> to the charity which runs it.</p>"
@@ -360,8 +379,8 @@ class RequestController < ApplicationController
flash[:notice] = "Please use the form below to tell us more."
redirect_to help_general_url(:action => 'contact')
elsif @info_request.calculate_status == 'user_withdrawn'
- flash[:notice] = "Thanks for letting us know that you've withdrawn your request. Please add an annotation below to let other people know why you withdrew it."
- redirect_to request_url(@info_request)
+ flash[:notice] = "If you have not done so already, please write a message below telling the authority that you have withdrawn your request. Otherwise they will not know it has been withdrawn."
+ redirect_to respond_to_last_url(@info_request)
else
raise "unknown calculate_status " + @info_request.calculate_status
end
@@ -519,7 +538,7 @@ class RequestController < ApplicationController
# Test for hidden
incoming_message = IncomingMessage.find(params[:incoming_message_id])
if !incoming_message.info_request.user_can_view?(authenticated_user)
- render :template => 'request/hidden'
+ render :template => 'request/hidden', :status => 410 # gone
end
end
@@ -527,8 +546,10 @@ class RequestController < ApplicationController
around_filter :cache_attachments, :only => [ :get_attachment, :get_attachment_as_html ]
def cache_attachments
key = params.merge(:only_path => true)
- if cached = read_fragment(key)
- #if cached = 'zzz***zzz'
+ key_path = foi_fragment_cache_path(key)
+
+ if File.exists?(key_path)
+ cached = File.read(key_path)
IncomingMessage # load global filename_to_mimetype XXX should move filename_to_mimetype to proper namespace
response.content_type = filename_to_mimetype(params[:file_name].join("/")) or 'application/octet-stream'
render_for_text(cached)
@@ -537,7 +558,14 @@ class RequestController < ApplicationController
yield
- write_fragment(key, response.body)
+ # write it to the fileystem ourselves, so is just a plain file. (The
+ # various fragment cache functions using Ruby Marshall to write the file
+ # which adds a header, so isnt compatible with images that have been
+ # extracted elsewhere from PDFs)
+ FileUtils.mkdir_p(File.dirname(key_path))
+ File.atomic_write(key_path) do |f|
+ f.write(response.body)
+ end
end
def get_attachment
@@ -558,14 +586,16 @@ class RequestController < ApplicationController
# images made during conversion (e.g. images in PDF files) are put in the cache directory, so
# the same cache code in cache_attachments above will display them.
- image_dir = File.dirname(ActionController::Base.cache_store.cache_path + "/views" + url_for(params.merge(:only_path => true)))
+ key = params.merge(:only_path => true)
+ key_path = foi_fragment_cache_path(key)
+ image_dir = File.dirname(key_path)
FileUtils.mkdir_p(image_dir)
- html = @attachment.body_as_html(image_dir)
+ html, wrapper_id = @attachment.body_as_html(image_dir)
view_html_stylesheet = render_to_string :partial => "request/view_html_stylesheet"
html.sub!(/<head>/i, "<head>" + view_html_stylesheet)
- html.sub!(/<body[^>]*>/i, '<body><prefix-here><div id="wrapper"><div id="view_html_content">' + view_html_stylesheet)
- html.sub!(/<\/body[^>]*>/i, '</div></div></body>' + view_html_stylesheet)
+ html.sub!(/<body[^>]*>/i, '<body><prefix-here><div id="' + wrapper_id + '"><div id="view_html_content">')
+ html.sub!(/<\/body[^>]*>/i, '</div></div></body>')
view_html_prefix = render_to_string :partial => "request/view_html_prefix"
html.sub!("<prefix-here>", view_html_prefix)
@@ -595,6 +625,7 @@ class RequestController < ApplicationController
raise "internal error, pre-auth filter should have caught this" if !@info_request.user_can_view?(authenticated_user)
@attachment = IncomingMessage.get_attachment_by_url_part_number(@incoming_message.get_attachments_for_display, @part_number)
+ raise "attachment not found part number " + @part_number.to_s + " incoming_message " + @incoming_message.id.to_s if @attachment.nil?
# check filename in URL matches that in database (use a censor rule if you want to change a filename)
raise "please use same filename as original file has, display: '" + @attachment.display_filename + "' old_display: '" + @attachment.old_display_filename + "' original: '" + @original_filename + "'" if @attachment.display_filename != @original_filename && @attachment.old_display_filename != @original_filename
@@ -631,7 +662,7 @@ class RequestController < ApplicationController
if params[:submitted_upload_response]
file_name = nil
file_content = nil
- if params[:file_1].class.to_s == "ActionController::UploadedTempfile"
+ if !params[:file_1].nil?
file_name = params[:file_1].original_filename
file_content = params[:file_1].read
end
diff --git a/app/controllers/user_controller.rb b/app/controllers/user_controller.rb
index bc117ce2e..b3f9511b5 100644
--- a/app/controllers/user_controller.rb
+++ b/app/controllers/user_controller.rb
@@ -147,10 +147,13 @@ class UserController < ApplicationController
end
# Logout form
- def signout
+ def _do_signout
session[:user_id] = nil
session[:user_circumstance] = nil
session[:remember_me] = false
+ end
+ def signout
+ self._do_signout
if params[:r]
redirect_to params[:r]
else
@@ -159,24 +162,24 @@ class UserController < ApplicationController
end
# Change password (XXX and perhaps later email) - requires email authentication
- def signchange
+ def signchangepassword
if @user and ((not session[:user_circumstance]) or (session[:user_circumstance] != "change_password"))
# Not logged in via email, so send confirmation
- params[:submitted_signchange_send_confirm] = true
- params[:signchange] = { :email => @user.email }
+ params[:submitted_signchangepassword_send_confirm] = true
+ params[:signchangepassword] = { :email => @user.email }
end
- if params[:submitted_signchange_send_confirm]
+ if params[:submitted_signchangepassword_send_confirm]
# They've entered the email, check it is OK and user exists
- if not MySociety::Validate.is_valid_email(params[:signchange][:email])
+ if not MySociety::Validate.is_valid_email(params[:signchangepassword][:email])
flash[:error] = "That doesn't look like a valid email address. Please check you have typed it correctly."
- render :action => 'signchange_send_confirm'
+ render :action => 'signchangepassword_send_confirm'
return
end
- user_signchange = User.find_user_by_email(params[:signchange][:email])
- if user_signchange
- # Send email with login link to go to signchange page
- url = signchange_url
+ user_signchangepassword = User.find_user_by_email(params[:signchangepassword][:email])
+ if user_signchangepassword
+ # Send email with login link to go to signchangepassword page
+ url = signchangepassword_url
if params[:pretoken]
url += "?pretoken=" + params[:pretoken]
end
@@ -188,27 +191,27 @@ class UserController < ApplicationController
},
:circumstance => "change_password" # special login that lets you change your password
)
- post_redirect.user = user_signchange
+ post_redirect.user = user_signchangepassword
post_redirect.save!
url = confirm_url(:email_token => post_redirect.email_token)
- UserMailer.deliver_confirm_login(user_signchange, post_redirect.reason_params, url)
+ UserMailer.deliver_confirm_login(user_signchangepassword, post_redirect.reason_params, url)
else
# User not found, but still show confirm page to not leak fact user exists
end
- render :action => 'signchange_confirm'
+ render :action => 'signchangepassword_confirm'
elsif not @user
# Not logged in, prompt for email
- render :action => 'signchange_send_confirm'
+ render :action => 'signchangepassword_send_confirm'
else
# Logged in via special email change password link, so can offer form to change password
raise "internal error" unless (session[:user_circumstance] == "change_password")
- if params[:submitted_signchange_password]
+ if params[:submitted_signchangepassword_do]
@user.password = params[:user][:password]
@user.password_confirmation = params[:user][:password_confirmation]
if not @user.valid?
- render :action => 'signchange'
+ render :action => 'signchangepassword'
else
@user.save!
flash[:notice] = "Your password has been changed."
@@ -220,11 +223,71 @@ class UserController < ApplicationController
end
end
else
- render :action => 'signchange'
+ render :action => 'signchangepassword'
end
end
end
+ # Change your email
+ def signchangeemail
+ if not authenticated?(
+ :web => "To change your email address used on WhatDoTheyKnow.com",
+ :email => "Then you can change your email address used on WhatDoTheyKnow.com",
+ :email_subject => "Change your email address used on WhatDoTheyKnow.com"
+ )
+ # "authenticated?" has done the redirect to signin page for us
+ return
+ end
+
+ if !params[:submitted_signchangeemail_do]
+ render :action => 'signchangeemail'
+ return
+ end
+
+ @signchangeemail = ChangeEmailValidator.new(params[:signchangeemail])
+ @signchangeemail.logged_in_user = @user
+
+ if !@signchangeemail.valid?
+ render :action => 'signchangeemail'
+ return
+ end
+
+ # if new email already in use, send email there saying what happened
+ user_alreadyexists = User.find_user_by_email(@signchangeemail.new_email)
+ if user_alreadyexists
+ UserMailer.deliver_changeemail_already_used(@user.email, @signchangeemail.new_email)
+ # it is important this screen looks the same as the one below, so
+ # you can't change to someone's email in order to tell if they are
+ # registered with that email on the site
+ render :action => 'signchangeemail_confirm'
+ return
+ end
+
+ # if not already, send a confirmation link to the new email address which logs
+ # them into the old email's user account, but with special user_circumstance
+ if (not session[:user_circumstance]) or (session[:user_circumstance] != "change_email")
+ post_redirect = PostRedirect.new(:uri => signchangeemail_url(), :post_params => params,
+ :circumstance => "change_email" # special login that lets you change your email
+ )
+ post_redirect.user = @user
+ post_redirect.save!
+
+ url = confirm_url(:email_token => post_redirect.email_token)
+ UserMailer.deliver_changeemail_confirm(@user, @signchangeemail.new_email, url)
+ # it is important this screen looks the same as the one above, so
+ # you can't change to someone's email in order to tell if they are
+ # registered with that email on the site
+ render :action => 'signchangeemail_confirm'
+ return
+ end
+
+ # circumstance is 'change_email', so can actually change the email
+ @user.email = @signchangeemail.new_email
+ @user.save!
+ flash[:notice] = "You have now changed your email address used on WhatDoTheyKnow.com"
+ redirect_to user_url(@user)
+ end
+
# Send a message to another user
def contact
@recipient_user = User.find(params[:id])
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 7cc0b0e5d..08908abee 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -59,5 +59,11 @@ module ApplicationHelper
t = highlight_words(t, words, html)
return t
end
+
+ # Use our own algorithm for finding path of cache
+ def foi_cache(name = {}, options = nil, &block)
+ name = @controller.foi_fragment_cache_part_path(name)
+ @controller.fragment_for(output_buffer, name, options, &block)
+ end
end
diff --git a/app/helpers/link_to_helper.rb b/app/helpers/link_to_helper.rb
index 6ee0edb2b..aa63ef65d 100644
--- a/app/helpers/link_to_helper.rb
+++ b/app/helpers/link_to_helper.rb
@@ -33,6 +33,10 @@ module LinkToHelper
return similar_request_url(:url_title => info_request.url_title, :only_path => true)
end
+ def request_details_url(info_request)
+ return details_request_url(:url_title => info_request.url_title, :only_path => true)
+ end
+
# Incoming / outgoing messages
def incoming_message_url(incoming_message)
return request_url(incoming_message.info_request)+"#incoming-"+incoming_message.id.to_s
diff --git a/app/models/change_email_validator.rb b/app/models/change_email_validator.rb
new file mode 100644
index 000000000..ff7f2f931
--- /dev/null
+++ b/app/models/change_email_validator.rb
@@ -0,0 +1,42 @@
+# models/changeemail_validator.rb:
+# Validates email change form submissions.
+#
+# Copyright (c) 2010 UK Citizens Online Democracy. All rights reserved.
+# Email: francis@mysociety.org; WWW: http://www.mysociety.org/
+#
+# $Id: contact_validator.rb,v 1.32 2009-09-17 21:10:05 francis Exp $
+
+class ChangeEmailValidator < ActiveRecord::BaseWithoutTable
+ strip_attributes!
+
+ column :old_email, :string
+ column :new_email, :string
+ column :password, :string
+
+ attr_accessor :logged_in_user
+
+ validates_presence_of :old_email, :message => "^Please enter your old email address"
+ validates_presence_of :new_email, :message => "^Please enter your new email address"
+ validates_presence_of :password, :message => "^Please enter your password"
+
+ def validate
+ if !self.old_email.blank? && !MySociety::Validate.is_valid_email(self.old_email)
+ errors.add(:old_email, "doesn't look like a valid address")
+ end
+
+ if !errors[:old_email]
+ if self.old_email.downcase != self.logged_in_user.email.downcase
+ errors.add(:old_email, "address isn't the same as the address of the account you are logged in with")
+ elsif !self.logged_in_user.has_this_password?(self.password)
+ if !errors[:password]
+ errors.add(:password, "is not correct")
+ end
+ end
+ end
+
+ if !self.new_email.blank? && !MySociety::Validate.is_valid_email(self.new_email)
+ errors.add(:new_email, "doesn't look like a valid address")
+ end
+ end
+
+end
diff --git a/app/models/comment.rb b/app/models/comment.rb
index 0da6c1ce3..4e9976373 100644
--- a/app/models/comment.rb
+++ b/app/models/comment.rb
@@ -45,7 +45,7 @@ class Comment < ActiveRecord::Base
read_attribute(:body)
end
- # So when made invisble it vanishes
+ # So when takes changes it updates, or when made invisble it vanishes
after_save :event_xapian_update
def event_xapian_update
for event in self.info_request_events
diff --git a/app/models/incoming_message.rb b/app/models/incoming_message.rb
index 2348c17b5..316f2683a 100644
--- a/app/models/incoming_message.rb
+++ b/app/models/incoming_message.rb
@@ -29,6 +29,8 @@ require 'htmlentities'
require 'rexml/document'
require 'zip/zip'
require 'mahoro'
+require 'mapi/msg'
+require 'mapi/convert'
# Monkeypatch! Adding some extra members to store extra info in.
module TMail
@@ -51,6 +53,9 @@ $file_extension_to_mime_type = {
"xlsx" => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
"ppt" => 'application/vnd.ms-powerpoint',
"pptx" => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
+ "oft" => 'application/vnd.ms-outlook',
+ "msg" => 'application/vnd.ms-outlook',
+ "tnef" => 'application/ms-tnef',
"tif" => 'image/tiff',
"gif" => 'image/gif',
"jpg" => 'image/jpeg', # XXX add jpeg
@@ -303,10 +308,54 @@ class FOIAttachment
end
end
+ # Whether this type has a "View as HTML"
+ def has_body_as_html?
+ if self.content_type == 'text/plain'
+ return true
+ elsif self.content_type == 'application/vnd.ms-word'
+ return true
+ elsif self.content_type == 'application/vnd.ms-excel'
+ return true
+ elsif self.content_type == 'application/pdf'
+ return true
+ elsif self.content_type == 'application/rtf'
+ return true
+ end
+ return false
+ end
+
+ # Name of type of attachment type - only valid for things that has_body_as_html?
+ def name_of_content_type
+ if self.content_type == 'text/plain'
+ return "Text file"
+ elsif self.content_type == 'application/vnd.ms-word'
+ return "Word document"
+ elsif self.content_type == 'application/vnd.ms-excel'
+ return "Excel spreadsheet"
+ elsif self.content_type == 'application/pdf'
+ return "PDF file"
+ elsif self.content_type == 'application/rtf'
+ return "RTF file"
+ end
+ end
+
# For "View as HTML" of attachment
def body_as_html(dir)
html = nil
+ wrapper_id = "wrapper"
+
+ # simple cases, can never fail
+ if self.content_type == 'text/plain'
+ text = self.body.strip
+ text = CGI.escapeHTML(text)
+ text = MySociety::Format.make_clickable(text)
+ html = text.gsub(/\n/, '<br>')
+ return "<html><head></head><body>" + html + "</body></html>", wrapper_id
+ end
+ # the extractions will also produce image files, which go in the
+ # current directory, so change to the directory the function caller
+ # wants everything in
Dir.chdir(dir) do
tempfile = Tempfile.new('foiextract', '.')
tempfile.print self.body
@@ -317,10 +366,22 @@ class FOIAttachment
system("/usr/bin/wvHtml --charset=UTF-8 " + tempfile.path + " " + tempfile.path + ".html")
html = File.read(tempfile.path + ".html")
File.unlink(tempfile.path + ".html")
+ elsif self.content_type == 'application/vnd.ms-excel'
+ # Don't colorise, e.g. otherwise this one comes out with white
+ # text which is nasty:
+ # http://www.whatdotheyknow.com/request/30485/response/74705/attach/html/2/Empty%20premises%20Sefton.xls.html
+ IO.popen("/usr/bin/xlhtml -nc -a " + tempfile.path + "", "r") do |child|
+ html = child.read()
+ wrapper_id = "wrapper_xlhtml"
+ end
elsif self.content_type == 'application/pdf'
IO.popen("/usr/bin/pdftohtml -nodrm -zoom 1.0 -stdout -enc UTF-8 -noframes " + tempfile.path + "", "r") do |child|
html = child.read()
end
+ elsif self.content_type == 'application/rtf'
+ IO.popen("/usr/bin/unrtf --html " + tempfile.path + "", "r") do |child|
+ html = child.read()
+ end
else
raise "No HTML conversion available for type " + self.content_type
end
@@ -341,30 +402,12 @@ class FOIAttachment
body_without_tags = body.gsub(/\s+/,"").gsub(/\<[^\>]*\>/, "")
contains_images = html.match(/<img/mi) ? true : false
if !$?.success? || html.size == 0 || (body_without_tags.size == 0 && !contains_images)
- return "<html><head></head><body><p>Sorry, the conversion to HTML failed. Please use the download link at the top right.</p></body></html>"
+ return "<html><head></head><body><p>Sorry, the conversion to HTML failed. Please use the download link at the top right.</p></body></html>", wrapper_id
end
- return html
- end
-
- # Whether this type has a "View as HTML"
- def has_body_as_html?
- if self.content_type == 'application/vnd.ms-word'
- return true
- elsif self.content_type == 'application/pdf'
- return true
- end
- return false
+ return html, wrapper_id
end
- # Name of type of attachment type - only valid for things that has_body_as_html?
- def name_of_content_type
- if self.content_type == 'application/vnd.ms-word'
- return "Word document"
- elsif self.content_type == 'application/pdf'
- return "PDF file"
- end
- end
end
class IncomingMessage < ActiveRecord::Base
@@ -419,20 +462,30 @@ class IncomingMessage < ActiveRecord::Base
_count_parts_recursive(p)
end
else
- if part.content_type == 'message/rfc822'
- # An email attached as text
- # e.g. http://www.whatdotheyknow.com/request/64/response/102
- begin
+ part_filename = TMail::Mail.get_part_file_name(part)
+ begin
+ if part.content_type == 'message/rfc822'
+ # An email attached as text
+ # e.g. http://www.whatdotheyknow.com/request/64/response/102
part.rfc822_attachment = TMail::Mail.parse(part.body)
- rescue
- # If attached mail doesn't parse, treat it as text part
- part.rfc822_attachment = nil
- @count_parts_count += 1
- part.url_part_number = @count_parts_count
- else
- _count_parts_recursive(part.rfc822_attachment)
+ elsif part.content_type == 'application/vnd.ms-outlook' || part_filename && filename_to_mimetype(part_filename) == 'application/vnd.ms-outlook'
+ # An email attached as an Outlook file
+ # 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'
+ # A set of attachments in a TNEF file
+ part.rfc822_attachment = TNEF.as_tmail(part.body)
end
+ rescue
+ # If attached mail doesn't parse, treat it as text part
+ part.rfc822_attachment = nil
else
+ unless part.rfc822_attachment.nil?
+ _count_parts_recursive(part.rfc822_attachment)
+ end
+ end
+ if part.rfc822_attachment.nil?
@count_parts_count += 1
part.url_part_number = @count_parts_count
end
@@ -486,7 +539,7 @@ class IncomingMessage < ActiveRecord::Base
uncompressed_text = child.read()
end
# if we managed to uncompress the PDF...
- if !uncompressed_text.nil?
+ if !uncompressed_text.nil? && !uncompressed_text.empty?
# then censor stuff (making a copy so can compare again in a bit)
censored_uncompressed_text = uncompressed_text.dup
self._binary_mask_stuff_internal!(censored_uncompressed_text)
@@ -499,7 +552,7 @@ class IncomingMessage < ActiveRecord::Base
child.close_write()
recompressed_text = child.read()
end
- if !recompressed_text.nil?
+ if !recompressed_text.nil? && !recompressed_text.empty?
text[0..-1] = recompressed_text # [0..-1] makes it change the 'text' string in place
end
end
@@ -707,15 +760,22 @@ class IncomingMessage < ActiveRecord::Base
if curr_mail.sub_type == 'alternative'
# Choose best part from alternatives
best_part = nil
+ # Take the last text/plain one, or else the first one
curr_mail.parts.each do |m|
- # Take the first one, or the last text/plain one
- # XXX - could do better!
if not best_part
best_part = m
elsif m.content_type == 'text/plain'
best_part = m
end
end
+ # Take an HTML one as even higher priority. (They tend
+ # to render better than text/plain, e.g. don't wrap links here:
+ # http://www.whatdotheyknow.com/request/amount_and_cost_of_freedom_of_in#incoming-72238 )
+ curr_mail.parts.each do |m|
+ if m.content_type == 'text/html'
+ best_part = m
+ end
+ end
leaves_found += _get_attachment_leaves_recursive(best_part, within_rfc822_attachment)
else
# Add all parts
@@ -724,6 +784,11 @@ class IncomingMessage < ActiveRecord::Base
end
end
else
+ # XXX Yuck. this section alters various content_type's. That puts
+ # it into conflict with ensure_parts_counted which it has to be
+ # called both before and after. It will fail with cases of
+ # attachments of attachments etc.
+
# Don't allow nil content_types
if curr_mail.content_type.nil?
curr_mail.content_type = 'application/octet-stream'
@@ -746,9 +811,16 @@ class IncomingMessage < ActiveRecord::Base
curr_mail.content_type = 'text/plain'
end
end
+ if curr_mail.content_type == 'application/vnd.ms-outlook' || curr_mail.content_type == 'application/ms-tnef'
+ ensure_parts_counted # fills in rfc822_attachment variable
+ if curr_mail.rfc822_attachment.nil?
+ # Attached mail didn't parse, so treat as binary
+ curr_mail.content_type = 'application/octet-stream'
+ end
+ end
- # If the part is an attachment of email in text form
- if curr_mail.content_type == 'message/rfc822'
+ # If the part is an attachment of email
+ if curr_mail.content_type == 'message/rfc822' || curr_mail.content_type == 'application/vnd.ms-outlook' || curr_mail.content_type == 'application/ms-tnef'
ensure_parts_counted # fills in rfc822_attachment variable
leaves_found += _get_attachment_leaves_recursive(curr_mail.rfc822_attachment, curr_mail.rfc822_attachment)
else
@@ -863,9 +935,13 @@ class IncomingMessage < ActiveRecord::Base
def get_main_body_text_part
leaves = get_attachment_leaves
- # Find first part which is text/plain
+ # 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
+ # e.g. http://www.whatdotheyknow.com/request/list_of_public_authorties)
leaves.each do |p|
- if p.content_type == 'text/plain'
+ if p.content_type == 'text/plain' or p.content_type == 'text/html'
return p
end
end
@@ -898,7 +974,7 @@ class IncomingMessage < ActiveRecord::Base
# e.g. for https://secure.mysociety.org/admin/foi/request/show_raw_email/24550
main_part = get_main_body_text_part
if main_part.nil?
- return
+ return []
end
text = main_part.body
@@ -936,10 +1012,13 @@ class IncomingMessage < ActiveRecord::Base
# Returns all attachments for use in display code
# XXX is this called multiple times and should be cached?
def get_attachments_for_display
- ensure_parts_counted
-
main_part = get_main_body_text_part
leaves = get_attachment_leaves
+
+ # XXX we have to call ensure_parts_counted after get_attachment_leaves
+ # which is really messy.
+ ensure_parts_counted
+
attachments = []
for leaf in leaves
if leaf != main_part
@@ -965,7 +1044,12 @@ class IncomingMessage < ActiveRecord::Base
headers = ""
for header in [ 'Date', 'Subject', 'From', 'To', 'Cc' ]
if leaf.within_rfc822_attachment.header.include?(header.downcase)
- headers = headers + header + ": " + leaf.within_rfc822_attachment.header[header.downcase].to_s + "\n"
+ header_value = leaf.within_rfc822_attachment.header[header.downcase]
+ # Example message which has a blank Date header:
+ # http://www.whatdotheyknow.com/request/30747/response/80253/attach/html/17/Common%20Purpose%20Advisory%20Group%20Meeting%20Tuesday%202nd%20March.txt.html
+ if !header_value.blank?
+ headers = headers + header + ": " + header_value.to_s + "\n"
+ end
end
end
# XXX call _convert_part_body_to_text here, but need to get charset somehow
@@ -1099,11 +1183,14 @@ class IncomingMessage < ActiveRecord::Base
File.unlink(tempfile.path + ".txt")
end
elsif content_type == 'application/rtf'
+ # catdoc on RTF prodcues less comments and extra bumf than --text option to unrtf
IO.popen("/usr/bin/catdoc " + tempfile.path, "r") do |child|
text += child.read() + "\n\n"
end
elsif content_type == 'text/html'
- IO.popen("/usr/bin/lynx -display_charset=UTF-8 -force_html -dump " + tempfile.path, "r") do |child|
+ # lynx wordwraps links in its output, which then don't get formatted properly
+ # by WhatDoTheyKnow. We use elinks instead, which doesn't do that.
+ IO.popen("/usr/bin/elinks -dump-charset utf-8 -force-html -dump " + tempfile.path, "r") do |child|
text += child.read() + "\n\n"
end
elsif content_type == 'application/vnd.ms-excel'
@@ -1273,7 +1360,7 @@ class IncomingMessage < ActiveRecord::Base
prefix = email
prefix =~ /^(.*)@/
prefix = $1
- if !prefix.nil? && prefix.downcase.match(/^(postmaster|mailer-daemon|auto_reply|donotreply)$/)
+ if !prefix.nil? && prefix.downcase.match(/^(postmaster|mailer-daemon|auto_reply|donotreply|no-reply)$/)
return false
end
diff --git a/app/models/info_request.rb b/app/models/info_request.rb
index 7cee3fe1c..f6eec0601 100644
--- a/app/models/info_request.rb
+++ b/app/models/info_request.rb
@@ -56,7 +56,7 @@ class InfoRequest < ActiveRecord::Base
'waiting_clarification',
'gone_postal',
'not_held',
- 'rejected',
+ 'rejected', # this is called 'refused' in UK FOI law and the user interface, but 'rejected' internally for historic reasons
'successful',
'partially_successful',
'internal_review',
@@ -83,7 +83,7 @@ class InfoRequest < ActiveRecord::Base
'authority_only', # only people from authority domains
'nobody'
]
- # what to do with rejected new responses
+ # what to do with refused new responses
validates_inclusion_of :handle_rejected_responses, :in => [
'bounce', # return them to sender
'holding_pen', # put them in the holding pen
@@ -95,6 +95,9 @@ class InfoRequest < ActiveRecord::Base
if !self.title.nil? && !MySociety::Validate.uses_mixed_capitals(self.title, 10)
errors.add(:title, '^Please write the summary using a mixture of capital and lower case letters. This makes it easier for others to read.')
end
+ if !self.title.nil? && title.size > 200
+ errors.add(:title, '^Please keep the summary short, like in the subject of an email. You can use a phrase, rather than a full sentence.')
+ end
end
OLD_AGE_IN_DAYS = 21.days
@@ -132,7 +135,7 @@ class InfoRequest < ActiveRecord::Base
# we update index for every event. Also reindex if prominence changes.
after_update :reindex_some_request_events
def reindex_some_request_events
- if self.changes.include?('url_title') || self.changes.include?('prominence')
+ if self.changes.include?('url_title') || self.changes.include?('prominence') || self.changes.include?('user_id')
self.reindex_request_events
end
end
@@ -199,6 +202,8 @@ public
# Subject lines for emails about the request
def email_subject_request
+ # XXX pull out this general_register_office specialisation
+ # into some sort of separate jurisdiction dependent file
if self.public_body.url_name == 'general_register_office'
# without GQ in the subject, you just get an auto response
self.law_used_full + ' request GQ - ' + self.title
@@ -206,11 +211,15 @@ public
self.law_used_full + ' request - ' + self.title
end
end
- def email_subject_followup
- if self.public_body.url_name == 'general_register_office'
- 'Re: ' + self.law_used_full + ' request GQ - ' + self.title
+ def email_subject_followup(incoming_message = nil)
+ if incoming_message.nil? || !incoming_message.valid_to_reply_to?
+ 'Re: ' + self.email_subject_request
else
- 'Re: ' + self.law_used_full + ' request - ' + self.title
+ if incoming_message.mail.subject.match(/^Re:/i)
+ incoming_message.mail.subject
+ else
+ 'Re: ' + incoming_message.mail.subject
+ end
end
end
@@ -487,10 +496,13 @@ public
# self.described_state, can take these two values:
# waiting_classification
# waiting_response_overdue
+ # waiting_response_very_overdue
def calculate_status
return 'waiting_classification' if self.awaiting_description
return described_state unless self.described_state == "waiting_response"
# Compare by date, so only overdue on next day, not if 1 second late
+ return 'waiting_response_very_overdue' if
+ Time.now.strftime("%Y-%m-%d") > self.date_very_overdue_after.strftime("%Y-%m-%d")
return 'waiting_response_overdue' if
Time.now.strftime("%Y-%m-%d") > self.date_response_required_by.strftime("%Y-%m-%d")
return 'waiting_response'
@@ -568,24 +580,35 @@ public
end
end
if last_sent.nil?
- raise "internal error, date_response_required_by gets nil for request " + self.id.to_s + " outgoing messages count " + self.outgoing_messages.size.to_s + " all events: " + self.info_request_events.to_yaml
+ raise "internal error, last_event_forming_initial_request gets nil for request " + self.id.to_s + " outgoing messages count " + self.outgoing_messages.size.to_s + " all events: " + self.info_request_events.to_yaml
end
return last_sent
end
+ # The last time that the initial request was sent/resent
+ def date_initial_request_last_sent_at
+ last_sent = last_event_forming_initial_request
+ return last_sent.outgoing_message.last_sent_at
+ end
# How do we cope with case where extra info was required from the requester
# by the public body in order to fulfill the request, as per sections 1(3)
# and 10(6b) ? For clarifications this is covered by
# last_event_forming_initial_request. There may be more obscure
# things, e.g. fees, not properly covered.
def date_response_required_by
- last_sent = last_event_forming_initial_request
- return Holiday.due_date_from(last_sent.outgoing_message.last_sent_at, 20)
+ return Holiday.due_date_from(self.date_initial_request_last_sent_at, 20)
end
-
- # Are we more than 20 working days overdue?
- def working_days_20_overdue?
- return Holiday.due_date_from(date_response_required_by.to_date, 20) <= Time.now.to_date
+ # This is a long stop - even with UK public interest test extensions, 40
+ # days is a very long time.
+ def date_very_overdue_after
+ last_sent = last_event_forming_initial_request
+ if self.public_body.is_school?
+ # schools have 60 working days maximum (even over a long holiday)
+ return Holiday.due_date_from(self.date_initial_request_last_sent_at, 60)
+ else
+ # public interest test ICO guidance gives 40 working maximum
+ return Holiday.due_date_from(self.date_initial_request_last_sent_at, 40)
+ end
end
# Where the initial request is sent to
@@ -715,11 +738,13 @@ public
elsif status == 'waiting_response'
"Awaiting response."
elsif status == 'waiting_response_overdue'
- "Response overdue."
+ "Delayed."
+ elsif status == 'waiting_response_very_overdue'
+ "Long overdue."
elsif status == 'not_held'
"Information not held."
elsif status == 'rejected'
- "Rejected."
+ "Refused."
elsif status == 'partially_successful'
"Partially successful."
elsif status == 'successful'
@@ -894,9 +919,9 @@ public
# This is called from cron regularly.
def InfoRequest.stop_new_responses_on_old_requests
# 6 months since last change to request, only allow new incoming messages from authority domains
- InfoRequest.update_all "allow_new_responses_from = 'authority_only' where updated_at < (now() - interval '6 months') and allow_new_responses_from = 'anybody'"
+ InfoRequest.update_all "allow_new_responses_from = 'authority_only' where updated_at < (now() - interval '6 months') and allow_new_responses_from = 'anybody' and url_title <> 'holding_pen'"
# 1 year since last change requests, don't allow any new incoming messages
- InfoRequest.update_all "allow_new_responses_from = 'nobody' where updated_at < (now() - interval '1 year') and allow_new_responses_from in ('anybody', 'authority_only')"
+ InfoRequest.update_all "allow_new_responses_from = 'nobody' where updated_at < (now() - interval '1 year') and allow_new_responses_from in ('anybody', 'authority_only') and url_title <> 'holding_pen'"
end
# Returns a random FOI request
diff --git a/app/models/info_request_event.rb b/app/models/info_request_event.rb
index 4e2a10a1f..1422431dc 100644
--- a/app/models/info_request_event.rb
+++ b/app/models/info_request_event.rb
@@ -282,7 +282,7 @@ class InfoRequestEvent < ActiveRecord::Base
elsif status == 'not_held'
return "Information not held"
elsif status == 'rejected'
- return "Rejection"
+ return "Refused"
elsif status == 'partially_successful'
return "Some information sent"
elsif status == 'successful'
diff --git a/app/models/outgoing_mailer.rb b/app/models/outgoing_mailer.rb
index ae7e86f4e..1546d3fd0 100644
--- a/app/models/outgoing_mailer.rb
+++ b/app/models/outgoing_mailer.rb
@@ -73,7 +73,7 @@ class OutgoingMailer < ApplicationMailer
if outgoing_message.what_doing == 'internal_review'
return "Internal review of " + info_request.email_subject_request
else
- return info_request.email_subject_followup
+ return info_request.email_subject_followup(outgoing_message.incoming_message_followup)
end
end
# Whether we have a valid email address for a followup
diff --git a/app/models/outgoing_message.rb b/app/models/outgoing_message.rb
index 36190ce2f..40abe0b0f 100644
--- a/app/models/outgoing_message.rb
+++ b/app/models/outgoing_message.rb
@@ -39,13 +39,23 @@ class OutgoingMessage < ActiveRecord::Base
# contact address changed
has_many :info_request_events
+ # 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')
+ for info_request_event in self.info_request_events
+ info_request_event.xapian_mark_needs_index
+ end
+ end
+ end
+
# How the default letter starts and ends
def get_salutation
ret = "Dear "
if self.message_type == 'followup' && !self.incoming_message_followup.nil? && !self.incoming_message_followup.safe_mail_from.nil? && self.incoming_message_followup.valid_to_reply_to?
ret = ret + OutgoingMailer.name_for_followup(self.info_request, self.incoming_message_followup)
else
- ret = ret + "Sir or Madam"
+ ret = ret + self.info_request.public_body.name
end
return ret + ","
end
@@ -56,6 +66,9 @@ class OutgoingMessage < ActiveRecord::Base
return "Yours faithfully,"
end
end
+ def get_internal_review_insert_here_note
+ return "GIVE DETAILS ABOUT YOUR COMPLAINT HERE"
+ end
def get_default_letter
if self.what_doing == 'internal_review'
"Please pass this on to the person who conducts Freedom of Information reviews." +
@@ -64,7 +77,7 @@ class OutgoingMessage < ActiveRecord::Base
self.info_request.public_body.name +
"'s handling of my FOI request " +
"'" + self.info_request.title + "'." +
- "\n\n" +
+ "\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
else
@@ -119,9 +132,13 @@ class OutgoingMessage < ActiveRecord::Base
# Check have edited letter
def validate
- if self.body.empty? || self.body =~ /\A#{get_salutation}\s+#{get_signoff}/
+ if self.body.empty? || self.body =~ /\A#{get_salutation}\s+#{get_signoff}/ || self.body =~ /#{get_internal_review_insert_here_note}/
if self.message_type == 'followup'
- errors.add(:body, "^Please enter your follow up message")
+ if self.what_doing == 'internal_review'
+ errors.add(:body, "^Please give details explaining why you want a review")
+ else
+ errors.add(:body, "^Please enter your follow up message")
+ end
elsif
errors.add(:body, "^Please enter your letter requesting information")
else
diff --git a/app/models/post_redirect.rb b/app/models/post_redirect.rb
index edd151730..a17b24d16 100644
--- a/app/models/post_redirect.rb
+++ b/app/models/post_redirect.rb
@@ -61,11 +61,7 @@ class PostRedirect < ActiveRecord::Base
# Makes a random token, suitable for using in URLs e.g confirmation messages.
def self.generate_random_token
- bits = 12 * 8
- # Make range from value to double value, so number of digits in base 36
- # encoding is quite long always.
- rand_num = rand(max = 2**(bits+1)) + 2**bits
- rand_num.to_s(base=36)
+ MySociety::Util.generate_token
end
# Make the token
@@ -90,7 +86,7 @@ class PostRedirect < ActiveRecord::Base
return post_redirects[0]
end
- # Called from cron job delete-old-post-redirects
+ # Called from cron job delete-old-things
def self.delete_old_post_redirects
PostRedirect.delete_all "updated_at < (now() - interval '6 months')"
end
diff --git a/app/models/request_mailer.rb b/app/models/request_mailer.rb
index 1bebb5181..7e2041b4f 100644
--- a/app/models/request_mailer.rb
+++ b/app/models/request_mailer.rb
@@ -85,7 +85,25 @@ class RequestMailer < ApplicationMailer
headers 'Return-Path' => blackhole_email, 'Reply-To' => @from, # not much we can do if the user's email is broken
'Auto-Submitted' => 'auto-generated' # http://tools.ietf.org/html/rfc3834
@recipients = user.name_and_email
- @subject = "You're overdue a response to your FOI request - " + info_request.title
+ @subject = "Delayed response to your FOI request - " + info_request.title
+ @body = { :info_request => info_request, :url => url }
+ end
+
+ # Tell the requester that the public body is very late in replying
+ def very_overdue_alert(info_request, user)
+ respond_url = respond_to_last_url(info_request) + "#followup"
+
+ post_redirect = PostRedirect.new(
+ :uri => respond_url,
+ :user_id => user.id)
+ post_redirect.save!
+ url = confirm_url(:email_token => post_redirect.email_token)
+
+ @from = contact_from_name_and_email
+ headers 'Return-Path' => blackhole_email, 'Reply-To' => @from, # not much we can do if the user's email is broken
+ 'Auto-Submitted' => 'auto-generated' # http://tools.ietf.org/html/rfc3834
+ @recipients = user.name_and_email
+ @subject = "You're long overdue a response to your FOI request - " + info_request.title
@body = { :info_request => info_request, :url => url }
end
@@ -209,21 +227,35 @@ class RequestMailer < ApplicationMailer
for info_request in info_requests
alert_event_id = info_request.last_event_forming_initial_request.id
# Only overdue requests
- if info_request.calculate_status == 'waiting_response_overdue'
+ if ['waiting_response_overdue', 'waiting_response_very_overdue'].include?(info_request.calculate_status)
+ if info_request.calculate_status == 'waiting_response_overdue'
+ alert_type = 'overdue_1'
+ elsif info_request.calculate_status == 'waiting_response_very_overdue'
+ alert_type = 'very_overdue_1'
+ else
+ raise "unknown request status"
+ end
+
# For now, just to the user who created the request
- sent_already = UserInfoRequestSentAlert.find(:first, :conditions => [ "alert_type = 'overdue_1' and user_id = ? and info_request_id = ? and info_request_event_id = ?", info_request.user_id, info_request.id, alert_event_id])
+ sent_already = UserInfoRequestSentAlert.find(:first, :conditions => [ "alert_type = ? and user_id = ? and info_request_id = ? and info_request_event_id = ?", alert_type, info_request.user_id, info_request.id, alert_event_id])
if sent_already.nil?
# Alert not yet sent for this user, so send it
#STDERR.puts "sending overdue alert to info_request " + info_request.id.to_s + " user " + info_request.user_id.to_s + " event " + alert_event_id
store_sent = UserInfoRequestSentAlert.new
store_sent.info_request = info_request
store_sent.user = info_request.user
- store_sent.alert_type = 'overdue_1'
+ store_sent.alert_type = alert_type
store_sent.info_request_event_id = alert_event_id
# Only send the alert if the user can act on it by making a followup
# (otherwise they are banned, and there is no point sending it)
if info_request.user.can_make_followup?
- RequestMailer.deliver_overdue_alert(info_request, info_request.user)
+ if info_request.calculate_status == 'waiting_response_overdue'
+ RequestMailer.deliver_overdue_alert(info_request, info_request.user)
+ elsif info_request.calculate_status == 'waiting_response_very_overdue'
+ RequestMailer.deliver_very_overdue_alert(info_request, info_request.user)
+ else
+ raise "unknown request status"
+ end
end
store_sent.save!
#STDERR.puts "sent " + info_request.user.email
diff --git a/app/models/track_mailer.rb b/app/models/track_mailer.rb
index 9d8e8348d..6c9d9949b 100644
--- a/app/models/track_mailer.rb
+++ b/app/models/track_mailer.rb
@@ -33,16 +33,23 @@ class TrackMailer < ApplicationMailer
now = Time.now()
users = User.find(:all, :conditions => [ "last_daily_track_email < ?", now - 1.day ])
for user in users
- #STDERR.puts "user " + user.url_name
+ #STDERR.puts Time.now.to_s + " user " + user.url_name
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
- #STDERR.puts " track " + track_thing.track_query
+ #STDERR.puts Time.now.to_s + " track " + track_thing.track_query
# What have we alerted on already?
+ #
+ # 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
+ # 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 = {}
- for t in track_thing.track_things_sent_emails
+ tt_sent = track_thing.track_things_sent_emails.find(:all, :conditions => ['created_at > ?', now - 14.days])
+ for t in tt_sent
if not t.info_request_event_id.nil?
done_info_request_events[t.info_request_event_id] = 1
end
@@ -51,19 +58,20 @@ 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, 200, 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
- if result[:model].class.to_s == "InfoRequestEvent"
- if not done_info_request_events.include?(result[:model].id) and track_thing.created_at < result[:model].described_at
- # OK alert this one
- alert_results.push(result)
- end
- else
+ if result[:model].class.to_s != "InfoRequestEvent"
raise "need to add other types to TrackMailer.alert_tracks (unalerted)"
end
+
+ next if track_thing.created_at >= result[:model].described_at # made before the track was created
+ next if result[:model].described_at < now - 7.days # older than 1 week (see 14 days / 7 days in comment above)
+ next if done_info_request_events.include?(result[:model].id) # definitely already done
+
+ # OK alert this one
+ alert_results.push(result)
end
# If there were more alerts for this track, then store them
if alert_results.size > 0
diff --git a/app/models/track_thing.rb b/app/models/track_thing.rb
index 3392a4210..c2036118f 100644
--- a/app/models/track_thing.rb
+++ b/app/models/track_thing.rb
@@ -215,7 +215,7 @@ class TrackThing < ActiveRecord::Base
# 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
- # all rejected requests in which case want to sort by
+ # all refused requests in which case want to sort by
# described (when we discover criteria is met). Rather
# conservatively am picking described, as that will make
# things appear in feed more than the should, rather than less.
diff --git a/app/models/track_things_sent_email.rb b/app/models/track_things_sent_email.rb
index 35229371c..b39dad932 100644
--- a/app/models/track_things_sent_email.rb
+++ b/app/models/track_things_sent_email.rb
@@ -25,6 +25,12 @@ class TrackThingsSentEmail < ActiveRecord::Base
belongs_to :user
belongs_to :public_body
belongs_to :track_thing
+
+ # Called from cron job delete-old-things
+ def self.delete_old_track_things_sent_email
+ TrackThingsSentEmail.delete_all "updated_at < (now() - interval '1 month')"
+ end
+
end
diff --git a/app/models/user.rb b/app/models/user.rb
index bcad6229f..eb8089cf1 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -134,8 +134,7 @@ class User < ActiveRecord::Base
user = self.find_user_by_email(params[:email])
if user
# There is user with email, check password
- expected_password = encrypted_password(params[:password], user.salt)
- if user.hashed_password != expected_password
+ if !user.has_this_password?(params[:password])
user.errors.add_to_base(auth_fail_message)
end
else
@@ -184,7 +183,12 @@ class User < ActiveRecord::Base
self.hashed_password = User.encrypted_password(self.password, self.salt)
end
- # For use in to/from in email messages
+ def has_this_password?(password)
+ expected_password = User.encrypted_password(password, self.salt)
+ return self.hashed_password == expected_password
+ end
+
+# For use in to/from in email messages
def name_and_email
return TMail::Address.address_from_name_and_email(self.name, self.email).to_s
end
diff --git a/app/models/user_info_request_sent_alert.rb b/app/models/user_info_request_sent_alert.rb
index 309466792..dde6fd339 100644
--- a/app/models/user_info_request_sent_alert.rb
+++ b/app/models/user_info_request_sent_alert.rb
@@ -25,6 +25,7 @@ class UserInfoRequestSentAlert < ActiveRecord::Base
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
'new_response_reminder_2', # repeat reminder user to classify the recent response
'new_response_reminder_3', # repeat reminder user to classify the recent response
diff --git a/app/models/user_mailer.rb b/app/models/user_mailer.rb
index 06a115c5b..70ca42675 100644
--- a/app/models/user_mailer.rb
+++ b/app/models/user_mailer.rb
@@ -27,5 +27,26 @@ class UserMailer < ApplicationMailer
@body[:url] = url
end
+ def changeemail_confirm(user, new_email, url)
+ @from = contact_from_name_and_email
+ headers 'Return-Path' => blackhole_email, 'Reply-To' => @from # we don't care about bounces when people are fiddling with their account
+ @recipients = new_email
+ @subject = "Confirm your new email address on WhatDoTheyKnow.com"
+ @body[:name] = user.name
+ @body[:url] = url
+ @body[:old_email] = user.email
+ @body[:new_email] = new_email
+ end
+
+ def changeemail_already_used(old_email, new_email)
+ @from = contact_from_name_and_email
+ headers 'Return-Path' => blackhole_email, 'Reply-To' => @from # we don't care about bounces when people are fiddling with their account
+ @recipients = new_email
+ @subject = "Unable to change email address on WhatDoTheyKnow.com"
+ @body[:old_email] = old_email
+ @body[:new_email] = new_email
+ end
+
+
end
diff --git a/app/views/admin_public_body/import_csv.rhtml b/app/views/admin_public_body/import_csv.rhtml
index 3ca1f4d59..75981a501 100644
--- a/app/views/admin_public_body/import_csv.rhtml
+++ b/app/views/admin_public_body/import_csv.rhtml
@@ -12,15 +12,7 @@
<% form_tag 'import_csv', :multipart => true do %>
<p>
- <label for="tag">Tag to add entries to (maybe you want:
- <% for category, description in PublicBodyCategories::CATEGORIES_BY_TAG %>
- <% if category != "other" %>
- <strong><%= category %></strong>=<%= description %>;
- <% end %>
- <% end %>
- )
- </label>
- <br/>
+ <label for="tag">Tag to add entries to / alter entries for:</label>
<%= text_field_tag 'tag', params[:tag] %>
</p>
@@ -30,10 +22,23 @@
<%= file_field_tag :csv_file, :size => 60 %>
</p>
- <p><strong>Note:</strong> This will report errors in the CSV file. If there are no errors, it will make updates.
- Any changes since last import will be overwritten - e.g. email addresses changed back. Changes and additions
- are reported on a successful import for you to check.
+ <p><strong>Note:</strong> Choose <strong>dry run</strong> to test, without
+ actually altering the database. Choose <strong>upload</strong> to actually
+ make the changes. In either case, you will be shown any errors, or details
+ of the changes. When uploading, any changes since last import will be
+ overwritten - e.g. email addresses changed back.
+ </p>
- <p><%= submit_tag 'Upload' %></p>
+ <p><%= submit_tag 'Dry run' %> <%= submit_tag 'Upload' %></p>
<% end %>
+<hr>
+
+<p>Standard tags:
+ <% for category, description in PublicBodyCategories::CATEGORIES_BY_TAG %>
+ <% if category != "other" %>
+ <strong><%= category %></strong>=<%= description %>;
+ <% end %>
+ <% end %>
+ </p>
+
diff --git a/app/views/admin_request/show.rhtml b/app/views/admin_request/show.rhtml
index b9115dd59..a6256478a 100644
--- a/app/views/admin_request/show.rhtml
+++ b/app/views/admin_request/show.rhtml
@@ -6,7 +6,15 @@
<p>
<% for column in InfoRequest.content_columns %>
<strong><%= column.human_name %>:</strong> <%=h @info_request.send(column.name) %>
- <br/>
+ <% if column.name == 'described_state' %>
+ <strong>Calculated status:</strong> <%= @info_request.calculate_status %>
+ <br/><strong>Initial request last sent at:</strong> <%=@info_request.date_initial_request_last_sent_at.to_date %>
+ <strong>Date response required by:</strong> <%= @info_request.date_response_required_by %>
+ <strong>Very overdue after:</strong> <%= @info_request.date_very_overdue_after %>
+ <% end %>
+ <% if ![ 'allow_new_responses_from' ].include?(column.name) %>
+ <br/>
+ <% end %>
<% end %>
<strong>Created by:</strong> <%= user_both_links(@info_request.user) %> <br>
<strong>Public authority:</strong> <%= public_body_both_links(@info_request.public_body) %> <br>
diff --git a/app/views/admin_track/_some_tracks.rhtml b/app/views/admin_track/_some_tracks.rhtml
index 8aaae7048..72ee5fd95 100644
--- a/app/views/admin_track/_some_tracks.rhtml
+++ b/app/views/admin_track/_some_tracks.rhtml
@@ -5,7 +5,7 @@
<% for column in TrackThing.content_columns %>
<th><%= column.human_name %></th>
<% end %>
- <th>Items sent by email</th>
+ <th>Items sent by email (in last month)</th>
<th>Actions</th>
</tr>
diff --git a/app/views/admin_track/list.rhtml b/app/views/admin_track/list.rhtml
index 1d076edf5..58c87ddba 100644
--- a/app/views/admin_track/list.rhtml
+++ b/app/views/admin_track/list.rhtml
@@ -16,7 +16,7 @@
<% for column in TrackThing.content_columns %>
<th><%= column.human_name %></th>
<% end %>
- <th>Items sent by email</th>
+ <th>Items sent by email (in last month)</th>
</tr>
<% for track_thing in @admin_tracks %>
diff --git a/app/views/comment/new.rhtml b/app/views/comment/new.rhtml
index c0e7e3a22..7d7dfee6f 100644
--- a/app/views/comment/new.rhtml
+++ b/app/views/comment/new.rhtml
@@ -49,7 +49,7 @@ Annotations are so anyone, including you, can help the requester with their requ
<li> Ideas on what <strong>other documents to request</strong> which the authority may hold. </li>
<% end %>
<% if [ 'rejected' ].include?(@info_request.described_state) %>
- <li> Advise on whether the <strong>rejection is legal</strong>, and how to complain about it if not. </li>
+ <li> Advise on whether the <strong>refusal is legal</strong>, and how to complain about it if not. </li>
<% end %>
<% if [ 'internal_review' ].include?(@info_request.described_state) %>
diff --git a/app/views/general/frontpage.rhtml b/app/views/general/frontpage.rhtml
index 00b758e9f..b3fb3f12c 100644
--- a/app/views/general/frontpage.rhtml
+++ b/app/views/general/frontpage.rhtml
@@ -17,7 +17,7 @@
<br>
<br>
- OR, <strong>search</strong> for information others have requested.
+ OR, <strong>search</strong> for information others have requested using WhatDoTheyKnow.com
</p>
<% end %>
</div>
diff --git a/app/views/general/search.rhtml b/app/views/general/search.rhtml
index 1fc93b099..9993e733a 100644
--- a/app/views/general/search.rhtml
+++ b/app/views/general/search.rhtml
@@ -127,7 +127,7 @@
<table class="status_table">
<tr><td><strong><%=search_link('status:waiting_response')%></strong></td><td> Waiting for the public authority to reply </td></tr>
<tr><td><strong><%=search_link('status:not_held')%></strong></td><td> The public authority does not have the information requested </td></tr>
- <tr><td><strong><%=search_link('status:rejected')%></strong></td><td> The request was rejected by the public authority </td></tr>
+ <tr><td><strong><%=search_link('status:rejected')%></strong></td><td> The request was refused by the public authority </td></tr>
<tr><td><strong><%=search_link('status:partially_successful')%></strong></td><td> Some of the information requested has been received </td></tr>
<tr><td><strong><%=search_link('status:successful')%></strong></td><td> All of the information requested has been received </td></tr>
<tr><td><strong><%=search_link('status:waiting_clarification')%></strong></td><td> The public authority would like part of the request explained </td></tr>
diff --git a/app/views/help/about.rhtml b/app/views/help/about.rhtml
index a67c65009..5ff22b23f 100644
--- a/app/views/help/about.rhtml
+++ b/app/views/help/about.rhtml
@@ -58,6 +58,28 @@ If you like what we're doing, then you can
<h1 id="making_requests">Making requests <a href="#making_requests">#</a> </h1>
<dl>
+<dt id="which_authority">I'm not sure which authority to make my request to, how can I find out? <a href="#which_authority">#</a> </dt>
+
+<dd>
+<p>It can be hard to untangle government's complicated structured, and work out
+who knows the information that you want. Here are a few tips:
+<ul>
+<li>Browse or search WhatDoTheyKnow looking for similar requests to yours.</li>
+<li>When you've found an authority you think might have the information, use
+the "home page" link on the right hand side of their page to check what they do
+on their website.</li>
+<li>Contact the authority by phone or email to ask if they hold the kind of
+information you're after.</li>
+<li>Don't worry excessively about getting the right authority. If you get it
+wrong, they ought to advise you who to make the request to instead.
+</li>
+<li>If you've got a thorny case, please <a href="/help/contact">contact us</a> for help.</li>
+</ul>
+
+</dd>
+
+
+
<dt id="missing_body">You're missing the public authority that I want to request from! <a href="#missing_body">#</a> </dt>
<dd>
@@ -70,6 +92,29 @@ to hear from you too.
</dd>
+<dt id="authorities">Why do you include some authorities that aren't formally subject to FOI?<a href="#authorities">#</a> </dt>
+
+<dd>
+<p>WhatDoTheyKnow lets you make requests for information to a range of
+organisations:</p>
+
+<ul>
+ <li> Those formally subject to the FOI Act</li>
+ <li> Those formally subject to the Environmental Regulations (a less well
+ defined group)</li>
+ <li> Those which voluntarily comply with the FOI Act</li>
+ <li> Those which aren't subject to the Act but we think should be, on grounds
+ such as them having significant public responsibilities.
+ </li>
+</ul>
+
+<p>In the last case, we're using the site to lobby for expansion of the
+scope of the FOI Act. Even if an organisation is not legally obliged to respond
+to an FOI request, they can still do so voluntarily.
+</p>
+
+</dd>
+
<dt id="focused">Why must I keep my request focused?<a href="#focused">#</a> </dt>
<dd>
@@ -111,18 +156,20 @@ annotations after submitting the request).
<p>Making an FOI request is nearly always free.</p>
-<p>Sometimes an authority will reject your request, saying that the cost
+<p>Authorities often include unnecessary, scary, boilerplate in
+acknowledgement messages saying they "may" charge a fee. Ignore such notices.
+They hardly ever will actually charge a fee. If they do, they can only charge you if
+you have specifically agreed in advance to pay. <a
+ href="http://www.ico.gov.uk/upload/documents/library/freedom_of_information/practical_application/chargingafee.pdf">More
+ details</a> from the Information Commissioner.
+</p>
+
+<p>Sometimes an authority will refuse your request, saying that the cost
of handling it exceeds £600 (for central government) or £450 (for all other
public authorities). At this point you can refine your
request. e.g. it would be much cheaper for an authority to tell you the amount
spent on marshmallows in the past year than in the past ten years.
-.</p>
-
-<p>There are other rare cases where an authority may say that they want to charge you, such as for postage
-or photocopying. Either way, don't worry, the authority cannot make a charge unless you have
-specifically agreed in advance to pay it. <a
-href="http://www.ico.gov.uk/upload/documents/library/freedom_of_information/practical_application/chargingafee.pdf
-">More details</a> from the Information Commissioner. </p>
+</p>
</dd>
@@ -131,21 +178,18 @@ href="http://www.ico.gov.uk/upload/documents/library/freedom_of_information/prac
<dt id="quickly_response">How quickly will I get a response? <a href="#quickly_response">#</a> </dt>
<dd>
-<p>By law public authorities must respond "promptly", and in most cases not
-later than 20 working days after receiving your request. That date
-is shown on the page for your request. </p>
-
-<p>You will be emailed if this date goes by without a response, so you can send
-the public authority another note to remind them if they are breaking the
-law.</p>
+<p>By law, public authorities must respond <strong>promptly</strong> to
+requests.
+</p>
-<p>There are some cases where the hard deadline is allowed to go beyond the 20
-day period, such as if you had to clarify your request, or if they are a
-school. They will normally say if they are invoking such a reason.
-See '<a href="#days">You've calculated our deadline wrongly!</a>' for
-details about what is allowed. </p>
+<p>Even if they are not prompt, in nearly all cases they must respond within
+20 working days. If you had to clarify your request, or they are a school,
+or one or two other cases, then they may have more time
+(<a href="#days">full details</a>).
-<p>Remember though, they should anyway have responded promptly.</p>
+<p>WhatDoTheyKnow will email you if you don't get a timely response. You can
+then send the public authority a message to remind them, and tell them if they
+are breaking the law.</p>
</dd>
@@ -174,6 +218,21 @@ details about what is allowed. </p>
then read our page '<a href="/help/unhappy">Unhappy about the response you got?</a>'.
</dd>
+<dt id="reuse">It says I can't re-use the information I got!<a href="#reuse">#</a> </dt>
+<dd>
+<p>Authorities often add legal boilerplate about the
+"<a href="http://www.opsi.gov.uk/si/si2005/20051515">Re-Use of Public Sector
+Information Regulations 2005</a>", which at first glance implies you may not
+be able do anything with the information.
+</p>
+
+<p>You can, of course, write articles about the information or summarise it, or
+quote parts of it. We also think you should feel free to republish the
+information in full, just as we do, even though in theory you might not be
+allowed to do so. See <a href="#copyright">our policy on copyright</a>.</p>
+
+</dd>
+
<dt id="ico_help">Can you tell me more of the nitty gritty about the process of making requests? <a href="#ico_help">#</a> </dt>
<dd>
@@ -209,28 +268,35 @@ immediately so we can remove it.</p>
<dt id="private_requests">I'd like to keep my request secret! (At least until I publish my story) <a href="#private_requests">#</a> </dt>
-<dd>WhatDoTheyKnow is currently only designed for public requests. All
+<dd><p>WhatDoTheyKnow is currently only designed for public requests. All
responses that we receive are automatically published on the website for anyone
-to read. You should contact the public authority directly if you would like to
+to read. </p>
+<p>You should contact the public authority directly if you would like to
make a request in private. If you're interested in buying a system which helps
you manage FOI requests in secret, then <a href="/help/contact">contact us</a>.
+</p>
</dd>
<dt id="eir">Why can I only request information about the environment from some authorities? <a href="#eir">#</a> </dt>
-<dd>Some public authorities, such as <a href="http://www.whatdotheyknow.com/body/south_east_water">South East Water</a>,
+<dd>
+<p>Some public authorities, such as <a href="http://www.whatdotheyknow.com/body/south_east_water">South East Water</a>,
don't come under the Freedom of Information Act, but do come under another law called
-the Environmental Information Regulations (EIR). It's a very similar law, so you make a request
-to them using WhatDoTheyKnow in just the same way as an FOI request. The only difference
-is that on the page where you write you request, it reminds you that you can only
-request "environmental information" and tells you what that means. It is quite broad.
-</dd>
+the Environmental Information Regulations (EIR).
+</p>
-<dt id="eir_2">So can I request information using EIR from other authorities? <a href="#eir_2">#</a> </dt>
+<p>It's a very similar law, so you make a request
+to them using WhatDoTheyKnow in just the same way as an FOI request. The only
+difference is that on the page where your write you request, it reminds you
+that you can only request "environmental information" and tells you what that
+means. It is quite broad.
+</p>
-<dd>Yes, just make a Freedom of Information (FOI) request as normal. The
+<p>You can, of course, request environmental information from other
+authorities. Just make a Freedom of Information (FOI) request as normal. The
authority has a duty to work out if the Environmental Information Regulations
(EIR) is the more appropriate legislation to reply under.
+</p>
</dd>
<dt id="multiple">Can I make the same to request to lots of authorities, e.g. all councils? <a href="#multiple">#</a> </dt>
@@ -280,7 +346,8 @@ you ask us to.
<dd>
<p>We publish your request on the Internet so that anybody can read it and
-make use of the information that you have found.
+make use of the information that you have found. We do not normally delete
+requests (<a href="#delete_requests">more details</a>).
</p>
<p>
Your name is tangled up with your request, so has to be published as well.
@@ -317,7 +384,7 @@ Information Commissioner later about the handling of your request.
<ul>
<li>Use a different form of your name. The guidance says
that "Mr Arthur Thomas Roberts" can make a valid request as "Arthur Roberts",
-"A. T. Roberts", or "Mr Roberts", but not as "Arthur" or "A.T.R.".
+"A. T. Roberts", or "Mr Roberts", but <strong>not</strong> as "Arthur" or "A.T.R.".
</li>
<li>Women may use their maiden name.</li>
<li>In most cases, you may use any name by which you are "widely known and/or
@@ -326,7 +393,7 @@ is regularly used".
a company, or the trading name of a sole trader.
<li>Ask someone else to make the request on your behalf.
<li>You may, if you are really stuck, ask us to make the request on
-your behalf. Please <a href="/help/about">contact us</a> with
+your behalf. Please <a href="/help/contact">contact us</a> with
a good reason why you cannot make the request yourself and cannot
ask a friend to. We don't have the resources to do this for everyone.
</ul>
@@ -335,6 +402,28 @@ ask a friend to. We don't have the resources to do this for everyone.
</dd>
+<dt id="delete_requests">Can you delete my requests, or alter my name? <a href="#delete_requests">#</a> </dt>
+
+<dd>
+
+<p>WhatDoTheyKnow is a permanent, public archive of Freedom of
+Information requests. Even though you may not find the response to
+a request useful any more, it may be of interest to others. For this
+reason, we will not delete requests.
+</p>
+
+<p>Under exceptional circumstances we may remove or change your name
+on the website, following similar policy as for the names of
+public servants. Similarly, we may also remove other personal information. See
+'<a href="#takedown">Can you take down personal information about me?</a>'.
+</p>
+
+<p>If you're worried about this before you make your request,
+see the section on <a href="#real_name">pseudonyms</a>.</p>
+
+</dd>
+
+
<dt id="full_address">They've asked for my postal address! <a href="#full_address">#</a> </dt>
<dd>
@@ -470,7 +559,7 @@ needing any email, using the "respond to request" link at the bottom of
each request page.
</dd>
-<dt id="days">You've calculated our deadline wrongly!<a href="#days">#</a> </dt>
+<dt id="days">How do you calculate the deadline shown on request pages?<a href="#days">#</a> </dt>
<dd>
<p>The Freedom of Information Act says:</p>
@@ -493,7 +582,7 @@ to have more of that complexity visible.</p>
</dd>
-<dt id="days2">But really, you calculated it wrong!<a href="#days2">#</a> </dt>
+<dt id="days2">But really, how do you calculate the deadline?<a href="#days2">#</a> </dt>
<dd>
@@ -503,45 +592,78 @@ it is best if they show the hard work they are doing by explaining what is
taking the extra time to do.
</p>
-<p>That said, WhatDoTheyKnow does attempt to show the maximum legal deadline
-for response to each request. Here is the complex detail of how we calculate
-it.</p>
+<p>That said, WhatDoTheyKnow does show the maximum legal deadline
+for response on each request. Here's how we calculate it.</p>
<ul>
<li>If the day we deliver the request by email is a working day, we count that
-as "day zero", even if it was delivered late in the evening. Days end at midnight.
-We then count the next working day as "day one", and so on up to 20 days.</li>
+as "day zero", even if it was delivered late in the evening. Days end at
+midnight. We then count the next working day as "day one", and so on up to
+<strong>20 working days</strong>.</li>
<li>If the day the request email was delivered was a non-working day, we count
the next working day as "day one". Delivery is delivery, even if it happened on
the weekend. Some authorities <a href="http://www.whatdotheyknow.com/request/policy_regarding_body_scans#incoming-1100">disagree with this</a>, our lawyer disagrees with them. </li>
-<li>In theory, authorities can claim a time extension for applying a public
-interest test. We don't think this should be a special reason for delay. There
-are lots of other good reasons the authority might want more time, such as if
-somebody is on holiday and they can't find the information. When
-there's going to be any delay at all, we prefer it if authorities simply
-apologise and explain what they are doing that is taking the extra time, rather
-than resorting to legal minutiae.
-</li>
+<li>Requesters are encouraged to mark when they have <strong>clarified</strong>
+their request so the clock resets, but sometimes they get this wrong. If you
+see a problem with a particular request, let us know and we'll fix it.</li>
+</ul>
-<li>Since June 2009, schools have "20 working days disregarding any working
-day which is not a school day, or 60 working days, whichever is first". Basically,
-cut them some slack if it is holiday time.
-</li>
+<p>The date thus calculated is shown on requests with the text "By law,
+Liverpool City Council should normally have responded by...". There is only
+one case which is not normal, see the next question about
+<a href="#public_interest_test">public interest test time extensions</a>.
+</p>
-<li>Requesters are encouraged to mark when they have clarified their request so
-the clock resets, but sometimes they get this wrong. If you see a problem with
-a particular request, let us know and we'll fix it.</li>
+<p>Schools are also a special case, which WhatDoTheyKnow displays differently.
+</p>
+<ul>
+<li>Since June 2009, <strong>schools</strong> have "20 working days
+disregarding any working day which is not a school day, or 60 working days,
+whichever is first" (<a href="http://www.opsi.gov.uk/si/si2009/draft/ukdsi_9780111477632_en_1">FOI (Time for Compliance with Request) Regulations 2009</a>). WhatDoTheyKnow indicates on requests to schools that the 20 day deadline is only
+during term time, and shows them as definitely overdue after 60 working days
+</li>
</ul>
-<p>If you're getting really nerdy about this, read the <a href="http://www.ico.gov.uk/upload/documents/library/freedom_of_information/detailed_specialist_guides/timeforcompliance.pdf">detailed ICO guidance</a>. Meanwhile,
-remember that the law says authorities must respond <strong>promptly</strong>.
+<p>If you're getting really nerdy about all this, read the <a href="http://www.ico.gov.uk/upload/documents/library/freedom_of_information/detailed_specialist_guides/timeforcompliance.pdf">detailed ICO guidance</a>.
+Meanwhile, remember that the law says authorities must respond
+<strong>promptly</strong>. That's really what matters.</p>
</dd>
+<dt id="public_interest_test">How do you reflect time extensions for public interest tests?<a href="#public_interest_test">#</a> </dt>
+
+<dd>
+
+<p>The Freedom of Information Act lets authorities claim an indefinite time
+extension when applying a <strong>public interest test</strong>. Information
+Commissioner guidance says that it should only be used in "exceptionally
+complex" cases
+(<a href="http://www.ico.gov.uk/upload/documents/library/freedom_of_information/detailed_specialist_guides/foi_good_practice_guidance_4.pdf">FOI Good Practice Guidance No. 4</a>).
+WhatDoTheyKnow doesn't specifically handle this case, which is why we use the
+phrase "should normally have responded by" when the 20 working day time is
+exceeded.
+</p>
+
+<p>The same guidance says that, even in exceptionally complex cases, no
+Freedom of Information request should take more than <strong>40 working days</strong>
+to answer. WhatDoTheyKnow displays requests which are overdue by that much
+with stronger wording to indicate they are definitely late.
+</p>
+
+<p>The Freedom of Information (Scotland) Act does not allow such a public
+interest extension. WhatDoTheyKnow would like to see the law changed to either
+remove the extension from the UK Act, or to reintroduce an absolute time limit
+of 40 working days even with the extension (the House of Lords <a
+href="http://www.publicwhip.org.uk/division.php?date=2000-10-17&amp;number=1&amp;house=lords">voted
+to remove</a> provision for such a time limit during the initial passage
+of the UK Act through Parliament).
+</p>
+</dd>
+
<dt id="large_file">How can I send a large file, which won't go by email?<a href="#large_file">#</a> </dt>
<dd>Instead of email, you can respond to a request directly from your web
@@ -592,9 +714,14 @@ that authorities resend these with the personal information removed.</p>
<dt id="mobiles">Do you publish email addresses or mobile phone numbers? <a href="#mobiles">#</a> </dt>
-<dd>We automatically remove some emails and mobile numbers from responses to requests.
-Please <a href="/help/contact">contact us</a> if we've missed one.
-For technical reasons we don't remove them all from attachments, such as PDFs.
+<dd><p>To prevent spam, we automatically remove most emails and some mobile numbers from
+responses to requests. Please <a href="/help/contact">contact us</a> if we've
+missed one.
+For technical reasons we don't always remove them from attachments, such as certain PDFs.</p>
+<p>If you need to know what an address was that we've removed, please <a
+ href="/help/contact">get in touch with us</a>. Occasionally, an email address
+forms an important part of a response and we will post it up in an obscured
+form in an annotation.
</dd>
<dt id="copyright"><a name="commercial"></a>What is your policy on copyright of documents?<a href="#copyright">#</a> </dt>
@@ -650,8 +777,14 @@ requests, and for good public relations, we'd advise you not to do that.
</li>
<li>
The amazing team of volunteers who run the site, answer your support
- emails, maintain the database of public authorities and so much more.
- Thanks to Tony Bowden, John Cross, Adam McGreggor, Alex Skene, Richard Taylor.
+ emails, maintain the database of public authorities and
+ <a href="http://www.mysociety.org/2009/10/13/behind-whatdotheyknow/">so much more</a>.
+ Thanks to John Cross, Ben Harris, Adam McGreggor, Alex Skene,
+ Richard Taylor.
+</li>
+<li>
+ Volunteers who have provided patches to the code - thanks Peter Collingbourne
+ and Tony Bowden.
</li>
<li>
Everyone who has helped look up FOI email addresses.
diff --git a/app/views/help/unhappy.rhtml b/app/views/help/unhappy.rhtml
index cd302a81a..432c00f2e 100644
--- a/app/views/help/unhappy.rhtml
+++ b/app/views/help/unhappy.rhtml
@@ -14,7 +14,7 @@ to your request '<%=request_link(@info_request) %>'?
<ul>
<li>You didn't get a reply within 20 working days</li>
<li>You did not get all of the information that you requested <strong>or</strong></li>
-<li>Your request was rejected, but without a reason valid under the law</li>
+<li>Your request was refused, but without a reason valid under the law</li>
</ul>
<p>... you can</p>
@@ -86,7 +86,7 @@ get the information by <strong>other means...</strong></p>
<ul>
<li>Make a <strong>new FOI request</strong> for summary information, or for
-documentation relating indirectly to matters in your rejected request.
+documentation relating indirectly to matters in your refused request.
<a href="/help/contact">Ask us for ideas</a> if you're stuck.</li>
<li>If any <strong>other public authorities</strong> or publicly owned companies are involved,
then make FOI requests to them.</li>
diff --git a/app/views/layouts/default.rhtml b/app/views/layouts/default.rhtml
index aad13dc97..0db75380f 100644
--- a/app/views/layouts/default.rhtml
+++ b/app/views/layouts/default.rhtml
@@ -3,14 +3,17 @@
<head>
<!-- <%= javascript_include_tag :defaults %> -->
<script type="text/javascript" src="/jslib/spell/spellChecker.js"></script>
+
<title>
<% if @title %>
<%=@title%> - WhatDoTheyKnow
<% else %>
- WhatDoTheyKnow - file and browse Freedom of Information (FOI) requests
+ WhatDoTheyKnow - make and browse Freedom of Information (FOI) requests
<% end %>
</title>
+ <link rel="shortcut icon" href="/favicon.ico">
+
<%= stylesheet_link_tag 'main', :title => "Main", :rel => "stylesheet" %>
<%= stylesheet_link_tag 'yucky-green', :title => "Yucky Green", :rel => "alternate stylesheet" %>
<!--[if LT IE 7]>
@@ -159,17 +162,14 @@
<script type="text/javascript">
var pkBaseURL = (("https:" == document.location.protocol) ? "https://piwik.mysociety.org/" : "http://piwik.mysociety.org/");
document.write(unescape("%3Cscript src='" + pkBaseURL + "piwik.js' type='text/javascript'%3E%3C/script%3E"));
-</script>
-<script type="text/javascript">
-<!--
-piwik_action_name = '';
-piwik_idsite = 4;
-piwik_url = pkBaseURL + "piwik.php";
-piwik_log(piwik_action_name, piwik_idsite, piwik_url);
-//-->
-</script>
-<noscript><p><img src="http://piwik.mysociety.org/piwik.php?idsite=4" style="border:0" alt=""></p></noscript>
-<!-- /Piwik -->
+</script><script type="text/javascript">
+try {
+var piwikTracker = Piwik.getTracker(pkBaseURL + "piwik.php", 4);
+piwikTracker.trackPageView();
+piwikTracker.enableLinkTracking();
+} catch( err ) {}
+</script><noscript><p><img src="http://piwik.mysociety.org/piwik.php?idsite=4" style="border:0" alt=""/></p></noscript>
+<!-- End Piwik Tag -->
<% end %>
</body>
diff --git a/app/views/public_body/list.rhtml b/app/views/public_body/list.rhtml
index 9b03e65d0..b1be9272a 100644
--- a/app/views/public_body/list.rhtml
+++ b/app/views/public_body/list.rhtml
@@ -1,32 +1,37 @@
<div id="body_sidebar">
-<h1>Show only...</h1>
-
-<h2>Alphabet</h2>
-<ul><li>
- <%= render :partial => 'alphabet' %>
-</li></ul>
-
-<% first_row = true %>
-<% for row in PublicBodyCategories::CATEGORIES_WITH_HEADINGS %>
- <% if row.instance_of?(Array) %>
- <li>
- <%= link_to_unless (@tag == row[0]), row[1], list_public_bodies_url(:tag => row[0]) %>
- </li>
- <% else %>
- <% if not first_row %>
- </ul>
+ <h1>Show only...</h1>
+
+ <h2>Alphabet</h2>
+ <ul><li>
+ <%= render :partial => 'alphabet' %>
+ </li></ul>
+
+ <% first_row = true %>
+ <% for row in PublicBodyCategories::CATEGORIES_WITH_HEADINGS %>
+ <% if row.instance_of?(Array) %>
+ <li>
+ <%= link_to_unless (@tag == row[0]), row[1], list_public_bodies_url(:tag => row[0]) %>
+ </li>
<% else %>
- <% first_row = false %>
+ <% if not first_row %>
+ </ul>
+ <% else %>
+ <% first_row = false %>
+ <% end %>
+ <h2><%=h row%></h2>
+ <ul>
<% end %>
- <h2><%=h row%></h2>
- <ul>
<% end %>
-<% end %>
-</ul>
-<p>
-<a href="/help/about#missing_body">Are we missing a public authority?</a>
-</p>
+ </ul>
+
+ <p>
+ <a href="/help/about#missing_body">Are we missing a public authority?</a>
+ </p>
+ <p>
+ <%= link_to "List of all authorities (CSV)", all_public_bodies_csv_url() %>
+ </p>
+
</div>
<% @title = "Public authorities - " + @description %>
diff --git a/app/views/public_body/show.rhtml b/app/views/public_body/show.rhtml
index f28581cdd..56d4cd75c 100644
--- a/app/views/public_body/show.rhtml
+++ b/app/views/public_body/show.rhtml
@@ -5,15 +5,19 @@
<%= render :partial => 'track/tracking_links', :locals => { :track_thing => @track_thing, :own_request => false, :location => 'sidebar' } %>
<h2>More about this authority</h2>
<% if !@public_body.calculated_home_page.nil? %>
- <%= link_to "Home page", @public_body.calculated_home_page %><br>
+ <%= link_to "Home page of authority", @public_body.calculated_home_page %><br>
<% end %>
<% if !@public_body.publication_scheme.empty? %>
<%= link_to "Publication scheme", @public_body.publication_scheme %><br>
<% end %>
<% if !@public_body.charity_number.empty? %>
- <%= link_to "Charity overview", 'http://www.charity-commission.gov.uk/SHOWCHARITY/RegisterOfCharities/CharityFramework.aspx?RegisteredCharityNumber=' + @public_body.charity_number %><br>
+ <% if @public_body.charity_number.match(/^SC/) %>
+ <%= link_to "Charity overview", "http://www.oscr.org.uk/CharityIndexDetails.aspx?id=" + @public_body.charity_number %><br>
+ <% else %>
+ <%= link_to "Charity overview", "http://www.charity-commission.gov.uk/SHOWCHARITY/RegisterOfCharities/CharityFramework.aspx?RegisteredCharityNumber=" + @public_body.charity_number %><br>
+ <% end %>
<% end %>
- <%= link_to "View FOI email address", view_public_body_email_url(@public_body.url_name) %>
+ <%= link_to "View FOI email address", view_public_body_email_url(@public_body.url_name) %><br>
</div>
<h1><%=h(@public_body.name)%></h1>
@@ -63,18 +67,18 @@
<% if !@xapian_requests.nil? %>
<% if @xapian_requests.results.empty? %>
<% if @public_body.eir_only? %>
- <h2>Environmental Information Regulations requests made</h2>
+ <h2>Environmental Information Regulations requests made using this site</h2>
<p>Nobody has made any Environmental Information Regulations requests to <%=h(@public_body.name)%> using this site yet.</p>
<% else %>
- <h2>Freedom of Information requests made</h2>
+ <h2>Freedom of Information requests made using this site</h2>
<p>Nobody has made any Freedom of Information requests to <%=h(@public_body.name)%> using this site yet.</p>
<% end %>
<% else %>
<h2>
<% if @public_body.eir_only? %>
- <%=pluralize(@public_body.info_requests.size, "Environmental Information Regulations request") %> made
+ <%=pluralize(@public_body.info_requests.size, "Environmental Information Regulations request") %> made using this site
<% else %>
- <%=pluralize(@public_body.info_requests.size, "Freedom of Information request") %> made
+ <%=pluralize(@public_body.info_requests.size, "Freedom of Information request") %> made using this site
<% end %>
<%= @page_desc %>
</h2>
diff --git a/app/views/request/_after_actions.rhtml b/app/views/request/_after_actions.rhtml
index d51c6bfb3..9bef04ce4 100644
--- a/app/views/request/_after_actions.rhtml
+++ b/app/views/request/_after_actions.rhtml
@@ -24,7 +24,7 @@
<% if @last_response.nil? %>
<%= link_to "Send follow up to " + OutgoingMailer.name_for_followup(@info_request, @last_response), show_response_no_followup_url(:id => @info_request.id, :incoming_message_id => nil) + "#followup" %>
<% else %>
- <% cache(:controller => "request", :action => "show_response", :id => @info_request.id, :incoming_message_id => @last_response.id, :only_path => true, :template => "_after_actions", :section => "reply_to_link") do %>
+ <% foi_cache(:controller => "request", :action => "show_response", :id => @info_request.id, :incoming_message_id => @last_response.id, :only_path => true, :template => "_after_actions", :section => "reply_to_link") do %>
<%= link_to "Reply to " + OutgoingMailer.name_for_followup(@info_request, @last_response), show_response_url(:id => @info_request.id, :incoming_message_id => @last_response.id) + "#followup" %>
<% end %>
<% end %>
diff --git a/app/views/request/_correspondence.rhtml b/app/views/request/_correspondence.rhtml
index 90beb4050..0756b0797 100644
--- a/app/views/request/_correspondence.rhtml
+++ b/app/views/request/_correspondence.rhtml
@@ -6,7 +6,7 @@ end
if not incoming_message.nil?
%>
<div class="correspondence" id="incoming-<%=incoming_message.id.to_s%>">
- <% cache(:controller => "request", :action => "show_response", :id => @info_request.id, :incoming_message_id => incoming_message.id, :only_path => true, :template => "_correspondence", :section => "incoming_message_bubble", :collapse => @collapse_quotes ? nil : 'no' ) do %>
+ <% foi_cache(:controller => "request", :action => "show_response", :id => @info_request.id, :incoming_message_id => incoming_message.id, :only_path => true, :template => "_correspondence", :section => "incoming_message_bubble", :collapse => @collapse_quotes ? nil : 'no' ) do %>
<h2>
<% if !incoming_message.safe_mail_from.nil? && incoming_message.safe_mail_from.strip != @info_request.public_body.name.strip %>
<%=h incoming_message.safe_mail_from %><br>
diff --git a/app/views/request/_describe_state.rhtml b/app/views/request/_describe_state.rhtml
index 4496fddb6..a91231ae8 100644
--- a/app/views/request/_describe_state.rhtml
+++ b/app/views/request/_describe_state.rhtml
@@ -58,7 +58,7 @@
</div>
<div>
<%= radio_button "incoming_message", "described_state", "rejected", :id => 'rejected' + id_suffix %>
- <label for="rejected<%=id_suffix%>">My request has been <strong>rejected</strong></label>
+ <label for="rejected<%=id_suffix%>">My request has been <strong>refused</strong></label>
</div>
<hr> <!------------------------------------------------>
diff --git a/app/views/request/_followup.rhtml b/app/views/request/_followup.rhtml
index 2429c63b2..b375befd1 100644
--- a/app/views/request/_followup.rhtml
+++ b/app/views/request/_followup.rhtml
@@ -19,46 +19,42 @@
<a href="/help/contact">contact us</a> if you are <%= user_link(@info_request.user) %>
and need to send a follow up.</p>
<% else %>
-
-<!-- <p>Please do <strong>not</strong> make new requests here.
- If you are asking for information which was not in your original request, then
- <%= link_to "file a new request", new_request_to_body_url(:public_body_id => @info_request.public_body.id.to_s) %>
- instead.
- </p>
- -->
-
<% if @internal_review %>
<p>
If you are dissatisfied by the response you got from
the public authority, you have the right to
complain (<a href="http://foiwiki.com/foiwiki/index.php/Internal_reviews">details</a>).
</p>
-
<% end %>
- <% if @info_request.calculate_status == 'waiting_response_overdue' %>
- <p>
- <% if @info_request.working_days_20_overdue? %>
- This request is <strong>long overdue a response</strong>.
- <% else %>
- This request is <strong>overdue a response</strong>.
- <% end %>
- You can say that, by law, the authority should have answered
- <strong>promptly</strong>. If they have not given you a legal
- reason why they need extra time
- (<%= link_to "more details", about_url + "#quickly_response" %>), then
- you can say they are breaking the law to have not replied by
- <strong><%= simple_date(@info_request.date_response_required_by) %></strong>.
+ <p>Please <strong>only</strong> write messages directly relating to your
+ request '<%= request_link(@info_request) %>'. If you would like to ask for information
+ that was not in your original request, then
+ <%= link_to "file a new request", new_request_to_body_url(:public_body_id => @info_request.public_body.id.to_s) %>.
+ </p>
+
+ <% status = @info_request.calculate_status %>
+ <% if status == 'waiting_response_overdue' %>
+ <p>The response to your request has been <strong>delayed</strong>. You can say that,
+ by law, the authority should normally have responded
+ <strong>promptly</strong> and
<% if @info_request.public_body.is_school? %>
- This is a school, so legally they get lots of extra slack if it is
- holiday time.
+ in term time
<% end %>
- </p>
+ by <strong><%= simple_date(@info_request.date_response_required_by) %></strong>
+ (<%= link_to "details", about_url + "#quickly_response" %>).
+ </p>
+ <% elsif status == 'waiting_response_very_overdue' %>
+ <p>
+ The response to your request is <strong>long overdue</strong>. You can say that, by
+ law, under all circumstances, the authority should have responded
+ by now (<%= link_to "details", about_url + "#quickly_response" %>).
+ </p>
<% end %>
<% form_for(:outgoing_message, @outgoing_message, :html => { :id => 'followup_form' }, :url => incoming_message.nil? ? show_response_no_followup_url(:id => @info_request.id) : show_response_url(:id => @info_request.id, :incoming_message_id => incoming_message.id)) do |o| %>
<p>
- <%= o.text_area :body, :rows => 10, :cols => 55 %>
+ <%= o.text_area :body, :rows => 15, :cols => 55 %>
<br><script type="text/javascript">document.write('<input name="doSpell" type="button" value="Check spelling" onClick="openSpellChecker(document.getElementById(\'followup_form\').body);"/> (optional)')</script>
</p>
@@ -93,12 +89,7 @@
<% if @internal_review %>
<p>Edit and add <strong>more details</strong> to the message above,
- explaining why you would like a review.
- <ul>
- <li>Say that you are dissatisfied by their response</li>
- <li>Set out your reasons why</li>
- <li>Ask them to review their response</li>
- </ul>
+ explaining why you are dissatisfied with their response.
</p>
<% end %>
diff --git a/app/views/request/_hidden_correspondence.rhtml b/app/views/request/_hidden_correspondence.rhtml
new file mode 100644
index 000000000..2c168c3fb
--- /dev/null
+++ b/app/views/request/_hidden_correspondence.rhtml
@@ -0,0 +1,37 @@
+<% if info_request_event.prominence == 'requester_only' %>
+ <%
+ if !info_request_event.nil? && info_request_event.event_type == 'response'
+ incoming_message = info_request_event.incoming_message
+ end
+ if not incoming_message.nil?
+ %>
+ <div class="correspondence" id="incoming-<%=incoming_message.id.to_s%>">
+ <p>This response has been hidden. See annotations to find out why.
+ If you are the requester, then you may
+ <%= link_to "sign in", signin_url(:r => request.request_uri) %>
+ to view the response.
+ </p>
+ </div>
+ <% elsif [ 'sent', 'followup_sent', 'resent', 'followup_resent' ].include?(info_request_event.event_type) %>
+ <div class="correspondence" id="outgoing-<%=outgoing_message.id.to_s%>">
+ <p>This outgoing message has been hidden. See annotations to
+ find out why. If you are the requester, then you may <%= link_to
+ "sign in", signin_url(:r => request.request_uri) %> to view the
+ response.
+ </p>
+ </div>
+ <% elsif info_request_event.event_type == 'comment' %>
+ <div class="comment_in_request" id="comment-<%=comment.id.to_s%>">
+ <p>This comment has been hidden. See annotations to
+ find out why. If you are the requester, then you may <%= link_to
+ "sign in", signin_url(:r => request.request_uri) %> to view the
+ response.
+ </p>
+ </div>
+ <% end %>
+
+<% elsif info_request_event.prominence == 'hidden' %>
+ <% # show nothing when hidden %>
+<% else %>
+ <% raise "unexpected prominence on request event" %>
+<% end %>
diff --git a/app/views/request/_other_describe_state.rhtml b/app/views/request/_other_describe_state.rhtml
index 866e90142..66f64c27a 100644
--- a/app/views/request/_other_describe_state.rhtml
+++ b/app/views/request/_other_describe_state.rhtml
@@ -54,7 +54,7 @@
</div>
<div>
<%= radio_button "incoming_message", "described_state", "rejected", :id => 'rejected' + id_suffix %>
- <label for="rejected<%=id_suffix%>">The request has been <strong>rejected</strong></label>
+ <label for="rejected<%=id_suffix%>">The request has been <strong>refused</strong></label>
</div>
<hr> <!------------------------------------------------>
diff --git a/app/views/request/_sidebar.rhtml b/app/views/request/_sidebar.rhtml
index 0cb9207d8..1509bf494 100644
--- a/app/views/request/_sidebar.rhtml
+++ b/app/views/request/_sidebar.rhtml
@@ -31,6 +31,7 @@
<!-- Important terms: <%= @xapian_similar.important_terms.join(" ") %> -->
<% end %>
+ <p><%= link_to "Event history details", request_details_url(@info_request) %></p>
<p><a href="/help/about#commercial">Are you the owner of
any commercial copyright on this page?</a></p>
-</div> \ No newline at end of file
+</div>
diff --git a/app/views/request/details.rhtml b/app/views/request/details.rhtml
new file mode 100644
index 000000000..db7e652f6
--- /dev/null
+++ b/app/views/request/details.rhtml
@@ -0,0 +1,54 @@
+<% @title = "Details of request '" + h(@info_request.title) + "'" %>
+<h1><%="Details of request '" + request_link(@info_request) + "'" %></h1>
+
+<h2>Event history</h2>
+
+<p>This table shows the technical details of the internal events that happened
+to this request on WhatDoTheyKnow. This could be used to generate information about
+the speed with which authorities respond to requests, the number of requests
+which require a postal response and much more.
+</p>
+
+<p><strong>Caveat emptor!</strong> To use this data in an honourable way, you will need
+a good internal knowledge of user behaviour on WhatDoTheyKnow. How,
+why and by whom requests are categorised is not straightforward, and there will
+be user error and ambiguity. You will also need to understand FOI law, and the
+way authorities use it. Plus you'll need to be an elite statistician. Please
+<a href="/help/contact">contact us</a> with questions.
+</p>
+
+<% columns = ['id', 'event_type', 'created_at', 'described_state', 'calculated_state', 'last_described_at' ] %>
+
+<table>
+ <tr>
+ <% for column in @columns%>
+ <th><%= column %></th>
+ <% end %>
+ <th>link</th>
+ </tr>
+
+<% for info_request_event in @info_request.info_request_events.find(:all, :order => "created_at, id") %>
+ <tr class="<%= cycle('odd', 'even') %>">
+ <% for column in @columns %>
+ <td>
+ <%=h info_request_event.send(column) %>
+ </td>
+ <% end %>
+ <td>
+ <% if info_request_event.outgoing_message %>
+ <%= link_to "outgoing", outgoing_message_url(info_request_event.outgoing_message) %>
+ <% end %>
+ <% if info_request_event.incoming_message %>
+ <%= link_to "incoming", incoming_message_url(info_request_event.incoming_message) %>
+ <% end %>
+ </td>
+ </tr>
+<% end %>
+</table>
+
+<p>Here <strong>described</strong> means when a user selected a status for the request, and
+the most recent event had its status updated to that value. <strong>calculated</strong> is then inferred by
+WhatDoTheyKnow for intermediate events, which weren't given an explicit
+description by a user. See the <a href="/search">search tips</a> for description of the states.</p>
+
+
diff --git a/app/views/request/new.rhtml b/app/views/request/new.rhtml
index aa9bf254d..b48966e2f 100644
--- a/app/views/request/new.rhtml
+++ b/app/views/request/new.rhtml
@@ -124,7 +124,7 @@
<p class="form_note">
Everything that you enter on this page, including <strong>your name</strong>,
will be <strong>displayed publicly</strong> on
- this website (<a href="/help/about/#public_request">why?</a>).
+ this website forever (<a href="/help/about/#public_request">why?</a>).
If you are thinking of using a pseudonym,
please <a href="/help/about/#real_name">read this first</a>.
</p>
@@ -132,7 +132,7 @@
<p class="form_note">
Everything that you enter on this page
will be <strong>displayed publicly</strong> on
- this website (<a href="/help/about/#public_request">why?</a>).
+ this website forever (<a href="/help/about/#public_request">why?</a>).
</p>
<% end %>
diff --git a/app/views/request/show.rhtml b/app/views/request/show.rhtml
index 5b89a6d12..97dc32512 100644
--- a/app/views/request/show.rhtml
+++ b/app/views/request/show.rhtml
@@ -56,34 +56,34 @@
<% end %>
<% elsif @status == 'waiting_response' %>
Currently <strong>waiting for a response</strong> from <%= public_body_link(@info_request.public_body) %>,
- they <%= link_to "must respond", about_url + "#quickly_response" %>
- promptly but no later than <strong><%= simple_date(@info_request.date_response_required_by) %></strong>.
- <% elsif @status == 'waiting_response_overdue' %>
- <% if @info_request.working_days_20_overdue? %>
- This request is <strong>long overdue a response</strong>.
- By law, <%= public_body_link(@info_request.public_body) %>
- should normally have answered by
- <strong><%= simple_date(@info_request.date_response_required_by) %></strong>
- (<%= link_to "more details", about_url + "#quickly_response" %>).
- You can <strong>complain</strong> by
- <%= link_to "requesting an internal review", show_response_no_followup_url(:id => @info_request.id, :incoming_message_id => nil) + "?internal_review=1#followup" %>.
- <% else %>
- This request is <strong>overdue a response</strong>.
- By law, <%= public_body_link(@info_request.public_body) %>
- should normally have answered by
- <strong><%= simple_date(@info_request.date_response_required_by) %></strong>.
- If they need extra time they should have told you
- why (<%= link_to "more details", about_url + "#quickly_response" %>).
- <% end %>
+ they must respond promptly and
<% if @info_request.public_body.is_school? %>
- This is a school, so legally they get lots of extra slack if it is
- holiday time.
+ in term time
+ <% else %>
+ normally
<% end %>
-
+ no later than <strong><%= simple_date(@info_request.date_response_required_by) %></strong>
+ (<%= link_to "details", about_url + "#quickly_response" %>).
+ <% elsif @status == 'waiting_response_overdue' %>
+ Response to this request is <strong>delayed</strong>.
+ By law, <%= public_body_link(@info_request.public_body) %> should
+ normally have responded <strong>promptly</strong> and
+ <% if @info_request.public_body.is_school? %>
+ in term time
+ <% end %>
+ by <strong><%= simple_date(@info_request.date_response_required_by) %></strong>
+ (<%= link_to "details", about_url + "#quickly_response" %>).
+ <% elsif @status == 'waiting_response_very_overdue' %>
+ Response to this request is <strong>long overdue</strong>.
+ By law, under all circumstances, <%= public_body_link(@info_request.public_body) %>
+ should have responded by now
+ (<%= link_to "details", about_url + "#quickly_response" %>).
+ You can <strong>complain</strong> by
+ <%= link_to "requesting an internal review", show_response_no_followup_url(:id => @info_request.id, :incoming_message_id => nil) + "?internal_review=1#followup" %>.
<% elsif @status == 'not_held' %>
<%= public_body_link(@info_request.public_body) %> <strong>did not have</strong> the information requested.
<% elsif @status == 'rejected' %>
- The request was <strong>rejected</strong> by <%= public_body_link(@info_request.public_body) %>.
+ The request was <strong>refused</strong> by <%= public_body_link(@info_request.public_body) %>.
<% elsif @status == 'successful' %>
The request was <strong>successful</strong>.
<% elsif @status == 'partially_successful' %>
diff --git a/app/views/request/show_response.rhtml b/app/views/request/show_response.rhtml
index 1d841c3a8..ed32a1b67 100644
--- a/app/views/request/show_response.rhtml
+++ b/app/views/request/show_response.rhtml
@@ -10,7 +10,7 @@
<% if @gone_postal %>
<div class="gone_postal_help">
- <h1>What exactly is happening?</h1>
+ <h1>Which of these is happening?</h1>
<dl>
diff --git a/app/views/request_mailer/overdue_alert.rhtml b/app/views/request_mailer/overdue_alert.rhtml
index ab2faf212..29a1a1d68 100644
--- a/app/views/request_mailer/overdue_alert.rhtml
+++ b/app/views/request_mailer/overdue_alert.rhtml
@@ -1,10 +1,10 @@
-<%= @info_request.public_body.name %> are late.
+<%= @info_request.public_body.name %> have delayed.
They have not replied to your <%=@info_request.law_used_short%> request '<%= @info_request.title %>'
-promptly, as required by law.
+promptly, as normally required by law<% if @info_request.public_body.is_school? %> during term time<% end %>.
Click on the link below to send a message to <%= @info_request.public_body.name
-%> reminding them to reply to your request. <% if @info_request.public_body.is_school? %> This is a school, so legally they get lots of extra slack if it is holiday time. <% end %>
+%> reminding them to reply to your request.
<%=@url%>
diff --git a/app/views/request_mailer/very_overdue_alert.rhtml b/app/views/request_mailer/very_overdue_alert.rhtml
new file mode 100644
index 000000000..2393d29e5
--- /dev/null
+++ b/app/views/request_mailer/very_overdue_alert.rhtml
@@ -0,0 +1,14 @@
+<%= @info_request.public_body.name %> are long overdue.
+
+They have not replied to your <%=@info_request.law_used_short%> request '<%= @info_request.title %>',
+as required by law<% if @info_request.public_body.is_school? %> even during holidays<% end %>.
+
+Click on the link below to send a message to <%= @info_request.public_body.name
+%> telling them to reply to your request. You might like to ask for an internal
+review, asking them to find out why response to the request has been so slow.
+
+<%=@url%>
+
+-- the WhatDoTheyKnow team
+
+
diff --git a/app/views/user/_signin.rhtml b/app/views/user/_signin.rhtml
index 812bf9b4b..52c2a9e71 100644
--- a/app/views/user/_signin.rhtml
+++ b/app/views/user/_signin.rhtml
@@ -18,7 +18,7 @@
</p>
<p class="form_note">
- <%= link_to "Forgotten your password?", signchange_url + "?pretoken=" + h(params[:token]) %>
+ <%= link_to "Forgotten your password?", signchangepassword_url + "?pretoken=" + h(params[:token]) %>
</p>
<p class="form_checkbox">
diff --git a/app/views/user/show.rhtml b/app/views/user/show.rhtml
index 59a9cfcd4..4d2020cdc 100644
--- a/app/views/user/show.rhtml
+++ b/app/views/user/show.rhtml
@@ -50,7 +50,8 @@
<%= link_to "Send message to " + h(@display_user.name), contact_user_url(:id => @display_user.id) %>
<% if @is_you %>
(just to see how it works)
- <br><%= link_to "Change your password", signchange_url() %>
+ <br><%= link_to "Change your password", signchangepassword_url() %> |
+ <br><%= link_to "Change your email", signchangeemail_url() %> |
<br><%= link_to "Set profile photo", profile_photo_url() %>
<% end %>
</p>
diff --git a/app/views/user/signchange_send_confirm.rhtml b/app/views/user/signchange_send_confirm.rhtml
deleted file mode 100644
index e1462760c..000000000
--- a/app/views/user/signchange_send_confirm.rhtml
+++ /dev/null
@@ -1,30 +0,0 @@
-<% @title = "Change password" %>
-
-<div id="change_password">
-
-<% form_tag({:action => "signchange"}, {:id => "signchange_form"}) do %>
- <%= foi_error_messages_for :signchange %>
-
- <div class="form_note">
- <h1>Change your password</h1>
- </div>
-
- <p>
- <label class="form_label" for="signchange_email">Your e-mail:</label>
- <%= text_field 'signchange', 'email', { :size => 20 } %>
- </p>
-
- <p class="form_note">
- <strong>Note:</strong>
- We will send you an email. Follow the instructions in it to change
- your password.
- </p>
-
- <div class="form_button">
- <%= hidden_field_tag 'submitted_signchange_send_confirm', 1 %>
- <%= hidden_field_tag 'pretoken', params[:pretoken] %>
- <%= submit_tag "Submit" %>
- </div>
-<% end %>
-
-</div>
diff --git a/app/views/user/signchangeemail.rhtml b/app/views/user/signchangeemail.rhtml
new file mode 100644
index 000000000..b98dc383c
--- /dev/null
+++ b/app/views/user/signchangeemail.rhtml
@@ -0,0 +1,41 @@
+<% @title = "Change your email address used on WhatDoTheyKnow.com" %>
+
+<% raise "internal error" if not @user %>
+
+<div id="change_email">
+
+<% form_tag({:action => "signchangeemail"}, {:id => "signchangeemail_form"}) do %>
+ <%= foi_error_messages_for :signchangeemail %>
+
+ <div class="form_note">
+ <h1>Change your email address used on WhatDoTheyKnow.com</h1>
+ </div>
+
+ <p>
+ <label class="form_label" for="signchangeemail_old_email">Old e-mail:</label>
+ <%= text_field 'signchangeemail', 'old_email', { :size => 20 } %>
+ </p>
+
+ <p>
+ <label class="form_label" for="signchangeemail_new_email">New e-mail:</label>
+ <%= text_field 'signchangeemail', 'new_email', { :size => 20 } %>
+ </p>
+
+ <p>
+ <label class="form_label" for="signchangeemail_password">Your password:</label>
+ <%= password_field 'signchangeemail', 'password', { :size => 15 } %>
+ </p>
+
+ <p class="form_note">
+ <strong>Note:</strong>
+ We will send an email to your new email address. Follow the
+ instructions in it to confirm changing your email.
+ </p>
+
+ <div class="form_button">
+ <%= hidden_field_tag 'submitted_signchangeemail_do', 1 %>
+ <%= submit_tag "Change email on WhatDoTheyKnow.com" %>
+ </div>
+<% end %>
+
+</div>
diff --git a/app/views/user/signchangeemail_confirm.rhtml b/app/views/user/signchangeemail_confirm.rhtml
new file mode 100644
index 000000000..96acbf424
--- /dev/null
+++ b/app/views/user/signchangeemail_confirm.rhtml
@@ -0,0 +1,14 @@
+<% @title = h("Now check your email!") %>
+
+<h1 class="confirmation_heading">Now check your email!</h1>
+
+<p class="confirmation_message">
+We've sent an email to your new email address. You'll need to click the link in
+it before your email address will be changed.
+</p>
+
+<p class="confirmation_message">
+<small>If you use web-based email or have "junk mail" filters, also check your
+bulk/spam mail folders. Sometimes, our messages are marked that way.</small>
+</p>
+
diff --git a/app/views/user/signchange.rhtml b/app/views/user/signchangepassword.rhtml
index 032d80945..4191344cb 100644
--- a/app/views/user/signchange.rhtml
+++ b/app/views/user/signchangepassword.rhtml
@@ -1,14 +1,14 @@
-<% @title = "Change password" %>
+<% @title = "Change your password on WhatDoTheyKnow.com" %>
<% raise "internal error" if not @user %>
<div id="change_password">
-<% form_tag({:action => "signchange"}, {:id => "signchange_form"}) do %>
+<% form_tag({:action => "signchangepassword"}, {:id => "signchangepassword_form"}) do %>
<%= foi_error_messages_for :user %>
<div class="form_note">
- <h1>Change your password</h1>
+ <h1>Change your password on WhatDoTheyKnow.com</h1>
</div>
<p>
@@ -22,9 +22,9 @@
</p>
<div class="form_button">
- <%= hidden_field_tag 'submitted_signchange_password', 1 %>
+ <%= hidden_field_tag 'submitted_signchangepassword_do', 1 %>
<%= hidden_field_tag 'pretoken', params[:pretoken] %>
- <%= submit_tag "Change password" %>
+ <%= submit_tag "Change password on WhatDoTheyKnow.com" %>
</div>
<% end %>
diff --git a/app/views/user/signchange_confirm.rhtml b/app/views/user/signchangepassword_confirm.rhtml
index baad6729b..baad6729b 100644
--- a/app/views/user/signchange_confirm.rhtml
+++ b/app/views/user/signchangepassword_confirm.rhtml
diff --git a/app/views/user/signchangepassword_send_confirm.rhtml b/app/views/user/signchangepassword_send_confirm.rhtml
new file mode 100644
index 000000000..8b2e4fa91
--- /dev/null
+++ b/app/views/user/signchangepassword_send_confirm.rhtml
@@ -0,0 +1,30 @@
+<% @title = "Change your password on WhatDoTheyKnow.com" %>
+
+<div id="change_password">
+
+<% form_tag({:action => "signchangepassword"}, {:id => "signchangepassword_form"}) do %>
+ <%= foi_error_messages_for :signchangepassword %>
+
+ <div class="form_note">
+ <h1>Change your password on WhatDoTheyKnow.com</h1>
+ </div>
+
+ <p>
+ <label class="form_label" for="signchangepassword_email">Your e-mail:</label>
+ <%= text_field 'signchangepassword', 'email', { :size => 20 } %>
+ </p>
+
+ <p class="form_note">
+ <strong>Note:</strong>
+ We will send you an email. Follow the instructions in it to change
+ your password.
+ </p>
+
+ <div class="form_button">
+ <%= hidden_field_tag 'submitted_signchangepassword_send_confirm', 1 %>
+ <%= hidden_field_tag 'pretoken', params[:pretoken] %>
+ <%= submit_tag "Submit" %>
+ </div>
+<% end %>
+
+</div>
diff --git a/app/views/user_mailer/changeemail_already_used.rhtml b/app/views/user_mailer/changeemail_already_used.rhtml
new file mode 100644
index 000000000..0f60ad798
--- /dev/null
+++ b/app/views/user_mailer/changeemail_already_used.rhtml
@@ -0,0 +1,9 @@
+Someone, perhaps you, just tried to change their email address on
+WhatDoTheyKnow.com from <%=@old_email%> to <%=@new_email%>.
+
+This was not possible because there is already an account using
+the email address <%=@new_email%>.
+
+The accounts have been left as they previously were.
+
+-- the WhatDoTheyKnow team
diff --git a/app/views/user_mailer/changeemail_confirm.rhtml b/app/views/user_mailer/changeemail_confirm.rhtml
new file mode 100644
index 000000000..9aa288fb0
--- /dev/null
+++ b/app/views/user_mailer/changeemail_confirm.rhtml
@@ -0,0 +1,12 @@
+<%= @name %>,
+
+Please click on the link below to confirm that you want to
+change the email address that you use for WhatDoTheyKnow
+from <%=@old_email%> to <%=@new_email%>
+
+<%=@url%>
+
+We will not reveal your email addresses to anybody unless you
+or the law tell us to.
+
+-- the WhatDoTheyKnow team