diff options
Diffstat (limited to 'app/models')
-rw-r--r-- | app/models/comment.rb | 2 | ||||
-rw-r--r-- | app/models/incoming_message.rb | 35 | ||||
-rw-r--r-- | app/models/info_request.rb | 62 | ||||
-rw-r--r-- | app/models/info_request_batch.rb | 73 | ||||
-rw-r--r-- | app/models/info_request_event.rb | 4 | ||||
-rw-r--r-- | app/models/outgoing_message.rb | 18 | ||||
-rw-r--r-- | app/models/profile_photo.rb | 12 | ||||
-rw-r--r-- | app/models/public_body.rb | 35 | ||||
-rw-r--r-- | app/models/public_body_change_request.rb | 131 | ||||
-rw-r--r-- | app/models/track_thing.rb | 2 | ||||
-rw-r--r-- | app/models/user.rb | 6 |
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]) |