diff --git a/app/models/channel/email_build.rb b/app/models/channel/email_build.rb index 8633e952e..800300075 100644 --- a/app/models/channel/email_build.rb +++ b/app/models/channel/email_build.rb @@ -26,9 +26,10 @@ module Channel::EmailBuild # notification if notification - attr['X-Loop'] = 'yes' - attr['Precedence'] = 'bulk' - attr['Auto-Submitted'] = 'auto-generated' + attr['X-Loop'] = 'yes' + attr['Precedence'] = 'bulk' + attr['Auto-Submitted'] = 'auto-generated' + attr['X-Auto-Response-Suppress'] = 'All' end #attr['X-Powered-BY'] = 'Zammad - Support/Helpdesk (http://www.zammad.org/)' diff --git a/app/models/channel/email_parser.rb b/app/models/channel/email_parser.rb index 5662bcdbe..7c87953c5 100644 --- a/app/models/channel/email_parser.rb +++ b/app/models/channel/email_parser.rb @@ -345,6 +345,8 @@ retrns # run postmaster pre filter filters = { '0010' => Channel::Filter::Trusted, + '0020' => Channel::Filter::AutoResponseCheck, + '0030' => Channel::Filter::OutOfOfficeCheck, '0100' => Channel::Filter::FollowUpCheck, '0900' => Channel::Filter::BounceCheck, '1000' => Channel::Filter::Database, @@ -427,8 +429,9 @@ retrns end - if state_type.name != 'new' - ticket.state = Ticket::State.find_by( name: 'open' ) + # set ticket to open again + if state_type.name != 'new' && !mail[ 'x-zammad-out-of-office'.to_sym ] + ticket.state = Ticket::State.find_by(name: 'open') ticket.save end end @@ -515,7 +518,7 @@ retrns } # return new objects - [ticket, article, user] + [ticket, article, user, mail] end def user_create(data) diff --git a/app/models/channel/filter/auto_response_check.rb b/app/models/channel/filter/auto_response_check.rb new file mode 100644 index 000000000..25fd23add --- /dev/null +++ b/app/models/channel/filter/auto_response_check.rb @@ -0,0 +1,18 @@ +# Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/ + +module Channel::Filter::AutoResponseCheck + + def self.run( _channel, mail ) + + # if header is available, do not generate auto response + mail[ 'x-zammad-send-auto-response'.to_sym ] = false + + return if mail[ 'x-loop'.to_sym ] && mail[ 'x-loop'.to_sym ] =~ /(yes|true)/i + return if mail[ 'precedence'.to_sym ] && mail[ 'precedence'.to_sym ] =~ /bulk/i + return if mail[ 'auto-submitted'.to_sym ] && mail[ 'auto-submitted'.to_sym ] =~ /auto-(generated|replied)/i + return if mail[ 'x-auto-response-suppress'.to_sym ] && mail[ 'x-auto-response-suppress'.to_sym ] =~ /all/i + + mail[ 'x-zammad-send-auto-response'.to_sym ] = true + + end +end diff --git a/app/models/channel/filter/out_of_office_check.rb b/app/models/channel/filter/out_of_office_check.rb new file mode 100644 index 000000000..6224caaf2 --- /dev/null +++ b/app/models/channel/filter/out_of_office_check.rb @@ -0,0 +1,17 @@ +# Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/ + +module Channel::Filter::OutOfOfficeCheck + + def self.run( _channel, mail ) + + mail[ 'x-zammad-out-of-office'.to_sym ] = false + + # check ms out of office characteristics + return if !mail[ 'x-auto-response-suppress'.to_sym ] + return if mail[ 'x-auto-response-suppress'.to_sym ] !~ /all/i + return if !mail[ 'x-ms-exchange-inbox-rules-loop'.to_sym ] + + mail[ 'x-zammad-out-of-office'.to_sym ] = true + + end +end diff --git a/lib/email_helper/probe.rb b/lib/email_helper/probe.rb index c9b3d7984..9b9df6efc 100644 --- a/lib/email_helper/probe.rb +++ b/lib/email_helper/probe.rb @@ -298,10 +298,11 @@ returns on fail if subject mail['X-Zammad-Test-Message'] = subject end - mail['X-Zammad-Ignore'] = 'true' - mail['X-Loop'] = 'yes' - mail['Precedence'] = 'bulk' - mail['Auto-Submitted'] = 'auto-generated' + mail['X-Zammad-Ignore'] = 'true' + mail['X-Loop'] = 'yes' + mail['Precedence'] = 'bulk' + mail['Auto-Submitted'] = 'auto-generated' + mail['X-Auto-Response-Suppress'] = 'All' # test connection begin diff --git a/test/unit/email_process_auto_response_test.rb b/test/unit/email_process_auto_response_test.rb new file mode 100644 index 000000000..4e6066458 --- /dev/null +++ b/test/unit/email_process_auto_response_test.rb @@ -0,0 +1,60 @@ +# encoding: utf-8 +require 'test_helper' + +class EmailProcessAutoResponseTest < ActiveSupport::TestCase + + test 'process with out of office check' do + + email_raw_string = "From: me@example.com +To: customer@example.com +Subject: some new subject + +Some Text" + + ticket_p, article_p, user_p, mail = Channel::EmailParser.new.process( {}, email_raw_string) + assert_equal(true, mail['x-zammad-send-auto-response'.to_sym]) + + email_raw_string = "From: me@example.com +To: customer@example.com +Subject: some new subject +X-Loop: yes + +Some Text" + + ticket_p, article_p, user_p, mail = Channel::EmailParser.new.process( {}, email_raw_string) + assert_equal(false, mail['x-zammad-send-auto-response'.to_sym]) + + email_raw_string = "From: me@example.com +To: customer@example.com +Subject: some new subject +Precedence: Bulk + +Some Text" + + ticket_p, article_p, user_p, mail = Channel::EmailParser.new.process( {}, email_raw_string) + assert_equal(false, mail['x-zammad-send-auto-response'.to_sym]) + + email_raw_string = "From: me@example.com +To: customer@example.com +Subject: some new subject +Auto-Submitted: auto-generated + +Some Text" + + ticket_p, article_p, user_p, mail = Channel::EmailParser.new.process( {}, email_raw_string) + assert_equal(false, mail['x-zammad-send-auto-response'.to_sym]) + + email_raw_string = "From: me@example.com +To: customer@example.com +Subject: some new subject +X-Auto-Response-Suppress: All + + +Some Text" + + ticket_p, article_p, user_p, mail = Channel::EmailParser.new.process( {}, email_raw_string) + assert_equal(false, mail['x-zammad-send-auto-response'.to_sym]) + + end + +end diff --git a/test/unit/email_process_bounce_test.rb b/test/unit/email_process_bounce_test.rb new file mode 100644 index 000000000..399b411cd --- /dev/null +++ b/test/unit/email_process_bounce_test.rb @@ -0,0 +1,37 @@ +# encoding: utf-8 +require 'test_helper' + +class EmailProcessBounceTest < ActiveSupport::TestCase + + test 'process with bounce check' do + + ticket = Ticket.create( + title: 'bounce check', + group: Group.lookup( name: 'Users'), + customer_id: 2, + state: Ticket::State.lookup( name: 'new' ), + priority: Ticket::Priority.lookup( name: '2 normal' ), + updated_by_id: 1, + created_by_id: 1, + ) + article = Ticket::Article.create( + ticket_id: ticket.id, + from: 'some_sender@example.com', + to: 'some_recipient@example.com', + subject: 'bounce check', + message_id: '<20150830145601.30.608881@edenhofer.zammad.com>', + body: 'some message bounce check', + internal: false, + sender: Ticket::Article::Sender.where(name: 'Agent').first, + type: Ticket::Article::Type.where(name: 'email').first, + updated_by_id: 1, + created_by_id: 1, + ) + sleep 1 + email_raw_string = IO.read('test/fixtures/mail33-undelivered-mail-returned-to-sender.box') + ticket_p, article_p, user_p = Channel::EmailParser.new.process( {}, email_raw_string) + assert_equal(ticket.id, ticket_p.id) + assert_equal('new', ticket_p.state.name) + end + +end diff --git a/test/unit/email_process_follow_up_test.rb b/test/unit/email_process_follow_up_test.rb new file mode 100644 index 000000000..e2488d0ee --- /dev/null +++ b/test/unit/email_process_follow_up_test.rb @@ -0,0 +1,130 @@ +# encoding: utf-8 +require 'test_helper' + +class EmailProcessFollowUpTest < ActiveSupport::TestCase + + test 'process with follow up check' do + + ticket = Ticket.create( + title: 'follow up check', + group: Group.lookup( name: 'Users'), + customer_id: 2, + state: Ticket::State.lookup( name: 'new' ), + priority: Ticket::Priority.lookup( name: '2 normal' ), + updated_by_id: 1, + created_by_id: 1, + ) + article = Ticket::Article.create( + ticket_id: ticket.id, + from: 'some_sender@example.com', + to: 'some_recipient@example.com', + subject: 'follow up check', + message_id: '<20150830145601.30.608882@edenhofer.zammad.com>', + body: 'some message article', + internal: false, + sender: Ticket::Article::Sender.where(name: 'Agent').first, + type: Ticket::Article::Type.where(name: 'email').first, + updated_by_id: 1, + created_by_id: 1, + ) + + email_raw_string_subject = "From: me@example.com +To: customer@example.com +Subject: #{ticket.subject_build('some new subject')} + +Some Text" + + email_raw_string_body = "From: me@example.com +To: customer@example.com +Subject: no reference + +Some Text #{ticket.subject_build('some new subject')} " + + email_raw_string_attachment = "From: me@example.com +Content-Type: multipart/mixed; boundary=\"Apple-Mail=_ED77AC8D-FB6F-40E5-8FBE-D41FF5E1BAF2\" +Subject: no reference +Date: Sun, 30 Aug 2015 23:20:54 +0200 +To: Martin Edenhofer +Mime-Version: 1.0 (Mac OS X Mail 8.2 \(2104\)) +X-Mailer: Apple Mail (2.2104) + + +--Apple-Mail=_ED77AC8D-FB6F-40E5-8FBE-D41FF5E1BAF2 +Content-Transfer-Encoding: 7bit +Content-Type: text/plain; + charset=us-ascii + +no reference +--Apple-Mail=_ED77AC8D-FB6F-40E5-8FBE-D41FF5E1BAF2 +Content-Disposition: attachment; + filename=test1.txt +Content-Type: text/plain; + name=\"test.txt\" +Content-Transfer-Encoding: 7bit + +Some Text #{ticket.subject_build('some new subject')} + +--Apple-Mail=_ED77AC8D-FB6F-40E5-8FBE-D41FF5E1BAF2--" + + email_raw_string_references1 = "From: me@example.com +To: customer@example.com +Subject: no reference +In-Reply-To: <20150830145601.30.608882@edenhofer.zammad.com> +References: + +no reference " + + email_raw_string_references2 = "From: me@example.com +To: customer@example.com +Subject: no reference +References: <20150830145601.30.608882@edenhofer.zammad.com> + +no reference " + + setting_orig = Setting.get('postmaster_follow_up_search_in') + Setting.set('postmaster_follow_up_search_in', %w(body attachment references)) + + sleep 1 + ticket_p, article_p, user_p = Channel::EmailParser.new.process( {}, email_raw_string_subject) + assert_equal(ticket.id, ticket_p.id) + + sleep 1 + ticket_p, article_p, user_p = Channel::EmailParser.new.process( {}, email_raw_string_body) + assert_equal(ticket.id, ticket_p.id) + + sleep 1 + ticket_p, article_p, user_p = Channel::EmailParser.new.process( {}, email_raw_string_attachment) + assert_equal(ticket.id, ticket_p.id) + + sleep 1 + ticket_p, article_p, user_p = Channel::EmailParser.new.process( {}, email_raw_string_references1) + assert_equal(ticket.id, ticket_p.id) + + sleep 1 + ticket_p, article_p, user_p = Channel::EmailParser.new.process( {}, email_raw_string_references2) + assert_equal(ticket.id, ticket_p.id) + + Setting.set('postmaster_follow_up_search_in', setting_orig) + + sleep 1 + ticket_p, article_p, user_p = Channel::EmailParser.new.process( {}, email_raw_string_subject) + assert_equal(ticket.id, ticket_p.id) + + sleep 1 + ticket_p, article_p, user_p = Channel::EmailParser.new.process( {}, email_raw_string_body) + assert_not_equal(ticket.id, ticket_p.id) + + sleep 1 + ticket_p, article_p, user_p = Channel::EmailParser.new.process( {}, email_raw_string_attachment) + assert_not_equal(ticket.id, ticket_p.id) + + sleep 1 + ticket_p, article_p, user_p = Channel::EmailParser.new.process( {}, email_raw_string_references1) + assert_not_equal(ticket.id, ticket_p.id) + + sleep 1 + ticket_p, article_p, user_p = Channel::EmailParser.new.process( {}, email_raw_string_references2) + assert_not_equal(ticket.id, ticket_p.id) + end + +end diff --git a/test/unit/email_process_out_of_office_test.rb b/test/unit/email_process_out_of_office_test.rb new file mode 100644 index 000000000..d406924b1 --- /dev/null +++ b/test/unit/email_process_out_of_office_test.rb @@ -0,0 +1,92 @@ +# encoding: utf-8 +require 'test_helper' + +class EmailProcessOutOfOfficeTest < ActiveSupport::TestCase + + test 'process with out of office check' do + + ticket = Ticket.create( + title: 'ooo check', + group: Group.lookup( name: 'Users'), + customer_id: 2, + state: Ticket::State.lookup( name: 'closed' ), + priority: Ticket::Priority.lookup( name: '2 normal' ), + updated_by_id: 1, + created_by_id: 1, + ) + article = Ticket::Article.create( + ticket_id: ticket.id, + from: 'some_sender@example.com', + to: 'some_recipient@example.com', + subject: 'ooo check', + message_id: '<20150830145601.30.608881@edenhofer.zammad.com>', + body: 'some message bounce check', + internal: false, + sender: Ticket::Article::Sender.where(name: 'Agent').first, + type: Ticket::Article::Type.where(name: 'email').first, + updated_by_id: 1, + created_by_id: 1, + ) + sleep 1 + + # exchange out of office example #1 + email_raw_string = "From: me@example.com +To: customer@example.com +Subject: #{ticket.subject_build('some new subject 1')} +X-MS-Has-Attach: +X-Auto-Response-Suppress: All +X-MS-Exchange-Inbox-Rules-Loop: aaa.bbb@example.com +X-MS-TNEF-Correlator: +x-olx-disclaimer: Done +x-tm-as-product-ver: SMEX-11.0.0.4179-8.000.1202-21706.006 +x-tm-as-result: No--39.689200-0.000000-31 +x-tm-as-user-approved-sender: Yes +x-tm-as-user-blocked-sender: No + +Some Text" + + ticket_p, article_p, user_p, mail = Channel::EmailParser.new.process( {}, email_raw_string) + assert_equal(true, mail['x-zammad-out-of-office'.to_sym]) + ticket = Ticket.find(ticket.id) + assert_equal(ticket.id, ticket_p.id) + assert_equal('closed', ticket.state.name) + + # normal follow up + email_raw_string = "From: me@example.com +To: customer@example.com +Subject: #{ticket.subject_build('some new subject - none')} +X-MS-Exchange-Inbox-Rules-Loop: aaa.bbb@example.com + +Some Text 2" + + ticket_p, article_p, user_p, mail = Channel::EmailParser.new.process( {}, email_raw_string) + assert_equal(false, mail['x-zammad-out-of-office'.to_sym]) + ticket = Ticket.find(ticket.id) + assert_equal(ticket.id, ticket_p.id) + assert_equal('open', ticket_p.state.name) + + ticket = Ticket.find(ticket.id) + ticket.state = Ticket::State.lookup(name: 'closed') + ticket.save + + # exchange out of office example #2 + email_raw_string = "From: me@example.com +To: customer@example.com +Subject: #{ticket.subject_build('some new subject 2')} +X-MS-Has-Attach: +X-Auto-Response-Suppress: All +X-MS-Exchange-Inbox-Rules-Loop: aaa.bbb@example.com +X-MS-TNEF-Correlator: +x-exclaimer-md-config: 8c10826d-4052-4c5c-a8e8-e09011276827 + +Some Text" + + ticket_p, article_p, user_p, mail = Channel::EmailParser.new.process( {}, email_raw_string) + assert_equal(true, mail['x-zammad-out-of-office'.to_sym]) + ticket = Ticket.find(ticket.id) + assert_equal(ticket.id, ticket_p.id) + assert_equal('closed', ticket.state.name) + + end + +end diff --git a/test/unit/email_process_test.rb b/test/unit/email_process_test.rb index 1dcdd8fab..8a6090290 100644 --- a/test/unit/email_process_test.rb +++ b/test/unit/email_process_test.rb @@ -2026,262 +2026,6 @@ Some Text', process(files) end - test 'process with bounce check' do - - ticket = Ticket.create( - title: 'bounce check', - group: Group.lookup( name: 'Users'), - customer_id: 2, - state: Ticket::State.lookup( name: 'new' ), - priority: Ticket::Priority.lookup( name: '2 normal' ), - updated_by_id: 1, - created_by_id: 1, - ) - article = Ticket::Article.create( - ticket_id: ticket.id, - from: 'some_sender@example.com', - to: 'some_recipient@example.com', - subject: 'bounce check', - message_id: '<20150830145601.30.608881@edenhofer.zammad.com>', - body: 'some message bounce check', - internal: false, - sender: Ticket::Article::Sender.where(name: 'Agent').first, - type: Ticket::Article::Type.where(name: 'email').first, - updated_by_id: 1, - created_by_id: 1, - ) - sleep 1 - email_raw_string = IO.read('test/fixtures/mail33-undelivered-mail-returned-to-sender.box') - ticket_p, article_p, user_p = Channel::EmailParser.new.process( {}, email_raw_string) - assert_equal(ticket.id, ticket_p.id) - end - - test 'process with follow up check' do - - ticket = Ticket.create( - title: 'follow up check', - group: Group.lookup( name: 'Users'), - customer_id: 2, - state: Ticket::State.lookup( name: 'new' ), - priority: Ticket::Priority.lookup( name: '2 normal' ), - updated_by_id: 1, - created_by_id: 1, - ) - article = Ticket::Article.create( - ticket_id: ticket.id, - from: 'some_sender@example.com', - to: 'some_recipient@example.com', - subject: 'follow up check', - message_id: '<20150830145601.30.608882@edenhofer.zammad.com>', - body: 'some message article', - internal: false, - sender: Ticket::Article::Sender.where(name: 'Agent').first, - type: Ticket::Article::Type.where(name: 'email').first, - updated_by_id: 1, - created_by_id: 1, - ) - - email_raw_string_subject = "From: me@example.com -To: customer@example.com -Subject: #{ticket.subject_build('some new subject')} - -Some Text" - - email_raw_string_body = "From: me@example.com -To: customer@example.com -Subject: no reference - -Some Text #{ticket.subject_build('some new subject')} " - - email_raw_string_attachment = "From: me@example.com -Content-Type: multipart/mixed; boundary=\"Apple-Mail=_ED77AC8D-FB6F-40E5-8FBE-D41FF5E1BAF2\" -Subject: no reference -Date: Sun, 30 Aug 2015 23:20:54 +0200 -To: Martin Edenhofer -Mime-Version: 1.0 (Mac OS X Mail 8.2 \(2104\)) -X-Mailer: Apple Mail (2.2104) - - ---Apple-Mail=_ED77AC8D-FB6F-40E5-8FBE-D41FF5E1BAF2 -Content-Transfer-Encoding: 7bit -Content-Type: text/plain; - charset=us-ascii - -no reference ---Apple-Mail=_ED77AC8D-FB6F-40E5-8FBE-D41FF5E1BAF2 -Content-Disposition: attachment; - filename=test1.txt -Content-Type: text/plain; - name=\"test.txt\" -Content-Transfer-Encoding: 7bit - -Some Text #{ticket.subject_build('some new subject')} - ---Apple-Mail=_ED77AC8D-FB6F-40E5-8FBE-D41FF5E1BAF2--" - - email_raw_string_references1 = "From: me@example.com -To: customer@example.com -Subject: no reference -In-Reply-To: <20150830145601.30.608882@edenhofer.zammad.com> -References: - -no reference " - - email_raw_string_references2 = "From: me@example.com -To: customer@example.com -Subject: no reference -References: <20150830145601.30.608882@edenhofer.zammad.com> - -no reference " - - setting_orig = Setting.get('postmaster_follow_up_search_in') - Setting.set('postmaster_follow_up_search_in', ['body', 'attachment', 'references']) - - sleep 1 - ticket_p, article_p, user_p = Channel::EmailParser.new.process( {}, email_raw_string_subject) - assert_equal(ticket.id, ticket_p.id) - - sleep 1 - ticket_p, article_p, user_p = Channel::EmailParser.new.process( {}, email_raw_string_body) - assert_equal(ticket.id, ticket_p.id) - - sleep 1 - ticket_p, article_p, user_p = Channel::EmailParser.new.process( {}, email_raw_string_attachment) - assert_equal(ticket.id, ticket_p.id) - - sleep 1 - ticket_p, article_p, user_p = Channel::EmailParser.new.process( {}, email_raw_string_references1) - assert_equal(ticket.id, ticket_p.id) - - sleep 1 - ticket_p, article_p, user_p = Channel::EmailParser.new.process( {}, email_raw_string_references2) - assert_equal(ticket.id, ticket_p.id) - - Setting.set('postmaster_follow_up_search_in', setting_orig) - - sleep 1 - ticket_p, article_p, user_p = Channel::EmailParser.new.process( {}, email_raw_string_subject) - assert_equal(ticket.id, ticket_p.id) - - sleep 1 - ticket_p, article_p, user_p = Channel::EmailParser.new.process( {}, email_raw_string_body) - assert_not_equal(ticket.id, ticket_p.id) - - sleep 1 - ticket_p, article_p, user_p = Channel::EmailParser.new.process( {}, email_raw_string_attachment) - assert_not_equal(ticket.id, ticket_p.id) - - sleep 1 - ticket_p, article_p, user_p = Channel::EmailParser.new.process( {}, email_raw_string_references1) - assert_not_equal(ticket.id, ticket_p.id) - - sleep 1 - ticket_p, article_p, user_p = Channel::EmailParser.new.process( {}, email_raw_string_references2) - assert_not_equal(ticket.id, ticket_p.id) - end - - test 'process with postmaster filter' do - group1 = Group.create_if_not_exists( - name: 'Test Group1', - created_by_id: 1, - updated_by_id: 1, - ) - group2 = Group.create_if_not_exists( - name: 'Test Group2', - created_by_id: 1, - updated_by_id: 1, - ) - PostmasterFilter.destroy_all - PostmasterFilter.create( - name: 'not used', - match: { - from: 'nobody@example.com', - }, - perform: { - 'X-Zammad-Ticket-priority' => '3 high', - }, - channel: 'email', - active: true, - created_by_id: 1, - updated_by_id: 1, - ) - PostmasterFilter.create( - name: 'used', - match: { - from: 'me@example.com', - }, - perform: { - 'X-Zammad-Ticket-group_id' => group1.id, - 'x-Zammad-Article-Internal' => true, - }, - channel: 'email', - active: true, - created_by_id: 1, - updated_by_id: 1, - ) - PostmasterFilter.create( - name: 'used x-any-recipient', - match: { - 'x-any-recipient' => 'any@example.com', - }, - perform: { - 'X-Zammad-Ticket-group_id' => group2.id, - 'x-Zammad-Article-Internal' => true, - }, - channel: 'email', - active: true, - created_by_id: 1, - updated_by_id: 1, - ) - files = [ - { - data: 'From: me@example.com -To: customer@example.com -Subject: some subject - -Some Text', - trusted: false, - success: true, - result: { - 0 => { - group: group1.name, - priority: '2 normal', - title: 'some subject', - }, - 1 => { - sender: 'Customer', - type: 'email', - internal: true, - }, - }, - }, - { - data: 'From: Some Body -To: Bob -Cc: any@example.com -Subject: some subject - -Some Text', - trusted: false, - success: true, - result: { - 0 => { - group: group2.name, - priority: '2 normal', - title: 'some subject', - }, - 1 => { - sender: 'Customer', - type: 'email', - internal: true, - }, - }, - }, - ] - process(files) - PostmasterFilter.destroy_all - end - def process(files) files.each { |file| parser = Channel::EmailParser.new