diff options
Diffstat (limited to 'app/models')
35 files changed, 686 insertions, 647 deletions
diff --git a/app/models/about_me_validator.rb b/app/models/about_me_validator.rb index 8c24cfd67..8465b0716 100644 --- a/app/models/about_me_validator.rb +++ b/app/models/about_me_validator.rb @@ -1,3 +1,4 @@ +# -*- encoding : utf-8 -*- # models/about_me_validator.rb: # Validates editing about me text on user profile pages. # @@ -9,20 +10,11 @@ class AboutMeValidator attr_accessor :about_me - # TODO: Switch to built in validations - validate :length_of_about_me + validates_length_of :about_me, :maximum => 500, :message => _("Please keep it shorter than 500 characters") def initialize(attributes = {}) attributes.each do |name, value| send("#{name}=", value) end end - - private - - def length_of_about_me - if !about_me.blank? && about_me.size > 500 - errors.add(:about_me, _("Please keep it shorter than 500 characters")) - end - end end diff --git a/app/models/censor_rule.rb b/app/models/censor_rule.rb index 3b5c2d805..aec8a87cc 100644 --- a/app/models/censor_rule.rb +++ b/app/models/censor_rule.rb @@ -1,3 +1,4 @@ +# -*- encoding : utf-8 -*- # == Schema Information # # Table name: censor_rules @@ -22,6 +23,7 @@ # Email: hello@mysociety.org; WWW: http://www.mysociety.org/ class CensorRule < ActiveRecord::Base + include AdminColumn belongs_to :info_request belongs_to :user belongs_to :public_body @@ -42,20 +44,19 @@ class CensorRule < ActiveRecord::Base :user_id => nil, :public_body_id => nil } } + def apply_to_text(text_to_censor) + return nil if text_to_censor.nil? + text_to_censor.gsub(to_replace('UTF-8'), replacement) + end + def apply_to_text!(text_to_censor) return nil if text_to_censor.nil? - text_to_censor.gsub!(to_replace, replacement) + text_to_censor.gsub!(to_replace('UTF-8'), replacement) end def apply_to_binary!(binary_to_censor) return nil if binary_to_censor.nil? - binary_to_censor.gsub!(to_replace) { |match| match.gsub(/./, 'x') } - end - - def for_admin_column - self.class.content_columns.each do |column| - yield(column.human_name, send(column.name), column.type.to_s, column.name) - end + binary_to_censor.gsub!(to_replace('ASCII-8BIT')) { |match| match.gsub(single_char_regexp, 'x') } end def is_global? @@ -64,6 +65,14 @@ class CensorRule < ActiveRecord::Base private + def single_char_regexp + if String.method_defined?(:encode) + Regexp.new('.'.force_encoding('ASCII-8BIT')) + else + Regexp.new('.', nil, 'N') + end + end + def require_user_request_or_public_body if info_request.nil? && user.nil? && public_body.nil? [:info_request, :user, :public_body].each do |a| @@ -74,18 +83,22 @@ class CensorRule < ActiveRecord::Base def require_valid_regexp begin - make_regexp + make_regexp('UTF-8') rescue RegexpError => e errors.add(:text, e.message) end end - def make_regexp - Regexp.new(text, Regexp::MULTILINE) + def to_replace(encoding) + regexp? ? make_regexp(encoding) : encoded_text(encoding) + end + + def encoded_text(encoding) + String.method_defined?(:encode) ? text.dup.force_encoding(encoding) : text end - def to_replace - regexp? ? make_regexp : text + def make_regexp(encoding) + Regexp.new(encoded_text(encoding), Regexp::MULTILINE) end end diff --git a/app/models/change_email_validator.rb b/app/models/change_email_validator.rb index 65f2fd81c..f5b31f038 100644 --- a/app/models/change_email_validator.rb +++ b/app/models/change_email_validator.rb @@ -1,3 +1,4 @@ +# -*- encoding : utf-8 -*- # models/changeemail_validator.rb: # Validates email change form submissions. # diff --git a/app/models/comment.rb b/app/models/comment.rb index cc8d0e94b..59f91ffb7 100644 --- a/app/models/comment.rb +++ b/app/models/comment.rb @@ -1,3 +1,4 @@ +# -*- encoding : utf-8 -*- # == Schema Information # # Table name: comments @@ -20,6 +21,7 @@ # Email: hello@mysociety.org; WWW: http://www.mysociety.org/ class Comment < ActiveRecord::Base + include AdminColumn strip_attributes! belongs_to :user @@ -31,6 +33,8 @@ class Comment < ActiveRecord::Base validate :check_body_has_content, :check_body_uses_mixed_capitals + scope :visible, where(:visible => true) + after_save :event_xapian_update # When posting a new comment, use this to check user hasn't double @@ -57,10 +61,6 @@ class Comment < ActiveRecord::Base ret end - def raw_body - read_attribute(:body) - end - # So when takes changes it updates, or when made invisble it vanishes def event_xapian_update info_request_events.each { |event| event.xapian_mark_needs_index } @@ -75,12 +75,6 @@ class Comment < ActiveRecord::Base text.html_safe end - def for_admin_column - self.class.content_columns.each do |column| - yield(column.human_name, send(column.name), column.type.to_s, column.name) - end - end - private def check_body_has_content diff --git a/app/models/concerns/admin_column.rb b/app/models/concerns/admin_column.rb new file mode 100644 index 000000000..6e19f5aa5 --- /dev/null +++ b/app/models/concerns/admin_column.rb @@ -0,0 +1,17 @@ +module AdminColumn + extend ActiveSupport::Concern + + included do + class << self + attr_reader :non_admin_columns + end + + @non_admin_columns = [] + end + + def for_admin_column + self.class.content_columns.reject { |c| self.class.non_admin_columns.include?(c.name) }.each do |column| + yield(column.human_name, send(column.name), column.type.to_s, column.name) + end + end +end diff --git a/app/models/concerns/public_body_derived_fields.rb b/app/models/concerns/public_body_derived_fields.rb new file mode 100644 index 000000000..f389e3cbf --- /dev/null +++ b/app/models/concerns/public_body_derived_fields.rb @@ -0,0 +1,47 @@ +module PublicBodyDerivedFields + + extend ActiveSupport::Concern + + included do + before_save :set_first_letter + + # When name or short name is changed, also change the url name + def short_name=(short_name) + super + update_url_name + end + + def name=(name) + super + update_url_name + end + + end + + # Return the short name if present, or else long name + def short_or_long_name + if self.short_name.nil? || self.short_name.empty? + self.name.nil? ? "" : self.name + else + self.short_name + end + end + + # Set the first letter, which is used for faster queries + def set_first_letter + unless name.blank? + # we use a regex to ensure it works with utf-8/multi-byte + first_letter = Unicode.upcase name.scan(/^./mu)[0] + if first_letter != self.first_letter + self.first_letter = first_letter + end + end + end + + def update_url_name + if changed.include?('name') || changed.include?('short_name') + self.url_name = MySociety::Format.simplify_url_part(self.short_or_long_name, 'body') + end + end + +end diff --git a/app/models/concerns/translatable.rb b/app/models/concerns/translatable.rb new file mode 100644 index 000000000..2aa4620d0 --- /dev/null +++ b/app/models/concerns/translatable.rb @@ -0,0 +1,37 @@ +module Translatable + extend ActiveSupport::Concern + + included do + accepts_nested_attributes_for :translations, :reject_if => :empty_translation_in_params? + end + + def find_translation_by_locale(locale) + translations.find_by_locale(locale) + end + + def translated_versions + translations + end + + def ordered_translations + translations.select do |translation| + I18n.available_locales.include?(translation.locale) + end.sort_by do |translation| + I18n.available_locales.index(translation.locale) + end + end + + def build_all_translations + I18n.available_locales.each do |locale| + if translations.none? { |translation| translation.locale == locale } + translations.build(:locale => locale) + end + end + end + + private + + def empty_translation_in_params?(attributes) + attributes.select { |k, v| v.present? && k.to_s != 'locale' }.empty? + end +end diff --git a/app/models/contact_validator.rb b/app/models/contact_validator.rb index 8d7e4ff08..1d50bf603 100644 --- a/app/models/contact_validator.rb +++ b/app/models/contact_validator.rb @@ -1,3 +1,4 @@ +# -*- encoding : utf-8 -*- # models/contact_validator.rb: # Validates contact form submissions. # diff --git a/app/models/foi_attachment.rb b/app/models/foi_attachment.rb index a8d105f52..37a9c9827 100644 --- a/app/models/foi_attachment.rb +++ b/app/models/foi_attachment.rb @@ -1,4 +1,4 @@ -# encoding: UTF-8 +# -*- encoding : utf-8 -*- # == Schema Information # # Table name: foi_attachments @@ -62,19 +62,18 @@ class FoiAttachment < ActiveRecord::Base } update_display_size! @cached_body = d + if String.method_defined?(:encode) + @cached_body = @cached_body.force_encoding("ASCII-8BIT") + end end + # raw body, encoded as binary def body if @cached_body.nil? tries = 0 delay = 1 begin - binary_data = File.open(self.filepath, "rb" ){ |file| file.read } - if self.content_type =~ /^text/ - @cached_body = convert_string_to_utf8_or_binary(binary_data, 'UTF-8') - else - @cached_body = binary_data - end + @cached_body = File.open(filepath, "rb" ){ |file| file.read } rescue Errno::ENOENT # we've lost our cached attachments for some reason. Reparse them. if tries > BODY_MAX_TRIES @@ -93,6 +92,17 @@ class FoiAttachment < ActiveRecord::Base return @cached_body end + # body as UTF-8 text, with scrubbing of invalid chars if needed + def body_as_text + convert_string_to_utf8(body, 'UTF-8') + end + + # for text types, the scrubbed UTF-8 text. For all other types, the + # raw binary + def default_body + text_type? ? body_as_text.string : body + end + # List of DSN codes taken from RFC 3463 # http://tools.ietf.org/html/rfc3463 DsnToMessage = { @@ -244,36 +254,32 @@ class FoiAttachment < ActiveRecord::Base # The full list of supported types can be found at # https://docs.google.com/support/bin/answer.py?hl=en&answer=1189935 def has_google_docs_viewer? - return !! { - "application/pdf" => true, # .pdf - "image/tiff" => true, # .tiff - - "application/vnd.ms-word" => true, # .doc - "application/vnd.openxmlformats-officedocument.wordprocessingml.document" => true, # .docx + [ + "application/pdf", # .pdf + "image/tiff", # .tiff - "application/vnd.ms-powerpoint" => true, # .ppt - "application/vnd.openxmlformats-officedocument.presentationml.presentation" => true, # .pptx + "application/vnd.ms-word", # .doc + "application/vnd.openxmlformats-officedocument.wordprocessingml.document", # .docx - "application/vnd.ms-excel" => true, # .xls - "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" => true, # .xlsx + "application/vnd.ms-powerpoint", # .ppt + "application/vnd.openxmlformats-officedocument.presentationml.presentation", # .pptx - } [self.content_type] + "application/vnd.ms-excel", # .xls + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", # .xlsx + ].include?(content_type) end # Whether this type has a "View as HTML" def has_body_as_html? - return ( - !!{ - "text/plain" => true, - "application/rtf" => true, - }[self.content_type] or - self.has_google_docs_viewer? - ) + [ + "text/plain", + "application/rtf", + ].include?(content_type) || has_google_docs_viewer? end # Name of type of attachment type - only valid for things that has_body_as_html? def name_of_content_type - return { + { "text/plain" => "Text file", 'application/rtf' => "RTF file", @@ -298,5 +304,11 @@ class FoiAttachment < ActiveRecord::Base AttachmentToHTML.to_html(self, to_html_opts) end + private + + def text_type? + AlaveteliTextMasker::TextMask.include?(content_type) + end + end diff --git a/app/models/holiday.rb b/app/models/holiday.rb index 34044683a..538e77051 100644 --- a/app/models/holiday.rb +++ b/app/models/holiday.rb @@ -1,3 +1,4 @@ +# -*- encoding : utf-8 -*- # == Schema Information # # Table name: holidays diff --git a/app/models/holiday_import.rb b/app/models/holiday_import.rb index 98a9b96fc..58ea0b1f7 100644 --- a/app/models/holiday_import.rb +++ b/app/models/holiday_import.rb @@ -1,3 +1,4 @@ +# -*- encoding : utf-8 -*- class HolidayImport include ActiveModel::Validations diff --git a/app/models/incoming_message.rb b/app/models/incoming_message.rb index 3606c39c2..71b081560 100644 --- a/app/models/incoming_message.rb +++ b/app/models/incoming_message.rb @@ -1,4 +1,4 @@ -# coding: utf-8 +# -*- encoding : utf-8 -*- # == Schema Information # # Table name: incoming_messages @@ -38,6 +38,7 @@ require 'zip/zip' require 'iconv' unless String.method_defined?(:encode) class IncomingMessage < ActiveRecord::Base + include AdminColumn extend MessageProminence belongs_to :info_request validates_presence_of :info_request @@ -371,41 +372,23 @@ class IncomingMessage < ActiveRecord::Base def _convert_part_body_to_text(part) if part.nil? text = "[ Email has no body, please see attachments ]" - source_charset = "utf-8" else - # by default, the body (coming from an foi_attachment) should have been converted to utf-8 - text = part.body - source_charset = part.charset + # whatever kind of attachment it is, get the UTF-8 encoded text + text = part.body_as_text.string if part.content_type == 'text/html' # e.g. http://www.whatdotheyknow.com/request/35/response/177 # TODO: This is a bit of a hack as it is calling a # convert to text routine. Could instead call a # sanitize HTML one. - - # If the text isn't UTF8, it means we had a problem - # converting it (invalid characters, etc), and we - # should instead tell elinks to respect the source - # charset - use_charset = "utf-8" - if String.method_defined?(:encode) - begin - text.encode('utf-8') - rescue Encoding::UndefinedConversionError, Encoding::InvalidByteSequenceError - use_charset = source_charset - end - else - begin - text = Iconv.conv('utf-8', 'utf-8', text) - rescue Iconv::IllegalSequence - use_charset = source_charset - end - end - text = MailHandler.get_attachment_text_one_file(part.content_type, text, use_charset) + text = MailHandler.get_attachment_text_one_file(part.content_type, text, "UTF-8") end end - # If text hasn't been converted, we sanitise it. - text = _sanitize_text(text) + # Add an annotation if the text had to be scrubbed + if part.body_as_text.scrubbed? + text += _("\n\n[ {{site_name}} note: The above text was badly encoded, and has had strange characters removed. ]", + :site_name => MySociety::Config.get('SITE_NAME', 'Alaveteli')) + end # Fix DOS style linefeeds to Unix style ones (or other later regexps won't work) text = text.gsub(/\r\n/, "\n") @@ -417,50 +400,6 @@ class IncomingMessage < ActiveRecord::Base return text end - def _sanitize_text(text) - if String.method_defined?(:encode) - begin - # Test if it's good UTF-8 - text.encode('utf-8') - rescue Encoding::UndefinedConversionError, Encoding::InvalidByteSequenceError - source_charset = 'utf-8' if source_charset.nil? - # strip out anything that isn't UTF-8 - begin - text = text.encode("utf-8", :invalid => :replace, - :undef => :replace, - :replace => "") + - _("\n\n[ {{site_name}} note: The above text was badly encoded, and has had strange characters removed. ]", - :site_name => MySociety::Config.get('SITE_NAME', 'Alaveteli')) - rescue Encoding::UndefinedConversionError, Encoding::InvalidByteSequenceError - if source_charset != "utf-8" - source_charset = "utf-8" - retry - end - end - end - else - begin - # Test if it's good UTF-8 - text = Iconv.conv('utf-8', 'utf-8', text) - rescue Iconv::IllegalSequence - # Text looks like unlabelled nonsense, - # strip out anything that isn't UTF-8 - begin - 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 => AlaveteliConfiguration::site_name) - rescue Iconv::InvalidEncoding, Iconv::IllegalSequence, Iconv::InvalidCharacter - if source_charset != "utf-8" - source_charset = "utf-8" - retry - end - end - end - end - text - end - # 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. @@ -560,7 +499,7 @@ class IncomingMessage < ActiveRecord::Base # because the hexdigest of an attachment is identical. 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. + # conversions, since _uudecode_and_save_attachments needs to deal with those. # e.g. for https://secure.mysociety.org/admin/foi/request/show_raw_email/24550 if !main_part.nil? uudecoded_attachments = _uudecode_and_save_attachments(main_part.body) @@ -676,16 +615,7 @@ class IncomingMessage < ActiveRecord::Base end def _get_attachment_text_internal - text = self._extract_text - - # Remove any bad characters - if String.method_defined?(:encode) - # handle "problematic" encoding - text.encode!('UTF-16', 'UTF-8', :invalid => :replace, :undef => :replace, :replace => '') - text.encode('UTF-8', 'UTF-16') - else - Iconv.conv('utf-8//IGNORE', 'utf-8', text) - end + convert_string_to_utf8(_extract_text, 'UTF-8').string end # Returns text for indexing @@ -719,7 +649,7 @@ class IncomingMessage < ActiveRecord::Base end # Search all info requests for - def IncomingMessage.find_all_unknown_mime_types + def self.find_all_unknown_mime_types for incoming_message in IncomingMessage.find(:all) for attachment in incoming_message.get_attachments_for_display raise "internal error incoming_message " + incoming_message.id.to_s if attachment.content_type.nil? @@ -745,16 +675,9 @@ class IncomingMessage < ActiveRecord::Base return ret.keys.join(" ") end # Return space separated list of all file extensions known - def IncomingMessage.get_all_file_extensions + def self.get_all_file_extensions return AlaveteliFileTypes.all_extensions.join(" ") end - - def for_admin_column - self.class.content_columns.each do |column| - yield(column.human_name, self.send(column.name), column.type.to_s, column.name) - end - end - end diff --git a/app/models/info_request.rb b/app/models/info_request.rb index c203f75c3..3ce0e3cd2 100644 --- a/app/models/info_request.rb +++ b/app/models/info_request.rb @@ -1,4 +1,4 @@ -# encoding: utf-8 +# -*- encoding : utf-8 -*- # == Schema Information # Schema version: 20131024114346 # @@ -28,8 +28,11 @@ require 'digest/sha1' class InfoRequest < ActiveRecord::Base + include AdminColumn include Rails.application.routes.url_helpers + @non_admin_columns = %w(title url_title) + strip_attributes! validates_presence_of :title, :message => N_("Please enter a summary of your request") @@ -50,6 +53,7 @@ class InfoRequest < ActiveRecord::Base has_many :info_request_events, :order => 'created_at' has_many :user_info_request_sent_alerts has_many :track_things, :order => 'created_at desc' + has_many :widget_votes has_many :comments, :order => 'created_at' has_many :censor_rules, :order => 'created_at desc' has_many :mail_server_logs, :order => 'mail_server_log_done_id' @@ -193,23 +197,12 @@ class InfoRequest < ActiveRecord::Base rescue MissingSourceFile, NameError end - # only check on create, so existing models with mixed case are allowed - def validate_on_create - 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 - OLD_AGE_IN_DAYS = 21.days def visible_comments - self.comments.find(:all, :conditions => 'visible') + warn %q([DEPRECATION] InfoRequest#visible_comments will be replaced with + InfoRequest#comments.visible as of 0.23).squish + comments.visible end # If the URL name has changed, then all request: queries will break unless @@ -345,7 +338,7 @@ public # only they are sent the email address with the has in it. (We don't check # the prefix and domain, as sometimes those change, or might be elided by # copying an email, and that doesn't matter) - def InfoRequest.find_by_incoming_email(incoming_email) + def self.find_by_incoming_email(incoming_email) id, hash = InfoRequest._extract_id_hash_from_email(incoming_email) if hash_from_id(id) == hash # Not using find(id) because we don't exception raised if nothing found @@ -355,7 +348,7 @@ public # Return list of info requests which *might* be right given email address # e.g. For the id-hash email addresses, don't match the hash. - def InfoRequest.guess_by_incoming_email(incoming_message) + def self.guess_by_incoming_email(incoming_message) guesses = [] # 1. Try to guess based on the email address(es) incoming_message.addresses.each do |address| @@ -367,7 +360,7 @@ public end # Internal function used by find_by_magic_email and guess_by_incoming_email - def InfoRequest._extract_id_hash_from_email(incoming_email) + def self._extract_id_hash_from_email(incoming_email) # Match case insensitively, FOI officers often write Request with capital R. incoming_email = incoming_email.downcase @@ -394,7 +387,7 @@ public # repeated requests, say once a quarter for time information, then might need to do that. # TODO: this *should* also check outgoing message joined to is an initial # request (rather than follow up) - def InfoRequest.find_existing(title, public_body_id, body) + def self.find_existing(title, public_body_id, body) return InfoRequest.find(:first, :conditions => [ "title = ? and public_body_id = ? and outgoing_messages.body = ?", title, public_body_id, body ], :include => [ :outgoing_messages ] ) end @@ -534,7 +527,7 @@ public # The "holding pen" is a special request which stores incoming emails whose # destination request is unknown. - def InfoRequest.holding_pen_request + def self.holding_pen_request ir = InfoRequest.find_by_url_title("holding_pen") if ir.nil? ir = InfoRequest.new( @@ -549,7 +542,7 @@ public :status => 'ready', :message_type => 'initial_request', :body => 'This is the holding pen request. It shows responses that were sent to invalid addresses, and need moving to the correct request by an adminstrator.', - :last_sent_at => Time.now(), + :last_sent_at => Time.now, :what_doing => 'normal_sort' }) @@ -564,7 +557,7 @@ 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 + def self.requires_admin_states return ['requires_admin', 'error_message', 'attention_requested'] end @@ -665,11 +658,11 @@ public if !curr_state.nil? && event.event_type == 'response' if event.calculated_state != curr_state event.calculated_state = curr_state - event.last_described_at = Time.now() + event.last_described_at = Time.now event.save! end if event.last_described_at.nil? # TODO: actually maybe this isn't needed - event.last_described_at = Time.now() + event.last_described_at = Time.now event.save! end curr_state = nil @@ -681,7 +674,7 @@ public # indexed. if event.calculated_state != event.described_state event.calculated_state = event.described_state - event.last_described_at = Time.now() + event.last_described_at = Time.now event.save! end @@ -698,7 +691,7 @@ public # case there is a preceding response that the described state should be applied to. if event.calculated_state != event.described_state event.calculated_state = event.described_state - event.last_described_at = Time.now() + event.last_described_at = Time.now event.save! end end @@ -783,7 +776,14 @@ public end def public_response_events - self.info_request_events.select{|e| e.response? && e.incoming_message.all_can_view? } + condition = <<-SQL + info_request_events.event_type = ? + AND incoming_messages.prominence = ? + SQL + + info_request_events. + joins(:incoming_message). + where(condition, 'response', 'normal') end # The last public response is the default one people might want to reply to @@ -810,8 +810,9 @@ public # Text from the the initial request, for use in summary display def initial_request_text - return '' if outgoing_messages.empty? # mainly for use with incomplete fixtures - outgoing_messages.first.get_text_for_indexing + return '' if outgoing_messages.empty? + body_opts = { :censor_rules => applicable_censor_rules } + outgoing_messages.first.try(:get_text_for_indexing, true, body_opts) or '' end # Returns index of last event which is described or nil if none described. @@ -873,7 +874,7 @@ public # Display version of status - def InfoRequest.get_status_description(status) + def self.get_status_description(status) descriptions = { 'waiting_classification' => _("Awaiting classification."), 'waiting_response' => _("Awaiting response."), @@ -908,21 +909,24 @@ public # Completely delete this request and all objects depending on it def fully_destroy - self.track_things.each do |track_thing| + track_things.each do |track_thing| track_thing.track_things_sent_emails.each { |a| a.destroy } track_thing.destroy end - self.user_info_request_sent_alerts.each { |a| a.destroy } - self.info_request_events.each do |info_request_event| + user_info_request_sent_alerts.each { |a| a.destroy } + info_request_events.each do |info_request_event| info_request_event.track_things_sent_emails.each { |a| a.destroy } info_request_event.destroy end - self.mail_server_logs.each do |mail_server_log| + mail_server_logs.each do |mail_server_log| mail_server_log.destroy end - self.outgoing_messages.each { |a| a.destroy } - self.incoming_messages.each { |a| a.destroy } - self.destroy + outgoing_messages.each { |a| a.destroy } + incoming_messages.each { |a| a.destroy } + comments.each { |comment| comment.destroy } + censor_rules.each{ |censor_rule| censor_rule.destroy } + + destroy end # Called by incoming_email - and used to be called to generate separate @@ -932,7 +936,7 @@ public return InfoRequest.magic_email_for_id(prefix_part, self.id) end - def InfoRequest.magic_email_for_id(prefix_part, id) + def self.magic_email_for_id(prefix_part, id) magic_email = AlaveteliConfiguration::incoming_email_prefix magic_email += prefix_part + id.to_s magic_email += "-" + InfoRequest.hash_from_id(id) @@ -946,7 +950,7 @@ public self.idhash = InfoRequest.hash_from_id(self.id) end - def InfoRequest.create_from_attributes(info_request_atts, outgoing_message_atts, user=nil) + def self.create_from_attributes(info_request_atts, outgoing_message_atts, user=nil) info_request = new(info_request_atts) default_message_params = { :status => 'ready', @@ -960,12 +964,12 @@ public info_request end - def InfoRequest.hash_from_id(id) + def self.hash_from_id(id) return Digest::SHA1.hexdigest(id.to_s + AlaveteliConfiguration::incoming_email_secret)[0,8] end # Used to find when event last changed - def InfoRequest.last_event_time_clause(event_type=nil, join_table=nil, join_clause=nil) + def self.last_event_time_clause(event_type=nil, join_table=nil, join_clause=nil) event_type_clause = '' event_type_clause = " AND info_request_events.event_type = '#{event_type}'" if event_type tables = ['info_request_events'] @@ -980,20 +984,20 @@ public LIMIT 1)" end - def InfoRequest.last_public_response_clause() + def self.last_public_response_clause join_clause = "incoming_messages.id = info_request_events.incoming_message_id AND incoming_messages.prominence = 'normal'" last_event_time_clause('response', 'incoming_messages', join_clause) end - def InfoRequest.old_unclassified_params(extra_params, include_last_response_time=false) - last_response_created_at = last_public_response_clause() + def self.old_unclassified_params(extra_params, include_last_response_time=false) + last_response_created_at = last_public_response_clause age = extra_params[:age_in_days] ? extra_params[:age_in_days].days : OLD_AGE_IN_DAYS params = { :conditions => ["awaiting_description = ? AND #{last_response_created_at} < ? AND url_title != 'holding_pen' AND user_id IS NOT NULL", - true, Time.now() - age] } + true, Time.now - age] } if include_last_response_time params[:select] = "*, #{last_response_created_at} AS last_response_time" params[:order] = 'last_response_time' @@ -1001,29 +1005,21 @@ public return params end - def InfoRequest.count_old_unclassified(extra_params={}) + def self.count_old_unclassified(extra_params={}) params = old_unclassified_params(extra_params) - if extra_params[:conditions] - condition_string = extra_params[:conditions].shift - params[:conditions][0] += " AND #{condition_string}" - params[:conditions] += extra_params[:conditions] - end + add_conditions_from_extra_params(params, extra_params) count(:all, params) end - def InfoRequest.get_random_old_unclassified(limit, extra_params) + def self.get_random_old_unclassified(limit, extra_params) params = old_unclassified_params({}) - if extra_params[:conditions] - condition_string = extra_params[:conditions].shift - params[:conditions][0] += " AND #{condition_string}" - params[:conditions] += extra_params[:conditions] - end + add_conditions_from_extra_params(params, extra_params) params[:limit] = limit params[:order] = "random()" find(:all, params) end - def InfoRequest.find_old_unclassified(extra_params={}) + def self.find_old_unclassified(extra_params={}) params = old_unclassified_params(extra_params, include_last_response_time=true) [:limit, :include, :offset].each do |extra| params[extra] = extra_params[extra] if extra_params[extra] @@ -1032,15 +1028,11 @@ public params[:order] = extra_params[:order] params.delete(:select) end - if extra_params[:conditions] - condition_string = extra_params[:conditions].shift - params[:conditions][0] += " AND #{condition_string}" - params[:conditions] += extra_params[:conditions] - end + add_conditions_from_extra_params(params, extra_params) find(:all, params) end - def InfoRequest.download_zip_dir() + def self.download_zip_dir File.join(Rails.root, "cache", "zips", "#{Rails.env}") end @@ -1058,7 +1050,7 @@ public end def request_dirs - first_three_digits = id.to_s()[0..2] + first_three_digits = id.to_s[0..2] File.join(first_three_digits.to_s, id.to_s) end @@ -1067,7 +1059,7 @@ public end def make_zip_cache_path(user) - cache_file_dir = File.join(InfoRequest.download_zip_dir(), + cache_file_dir = File.join(InfoRequest.download_zip_dir, "download", request_dirs, last_update_hash) @@ -1191,7 +1183,7 @@ public end # This is called from cron regularly. - def InfoRequest.stop_new_responses_on_old_requests + def self.stop_new_responses_on_old_requests # 6 months since last change to request, only allow new incoming messages from authority domains InfoRequest.update_all "allow_new_responses_from = 'authority_only' where updated_at < (now() - interval '6 months') and allow_new_responses_from = 'anybody' and url_title <> 'holding_pen'" # 1 year since last change requests, don't allow any new incoming messages @@ -1240,7 +1232,7 @@ public :model => self.class.base_class.to_s, :model_id => self.id) end - req.save() + req.save end end @@ -1270,13 +1262,6 @@ public PublicBody.set_callback(:save, :after, :purge_in_cache) end - def for_admin_column - self.class.content_columns.map{|c| c unless %w(title url_title).include?(c.name) }.compact.each do |column| - yield(column.human_name, self.send(column.name), column.type.to_s, column.name) - end - end - - # Get requests that have similar important terms def similar_requests(limit=10) xapian_similar = nil @@ -1292,7 +1277,7 @@ public return [xapian_similar, xapian_similar_more] end - def InfoRequest.request_list(filters, page, per_page, max_results) + def self.request_list(filters, page, per_page, max_results) xapian_object = ActsAsXapian::Search.new([InfoRequestEvent], InfoRequestEvent.make_query_from_params(filters), :offset => (page - 1) * per_page, @@ -1309,7 +1294,7 @@ public :show_no_more_than => show_no_more_than } end - def InfoRequest.recent_requests + def self.recent_requests request_events = [] request_events_all_successful = false # Get some successful requests @@ -1356,12 +1341,45 @@ public return [request_events, request_events_all_successful] end - def InfoRequest.find_in_state(state) + def self.find_in_state(state) select("*, #{ last_event_time_clause } as last_event_time"). where(:described_state => state). order('last_event_time') end + def move_to_public_body(destination_public_body, opts = {}) + old_body = public_body + editor = opts.fetch(:editor) + + attrs = { :public_body => destination_public_body } + + if destination_public_body + attrs.merge!({ + :law_used => destination_public_body.law_only_short.downcase + }) + end + + if update_attributes(attrs) + log_event('move_request', + :editor => editor, + :public_body_url_name => public_body.url_name, + :old_public_body_url_name => old_body.url_name) + + reindex_request_events + + public_body + end + end + + # The DateTime of the last InfoRequestEvent belonging to the InfoRequest + # Only available if the last_event_time attribute has been set. This is + # currentlt only set through .find_in_state + # + # Returns a DateTime + def last_event_time + attributes['last_event_time'].try(:to_datetime) + end + private def set_defaults @@ -1370,11 +1388,12 @@ public self.described_state = 'waiting_response' end rescue ActiveModel::MissingAttributeError - # this should only happen on Model.exists?() call. It can be safely ignored. + # 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? + if new_record? && public_body && public_body.eir_only? self.law_used = 'eir' end end @@ -1390,5 +1409,13 @@ public 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 + + def self.add_conditions_from_extra_params(params, extra_params) + if extra_params[:conditions] + condition_string = extra_params[:conditions].shift + params[:conditions][0] += " AND #{condition_string}" + params[:conditions] += extra_params[:conditions] + end + end end diff --git a/app/models/info_request_batch.rb b/app/models/info_request_batch.rb index 8a5ebeaba..684467c61 100644 --- a/app/models/info_request_batch.rb +++ b/app/models/info_request_batch.rb @@ -1,3 +1,4 @@ +# -*- encoding : utf-8 -*- # == Schema Information # # Table name: info_request_batches @@ -21,7 +22,7 @@ class InfoRequestBatch < ActiveRecord::Base validates_presence_of :body # When constructing a new batch, use this to check user hasn't double submitted. - def InfoRequestBatch.find_existing(user, title, body, public_body_ids) + def self.find_existing(user, title, body, public_body_ids) find(:first, :conditions => ['user_id = ? AND title = ? AND body = ? @@ -69,7 +70,7 @@ class InfoRequestBatch < ActiveRecord::Base info_request end - def InfoRequestBatch.send_batches() + def self.send_batches find_each(:conditions => "sent_at IS NULL") do |info_request_batch| unrequestable = info_request_batch.create_batch! mail_message = InfoRequestBatchMailer.batch_sent(info_request_batch, diff --git a/app/models/info_request_event.rb b/app/models/info_request_event.rb index 635ba8f58..263de20a0 100644 --- a/app/models/info_request_event.rb +++ b/app/models/info_request_event.rb @@ -1,3 +1,4 @@ +# -*- encoding : utf-8 -*- # == Schema Information # # Table name: info_request_events @@ -21,7 +22,7 @@ # Email: hello@mysociety.org; WWW: http://www.mysociety.org/ class InfoRequestEvent < ActiveRecord::Base - + include AdminColumn extend XapianQueries belongs_to :info_request @@ -278,9 +279,15 @@ class InfoRequestEvent < ActiveRecord::Base end self.params_yaml = params.to_yaml end + def params - YAML.load(self.params_yaml) + param_hash = YAML.load(params_yaml) + param_hash.each do |key, value| + param_hash[key] = value.force_encoding('UTF-8') if value.respond_to?(:force_encoding) + end + param_hash end + def params_yaml_as_html ret = '' # split out parameters into old/new diffs, and other ones @@ -319,9 +326,17 @@ class InfoRequestEvent < ActiveRecord::Base end - def is_incoming_message?() not self.incoming_message_selective_columns("incoming_messages.id").nil? end - def is_outgoing_message?() not self.outgoing_message.nil? end - def is_comment?() not self.comment.nil? end + def is_incoming_message? + incoming_message_id? or (incoming_message if new_record?) + end + + def is_outgoing_message? + outgoing_message_id? or (outgoing_message if new_record?) + end + + def is_comment? + comment_id? or (comment if new_record?) + end # Display version of status def display_status @@ -412,11 +427,4 @@ class InfoRequestEvent < ActiveRecord::Base return ret end - - def for_admin_column - self.class.content_columns.each do |column| - yield(column.human_name, self.send(column.name), column.type.to_s, column.name) - end - end - end diff --git a/app/models/mail_server_log.rb b/app/models/mail_server_log.rb index 07d2fdac0..64a740e1d 100644 --- a/app/models/mail_server_log.rb +++ b/app/models/mail_server_log.rb @@ -1,3 +1,4 @@ +# -*- encoding : utf-8 -*- # == Schema Information # # Table name: mail_server_logs @@ -25,7 +26,7 @@ class MailServerLog < ActiveRecord::Base # Doesn't do anything if file hasn't been modified since it was last loaded. # Note: If you do use rotated log files (rather than files named by date), at some # point old loaded log lines will get deleted in the database. - def MailServerLog.load_file(file_name) + def self.load_file(file_name) is_gz = file_name.include?(".gz") file_name_db = is_gz ? file_name.gsub(".gz", "") : file_name @@ -62,23 +63,16 @@ class MailServerLog < ActiveRecord::Base end # Scan the file - def MailServerLog.load_exim_log_data(f, done) + def self.load_exim_log_data(f, done) order = 0 f.each do |line| order = order + 1 emails = email_addresses_on_line(line) - for email in emails - info_request = InfoRequest.find_by_incoming_email(email) - if info_request - info_request.mail_server_logs.create!(:line => line, :order => order, :mail_server_log_done => done) - else - puts "Warning: Could not find request with email #{email}" - end - end + create_mail_server_logs(emails, line, order, done) end end - def MailServerLog.load_postfix_log_data(f, done) + def self.load_postfix_log_data(f, done) order = 0 emails = scan_for_postfix_queue_ids(f) # Go back to the beginning of the file @@ -87,19 +81,12 @@ class MailServerLog < ActiveRecord::Base order = order + 1 queue_id = extract_postfix_queue_id_from_syslog_line(line) if emails.has_key?(queue_id) - emails[queue_id].each do |email| - info_request = InfoRequest.find_by_incoming_email(email) - if info_request - info_request.mail_server_logs.create!(:line => line, :order => order, :mail_server_log_done => done) - else - puts "Warning: Could not find request with email #{email}" - end - end + create_mail_server_logs(emails[queue_id], line, order, done) end end end - def MailServerLog.scan_for_postfix_queue_ids(f) + def self.scan_for_postfix_queue_ids(f) result = {} f.each do |line| emails = email_addresses_on_line(line) @@ -111,7 +98,7 @@ class MailServerLog < ActiveRecord::Base end # Retuns nil if there is no queue id - def MailServerLog.extract_postfix_queue_id_from_syslog_line(line) + def self.extract_postfix_queue_id_from_syslog_line(line) # Assume the log file was written using syslog and parse accordingly m = SyslogProtocol.parse("<13>" + line).content.match(/^\S+: (\S+):/) m[1] if m @@ -119,13 +106,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) + def self.email_addresses_on_line(line) 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) + def self.request_sent?(ir) case(AlaveteliConfiguration::mta_log_type.to_sym) when :exim request_exim_sent?(ir) @@ -137,7 +124,7 @@ class MailServerLog < ActiveRecord::Base end # Look at the log for a request and check that an email was delivered - def MailServerLog.request_exim_sent?(ir) + def self.request_exim_sent?(ir) # Look for line showing request was sent found = false ir.mail_server_logs.each do |mail_server_log| @@ -156,7 +143,7 @@ class MailServerLog < ActiveRecord::Base found end - def MailServerLog.request_postfix_sent?(ir) + def self.request_postfix_sent?(ir) # dsn=2.0.0 is the magic word that says that postfix delivered the email # See http://tools.ietf.org/html/rfc3464 ir.mail_server_logs.any? { |l| l.line.include?("dsn=2.0.0") } @@ -173,11 +160,11 @@ class MailServerLog < ActiveRecord::Base # NB: There can be several emails involved in a request. This just checks that # at least one of them has been succesfully sent. # - def MailServerLog.check_recent_requests_have_been_sent + def self.check_recent_requests_have_been_sent # Get all requests sent for from 2 to 10 days ago. The 2 day gap is # because we load mail server log lines via cron at best an hour after they # are made) - irs = InfoRequest.find(:all, :conditions => [ "created_at < ? and created_at > ? and user_id is not null", Time.now() - 2.day, Time.now() - 10.days ] ) + irs = InfoRequest.find(:all, :conditions => [ "created_at < ? and created_at > ? and user_id is not null", Time.now - 2.day, Time.now - 10.days ] ) # Go through each request and check it ok = true @@ -192,7 +179,17 @@ class MailServerLog < ActiveRecord::Base ok end -end - + private + def self.create_mail_server_logs(emails, line, order, done) + emails.each do |email| + info_request = InfoRequest.find_by_incoming_email(email) + if info_request + info_request.mail_server_logs.create!(:line => line, :order => order, :mail_server_log_done => done) + else + puts "Warning: Could not find request with email #{email}" + end + end + end +end diff --git a/app/models/mail_server_log_done.rb b/app/models/mail_server_log_done.rb index 1bbb23ac4..542c3db3a 100644 --- a/app/models/mail_server_log_done.rb +++ b/app/models/mail_server_log_done.rb @@ -1,3 +1,4 @@ +# -*- encoding : utf-8 -*- # == Schema Information # # Table name: mail_server_log_dones diff --git a/app/models/outgoing_message.rb b/app/models/outgoing_message.rb index c2c8ef4f2..2e1b27fba 100644 --- a/app/models/outgoing_message.rb +++ b/app/models/outgoing_message.rb @@ -1,3 +1,4 @@ +# -*- encoding : utf-8 -*- # == Schema Information # Schema version: 20131024114346 # @@ -25,6 +26,7 @@ # Email: hello@mysociety.org; WWW: http://www.mysociety.org/ class OutgoingMessage < ActiveRecord::Base + include AdminColumn extend MessageProminence include Rails.application.routes.url_helpers include LinkToHelper @@ -140,22 +142,28 @@ class OutgoingMessage < ActiveRecord::Base end end - def body - ret = read_attribute(:body) - if ret.nil? - return ret + # Public: The body text of the OutgoingMessage. The text is cleaned and + # CensorRules are applied. + # + # options - Hash of options + # :censor_rules - Array of CensorRules to apply. Defaults to the + # applicable_censor_rules of the associated + # InfoRequest. (optional) + # + # Returns a String + def body(options = {}) + text = raw_body.dup + return text if text.nil? + + text = clean_text(text) + + # Use the given censor_rules; otherwise fetch them from the associated + # info_request + censor_rules = options.fetch(:censor_rules) do + info_request.try(:applicable_censor_rules) or [] end - ret = ret.dup - ret.strip! - ret.gsub!(/(?:\n\s*){2,}/, "\n\n") # remove excess linebreaks that unnecessarily space it out - - # Remove things from censor rules - unless info_request.nil? - self.info_request.apply_censor_rules_to_text!(ret) - end - - ret + censor_rules.reduce(text) { |text, rule| rule.apply_to_text(text) } end def raw_body @@ -227,8 +235,12 @@ class OutgoingMessage < ActiveRecord::Base end # Returns text for indexing / text display - def get_text_for_indexing(strip_salutation = true) - text = body.strip + def get_text_for_indexing(strip_salutation = true, opts = {}) + if opts.empty? + text = body.strip + else + text = body(opts).strip + end # Remove salutation text.sub!(/Dear .+,/, "") if strip_salutation @@ -272,12 +284,6 @@ class OutgoingMessage < ActiveRecord::Base info_request.purge_in_cache end - def for_admin_column - self.class.content_columns.each do |column| - yield(column.human_name, self.send(column.name), column.type.to_s, column.name) - end - end - def xapian_reindex_after_update if changes.include?('body') info_request_events.each do |event| @@ -332,6 +338,11 @@ class OutgoingMessage < ActiveRecord::Base errors.add(:what_doing_dummy, _('Please choose what sort of reply you are making.')) end end + + # remove excess linebreaks that unnecessarily space it out + def clean_text(text) + text.strip.gsub(/(?:\n\s*){2,}/, "\n\n") + end end diff --git a/app/models/post_redirect.rb b/app/models/post_redirect.rb index 8049349d0..3cdc69995 100644 --- a/app/models/post_redirect.rb +++ b/app/models/post_redirect.rb @@ -1,3 +1,4 @@ +# -*- encoding : utf-8 -*- # == Schema Information # # Table name: post_redirects @@ -71,7 +72,11 @@ class PostRedirect < ActiveRecord::Base end def reason_params - YAML.load(reason_params_yaml) + param_hash = YAML.load(reason_params_yaml) + param_hash.each do |key, value| + param_hash[key] = value.force_encoding('UTF-8') if value.respond_to?(:force_encoding) + end + param_hash end # Extract just local path part, without domain or # diff --git a/app/models/profile_photo.rb b/app/models/profile_photo.rb index 61f88faf3..94edc2967 100644 --- a/app/models/profile_photo.rb +++ b/app/models/profile_photo.rb @@ -1,3 +1,4 @@ +# -*- encoding : utf-8 -*- # == Schema Information # # Table name: profile_photos diff --git a/app/models/public_body.rb b/app/models/public_body.rb index 232c0ffa1..ac924a941 100644 --- a/app/models/public_body.rb +++ b/app/models/public_body.rb @@ -1,4 +1,4 @@ -# -*- coding: utf-8 -*- +# -*- encoding : utf-8 -*- # == Schema Information # Schema version: 20131024114346 # @@ -32,7 +32,32 @@ require 'securerandom' require 'set' class PublicBody < ActiveRecord::Base - strip_attributes! + include AdminColumn + + class ImportCSVDryRun < StandardError ; end + + @non_admin_columns = %w(name last_edit_comment) + + attr_accessor :no_xapian_reindex + + # Default fields available for importing from CSV, in the format + # [field_name, 'short description of field (basic html allowed)'] + cattr_accessor :csv_import_fields do + [ + ['name', '(i18n)<strong>Existing records cannot be renamed</strong>'], + ['short_name', '(i18n)'], + ['request_email', '(i18n)'], + ['notes', '(i18n)'], + ['publication_scheme', '(i18n)'], + ['disclosure_log', '(i18n)'], + ['home_page', ''], + ['tag_string', '(tags separated by spaces)'], + ] + end + + has_many :info_requests, :order => 'created_at desc' + has_many :track_things, :order => 'created_at desc' + has_many :censor_rules, :order => 'created_at desc' validates_presence_of :name, :message => N_("Name can't be blank") validates_presence_of :url_name, :message => N_("URL name can't be blank") @@ -43,19 +68,12 @@ class PublicBody < ActiveRecord::Base validate :request_email_if_requestable - has_many :info_requests, :order => 'created_at desc' - has_many :track_things, :order => 'created_at desc' - has_many :censor_rules, :order => 'created_at desc' - attr_accessor :no_xapian_reindex - - has_tag_string - - before_save :set_api_key, - :set_default_publication_scheme, - :set_first_letter + before_save :set_api_key!, :unless => :api_key + before_save :set_default_publication_scheme after_save :purge_in_cache after_update :reindex_requested_from + # Every public body except for the internal admin one is visible scope :visible, lambda { { @@ -63,39 +81,83 @@ class PublicBody < ActiveRecord::Base } } + acts_as_versioned + acts_as_xapian :texts => [:name, :short_name, :notes], + :values => [ + # for sorting + [:created_at_numeric, 1, "created_at", :number] + ], + :terms => [ + [:variety, 'V', "variety"], + [:tag_array_for_search, 'U', "tag"] + ] + has_tag_string + strip_attributes! translates :name, :short_name, :request_email, :url_name, :notes, :first_letter, :publication_scheme - accepts_nested_attributes_for :translations, :reject_if => :empty_translation_in_params? - # Default fields available for importing from CSV, in the format - # [field_name, 'short description of field (basic html allowed)'] - cattr_accessor :csv_import_fields do - [ - ['name', '(i18n)<strong>Existing records cannot be renamed</strong>'], - ['short_name', '(i18n)'], - ['request_email', '(i18n)'], - ['notes', '(i18n)'], - ['publication_scheme', '(i18n)'], - ['disclosure_log', '(i18n)'], - ['home_page', ''], - ['tag_string', '(tags separated by spaces)'], - ] - end + # Cannot be grouped at top as it depends on the `translates` macro + include Translatable - acts_as_xapian :texts => [ :name, :short_name, :notes ], - :values => [ - [ :created_at_numeric, 1, "created_at", :number ] # for sorting - ], - :terms => [ [ :variety, 'V', "variety" ], - [ :tag_array_for_search, 'U', "tag" ] - ] + # Cannot be grouped at top as it depends on the `translates` macro + include PublicBodyDerivedFields + + # Cannot be grouped at top as it depends on the `translates` macro + class Translation + include PublicBodyDerivedFields + end - acts_as_versioned self.non_versioned_columns << 'created_at' << 'updated_at' << 'first_letter' << 'api_key' self.non_versioned_columns << 'info_requests_count' << 'info_requests_successful_count' self.non_versioned_columns << 'info_requests_count' << 'info_requests_visible_classified_count' self.non_versioned_columns << 'info_requests_not_held_count' << 'info_requests_overdue' self.non_versioned_columns << 'info_requests_overdue_count' + # Cannot be defined directly under `include` statements as this is opening + # the PublicBody::Version class dynamically defined by the + # `acts_as_versioned` macro. + # + # TODO: acts_as_versioned accepts an extend parameter [1] so these methods + # could be extracted to a module: + # + # acts_as_versioned :extend => PublicBodyVersionExtensions + # + # This includes the module in both the parent class (PublicBody) and the + # Version class (PublicBody::Version), so the behaviour is slightly + # different to opening up PublicBody::Version. + # + # We could add an `extend_version_class` option pretty trivially by + # following the pattern for the existing `extend` option. + # + # [1] http://git.io/vIetK + class Version + def last_edit_comment_for_html_display + text = self.last_edit_comment.strip + text = CGI.escapeHTML(text) + text = MySociety::Format.make_clickable(text) + text = text.gsub(/\n/, '<br>') + return text + end + + def compare(previous = nil) + if previous.nil? + yield([]) + else + v = self + changes = self.class.content_columns.inject([]) {|memo, c| + unless %w(version last_edit_editor last_edit_comment updated_at).include?(c.name) + from = previous.send(c.name) + to = self.send(c.name) + memo << { :name => c.human_name, :from => from, :to => to } if from != to + end + memo + } + changes.each do |change| + yield(change) + end + end + end + end + # Public: Search for Public Bodies whose name, short_name, request_email or # tags contain the given query # @@ -124,97 +186,62 @@ class PublicBody < ActiveRecord::Base uniq end - # Convenience methods for creating/editing translations via forms - def find_translation_by_locale(locale) - self.translations.find_by_locale(locale) - end - - # TODO: - Don't like repeating this! - def calculate_cached_fields(t) - PublicBody.set_first_letter(t) - short_long_name = t.name - short_long_name = t.short_name if t.short_name and !t.short_name.empty? - t.url_name = MySociety::Format.simplify_url_part(short_long_name, 'body') - end - - # Set the first letter on a public body or translation - def PublicBody.set_first_letter(instance) - unless instance.name.nil? or instance.name.empty? - # we use a regex to ensure it works with utf-8/multi-byte - first_letter = Unicode.upcase instance.name.scan(/^./mu)[0] - if first_letter != instance.first_letter - instance.first_letter = first_letter - end - end - end - - def translated_versions - translations - end - - def ordered_translations - translations. - select { |t| I18n.available_locales.include?(t.locale) }. - sort_by { |t| I18n.available_locales.index(t.locale) } + def set_api_key + set_api_key! if api_key.nil? end - def build_all_translations - I18n.available_locales.each do |locale| - translations.build(:locale => locale) unless translations.detect{ |t| t.locale == locale } - end + def set_api_key! + self.api_key = SecureRandom.base64(33) end - def translated_versions=(translation_attrs) - warn "[DEPRECATION] PublicBody#translated_versions= will be replaced " \ - "by PublicBody#translations_attributes= as of release 0.22" - self.translations_attributes = translation_attrs - end + # like find_by_url_name but also search historic url_name if none found + def self.find_by_url_name_with_historic(name) + # If many bodies are found (usually because the url_name is the same + # across locales) return any of them. + found = joins(:translations). + where("public_body_translations.url_name = ?", name). + readonly(false). + first - def set_default_publication_scheme - # Make sure publication_scheme gets the correct default value. - # (This would work automatically, were publication_scheme not a translated attribute) - self.publication_scheme = "" if self.publication_scheme.nil? - end + return found if found - def set_api_key - self.api_key = SecureRandom.base64(33) if self.api_key.nil? - end + # If none found, then search the history of short names and find unique + # public bodies in it + old = PublicBody::Version. + where(:url_name => name). + pluck('DISTINCT public_body_id') - # like find_by_url_name but also search historic url_name if none found - def self.find_by_url_name_with_historic(name) - found = PublicBody.find(:all, - :conditions => ["public_body_translations.url_name=?", name], - :joins => :translations, - :readonly => false) - # If many bodies are found (usually because the url_name is the same across - # locales) return any of them - return found.first if found.size >= 1 - - # If none found, then search the history of short names - old = PublicBody::Version.find_all_by_url_name(name) - # Find unique public bodies in it - old = old.map { |x| x.public_body_id } - old = old.uniq # Maybe return the first one, so we show something relevant, # rather than throwing an error? raise "Two bodies with the same historical URL name: #{name}" if old.size > 1 return unless old.size == 1 # does acts_as_versioned provide a method that returns the current version? - return PublicBody.find(old.first) - end - - # Set the first letter, which is used for faster queries - def set_first_letter - PublicBody.set_first_letter(self) + PublicBody.find(old.first) end # If tagged "not_apply", then FOI/EIR no longer applies to authority at all def not_apply? - return self.has_tag?('not_apply') + has_tag?('not_apply') end + # If tagged "defunct", then the authority no longer exists at all def defunct? - return self.has_tag?('defunct') + has_tag?('defunct') + end + + # Are all requests to this body under the Environmental Information + # Regulations? + def eir_only? + has_tag?('eir_only') + end + + # Schools are allowed more time in holidays, so we change some wordings + def is_school? + has_tag?('school') + end + + def site_administration? + has_tag?('site_administration') end # Can an FOI (etc.) request be made to this body? @@ -233,102 +260,39 @@ class PublicBody < ActiveRecord::Base # Also used as not_followable_reason def not_requestable_reason - if self.defunct? - return 'defunct' - elsif self.not_apply? - return 'not_apply' + if defunct? + 'defunct' + elsif not_apply? + 'not_apply' elsif !has_request_email? - return 'bad_contact' + 'bad_contact' else raise "not_requestable_reason called with type that has no reason" end end def special_not_requestable_reason? - self.defunct? || self.not_apply? - end - - - class Version - - def last_edit_comment_for_html_display - text = self.last_edit_comment.strip - text = CGI.escapeHTML(text) - text = MySociety::Format.make_clickable(text) - text = text.gsub(/\n/, '<br>') - return text - end - - def compare(previous = nil) - if previous.nil? - yield([]) - else - v = self - changes = self.class.content_columns.inject([]) {|memo, c| - unless %w(version last_edit_editor last_edit_comment updated_at).include?(c.name) - from = previous.send(c.name) - to = self.send(c.name) - memo << { :name => c.human_name, :from => from, :to => to } if from != to - end - memo - } - changes.each do |change| - yield(change) - end - end - end + defunct? || not_apply? end 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") - end - def variety - return "authority" - end - - # if the URL name has changed, then all requested_from: queries - # will break unless we update index for every event for every - # request linked to it - def reindex_requested_from - if self.changes.include?('url_name') - for info_request in self.info_requests - - for info_request_event in info_request.info_request_events - info_request_event.xapian_mark_needs_index - end - end - end - end - - # When name or short name is changed, also change the url name - def short_name=(short_name) - globalize.write(Globalize.locale, :short_name, short_name) - self[:short_name] = short_name - self.update_url_name - end - - def name=(name) - globalize.write(Globalize.locale, :name, name) - self[:name] = name - self.update_url_name + created_at.strftime("%Y%m%d%H%M%S") end - def update_url_name - self.url_name = MySociety::Format.simplify_url_part(self.short_or_long_name, 'body') + def variety + "authority" end - # Return the short name if present, or else long name - def short_or_long_name - if self.short_name.nil? || self.short_name.empty? # 'nil' can happen during construction - self.name.nil? ? "" : self.name - else - self.short_name - end + def law_only_short + eir_only? ? 'EIR' : 'FOI' end # Guess home page from the request email, or use explicit override, or nil # if not known. + # + # TODO: PublicBody#calculated_home_page would be a good candidate to cache + # in an instance variable def calculated_home_page if home_page && !home_page.empty? home_page[URI::regexp(%w(http https))] ? home_page : "http://#{home_page}" @@ -337,25 +301,8 @@ class PublicBody < ActiveRecord::Base end end - # Are all requests to this body under the Environmental Information Regulations? - def eir_only? - return self.has_tag?('eir_only') - end - def law_only_short - if self.eir_only? - return "EIR" - else - return "FOI" - end - end - - # Schools are allowed more time in holidays, so we change some wordings - def is_school? - return self.has_tag?('school') - end - # The "internal admin" is a special body for internal use. - def PublicBody.internal_admin_body + def self.internal_admin_body # Use find_by_sql to avoid the search being specific to a # locale, since url_name is a translated field: sql = "SELECT * FROM public_bodies WHERE url_name = 'internal_admin_authority'" @@ -379,13 +326,6 @@ class PublicBody < ActiveRecord::Base end end - def site_administration? - has_tag?('site_administration') - end - - class ImportCSVDryRun < StandardError - end - # Import from a string in CSV format. # Just tests things and returns messages if dry_run is true. # Returns an array of [array of errors, array of notes]. If there @@ -412,7 +352,7 @@ class PublicBody < ActiveRecord::Base # matching names won't work afterwards, and we'll create new bodies instead # of updating them bodies_by_name = {} - set_of_existing = Set.new() + set_of_existing = Set.new internal_admin_body_id = PublicBody.internal_admin_body.id I18n.with_locale(I18n.default_locale) do bodies = (tag.nil? || tag.empty?) ? PublicBody.find(:all, :include => :translations) : PublicBody.find_by_tag(tag) @@ -425,7 +365,7 @@ class PublicBody < ActiveRecord::Base end end - set_of_importing = Set.new() + set_of_importing = Set.new # Default values in case no field list is given field_names = { 'name' => 1, 'request_email' => 2 } line = 0 @@ -565,40 +505,32 @@ class PublicBody < ActiveRecord::Base # Does this user have the power of FOI officer for this body? def is_foi_officer?(user) user_domain = user.email_domain - our_domain = self.request_email_domain - - if user_domain.nil? or our_domain.nil? - return false - end - - return our_domain == user_domain - end - def foi_officer_domain_required - return self.request_email_domain - end + our_domain = request_email_domain - # Returns nil if configuration variable not set - def override_request_email - e = AlaveteliConfiguration::override_all_public_body_request_emails - e if e != "" + return false if user_domain.nil? or our_domain.nil? + our_domain == user_domain end def request_email - if override_request_email - override_request_email - else + if AlaveteliConfiguration::override_all_public_body_request_emails.blank? || read_attribute(:request_email).blank? read_attribute(:request_email) + else + AlaveteliConfiguration::override_all_public_body_request_emails end end # Domain name of the request email def request_email_domain - return PublicBody.extract_domain_from_email(self.request_email) + PublicBody.extract_domain_from_email(request_email) end + alias_method :foi_officer_domain_required, :request_email_domain + # Return the domain part of an email address, canonicalised and with common # extra UK Government server name parts removed. - def PublicBody.extract_domain_from_email(email) + # + # TODO: Extract to library class + def self.extract_domain_from_email(email) email =~ /@(.*)/ if $1.nil? return nil @@ -615,51 +547,53 @@ class PublicBody < ActiveRecord::Base return ret end + # TODO: Could this be defined as `sorted_versions.reverse`? def reverse_sorted_versions - self.versions.sort { |a,b| b.version <=> a.version } + versions.sort { |a,b| b.version <=> a.version } end + def sorted_versions - self.versions.sort { |a,b| a.version <=> b.version } + versions.sort { |a,b| a.version <=> b.version } end def has_notes? - return !self.notes.nil? && self.notes != "" + !notes.nil? && notes != "" end + + # TODO: Deprecate this method. Its only used in a couple of views so easy to + # update to just call PublicBody#notes def notes_as_html - self.notes + notes end def notes_without_html - # assume notes are reasonably behaved HTML, so just use simple regexp on this - @notes_without_html ||= (self.notes.nil? ? '' : self.notes.gsub(/<\/?[^>]*>/, "")) + # assume notes are reasonably behaved HTML, so just use simple regexp + # on this + @notes_without_html ||= (notes.nil? ? '' : notes.gsub(/<\/?[^>]*>/, "")) end def json_for_api - return { - :id => self.id, - :url_name => self.url_name, - :name => self.name, - :short_name => self.short_name, - # :request_email # we hide this behind a captcha, to stop people doing bulk requests easily - :created_at => self.created_at, - :updated_at => self.updated_at, - # don't add the history as some edit comments contain sensitive information + { + :id => id, + :url_name => url_name, + :name => name, + :short_name => short_name, + # :request_email # we hide this behind a captcha, to stop people + # doing bulk requests easily + :created_at => created_at, + :updated_at => updated_at, + # don't add the history as some edit comments contain sensitive + # information # :version, :last_edit_editor, :last_edit_comment - :home_page => self.calculated_home_page, - :notes => self.notes, - :publication_scheme => self.publication_scheme, - :tags => self.tag_array, + :home_page => calculated_home_page, + :notes => notes, + :publication_scheme => publication_scheme, + :tags => tag_array, } end def purge_in_cache - self.info_requests.each {|x| x.purge_in_cache} - end - - def for_admin_column - self.class.content_columns.map{|c| c unless %w(name last_edit_comment).include?(c.name)}.compact.each do |column| - yield(column.human_name, self.send(column.name), column.type.to_s, column.name) - end + info_requests.each { |x| x.purge_in_cache } end def self.where_clause_for_stats(minimum_requests, total_column) @@ -732,6 +666,7 @@ class PublicBody < ActiveRecord::Base 'y_max' => 100, 'totals' => original_totals} end + def self.popular_bodies(locale) # get some example searches and public bodies to display # either from config, or based on a (slow!) query if not set @@ -758,6 +693,70 @@ class PublicBody < ActiveRecord::Base return bodies end + # Methods to privatise + # -------------------------------------------------------------------------- + + # TODO: This could be removed by updating the default value (to '') of the + # `publication_scheme` column in the `public_body_translations` table. + # + # TODO: Can't actually deprecate this because spec/script/mailin_spec.rb:28 + # fails due to the deprecation notice output + def set_default_publication_scheme + # warn %q([DEPRECATION] PublicBody#set_default_publication_scheme will + # become a private method in 0.23).squish + + # Make sure publication_scheme gets the correct default value. + # (This would work automatically, were publication_scheme not a + # translated attribute) + self.publication_scheme = "" if publication_scheme.nil? + end + + # if the URL name has changed, then all requested_from: queries + # will break unless we update index for every event for every + # request linked to it + # + # TODO: Can't actually deprecate this because spec/script/mailin_spec.rb:28 + # fails due to the deprecation notice output + def reindex_requested_from + # warn %q([DEPRECATION] PublicBody#reindex_requested_from will become a + # private method in 0.23).squish + + if changes.include?('url_name') + info_requests.each do |info_request| + info_request.info_request_events.each do |info_request_event| + info_request_event.xapian_mark_needs_index + end + end + end + end + + # Methods to remove + # -------------------------------------------------------------------------- + + # Set the first letter on a public body or translation + def self.set_first_letter(instance) + warn %q([DEPRECATION] PublicBody.set_first_letter will be removed + in 0.23).squish + + unless instance.name.nil? or instance.name.empty? + # we use a regex to ensure it works with utf-8/multi-byte + first_letter = Unicode.upcase instance.name.scan(/^./mu)[0] + if first_letter != instance.first_letter + instance.first_letter = first_letter + end + end + end + + def calculate_cached_fields(t) + warn %q([DEPRECATION] PublicBody#calculate_cached_fields will be removed + in 0.23).squish + + PublicBody.set_first_letter(t) + short_long_name = t.name + short_long_name = t.short_name if t.short_name and !t.short_name.empty? + t.url_name = MySociety::Format.simplify_url_part(short_long_name, 'body') + end + private # Read an attribute value (without using locale fallbacks if the attribute is translated) @@ -773,13 +772,6 @@ class PublicBody < ActiveRecord::Base end end - def empty_translation_in_params?(attributes) - attrs_with_values = attributes.select do |key, value| - value != '' and key.to_s != 'locale' - end - attrs_with_values.empty? - end - 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/public_body_category.rb b/app/models/public_body_category.rb index b88c683de..0a64172c1 100644 --- a/app/models/public_body_category.rb +++ b/app/models/public_body_category.rb @@ -1,3 +1,4 @@ +# -*- encoding : utf-8 -*- # == Schema Information # # Table name: public_body_categories @@ -17,13 +18,14 @@ class PublicBodyCategory < ActiveRecord::Base has_many :public_body_headings, :through => :public_body_category_links translates :title, :description - accepts_nested_attributes_for :translations, :reject_if => :empty_translation_in_params? validates_uniqueness_of :category_tag, :message => 'Tag is already taken' validates_presence_of :title, :message => "Title can't be blank" validates_presence_of :category_tag, :message => "Tag can't be blank" validates_presence_of :description, :message => "Description can't be blank" + include Translatable + def self.get locale = I18n.locale.to_s || default_locale.to_s || "" categories = CategoryCollection.new @@ -51,43 +53,6 @@ class PublicBodyCategory < ActiveRecord::Base ) | PublicBodyCategory.find_by_sql(sql) end - - # Convenience methods for creating/editing translations via forms - def find_translation_by_locale(locale) - translations.find_by_locale(locale) - end - - def translated_versions - translations - end - - def translated_versions=(translation_attrs) - warn "[DEPRECATION] PublicBodyCategory#translated_versions= will be replaced " \ - "by PublicBodyCategory#translations_attributes= as of release 0.22" - self.translations_attributes = translation_attrs - end - - def ordered_translations - translations. - select { |t| I18n.available_locales.include?(t.locale) }. - sort_by { |t| I18n.available_locales.index(t.locale) } - end - - def build_all_translations - I18n.available_locales.each do |locale| - translations.build(:locale => locale) unless translations.detect{ |t| t.locale == locale } - end - end - - private - - def empty_translation_in_params?(attributes) - attrs_with_values = attributes.select do |key, value| - value != '' and key.to_s != 'locale' - end - attrs_with_values.empty? - end - end PublicBodyCategory::Translation.class_eval do diff --git a/app/models/public_body_category/category_collection.rb b/app/models/public_body_category/category_collection.rb index 8286e2710..7d5732a82 100644 --- a/app/models/public_body_category/category_collection.rb +++ b/app/models/public_body_category/category_collection.rb @@ -1,3 +1,4 @@ +# -*- encoding : utf-8 -*- # replicate original file-based PublicBodyCategories functionality class PublicBodyCategory::CategoryCollection include Enumerable @@ -13,19 +14,19 @@ class PublicBodyCategory::CategoryCollection end def with_description - @categories.select() { |a| a.instance_of?(Array) } + @categories.select { |a| a.instance_of?(Array) } end def tags - tags = with_description.map() { |a| a[0] } + tags = with_description.map { |a| a[0] } end def by_tag - Hash[*with_description.map() { |a| a[0..1] }.flatten] + Hash[*with_description.map { |a| a[0..1] }.flatten] end def singular_by_tag - Hash[*with_description.map() { |a| [a[0],a[2]] }.flatten] + Hash[*with_description.map { |a| [a[0],a[2]] }.flatten] end def by_heading diff --git a/app/models/public_body_category_link.rb b/app/models/public_body_category_link.rb index 8c3eb8060..be73a9afa 100644 --- a/app/models/public_body_category_link.rb +++ b/app/models/public_body_category_link.rb @@ -1,3 +1,4 @@ +# -*- encoding : utf-8 -*- # == Schema Information # # Table name: public_body_category_links diff --git a/app/models/public_body_change_request.rb b/app/models/public_body_change_request.rb index 0e59cbecc..88a24dbd9 100644 --- a/app/models/public_body_change_request.rb +++ b/app/models/public_body_change_request.rb @@ -1,3 +1,4 @@ +# -*- encoding : utf-8 -*- # == Schema Information # # Table name: public_body_change_requests diff --git a/app/models/public_body_heading.rb b/app/models/public_body_heading.rb index 8c160ba8b..d49b388bb 100644 --- a/app/models/public_body_heading.rb +++ b/app/models/public_body_heading.rb @@ -1,3 +1,4 @@ +# -*- encoding : utf-8 -*- # == Schema Information # # Table name: public_body_headings @@ -15,7 +16,6 @@ class PublicBodyHeading < ActiveRecord::Base default_scope order('display_order ASC') translates :name - accepts_nested_attributes_for :translations, :reject_if => :empty_translation_in_params? validates_uniqueness_of :name, :message => 'Name is already taken' validates_presence_of :name, :message => 'Name can\'t be blank' @@ -28,32 +28,7 @@ class PublicBodyHeading < ActiveRecord::Base end end - # Convenience methods for creating/editing translations via forms - def find_translation_by_locale(locale) - translations.find_by_locale(locale) - end - - def translated_versions - translations - end - - def translated_versions=(translation_attrs) - warn "[DEPRECATION] PublicBodyHeading#translated_versions= will be replaced " \ - "by PublicBodyHeading#translations_attributes= as of release 0.22" - self.translations_attributes = translation_attrs - end - - def ordered_translations - translations. - select { |t| I18n.available_locales.include?(t.locale) }. - sort_by { |t| I18n.available_locales.index(t.locale) } - end - - def build_all_translations - I18n.available_locales.each do |locale| - translations.build(:locale => locale) unless translations.detect{ |t| t.locale == locale } - end - end + include Translatable def add_category(category) unless public_body_categories.include?(category) @@ -68,14 +43,4 @@ class PublicBodyHeading < ActiveRecord::Base 0 end end - - private - - def empty_translation_in_params?(attributes) - attrs_with_values = attributes.select do |key, value| - value != '' and key.to_s != 'locale' - end - attrs_with_values.empty? - end - end diff --git a/app/models/purge_request.rb b/app/models/purge_request.rb index 81980188d..39dae6a74 100644 --- a/app/models/purge_request.rb +++ b/app/models/purge_request.rb @@ -1,3 +1,4 @@ +# -*- encoding : utf-8 -*- # == Schema Information # # Table name: purge_requests diff --git a/app/models/raw_email.rb b/app/models/raw_email.rb index 907d3c7a0..58ae29a3b 100644 --- a/app/models/raw_email.rb +++ b/app/models/raw_email.rb @@ -1,3 +1,4 @@ +# -*- encoding : utf-8 -*- # == Schema Information # # Table name: raw_emails diff --git a/app/models/request_classification.rb b/app/models/request_classification.rb index 478a543d3..ab0cd1f21 100644 --- a/app/models/request_classification.rb +++ b/app/models/request_classification.rb @@ -1,3 +1,4 @@ +# -*- encoding : utf-8 -*- # == Schema Information # # Table name: request_classifications @@ -15,7 +16,7 @@ class RequestClassification < ActiveRecord::Base # return classification instances representing the top n # users, with a 'cnt' attribute representing the number # of classifications the user has made. - def RequestClassification.league_table(size, conditions=[]) + def self.league_table(size, conditions=[]) find(:all, :select => 'user_id, count(*) as cnt', :conditions => conditions, :group => 'user_id', diff --git a/app/models/spam_address.rb b/app/models/spam_address.rb index 2c84beaa0..893826a96 100644 --- a/app/models/spam_address.rb +++ b/app/models/spam_address.rb @@ -1,3 +1,4 @@ +# -*- encoding : utf-8 -*- # == Schema Information # # Table name: spam_addresses diff --git a/app/models/track_thing.rb b/app/models/track_thing.rb index cd90c4a9e..aad7cc51b 100644 --- a/app/models/track_thing.rb +++ b/app/models/track_thing.rb @@ -1,3 +1,4 @@ +# -*- encoding : utf-8 -*- # == Schema Information # # Table name: track_things diff --git a/app/models/track_things_sent_email.rb b/app/models/track_things_sent_email.rb index 072d3bdea..d4f3f3f04 100644 --- a/app/models/track_things_sent_email.rb +++ b/app/models/track_things_sent_email.rb @@ -1,3 +1,4 @@ +# -*- encoding : utf-8 -*- # == Schema Information # # Table name: track_things_sent_emails diff --git a/app/models/user.rb b/app/models/user.rb index 8fd7851e0..8c9e3c453 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -39,6 +39,7 @@ class User < ActiveRecord::Base has_one :profile_photo has_many :censor_rules, :order => 'created_at desc' has_many :info_request_batches, :order => 'created_at desc' + has_many :request_classifications validates_presence_of :email, :message => _("Please enter your email address") validates_presence_of :name, :message => _("Please enter your name") @@ -197,7 +198,9 @@ class User < ActiveRecord::Base end def visible_comments - comments.find(:all, :conditions => 'visible') + warn %q([DEPRECATION] User#visible_comments will be replaced with + User#comments.visible as of 0.23).squish + comments.visible end # Don't display any leading/trailing spaces @@ -301,12 +304,6 @@ class User < ActiveRecord::Base !ban_text.empty? end - def public_banned? - warn %q([DEPRECATION] User#public_banned? will be replaced with - User#banned? as of 0.22).squish - banned? - end - # Various ways the user can be banned, and text to describe it if failed def can_file_requests? ban_text.empty? && !exceeded_limit? diff --git a/app/models/user_info_request_sent_alert.rb b/app/models/user_info_request_sent_alert.rb index cd163d14b..e6a6405ef 100644 --- a/app/models/user_info_request_sent_alert.rb +++ b/app/models/user_info_request_sent_alert.rb @@ -1,3 +1,4 @@ +# -*- encoding : utf-8 -*- # == Schema Information # # Table name: user_info_request_sent_alerts diff --git a/app/models/widget_vote.rb b/app/models/widget_vote.rb new file mode 100644 index 000000000..dda77007f --- /dev/null +++ b/app/models/widget_vote.rb @@ -0,0 +1,20 @@ +# -*- encoding : utf-8 -*- +# == Schema Information +# +# Table name: widget_votes +# +# id :integer not null, primary key +# cookie :string(255) +# info_request_id :integer not null +# created_at :datetime not null +# updated_at :datetime not null +# + +class WidgetVote < ActiveRecord::Base + belongs_to :info_request + validates :info_request, :presence => true + + attr_accessible :cookie + validates :cookie, :length => { :is => 20 } + validates_uniqueness_of :cookie, :scope => :info_request_id +end |