diff options
Diffstat (limited to 'app/models')
28 files changed, 281 insertions, 1174 deletions
diff --git a/app/models/about_me_validator.rb b/app/models/about_me_validator.rb index 8ee505ac8..7df70fb61 100644 --- a/app/models/about_me_validator.rb +++ b/app/models/about_me_validator.rb @@ -1,25 +1,23 @@ -# == Schema Information -# Schema version: 114 -# -# Table name: about_me_validators -# -#  about_me :text            default("I..."), not null -# -  # models/about_me_validator.rb:  # Validates editing about me text on user profile pages.  #  # Copyright (c) 2010 UK Citizens Online Democracy. All rights reserved. -# Email: francis@mysociety.org; WWW: http://www.mysociety.org/ +# Email: hello@mysociety.org; WWW: http://www.mysociety.org/ -class AboutMeValidator < ActiveRecord::BaseWithoutTable -    strip_attributes! +class AboutMeValidator +    include ActiveModel::Validations -    column :about_me, :text, "I...", false +    attr_accessor :about_me      # TODO: Switch to built in validations      validate :length_of_about_me +    def initialize(attributes = {}) +        attributes.each do |name, value| +            send("#{name}=", value) +        end +    end +      private      def length_of_about_me diff --git a/app/models/application_mailer.rb b/app/models/application_mailer.rb deleted file mode 100644 index 1a97a4bf9..000000000 --- a/app/models/application_mailer.rb +++ /dev/null @@ -1,164 +0,0 @@ -# models/application_mailer.rb: -# Shared code between different mailers. -# -# Copyright (c) 2008 UK Citizens Online Democracy. All rights reserved. -# Email: francis@mysociety.org; WWW: http://www.mysociety.org/ - -require 'action_mailer/version' -class ApplicationMailer < ActionMailer::Base -    # Include all the functions views get, as emails call similar things. -    helper :application -    include MailerHelper - -    # This really should be the default - otherwise you lose any information -    # about the errors, and have to do error checking on return codes. -    self.raise_delivery_errors = true - -    def blackhole_email -        Configuration::blackhole_prefix+"@"+Configuration::incoming_email_domain -    end - -    # URL generating functions are needed by all controllers (for redirects), -    # views (for links) and mailers (for use in emails), so include them into -    # all of all. -    include LinkToHelper - -    # Site-wide access to configuration settings -    include ConfigHelper - -    # Instantiate a new mailer object. If +method_name+ is not +nil+, the mailer -    # will be initialized according to the named method. If not, the mailer will -    # remain uninitialized (useful when you only need to invoke the "receive" -    # method, for instance). -    def initialize(method_name=nil, *parameters) #:nodoc: -      create!(method_name, *parameters) if method_name -    end - -    # For each multipart template (e.g. "the_template_file.text.html.erb") available, -        # add the one from the view path with the highest priority as a part to the mail -        def render_multipart_templates -            added_content_types = {} -            self.view_paths.each do |view_path| -                Dir.glob("#{view_path}/#{mailer_name}/#{@template}.*").each do |path| -                  template = view_path["#{mailer_name}/#{File.basename(path)}"] - -                  # Skip unless template has a multipart format -                  next unless template && template.multipart? -                  next if added_content_types[template.content_type] == true -                  @parts << Part.new( -                    :content_type => template.content_type, -                    :disposition => "inline", -                    :charset => charset, -                    :body => render_message(template, @body) -                  ) -                  added_content_types[template.content_type] = true -                end -            end -        end - -        # Look for the current template in each element of view_paths in order, -        # return the first -        def find_template -            self.view_paths.each do |view_path| -                if template = view_path["#{mailer_name}/#{@template}"] -                    return template -                end -            end -            return nil -        end - -    if ActionMailer::VERSION::MAJOR == 2 - -        # This method is a customised version of ActionMailer::Base.create! -        # modified to allow templates to be selected correctly for multipart -        # mails when themes have added to the view_paths. The problem from our -        # point of view with ActionMailer::Base is that it sets template_root to -        # the first element of the view_paths array and then uses only that (directly -        # and via template_path, which is created from it) in the create! method when -        # looking for templates. Our modified version looks for templates in the view_paths -        # in order. - -        # It also has a line converting the mail subject to a string. This is because we -        # use translated strings in the subject lines, sometimes in conjunction with -        # user input, like request titles. The _() function used for translation -        # returns an instance of SafeBuffer, which doesn't handle gsub calls in the block form -        # with $ variables - https://github.com/rails/rails/issues/1555. -        # Unfortunately ActionMailer uses that form in quoted_printable(), which will be -        # called if any part of the subject requires quoting. So we convert the subject -        # back to a string via to_str() before passing in to create_mail. There is a test -        # for this in spec/models/request_mailer_spec.rb - -        # Changed lines marked with *** - -        # Initialize the mailer via the given +method_name+. The body will be -        # rendered and a new TMail::Mail object created. -        def create!(method_name, *parameters) #:nodoc: -          initialize_defaults(method_name) -          __send__(method_name, *parameters) - -          # If an explicit, textual body has not been set, we check assumptions. -          unless String === @body -            # First, we look to see if there are any likely templates that match, -            # which include the content-type in their file name (i.e., -            # "the_template_file.text.html.erb", etc.). Only do this if parts -            # have not already been specified manually. -            if @parts.empty? -              # *** render_multipart_templates replaces the following code -              # Dir.glob("#{template_path}/#{@template}.*").each do |path| -              #   template = template_root["#{mailer_name}/#{File.basename(path)}"] -              # -              #   # Skip unless template has a multipart format -              #   next unless template && template.multipart? -              # -              #   @parts << Part.new( -              #     :content_type => template.content_type, -              #     :disposition => "inline", -              #     :charset => charset, -              #     :body => render_message(template, @body) -              #   ) -              # end -              render_multipart_templates - -              unless @parts.empty? -                @content_type = "multipart/alternative" if @content_type !~ /^multipart/ -                @parts = sort_parts(@parts, @implicit_parts_order) -              end -            end - -            # Then, if there were such templates, we check to see if we ought to -            # also render a "normal" template (without the content type). If a -            # normal template exists (or if there were no implicit parts) we render -            # it. -            template_exists = @parts.empty? - -            # *** find_template replaces template_root call -            # template_exists ||= template_root["#{mailer_name}/#{@template}"] -            template_exists ||= find_template - -            @body = render_message(@template, @body) if template_exists - -            # Finally, if there are other message parts and a textual body exists, -            # we shift it onto the front of the parts and set the body to nil (so -            # that create_mail doesn't try to render it in addition to the parts). -            if !@parts.empty? && String === @body -              @parts.unshift ActionMailer::Part.new(:charset => charset, :body => @body) -              @body = nil -            end -          end - -          # If this is a multipart e-mail add the mime_version if it is not -          # already set. -          @mime_version ||= "1.0" if !@parts.empty? - -          # *** Convert into a string -          @subject = @subject.to_str if @subject - -          # build the mail object itself -          @mail = create_mail -        end -    else -        raise "ApplicationMailer.create! is obsolete - find another way to ensure that themes can override mail templates for multipart mails" -    end - -end - diff --git a/app/models/censor_rule.rb b/app/models/censor_rule.rb index f40ab6fbb..f0d06e088 100644 --- a/app/models/censor_rule.rb +++ b/app/models/censor_rule.rb @@ -20,7 +20,7 @@  # Stores alterations to remove specific data from requests.  #  # Copyright (c) 2008 UK Citizens Online Democracy. All rights reserved. -# Email: francis@mysociety.org; WWW: http://www.mysociety.org/ +# Email: hello@mysociety.org; WWW: http://www.mysociety.org/  class CensorRule < ActiveRecord::Base      belongs_to :info_request @@ -33,13 +33,15 @@ class CensorRule < ActiveRecord::Base      validate :require_valid_regexp, :if => proc{ |rule| rule.regexp? == true }      validates_presence_of :text -    named_scope :global, {:conditions => {:info_request_id => nil, -                                          :user_id => nil, -                                          :public_body_id => nil}} +    scope :global, {:conditions => {:info_request_id => nil, +                                    :user_id => nil, +                                    :public_body_id => nil}}      def require_user_request_or_public_body          if self.info_request.nil? && self.user.nil? && self.public_body.nil? -            errors.add("Censor must apply to an info request a user or a body; ") +            [:info_request, :user, :public_body].each do |a| +                errors.add(a, "Rule must apply to an info request, a user or a body") +            end          end      end diff --git a/app/models/change_email_validator.rb b/app/models/change_email_validator.rb index 2ddebb177..5cc13d4c2 100644 --- a/app/models/change_email_validator.rb +++ b/app/models/change_email_validator.rb @@ -1,36 +1,27 @@ -# == Schema Information -# Schema version: 114 -# -# Table name: change_email_validators -# -#  old_email         :string -#  new_email         :string -#  password          :string -#  user_circumstance :string -# -  # models/changeemail_validator.rb:  # Validates email change form submissions.  #  # Copyright (c) 2010 UK Citizens Online Democracy. All rights reserved. -# Email: francis@mysociety.org; WWW: http://www.mysociety.org/ - -class ChangeEmailValidator < ActiveRecord::BaseWithoutTable -    strip_attributes! +# Email: hello@mysociety.org; WWW: http://www.mysociety.org/ -    column :old_email, :string -    column :new_email, :string -    column :password, :string -    column :user_circumstance, :string +class ChangeEmailValidator +    include ActiveModel::Validations -    attr_accessor :logged_in_user +    attr_accessor :old_email, :new_email, :password, :user_circumstance, :logged_in_user      validates_presence_of :old_email, :message => N_("Please enter your old email address")      validates_presence_of :new_email, :message => N_("Please enter your new email address")      validates_presence_of :password, :message => N_("Please enter your password"), :unless => :changing_email      validate :password_and_format_of_email -    def changing_email() +    def initialize(attributes = {}) +        attributes.each do |name, value| +            send("#{name}=", value) +        end +    end + + +    def changing_email        self.user_circumstance == 'change_email'      end @@ -41,11 +32,11 @@ class ChangeEmailValidator < ActiveRecord::BaseWithoutTable              errors.add(:old_email, _("Old email doesn't look like a valid address"))          end -        if !errors[:old_email] +        if errors[:old_email].blank?              if self.old_email.downcase != self.logged_in_user.email.downcase                  errors.add(:old_email, _("Old email address isn't the same as the address of the account you are logged in with"))              elsif (!self.changing_email) && (!self.logged_in_user.has_this_password?(self.password)) -                if !errors[:password] +                if errors[:password].blank?                      errors.add(:password, _("Password is not correct"))                  end              end @@ -55,5 +46,4 @@ class ChangeEmailValidator < ActiveRecord::BaseWithoutTable              errors.add(:new_email, _("New email doesn't look like a valid address"))          end      end -  end diff --git a/app/models/comment.rb b/app/models/comment.rb index 70f3ba00d..9527030a9 100644 --- a/app/models/comment.rb +++ b/app/models/comment.rb @@ -18,7 +18,7 @@  # A comment by a user upon something.  #  # Copyright (c) 2008 UK Citizens Online Democracy. All rights reserved. -# Email: francis@mysociety.org; WWW: http://www.mysociety.org/ +# Email: hello@mysociety.org; WWW: http://www.mysociety.org/  class Comment < ActiveRecord::Base      strip_attributes! diff --git a/app/models/contact_mailer.rb b/app/models/contact_mailer.rb deleted file mode 100644 index 318f54ea8..000000000 --- a/app/models/contact_mailer.rb +++ /dev/null @@ -1,56 +0,0 @@ -# models/contact_mailer.rb: -# Sends contact form mails. -# -# Copyright (c) 2008 UK Citizens Online Democracy. All rights reserved. -# Email: francis@mysociety.org; WWW: http://www.mysociety.org/ - -class ContactMailer < ApplicationMailer - -    # Send message to administrator -    def to_admin_message(name, email, subject, message, logged_in_user, last_request, last_body) -        @from = "#{name} <#{email}>" -        @recipients = contact_from_name_and_email -        @subject = subject -        @body = { :message => message, -            :logged_in_user => logged_in_user , -            :last_request => last_request, -            :last_body => last_body -        } -    end - -    # We always set Reply-To when we set Return-Path to be different from From, -    # since some email clients seem to erroneously use the envelope from when -    # they shouldn't, and this might help. (Have had mysterious cases of a -    # reply coming in duplicate from a public body to both From and envelope -    # from) - -    # Send message to another user -    def user_message(from_user, recipient_user, from_user_url, subject, message) -        @from = from_user.name_and_email -        # Do not set envelope from address to the from_user, so they can't get -        # someone's email addresses from transitory bounce messages. -        headers 'Return-Path' => blackhole_email, 'Reply-To' => @from -        @recipients = recipient_user.name_and_email -        @subject = subject -        @body = { -            :message => message, -            :from_user => from_user, -            :recipient_user => recipient_user, -            :from_user_url => from_user_url -        } -    end - -    # Send message to a user from the administrator -    def from_admin_message(recipient_user, subject, message) -        @from = contact_from_name_and_email -        @recipients = recipient_user.name_and_email -        @subject = subject -        @body = { -            :message => message, -            :from_user => @from, -            :recipient_user => recipient_user, -        } -        bcc Configuration::contact_email -    end - -end diff --git a/app/models/contact_validator.rb b/app/models/contact_validator.rb index d277161f9..65e539669 100644 --- a/app/models/contact_validator.rb +++ b/app/models/contact_validator.rb @@ -1,35 +1,29 @@ -# == Schema Information -# Schema version: 114 -# -# Table name: contact_validators -# -#  name    :string -#  email   :string -#  subject :text -#  message :text -# -  # models/contact_validator.rb:  # Validates contact form submissions.  #  # Copyright (c) 2008 UK Citizens Online Democracy. All rights reserved. -# Email: francis@mysociety.org; WWW: http://www.mysociety.org/ +# Email: hello@mysociety.org; WWW: http://www.mysociety.org/ -class ContactValidator < ActiveRecord::BaseWithoutTable -    strip_attributes! +class ContactValidator +    include ActiveModel::Validations -    column :name, :string -    column :email, :string -    column :subject, :text -    column :message, :text +    attr_accessor :name, :email, :subject, :message      validates_presence_of :name, :message => N_("Please enter your name")      validates_presence_of :email, :message => N_("Please enter your email address")      validates_presence_of :subject, :message => N_("Please enter a subject")      validates_presence_of :message, :message => N_("Please enter the message you want to send") +    validate :email_format -    def validate -        errors.add(:email, _("Email doesn't look like a valid address")) unless MySociety::Validate.is_valid_email(self.email) +    def initialize(attributes = {}) +        attributes.each do |name, value| +            send("#{name}=", value) +        end      end +    private + +    def email_format +        errors.add(:email, _("Email doesn't look like a valid address")) unless MySociety::Validate.is_valid_email(self.email) +    end  end diff --git a/app/models/foi_attachment.rb b/app/models/foi_attachment.rb index bba0b6a8d..0340f2b83 100644 --- a/app/models/foi_attachment.rb +++ b/app/models/foi_attachment.rb @@ -20,7 +20,7 @@  # 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/ +# Email: hello@mysociety.org; WWW: http://www.mysociety.org/  # This is the type which is used to send data about attachments to the view  require 'digest' @@ -38,11 +38,7 @@ class FoiAttachment < ActiveRecord::Base      BODY_MAX_DELAY = 5      def directory -        rails_env = Rails.env -        if rails_env.nil? || rails_env.empty? -            raise "$RAILS_ENV is not set" -        end -        base_dir = File.expand_path(File.join(File.dirname(__FILE__), "../../cache", "attachments_#{rails_env}")) +        base_dir = File.expand_path(File.join(File.dirname(__FILE__), "../../cache", "attachments_#{Rails.env}"))          return File.join(base_dir, self.hexdigest[0..2])      end @@ -67,28 +63,20 @@ class FoiAttachment < ActiveRecord::Base              file.write d          }          update_display_size! -        encode_cached_body!          @cached_body = d      end -    # If the original mail part had a charset, it's some kind of string, so assume that -    # it should be handled as a string in the stated charset, not a bytearray, and then -    # convert it our default encoding. For ruby 1.8 this is a noop. -    def encode_cached_body! -        if RUBY_VERSION.to_f >= 1.9 -            if charset -                @cached_body.force_encoding(charset) -                @cached_body = @cached_body.encode(Encoding.default_internal, charset) -            end -        end -    end -      def body          if @cached_body.nil?              tries = 0              delay = 1              begin -                @cached_body = File.open(self.filepath, "rb" ).read +                binary_data = File.open(self.filepath, "rb" ).read +                if self.content_type =~ /^text/ +                    @cached_body = convert_string_to_utf8_or_binary(binary_data, 'UTF-8') +                else +                    @cached_body = binary_data +                end              rescue Errno::ENOENT                  # we've lost our cached attachments for some reason.  Reparse them.                  if tries > BODY_MAX_TRIES @@ -103,7 +91,6 @@ class FoiAttachment < ActiveRecord::Base                  self.incoming_message.parse_raw_email!(force)                  retry              end -            encode_cached_body!          end          return @cached_body      end diff --git a/app/models/holiday.rb b/app/models/holiday.rb index 13258396a..98f73e963 100644 --- a/app/models/holiday.rb +++ b/app/models/holiday.rb @@ -19,7 +19,7 @@  #    -- Freedom of Information Act 2000 section 10  #  # Copyright (c) 2009 UK Citizens Online Democracy. All rights reserved. -# Email: francis@mysociety.org; WWW: http://www.mysociety.org/ +# Email: hello@mysociety.org; WWW: http://www.mysociety.org/  class Holiday < ActiveRecord::Base diff --git a/app/models/incoming_message.rb b/app/models/incoming_message.rb index 5c845ead3..f959a8799 100644 --- a/app/models/incoming_message.rb +++ b/app/models/incoming_message.rb @@ -25,19 +25,16 @@  # response from the public body.  #  # Copyright (c) 2007 UK Citizens Online Democracy. All rights reserved. -# Email: francis@mysociety.org; WWW: http://www.mysociety.org/ +# Email: hello@mysociety.org; WWW: http://www.mysociety.org/  # TODO  # Move some of the (e.g. quoting) functions here into rblib, as they feel  # general not specific to IncomingMessage. -require 'alaveteli_file_types'  require 'htmlentities'  require 'rexml/document'  require 'zip/zip' -require 'mapi/msg' -require 'mapi/convert' - +require 'iconv' unless RUBY_VERSION >= '1.9'  class IncomingMessage < ActiveRecord::Base      belongs_to :info_request @@ -132,6 +129,7 @@ class IncomingMessage < ActiveRecord::Base                  end                  self.valid_to_reply_to = self._calculate_valid_to_reply_to                  self.last_parsed = Time.now +                self.foi_attachments reload=true                  self.save!              end          end @@ -173,15 +171,29 @@ class IncomingMessage < ActiveRecord::Base          super      end -    # And look up by URL part number to get an attachment +    # And look up by URL part number and display filename to get an attachment      # XXX relies on extract_attachments calling MailHandler.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 -                return a +    # The filename here is passed from the URL parameter, so it's the +    # display_filename rather than the real filename. +    def self.get_attachment_by_url_part_number_and_filename(attachments, found_url_part_number, display_filename) +        attachment_by_part_number = attachments.detect { |a| a.url_part_number == found_url_part_number } +        if attachment_by_part_number && attachment_by_part_number.display_filename == display_filename +            # Then the filename matches, which is fine: +            attachment_by_part_number +        else +            # Otherwise if the URL part number and filename don't +            # match - this is probably due to a reparsing of the +            # email.  In that case, try to find a unique matching +            # filename from any attachment. +            attachments_by_filename = attachments.select { |a| +                a.display_filename == display_filename +            } +            if attachments_by_filename.length == 1 +                attachments_by_filename[0] +            else +                nil              end          end -        return nil      end      # Converts email addresses we know about into textual descriptions of them @@ -193,7 +205,7 @@ class IncomingMessage < ActiveRecord::Base              text.gsub!(self.info_request.public_body.request_email, _("[{{public_body}} request email]", :public_body => self.info_request.public_body.short_or_long_name))          end          text.gsub!(self.info_request.incoming_email, _('[FOI #{{request}} email]', :request => self.info_request.id.to_s) ) -        text.gsub!(Configuration::contact_email, _("[{{site_name}} contact email]", :site_name => Configuration::site_name) ) +        text.gsub!(AlaveteliConfiguration::contact_email, _("[{{site_name}} contact email]", :site_name => AlaveteliConfiguration::site_name) )      end      # Replaces all email addresses in (possibly binary data) with equal length alternative ones. @@ -219,7 +231,7 @@ class IncomingMessage < ActiveRecord::Base                  if censored_uncompressed_text != uncompressed_text                      # then use the altered file (recompressed)                      recompressed_text = nil -                    if Configuration::use_ghostscript_compression == true +                    if AlaveteliConfiguration::use_ghostscript_compression == true                          command = ["gs", "-sDEVICE=pdfwrite", "-dCompatibilityLevel=1.4", "-dPDFSETTINGS=/screen", "-dNOPAUSE", "-dQUIET", "-dBATCH", "-sOutputFile=-", "-"]                      else                          command = ["pdftk", "-", "output", "-", "compress"] @@ -258,11 +270,21 @@ class IncomingMessage < ActiveRecord::Base          # equivalents to the UCS-2          ascii_chars = text.gsub(/\0/, "")          emails = ascii_chars.scan(MySociety::Validate.email_find_regexp) +          # Convert back to UCS-2, making a mask at the same time -        emails.map! {|email| [ -                Iconv.conv('ucs-2le', 'ascii', email[0]), -                Iconv.conv('ucs-2le', 'ascii', email[0].gsub(/[^@.]/, 'x')) -        ] } +        if RUBY_VERSION >= '1.9' +            emails.map! do |email| +                # We want the ASCII representation of UCS-2 +                [email[0].encode('UTF-16LE').force_encoding('US-ASCII'), +                 email[0].gsub(/[^@.]/, 'x').encode('UTF-16LE').force_encoding('US-ASCII')] +            end +        else +            emails.map! {|email| [ +                    Iconv.conv('ucs-2le', 'ascii', email[0]), +                    Iconv.conv('ucs-2le', 'ascii', email[0].gsub(/[^@.]/, 'x')) +            ] } +        end +          # Now search and replace the UCS-2 email with the UCS-2 mask          for email, mask in emails              text.gsub!(email, mask) @@ -316,7 +338,7 @@ class IncomingMessage < ActiveRecord::Base          text.gsub!(/(Mobile|Mob)([\s\/]*(Fax|Tel))*\s*:?[\s\d]*\d/, "[mobile number]")          # Remove WhatDoTheyKnow signup links -        text.gsub!(/http:\/\/#{Configuration::domain}\/c\/[^\s]+/, "[WDTK login link]") +        text.gsub!(/http:\/\/#{AlaveteliConfiguration::domain}\/c\/[^\s]+/, "[WDTK login link]")          # Remove things from censor rules          self.info_request.apply_censor_rules_to_text!(text) @@ -534,7 +556,7 @@ class IncomingMessage < ActiveRecord::Base                      source_charset = 'utf-8' if source_charset.nil?                      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 => Configuration::site_name) +                          :site_name => AlaveteliConfiguration::site_name)                  rescue Iconv::InvalidEncoding, Iconv::IllegalSequence, Iconv::InvalidCharacter                      if source_charset != "utf-8"                          source_charset = "utf-8" @@ -546,9 +568,11 @@ class IncomingMessage < ActiveRecord::Base          text      end -    # Returns part which contains main body text, or nil if there isn't one -    def get_main_body_text_part -        leaves = self.foi_attachments +    # Returns part which contains main body text, or nil if there isn't one, +    # from a set of foi_attachments. If the leaves parameter is empty or not +    # supplied, uses its own foi_attachments. +    def get_main_body_text_part(leaves=[]) +        leaves = self.foi_attachments if leaves.empty?          # Find first part which is text/plain or text/html          # (We have to include HTML, as increasingly there are mail clients that @@ -582,6 +606,7 @@ class IncomingMessage < ActiveRecord::Base          # nil in this case)          return p      end +      # Returns attachments that are uuencoded in main body part      def _uudecode_and_save_attachments(text)          # Find any uudecoded things buried in it, yeuchly @@ -605,7 +630,7 @@ class IncomingMessage < ActiveRecord::Base                  content_type = 'application/octet-stream'              end              hexdigest = Digest::MD5.hexdigest(content) -            attachment = self.foi_attachments.find_or_create_by_hexdigest(:hexdigest => hexdigest) +            attachment = self.foi_attachments.find_or_create_by_hexdigest(hexdigest)              attachment.update_attributes(:filename => filename,                                           :content_type => content_type,                                           :body => content, @@ -632,15 +657,19 @@ class IncomingMessage < ActiveRecord::Base          attachment_attributes = MailHandler.get_attachment_attributes(self.mail(force))          attachments = []          attachment_attributes.each do |attrs| -            attachment = self.foi_attachments.find_or_create_by_hexdigest(:hexdigest => attrs[:hexdigest]) -            body = attrs.delete(:body) +            attachment = self.foi_attachments.find_or_create_by_hexdigest(attrs[:hexdigest])              attachment.update_attributes(attrs) -            # Set the body separately as its handling can depend on the value of charset -            attachment.body = body              attachment.save! -            attachments << attachment.id +            attachments << attachment          end -        main_part = get_main_body_text_part + +        # Reload to refresh newly created foi_attachments +        self.reload + +        # get the main body part from the set of attachments we just created, +        # not from the self.foi_attachments association - some of the total set of +        # self.foi_attachments may now be obsolete +        main_part = get_main_body_text_part(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 @@ -651,12 +680,14 @@ class IncomingMessage < ActiveRecord::Base                  c += 1                  uudecode_attachment.url_part_number = c                  uudecode_attachment.save! -                attachments << uudecode_attachment.id +                attachments << uudecode_attachment              end          end +        attachment_ids = attachments.map{ |attachment| attachment.id }          # now get rid of any attachments we no longer have -        FoiAttachment.destroy_all("id NOT IN (#{attachments.join(',')}) AND incoming_message_id = #{self.id}") +        FoiAttachment.destroy_all(["id NOT IN (?) AND incoming_message_id = ?", +                                    attachment_ids, self.id])     end      # Returns body text as HTML with quotes flattened, and emails removed. @@ -748,9 +779,15 @@ class IncomingMessage < ActiveRecord::Base                                                               attachment.body,                                                               attachment.charset)          end +          # Remove any bad characters -        text = Iconv.conv('utf-8//IGNORE', 'utf-8', text) -        return text +        if RUBY_VERSION >= '1.9' +            text.encode("utf-8", :invalid => :replace, +                                 :undef => :replace, +                                 :replace => "") +        else +            Iconv.conv('utf-8//IGNORE', 'utf-8', text) +        end      end diff --git a/app/models/info_request.rb b/app/models/info_request.rb index eaed25a61..cf1af0e87 100644 --- a/app/models/info_request.rb +++ b/app/models/info_request.rb @@ -26,8 +26,7 @@  require 'digest/sha1'  class InfoRequest < ActiveRecord::Base -    include ActionView::Helpers::UrlHelper -    include ActionController::UrlWriter +    include Rails.application.routes.url_helpers      strip_attributes! @@ -51,7 +50,7 @@ class InfoRequest < ActiveRecord::Base      has_tag_string -    named_scope :visible, :conditions => {:prominence => "normal"} +    scope :visible, :conditions => {:prominence => "normal"}      # user described state (also update in info_request_event, admin_request/edit.rhtml)      validate :must_be_valid_state @@ -81,6 +80,11 @@ class InfoRequest < ActiveRecord::Base          'blackhole' # just dump them      ] +    # only check on create, so existing models with mixed case are allowed +    validate :title_formatting, :on => :create + +    after_initialize :set_defaults +      def self.enumerate_states          states = [          'waiting_response', @@ -148,7 +152,7 @@ class InfoRequest < ActiveRecord::Base      @@custom_states_loaded = false      begin -        if ENV["RAILS_ENV"] != "test" +        if !Rails.env.test?              require 'customstates'              include InfoRequestCustomStates              @@custom_states_loaded = true @@ -456,7 +460,7 @@ public              if !allow                  if self.handle_rejected_responses == 'bounce' -                    RequestMailer.deliver_stopped_responses(self, email, raw_email_data) if !is_external? +                    RequestMailer.stopped_responses(self, email, raw_email_data).deliver if !is_external?                  elsif self.handle_rejected_responses == 'holding_pen'                      InfoRequest.holding_pen_request.receive(email, raw_email_data, false, reason)                  elsif self.handle_rejected_responses == 'blackhole' @@ -495,13 +499,13 @@ public              self.awaiting_description = true              params = { :incoming_message_id => incoming_message.id }              if !rejected_reason.empty? -                params[:rejected_reason] = rejected_reason +                params[:rejected_reason] = rejected_reason.to_str              end              self.log_event("response", params)              self.save!          end          self.info_request_events.each { |event| event.xapian_mark_needs_index } # for the "waiting_classification" index -        RequestMailer.deliver_new_response(self, incoming_message) if !is_external? +        RequestMailer.new_response(self, incoming_message).deliver if !is_external?      end @@ -554,6 +558,10 @@ public      # states which require administrator action (hence email administrators      # when they are entered, and offer state change dialog to them) +    def InfoRequest.requires_admin_states +        return ['requires_admin', 'error_message', 'attention_requested'] +    end +      def requires_admin?          ['requires_admin', 'error_message', 'attention_requested'].include?(described_state)      end @@ -575,7 +583,7 @@ public          if self.requires_admin?              # Check there is someone to send the message "from"              if !set_by.nil? || !self.user.nil? -                RequestMailer.deliver_requires_admin(self, set_by, message) +                RequestMailer.requires_admin(self, set_by, message).deliver              end          end @@ -590,7 +598,7 @@ public              RequestClassification.create!(:user_id => set_by.id,                                            :info_request_event_id => event.id) -            RequestMailer.deliver_old_unclassified_updated(self) if !is_external? +            RequestMailer.old_unclassified_updated(self).deliver if !is_external?          end      end @@ -705,7 +713,7 @@ public      # last_event_forming_initial_request. There may be more obscure      # things, e.g. fees, not properly covered.      def date_response_required_by -        Holiday.due_date_from(self.date_initial_request_last_sent_at, Configuration::reply_late_after_days, Configuration::working_or_calendar_days) +        Holiday.due_date_from(self.date_initial_request_last_sent_at, AlaveteliConfiguration::reply_late_after_days, AlaveteliConfiguration::working_or_calendar_days)      end      # This is a long stop - even with UK public interest test extensions, 40      # days is a very long time. @@ -713,10 +721,10 @@ public          last_sent = last_event_forming_initial_request          if self.public_body.is_school?              # schools have 60 working days maximum (even over a long holiday) -            Holiday.due_date_from(self.date_initial_request_last_sent_at, Configuration::special_reply_very_late_after_days, Configuration::working_or_calendar_days) +            Holiday.due_date_from(self.date_initial_request_last_sent_at, AlaveteliConfiguration::special_reply_very_late_after_days, AlaveteliConfiguration::working_or_calendar_days)          else              # public interest test ICO guidance gives 40 working maximum -            Holiday.due_date_from(self.date_initial_request_last_sent_at, Configuration::reply_very_late_after_days, Configuration::working_or_calendar_days) +            Holiday.due_date_from(self.date_initial_request_last_sent_at, AlaveteliConfiguration::reply_very_late_after_days, AlaveteliConfiguration::working_or_calendar_days)          end      end @@ -802,6 +810,16 @@ public          end      end +    # Returns last event +    def get_last_event +        events = self.info_request_events +        if events.size == 0 +            return nil +        else +            return events[-1] +        end +    end +      # Get previous email sent to      def get_previous_email_sent_to(info_request_event)          last_email = nil @@ -878,10 +896,10 @@ public      end      def InfoRequest.magic_email_for_id(prefix_part, id) -        magic_email = Configuration::incoming_email_prefix +        magic_email = AlaveteliConfiguration::incoming_email_prefix          magic_email += prefix_part + id.to_s          magic_email += "-" + InfoRequest.hash_from_id(id) -        magic_email += "@" + Configuration::incoming_email_domain +        magic_email += "@" + AlaveteliConfiguration::incoming_email_domain          return magic_email      end @@ -892,7 +910,7 @@ public      end      def InfoRequest.hash_from_id(id) -        return Digest::SHA1.hexdigest(id.to_s + Configuration::incoming_email_secret)[0,8] +        return Digest::SHA1.hexdigest(id.to_s + AlaveteliConfiguration::incoming_email_secret)[0,8]      end      # Called by find_by_incoming_email - and used to be called by separate @@ -1115,7 +1133,7 @@ public      before_save :purge_in_cache      def purge_in_cache -        if !Configuration::varnish_host.blank? && !self.id.nil? +        if !AlaveteliConfiguration::varnish_host.blank? && !self.id.nil?              # we only do this for existing info_requests (new ones have a nil id)              path = url_for(:controller => 'request', :action => 'show', :url_title => self.url_title, :only_path => true, :locale => :none)              req = PurgeRequest.find_by_url(path) @@ -1133,5 +1151,34 @@ public          yield(column.human_name, self.send(column.name), column.type.to_s, column.name)        end      end + +    private + +    def set_defaults +        begin +            if self.described_state.nil? +                self.described_state = 'waiting_response' +            end             +        rescue ActiveModel::MissingAttributeError +            # this should only happen on Model.exists?() call. It can be safely ignored. +            # See http://www.tatvartha.com/2011/03/activerecordmissingattributeerror-missing-attribute-a-bug-or-a-features/        +        end +        # FOI or EIR? +        if !self.public_body.nil? && self.public_body.eir_only? +            self.law_used = 'eir' +        end +    end + +    def title_formatting +        if !self.title.nil? && !MySociety::Validate.uses_mixed_capitals(self.title, 10) +            errors.add(:title, _('Please write the summary using a mixture of capital and lower case letters. This makes it easier for others to read.')) +        end +        if !self.title.nil? && title.size > 200 +            errors.add(:title, _('Please keep the summary short, like in the subject of an email. You can use a phrase, rather than a full sentence.')) +        end +        if !self.title.nil? && self.title =~ /^(FOI|Freedom of Information)\s*requests?$/i +            errors.add(:title, _('Please describe more what the request is about in the subject. There is no need to say it is an FOI request, we add that on anyway.')) +        end +    end  end diff --git a/app/models/info_request_event.rb b/app/models/info_request_event.rb index 871b81b1f..469aabc4a 100644 --- a/app/models/info_request_event.rb +++ b/app/models/info_request_event.rb @@ -20,7 +20,7 @@  # models/info_request_event.rb:  #  # Copyright (c) 2007 UK Citizens Online Democracy. All rights reserved. -# Email: francis@mysociety.org; WWW: http://www.mysociety.org/ +# Email: hello@mysociety.org; WWW: http://www.mysociety.org/  class InfoRequestEvent < ActiveRecord::Base      belongs_to :info_request @@ -405,7 +405,7 @@ class InfoRequestEvent < ActiveRecord::Base              :comment_id => self.comment_id,              # XXX would be nice to add links here, but alas the -            # code to make them is in views only. See views/request/details.rhtml +            # code to make them is in views only. See views/request/details.html.erb              # perhaps can call with @template somehow          } diff --git a/app/models/mail_server_log.rb b/app/models/mail_server_log.rb index 755584b90..7f61377ce 100644 --- a/app/models/mail_server_log.rb +++ b/app/models/mail_server_log.rb @@ -15,9 +15,7 @@  # We load log file lines for requests in here, for display in the admin interface.  #  # Copyright (c) 2009 UK Citizens Online Democracy. All rights reserved. -# Email: francis@mysociety.org; WWW: http://www.mysociety.org/ -# -# $Id: exim_log.rb,v 1.14 2009-09-17 21:10:05 francis Exp $ +# Email: hello@mysociety.org; WWW: http://www.mysociety.org/  class MailServerLog < ActiveRecord::Base      belongs_to :info_request @@ -53,7 +51,7 @@ class MailServerLog < ActiveRecord::Base              done.save!              f = is_gz ? Zlib::GzipReader.open(file_name) : File.open(file_name, 'r') -            case(Configuration::mta_log_type.to_sym) +            case(AlaveteliConfiguration::mta_log_type.to_sym)              when :exim                  load_exim_log_data(f, done)              when :postfix @@ -123,13 +121,13 @@ class MailServerLog < ActiveRecord::Base      # We also check the email prefix so that we could, for instance, separately handle a staging and production      # instance running on the same server with different email prefixes.      def MailServerLog.email_addresses_on_line(line) -        prefix = Regexp::quote(Configuration::incoming_email_prefix) -        domain = Regexp::quote(Configuration::incoming_email_domain) +        prefix = Regexp::quote(AlaveteliConfiguration::incoming_email_prefix) +        domain = Regexp::quote(AlaveteliConfiguration::incoming_email_domain)          line.scan(/#{prefix}request-[^\s]+@#{domain}/).sort.uniq      end      def MailServerLog.request_sent?(ir) -        case(Configuration::mta_log_type.to_sym) +        case(AlaveteliConfiguration::mta_log_type.to_sym)          when :exim              request_exim_sent?(ir)          when :postfix diff --git a/app/models/mail_server_log_done.rb b/app/models/mail_server_log_done.rb index 3fb20f0b3..0e7e9eec3 100644 --- a/app/models/mail_server_log_done.rb +++ b/app/models/mail_server_log_done.rb @@ -13,7 +13,7 @@  # Stores that a particular mail server log file has been loaded in, see mail_server_log.rb  #  # Copyright (c) 2009 UK Citizens Online Democracy. All rights reserved. -# Email: francis@mysociety.org; WWW: http://www.mysociety.org/ +# Email: hello@mysociety.org; WWW: http://www.mysociety.org/  class MailServerLogDone < ActiveRecord::Base      has_many :mail_server_logs diff --git a/app/models/outgoing_mailer.rb b/app/models/outgoing_mailer.rb deleted file mode 100644 index 503166b8a..000000000 --- a/app/models/outgoing_mailer.rb +++ /dev/null @@ -1,98 +0,0 @@ -# models/outgoing_mailer.rb: -# Emails which go to public bodies on behalf of users. -# -# Copyright (c) 2009 UK Citizens Online Democracy. All rights reserved. -# Email: francis@mysociety.org; WWW: http://www.mysociety.org/ - -# Note: The layout for this wraps messages by lines rather than (blank line -# separated) paragraphs, as is the convention for all the other mailers. This -# turned out to fit better with user exepectations when formatting messages. -# -# XXX The other mail templates are written to use blank line separated -# paragraphs. They could be rewritten, and the wrapping method made uniform -# throughout the application. - -class OutgoingMailer < ApplicationMailer - -    # Email to public body requesting info -    def initial_request(info_request, outgoing_message) -        @wrap_lines_as_paragraphs = true -        @from = info_request.incoming_name_and_email -        @recipients = info_request.recipient_name_and_email -        @subject    = info_request.email_subject_request -        @headers["message-id"] = OutgoingMailer.id_for_message(outgoing_message) -        @body       = {:info_request => info_request, :outgoing_message => outgoing_message, -            :contact_email => Configuration::contact_email } -    end - -    # Later message to public body regarding existing request -    def followup(info_request, outgoing_message, incoming_message_followup) -        @wrap_lines_as_paragraphs = true -        @from = info_request.incoming_name_and_email -        @recipients = OutgoingMailer.name_and_email_for_followup(info_request, incoming_message_followup) -        @subject    = OutgoingMailer.subject_for_followup(info_request, outgoing_message) -        @headers["message-id"] = OutgoingMailer.id_for_message(outgoing_message) -        @body       = {:info_request => info_request, :outgoing_message => outgoing_message, -            :incoming_message_followup => incoming_message_followup, -            :contact_email => Configuration::contact_email } -    end - -    # XXX the condition checking valid_to_reply_to? also appears in views/request/_followup.rhtml, -    # it shouldn't really, should call something here. -    # XXX also OutgoingMessage.get_salutation -    # XXX these look like they should be members of IncomingMessage, but logically they -    # need to work even when IncomingMessage is nil -    def OutgoingMailer.name_and_email_for_followup(info_request, incoming_message_followup) -        if incoming_message_followup.nil? || !incoming_message_followup.valid_to_reply_to? -            return info_request.recipient_name_and_email -        else -            # calling safe_mail_from from so censor rules are run -            return MailHandler.address_from_name_and_email(incoming_message_followup.safe_mail_from, -                                                           incoming_message_followup.from_email) -        end -    end -    # Used in the preview of followup -    def OutgoingMailer.name_for_followup(info_request, incoming_message_followup) -        if incoming_message_followup.nil? || !incoming_message_followup.valid_to_reply_to? -            return info_request.public_body.name -        else -            # calling safe_mail_from from so censor rules are run -            return incoming_message_followup.safe_mail_from || info_request.public_body.name -        end -    end -    # Used when making list of followup places to remove duplicates -    def OutgoingMailer.email_for_followup(info_request, incoming_message_followup) -        if incoming_message_followup.nil? || !incoming_message_followup.valid_to_reply_to? -            return info_request.recipient_email -        else -            return incoming_message_followup.from_email -        end -    end -    # Subject to use for followup -    def OutgoingMailer.subject_for_followup(info_request, outgoing_message) -        if outgoing_message.what_doing == 'internal_review' -            return "Internal review of " + info_request.email_subject_request -        else -            return info_request.email_subject_followup(outgoing_message.incoming_message_followup) -        end -    end -    # Whether we have a valid email address for a followup -    def OutgoingMailer.is_followupable?(info_request, incoming_message_followup) -        if incoming_message_followup.nil? || !incoming_message_followup.valid_to_reply_to? -            return info_request.recipient_email_valid_for_followup? -        else -            # email has been checked in incoming_message_followup.valid_to_reply_to? above -            return true -        end -    end -    # Message-ID to use -    def OutgoingMailer.id_for_message(outgoing_message) -        message_id = "ogm-" + outgoing_message.id.to_s -        t = Time.now -        message_id += "+" + '%08x%05x-%04x' % [t.to_i, t.tv_usec, rand(0xffff)] -        message_id += "@" + Configuration::incoming_email_domain -        return "<" + message_id + ">" -    end - -end - diff --git a/app/models/outgoing_message.rb b/app/models/outgoing_message.rb index 248125808..dbe2cf1ca 100644 --- a/app/models/outgoing_message.rb +++ b/app/models/outgoing_message.rb @@ -20,7 +20,7 @@  # else. e.g. An initial request for information, or a complaint.  #  # Copyright (c) 2007 UK Citizens Online Democracy. All rights reserved. -# Email: francis@mysociety.org; WWW: http://www.mysociety.org/ +# Email: hello@mysociety.org; WWW: http://www.mysociety.org/  class OutgoingMessage < ActiveRecord::Base      strip_attributes! @@ -51,6 +51,8 @@ class OutgoingMessage < ActiveRecord::Base          end      end +    after_initialize :set_default_letter +      # How the default letter starts and ends      def get_salutation          ret = "" @@ -86,7 +88,7 @@ class OutgoingMessage < ActiveRecord::Base              "'" + self.info_request.title + "'." +              "\n\n\n\n [ " + self.get_internal_review_insert_here_note + " ] \n\n\n\n" +              "A full history of my FOI request and all correspondence is available on the Internet at this address:\n" + -            "http://" + Configuration::domain + "/request/" + self.info_request.url_title +            "http://" + AlaveteliConfiguration::domain + "/request/" + self.info_request.url_title          else              ""          end @@ -130,13 +132,6 @@ class OutgoingMessage < ActiveRecord::Base          MySociety::Validate.contains_postcode?(self.body)      end -    # Set default letter -    def after_initialize -        if self.body.nil? -            self.body = get_default_message -        end -    end -      # Deliver outgoing message      # Note: You can test this from script/console with, say:      # InfoRequest.find(1).outgoing_messages[0].send_message @@ -147,7 +142,7 @@ class OutgoingMessage < ActiveRecord::Base                  self.status = 'sent'                  self.save! -                mail_message = OutgoingMailer.deliver_initial_request(self.info_request, self) +                mail_message = OutgoingMailer.initial_request(self.info_request, self).deliver                  self.info_request.log_event(log_event_type, {                      :email => mail_message.to_addrs.join(", "),                      :outgoing_message_id => self.id, @@ -159,7 +154,7 @@ class OutgoingMessage < ActiveRecord::Base                  self.status = 'sent'                  self.save! -                mail_message = OutgoingMailer.deliver_followup(self.info_request, self, self.incoming_message_followup) +                mail_message = OutgoingMailer.followup(self.info_request, self, self.incoming_message_followup).deliver                  self.info_request.log_event('followup_' + log_event_type, {                      :email => mail_message.to_addrs.join(", "),                      :outgoing_message_id => self.id, @@ -253,6 +248,12 @@ class OutgoingMessage < ActiveRecord::Base      private +    def set_default_letter +        if self.body.nil? +            self.body = get_default_message +        end +    end +      def format_of_body          if self.body.empty? || self.body =~ /\A#{get_salutation}\s+#{get_signoff}/ || self.body =~ /#{get_internal_review_insert_here_note}/              if self.message_type == 'followup' diff --git a/app/models/post_redirect.rb b/app/models/post_redirect.rb index 31f08c21a..409069cb6 100644 --- a/app/models/post_redirect.rb +++ b/app/models/post_redirect.rb @@ -24,7 +24,7 @@  # fakes the redirect to include POST parameters in request later.  #  # Copyright (c) 2007 UK Citizens Online Democracy. All rights reserved. -# Email: francis@mysociety.org; WWW: http://www.mysociety.org/ +# Email: hello@mysociety.org; WWW: http://www.mysociety.org/  require 'openssl' # for random bytes function @@ -32,6 +32,8 @@ class PostRedirect < ActiveRecord::Base      # Optional, does a login confirm before redirect for use in email links.      belongs_to :user +    after_initialize :generate_token +      # We store YAML version of POST parameters in the database      def post_params=(params)          self.post_params_yaml = params.to_yaml @@ -62,18 +64,6 @@ class PostRedirect < ActiveRecord::Base          MySociety::Util.generate_token      end -    # Make the token -    def after_initialize -        # The token is used to return you to what you are doing after the login form. -        if not self.token -            self.token = PostRedirect.generate_random_token -        end -        # There is a separate token to use in the URL if we send a confirmation email. -        if not self.email_token -            self.email_token = PostRedirect.generate_random_token -        end -    end -      # Used by (rspec) test code only      def self.get_last_post_redirect          # XXX yeuch - no other easy way of getting the token so we can check @@ -89,6 +79,18 @@ class PostRedirect < ActiveRecord::Base          PostRedirect.delete_all "updated_at < (now() - interval '2 months')"      end +    private + +    def generate_token +        # The token is used to return you to what you are doing after the login form. +        if not self.token +            self.token = PostRedirect.generate_random_token +        end +        # There is a separate token to use in the URL if we send a confirmation email. +        if not self.email_token +            self.email_token = PostRedirect.generate_random_token +        end +    end  end diff --git a/app/models/profile_photo.rb b/app/models/profile_photo.rb index f6aec6338..5d542daf1 100644 --- a/app/models/profile_photo.rb +++ b/app/models/profile_photo.rb @@ -13,7 +13,7 @@  # Image of user that goes on their profile.  #  # Copyright (c) 2009 UK Citizens Online Democracy. All rights reserved. -# Email: francis@mysociety.org; WWW: http://www.mysociety.org/ +# Email: hello@mysociety.org; WWW: http://www.mysociety.org/  class ProfilePhoto < ActiveRecord::Base      WIDTH = 96 @@ -29,25 +29,9 @@ class ProfilePhoto < ActiveRecord::Base      attr_accessor :x, :y, :w, :h -    # convert binary data blob into ImageMagick image when assigned      attr_accessor :image -    def after_initialize -        if data.nil? -            self.image = nil -            return -        end - -        image_list = Magick::ImageList.new -        begin -            image_list.from_blob(data) -        rescue Magick::ImageMagickError -            self.image = nil -            return -        end -        self.image = image_list[0] # XXX perhaps take largest image or somesuch if there were multiple in the file? -        self.convert_image -    end +    after_initialize :convert_data_to_image      # make image valid format and size      def convert_image @@ -112,6 +96,25 @@ class ProfilePhoto < ActiveRecord::Base              raise "Internal error, real pictures must have a user"          end      end + +    # Convert binary data blob into ImageMagick image when assigned +    def convert_data_to_image +        if data.nil? +            self.image = nil +            return +        end + +        image_list = Magick::ImageList.new +        begin +            image_list.from_blob(data) +        rescue Magick::ImageMagickError +            self.image = nil +            return +        end + +        self.image = image_list[0] # XXX perhaps take largest image or somesuch if there were multiple in the file? +        self.convert_image +    end  end diff --git a/app/models/public_body.rb b/app/models/public_body.rb index b4d36fe91..a76aeb189 100644 --- a/app/models/public_body.rb +++ b/app/models/public_body.rb @@ -45,7 +45,7 @@ class PublicBody < ActiveRecord::Base      before_save :set_api_key, :set_default_publication_scheme      # Every public body except for the internal admin one is visible -    named_scope :visible, lambda { +    scope :visible, lambda {          {              :conditions => "public_bodies.id <> #{PublicBody.internal_admin_body.id}"          } @@ -54,7 +54,7 @@ class PublicBody < ActiveRecord::Base      translates :name, :short_name, :request_email, :url_name, :notes, :first_letter, :publication_scheme      # Convenience methods for creating/editing translations via forms -    def translation(locale) +    def find_translation_by_locale(locale)          self.translations.find_by_locale(locale)      end @@ -79,7 +79,7 @@ class PublicBody < ActiveRecord::Base          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 = translation_for(attrs[:locale]) || PublicBody::Translation.new                  t.attributes = attrs                  calculate_cached_fields(t)                  t.save! @@ -332,7 +332,7 @@ class PublicBody < ActiveRecord::Base                  pb = PublicBody.new(                   :name => 'Internal admin authority',                   :short_name => "", -                 :request_email => Configuration::contact_email, +                 :request_email => AlaveteliConfiguration::contact_email,                   :home_page => "",                   :notes => "",                   :publication_scheme => "", @@ -548,7 +548,7 @@ class PublicBody < ActiveRecord::Base      # Returns nil if configuration variable not set      def override_request_email -        e = Configuration::override_all_public_body_request_emails +        e = AlaveteliConfiguration::override_all_public_body_request_emails          e if e != ""      end @@ -632,6 +632,8 @@ class PublicBody < ActiveRecord::Base          end      end +    private +      def request_email_if_requestable          # Request_email can be blank, meaning we don't have details          if self.is_requestable? diff --git a/app/models/purge_request.rb b/app/models/purge_request.rb index 48a16f9e6..e48f3cc6f 100644 --- a/app/models/purge_request.rb +++ b/app/models/purge_request.rb @@ -14,7 +14,7 @@  # A queue of URLs to purge  #  # Copyright (c) 2008 UK Citizens Online Democracy. All rights reserved. -# Email: francis@mysociety.org; WWW: http://www.mysociety.org/ +# Email: hello@mysociety.org; WWW: http://www.mysociety.org/  #  class PurgeRequest < ActiveRecord::Base diff --git a/app/models/raw_email.rb b/app/models/raw_email.rb index de7978b82..6bf01bc74 100644 --- a/app/models/raw_email.rb +++ b/app/models/raw_email.rb @@ -10,7 +10,7 @@  # The fat part of models/incoming_message.rb  #  # Copyright (c) 2008 UK Citizens Online Democracy. All rights reserved. -# Email: francis@mysociety.org; WWW: http://www.mysociety.org/ +# Email: hello@mysociety.org; WWW: http://www.mysociety.org/  class RawEmail < ActiveRecord::Base      # deliberately don't strip_attributes, so keeps raw email properly @@ -23,10 +23,10 @@ class RawEmail < ActiveRecord::Base              raise "Failed to find the id number of the associated request: has it been saved?"          end -        if ENV["RAILS_ENV"] == "test" +        if Rails.env.test?              return File.join(Rails.root, 'files/raw_email_test')          else -            return File.join(Configuration::raw_emails_location, +            return File.join(AlaveteliConfiguration::raw_emails_location,                               request_id[0..2], request_id)          end      end diff --git a/app/models/request_mailer.rb b/app/models/request_mailer.rb deleted file mode 100644 index 955f73ef4..000000000 --- a/app/models/request_mailer.rb +++ /dev/null @@ -1,457 +0,0 @@ -# models/request_mailer.rb: -# Alerts relating to requests. -# -# Copyright (c) 2007 UK Citizens Online Democracy. All rights reserved. -# Email: francis@mysociety.org; WWW: http://www.mysociety.org/ - -require 'alaveteli_file_types' -if Rails.env == 'test' && RUBY_VERSION.to_f >= 1.9 -    # Avoid spec/script/mailin_spec.rb running script/runner as a test suite -    # http://stackoverflow.com/questions/1899009/why-are-tests-running-in-production-mode-and-causing-my-script-runners-to-fail -    Test::Unit.run = true -end -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. -    def fake_response(info_request, from_user, body, attachment_name, attachment_content) -        @from = from_user.name_and_email -        @recipients = info_request.incoming_name_and_email -        @body = { -            :body => body -        } -        if !attachment_name.nil? && !attachment_content.nil? -            content_type = AlaveteliFileTypes.filename_to_mimetype(attachment_name) || 'application/octet-stream' - -            attachment :content_type => content_type, -                :body => attachment_content, -                :filename => attachment_name -        end -    end - -    # Used when a response is uploaded using the API -    def external_response(info_request, body, sent_at, attachments) -        @from = blackhole_email -        @recipients = info_request.incoming_name_and_email -        @body = { :body => body } - -        # ActionMailer only works properly when the time is in the local timezone: -        # see https://rails.lighthouseapp.com/projects/8994/tickets/3113-actionmailer-only-works-correctly-with-sent_on-times-that-are-in-the-local-time-zone -        @sent_on = sent_at.dup.localtime - -        attachments.each do |attachment_hash| -            attachment attachment_hash -        end -    end - -    # Incoming message arrived for a request, but new responses have been stopped. -    def stopped_responses(info_request, email, raw_email_data) -        @from = contact_from_name_and_email -        headers 'Return-Path' => blackhole_email, 'Reply-To' => @from, # we don't care about bounces, likely from spammers -                'Auto-Submitted' => 'auto-replied' # http://tools.ietf.org/html/rfc3834 -        @recipients = email.from_addrs[0].to_s -        @subject = _("Your response to an FOI request was not delivered") -        attachment :content_type => 'message/rfc822', :body => raw_email_data, -            :filename => "original.eml", :transfer_encoding => '7bit', :content_disposition => 'inline' -        @body = { -            :info_request => info_request, -            :contact_email => Configuration::contact_email -        } -    end - -    # An FOI response is outside the scope of the system, and needs admin attention -    def requires_admin(info_request, set_by = nil, message = "") -        user = set_by || info_request.user -        @from = user.name_and_email -        @recipients = contact_from_name_and_email -        @subject = _("FOI response requires admin ({{reason}}) - {{title}}", :reason => info_request.described_state, :title => info_request.title) -        url = request_url(info_request) -        admin_url = admin_request_show_url(info_request) -        @body = {:reported_by => user, :message => message, :info_request => info_request, :url => url, :admin_url => admin_url } -    end - -    # Tell the requester that a new response has arrived -    def new_response(info_request, incoming_message) -        # Don't use login link here, just send actual URL. This is -        # because people tend to forward these emails amongst themselves. -        url = incoming_message_url(incoming_message) - -        @from = contact_from_name_and_email -        headers 'Return-Path' => blackhole_email, 'Reply-To' => @from, # not much we can do if the user's email is broken -                'Auto-Submitted' => 'auto-generated', # http://tools.ietf.org/html/rfc3834 -                'X-Auto-Response-Suppress' => 'OOF' -        @recipients = info_request.user.name_and_email -        @subject = (_("New response to your FOI request - ") + info_request.title).html_safe -        @body = { :incoming_message => incoming_message, :info_request => info_request, :url => url } -    end - -    # Tell the requester that the public body is late in replying -    def overdue_alert(info_request, user) -        respond_url = respond_to_last_url(info_request) + "#followup" - -        post_redirect = PostRedirect.new( -            :uri => respond_url, -            :user_id => user.id) -        post_redirect.save! -        url = confirm_url(:email_token => post_redirect.email_token) - -        @from = contact_from_name_and_email -        headers 'Return-Path' => blackhole_email, 'Reply-To' => @from, # not much we can do if the user's email is broken -                'Auto-Submitted' => 'auto-generated', # http://tools.ietf.org/html/rfc3834 -                'X-Auto-Response-Suppress' => 'OOF' -        @recipients = user.name_and_email -        @subject = (_("Delayed response to your FOI request - ") + info_request.title).html_safe -        @body = { :info_request => info_request, :url => url } -    end - -    # Tell the requester that the public body is very late in replying -    def very_overdue_alert(info_request, user) -        respond_url = respond_to_last_url(info_request) + "#followup" - -        post_redirect = PostRedirect.new( -            :uri => respond_url, -            :user_id => user.id) -        post_redirect.save! -        url = confirm_url(:email_token => post_redirect.email_token) - -        @from = contact_from_name_and_email -        headers 'Return-Path' => blackhole_email, 'Reply-To' => @from, # not much we can do if the user's email is broken -                'Auto-Submitted' => 'auto-generated', # http://tools.ietf.org/html/rfc3834 -                'X-Auto-Response-Suppress' => 'OOF' -        @recipients = user.name_and_email -        @subject = (_("You're long overdue a response to your FOI request - ") + info_request.title).html_safe -        @body = { :info_request => info_request, :url => url } -    end - -    # Tell the requester that they need to say if the new response -    # contains info or not -    def new_response_reminder_alert(info_request, incoming_message) -        # Make a link going to the form to describe state, and which logs the -        # user in. -        post_redirect = PostRedirect.new( -            :uri => request_url(info_request) + "#describe_state_form_1", -            :user_id => info_request.user.id) -        post_redirect.save! -        url = confirm_url(:email_token => post_redirect.email_token) - -        @from = contact_from_name_and_email -        headers 'Return-Path' => blackhole_email, 'Reply-To' => @from, # not much we can do if the user's email is broken -                'Auto-Submitted' => 'auto-generated', # http://tools.ietf.org/html/rfc3834 -                'X-Auto-Response-Suppress' => 'OOF' -        @recipients = info_request.user.name_and_email -        @subject = _("Was the response you got to your FOI request any good?") -        @body = { :incoming_message => incoming_message, :info_request => info_request, :url => url } -    end - -    # Tell the requester that someone updated their old unclassified request -    def old_unclassified_updated(info_request) -        @from = contact_from_name_and_email -        headers 'Return-Path' => blackhole_email, 'Reply-To' => @from, # not much we can do if the user's email is broken -                'Auto-Submitted' => 'auto-generated', # http://tools.ietf.org/html/rfc3834 -                'X-Auto-Response-Suppress' => 'OOF' -        @recipients = info_request.user.name_and_email -        @subject = _("Someone has updated the status of your request") -        url = request_url(info_request) -        @body = {:info_request => info_request, :url => url} -    end - -    # Tell the requester that they need to clarify their request -    def not_clarified_alert(info_request, incoming_message) -        respond_url = show_response_url(:id => info_request.id, :incoming_message_id => incoming_message.id) -        respond_url = respond_url + "#followup" - -        post_redirect = PostRedirect.new( -            :uri => respond_url, -            :user_id => info_request.user.id) -        post_redirect.save! -        url = confirm_url(:email_token => post_redirect.email_token) - -        @from = contact_from_name_and_email -        headers 'Return-Path' => blackhole_email, 'Reply-To' => @from, # not much we can do if the user's email is broken -                'Auto-Submitted' => 'auto-generated', # http://tools.ietf.org/html/rfc3834 -                'X-Auto-Response-Suppress' => 'OOF' -        @recipients = info_request.user.name_and_email -        @subject = (_("Clarify your FOI request - ") + info_request.title).html_safe -        @body = { :incoming_message => incoming_message, :info_request => info_request, :url => url } -    end - -    # Tell requester that somebody add an annotation to their request -    def comment_on_alert(info_request, comment) -        @from = contact_from_name_and_email -        headers 'Return-Path' => blackhole_email, 'Reply-To' => @from, # not much we can do if the user's email is broken -                'Auto-Submitted' => 'auto-generated', # http://tools.ietf.org/html/rfc3834 -                'X-Auto-Response-Suppress' => 'OOF' -        @recipients = info_request.user.name_and_email -        @subject = (_("Somebody added a note to your FOI request - ") + info_request.title).html_safe -        @body = { :comment => comment, :info_request => info_request, :url => comment_url(comment) } -    end -    def comment_on_alert_plural(info_request, count, earliest_unalerted_comment) -        @from = contact_from_name_and_email -        headers 'Return-Path' => blackhole_email, 'Reply-To' => @from, # not much we can do if the user's email is broken -                'Auto-Submitted' => 'auto-generated', # http://tools.ietf.org/html/rfc3834 -                'X-Auto-Response-Suppress' => 'OOF' -        @recipients = info_request.user.name_and_email -        @subject = (_("Some notes have been added to your FOI request - ") + info_request.title).html_safe -        @body = { :count => count, :info_request => info_request, :url => comment_url(earliest_unalerted_comment) } -    end - -    # Class function, called by script/mailin with all incoming responses. -    # [ This is a copy (Monkeypatch!) of function from action_mailer/base.rb, -    # but which additionally passes the raw_email to the member function, as we -    # want to record it. -    # -    # That is because we want to be sure we properly record the actual message -    # received in its raw form - so any information won't be lost in a round -    # trip via the mail handler, or by bugs in it, and so we can use something -    # other than TMail at a later date. And so we can offer an option to download the -    # actual original mail sent by the authority in the admin interface (so -    # can check that attachment decoding failures are problems in the message, -    # not in our code). ] -    def self.receive(raw_email) -        logger.info "Received mail:\n #{raw_email}" unless logger.nil? -        mail = MailHandler.mail_from_raw_email(raw_email) -        new.receive(mail, raw_email) -    end - -    # Find which info requests the email is for -    def requests_matching_email(email) -        # We deliberately don't use Envelope-to here, so ones that are BCC -        # drop into the holding pen for checking. -        reply_info_requests = [] # XXX should be set? -        for address in (email.to || []) + (email.cc || []) -            reply_info_request = InfoRequest.find_by_incoming_email(address) -            reply_info_requests.push(reply_info_request) if reply_info_request -        end -        return reply_info_requests -    end - -    # Member function, called on the new class made in self.receive above -    def receive(email, raw_email) -        # Find which info requests the email is for -        reply_info_requests = self.requests_matching_email(email) -        # Nothing found, so save in holding pen -        if reply_info_requests.size == 0 -            reason = _("Could not identify the request from the email address") -            request = InfoRequest.holding_pen_request -            request.receive(email, raw_email, false, reason) -            return -        end - -        # Send the message to each request, to be archived with it -        for reply_info_request in reply_info_requests -            # If environment variable STOP_DUPLICATES is set, don't send message with same id again -            if ENV['STOP_DUPLICATES'] -                if reply_info_request.already_received?(email, raw_email) -                    raise "message " + email.message_id + " already received by request" -                end -            end -            reply_info_request.receive(email, raw_email) -        end -    end - -    # Send email alerts for overdue requests -    def self.alert_overdue_requests() -        info_requests = InfoRequest.find(:all, -            :conditions => [ -                "described_state = 'waiting_response' -                 AND awaiting_description = ? -                 AND user_id is not null -                 AND (SELECT id -                      FROM user_info_request_sent_alerts -                      WHERE alert_type = 'very_overdue_1' -                      AND info_request_id = info_requests.id -                      AND user_id = info_requests.user_id -                      AND info_request_event_id = (SELECT max(id) -                                                   FROM info_request_events -                                                   WHERE event_type in ('sent', -                                                                        'followup_sent', -                                                                        'resent', -                                                                        'followup_resent') -                      AND info_request_id = info_requests.id) -                      ) IS NULL", false -            ], -            :include => [ :user ] -        ) -        for info_request in info_requests -            alert_event_id = info_request.last_event_forming_initial_request.id -            # Only overdue requests -            calculated_status = info_request.calculate_status -            if ['waiting_response_overdue', 'waiting_response_very_overdue'].include?(calculated_status) -                if calculated_status == 'waiting_response_overdue' -                    alert_type = 'overdue_1' -                elsif calculated_status == 'waiting_response_very_overdue' -                    alert_type = 'very_overdue_1' -                else -                    raise "unknown request status" -                end - -                # For now, just to the user who created the request -                sent_already = UserInfoRequestSentAlert.find(:first, :conditions => [ "alert_type = ? -                                                                                       AND user_id = ? -                                                                                       AND info_request_id = ? -                                                                                       AND info_request_event_id = ?", -                                                                                       alert_type, -                                                                                       info_request.user_id, -                                                                                       info_request.id, -                                                                                       alert_event_id]) -                if sent_already.nil? -                    # Alert not yet sent for this user, so send it -                    store_sent = UserInfoRequestSentAlert.new -                    store_sent.info_request = info_request -                    store_sent.user = info_request.user -                    store_sent.alert_type = alert_type -                    store_sent.info_request_event_id = alert_event_id -                    # Only send the alert if the user can act on it by making a followup -                    # (otherwise they are banned, and there is no point sending it) -                    if info_request.user.can_make_followup? -                        if calculated_status == 'waiting_response_overdue' -                            RequestMailer.deliver_overdue_alert(info_request, info_request.user) -                        elsif calculated_status == 'waiting_response_very_overdue' -                            RequestMailer.deliver_very_overdue_alert(info_request, info_request.user) -                        else -                            raise "unknown request status" -                        end -                    end -                    store_sent.save! -                end -            end -        end -    end - -    # Send email alerts for new responses which haven't been classified. By default, -    # it goes out 3 days after last update of event, then after 10, then after 24. -    def self.alert_new_response_reminders -        Configuration::new_response_reminder_after_days.each_with_index do |days, i| -            self.alert_new_response_reminders_internal(days, "new_response_reminder_#{i+1}") -        end -    end -    def self.alert_new_response_reminders_internal(days_since, type_code) -        info_requests = InfoRequest.find_old_unclassified(:order => 'info_requests.id', -                                                          :include => [:user], -                                                          :age_in_days => days_since) - -        for info_request in info_requests -            alert_event_id = info_request.get_last_response_event_id -            last_response_message = info_request.get_last_response -            if alert_event_id.nil? -                raise "internal error, no last response while making alert new response reminder, request id " + info_request.id.to_s -            end -            # To the user who created the request -            sent_already = UserInfoRequestSentAlert.find(:first, :conditions => [ "alert_type = ? and user_id = ? and info_request_id = ? and info_request_event_id = ?", type_code, info_request.user_id, info_request.id, alert_event_id]) -            if sent_already.nil? -                # Alert not yet sent for this user -                store_sent = UserInfoRequestSentAlert.new -                store_sent.info_request = info_request -                store_sent.user = info_request.user -                store_sent.alert_type = type_code -                store_sent.info_request_event_id = alert_event_id -                # XXX uses same template for reminder 1 and reminder 2 right now. -                RequestMailer.deliver_new_response_reminder_alert(info_request, last_response_message) -                store_sent.save! -            end -        end -    end - -    # Send email alerts for requests which need clarification. Goes out 3 days -    # after last update of event. -    def self.alert_not_clarified_request() -        info_requests = InfoRequest.find(:all, :conditions => [ "awaiting_description = ? and described_state = 'waiting_clarification' and info_requests.updated_at < ?", false, Time.now() - 3.days ], :include => [ :user ], :order => "info_requests.id" ) -        for info_request in info_requests -            alert_event_id = info_request.get_last_response_event_id -            last_response_message = info_request.get_last_response -            if alert_event_id.nil? -                raise "internal error, no last response while making alert not clarified reminder, request id " + info_request.id.to_s -            end -            # To the user who created the request -            sent_already = UserInfoRequestSentAlert.find(:first, :conditions => [ "alert_type = 'not_clarified_1' and user_id = ? and info_request_id = ? and info_request_event_id = ?", info_request.user_id, info_request.id, alert_event_id]) -            if sent_already.nil? -                # Alert not yet sent for this user -                store_sent = UserInfoRequestSentAlert.new -                store_sent.info_request = info_request -                store_sent.user = info_request.user -                store_sent.alert_type = 'not_clarified_1' -                store_sent.info_request_event_id = alert_event_id -                # Only send the alert if the user can act on it by making a followup -                # (otherwise they are banned, and there is no point sending it) -                if info_request.user.can_make_followup? -                    RequestMailer.deliver_not_clarified_alert(info_request, last_response_message) -                end -                store_sent.save! -            end -        end -    end - -    # Send email alert to request submitter for new comments on the request. -    def self.alert_comment_on_request() - -        # We only check comments made in the last month - this means if the -        # cron jobs broke for more than a month events would be lost, but no -        # matter. I suspect the performance gain will be needed (with an index on updated_at) - -        # XXX the :order part info_request_events.created_at is a work around -        # for a very old Rails bug which means eager loading does not respect -        # association orders. -        #   http://dev.rubyonrails.org/ticket/3438 -        #   http://lists.rubyonrails.org/pipermail/rails-core/2006-July/001798.html -        # That that patch has not been applied, despite bribes of beer, is -        # typical of the lack of quality of Rails. - -        info_requests = InfoRequest.find(:all, -            :conditions => [ -               "info_requests.id in ( -                    select info_request_id -                    from info_request_events -                    where event_type = 'comment' -                    and created_at > (now() - '1 month'::interval) -                )" -            ], -            :include => [ { :info_request_events => :user_info_request_sent_alerts } ], -            :order => "info_requests.id, info_request_events.created_at" -        ) -        for info_request in info_requests - -            next if info_request.is_external? -            # Count number of new comments to alert on -            earliest_unalerted_comment_event = nil -            last_comment_event = nil -            count = 0 -            for e in info_request.info_request_events.reverse -                # alert on comments, which were not made by the user who originally made the request -                if e.event_type == 'comment' && e.comment.user_id != info_request.user_id -                    last_comment_event = e if last_comment_event.nil? - -                    alerted_for = e.user_info_request_sent_alerts.find(:first, :conditions => [ "alert_type = 'comment_1' and user_id = ?", info_request.user_id]) -                    if alerted_for.nil? -                        count = count + 1 -                        earliest_unalerted_comment_event = e -                    else -                        break -                    end -                end -            end - -            # Alert needs sending if there are new comments -            if count > 0 -                store_sent = UserInfoRequestSentAlert.new -                store_sent.info_request = info_request -                store_sent.user = info_request.user -                store_sent.alert_type = 'comment_1' -                store_sent.info_request_event_id = last_comment_event.id -                if count > 1 -                    RequestMailer.deliver_comment_on_alert_plural(info_request, count, earliest_unalerted_comment_event.comment) -                elsif count == 1 -                    RequestMailer.deliver_comment_on_alert(info_request, last_comment_event.comment) -                else -                    raise "internal error" -                end -                store_sent.save! -            end -        end -    end - - -end - - diff --git a/app/models/track_mailer.rb b/app/models/track_mailer.rb deleted file mode 100644 index 7262c82f3..000000000 --- a/app/models/track_mailer.rb +++ /dev/null @@ -1,134 +0,0 @@ -# models/track_mailer.rb: -# Emails which go to users who are tracking things. -# -# Copyright (c) 2008 UK Citizens Online Democracy. All rights reserved. -# Email: francis@mysociety.org; WWW: http://www.mysociety.org/ - -class TrackMailer < ApplicationMailer -    def event_digest(user, email_about_things) -        post_redirect = PostRedirect.new( -            :uri => user_url(user) + "#email_subscriptions", -            :user_id => user.id) -        post_redirect.save! -        unsubscribe_url = confirm_url(:email_token => post_redirect.email_token) - -        @from = contact_from_name_and_email -        headers 'Auto-Submitted' => 'auto-generated', # http://tools.ietf.org/html/rfc3834 -                'Precedence' => 'bulk' # http://www.vbulletin.com/forum/project.php?issueid=27687 (Exchange hack) -        # 'Return-Path' => blackhole_email, 'Reply-To' => @from # we don't care about bounces for tracks -        # (We let it return bounces for now, so we can manually kill the tracks that bounce so Yahoo -        # etc. don't decide we are spammers.) - -        @recipients = user.name_and_email -        @subject = _("Your {{site_name}} email alert", :site_name => site_name) -        @body = { :user => user, :email_about_things => email_about_things, :unsubscribe_url => unsubscribe_url } -    end - -    def contact_from_name_and_email -        "#{Configuration::track_sender_name} <#{Configuration::track_sender_email}>" -    end - -    # Send email alerts for tracked things.  Never more than one email -    # a day, nor about events which are more than a week old, nor -    # events about which emails have been sent within the last two -    # weeks. - -    # Useful query to run by hand to see how many alerts are due: -    #   User.find(:all, :conditions => [ "last_daily_track_email < ?", Time.now - 1.day ]).size -    def self.alert_tracks -        done_something = false -        now = Time.now() -        users = User.find(:all, :conditions => [ "last_daily_track_email < ?", now - 1.day ]) -        if users.empty? -            return done_something -        end -        for user in users -            next if !user.should_be_emailed? || !user.receive_email_alerts - -            email_about_things = [] -            track_things = TrackThing.find(:all, :conditions => [ "tracking_user_id = ? and track_medium = ?", user.id, 'email_daily' ]) -            for track_thing in track_things -                # What have we alerted on already? -                # -                # We only use track_things_sent_emails records which are less than 14 days old. -                # In the search query loop below, we also only use items described in last 7 days. -                # An item described that recently definitely can't appear in track_things_sent_emails -                # earlier, so this is safe (with a week long margin of error). If the alerts break -                # for a whole week, then they will miss some items. Tough. -                done_info_request_events = {} -                tt_sent = track_thing.track_things_sent_emails.find(:all, :conditions => ['created_at > ?', now - 14.days]) -                for t in tt_sent -                    if not t.info_request_event_id.nil? -                        done_info_request_events[t.info_request_event_id] = 1 -                    end -                end - -                # Query for things in this track. We use described_at for the -                # ordering, so we catch anything new (before described), or -                # anything whose new status has been described. -                xapian_object = InfoRequest.full_search([InfoRequestEvent], track_thing.track_query, 'described_at', true, nil, 100, 1) -                # Go through looking for unalerted things -                alert_results = [] -                for result in xapian_object.results -                    if result[:model].class.to_s != "InfoRequestEvent" -                        raise "need to add other types to TrackMailer.alert_tracks (unalerted)" -                    end - -                    next if track_thing.created_at >= result[:model].described_at # made before the track was created -                    next if result[:model].described_at < now - 7.days # older than 1 week (see 14 days / 7 days in comment above) -                    next if done_info_request_events.include?(result[:model].id) # definitely already done - -                    # OK alert this one -                    alert_results.push(result) -                end -                # If there were more alerts for this track, then store them -                if alert_results.size > 0 -                    email_about_things.push([track_thing, alert_results, xapian_object]) -                end -            end - -            # If we have anything to send, then send everything for the user in one mail -            if email_about_things.size > 0 -                # Send the email - -                I18n.with_locale(user.get_locale) do -                    TrackMailer.deliver_event_digest(user, email_about_things) -                end -            end - -            # Record that we've now sent those alerts to that user -            for track_thing, alert_results in email_about_things -                for result in alert_results -                    track_things_sent_email = TrackThingsSentEmail.new -                    track_things_sent_email.track_thing_id = track_thing.id -                    if result[:model].class.to_s == "InfoRequestEvent" -                        track_things_sent_email.info_request_event_id = result[:model].id -                    else -                        raise "need to add other types to TrackMailer.alert_tracks (mark alerted)" -                    end -                    track_things_sent_email.save! -                end -            end -            user.last_daily_track_email = now -            user.no_xapian_reindex = true -            user.save! -            done_something = true -        end -        return done_something -    end - -    def self.alert_tracks_loop -        # Run alert_tracks in an endless loop, sleeping when there is nothing to do -        while true -            sleep_seconds = 1 -            while !alert_tracks -                sleep sleep_seconds -                sleep_seconds *= 2 -                sleep_seconds = 300 if sleep_seconds > 300 -            end -        end -    end - -end - - diff --git a/app/models/track_thing.rb b/app/models/track_thing.rb index a0c74bdb6..66b8a5c47 100644 --- a/app/models/track_thing.rb +++ b/app/models/track_thing.rb @@ -19,7 +19,7 @@  # When somebody is getting alerts for something.  #  # Copyright (c) 2008 UK Citizens Online Democracy. All rights reserved. -# Email: francis@mysociety.org; WWW: http://www.mysociety.org/ +# Email: hello@mysociety.org; WWW: http://www.mysociety.org/  require 'set' @@ -260,7 +260,7 @@ class TrackThing < ActiveRecord::Base                      :title_in_email => self.public_body.law_only_short + " requests to '" + self.public_body.name + "'",                      :title_in_rss => self.public_body.law_only_short + " requests to '" + self.public_body.name + "'",                      # Authentication -                    :web => _("To follow requests made using {{site_name}} to the public authority '{{public_body_name}}'", :site_name=>Configuration::site_name, :public_body_name=>CGI.escapeHTML(self.public_body.name)), +                    :web => _("To follow requests made using {{site_name}} to the public authority '{{public_body_name}}'", :site_name=>AlaveteliConfiguration::site_name, :public_body_name=>CGI.escapeHTML(self.public_body.name)),                      :email => _("Then you will be notified whenever someone requests something or gets a response from '{{public_body_name}}'.", :public_body_name=>CGI.escapeHTML(self.public_body.name)),                      :email_subject => _("Confirm you want to follow requests to '{{public_body_name}}'", :public_body_name=>self.public_body.name),                      # RSS sorting diff --git a/app/models/track_things_sent_email.rb b/app/models/track_things_sent_email.rb index a0a4c0f0c..a9ea2520e 100644 --- a/app/models/track_things_sent_email.rb +++ b/app/models/track_things_sent_email.rb @@ -16,7 +16,7 @@  # Record that alert has arrived.  #  # Copyright (c) 2008 UK Citizens Online Democracy. All rights reserved. -# Email: francis@mysociety.org; WWW: http://www.mysociety.org/ +# Email: hello@mysociety.org; WWW: http://www.mysociety.org/  class TrackThingsSentEmail < ActiveRecord::Base      belongs_to :info_request_event diff --git a/app/models/user.rb b/app/models/user.rb index 37edbe360..306d6ad4a 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -58,6 +58,9 @@ class User < ActiveRecord::Base          ],          :terms => [ [ :variety, 'V', "variety" ] ],          :if => :indexed_by_search? + +    after_initialize :set_defaults +      def created_at_numeric          # format it here as no datetime support in Xapian's value ranges          return self.created_at.strftime("%Y%m%d%H%M%S") @@ -67,17 +70,6 @@ class User < ActiveRecord::Base          "user"      end -    def after_initialize -        if self.admin_level.nil? -            self.admin_level = 'none' -        end -        if self.new_record? -            # make alert emails go out at a random time for each new user, so -            # overall they are spread out throughout the day. -            self.last_daily_track_email = User.random_time_in_last_day -        end -    end -      # requested_by: and commented_by: search queries also need updating after save      after_update :reindex_referencing_models      def reindex_referencing_models @@ -138,14 +130,14 @@ class User < ActiveRecord::Base          if user              # There is user with email, check password              if !user.has_this_password?(params[:password]) -                user.errors.add_to_base(auth_fail_message) +                user.errors.add(:base, auth_fail_message)              end          else              # No user of same email, make one (that we don't save in the database)              # for the forms code to use.              user = User.new(params)              # deliberately same message as above so as not to leak whether registered -            user.errors.add_to_base(auth_fail_message) +            user.errors.add(:base, auth_fail_message)          end          user      end @@ -198,12 +190,12 @@ class User < ActiveRecord::Base      # The "internal admin" is a special user for internal use.      def User.internal_admin_user -        u = User.find_by_email(Configuration::contact_email) +        u = User.find_by_email(AlaveteliConfiguration::contact_email)          if u.nil?              password = PostRedirect.generate_random_token              u = User.new(                  :name => 'Internal admin user', -                :email => Configuration::contact_email, +                :email => AlaveteliConfiguration::contact_email,                  :password => password,                  :password_confirmation => password              ) @@ -276,16 +268,16 @@ class User < ActiveRecord::Base          return false if self.no_limit          # Has the user issued as many as MAX_REQUESTS_PER_USER_PER_DAY requests in the past 24 hours? -        return false if Configuration::max_requests_per_user_per_day.blank? +        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]) -        return (recent_requests >= Configuration::max_requests_per_user_per_day) +        return (recent_requests >= AlaveteliConfiguration::max_requests_per_user_per_day)      end      def next_request_permitted_at          return nil if self.no_limit -        n_most_recent_requests = InfoRequest.all(:conditions => ["user_id = ? and created_at > now() - '1 day'::interval", self.id], :order => "created_at DESC", :limit => Configuration::max_requests_per_user_per_day) -        return nil if n_most_recent_requests.size < Configuration::max_requests_per_user_per_day +        n_most_recent_requests = InfoRequest.all(:conditions => ["user_id = ? and created_at > now() - '1 day'::interval", self.id], :order => "created_at DESC", :limit => AlaveteliConfiguration::max_requests_per_user_per_day) +        return nil if n_most_recent_requests.size < AlaveteliConfiguration::max_requests_per_user_per_day          nth_most_recent_request = n_most_recent_requests[-1]          return nth_most_recent_request.created_at + 1.day @@ -404,6 +396,17 @@ class User < ActiveRecord::Base          self.salt = self.object_id.to_s + rand.to_s      end +    def set_defaults +        if self.admin_level.nil? +            self.admin_level = 'none' +        end +        if self.new_record? +            # make alert emails go out at a random time for each new user, so +            # overall they are spread out throughout the day. +            self.last_daily_track_email = User.random_time_in_last_day +        end +    end +      def email_and_name_are_valid          if self.email != "" && !MySociety::Validate.is_valid_email(self.email)              errors.add(:email, _("Please enter a valid email address")) diff --git a/app/models/user_info_request_sent_alert.rb b/app/models/user_info_request_sent_alert.rb index cf20bcbf5..449a4c237 100644 --- a/app/models/user_info_request_sent_alert.rb +++ b/app/models/user_info_request_sent_alert.rb @@ -15,7 +15,7 @@  # given type of alert.  #  # Copyright (c) 2008 UK Citizens Online Democracy. All rights reserved. -# Email: francis@mysociety.org; WWW: http://www.mysociety.org/ +# Email: hello@mysociety.org; WWW: http://www.mysociety.org/  class UserInfoRequestSentAlert < ActiveRecord::Base      belongs_to :user diff --git a/app/models/user_mailer.rb b/app/models/user_mailer.rb deleted file mode 100644 index 1be4f8aa3..000000000 --- a/app/models/user_mailer.rb +++ /dev/null @@ -1,48 +0,0 @@ -# models/user_mailer.rb: -# Emails relating to user accounts. e.g. Confirming a new account -# -# Copyright (c) 2007 UK Citizens Online Democracy. All rights reserved. -# Email: francis@mysociety.org; WWW: http://www.mysociety.org/ - -class UserMailer < ApplicationMailer -    def confirm_login(user, reasons, url) -        @from = contact_from_name_and_email -        headers 'Return-Path' => blackhole_email, 'Reply-To' => @from # we don't care about bounces when people are fiddling with their account -        @recipients = user.name_and_email -        @subject    = reasons[:email_subject] -        @body[:reasons] = reasons -        @body[:name] = user.name -        @body[:url] = url -    end - -    def already_registered(user, reasons, url) -        @from = contact_from_name_and_email -        headers 'Return-Path' => blackhole_email, 'Reply-To' => @from # we don't care about bounces when people are fiddling with their account -        @recipients = user.name_and_email -        @subject    = reasons[:email_subject] -        @body[:reasons] = reasons -        @body[:name] = user.name -        @body[:url] = url -    end - -    def changeemail_confirm(user, new_email, url) -        @from = contact_from_name_and_email -        headers 'Return-Path' => blackhole_email, 'Reply-To' => @from # we don't care about bounces when people are fiddling with their account -        @recipients = new_email -        @subject    = _("Confirm your new email address on {{site_name}}", :site_name=>site_name) -        @body[:name] = user.name -        @body[:url] = url -        @body[:old_email] = user.email -        @body[:new_email] = new_email -    end - -    def changeemail_already_used(old_email, new_email) -        @from = contact_from_name_and_email -        headers 'Return-Path' => blackhole_email, 'Reply-To' => @from # we don't care about bounces when people are fiddling with their account -        @recipients = new_email -        @subject    = _("Unable to change email address on {{site_name}}", :site_name=>site_name) -        @body[:old_email] = old_email -        @body[:new_email] = new_email -    end -end -  | 
