aboutsummaryrefslogtreecommitdiffstats
path: root/app/models
diff options
context:
space:
mode:
Diffstat (limited to 'app/models')
-rw-r--r--app/models/comment.rb2
-rw-r--r--app/models/incoming_message.rb35
-rw-r--r--app/models/info_request.rb62
-rw-r--r--app/models/info_request_batch.rb73
-rw-r--r--app/models/info_request_event.rb4
-rw-r--r--app/models/outgoing_message.rb18
-rw-r--r--app/models/profile_photo.rb12
-rw-r--r--app/models/public_body.rb35
-rw-r--r--app/models/public_body_change_request.rb131
-rw-r--r--app/models/track_thing.rb2
-rw-r--r--app/models/user.rb6
11 files changed, 338 insertions, 42 deletions
diff --git a/app/models/comment.rb b/app/models/comment.rb
index 75d37e04f..b4c099123 100644
--- a/app/models/comment.rb
+++ b/app/models/comment.rb
@@ -62,7 +62,7 @@ class Comment < ActiveRecord::Base
end
# When posting a new comment, use this to check user hasn't double submitted.
- def Comment.find_by_existing_comment(info_request_id, body)
+ def Comment.find_existing(info_request_id, body)
# XXX can add other databases here which have regexp_replace
if ActiveRecord::Base.connection.adapter_name == "PostgreSQL"
# Exclude spaces from the body comparison using regexp_replace
diff --git a/app/models/incoming_message.rb b/app/models/incoming_message.rb
index bcf0b6ec9..6db145348 100644
--- a/app/models/incoming_message.rb
+++ b/app/models/incoming_message.rb
@@ -35,7 +35,7 @@
require 'htmlentities'
require 'rexml/document'
require 'zip/zip'
-require 'iconv' unless RUBY_VERSION >= '1.9'
+require 'iconv' unless String.method_defined?(:encode)
class IncomingMessage < ActiveRecord::Base
extend MessageProminence
@@ -294,7 +294,7 @@ class IncomingMessage < ActiveRecord::Base
emails = ascii_chars.scan(MySociety::Validate.email_find_regexp)
# Convert back to UCS-2, making a mask at the same time
- if RUBY_VERSION >= '1.9'
+ if String.method_defined?(:encode)
emails.map! do |email|
# We want the ASCII representation of UCS-2
[email[0].encode('UTF-16LE').force_encoding('US-ASCII'),
@@ -385,6 +385,10 @@ class IncomingMessage < ActiveRecord::Base
multiline_original_message = '(' + '''>>>.* \d\d/\d\d/\d\d\d\d\s+\d\d:\d\d(?::\d\d)?\s*>>>''' + ')'
text.gsub!(/^(#{multiline_original_message}\n.*)$/m, replacement)
+ # On Thu, Nov 28, 2013 at 9:08 AM, A User
+ # <[1]request-7-skm40s2ls@xxx.xxxx> wrote:
+ text.gsub!(/^( On [^\n]+\n\s*\<[^>\n]+\> (wrote|said):\s*\n.*)$/m, replacement)
+
# Single line sections
text.gsub!(/^(>.*\n)/, replacement)
text.gsub!(/^(On .+ (wrote|said):\n)/, replacement)
@@ -516,7 +520,7 @@ class IncomingMessage < ActiveRecord::Base
# should instead tell elinks to respect the source
# charset
use_charset = "utf-8"
- if RUBY_VERSION.to_f >= 1.9
+ if String.method_defined?(:encode)
begin
text.encode('utf-8')
rescue Encoding::UndefinedConversionError, Encoding::InvalidByteSequenceError
@@ -547,7 +551,7 @@ class IncomingMessage < ActiveRecord::Base
end
def _sanitize_text(text)
- if RUBY_VERSION.to_f >= 1.9
+ if String.method_defined?(:encode)
begin
# Test if it's good UTF-8
text.encode('utf-8')
@@ -792,27 +796,28 @@ class IncomingMessage < ActiveRecord::Base
return self.cached_attachment_text_clipped
end
- def _get_attachment_text_internal
+ def _extract_text
# Extract text from each attachment
- text = ''
- attachments = self.get_attachments_for_display
- for attachment in attachments
- text += MailHandler.get_attachment_text_one_file(attachment.content_type,
+ self.get_attachments_for_display.reduce(''){ |memo, attachment|
+ memo += MailHandler.get_attachment_text_one_file(attachment.content_type,
attachment.body,
attachment.charset)
- end
+ }
+ end
+
+ def _get_attachment_text_internal
+ text = self._extract_text
# Remove any bad characters
- if RUBY_VERSION >= '1.9'
- text.encode("utf-8", :invalid => :replace,
- :undef => :replace,
- :replace => "")
+ if String.method_defined?(:encode)
+ # handle "problematic" encoding
+ text.encode!('UTF-16', 'UTF-8', :invalid => :replace, :undef => :replace, :replace => '')
+ text.encode('UTF-8', 'UTF-16')
else
Iconv.conv('utf-8//IGNORE', 'utf-8', text)
end
end
-
# Returns text for indexing
def get_text_for_indexing_full
return get_body_for_quoting + "\n\n" + get_attachment_text_full
diff --git a/app/models/info_request.rb b/app/models/info_request.rb
index 0a073dc79..47ad435cb 100644
--- a/app/models/info_request.rb
+++ b/app/models/info_request.rb
@@ -1,5 +1,6 @@
# encoding: utf-8
# == Schema Information
+# Schema version: 20131024114346
#
# Table name: info_requests
#
@@ -21,6 +22,7 @@
# external_url :string(255)
# attention_requested :boolean default(FALSE)
# comments_allowed :boolean default(TRUE), not null
+# info_request_batch_id :integer
#
require 'digest/sha1'
@@ -40,7 +42,8 @@ class InfoRequest < ActiveRecord::Base
validate :must_be_internal_or_external
belongs_to :public_body, :counter_cache => true
- validates_presence_of :public_body_id
+ belongs_to :info_request_batch
+ validates_presence_of :public_body_id, :unless => Proc.new { |info_request| info_request.is_batch_request_template? }
has_many :outgoing_messages, :order => 'created_at'
has_many :incoming_messages, :order => 'created_at'
@@ -50,6 +53,7 @@ class InfoRequest < ActiveRecord::Base
has_many :comments, :order => 'created_at'
has_many :censor_rules, :order => 'created_at desc'
has_many :mail_server_logs, :order => 'mail_server_log_done_id'
+ attr_accessor :is_batch_request_template
has_tag_string
@@ -113,8 +117,12 @@ class InfoRequest < ActiveRecord::Base
# Possible reasons that a request could be reported for administrator attention
def report_reasons
- ["Contains defamatory material", "Not a valid request", "Request for personal information",
- "Contains personal information", "Vexatious", "Other"]
+ [_("Contains defamatory material"),
+ _("Not a valid request"),
+ _("Request for personal information"),
+ _("Contains personal information"),
+ _("Vexatious"),
+ _("Other")]
end
def must_be_valid_state
@@ -122,6 +130,10 @@ class InfoRequest < ActiveRecord::Base
!InfoRequest.enumerate_states.include? described_state
end
+ def is_batch_request_template?
+ is_batch_request_template == true
+ end
+
# The request must either be internal, in which case it has
# a foreign key reference to a User object and no external_url or external_user_name,
# or else be external in which case it has no user_id but does have an external_url,
@@ -379,7 +391,7 @@ public
# repeated requests, say once a quarter for time information, then might need to do that.
# XXX this *should* also check outgoing message joined to is an initial
# request (rather than follow up)
- def InfoRequest.find_by_existing_request(title, public_body_id, body)
+ def InfoRequest.find_existing(title, public_body_id, body)
return InfoRequest.find(:first, :conditions => [ "title = ? and public_body_id = ? and outgoing_messages.body = ?", title, public_body_id, body ], :include => [ :outgoing_messages ] )
end
@@ -928,6 +940,20 @@ public
self.idhash = InfoRequest.hash_from_id(self.id)
end
+ def InfoRequest.create_from_attributes(info_request_atts, outgoing_message_atts, user=nil)
+ info_request = new(info_request_atts)
+ default_message_params = {
+ :status => 'ready',
+ :message_type => 'initial_request',
+ :what_doing => 'normal_sort'
+ }
+ outgoing_message = OutgoingMessage.new(outgoing_message_atts.merge(default_message_params))
+ info_request.outgoing_messages << outgoing_message
+ outgoing_message.info_request = info_request
+ info_request.user = user
+ info_request
+ end
+
def InfoRequest.hash_from_id(id)
return Digest::SHA1.hexdigest(id.to_s + AlaveteliConfiguration::incoming_email_secret)[0,8]
end
@@ -1074,7 +1100,10 @@ public
# Get the list of censor rules that apply to this request
def applicable_censor_rules
- applicable_rules = [self.censor_rules, self.public_body.censor_rules, CensorRule.global.all]
+ applicable_rules = [self.censor_rules, CensorRule.global.all]
+ unless is_batch_request_template?
+ applicable_rules << self.public_body.censor_rules
+ end
if self.user && !self.user.censor_rules.empty?
applicable_rules << self.user.censor_rules
end
@@ -1228,6 +1257,23 @@ public
return [xapian_similar, xapian_similar_more]
end
+ def InfoRequest.request_list(filters, page, per_page, max_results)
+ xapian_object = ActsAsXapian::Search.new([InfoRequestEvent],
+ InfoRequestEvent.make_query_from_params(filters),
+ :offset => (page - 1) * per_page,
+ :limit => 25,
+ :sort_by_prefix => 'created_at',
+ :sort_by_ascending => true,
+ :collapse_by_prefix => 'request_collapse'
+ )
+ list_results = xapian_object.results.map { |r| r[:model] }
+ matches_estimated = xapian_object.matches_estimated
+ show_no_more_than = [matches_estimated, max_results].min
+ return { :results => list_results,
+ :matches_estimated => matches_estimated,
+ :show_no_more_than => show_no_more_than }
+ end
+
def InfoRequest.recent_requests
request_events = []
request_events_all_successful = false
@@ -1275,6 +1321,12 @@ public
return [request_events, request_events_all_successful]
end
+ def InfoRequest.find_in_state(state)
+ find(:all, :select => '*, ' + last_event_time_clause + ' as last_event_time',
+ :conditions => ["described_state = ?", state],
+ :order => "last_event_time")
+ end
+
private
def set_defaults
diff --git a/app/models/info_request_batch.rb b/app/models/info_request_batch.rb
new file mode 100644
index 000000000..498ab4951
--- /dev/null
+++ b/app/models/info_request_batch.rb
@@ -0,0 +1,73 @@
+# == Schema Information
+# Schema version: 20131024114346
+#
+# Table name: info_request_batches
+#
+# id :integer not null, primary key
+# title :text not null
+# user_id :integer not null
+# created_at :datetime
+# updated_at :datetime
+#
+
+class InfoRequestBatch < ActiveRecord::Base
+ has_many :info_requests
+ belongs_to :user
+ has_and_belongs_to_many :public_bodies
+
+ validates_presence_of :user
+ validates_presence_of :title
+ validates_presence_of :body
+
+ # When constructing a new batch, use this to check user hasn't double submitted.
+ def InfoRequestBatch.find_existing(user, title, body, public_body_ids)
+ find(:first, :conditions => ['user_id = ?
+ AND title = ?
+ AND body = ?
+ AND info_request_batches_public_bodies.public_body_id in (?)',
+ user, title, body, public_body_ids],
+ :include => :public_bodies)
+ end
+
+ # Create a batch of information requests, returning a list of public bodies
+ # that are unrequestable from the initial list of public body ids passed.
+ def create_batch!
+ unrequestable = []
+ created = []
+ ActiveRecord::Base.transaction do
+ public_bodies.each do |public_body|
+ if public_body.is_requestable?
+ created << create_request!(public_body)
+ else
+ unrequestable << public_body
+ end
+ end
+ self.sent_at = Time.now
+ self.save!
+ end
+ created.each{ |info_request| info_request.outgoing_messages.first.send_message }
+
+ return unrequestable
+ end
+
+ # Create and send an FOI request to a public body
+ def create_request!(public_body)
+ body = OutgoingMessage.fill_in_salutation(self.body, public_body)
+ info_request = InfoRequest.create_from_attributes({:title => self.title},
+ {:body => body},
+ self.user)
+ info_request.public_body_id = public_body.id
+ info_request.info_request_batch = self
+ info_request.save!
+ info_request
+ end
+
+ def InfoRequestBatch.send_batches()
+ find_each(:conditions => "sent_at IS NULL") do |info_request_batch|
+ unrequestable = info_request_batch.create_batch!
+ mail_message = InfoRequestBatchMailer.batch_sent(info_request_batch,
+ unrequestable,
+ info_request_batch.user).deliver
+ end
+ end
+end
diff --git a/app/models/info_request_event.rb b/app/models/info_request_event.rb
index e268b28ca..5eed5ba83 100644
--- a/app/models/info_request_event.rb
+++ b/app/models/info_request_event.rb
@@ -21,6 +21,9 @@
# Email: hello@mysociety.org; WWW: http://www.mysociety.org/
class InfoRequestEvent < ActiveRecord::Base
+
+ extend XapianQueries
+
belongs_to :info_request
validates_presence_of :info_request
@@ -416,4 +419,5 @@ class InfoRequestEvent < ActiveRecord::Base
yield(column.human_name, self.send(column.name), column.type.to_s, column.name)
end
end
+
end
diff --git a/app/models/outgoing_message.rb b/app/models/outgoing_message.rb
index 6efc1d2ba..a435511d3 100644
--- a/app/models/outgoing_message.rb
+++ b/app/models/outgoing_message.rb
@@ -1,4 +1,5 @@
# == Schema Information
+# Schema version: 20131024114346
#
# Table name: outgoing_messages
#
@@ -67,15 +68,30 @@ class OutgoingMessage < ActiveRecord::Base
# How the default letter starts and ends
def get_salutation
+ if self.info_request.is_batch_request_template?
+ return OutgoingMessage.placeholder_salutation
+ end
ret = ""
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 + self.info_request.public_body.name
+ return OutgoingMessage.default_salutation(self.info_request.public_body)
end
salutation = _("Dear {{public_body_name}},", :public_body_name => ret)
end
+ def OutgoingMessage.default_salutation(public_body)
+ _("Dear {{public_body_name}},", :public_body_name => public_body.name)
+ end
+
+ def OutgoingMessage.placeholder_salutation
+ _("Dear [Authority name],")
+ end
+
+ def OutgoingMessage.fill_in_salutation(body, public_body)
+ body.gsub(placeholder_salutation, default_salutation(public_body))
+ end
+
def get_signoff
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?
return _("Yours sincerely,")
diff --git a/app/models/profile_photo.rb b/app/models/profile_photo.rb
index 322ebe53c..6c3b2cfa0 100644
--- a/app/models/profile_photo.rb
+++ b/app/models/profile_photo.rb
@@ -70,21 +70,25 @@ class ProfilePhoto < ActiveRecord::Base
def data_and_draft_checks
if self.data.nil?
- errors.add(:data, N_("Please choose a file containing your photo."))
+ errors.add(:data, _("Please choose a file containing your photo."))
return
end
if self.image.nil?
- errors.add(:data, N_("Couldn't understand the image file that you uploaded. PNG, JPEG, GIF and many other common image file formats are supported."))
+ errors.add(:data, _("Couldn't understand the image file that you uploaded. PNG, JPEG, GIF and many other common image file formats are supported."))
return
end
if self.image.format != 'PNG'
- errors.add(:data, N_("Failed to convert image to a PNG"))
+ errors.add(:data, _("Failed to convert image to a PNG"))
end
if !self.draft && (self.image.columns != WIDTH || self.image.rows != HEIGHT)
- errors.add(:data, N_("Failed to convert image to the correct size: at {{cols}}x{{rows}}, need {{width}}x{{height}}", :cols => self.image.columns, :rows => self.image.rows, :width => WIDTH, :height => HEIGHT))
+ errors.add(:data, _("Failed to convert image to the correct size: at {{cols}}x{{rows}}, need {{width}}x{{height}}",
+ :cols => self.image.columns,
+ :rows => self.image.rows,
+ :width => WIDTH,
+ :height => HEIGHT))
end
if self.draft && self.user_id
diff --git a/app/models/public_body.rb b/app/models/public_body.rb
index 933825d2a..7b1ded820 100644
--- a/app/models/public_body.rb
+++ b/app/models/public_body.rb
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
# == Schema Information
+# Schema version: 20131024114346
#
# Table name: public_bodies
#
@@ -346,22 +347,26 @@ class PublicBody < ActiveRecord::Base
# The "internal admin" is a special body for internal use.
def PublicBody.internal_admin_body
- I18n.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 => AlaveteliConfiguration::contact_email,
- :home_page => "",
- :notes => "",
- :publication_scheme => "",
- :last_edit_editor => "internal_admin",
- :last_edit_comment => "Made by PublicBody.internal_admin_body"
- )
- pb.save!
+ # Use find_by_sql to avoid the search being specific to a
+ # locale, since url_name is a translated field:
+ sql = "SELECT * FROM public_bodies WHERE url_name = 'internal_admin_authority'"
+ matching_pbs = PublicBody.find_by_sql sql
+ case
+ when matching_pbs.empty? then
+ I18n.with_locale(I18n.default_locale) do
+ PublicBody.create!(:name => 'Internal admin authority',
+ :short_name => "",
+ :request_email => AlaveteliConfiguration::contact_email,
+ :home_page => "",
+ :notes => "",
+ :publication_scheme => "",
+ :last_edit_editor => "internal_admin",
+ :last_edit_comment => "Made by PublicBody.internal_admin_body")
end
- return pb
+ when matching_pbs.length == 1 then
+ matching_pbs[0]
+ else
+ raise "Multiple public bodies (#{matching_pbs.length}) found with url_name 'internal_admin_authority'"
end
end
diff --git a/app/models/public_body_change_request.rb b/app/models/public_body_change_request.rb
new file mode 100644
index 000000000..0e59cbecc
--- /dev/null
+++ b/app/models/public_body_change_request.rb
@@ -0,0 +1,131 @@
+# == Schema Information
+#
+# Table name: public_body_change_requests
+#
+# id :integer not null, primary key
+# user_email :string(255)
+# user_name :string(255)
+# user_id :integer
+# public_body_name :text
+# public_body_id :integer
+# public_body_email :string(255)
+# source_url :text
+# notes :text
+# is_open :boolean default(TRUE), not null
+# created_at :datetime not null
+# updated_at :datetime not null
+#
+
+class PublicBodyChangeRequest < ActiveRecord::Base
+
+ belongs_to :user
+ belongs_to :public_body
+ validates_presence_of :public_body_name, :message => N_("Please enter the name of the authority"),
+ :unless => proc{ |change_request| change_request.public_body }
+ validates_presence_of :user_name, :message => N_("Please enter your name"),
+ :unless => proc{ |change_request| change_request.user }
+ validates_presence_of :user_email, :message => N_("Please enter your email address"),
+ :unless => proc{ |change_request| change_request.user }
+ validate :user_email_format, :unless => proc{ |change_request| change_request.user_email.blank? }
+ validate :body_email_format, :unless => proc{ |change_request| change_request.public_body_email.blank? }
+
+ scope :new_body_requests, :conditions => ['public_body_id IS NULL'], :order => 'created_at'
+ scope :body_update_requests, :conditions => ['public_body_id IS NOT NULL'], :order => 'created_at'
+ scope :open, :conditions => ['is_open = ?', true]
+
+ def self.from_params(params, user)
+ change_request = new
+ change_request.update_from_params(params, user)
+ end
+
+ def update_from_params(params, user)
+ if user
+ self.user_id = user.id
+ else
+ self.user_name = params[:user_name]
+ self.user_email = params[:user_email]
+ end
+ self.public_body_name = params[:public_body_name]
+ self.public_body_id = params[:public_body_id]
+ self.public_body_email = params[:public_body_email]
+ self.source_url = params[:source_url]
+ self.notes = params[:notes]
+ self
+ end
+
+ def get_user_name
+ user ? user.name : user_name
+ end
+
+ def get_user_email
+ user ? user.email : user_email
+ end
+
+ def get_public_body_name
+ public_body ? public_body.name : public_body_name
+ end
+
+ def send_message
+ if public_body
+ ContactMailer.update_public_body_email(self).deliver
+ else
+ ContactMailer.add_public_body(self).deliver
+ end
+ end
+
+ def thanks_notice
+ if self.public_body
+ _("Your request to update the address for {{public_body_name}} has been sent. Thank you for getting in touch! We'll get back to you soon.",
+ :public_body_name => get_public_body_name)
+ else
+ _("Your request to add an authority has been sent. Thank you for getting in touch! We'll get back to you soon.")
+ end
+ end
+
+ def send_response(subject, response)
+ ContactMailer.from_admin_message(get_user_name,
+ get_user_email,
+ subject,
+ response.strip.html_safe).deliver
+ end
+
+ def comment_for_public_body
+ comments = ["Requested by: #{get_user_name} (#{get_user_email})"]
+ if !source_url.blank?
+ comments << "Source URL: #{source_url}"
+ end
+ if !notes.blank?
+ comments << "Notes: #{notes}"
+ end
+ comments.join("\n")
+ end
+
+ def default_response_subject
+ if self.public_body
+ _("Your request to update {{public_body_name}} on {{site_name}}", :site_name => AlaveteliConfiguration::site_name,
+ :public_body_name => public_body.name)
+ else
+ _("Your request to add {{public_body_name}} to {{site_name}}", :site_name => AlaveteliConfiguration::site_name,
+ :public_body_name => public_body_name)
+ end
+ end
+
+ def close!
+ self.is_open = false
+ self.save!
+ end
+
+ private
+
+ def body_email_format
+ unless MySociety::Validate.is_valid_email(self.public_body_email)
+ errors.add(:public_body_email, _("The authority email doesn't look like a valid address"))
+ end
+ end
+
+ def user_email_format
+ unless MySociety::Validate.is_valid_email(self.user_email)
+ errors.add(:user_email, _("Your email doesn't look like a valid address"))
+ end
+ end
+end
diff --git a/app/models/track_thing.rb b/app/models/track_thing.rb
index d5e1cdb75..d5dda7bb5 100644
--- a/app/models/track_thing.rb
+++ b/app/models/track_thing.rb
@@ -311,7 +311,7 @@ class TrackThing < ActiveRecord::Base
end
# When constructing a new track, use this to avoid duplicates / double posting
- def TrackThing.find_by_existing_track(tracking_user, track)
+ def TrackThing.find_existing(tracking_user, track)
if tracking_user.nil?
return nil
end
diff --git a/app/models/user.rb b/app/models/user.rb
index 2c4f87944..e63ce8129 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -1,4 +1,5 @@
# == Schema Information
+# Schema version: 20131024114346
#
# Table name: users
#
@@ -20,6 +21,7 @@
# email_bounce_message :text default(""), not null
# no_limit :boolean default(FALSE), not null
# receive_email_alerts :boolean default(TRUE), not null
+# can_make_batch_requests :boolean default(FALSE), not null
#
require 'digest/sha1'
@@ -40,6 +42,7 @@ class User < ActiveRecord::Base
has_many :comments, :order => 'created_at desc'
has_one :profile_photo
has_many :censor_rules, :order => 'created_at desc'
+ has_many :info_request_batches, :order => 'created_at desc'
attr_accessor :password_confirmation, :no_xapian_reindex
validates_confirmation_of :password, :message => _("Please enter the same password twice")
@@ -269,6 +272,9 @@ class User < ActiveRecord::Base
# Some users have no limit
return false if self.no_limit
+ # Batch request users don't have a limit
+ return false if self.can_make_batch_requests?
+
# Has the user issued as many as MAX_REQUESTS_PER_USER_PER_DAY requests in the past 24 hours?
return false if AlaveteliConfiguration::max_requests_per_user_per_day.blank?
recent_requests = InfoRequest.count(:conditions => ["user_id = ? and created_at > now() - '1 day'::interval", self.id])