aboutsummaryrefslogtreecommitdiffstats
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/controllers/admin_controller.rb1
-rw-r--r--app/controllers/public_body_controller.rb25
-rw-r--r--app/controllers/request_controller.rb18
-rw-r--r--app/controllers/user_controller.rb58
-rwxr-xr-xapp/helpers/link_to_helper.rb11
-rw-r--r--app/models/foi_attachment.rb321
-rw-r--r--app/models/incoming_message.rb710
-rw-r--r--app/models/info_request.rb27
-rw-r--r--app/models/public_body.rb48
-rw-r--r--app/models/request_mailer.rb1
-rw-r--r--app/views/layouts/default.rhtml11
-rw-r--r--app/views/public_body/_search_ahead.rhtml2
-rw-r--r--app/views/request/_search_ahead.rhtml2
-rw-r--r--app/views/request/upload_response.rhtml2
-rw-r--r--app/views/user/bad_token.rhtml2
-rw-r--r--app/views/user/show.rhtml36
16 files changed, 721 insertions, 554 deletions
diff --git a/app/controllers/admin_controller.rb b/app/controllers/admin_controller.rb
index 0bfbcd3d1..adb506b91 100644
--- a/app/controllers/admin_controller.rb
+++ b/app/controllers/admin_controller.rb
@@ -53,6 +53,7 @@ class AdminController < ApplicationController
authenticate_or_request_with_http_basic do |user_name, password|
if user_name == config_username && password == config_password
session[:using_admin] = 1
+ request.env['REMOTE_USER'] = user_name
else
request_http_basic_authentication
end
diff --git a/app/controllers/public_body_controller.rb b/app/controllers/public_body_controller.rb
index 251ab5efe..62229a441 100644
--- a/app/controllers/public_body_controller.rb
+++ b/app/controllers/public_body_controller.rb
@@ -91,31 +91,31 @@ class PublicBodyController < ApplicationController
@query = "%#{params[:public_body_query].nil? ? "" : params[:public_body_query]}%"
@tag = params[:tag]
@locale = self.locale_from_params()
-
+ default_locale = I18n.default_locale.to_s
locale_condition = "(upper(public_body_translations.name) LIKE upper(?)
OR upper(public_body_translations.notes) LIKE upper (?))
AND public_body_translations.locale = ?
AND public_bodies.id <> #{PublicBody.internal_admin_body.id}"
if @tag.nil? or @tag == "all"
@tag = "all"
- conditions = [locale_condition, @query, @query, @locale]
+ conditions = [locale_condition, @query, @query, default_locale]
elsif @tag == 'other'
category_list = PublicBodyCategories::get().tags().map{|c| "'"+c+"'"}.join(",")
conditions = [locale_condition + ' AND (select count(*) from has_tag_string_tags where has_tag_string_tags.model_id = public_bodies.id
and has_tag_string_tags.model = \'PublicBody\'
- and has_tag_string_tags.name in (' + category_list + ')) = 0', @query, @query, @locale]
+ and has_tag_string_tags.name in (' + category_list + ')) = 0', @query, @query, default_locale]
elsif @tag.size == 1
@tag.upcase!
- conditions = [locale_condition + ' AND public_body_translations.first_letter = ?', @query, @query, @locale, @tag]
+ conditions = [locale_condition + ' AND public_body_translations.first_letter = ?', @query, @query, default_locale, @tag]
elsif @tag.include?(":")
name, value = HasTagString::HasTagStringTag.split_tag_into_name_value(@tag)
conditions = [locale_condition + ' AND (select count(*) from has_tag_string_tags where has_tag_string_tags.model_id = public_bodies.id
and has_tag_string_tags.model = \'PublicBody\'
- and has_tag_string_tags.name = ? and has_tag_string_tags.value = ?) > 0', @query, @query, @locale, name, value]
+ and has_tag_string_tags.name = ? and has_tag_string_tags.value = ?) > 0', @query, @query, default_locale, name, value]
else
conditions = [locale_condition + ' AND (select count(*) from has_tag_string_tags where has_tag_string_tags.model_id = public_bodies.id
and has_tag_string_tags.model = \'PublicBody\'
- and has_tag_string_tags.name = ?) > 0', @query, @query, @locale, @tag]
+ and has_tag_string_tags.name = ?) > 0', @query, @query, default_locale, @tag]
end
if @tag == "all"
@description = ""
@@ -185,11 +185,14 @@ class PublicBodyController < ApplicationController
def search_typeahead
# Since acts_as_xapian doesn't support the Partial match flag, we work around it
# by making the last work a wildcard, which is quite the same
- query = params[:q] + '*'
-
- query = query.split(' ').join(' OR ') # XXX: HACK for OR instead of default AND!
- @xapian_requests = perform_search([PublicBody], query, 'relevant', nil, 5)
-
+ query = params[:q]
+ query = query.split(' ')
+ if query.last.nil? || query.last.strip.length < 3
+ @xapian_requests = nil
+ else
+ query = query.join(' OR ') # XXX: HACK for OR instead of default AND!
+ @xapian_requests = perform_search([PublicBody], query, 'relevant', nil, 5)
+ end
render :partial => "public_body/search_ahead"
end
end
diff --git a/app/controllers/request_controller.rb b/app/controllers/request_controller.rb
index 4b7884065..dad5e81cd 100644
--- a/app/controllers/request_controller.rb
+++ b/app/controllers/request_controller.rb
@@ -676,6 +676,7 @@ class RequestController < ApplicationController
# Internal function
def get_attachment_internal(html_conversion)
@incoming_message = IncomingMessage.find(params[:incoming_message_id])
+ @incoming_message.parse_raw_email!
@info_request = @incoming_message.info_request
if @incoming_message.info_request_id != params[:id].to_i
raise sprintf("Incoming message %d does not belong to request %d", @incoming_message.info_request_id, params[:id])
@@ -690,7 +691,6 @@ class RequestController < ApplicationController
# check permissions
raise "internal error, pre-auth filter should have caught this" if !@info_request.user_can_view?(authenticated_user)
-
@attachment = IncomingMessage.get_attachment_by_url_part_number(@incoming_message.get_attachments_for_display, @part_number)
raise ActiveRecord::RecordNotFound.new("attachment not found part number " + @part_number.to_s + " incoming_message " + @incoming_message.id.to_s) if @attachment.nil?
@@ -713,6 +713,7 @@ class RequestController < ApplicationController
:email => _("Then you can upload an FOI response. "),
:email_subject => _("Confirm your account on {{site_name}}",:site_name=>site_name)
}
+
if !authenticated?(@reason_params)
return
end
@@ -754,11 +755,14 @@ class RequestController < ApplicationController
def search_typeahead
# Since acts_as_xapian doesn't support the Partial match flag, we work around it
# by making the last work a wildcard, which is quite the same
- query = params[:q] + '*'
-
- query = query.split(' ').join(' OR ') # XXX: HACK for OR instead of default AND!
- @xapian_requests = perform_search([InfoRequestEvent], query, 'relevant', 'request_collapse', 5)
-
+ query = params[:q]
+ query = query.split(' ')
+ if query.last.nil? || query.last.strip.length < 3
+ @xapian_requests = nil
+ else
+ query = query.join(' OR ') # XXX: HACK for OR instead of default AND!
+ @xapian_requests = perform_search([InfoRequestEvent], query, 'relevant', 'request_collapse', 5)
+ end
render :partial => "request/search_ahead.rhtml"
end
@@ -774,7 +778,7 @@ class RequestController < ApplicationController
:email => _("Then you can download a zip file of {{info_request_title}}.",:info_request_title=>info_request.title),
:email_subject => _("Log in to download a zip file of {{info_request_title}}",:info_request_title=>info_request.title)
)
- updated = Digest::SHA1.hexdigest(info_request.get_last_event.created_at.to_s + info_request.updated_at.to_s)
+ updated = Digest::SHA1.hexdigest(info_request.get_last_event.created_at.to_i.to_s + info_request.updated_at.to_i.to_s)
@url_path = "/download/#{updated[0..1]}/#{updated}/#{params[:url_title]}.zip"
file_path = File.join(File.dirname(__FILE__), '../../cache/zips', @url_path)
if !File.exists?(file_path)
diff --git a/app/controllers/user_controller.rb b/app/controllers/user_controller.rb
index 96dbfba74..fc29a847c 100644
--- a/app/controllers/user_controller.rb
+++ b/app/controllers/user_controller.rb
@@ -23,7 +23,17 @@ class UserController < ApplicationController
redirect_to :url_name => MySociety::Format.simplify_url_part(params[:url_name], 'user', 32), :status => :moved_permanently
return
end
-
+ if params[:view].nil?
+ @show_requests = true
+ @show_profile = true
+ elsif params[:view] == 'profile'
+ @show_profile = true
+ @show_requests = false
+ elsif params[:view] == 'requests'
+ @show_profile = false
+ @show_requests = true
+ end
+
@display_user = User.find(:first, :conditions => [ "url_name = ? and email_confirmed = ?", params[:url_name], true ])
if not @display_user
raise ActiveRecord::RecordNotFound.new("user not found, url_name=" + params[:url_name])
@@ -34,31 +44,33 @@ class UserController < ApplicationController
# Use search query for this so can collapse and paginate easily
# XXX really should just use SQL query here rather than Xapian.
- begin
- requests_query = 'requested_by:' + @display_user.url_name
- comments_query = 'commented_by:' + @display_user.url_name
- if !params[:user_query].nil?
- requests_query += " " + params[:user_query]
- comments_query += " " + params[:user_query]
- @match_phrase = _("{{search_results}} matching '{{query}}'", :search_results => "", :query => params[:user_query])
- end
- @xapian_requests = perform_search([InfoRequestEvent], requests_query, 'newest', 'request_collapse')
- @xapian_comments = perform_search([InfoRequestEvent], comments_query, 'newest', nil)
-
- if (@page > 1)
- @page_desc = " (page " + @page.to_s + ")"
- else
- @page_desc = ""
+ if @show_requests
+ begin
+ requests_query = 'requested_by:' + @display_user.url_name
+ comments_query = 'commented_by:' + @display_user.url_name
+ if !params[:user_query].nil?
+ requests_query += " " + params[:user_query]
+ comments_query += " " + params[:user_query]
+ @match_phrase = _("{{search_results}} matching '{{query}}'", :search_results => "", :query => params[:user_query])
+ end
+ @xapian_requests = perform_search([InfoRequestEvent], requests_query, 'newest', 'request_collapse')
+ @xapian_comments = perform_search([InfoRequestEvent], comments_query, 'newest', nil)
+
+ if (@page > 1)
+ @page_desc = " (page " + @page.to_s + ")"
+ else
+ @page_desc = ""
+ end
+ rescue
+ @xapian_requests = nil
+ @xapian_comments = nil
end
- rescue
- @xapian_requests = nil
- @xapian_comments = nil
- end
- # Track corresponding to this page
- @track_thing = TrackThing.create_track_for_user(@display_user)
- @feed_autodetect = [ { :url => do_track_url(@track_thing, 'feed'), :title => @track_thing.params[:title_in_rss], :has_json => true } ]
+ # Track corresponding to this page
+ @track_thing = TrackThing.create_track_for_user(@display_user)
+ @feed_autodetect = [ { :url => do_track_url(@track_thing, 'feed'), :title => @track_thing.params[:title_in_rss], :has_json => true } ]
+ end
# All tracks for the user
if @is_you
@track_things = TrackThing.find(:all, :conditions => ["tracking_user_id = ? and track_medium = ?", @display_user.id, 'email_daily'], :order => 'created_at desc')
diff --git a/app/helpers/link_to_helper.rb b/app/helpers/link_to_helper.rb
index 54b8d69d0..5866c31f0 100755
--- a/app/helpers/link_to_helper.rb
+++ b/app/helpers/link_to_helper.rb
@@ -185,9 +185,16 @@ module LinkToHelper
end
- def main_url(relative_path)
+ def main_url(relative_path, append = nil)
url_prefix = "http://" + MySociety::Config.get("DOMAIN", '127.0.0.1:3000')
- return url_prefix + relative_path
+ url = url_prefix + relative_path
+ if !append.nil?
+ env = Rack::MockRequest.env_for(url)
+ req = Rack::Request.new(env)
+ req.path_info += append
+ url = req.url
+ end
+ return url
end
# Basic date format
diff --git a/app/models/foi_attachment.rb b/app/models/foi_attachment.rb
new file mode 100644
index 000000000..057dcdb69
--- /dev/null
+++ b/app/models/foi_attachment.rb
@@ -0,0 +1,321 @@
+# encoding: UTF-8
+
+# models/foi_attachment.rb:
+# An attachment to an email (IncomingMessage)
+#
+# Copyright (c) 2007 UK Citizens Online Democracy. All rights reserved.
+# Email: francis@mysociety.org; WWW: http://www.mysociety.org/
+# This is the type which is used to send data about attachments to the view
+
+require 'digest'
+
+class FoiAttachment < ActiveRecord::Base
+ belongs_to :incoming_message
+ validates_presence_of :content_type
+ validates_presence_of :filename
+ validates_presence_of :display_size
+
+ before_validation :ensure_filename!, :only => [:filename]
+ before_destroy :delete_cached_file!
+
+ def directory
+ base_dir = File.join("cache", "attachments_#{ENV['RAILS_ENV']}")
+ return File.join(base_dir, self.hexdigest[0..2])
+ end
+
+ def filepath
+ File.join(self.directory, self.hexdigest)
+ end
+
+ def delete_cached_file!
+ begin
+ File.delete(self.filepath)
+ rescue
+ end
+ end
+
+ def body=(d)
+ self.hexdigest = Digest::MD5.hexdigest(d)
+ if !File.exists?(self.directory)
+ FileUtils.mkdir_p self.directory
+ end
+ File.open(self.filepath, "wb") { |file|
+ file.write d
+ }
+ update_display_size!
+ end
+
+ def body
+ if @cached_body.nil?
+ @cached_body = File.open(self.filepath, "rb" ).read
+ end
+ return @cached_body
+ end
+
+ # List of DSN codes taken from RFC 3463
+ # http://tools.ietf.org/html/rfc3463
+ DsnToMessage = {
+ 'X.1.0' => 'Other address status',
+ 'X.1.1' => 'Bad destination mailbox address',
+ 'X.1.2' => 'Bad destination system address',
+ 'X.1.3' => 'Bad destination mailbox address syntax',
+ 'X.1.4' => 'Destination mailbox address ambiguous',
+ 'X.1.5' => 'Destination mailbox address valid',
+ 'X.1.6' => 'Mailbox has moved',
+ 'X.1.7' => 'Bad sender\'s mailbox address syntax',
+ 'X.1.8' => 'Bad sender\'s system address',
+ 'X.2.0' => 'Other or undefined mailbox status',
+ 'X.2.1' => 'Mailbox disabled, not accepting messages',
+ 'X.2.2' => 'Mailbox full',
+ 'X.2.3' => 'Message length exceeds administrative limit.',
+ 'X.2.4' => 'Mailing list expansion problem',
+ 'X.3.0' => 'Other or undefined mail system status',
+ 'X.3.1' => 'Mail system full',
+ 'X.3.2' => 'System not accepting network messages',
+ 'X.3.3' => 'System not capable of selected features',
+ 'X.3.4' => 'Message too big for system',
+ 'X.4.0' => 'Other or undefined network or routing status',
+ 'X.4.1' => 'No answer from host',
+ 'X.4.2' => 'Bad connection',
+ 'X.4.3' => 'Routing server failure',
+ 'X.4.4' => 'Unable to route',
+ 'X.4.5' => 'Network congestion',
+ 'X.4.6' => 'Routing loop detected',
+ 'X.4.7' => 'Delivery time expired',
+ 'X.5.0' => 'Other or undefined protocol status',
+ 'X.5.1' => 'Invalid command',
+ 'X.5.2' => 'Syntax error',
+ 'X.5.3' => 'Too many recipients',
+ 'X.5.4' => 'Invalid command arguments',
+ 'X.5.5' => 'Wrong protocol version',
+ 'X.6.0' => 'Other or undefined media error',
+ 'X.6.1' => 'Media not supported',
+ 'X.6.2' => 'Conversion required and prohibited',
+ 'X.6.3' => 'Conversion required but not supported',
+ 'X.6.4' => 'Conversion with loss performed',
+ 'X.6.5' => 'Conversion failed',
+ 'X.7.0' => 'Other or undefined security status',
+ 'X.7.1' => 'Delivery not authorized, message refused',
+ 'X.7.2' => 'Mailing list expansion prohibited',
+ 'X.7.3' => 'Security conversion required but not possible',
+ 'X.7.4' => 'Security features not supported',
+ 'X.7.5' => 'Cryptographic failure',
+ 'X.7.6' => 'Cryptographic algorithm not supported',
+ 'X.7.7' => 'Message integrity failure'
+ }
+
+ # Returns HTML, of extra comment to put by attachment
+ def extra_note
+ # For delivery status notification attachments, extract the status and
+ # look up what it means in the DSN table.
+ if @content_type == 'message/delivery-status'
+ if !@body.match(/Status:\s+([0-9]+\.([0-9]+\.[0-9]+))\s+/)
+ return ""
+ end
+ dsn = $1
+ dsn_part = 'X.' + $2
+
+ dsn_message = ""
+ if DsnToMessage.include?(dsn_part)
+ dsn_message = " (" + DsnToMessage[dsn_part] + ")"
+ end
+
+ return "<br><em>DSN: " + dsn + dsn_message + "</em>"
+ end
+ return ""
+ end
+
+ # Called by controller so old filenames still work
+ def old_display_filename
+ filename = self.filename
+
+ # Convert weird spaces (e.g. \n) to normal ones
+ filename = filename.gsub(/\s/, " ")
+ # Remove slashes, they mess with URLs
+ filename = filename.gsub(/\//, "-")
+
+ return filename
+ end
+
+ # XXX changing this will break existing URLs, so have a care - maybe
+ # make another old_display_filename see above
+ def display_filename
+ filename = self.filename
+ if !self.incoming_message.nil?
+ self.incoming_message.info_request.apply_censor_rules_to_text!(filename)
+ end
+ # Sometimes filenames have e.g. %20 in - no point butchering that
+ # (without unescaping it, this would remove the % and leave 20s in there)
+ filename = CGI.unescape(filename)
+
+ # Remove weird spaces
+ filename = filename.gsub(/\s+/, " ")
+ # Remove non-alphabetic characters
+ filename = filename.gsub(/[^A-Za-z0-9.]/, " ")
+ # Remove spaces near dots
+ filename = filename.gsub(/\s*\.\s*/, ".")
+ # Compress adjacent spaces down to a single one
+ filename = filename.gsub(/\s+/, " ")
+ filename = filename.strip
+
+ return filename
+ end
+
+
+ def ensure_filename!
+ if self.filename.nil?
+ calc_ext = AlaveteliFileTypes.mimetype_to_extension(self.content_type)
+ if !calc_ext
+ calc_ext = "bin"
+ end
+ if !self.within_rfc822_subject.nil?
+ computed = self.within_rfc822_subject + "." + calc_ext
+ else
+ computed = "attachment." + calc_ext
+ end
+ self.filename = computed
+ end
+ end
+
+ def filename=(filename)
+ calc_ext = AlaveteliFileTypes.mimetype_to_extension(self.content_type)
+ # Put right extension on if missing
+ if !filename.nil? && !filename.match(/\.#{calc_ext}$/) && calc_ext
+ computed = filename + "." + calc_ext
+ else
+ computed = filename
+ end
+ write_attribute('filename', computed)
+ end
+
+ # Size to show next to the download link for the attachment
+ def update_display_size!
+ s = self.body.size
+
+ if s > 1024 * 1024
+ self.display_size = sprintf("%.1f", s.to_f / 1024 / 1024) + 'M'
+ else
+ self.display_size = (s / 1024).to_s + 'K'
+ end
+ end
+
+ # Whether this type can be shown in the Google Docs Viewer.
+ # The full list of supported types can be found at
+ # https://docs.google.com/support/bin/answer.py?hl=en&answer=1189935
+ def has_google_docs_viewer?
+ return !! {
+ "application/pdf" => true, # .pdf
+ "image/tiff" => true, # .tiff
+
+ "application/vnd.ms-word" => true, # .doc
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document" => true, # .docx
+
+ "application/vnd.ms-powerpoint" => true, # .ppt
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation" => true, # .pptx
+
+ "application/vnd.ms-excel" => true, # .xls
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" => true, # .xlsx
+
+ } [self.content_type]
+ end
+
+ # Whether this type has a "View as HTML"
+ def has_body_as_html?
+ return (
+ !!{
+ "text/plain" => true,
+ "application/rtf" => true,
+ }[self.content_type] or
+ self.has_google_docs_viewer?
+ )
+ end
+
+ # Name of type of attachment type - only valid for things that has_body_as_html?
+ def name_of_content_type
+ return {
+ "text/plain" => "Text file",
+ 'application/rtf' => "RTF file",
+
+ 'application/pdf' => "PDF file",
+ 'image/tiff' => "TIFF image",
+
+ 'application/vnd.ms-word' => "Word document",
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => "Word document",
+
+ 'application/vnd.ms-powerpoint' => "PowerPoint presentation",
+ 'application/vnd.openxmlformats-officedocument.presentationml.presentation' => "PowerPoint presentation",
+
+ 'application/vnd.ms-excel' => "Excel spreadsheet",
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => "Excel spreadsheet",
+ }[self.content_type]
+ 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 '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd"><html><head><title></title></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
+ tempfile.flush
+
+ if 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
+ elsif self.has_google_docs_viewer?
+ html = '' # force error and using Google docs viewer
+ else
+ raise "No HTML conversion available for type " + self.content_type
+ end
+
+ tempfile.close
+ tempfile.delete
+ end
+
+ # We need to look at:
+ # a) Any error code
+ # b) The output size, as pdftohtml does not return an error code upon error.
+ # c) For cases when there is no text in the body of the HTML, or
+ # images, so nothing will be rendered. This is to detect some bug in
+ # pdftohtml, which sometimes makes it return just <hr>s and no other
+ # content.
+ html.match(/(\<body[^>]*\>.*)/mi)
+ body = $1.to_s
+ 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)
+ ret = "<html><head></head><body>";
+ if self.has_google_docs_viewer?
+ wrapper_id = "wrapper_google_embed"
+ ret = ret + "<iframe src='http://docs.google.com/viewer?url=<attachment-url-here>&embedded=true' width='100%' height='100%' style='border: none;'></iframe>";
+ else
+ ret = ret + "<p>Sorry, we were unable to convert this file to HTML. Please use the download link at the top right.</p>"
+ end
+ ret = ret + "</body></html>"
+ return ret, wrapper_id
+ end
+
+ return html, wrapper_id
+ end
+
+end
+
diff --git a/app/models/incoming_message.rb b/app/models/incoming_message.rb
index 2b795ddf5..a8498b6e8 100644
--- a/app/models/incoming_message.rb
+++ b/app/models/incoming_message.rb
@@ -44,275 +44,6 @@ module TMail
end
end
-# This is the type which is used to send data about attachments to the view
-class FOIAttachment
- attr_accessor :body
- attr_accessor :content_type
- attr_accessor :filename
- attr_accessor :url_part_number
- attr_accessor :within_rfc822_subject # we use the subject as the filename for email attachments
-
- # List of DSN codes taken from RFC 3463
- # http://tools.ietf.org/html/rfc3463
- DsnToMessage = {
- 'X.1.0' => 'Other address status',
- 'X.1.1' => 'Bad destination mailbox address',
- 'X.1.2' => 'Bad destination system address',
- 'X.1.3' => 'Bad destination mailbox address syntax',
- 'X.1.4' => 'Destination mailbox address ambiguous',
- 'X.1.5' => 'Destination mailbox address valid',
- 'X.1.6' => 'Mailbox has moved',
- 'X.1.7' => 'Bad sender\'s mailbox address syntax',
- 'X.1.8' => 'Bad sender\'s system address',
- 'X.2.0' => 'Other or undefined mailbox status',
- 'X.2.1' => 'Mailbox disabled, not accepting messages',
- 'X.2.2' => 'Mailbox full',
- 'X.2.3' => 'Message length exceeds administrative limit.',
- 'X.2.4' => 'Mailing list expansion problem',
- 'X.3.0' => 'Other or undefined mail system status',
- 'X.3.1' => 'Mail system full',
- 'X.3.2' => 'System not accepting network messages',
- 'X.3.3' => 'System not capable of selected features',
- 'X.3.4' => 'Message too big for system',
- 'X.4.0' => 'Other or undefined network or routing status',
- 'X.4.1' => 'No answer from host',
- 'X.4.2' => 'Bad connection',
- 'X.4.3' => 'Routing server failure',
- 'X.4.4' => 'Unable to route',
- 'X.4.5' => 'Network congestion',
- 'X.4.6' => 'Routing loop detected',
- 'X.4.7' => 'Delivery time expired',
- 'X.5.0' => 'Other or undefined protocol status',
- 'X.5.1' => 'Invalid command',
- 'X.5.2' => 'Syntax error',
- 'X.5.3' => 'Too many recipients',
- 'X.5.4' => 'Invalid command arguments',
- 'X.5.5' => 'Wrong protocol version',
- 'X.6.0' => 'Other or undefined media error',
- 'X.6.1' => 'Media not supported',
- 'X.6.2' => 'Conversion required and prohibited',
- 'X.6.3' => 'Conversion required but not supported',
- 'X.6.4' => 'Conversion with loss performed',
- 'X.6.5' => 'Conversion failed',
- 'X.7.0' => 'Other or undefined security status',
- 'X.7.1' => 'Delivery not authorized, message refused',
- 'X.7.2' => 'Mailing list expansion prohibited',
- 'X.7.3' => 'Security conversion required but not possible',
- 'X.7.4' => 'Security features not supported',
- 'X.7.5' => 'Cryptographic failure',
- 'X.7.6' => 'Cryptographic algorithm not supported',
- 'X.7.7' => 'Message integrity failure'
- }
-
- # Returns HTML, of extra comment to put by attachment
- def extra_note
- # For delivery status notification attachments, extract the status and
- # look up what it means in the DSN table.
- if @content_type == 'message/delivery-status'
- if !@body.match(/Status:\s+([0-9]+\.([0-9]+\.[0-9]+))\s+/)
- return ""
- end
- dsn = $1
- dsn_part = 'X.' + $2
-
- dsn_message = ""
- if DsnToMessage.include?(dsn_part)
- dsn_message = " (" + DsnToMessage[dsn_part] + ")"
- end
-
- return "<br><em>DSN: " + dsn + dsn_message + "</em>"
- end
- return ""
- end
-
- # Called by controller so old filenames still work
- def old_display_filename
- filename = self._internal_display_filename
-
- # Convert weird spaces (e.g. \n) to normal ones
- filename = filename.gsub(/\s/, " ")
- # Remove slashes, they mess with URLs
- filename = filename.gsub(/\//, "-")
-
- return filename
- end
-
- # XXX changing this will break existing URLs, so have a care - maybe
- # make another old_display_filename see above
- def display_filename
- filename = self._internal_display_filename
-
- # Sometimes filenames have e.g. %20 in - no point butchering that
- # (without unescaping it, this would remove the % and leave 20s in there)
- filename = CGI.unescape(filename)
-
- # Remove weird spaces
- filename = filename.gsub(/\s+/, " ")
- # Remove non-alphabetic characters
- filename = filename.gsub(/[^A-Za-z0-9.]/, " ")
- # Remove spaces near dots
- filename = filename.gsub(/\s*\.\s*/, ".")
- # Compress adjacent spaces down to a single one
- filename = filename.gsub(/\s+/, " ")
- filename = filename.strip
-
- return filename
- end
-
- def _internal_display_filename
- calc_ext = AlaveteliFileTypes.mimetype_to_extension(@content_type)
-
- if @filename
- # Put right extension on if missing
- if !filename.match(/\.#{calc_ext}$/) && calc_ext
- filename + "." + calc_ext
- else
- filename
- end
- else
- if !calc_ext
- calc_ext = "bin"
- end
- if @within_rfc822_subject
- @within_rfc822_subject + "." + calc_ext
- else
- "attachment." + calc_ext
- end
- end
- end
-
- # Size to show next to the download link for the attachment
- def display_size
- s = self.body.size
-
- if s > 1024 * 1024
- return sprintf("%.1f", s.to_f / 1024 / 1024) + 'M'
- else
- return (s / 1024).to_s + 'K'
- end
- end
-
- # Whether this type can be shown in the Google Docs Viewer.
- # The full list of supported types can be found at
- # https://docs.google.com/support/bin/answer.py?hl=en&answer=1189935
- def has_google_docs_viewer?
- return !! {
- "application/pdf" => true, # .pdf
- "image/tiff" => true, # .tiff
-
- "application/vnd.ms-word" => true, # .doc
- "application/vnd.openxmlformats-officedocument.wordprocessingml.document" => true, # .docx
-
- "application/vnd.ms-powerpoint" => true, # .ppt
- "application/vnd.openxmlformats-officedocument.presentationml.presentation" => true, # .pptx
-
- "application/vnd.ms-excel" => true, # .xls
- "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" => true, # .xlsx
-
- } [self.content_type]
- end
-
- # Whether this type has a "View as HTML"
- def has_body_as_html?
- return (
- !!{
- "text/plain" => true,
- "application/rtf" => true,
- }[self.content_type] or
- self.has_google_docs_viewer?
- )
- end
-
- # Name of type of attachment type - only valid for things that has_body_as_html?
- def name_of_content_type
- return {
- "text/plain" => "Text file",
- 'application/rtf' => "RTF file",
-
- 'application/pdf' => "PDF file",
- 'image/tiff' => "TIFF image",
-
- 'application/vnd.ms-word' => "Word document",
- 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => "Word document",
-
- 'application/vnd.ms-powerpoint' => "PowerPoint presentation",
- 'application/vnd.openxmlformats-officedocument.presentationml.presentation' => "PowerPoint presentation",
-
- 'application/vnd.ms-excel' => "Excel spreadsheet",
- 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => "Excel spreadsheet",
- }[self.content_type]
- 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 '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
- "http://www.w3.org/TR/html4/loose.dtd"><html><head><title></title></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
- tempfile.flush
-
- if 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
- elsif self.has_google_docs_viewer?
- html = '' # force error and using Google docs viewer
- else
- raise "No HTML conversion available for type " + self.content_type
- end
-
- tempfile.close
- tempfile.delete
- end
-
- # We need to look at:
- # a) Any error code
- # b) The output size, as pdftohtml does not return an error code upon error.
- # c) For cases when there is no text in the body of the HTML, or
- # images, so nothing will be rendered. This is to detect some bug in
- # pdftohtml, which sometimes makes it return just <hr>s and no other
- # content.
- html.match(/(\<body[^>]*\>.*)/mi)
- body = $1.to_s
- 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)
- ret = "<html><head></head><body>";
- if self.has_google_docs_viewer?
- wrapper_id = "wrapper_google_embed"
- ret = ret + "<iframe src='http://docs.google.com/viewer?url=<attachment-url-here>&embedded=true' width='100%' height='100%' style='border: none;'></iframe>";
- else
- ret = ret + "<p>Sorry, we were unable to convert this file to HTML. Please use the download link at the top right.</p>"
- end
- ret = ret + "</body></html>"
- return ret, wrapper_id
- end
-
- return html, wrapper_id
- end
-
-end
-
-
class IncomingMessage < ActiveRecord::Base
belongs_to :info_request
validates_presence_of :info_request
@@ -320,7 +51,7 @@ class IncomingMessage < ActiveRecord::Base
validates_presence_of :raw_email
has_many :outgoing_message_followups, :foreign_key => 'incoming_message_followup_id', :class_name => 'OutgoingMessage'
-
+ has_many :foi_attachments
has_many :info_request_events # never really has many, but could in theory
belongs_to :raw_email
@@ -338,8 +69,8 @@ class IncomingMessage < ActiveRecord::Base
# Return the structured TMail::Mail object
# Documentation at http://i.loveruby.net/en/projects/tmail/doc/
- def mail
- if @mail.nil? && !self.raw_email.nil?
+ def mail(force = nil)
+ if (!force.nil? || @mail.nil?) && !self.raw_email.nil?
# Hack round bug in TMail's MIME decoding. Example request which provokes it:
# http://www.whatdotheyknow.com/request/reviews_of_unduly_lenient_senten#incoming-4830
# Report of TMail bug:
@@ -352,23 +83,109 @@ class IncomingMessage < ActiveRecord::Base
@mail
end
+ # Returns the name of the person the incoming message is from, or nil if
+ # there isn't one or if there is only an email address. XXX can probably
+ # remove from_name_if_present (which is a monkey patch) by just calling
+ # .from_addrs[0].name here instead?
+
+ # Return false if for some reason this is a message that we shouldn't let them reply to
+ def _calculate_valid_to_reply_to
+ # check validity of email
+ if self.mail.from_addrs.nil? || self.mail.from_addrs.size == 0
+ return false
+ end
+ email = self.mail.from_addrs[0].spec
+ if !MySociety::Validate.is_valid_email(email)
+ return false
+ end
+
+ # reject postmaster - authorities seem to nearly always not respond to
+ # email to postmaster, and it tends to only happen after delivery failure.
+ # likewise Mailer-Daemon, Auto_Reply...
+ prefix = email
+ prefix =~ /^(.*)@/
+ prefix = $1
+ if !prefix.nil? && prefix.downcase.match(/^(postmaster|mailer-daemon|auto_reply|donotreply|no.reply)$/)
+ return false
+ end
+ if !self.mail['return-path'].nil? && self.mail['return-path'].addr == "<>"
+ return false
+ end
+ if !self.mail['auto-submitted'].nil?
+ return false
+ end
+ return true
+ end
+
+ def parse_raw_email!(force = nil)
+ # The following fields may be absent; we treat them as cached
+ # values in case we want to regenerate them (due to mail
+ # parsing bugs, etc).
+ if (!force.nil? || self.last_parsed.nil?)
+ self.extract_attachments!
+ self.sent_at = self.mail.date || self.created_at
+ self.subject = self.mail.subject
+ # XXX can probably remove from_name_if_present (which is a
+ # monkey patch) by just calling .from_addrs[0].name here
+ # instead?
+ self.mail_from = self.mail.from_name_if_present
+ begin
+ self.mail_from_domain = PublicBody.extract_domain_from_email(self.mail.from_addrs[0].spec)
+ rescue NoMethodError
+ self.mail_from_domain = ""
+ end
+ self.valid_to_reply_to = self._calculate_valid_to_reply_to
+ self.last_parsed = Time.now
+ self.save!
+ end
+ end
+
+ def valid_to_reply_to?
+ return self.valid_to_reply_to
+ end
+
+ # The cached fields mentioned in the previous comment
+ # XXX there must be a nicer way to do this without all that
+ # repetition. I tried overriding method_missing but got some
+ # unpredictable results.
+ def valid_to_reply_to
+ parse_raw_email!
+ super
+ end
+ def sent_at
+ parse_raw_email!
+ super
+ end
+ def subject
+ parse_raw_email!
+ super
+ end
+ def mail_from
+ parse_raw_email!
+ super
+ end
+ def safe_mail_from
+ if !self.mail_from.nil?
+ mail_from = self.mail_from.dup
+ self.info_request.apply_censor_rules_to_text!(mail_from)
+ return mail_from
+ end
+ end
+ def mail_from_domain
+ parse_raw_email!
+ super
+ end
+
# Number the attachments in depth first tree order, for use in URLs.
# XXX This fills in part.rfc822_attachment and part.url_part_number within
# all the parts of the email (see TMail monkeypatch above for how these
# attributes are added). ensure_parts_counted must be called before using
- # the attributes. This calculation is done only when required to avoid
- # having to load and parse the email unnecessarily.
- def after_initialize
- @parts_counted = false
- end
+ # the attributes.
def ensure_parts_counted
- if not @parts_counted
- @count_parts_count = 0
- _count_parts_recursive(self.mail)
- # we carry on using these numeric ids for attachments uudecoded from within text parts
- @count_first_uudecode_count = @count_parts_count
- @parts_counted = true
- end
+ @count_parts_count = 0
+ _count_parts_recursive(self.mail)
+ # we carry on using these numeric ids for attachments uudecoded from within text parts
+ @count_first_uudecode_count = @count_parts_count
end
def _count_parts_recursive(part)
if part.multipart?
@@ -406,7 +223,7 @@ class IncomingMessage < ActiveRecord::Base
end
end
# And look up by URL part number to get an attachment
- # XXX relies on get_attachments_for_display calling ensure_parts_counted
+ # XXX relies on extract_attachments calling ensure_parts_counted
def self.get_attachment_by_url_part_number(attachments, found_url_part_number)
attachments.each do |a|
if a.url_part_number == found_url_part_number
@@ -416,12 +233,6 @@ class IncomingMessage < ActiveRecord::Base
return nil
end
- # Return date mail was sent
- def sent_at
- # Use date it arrived (created_at) if mail itself doesn't have Date: header
- self.mail.date || self.created_at
- end
-
# Converts email addresses we know about into textual descriptions of them
def mask_special_emails!(text)
# XXX can later display some of these special emails as actual emails,
@@ -447,7 +258,7 @@ class IncomingMessage < ActiveRecord::Base
# Special cases for some content types
if content_type == 'application/pdf'
uncompressed_text = nil
- IO.popen("/usr/bin/pdftk - output - uncompress", "r+") do |child|
+ IO.popen("#{`which pdftk`.chomp} - output - uncompress", "r+") do |child|
child.write(text)
child.close_write()
uncompressed_text = child.read()
@@ -464,7 +275,7 @@ class IncomingMessage < ActiveRecord::Base
if MySociety::Config.get('USE_GHOSTSCRIPT_COMPRESSION') == true
command = "gs -sDEVICE=pdfwrite -dCompatibilityLevel=1.4 -dPDFSETTINGS=/screen -dNOPAUSE -dQUIET -dBATCH -sOutputFile=- -"
else
- command = "/usr/bin/pdftk - output - compress"
+ command = "#{`which pdftk`.chomp} - output - compress"
end
IO.popen(command, "r+") do |child|
child.write(censored_uncompressed_text)
@@ -518,6 +329,7 @@ class IncomingMessage < ActiveRecord::Base
self.info_request.apply_censor_rules_to_binary!(text)
raise "internal error in binary_mask_stuff" if text.size != orig_size
+ return text
end
# Removes censored stuff from from HTML conversion of downloaded binaries
@@ -606,21 +418,13 @@ class IncomingMessage < ActiveRecord::Base
text.gsub!(/^(>.*\n)/, replacement)
text.gsub!(/^(On .+ (wrote|said):\n)/, replacement)
- # Multiple line sections
- # http://www.whatdotheyknow.com/request/identity_card_scheme_expenditure
- # http://www.whatdotheyknow.com/request/parliament_protest_actions
- # http://www.whatdotheyknow.com/request/64/response/102
- # http://www.whatdotheyknow.com/request/47/response/283
- # http://www.whatdotheyknow.com/request/30/response/166
- # http://www.whatdotheyknow.com/request/52/response/238
- # http://www.whatdotheyknow.com/request/224/response/328 # example with * * * * *
- # http://www.whatdotheyknow.com/request/297/response/506
- ['-', '_', '*', '#'].each do |score|
+ ['-', '_', '*', '#'].each do |scorechar|
+ score = /(?:[#{scorechar}]\s*){8,}/
text.sub!(/(Disclaimer\s+)? # appears just before
(
- \s*(?:[#{score}]\s*){8,}\s*\n.*? # top line
+ \s*#{score}\n(?:(?!#{score}\n).)*? # top line
(disclaimer:\n|confidential|received\sthis\semail\sin\serror|virus|intended\s+recipient|monitored\s+centrally|intended\s+(for\s+|only\s+for\s+use\s+by\s+)the\s+addressee|routinely\s+monitored|MessageLabs|unauthorised\s+use)
- .*?((?:[#{score}]\s*){8,}\s*\n|\z) # bottom line OR end of whole string (for ones with no terminator XXX risky)
+ .*?(?:#{score}|\z) # bottom line OR end of whole string (for ones with no terminator XXX risky)
)
/imx, replacement)
end
@@ -666,20 +470,20 @@ class IncomingMessage < ActiveRecord::Base
end
# Internal function
- def _get_censored_part_file_name(mail)
+ def _get_part_file_name(mail)
part_file_name = TMail::Mail.get_part_file_name(mail)
if part_file_name.nil?
return nil
end
part_file_name = part_file_name.dup
- self.info_request.apply_censor_rules_to_text!(part_file_name)
return part_file_name
end
# (This risks losing info if the unchosen alternative is the only one to contain
# useful info, but let's worry about that another time)
def get_attachment_leaves
- return _get_attachment_leaves_recursive(self.mail)
+ force = true
+ return _get_attachment_leaves_recursive(self.mail(force))
end
def _get_attachment_leaves_recursive(curr_mail, within_rfc822_attachment = nil)
leaves_found = []
@@ -719,14 +523,14 @@ class IncomingMessage < ActiveRecord::Base
# 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.
-
+ charset = curr_mail.charset # save this, because overwriting content_type also resets charset
# Don't allow nil content_types
if curr_mail.content_type.nil?
curr_mail.content_type = 'application/octet-stream'
end
# PDFs often come with this mime type, fix it up for view code
if curr_mail.content_type == 'application/octet-stream'
- part_file_name = self._get_censored_part_file_name(curr_mail)
+ part_file_name = self._get_part_file_name(curr_mail)
calc_mime = AlaveteliFileTypes.filename_and_content_to_mimetype(part_file_name, curr_mail.body)
if calc_mime
curr_mail.content_type = calc_mime
@@ -749,7 +553,6 @@ class IncomingMessage < ActiveRecord::Base
curr_mail.content_type = 'application/octet-stream'
end
end
-
# 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
@@ -759,6 +562,8 @@ class IncomingMessage < ActiveRecord::Base
curr_mail.within_rfc822_attachment = within_rfc822_attachment
leaves_found += [curr_mail]
end
+ # restore original charset
+ curr_mail.charset = charset
end
return leaves_found
end
@@ -776,7 +581,6 @@ class IncomingMessage < ActiveRecord::Base
# search results
def _cache_main_body_text
text = self.get_main_body_text_internal
-
# Strip the uudecode parts from main text
# - this also effectively does a .dup as well, so text mods don't alter original
text = text.split(/^begin.+^`\n^end\n/sm).join(" ")
@@ -818,61 +622,54 @@ class IncomingMessage < ActiveRecord::Base
main_part = get_main_body_text_part
return _convert_part_body_to_text(main_part)
end
+
# Given a main text part, converts it to text
def _convert_part_body_to_text(part)
if part.nil?
text = "[ Email has no body, please see attachments ]"
- text_charset = "utf-8"
+ source_charset = "utf-8"
else
- text = part.body
- text_charset = part.charset
+ text = part.body # by default, TMail converts to UT8 in this call
+ source_charset = part.charset
if part.content_type == 'text/html'
# e.g. http://www.whatdotheyknow.com/request/35/response/177
- # XXX This is a bit of a hack as it is calling a convert to text routine.
- # Could instead call a sanitize HTML one.
- text = self.class._get_attachment_text_internal_one_file(part.content_type, text)
- end
- end
-
- # Charset conversion, turn everything into UTF-8
- if not text_charset.nil?
- begin
- # XXX specially convert unicode pound signs, was needed here
- # http://www.whatdotheyknow.com/request/88/response/352
- text = text.gsub("£", Iconv.conv(text_charset, 'utf-8', '£'))
- # Try proper conversion
- text = Iconv.conv('utf-8', text_charset, text)
- rescue Iconv::IllegalSequence, Iconv::InvalidEncoding
- # Clearly specified charset was nonsense
- text_charset = nil
+ # XXX This is a bit of a hack as it is calling a
+ # convert to text routine. Could instead call a
+ # sanitize HTML one.
+
+ # If the text isn't UTF8, it means TMail had a problem
+ # converting it (invalid characters, etc), and we
+ # should instead tell elinks to respect the source
+ # charset
+ use_charset = "utf-8"
+ begin
+ text = Iconv.conv('utf-8', 'utf-8', text)
+ rescue Iconv::IllegalSequence
+ use_charset = source_charset
+ end
+ text = self.class._get_attachment_text_internal_one_file(part.content_type, text, use_charset)
end
end
- if text_charset.nil?
- # No specified charset, so guess
-
- # Could use rchardet here, but it had trouble with
- # http://www.whatdotheyknow.com/request/107/response/144
- # So I gave up - most likely in UK we'll only get windows-1252 anyway.
+ # If TMail can't convert text, it just returns it, so we sanitise it.
+ begin
+ # Test if it's good UTF-8
+ text = Iconv.conv('utf-8', 'utf-8', text)
+ rescue Iconv::IllegalSequence
+ # Text looks like unlabelled nonsense,
+ # strip out anything that isn't UTF-8
begin
- # See if it is good UTF-8 anyway
- text = Iconv.conv('utf-8', 'utf-8', text)
- rescue Iconv::IllegalSequence
- begin
- # Or is it good windows-1252, most likely
- text = Iconv.conv('utf-8', 'windows-1252', text)
- rescue Iconv::IllegalSequence
- # Text looks like unlabelled nonsense, strip out anything that isn't UTF-8
- text = Iconv.conv('utf-8//IGNORE', 'utf-8', text) +
- _("\n\n[ {{site_name}} note: The above text was badly encoded, and has had strange characters removed. ]",
- :site_name => MySociety::Config.get('SITE_NAME', 'Alaveteli'))
+ text = Iconv.conv('utf-8//IGNORE', source_charset, text) +
+ _("\n\n[ {{site_name}} note: The above text was badly encoded, and has had strange characters removed. ]",
+ :site_name => MySociety::Config.get('SITE_NAME', 'Alaveteli'))
+ rescue Iconv::InvalidEncoding, Iconv::IllegalSequence
+ if source_charset != "utf-8"
+ source_charset = "utf-8"
+ retry
end
end
end
- # An assertion that we have ended up with UTF-8 XXX can remove as this should
- # always be fine if code above is
- Iconv.conv('utf-8', 'utf-8', text)
# Fix DOS style linefeeds to Unix style ones (or other later regexps won't work)
# Needed for e.g. http://www.whatdotheyknow.com/request/60/response/98
@@ -887,8 +684,8 @@ class IncomingMessage < ActiveRecord::Base
end
# Returns part which contains main body text, or nil if there isn't one
def get_main_body_text_part
- leaves = get_attachment_leaves
-
+ leaves = self.foi_attachments
+
# 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
@@ -902,7 +699,7 @@ class IncomingMessage < ActiveRecord::Base
# Otherwise first part which is any sort of text
leaves.each do |p|
- if p.main_type == 'text'
+ if p.content_type.match(/^text/)
return p
end
end
@@ -910,7 +707,7 @@ class IncomingMessage < ActiveRecord::Base
# ... or if none, consider first part
p = leaves[0]
# if it is a known type then don't use it, return no body (nil)
- if AlaveteliFileTypes.mimetype_to_extension(p.content_type)
+ if !p.nil? && AlaveteliFileTypes.mimetype_to_extension(p.content_type)
# this is guess of case where there are only attachments, no body text
# e.g. http://www.whatdotheyknow.com/request/cost_benefit_analysis_for_real_n
return nil
@@ -922,16 +719,7 @@ class IncomingMessage < ActiveRecord::Base
return p
end
# Returns attachments that are uuencoded in main body part
- def get_main_body_text_uudecode_attachments
- # we don't use get_main_body_text_internal, as we want to avoid charset
- # conversions, since /usr/bin/uudecode needs to deal with those.
- # 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 []
- end
- text = main_part.body
-
+ def _uudecode_and_save_attachments(text)
# Find any uudecoded things buried in it, yeuchly
uus = text.scan(/^begin.+^`\n^end\n/sm)
attachments = []
@@ -946,91 +734,109 @@ class IncomingMessage < ActiveRecord::Base
end
tempfile.close
# Make attachment type from it, working out filename and mime type
- attachment = FOIAttachment.new()
- attachment.body = content
- attachment.filename = uu.match(/^begin\s+[0-9]+\s+(.*)$/)[1]
- self.info_request.apply_censor_rules_to_text!(attachment.filename)
- calc_mime = AlaveteliFileTypes.filename_and_content_to_mimetype(attachment.filename, attachment.body)
+ filename = uu.match(/^begin\s+[0-9]+\s+(.*)$/)[1]
+ calc_mime = AlaveteliFileTypes.filename_and_content_to_mimetype(filename, content)
if calc_mime
calc_mime = normalise_content_type(calc_mime)
- attachment.content_type = calc_mime
+ content_type = calc_mime
else
- attachment.content_type = 'application/octet-stream'
+ content_type = 'application/octet-stream'
end
- attachments += [attachment]
- end
-
+ hexdigest = Digest::MD5.hexdigest(content)
+ attachment = self.foi_attachments.find_or_create_by_hexdigest(:hexdigest => hexdigest)
+ attachment.update_attributes(:filename => filename,
+ :content_type => content_type,
+ :body => content,
+ :display_size => "0K")
+ attachment.save!
+ attachments << attachment
+ end
return attachments
end
- # Returns all attachments for use in display code
- # XXX is this called multiple times and should be cached?
def get_attachments_for_display
+ parse_raw_email!
+ # return what user would consider attachments, i.e. not the main body
main_part = get_main_body_text_part
- leaves = get_attachment_leaves
+ attachments = []
+ for attachment in self.foi_attachments
+ attachments << attachment if attachment != main_part
+ end
+ return attachments
+ end
+ def extract_attachments!
+ leaves = get_attachment_leaves # XXX check where else this is called from
# 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
- attachment = FOIAttachment.new
-
- attachment.body = leaf.body
- # As leaf.body causes MIME decoding which uses lots of RAM, do garbage collection here
- # to prevent excess memory use. XXX not really sure if this helps reduce
- # peak RAM use overall. Anyway, maybe there is something better to do than this.
- GC.start
-
- attachment.filename = _get_censored_part_file_name(leaf)
- if leaf.within_rfc822_attachment
- attachment.within_rfc822_subject = leaf.within_rfc822_attachment.subject
- # Test to see if we are in the first part of the attached
- # RFC822 message and it is text, if so add headers.
- # XXX should probably use hunting algorithm to find main text part, rather than
- # just expect it to be first. This will do for now though.
- # Example request that needs this:
- # http://www.whatdotheyknow.com/request/2923/response/7013/attach/2/Cycle%20Path%20Bank.txt
- if leaf.within_rfc822_attachment == leaf && leaf.content_type == 'text/plain'
- headers = ""
- for header in [ 'Date', 'Subject', 'From', 'To', 'Cc' ]
- if leaf.within_rfc822_attachment.header.include?(header.downcase)
- 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
+ for leaf in leaves
+ body = leaf.body
+ # As leaf.body causes MIME decoding which uses lots of RAM, do garbage collection here
+ # to prevent excess memory use. XXX not really sure if this helps reduce
+ # peak RAM use overall. Anyway, maybe there is something better to do than this.
+ GC.start
+ if leaf.within_rfc822_attachment
+ within_rfc822_subject = leaf.within_rfc822_attachment.subject
+ # Test to see if we are in the first part of the attached
+ # RFC822 message and it is text, if so add headers.
+ # XXX should probably use hunting algorithm to find main text part, rather than
+ # just expect it to be first. This will do for now though.
+ # Example request that needs this:
+ # http://www.whatdotheyknow.com/request/2923/response/7013/attach/2/Cycle%20Path%20Bank.txt
+ if leaf.within_rfc822_attachment == leaf && leaf.content_type == 'text/plain'
+ headers = ""
+ for header in [ 'Date', 'Subject', 'From', 'To', 'Cc' ]
+ if leaf.within_rfc822_attachment.header.include?(header.downcase)
+ 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
- # XXX call _convert_part_body_to_text here, but need to get charset somehow
- # e.g. http://www.whatdotheyknow.com/request/1593/response/3088/attach/4/Freedom%20of%20Information%20request%20-%20car%20oval%20sticker:%20Article%2020,%20Convention%20on%20Road%20Traffic%201949.txt
- attachment.body = headers + "\n" + attachment.body
-
- # This is quick way of getting all headers, but instead we only add some a) to
- # make it more usable, b) as at least one authority accidentally leaked security
- # information into a header.
- #attachment.body = leaf.within_rfc822_attachment.port.to_s
end
+ # XXX call _convert_part_body_to_text here, but need to get charset somehow
+ # e.g. http://www.whatdotheyknow.com/request/1593/response/3088/attach/4/Freedom%20of%20Information%20request%20-%20car%20oval%20sticker:%20Article%2020,%20Convention%20on%20Road%20Traffic%201949.txt
+ body = headers + "\n" + body
+
+ # This is quick way of getting all headers, but instead we only add some a) to
+ # make it more usable, b) as at least one authority accidentally leaked security
+ # information into a header.
+ #attachment.body = leaf.within_rfc822_attachment.port.to_s
end
- attachment.content_type = leaf.content_type
- attachment.url_part_number = leaf.url_part_number
- attachments += [attachment]
end
+ hexdigest = Digest::MD5.hexdigest(body)
+ attachment = self.foi_attachments.find_or_create_by_hexdigest(:hexdigest => hexdigest)
+ attachment.update_attributes(:url_part_number => leaf.url_part_number,
+ :content_type => leaf.content_type,
+ :filename => _get_part_file_name(leaf),
+ :charset => leaf.charset,
+ :within_rfc822_subject => within_rfc822_subject,
+ :display_size => "0K",
+ :body => body)
+ attachment.save!
+ attachments << attachment.id
end
-
- uudecode_attachments = get_main_body_text_uudecode_attachments
- c = @count_first_uudecode_count
- for uudecode_attachment in uudecode_attachments
- c += 1
- uudecode_attachment.url_part_number = c
- attachments += [uudecode_attachment]
+ main_part = get_main_body_text_part
+ # we don't use get_main_body_text_internal, as we want to avoid charset
+ # conversions, since /usr/bin/uudecode needs to deal with those.
+ # e.g. for https://secure.mysociety.org/admin/foi/request/show_raw_email/24550
+ if !main_part.nil?
+ uudecoded_attachments = _uudecode_and_save_attachments(main_part.body)
+ c = @count_first_uudecode_count
+ for uudecode_attachment in uudecoded_attachments
+ c += 1
+ uudecode_attachment.url_part_number = c
+ uudecode_attachment.save!
+ attachments << uudecode_attachment.id
+ end
end
- return attachments
- end
+ # now get rid of any attachments we no longer have
+ FoiAttachment.destroy_all("id NOT IN (#{attachments.join(',')}) AND incoming_message_id = #{self.id}")
+ end
# Returns body text as HTML with quotes flattened, and emails removed.
def get_body_for_html_display(collapse_quoted_sections = true)
@@ -1055,7 +861,7 @@ class IncomingMessage < ActiveRecord::Base
text.strip!
# if there is nothing but quoted stuff, then show the subject
if text == "FOLDED_QUOTED_SECTION"
- text = "[Subject only] " + CGI.escapeHTML(self.mail.subject) + text
+ text = "[Subject only] " + CGI.escapeHTML(self.subject) + text
end
# and display link for quoted stuff
text = text.gsub(/FOLDED_QUOTED_SECTION/, "\n\n" + '<span class="unfold_link"><a href="?unfold=1#incoming-'+self.id.to_s+'">show quoted sections</a></span>' + "\n\n")
@@ -1071,6 +877,7 @@ class IncomingMessage < ActiveRecord::Base
return text
end
+
# Returns text of email for using in quoted section when replying
def get_body_for_quoting
# Get the body text with emails and quoted sections removed
@@ -1110,7 +917,9 @@ class IncomingMessage < ActiveRecord::Base
return self.cached_attachment_text_clipped
end
- def IncomingMessage._get_attachment_text_internal_one_file(content_type, body)
+ def IncomingMessage._get_attachment_text_internal_one_file(content_type, body, charset = 'utf-8')
+ # note re. charset: TMail always tries to convert email bodies
+ # to UTF8 by default, so normally it should already be that.
text = ''
# XXX - tell all these command line tools to return utf-8
if content_type == 'text/plain'
@@ -1120,21 +929,22 @@ class IncomingMessage < ActiveRecord::Base
tempfile.print body
tempfile.flush
if content_type == 'application/vnd.ms-word'
- AlaveteliExternalCommand.run("/usr/bin/wvText", tempfile.path, tempfile.path + ".txt")
+ AlaveteliExternalCommand.run(`which wvText`.chomp, tempfile.path, tempfile.path + ".txt")
# Try catdoc if we get into trouble (e.g. for InfoRequestEvent 2701)
if not File.exists?(tempfile.path + ".txt")
- AlaveteliExternalCommand.run("/usr/bin/catdoc", tempfile.path, :append_to => text)
+ AlaveteliExternalCommand.run(`which catdoc`.chomp, tempfile.path, :append_to => text)
else
text += File.read(tempfile.path + ".txt") + "\n\n"
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
- AlaveteliExternalCommand.run("/usr/bin/catdoc", tempfile.path, :append_to => text)
+ AlaveteliExternalCommand.run(`which catdoc`.chomp, tempfile.path, :append_to => text)
elsif content_type == 'text/html'
- # lynx wordwraps links in its output, which then don't get formatted properly
- # by Alaveteli. We use elinks instead, which doesn't do that.
- AlaveteliExternalCommand.run("/usr/bin/elinks", "-eval", "'set document.codepage.assume = \"utf-8\"'", "-dump-charset", "utf-8", "-force-html", "-dump",
+ # lynx wordwraps links in its output, which then don't
+ # get formatted properly by Alaveteli. We use elinks
+ # instead, which doesn't do that.
+ AlaveteliExternalCommand.run(`which elinks`.chomp, "-eval", "'set document.codepage.assume = \"#{charset}\"'", "-eval", "'set document.codepage.force_assumed = 1'", "-dump-charset", "utf-8", "-force-html", "-dump",
tempfile.path, :append_to => text)
elsif content_type == 'application/vnd.ms-excel'
# Bit crazy using /usr/bin/strings - but xls2csv, xlhtml and
@@ -1145,9 +955,9 @@ class IncomingMessage < ActiveRecord::Base
elsif content_type == 'application/vnd.ms-powerpoint'
# ppthtml seems to catch more text, but only outputs HTML when
# we want text, so just use catppt for now
- AlaveteliExternalCommand.run("/usr/bin/catppt", tempfile.path, :append_to => text)
+ AlaveteliExternalCommand.run(`which catppt`.chomp, tempfile.path, :append_to => text)
elsif content_type == 'application/pdf'
- AlaveteliExternalCommand.run("/usr/bin/pdftotext", tempfile.path, "-", :append_to => text)
+ AlaveteliExternalCommand.run(`which pdftotext`.chomp, tempfile.path, "-", :append_to => text)
elsif content_type == 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
# This is Microsoft's XML office document format.
# Just pull out the main XML file, and strip it of text.
@@ -1201,13 +1011,14 @@ class IncomingMessage < ActiveRecord::Base
text = ''
attachments = self.get_attachments_for_display
for attachment in attachments
- text += IncomingMessage._get_attachment_text_internal_one_file(attachment.content_type, attachment.body)
+ text += IncomingMessage._get_attachment_text_internal_one_file(attachment.content_type, attachment.body, attachment.charset)
end
# Remove any bad characters
text = Iconv.conv('utf-8//IGNORE', 'utf-8', text)
return text
end
+
# Returns text for indexing
def get_text_for_indexing_full
return get_body_for_quoting + "\n\n" + get_attachment_text_full
@@ -1217,23 +1028,6 @@ class IncomingMessage < ActiveRecord::Base
return get_body_for_quoting + "\n\n" + get_attachment_text_clipped
end
- # Returns the name of the person the incoming message is from, or nil if
- # there isn't one or if there is only an email address. XXX can probably
- # remove from_name_if_present (which is a monkey patch) by just calling
- # .from_addrs[0].name here instead?
- def safe_mail_from
- name = self.mail.from_name_if_present
- if name.nil?
- return nil
- end
- name = name.dup
- self.info_request.apply_censor_rules_to_text!(name)
- return name
- end
-
- def mail_from_domain
- return PublicBody.extract_domain_from_email(self.mail.from_addrs[0].spec)
- end
# Has message arrived "recently"?
@@ -1310,7 +1104,7 @@ class IncomingMessage < ActiveRecord::Base
if !self.mail['return-path'].nil? && self.mail['return-path'].addr == "<>"
return false
end
- if !self.mail['auto-submitted'].nil? && !self.mail['auto-submitted'].keys.empty?
+ if !self.mail['auto-submitted'].nil?
return false
end
return true
diff --git a/app/models/info_request.rb b/app/models/info_request.rb
index 92322f74f..cfef6ebd8 100644
--- a/app/models/info_request.rb
+++ b/app/models/info_request.rb
@@ -1,3 +1,4 @@
+
# == Schema Information
# Schema version: 95
#
@@ -240,19 +241,19 @@ public
# 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
+ _('{{law_used_full}} request GQ - {{title}}',:law_used_full=>self.law_used_full,:title=>self.title)
else
- self.law_used_full + ' request - ' + self.title
+ _('{{law_used_full}} request - {{title}}',:law_used_full=>self.law_used_full,:title=>self.title)
end
end
def email_subject_followup(incoming_message = nil)
if incoming_message.nil? || !incoming_message.valid_to_reply_to?
'Re: ' + self.email_subject_request
else
- if incoming_message.mail.subject.match(/^Re:/i)
- incoming_message.mail.subject
+ if incoming_message.subject.match(/^Re:/i)
+ incoming_message.subject
else
- 'Re: ' + incoming_message.mail.subject
+ 'Re: ' + incoming_message.subject
end
end
end
@@ -260,36 +261,36 @@ public
# Two sorts of laws for requests, FOI or EIR
def law_used_full
if self.law_used == 'foi'
- return "Freedom of Information"
+ return _("Freedom of Information")
elsif self.law_used == 'eir'
- return "Environmental Information Regulations"
+ return _("Environmental Information Regulations")
else
raise "Unknown law used '" + self.law_used + "'"
end
end
def law_used_short
if self.law_used == 'foi'
- return "FOI"
+ return _("FOI")
elsif self.law_used == 'eir'
- return "EIR"
+ return _("EIR")
else
raise "Unknown law used '" + self.law_used + "'"
end
end
def law_used_act
if self.law_used == 'foi'
- return "Freedom of Information Act"
+ return _("Freedom of Information Act")
elsif self.law_used == 'eir'
- return "Environmental Information Regulations"
+ return _("Environmental Information Regulations")
else
raise "Unknown law used '" + self.law_used + "'"
end
end
def law_used_with_a
if self.law_used == 'foi'
- return "A Freedom of Information request"
+ return _("A Freedom of Information request")
elsif self.law_used == 'eir'
- return "An Environmental Information Regulations request"
+ return _("An Environmental Information Regulations request")
else
raise "Unknown law used '" + self.law_used + "'"
end
diff --git a/app/models/public_body.rb b/app/models/public_body.rb
index ab836657b..453e3a6cf 100644
--- a/app/models/public_body.rb
+++ b/app/models/public_body.rb
@@ -64,8 +64,14 @@ class PublicBody < ActiveRecord::Base
end
def translated_versions=(translation_attrs)
+ def skip?(attrs)
+ valueless = attrs.inject({}) { |h, (k, v)| h[k] = v if v != '' and k != 'locale'; h } # because we want to fall back to alternative translations where there are empty values
+ return valueless.length == 0
+ end
+
if translation_attrs.respond_to? :each_value # Hash => updating
translation_attrs.each_value do |attrs|
+ next if skip?(attrs)
t = translation(attrs[:locale]) || PublicBody::Translation.new
t.attributes = attrs
calculate_cached_fields(t)
@@ -73,6 +79,7 @@ class PublicBody < ActiveRecord::Base
end
else # Array => creating
translation_attrs.each do |attrs|
+ next if skip?(attrs)
new_translation = PublicBody::Translation.new(attrs)
calculate_cached_fields(new_translation)
translations << new_translation
@@ -309,22 +316,23 @@ class PublicBody < ActiveRecord::Base
# The "internal admin" is a special body for internal use.
def PublicBody.internal_admin_body
- pb = PublicBody.find_by_url_name("internal_admin_authority")
- if pb.nil?
- pb = PublicBody.new(
- :name => 'Internal admin authority',
- :short_name => "",
- :request_email => MySociety::Config.get("CONTACT_EMAIL", 'contact@localhost'),
- :home_page => "",
- :notes => "",
- :publication_scheme => "",
- :last_edit_editor => "internal_admin",
- :last_edit_comment => "Made by PublicBody.internal_admin_body"
- )
- pb.save!
+ PublicBody.with_locale(I18n.default_locale) do
+ pb = PublicBody.find_by_url_name("internal_admin_authority")
+ if pb.nil?
+ pb = PublicBody.new(
+ :name => 'Internal admin authority',
+ :short_name => "",
+ :request_email => MySociety::Config.get("CONTACT_EMAIL", 'contact@localhost'),
+ :home_page => "",
+ :notes => "",
+ :publication_scheme => "",
+ :last_edit_editor => "internal_admin",
+ :last_edit_comment => "Made by PublicBody.internal_admin_body"
+ )
+ pb.save!
+ end
+ return pb
end
-
- return pb
end
@@ -360,11 +368,11 @@ class PublicBody < ActiveRecord::Base
set_of_importing = Set.new()
field_names = { 'name'=>1, 'request_email'=>2 } # Default values in case no field list is given
line = 0
- CSV::Reader.parse(csv) do |row|
+ CSV.parse(csv) do |row|
line = line + 1
# Parse the first line as a field list if it starts with '#'
- if line==1 and row.to_s =~ /^#(.*)$/
+ if line==1 and row.first.to_s =~ /^#(.*)$/
row[0] = row[0][1..-1] # Remove the # sign on first field
row.each_with_index {|field, i| field_names[field] = i}
next
@@ -390,7 +398,7 @@ class PublicBody < ActiveRecord::Base
if public_body = bodies_by_name[name] # Existing public body
available_locales.each do |locale|
PublicBody.with_locale(locale) do
- changed = {}
+ changed = ActiveSupport::OrderedHash.new
field_list.each do |field_name|
localized_field_name = (locale.to_s == I18n.default_locale.to_s) ? field_name : "#{field_name}.#{locale}"
localized_value = field_names[localized_field_name] && row[field_names[localized_field_name]]
@@ -425,7 +433,7 @@ class PublicBody < ActiveRecord::Base
public_body = PublicBody.new(:name=>"", :short_name=>"", :request_email=>"")
available_locales.each do |locale|
PublicBody.with_locale(locale) do
- changed = {}
+ changed = ActiveSupport::OrderedHash.new
field_list.each do |field_name|
localized_field_name = (locale.to_s == I18n.default_locale.to_s) ? field_name : "#{field_name}.#{locale}"
localized_value = field_names[localized_field_name] && row[field_names[localized_field_name]]
@@ -457,7 +465,7 @@ class PublicBody < ActiveRecord::Base
# Give an error listing ones that are to be deleted
deleted_ones = set_of_existing - set_of_importing
if deleted_ones.size > 0
- notes.push "Notes: Some " + tag + " bodies are in database, but not in CSV file:\n " + Array(deleted_ones).join("\n ") + "\nYou may want to delete them manually.\n"
+ notes.push "Notes: Some " + tag + " bodies are in database, but not in CSV file:\n " + Array(deleted_ones).sort.join("\n ") + "\nYou may want to delete them manually.\n"
end
# Rollback if a dry run, or we had errors
diff --git a/app/models/request_mailer.rb b/app/models/request_mailer.rb
index 75dc58447..272f2ea83 100644
--- a/app/models/request_mailer.rb
+++ b/app/models/request_mailer.rb
@@ -10,6 +10,7 @@ require 'alaveteli_file_types'
class RequestMailer < ApplicationMailer
+
# Used when an FOI officer uploads a response from their web browser - this is
# the "fake" email used to store in the same format in the database as if they
# had emailed it.
diff --git a/app/views/layouts/default.rhtml b/app/views/layouts/default.rhtml
index 2af13f342..ad0560baa 100644
--- a/app/views/layouts/default.rhtml
+++ b/app/views/layouts/default.rhtml
@@ -12,7 +12,6 @@
<link rel="shortcut icon" href="/favicon.ico">
<%= stylesheet_link_tag 'main', :title => "Main", :rel => "stylesheet", :media => "all" %>
<%= stylesheet_link_tag 'fonts', :rel => "stylesheet", :media => "all" %>
- <%= stylesheet_link_tag 'theme', :rel => "stylesheet", :media => "all" %>
<%= stylesheet_link_tag 'print', :rel => "stylesheet", :media => "print" %>
<% if !params[:print_stylesheet].nil? %>
<%= stylesheet_link_tag 'print', :rel => "stylesheet", :media => "all" %>
@@ -35,11 +34,12 @@
<!--[if LT IE 8]>
<style type="text/css">@import url("/stylesheets/ie7.css");</style>
<![endif]-->
- <%= stylesheet_link_tag 'custom', :title => "Main", :rel => "stylesheet" %>
+ <!-- the following method for customising CSS is deprecated; see `doc/THEMES.md` for detail -->
+ <%= stylesheet_link_tag 'custom', :title => "Main", :rel => "stylesheet" %>
<% if force_registration_on_new_request %>
<%= stylesheet_link_tag 'jquery.fancybox-1.3.4', :rel => "stylesheet" %>
<% end %>
-
+
<% if @feed_autodetect %>
<% for feed in @feed_autodetect %>
<link rel="alternate" type="application/atom+xml" title="<%=h feed[:title] %>" href="<%=h feed[:url]%>">
@@ -49,7 +49,7 @@
<% end %>
<% end %>
<% if @has_json %>
- <link rel="alternate" type="application/json" title="JSON version of this page" href="<%=h main_url(request.request_uri) %>.json">
+ <link rel="alternate" type="application/json" title="JSON version of this page" href="<%=h main_url(request.request_uri, '.json') %>">
<% end %>
<% if @no_crawl %>
@@ -102,7 +102,8 @@
<%= _('Hello, {{username}}!', :username => h(@user.name))%>
<% if @user %>
- <%=link_to _("My profile"), user_url(@user) %>
+ <%=link_to _("My requests"), show_user_requests_path(:url_name => @user.url_name) %>
+ <%=link_to _("My profile"), show_user_profile_path(:url_name => @user.url_name) %>
<% end %>
diff --git a/app/views/public_body/_search_ahead.rhtml b/app/views/public_body/_search_ahead.rhtml
index 19c7eb4e8..436471544 100644
--- a/app/views/public_body/_search_ahead.rhtml
+++ b/app/views/public_body/_search_ahead.rhtml
@@ -1,4 +1,5 @@
<p>
+ <% if !@xapian_requests.nil? %>
<% if @xapian_requests.results.size > 0 %>
<h3><%= _('Top search results:') %></h3>
<p>
@@ -12,6 +13,7 @@
<%= render :partial => 'body_listing_single', :locals => { :public_body => result[:model] } %>
<% end %>
</div>
+ <% end %>
</p>
diff --git a/app/views/request/_search_ahead.rhtml b/app/views/request/_search_ahead.rhtml
index 9c49680c3..d0b19de7d 100644
--- a/app/views/request/_search_ahead.rhtml
+++ b/app/views/request/_search_ahead.rhtml
@@ -1,4 +1,5 @@
<div id="request_search_ahead_results">
+ <% if !@xapian_requests.nil? %>
<% if @xapian_requests.results.size > 0 %>
<h3><%= _("Possibly related requests:") %></h3>
<% end %>
@@ -9,4 +10,5 @@
<p>
<a id="body-site-search-link" target="_blank"><%= _("Or search in their website for this information.") %></a>
</p>
+ <% end %>
</div>
diff --git a/app/views/request/upload_response.rhtml b/app/views/request/upload_response.rhtml
index eaa6602a1..38c3db268 100644
--- a/app/views/request/upload_response.rhtml
+++ b/app/views/request/upload_response.rhtml
@@ -23,7 +23,7 @@ file too large for email, use the form below.')%>
<p><%= _('Enter your response below. You may attach one file (use email, or
<a href="%s">contact us</a> if you need more).')% [help_contact_path] %></p>
-<% form_tag '', :html => { :id => 'upload_response_form' }, :multipart => true do %>
+<% form_tag '', :id => 'upload_response_form', :multipart => true do %>
<p>
<label class="form_label" for="body"><% _('Response:')%></label>
<%= text_area_tag :body, "", :rows => 10, :cols => 55 %>
diff --git a/app/views/user/bad_token.rhtml b/app/views/user/bad_token.rhtml
index 443835382..538bc5606 100644
--- a/app/views/user/bad_token.rhtml
+++ b/app/views/user/bad_token.rhtml
@@ -1,6 +1,6 @@
<h1>
<%= _('Please check the URL (i.e. the long code of letters and numbers) is copied
-correctly from your email.'%>
+correctly from your email.')%>
</h1>
<p>
diff --git a/app/views/user/show.rhtml b/app/views/user/show.rhtml
index baf6621df..9ac203541 100644
--- a/app/views/user/show.rhtml
+++ b/app/views/user/show.rhtml
@@ -1,4 +1,8 @@
-<% @title = h(@display_user.name) + " - Freedom of Information requests" %>
+<% if @show_requests %>
+ <% @title = h(@display_user.name) + " - Freedom of Information requests" %>
+<% else %>
+ <% @title = h(@display_user.name) + " - user profile" %>
+<% end %>
<% if (@same_name_users.size >= 1) %>
<p><%= _('There is <strong>more than one person</strong> who uses this site and has this name.
@@ -7,7 +11,7 @@
<% end %>
<% end%>
-<% if @is_you && @undescribed_requests.size > 0 %>
+<% if @show_profile && @is_you && @undescribed_requests.size > 0 %>
<div class="undescribed_requests">
<p><%= _('Please <strong>go to the following requests</strong>, and let us
know if there was information in the recent responses to them.')%></p>
@@ -24,17 +28,18 @@
</div>
<% end %>
+<% if @show_profile %>
<div id="user_profile_header">
<div id="header_right">
- <h2><%= _('Track this person')%></h2>
- <%= render :partial => 'track/tracking_links', :locals => { :track_thing => @track_thing, :own_request => false, :location => 'sidebar' } %>
-
- <h2><%= _('On this page')%></h2>
- <a href="#foi_requests"><%= _('FOI requests')%></a>
- <br><a href="#annotations"><%= _('Annotations')%></a>
- <% if @is_you %>
- <br><a href="#email_subscriptions"><%= _('Email subscriptions')%></a>
- <% end %>
+ <% if !@track_thing.nil? %>
+ <h2><%= _('Track this person')%></h2>
+ <%= render :partial => 'track/tracking_links', :locals => { :track_thing => @track_thing, :own_request => false, :location => 'sidebar' } %>
+ <% end %>
+ <% if !@xapian_requests.nil? %>
+ <h2><%= _('On this page')%></h2>
+ <a href="#foi_requests"><%= _('FOI requests')%></a>
+ <br><a href="#annotations"><%= _('Annotations')%></a>
+ <% end %>
</div>
<div class="header_left">
@@ -116,7 +121,9 @@
</div>
</div>
<div style="clear:both"></div>
+<% end %>
+<% if @show_requests %>
<div id="user_profile_search">
<% form_tag(show_user_url, :method => "get", :id=>"search_form") do %>
<div>
@@ -154,8 +161,10 @@
<%= will_paginate WillPaginate::Collection.new(@page, @per_page, @display_user.info_requests.size) %>
<% end %>
<% else %>
+ <% if @show_requests %>
<h2 class="foi_results" id="foi_requests"><%= @is_you ? _('Freedom of Information requests made by you') : _('Freedom of Information requests made by this person') %> </h2>
<p><%= _('The search index is currently offline, so we can\'t show the Freedom of Information requests this person has made.')%></p>
+ <% end %>
<% end %>
<% if !@xapian_comments.nil? %>
@@ -181,7 +190,7 @@
<% end %>
<% end %>
- <% if @is_you %>
+ <% if @show_profile && @is_you %>
<% if @track_things.empty? %>
<h2 id="email_subscriptions"> <%= _('Your email subscriptions')%></h2>
<p><%= _('None made.')%></p>
@@ -232,4 +241,5 @@
<% end %>
<% end %>
<% end %>
-</div> \ No newline at end of file
+</div>
+<% end %>