diff app/models/mail_handler.rb @ 1464:261b3d9a4903 redmine-2.4

Update to Redmine 2.4 branch rev 12663
author Chris Cannam
date Tue, 14 Jan 2014 14:37:42 +0000
parents 3e4c3460b6ca
children e248c7af89ec
line wrap: on
line diff
--- a/app/models/mail_handler.rb	Fri Jun 14 09:05:06 2013 +0100
+++ b/app/models/mail_handler.rb	Tue Jan 14 14:37:42 2014 +0000
@@ -1,5 +1,5 @@
 # Redmine - project management software
-# Copyright (C) 2006-2012  Jean-Philippe Lang
+# Copyright (C) 2006-2013  Jean-Philippe Lang
 #
 # This program is free software; you can redistribute it and/or
 # modify it under the terms of the GNU General Public License
@@ -38,12 +38,27 @@
     # Status overridable by default
     @@handler_options[:allow_override] << 'status' unless @@handler_options[:issue].has_key?(:status)
 
-    @@handler_options[:no_permission_check] = (@@handler_options[:no_permission_check].to_s == '1' ? true : false)
+    @@handler_options[:no_account_notice] = (@@handler_options[:no_account_notice].to_s == '1')
+    @@handler_options[:no_notification] = (@@handler_options[:no_notification].to_s == '1')
+    @@handler_options[:no_permission_check] = (@@handler_options[:no_permission_check].to_s == '1')
 
     email.force_encoding('ASCII-8BIT') if email.respond_to?(:force_encoding)
     super(email)
   end
 
+  # Extracts MailHandler options from environment variables
+  # Use when receiving emails with rake tasks
+  def self.extract_options_from_env(env)
+    options = {:issue => {}}
+    %w(project status tracker category priority).each do |option|
+      options[:issue][option.to_sym] = env[option] if env[option]
+    end
+    %w(allow_override unknown_user no_permission_check no_account_notice default_group).each do |option|
+      options[option.to_sym] = env[option] if env[option]
+    end
+    options
+  end
+
   def logger
     Rails.logger
   end
@@ -61,7 +76,7 @@
     sender_email = email.from.to_a.first.to_s.strip
     # Ignore emails received from the application emission address to avoid hell cycles
     if sender_email.downcase == Setting.mail_from.to_s.strip.downcase
-      if logger && logger.info
+      if logger
         logger.info  "MailHandler: ignoring email from Redmine emission address [#{sender_email}]"
       end
       return false
@@ -72,7 +87,7 @@
       if value
         value = value.to_s.downcase
         if (ignored_value.is_a?(Regexp) && value.match(ignored_value)) || value == ignored_value
-          if logger && logger.info
+          if logger
             logger.info "MailHandler: ignoring email with #{key}:#{value} header"
           end
           return false
@@ -81,7 +96,7 @@
     end
     @user = User.find_by_mail(sender_email) if sender_email.present?
     if @user && !@user.active?
-      if logger && logger.info
+      if logger
         logger.info  "MailHandler: ignoring email from non-active user [#{@user.login}]"
       end
       return false
@@ -94,20 +109,23 @@
       when 'create'
         @user = create_user_from_email
         if @user
-          if logger && logger.info
+          if logger
             logger.info "MailHandler: [#{@user.login}] account created"
           end
-          Mailer.account_information(@user, @user.password).deliver
+          add_user_to_group(@@handler_options[:default_group])
+          unless @@handler_options[:no_account_notice]
+            Mailer.account_information(@user, @user.password).deliver
+          end
         else
-          if logger && logger.error
+          if logger
             logger.error "MailHandler: could not create account for [#{sender_email}]"
           end
           return false
         end
       else
         # Default behaviour, emails from unknown users are ignored
-        if logger && logger.info
-          logger.info  "MailHandler: ignoring email from unknown user [#{sender_email}]" 
+        if logger
+          logger.info  "MailHandler: ignoring email from unknown user [#{sender_email}]"
         end
         return false
       end
@@ -118,7 +136,7 @@
 
   private
 
-  MESSAGE_ID_RE = %r{^<?redmine\.([a-z0-9_]+)\-(\d+)\.\d+@}
+  MESSAGE_ID_RE = %r{^<?redmine\.([a-z0-9_]+)\-(\d+)\.\d+(\.[a-f0-9]+)?@}
   ISSUE_REPLY_SUBJECT_RE = %r{\[[^\]]*#(\d+)\]}
   MESSAGE_REPLY_SUBJECT_RE = %r{\[[^\]]*msg(\d+)\]}
 
@@ -177,7 +195,7 @@
     add_watchers(issue)
     issue.save!
     add_attachments(issue)
-    logger.info "MailHandler: issue ##{issue.id} created by #{user}" if logger && logger.info
+    logger.info "MailHandler: issue ##{issue.id} created by #{user}" if logger
     issue
   end
 
@@ -206,7 +224,7 @@
     journal.notes = cleaned_up_text_body
     add_attachments(issue)
     issue.save!
-    if logger && logger.info
+    if logger
       logger.info "MailHandler: issue ##{issue.id} updated by #{user}"
     end
     journal
@@ -239,7 +257,7 @@
         add_attachments(reply)
         reply
       else
-        if logger && logger.info
+        if logger
           logger.info "MailHandler: ignoring reply from [#{sender_email}] to a locked topic"
         end
       end
@@ -249,6 +267,7 @@
   def add_attachments(obj)
     if email.attachments && email.attachments.any?
       email.attachments.each do |attachment|
+        next unless accept_attachment?(attachment)
         obj.attachments << Attachment.create(:container => obj,
                           :file => attachment.decoded,
                           :filename => attachment.filename,
@@ -258,13 +277,26 @@
     end
   end
 
+  # Returns false if the +attachment+ of the incoming email should be ignored
+  def accept_attachment?(attachment)
+    @excluded ||= Setting.mail_handler_excluded_filenames.to_s.split(',').map(&:strip).reject(&:blank?)
+    @excluded.each do |pattern|
+      regexp = %r{\A#{Regexp.escape(pattern).gsub("\\*", ".*")}\z}i
+      if attachment.filename.to_s =~ regexp
+        logger.info "MailHandler: ignoring attachment #{attachment.filename} matching #{pattern}"
+        return false
+      end
+    end
+    true
+  end
+
   # Adds To and Cc as watchers of the given object if the sender has the
   # appropriate permission
   def add_watchers(obj)
     if user.allowed_to?("add_#{obj.class.name.underscore}_watchers".to_sym, obj.project)
       addresses = [email.to, email.cc].flatten.compact.uniq.collect {|a| a.strip.downcase}
       unless addresses.empty?
-        watchers = User.active.find(:all, :conditions => ['LOWER(mail) IN (?)', addresses])
+        watchers = User.active.where('LOWER(mail) IN (?)', addresses).all
         watchers.each {|w| obj.add_watcher(w)}
       end
     end
@@ -315,6 +347,13 @@
     # * parse the email To field
     # * specific project (eg. Setting.mail_handler_target_project)
     target = Project.find_by_identifier(get_keyword(:project))
+    if target.nil?
+      # Invalid project keyword, use the project specified as the default one
+      default_project = @@handler_options[:issue][:project]
+      if default_project.present?
+        target = Project.find_by_identifier(default_project)
+      end
+    end
     raise MissingInformation.new('Unable to determine target project') if target.nil?
     target
   end
@@ -338,7 +377,7 @@
     }.delete_if {|k, v| v.blank? }
 
     if issue.new_record? && attrs['tracker_id'].nil?
-      attrs['tracker_id'] = issue.project.trackers.find(:first).try(:id)
+      attrs['tracker_id'] = issue.project.trackers.first.try(:id)
     end
 
     attrs
@@ -359,12 +398,26 @@
   def plain_text_body
     return @plain_text_body unless @plain_text_body.nil?
 
-    part = email.text_part || email.html_part || email
-    @plain_text_body = Redmine::CodesetUtil.to_utf8(part.body.decoded, part.charset)
+    parts = if (text_parts = email.all_parts.select {|p| p.mime_type == 'text/plain'}).present?
+              text_parts
+            elsif (html_parts = email.all_parts.select {|p| p.mime_type == 'text/html'}).present?
+              html_parts
+            else
+              [email]
+            end
+
+    parts.reject! do |part|
+      part.header[:content_disposition].try(:disposition_type) == 'attachment'
+    end
+
+    @plain_text_body = parts.map {|p| Redmine::CodesetUtil.to_utf8(p.body.decoded, p.charset)}.join("\r\n")
 
     # strip html tags and remove doctype directive
-    @plain_text_body = strip_tags(@plain_text_body.strip)
-    @plain_text_body.sub! %r{^<!DOCTYPE .*$}, ''
+    if parts.any? {|p| p.mime_type == 'text/html'}
+      @plain_text_body = strip_tags(@plain_text_body.strip)
+      @plain_text_body.sub! %r{^<!DOCTYPE .*$}, ''
+    end
+
     @plain_text_body
   end
 
@@ -396,18 +449,17 @@
     assign_string_attribute_with_limit(user, 'login', email_address, User::LOGIN_LENGTH_LIMIT)
 
     names = fullname.blank? ? email_address.gsub(/@.*$/, '').split('.') : fullname.split
-    assign_string_attribute_with_limit(user, 'firstname', names.shift)
-    assign_string_attribute_with_limit(user, 'lastname', names.join(' '))
+    assign_string_attribute_with_limit(user, 'firstname', names.shift, 30)
+    assign_string_attribute_with_limit(user, 'lastname', names.join(' '), 30)
     user.lastname = '-' if user.lastname.blank?
-
-    password_length = [Setting.password_min_length.to_i, 10].max
-    user.password = Redmine::Utils.random_hex(password_length / 2 + 1)
     user.language = Setting.default_language
+    user.generate_password = true
+    user.mail_notification = 'only_my_events'
 
     unless user.valid?
       user.login = "user#{Redmine::Utils.random_hex(6)}" unless user.errors[:login].blank?
       user.firstname = "-" unless user.errors[:firstname].blank?
-      user.lastname  = "-" unless user.errors[:lastname].blank?
+      (puts user.errors[:lastname];user.lastname  = "-") unless user.errors[:lastname].blank?
     end
 
     user
@@ -423,6 +475,9 @@
     end
     if addr.present?
       user = self.class.new_user_from_attributes(addr, name)
+      if @@handler_options[:no_notification]
+        user.mail_notification = 'none'
+      end
       if user.save
         user
       else
@@ -435,6 +490,19 @@
     end
   end
 
+  # Adds the newly created user to default group
+  def add_user_to_group(default_group)
+    if default_group.present?
+      default_group.split(',').each do |group_name|
+        if group = Group.named(group_name).first
+          group.users << @user
+        elsif logger
+          logger.warn "MailHandler: could not add user to [#{group_name}], group not found"
+        end
+      end
+    end
+  end
+
   # Removes the email body of text after the truncation configurations.
   def cleanup_body(body)
     delimiters = Setting.mail_handler_body_delimiters.to_s.split(/[\r\n]+/).reject(&:blank?).map {|s| Regexp.escape(s)}
@@ -455,7 +523,7 @@
                  }
     if assignee.nil? && keyword.match(/ /)
       firstname, lastname = *(keyword.split) # "First Last Throwaway"
-      assignee ||= assignable.detect {|a| 
+      assignee ||= assignable.detect {|a|
                      a.is_a?(User) && a.firstname.to_s.downcase == firstname &&
                        a.lastname.to_s.downcase == lastname
                    }